How to preload a AVPlayerItem - ios

I understand that when using a AVQueuePlayer to play a list of AVPlayerItem objects the player preload the next item in queue for faster reload when user get to this item.
The problem is that I need to have more control of which items preload, for example I want that the 3 next song and the 2 previous song be preloaded and prepare for fast loading.
So I think to manage the AVPlayerItem objects myself, I just not sure how do I preload a AVPlayerItem?
How can I preload the first 30 seconds for example?

Controlling the preload of the assets is a different problem than controlling the previous items. As soon as the AVQueuePlayer finishes an item, it'll release it from memory along with its cache. To get around that, you can add the AVPlayerItems into an array at the same time you add them into the player. This way once the Player removes the item, ARC will know there's still a reference to that object and not release it. Then you can simply put the item from the array back into the player and all the content will be loaded already. NOTE: you may want to limit the size of this cache array otherwise it will grow indefinitely if items are never removed from it.
If you want to have more control over the preloading, you can do so by loading the asset asynchronously and then creating the AVPlayerItem:
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL URLWithString:url] options:nil];
NSArray *keys = #[#"playable", #"tracks",#"duration" ];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^()
{
// make sure everything downloaded properly
for (NSString *thisKey in keys) {
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
if (keyStatus == AVKeyValueStatusFailed) {
return ;
}
}
AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:asset];
dispatch_async(dispatch_get_main_queue(), ^ {
[player insertItem:item afterItem:nil];
});
}];

Related

Caching Video iOS

AVAsset *asset = [AVAsset assetWithUrl:#"myUrl.mp4"];
AVPlayerItem * item = [AVPlayerItem itemWithAsset:asset];
AVPlayer * player = [AVPlayer playerWithItem:item];
playerViewController.player = player;
[player play];
How Can i know that asset or item or whatever downloaded video fully.
I need it for future caching it;
Now im using
NSData * videoData = [NSData dataWithUrl:myurl.mp4"];
NSCache *cache = [NSCache new];
[cache setObject:videoData forKey:myUrl];
And when i retrieve data from nscache i write it to file and play
NSData *videoData = [cache objectForKey:myUrl];
[videoData writeToFile:MyPath.mp4 atomically:YES];
And then
NSURL *url = [NSURL fileUrlWithPath:mypath];
AVAsset *asset = =[AVAsset assetWithURL:url];
..etc and play
But it a little bit slow.
if videoData contains in AVAsset , i can store it or AVPlayerItem .But i need to know when it downloaded.
How can i implement caching in a different way or upgrade this one. Help.
we can download any data from cloud using NSURLSession, Download in progress and completion NSURLSession fires delegate methods , we can know download completion events and and progression events by using that delegate methods, you can implement downloading functionality by using NSURLSession as follows
https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started
I have an app with vertical collection view of video items, each item should repeat itself continuously, until swiped up or down.
So I need not to stream file every time and not download files that were downloaded earlier.
I came up with slightly different approach:
Stream AVURLAsset,
When reached end I use AVAssetExportSession to save file to disk,
Then I create new AVPLayerItem with new AVAsset using saved file,
AVPlayer's replaceCurrentItem(with: <#T##AVPlayerItem?#>)
But this is lame, I guess. I have to steam file two times. First time on first run, and then then when saving file. I'd like to just cache AVAsset into memory for the current application session, so I don't need to keep files.

MP3 Queue Player - Load in background thread?

I have an AVQueuePlayer that is used to play a list of MP3 songs from the internet (http). I need to also know which song is currently playing. The current problem is that loading the song causes a delay that blocks the main thread while waiting for the song to load (first song as well as sequential songs after the first has completed playback).
The following code blocks the main thread:
queuePlayer = [[AVQueuePlayer alloc] init];
[queuePlayer insertItem: [AVPlayerItem playerItemWithURL:url] afterItem: nil]; // etc.
[queuePlayer play]
I am looking for a way to create a playlist of MP3s where the next file to be played back is preloaded in the background.
I tried the following code:
NSArray* tracks = [NSArray arrayWithObjects:#"http://example.com/song1.mp3", #"http://example.com/song2.mp3", #"http://example.com/song3.mp3", nil];
for (NSString* trackName in tracks)
{
AVURLAsset* audioAsset = [[AVURLAsset alloc]initWithURL:[NSURL URLWithString:trackName]
options:nil];
AVMutableCompositionTrack* audioTrack = [_composition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
NSError* error;
[audioTrack insertTimeRange:CMTimeRangeMake([_composition duration], audioAsset.duration)
ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0]
atTime:kCMTimeZero
error:&error];
if (error)
{
NSLog(#"%#", [error localizedDescription]);
}
// Store the track IDs as track name -> track ID
[_audioMixTrackIDs setValue:[NSNumber numberWithInteger:audioTrack.trackID]
forKey:trackName];
}
_player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
[_player play];
The issue with this is that I am not sure how to detect when the next song starts playing. Also, the docs don't specify whether or not this will pre-load MP3 files or not.
I am looking for a solution that:
Plays MP3s by pre-loading them in the background prior to playback (ideally start loading the next song before the current song finishes, so it is ready for immediate playback once the current song finishes)
Allow me to view the current song playing.
AVFoundation has some classes designed to do exactly what you're looking for.
It looks like your current solution is to build a single AVPlayerItem that concatenates all of the MP3 files that you want to play. A better solution is to create an AVQueuePlayer with an array of the AVPlayerItem objects that you want to play.
NSArray* tracks = [NSArray arrayWithObjects:#"http://example.com/song1.mp3", #"http://example.com/song2.mp3", #"http://example.com/song3.mp3", nil];
NSMutableArray *playerItems = [[NSMutableArray alloc] init];
for (NSString* trackName in tracks)
{
NSURL *assetURL = [NSURL URLWithString:trackName];
if (!assetURL) {
continue;
}
AVURLAsset* audioAsset = [[AVURLAsset alloc] initWithURL:assetURL
options:nil];
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:audioAsset];
[playerItems addObject:playerItem];
}
_player = [[AVQueuePlayer alloc] initWithItems:playerItems];
[_player play];
In answer to your final wrap-up questions:
Yes, AVQueuePlayer DOES preload the next item in the playlist while it's playing the current one.
You can access the currentItem property to determine which AVPlayerItem is currently playing.

How to save an array of AVPlayerItems to file or nsuserdefaults

I am currently trying to figure out a way to create a playlist from an array of avplayeritems and save the array to the nssuserdefaults but it wont work.
here is some code
AVAsset *asset = [AVAsset assetWithURL:_videoURL];
AAPLPlayerViewController __weak *weakSelf = self;
NSArray *oldItemsArray = [weakSelf.player items];
AVPlayerItem *newPlayerItem = [AVPlayerItem playerItemWithAsset:asset];
[weakSelf.player insertItem:newPlayerItem afterItem:nil];
[weakSelf queueDidChangeFromArray:oldItemsArray toArray:[self.player items]];
i am trying to create a playlist and store the AVPlayerItems from the queue of the AVQueuePlayer but since it has complex data that includes the AVPlayerItem and AVAsset it will crash when i try to save the [self.player items];
Ive also tried to store it to a Mutable dictionary but no methods ive tried have worked. Can anyone with experience suggest a good method of creating a playlist and storing it for the AVQueuePlayer?
I am using the AVFoundationQueuePlayer Objective C sample from the IOS developer downloads at this link :
https://developer.apple.com/library/ios/samplecode/AVFoundationQueuePlayer-iOS/Introduction/Intro.html#//apple_ref/doc/uid/TP40016104

How to release the NSDATA from AVAudioPlayer?

Here I used to play the songs using AVAudioPlayer Framework. Hopefully it's playing, but if I click on other songs from the Songslist I will check the condition :
if(player.isPlaying == YES)
{
[player stop];
// And also i need to release or remove the existing NSDATA from the Player. otherwise the player won't release the existing data. So the memory pressure occurring in my project.
}
self.audioPlayer = [self.audioPlayer initWithData:m_currentMusic.fileData error:&error];
[self.audioPlayer prepareToPlay];
[self.audioPlayer play];
AVAudioPlayer#data is a read-only non-retained property. So, the data is not retained and you have no need to releease it (it's assigned as by default).
However, as I said, it's read-only so you can't set it if you wish to change tracks. You also shouldn't call init... on an already initialied object, as in your code:
self.audioPlayer=[self.audioPlayer initWithData:m_currentMusic.fileData error:&error];
I believe the intended action for what you want is to relesae your existing self.audioPlayer and re-create a new one, as follows:
// stop player as in your original code...
[self.audioPlayer release];
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithData:m_currentMusic.fileData error:&error];
self.audioPlayer = newPlayer;
[self.audioPlayer setDelegate:self];
[newPlayer release];
Note that the release will cause your original track data to be reclaimed (as it's non-retained).
See also Core Audio Overview

Load two AVPlayers with one video

I have two different views that are meant to play the same video, I am creating an app that will switch several times between the two views while the video is running.
I currently load the first view with the video as follows:
NSURL *url = [NSURL URLWithString:#"http://[URL TO VIDEO HERE]"];
AVURLAsset *avasset = [[AVURLAsset alloc] initWithURL:url options:nil];
AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:avasset];
player = [[AVPlayer alloc] initWithPlayerItem:item];
playerLayer = [[AVPlayerLayer playerLayerWithPlayer:player] retain];
CGSize size = self.bounds.size;
float x = size.width/2.0-202.0;
float y = size.height/2.0 - 100;
//[player play];
playerLayer.frame = CGRectMake(x, y, 404, 200);
playerLayer.backgroundColor = [UIColor blackColor].CGColor;
[self.layer addSublayer:playerLayer];
NSString *tracksKey = #"tracks";
[avasset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:tracksKey] completionHandler:
^{
dispatch_async(dispatch_get_main_queue(),
^{
NSError *error = nil;
AVKeyValueStatus status = [avasset statusOfValueForKey:tracksKey error:&error];
if (status == AVKeyValueStatusLoaded) {
//videoInitialized = YES;
[player play];
}
else {
// You should deal with the error appropriately.
NSLog(#"The asset's tracks were not loaded:\n%#", [error localizedDescription]);
}
});
}];
In my second view I want to load the video from the dispatch_get_main_queue so that the video in both views are in sync.
I was hoping someone could help me out with loading the data of the video from the first view into the second view.
It is very simple:
Init the first player:
AVAsset *asset = [AVAsset assetWithURL:URL];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
And the second player in the same way, BUT, use the same asset from the first one.
I have verified, it works.
There is all the info you need on the Apple page:
https://developer.apple.com/library/mac/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/02_Playback.html
This abstraction means that you can play a given asset using different
players simultaneously
this quote is from this page.
I don't think you will be able to get this approach to work. Videos are decoded in hardware and then the graphics buffer is sent to the graphics card. What you seem to want to do is decode a video in one view but then capture the contents of the first view and show it in a second view. That will not stay in sync because it would take time to capture the contents of the first window back into main memory and then those contents would need to be sent to the video card again. Basically, that is not going to work. You also cannot decode two h.264 videos streams and expect them to be in sync.
You could implement this with another approach entirely. If you decode the h.264 video to frames on disk (save each frame as a PNG) and then write your own loop that will decode the Nth PNG in a series of PNGs and then display the results in the two different windows. That will work fast enough to be an effective implementation on newer iPhone 4 and 5 and iPad 2 and 3. If you want to make use of a more advanced implementation, take a look at my AVAnimator library for iOS, you could get this approach working in 20 minutes if you use existing code.
For this ten year old question which has only ten year old answers which are out of date, here's the up to date answer.
var leadPlayer: AVPlayer ... the lead player you want to dupe
This does not work:
let leadPlayerItem: AVPlayerItem = leadPlayer.currentItem!
yourPlayer = AVPlayer(playerItem: leadPlayerItem)
yourPlayer.play()
Apple does not allow that (try it, see error).
This works. You must use the item:
let dupeItem: AVPlayerItem = AVPlayerItem(asset: leadPlayer.currentItem!.asset)
yourPlayer = AVPlayer(playerItem: dupeItem)
yourPlayer.play()
Fortunately it's now that easy.

Resources