Application design for retrieving multiple deleted videos and using AVQueuePlayer

I wrote an application for the iPhone and iPad, which should be broadcast, for example. 4 videos over HTTP. Each video should be requested as the previous one starts, so I cannot ask them all at the beginning. The exception is that I can request a second video (let it name its contents) at the beginning. Letโ€™s name other video ads.

I have a URL for each ad or video that returns some XML description containing the URL of the actual content. (This is actually JSON in the case of the main video, but it doesn't matter.)

When the XML returns, I find the content URL and create an AVPlayerItem for it. When the first ad returns, I do an AVQueuePlayer with it. If the main video has already returned by this time, I create an AVPlayerItem for it and insert it into the queue player after the first announcement.

Two formats are available for advertising and video, MP4 and 3GP. I choose 3GP if the device is not connected to Wi-Fi, or it is a low-end device (examples of iPhone 3G and iPad of the 2nd generation.)

Then I observe various things on the player and any items that I created. First, the current item changes:

-(void)playerCurrentItemChanged:(NSString*)aPath ofPlayer:(AVQueuePlayer*)aQueuePlayer change:(NSDictionary*)aChange { if ([aPath isEqualToString:kCurrentItemKey]) { if (!_quitting) { [self performSelectorOnMainThread:@selector(queuePlayerCurrentItemChanged) withObject:nil waitUntilDone:NO]; } } } 

Note. I am calling another method in the main thread because the docs say that the non-atomic properties of AVPlayer should be used this way.

This method is as follows:

 -(void)queuePlayerCurrentItemChanged { NSAssert([NSThread isMainThread], @"FIX ME! VideoController method called using a thread other than main!"); AVPlayerItem* playerItem = _queuePlayer.currentItem; if (playerItem) { VideoItem* videoItem = [self findVideoItemFromPlayerItem:playerItem]; [self getReadyToPlay:videoItem]; // don't continue to FF if it an ad if (videoItem.isAd && (_queuePlayer.rate > 1.0)) { _queuePlayer.rate = 1.0; } } else { NSLog(@"queuePlayerCurrentItemChanged to nil!!!"); } } 

VideoItem is my own class that wraps AVPlayerItem and adds extra data that I need to track.

In principle, getReadyToPlay customizes the user interface, depending on whether it is an ad (not allowed by FF) or the main video. The code also stops FFing if we go to the declaration.

I also monitor the status of any object that was created. The following method is also called in the main thread if the status has become reproducible for any element:

 -(void)queuePlayerItemStatusPlayable:(AVPlayerItem*)aPlayerItem { NSAssert([NSThread isMainThread], @"FIX ME! VideoController method called using a thread other than main!"); VideoItem* videoItem = [self findVideoItemFromPlayerItem:aPlayerItem]; NSLog(@"queuePlayerItemStatusPlayable for item %@",videoItem); if (_queuePlayer.currentItem != aPlayerItem) { NSLog(@" but playable status is for non current item %@, ignoring",videoItem); return; } if (_videoItemIndex == 0) { NSLog(@" and playable status is for item 0 - call getReadyToPlay"); [self getReadyToPlay:videoItem]; // the first item doesn't get a current item notification so do this here } [self playIfReady]; //pausenotadvance (do this every time now) 

}

If this is the first ad, I have to call getReadyToPlay, since there is not a single notification about the current item for the player. I used to call playIfReady only for this element, but now I call it for any current element, trying to avoid stopping.

Likewise, this code is called if the item receives the status AVPlayerItemStatusFailed:

 -(void)queuePlayerItemStatusFailed:(AVPlayerItem*)aPlayerItem { NSAssert([NSThread isMainThread], @"FIX ME! VideoController method called using a thread other than main!"); VideoItem* videoItem = [self findVideoItemFromPlayerItem:aPlayerItem]; if (videoItem.isAd) { // this seems to be the only notification that we've run out of video when reachability is off if (appDelegate._networkStatus == NotReachable) { NSLog(@"AVPlayerItemStatusFailed when not reachable, show low bandwidth UI"); if (!_isPaused && !_lowBandwidthUIShowing) { [_queuePlayer pause];; [self showLowBandwidthUI]; } return; } NSError* error = aPlayerItem.error; if (aPlayerItem == _queuePlayer.currentItem) { NSLog(@"AVPlayerStatusFailed playing currently playing ad with error: %@",error.localizedDescription); if (videoItem.isLast) { NSLog(@"Failed to play last ad, quitting"); [self playEnded]; } else { NSLog(@"Not the last ad, advance"); [_queuePlayer advanceToNextItem]; } } else { NSLog(@"Error - AVPlayerStatusFailed on non-playing ad with error: %@",error.localizedDescription); [_queuePlayer removeItem:aPlayerItem]; } } else { // This is can be an invalid URL in the main video JSON or really bad network // Assuming invalid URLS are pretty rare by the time we're in the app store, blame the network // Whatever, give up because it the main video NSError* error = aPlayerItem.error; if (appDelegate._networkStatus == ReachableViaWiFi) { if (!_alertShowing) { NSLog(@"Error - AVPlayerStatusFailed playing main video, bad JSON? : error %@",error.localizedDescription); [self showServerAlertAndExit]; } } else { if (!_alertShowing) { NSLog(@"Error - AVPlayerStatusFailed playing main video, bandwidth? : error %@",error.localizedDescription); [self showNetworkAlertAndExit]; } } } return; 

}

When another ad appears, I will either add it to the end of the player or replace the current item. I only do the last if the current element is zero, which means that the player has finished the current fragment of the video and stopped, expecting more:

 -(void) addNewItemToVideoPlayer:(AVPlayerItem*)aPlayerItem { NSAssert([NSThread isMainThread], @"FIX ME! VideoController method called using a thread other than main!"); if (_queuePlayer.currentItem == nil) { NSLog(@" CCV replaced nil current item, player %@",_queuePlayer); [_queuePlayer replaceCurrentItemWithPlayerItem:aPlayerItem]; if (!_isPaused) [_queuePlayer play]; } else if ([_queuePlayer canInsertItem:aPlayerItem afterItem:((VideoItem*)[_videoItems objectAtIndex:_indexLastCued]).avPlayerItem]) { NSLog(@" CCV inserted item after valid current item, player %@",_queuePlayer); [_queuePlayer insertItem:aPlayerItem afterItem:((VideoItem*)[_videoItems objectAtIndex:_indexLastCued]).avPlayerItem]; } } 

This code seems to work very well on simulators / Wi-Fi and possibly on high-performance devices.

iPhone 3G on a slow 3G network shows a lot of not very repeatable defects.

I am watching playback that is probably not far behind, and call it in the main thread:

 -(void)queuePlayerLikelyToKeepUp:(AVPlayerItem*)aPlayerItem { NSAssert([NSThread isMainThread], @"FIX ME! VideoController method called using a thread other than main!"); if (aPlayerItem.playbackLikelyToKeepUp == NO) { if (!_isPaused) { [_queuePlayer pause]; [self showLowBandwidthUI]; [self performSelector:@selector(restartVideo) withObject:nil afterDelay:1.0]; // delay and try again } } else { // if we forced the showing of UI due to low bandwidth, fade it out, remove spinner if (_lowBandwidthUIShowing) { [self hideLowBandwidthUI]; [_queuePlayer play]; } } } 

This basically pauses the video and displays a user interface (which shows a progress bar showing how many videos have been downloaded to give the user feedback on why the video stopped). Then he tries to restart the video after a second.

The worst mistakes that I see on the iPhone 3G are due to a complete breakdown. Sometimes I get AVPlayerItemStatusFailed for the main video (the error message is useless - just โ€œunknown errorโ€ - AVFoundationErrorDomain error -11800), so the user exits the locked video and tries again, but then it seems that as soon as the player is in this state, he doesnโ€™t will play videos, even those that he played before. And yet this is a brand new AVQueuePlayer - I exit my VideoController after each set of videos is done or when the user gets tired of them.

Videos can also fall into ad-free mode. The main video is played, but then the ads are not showing, and there are no impressions for all subsequent videos.

I know that this is a difficult, if not impossible, question that you answer, but I was asked to ask, so I go ahead.

Do you see something that I am doing wrong?

More generally, can you give a short tutorial on how to use AVQueuePlayer in a situation where all video URLs are unknown when creating AVQueuePlayer? What to do and not do ABQueuePlayer and AVPlayer?

Can you give advice on handling low bandwidth situations with AVQueuePlayer? What makes it linger on a restart?

Do you have any thoughts on what should be done in the main thread and what should not?

+4
source share
3 answers

The answer was that all methods (not just properties) of AVQueuePlayer should be called in the main thread. In addition, KVO monitoring must be started and stopped on the main thread.

If you do not, even in one tiny case, your player will end up in a state in which he refuses to play. And even worse, the mediaserverd process supporting AVQueuePlayer is also in such a state, so even starting to play a new video and re- AVQueuePlayer will not work.

+1
source

I had the same problem (audio playback stops in the background due to a mediaserverd failure) ... I tried to make all calls / properties in AVQueuePlayer in the main thread, but it did not completely solve the problem.

So, I opened TSI on developers.apple.com and here is the answer:

1) What can cause this crash, is it something in our code, or is it a mistake to know, and it will be fixed in the near future? โ†’ Media Server crashes and when they do, we rely on developer reports to track problems and fix them. AVFoundation and the queue player are on a large number of other AV and Core Audio to do what it does, and therefore for us to analyze what causes this particular media server we need a bug report with a playable test case. I did not see this crash log in person, other users reported this, and it looks like 5.1. I suggest applying for an error as soon as possible, a specific case of this issue. You can send me the error identifier, and I can escalate it into our AVFoundation and Core Audio commands.

2) What can I do (in addition to what I'm doing now) to prevent iOS from freezing the application in this case? 3) How can I get a notification if mediaserverd crashes and it reboots? โ†’

I wrote Q&A regarding a general way to handle crashes with a media server. You can listen to the kAudioSessionProperty_ServerDied property. https://developer.apple.com/library/ios/#qa/qa1749/_index.html Let me stress this is a bandida, and any failures are considered errors on ours and we would like to fix them as soon as possible. Please write the error as soon as you can.

Thank you. hello edward

Edward A. :: Technical support for developers of audio and video engineers Worldwide developer relations Apple Inc. *

Hope this helps ... or at least prevent you from breaking your head how to fix it :-)

+1
source

RESOLVED:

The cause of the accident caused this in the main thread:

  currentDuration = CMTimeGetSeconds([[myAVQueuePlayer currentItem] duration]); 

and my solution:

  NSError* error = nil; if ( [[[myAVQueuePlayer currentItem] asset] statusOfValueForKey:@"duration" error:&error] == AVKeyValueStatusLoaded ) { currentDuration = CMTimeGetSeconds([[myAVQueuePlayer currentItem] duration]); } else { [[[myAVQueuePlayer currentItem] asset] loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:@"duration"] completionHandler:^{ currentDuration = CMTimeGetSeconds([[myAVQueuePlayer currentItem] duration]); }]; } 
+1
source

Source: https://habr.com/ru/post/1369021/


All Articles