AVQueuePlayer playback without gap and freeze - ios

I use AVQueuePlayer to play a sequence of movies which are loaded from URLs.
I tried to initialize player instance with array of all AVPlayerItems that I need to play.
player = [[AVQueuePlayer queuePlayerWithItems:playerItemsArray]
But in this case AVQueuePlayer loads some initial part of each AVPlayerItem before starting playback. It causes frustrating freeze and application doesn't respond for some seconds.
There is possibility to add only first AVPLayerItem to player's queue, observe its state and add second item in queue only when first will reach end, but in this case there will be a gap between playback of two items caused by initializing and buffering of second AVPlayerItem.
Is there any way to organize gapless playback of several videos without a freeze?
Should I use some other player for this purposes?
Thanks in advance.

The solution is found.
When adding new AVPlayerItem in queue of AVQueuePlayer player will synchronously wait till initial part of player item will be buffered.
So in this case player item should be buffered asynchronously and after that it can be added in the queue. It can be done using [AVURLAsset loadValuesAsynchronouslyForKeys: completionHandler:]
For example:
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = [NSArray arrayWithObject:#"playable"];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^()
{
dispatch_async(dispatch_get_main_queue(), ^
{
AVPlayerItem *playerItem = [[[AVPlayerItem alloc] initWithAsset:asset] autorelease];
[player insertItem:playerItem afterItem:nil];
});
}];
Using this solution queue of AVQueuePlayer can be populated with items without any gaps and freezes.

in Swift 2, working here:
func load() {
let player = AVQueuePlayer()
for url in urls {
makeItem(url)
}
}
func makeItem(url: String) {
let avAsset = AVURLAsset(URL: NSURL(string: url)!)
avAsset.loadValuesAsynchronouslyForKeys(["playable", "tracks", "duration"], completionHandler: {
dispatch_async(dispatch_get_main_queue(), {
self.enqueue(avAsset: avAsset)
})
})
}
func enqueue(avAsset: AVURLAsset) {
let item = AVPlayerItem(asset: avAsset)
self.player.insertItem(item, afterItem: nil)
}

Here is solution.
- (void)_makePlayer{
_player = [[AVQueuePlayer alloc] initWithPlayerItem:[AVPlayerItem playerItemWithAsset:[SSMoviePreviewItemMaker generateAVMovieItem]]];
}
+ (AVAsset *)generateAVMovieItem{
NSArray * array = [SSMovieFileManager getAllMovieResourceURL];
AVMutableComposition *composition = [[AVMutableComposition alloc] init];
for (int i = 0; i < array.count; i++) {
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:array[i] options:nil];
[composition insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)
ofAsset:asset
atTime:composition.duration error:nil];
}
return composition;
}

Related

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.

AVURLAsset load webvtt file

I am trying to use AVURLAsset to load a webvtt file.
Below is my code.
NSString *urlAddress = #"http://somewhere/some.vtt";
NSURL *urlStream = [[NSURL alloc] initWithString:urlAddress];
AVAsset *avAsset = [AVURLAsset URLAssetWithURL:urlStream options:nil];
NSArray *requestKeys = [NSArray arrayWithObjects:#"tracks",#"playable",nil];
[avAsset loadValuesAsynchronouslyForKeys:requestKeys completionHandler:^{
dispatch_async(dispatch_get_main_queue(),^{
//complete block here
AVKeyValueStatus status =[avAsset statusOfValueForKey:#"tracks" error:nil];
if(status == AVKeyValueStatusLoaded) {
//loaded block !
//Question 1
CMTime assetTime = [avAsset duration];
Float64 duration = CMTimeGetSeconds(assetTime);
NSLog(#"%f", duration);
//Question 2
AVMediaSelectionGroup *subtitle = [avAsset mediaSelectionGroupForMediaCharacteristic: AVMediaCharacteristicLegible];
NSLog(#"%#", subtitle);
}
else {
//don’t load block !
}
});
}];
Question 1: It always go into the "Loaded Block", but I find the avAsset's duration is not complete, that means the data is not loaded? How should I modify it?
Question 2: I am trying to use it to my avplayer's subtitle, but the AVMediaSelectionGroup is always null. What should I do?
For question 1, add duration to your keys:
NSArray *requestKeys = #[#"tracks",#"playable", #"duration"];
I've posted a solution over here: https://stackoverflow.com/a/37945178/171933 Basically you need to use an AVMutableComposition to join the video with the subtitles and then play back that composition.
About your second question: mediaSelectionGroupForMediaCharacteristic seems to only be supported when these "characteristics" are already baked into either the media file or your m3u8 stream, according to this statement by an Apple engineer (bottom of the page).

Unable to play music from using avplayer in iOS?

I am uploading music to server by converting it to NSData. When i upload the file to server then i also save it to document directory.So when i pass the url of any other music file other than i uploaded then it plays music.But when i try to play music file which i uploaded then it does not play the music.Below is the code for that.
Code for play music
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:fileURL];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
player = [AVPlayer playerWithURL:fileURL];
[player play];
it may help you
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileURL options:nil];
NSArray *keys = [NSArray arrayWithObject:[NSString stringWithFormat:#"%d",self.indexValue]];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:
^{
dispatch_async( dispatch_get_main_queue(),
^{
/* IMPORTANT: Must dispatch to main queue in order to operate on the AVPlayer and AVPlayerItem. */
if (![[keys objectAtIndex:0] isEqual:[NSString stringWithFormat:#"%d",self.indexValue]])
{
}
// NSLog(#"&______________________________________________NOreturned");
playerItem = [AVPlayerItem playerItemWithAsset:asset];
if (!player)
{
player=nil;
}
player=[AVPlayer playerWithPlayerItem:playerItem];
[player play];//: Playground - noun: a place where people can play
});
}];
}
There is a redundant reinitialization for the player object.
Do not forget to subscribe for notifications, for instance :
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:nil];
As it was said in comments, download via browser the file using its URL and try to play by regular player.
Or create asset, and asks about it's tracks, duration, playable state.
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_videoURL options:nil];
NSArray *requestedKeys = #[#"tracks",#"playable",#"duration"];
__weak typeof(self) wSelf = self;
[asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
^{
for (NSString *thisKey in requestedKeys) {
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
if (keyStatus == AVKeyValueStatusFailed) {
//Failed here
//return
}
}
if (!asset.isPlayable) {
//not playable....
}
}
I advice you use AVAudioPlayer for playing mp3. And I show you my two swift examples.
The first example when AVAudioPlayer plays mp3 file from NSData
var audioPlayer = AVAudioPlayer()
func playFromCore() {
let object = fetchedResultsController.fetchedObjects!.first as! Song
let file = object.file // This is NSData
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
audioPlayer = AVAudioPlayer(data: file, fileTypeHint: AVFileTypeMPEGLayer3, error: nil)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
The second example when AVAudioPlayer plays NSURL
func playMusic() {
let url = NSBundle.mainBundle().URLForResource("89", withExtension: "mp3")!
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
audioPlayer = AVAudioPlayer(contentsOfURL: url, error: nil)
audioPlayer.prepareToPlay()
audioPlayer.play()
}

What is the best way to play video in UITableViewCell

I'm trying to make auto-play video in UITableViewCell depending on cell position.
I'm using the AVPlayer
Here is my code:
__weak typeof(self)this = self;
NSString* videoPath = #"http://test.com/test.mp4";
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL URLWithString:videoPath] options:nil];
NSArray* keys = [NSArray arrayWithObjects:#"playable",nil];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^(){
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithAsset:asset];
this.avPlayer = [AVPlayer playerWithPlayerItem:playerItem];
this.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:this.avPlayer];
this.avPlayerLayer.frame = _videoContent.frame;
dispatch_async(dispatch_get_main_queue(),^{
[this.videoContent.layer addSublayer:this.avPlayerLayer];
[this.avPlayer play];
});
}];
But my UITableView is frozen when i scroll the table.
I think there are many time-consuming work but most biggest thing is
[this.avPlayer play]
So my question is that AVPlayer is the best way in this situation?
And is there any way to improve the performance?
Are you sure that creating the AVPlayerItem, AVPlayer, and AVPlayerLayer can all be performed off the main thread? You might want to try putting those inside the block that dispatches on the main queue.
Use the below link , this suits for your question.
https://github.com/PRX/PRXPlayer

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