I'm using Apple's AVPlayerDemo in order to playback video files (the videos are .mp4 coming from my Amazon S3 server).
The player is a subview of a custom table view cell, and the user can scroll the table to switch between videos (an players). There is no simultaneous playback, the player is deallocated on the 'didEndDisplayingCell' method.
The problem is that there is a deadlock after several scrolls (the number of scrolls before the deadlock varies, but eventually it always happens).
the deadlock is on this line (running on the main thread):
[self setPlayer:[AVPlayer playerWithPlayerItem:self.playerItem]];
It seems that playerWithPlayerItem is on mutexwait, waiting for a background thread.
Logging shows that the dealloc of the former AVPlayerDemoPlaybackViewController is called before the deadlock. here is the dealloc code:
if (self.player) {
[self.player removeObserver:self forKeyPath:#"currentItem"];
[self.player.currentItem removeObserver:self forKeyPath:#"status"];
[self.player pause];
[self.playbackView.layer removeFromSuperlayer];
[self.playbackView removeFromSuperview];
self.player = nil;
}
The view controller's player property is nil before it is assigned by the dead-locked line.
The dead-locked line is called from this block:
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:mURL options:nil];
NSArray *requestedKeys = #[#"playable"];
/* Tells the asset to load the values of any of the specified keys that are not already loaded. */
[asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
^{
dispatch_async( dispatch_get_main_queue(),
^{
[self prepareToPlayAsset:asset withKeys:requestedKeys];
});
}];
Here is the threads state at the time of the deadlock:
Please advise - thanks!!
Related
Hi in my application I have avplayerViewController. I just launch the application and click on start play button - which will launch avplayerviewcontroller. There i just drag the seek bar and content is buffering before content start play i just close the playerViewcontroller. After few seconds content is start playing (audio is coming). It's very serious issue please guide me how to resolve this issue. As of now when ever i close the player i am doing below operations.
#try{
[self.player replaceCurrentItemWithPlayerItem:nil];
[self.player setRate:0.0];
[self.player seekToTime:CMTimeMake(0, 1)];
[self.player pause];
dispatch_async(dispatch_get_main_queue(), ^{
[self.contentOverlayView sendSubviewToBack:self->playerActivityIndicator];
[self->playerActivityIndicator stopAnimating];
});
// [[self.player currentItem] removeObserver:self forKeyPath:#"status"];
[[self.player currentItem] removeObserver:self forKeyPath:#"playbackBufferEmpty"];
[self.player removeTimeObserver:self->playerPeriodicTimeObserver];
}#catch(id anException){
//do nothing, obviously it wasn't attached because an exception was thrown
NSLog(#"No observer for a player in hide placeholder");
}
I'm really going crazy with my welcome view controller.
I have a video in background in continuos loop but every solution that I used causes a small pause/flash when the video is finished and loop.
I use two solution: MPMoviePlayerController and AVPlayer from AVFoundation but I got the same result, a small white flash when video is looped for replay.
My MPMoviePlayerController solution (I prefer a fix for this)
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:#"welcome_video" withExtension:#"mp4"];
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:videoURL];
self.moviePlayer.controlStyle = MPMovieControlStyleNone;
self.moviePlayer.scalingMode = MPMovieScalingModeAspectFill;
self.moviePlayer.view.frame = self.view.frame;
[self.view insertSubview:self.moviePlayer.view atIndex:0];
[self.moviePlayer prepareToPlay];
// Loop video
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(loopVideo) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
}
- (void)loopVideo
{
[self.moviePlayer play];
}
My AVPlayer solution
(void)viewDidLoad
{
[super viewDidLoad];
NSError *sessionError = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&sessionError];
[[AVAudioSession sharedInstance] setActive:YES error:&sessionError];
//Set up player
NSURL *movieURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"welcome_video" ofType:#"mp4"]];
AVAsset *avAsset = [AVAsset assetWithURL:movieURL];
AVPlayerItem *avPlayerItem =[[AVPlayerItem alloc]initWithAsset:avAsset];
self.avplayer = [[AVPlayer alloc]initWithPlayerItem:avPlayerItem];
AVPlayerLayer *avPlayerLayer =[AVPlayerLayer playerLayerWithPlayer:self.avplayer];
[avPlayerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[avPlayerLayer setFrame:[[UIScreen mainScreen] bounds]];
[self.movieView.layer addSublayer:avPlayerLayer];
//Config player
[self.avplayer seekToTime:kCMTimeZero];
[self.avplayer setVolume:0.0f];
[self.avplayer setActionAtItemEnd:AVPlayerActionAtItemEndNone];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.avplayer currentItem]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerStartPlaying)
name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.avplayer pause];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.avplayer play];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}
- (void)playerItemDidReachEnd:(NSNotification *)notification
{
AVPlayerItem *p = [notification object];
[p seekToTime:kCMTimeZero];
}
- (void)playerStartPlaying
{
[self.avplayer play];
}
What's wrong with these implementation? I really try different fixes found on this site but nothing seems to work.
Any suggestions?
Hm.. I might have an idea for the AVPlayer-approach.
The AVPlayer has this method:
- (void)setRate:(float)rate
time:(CMTime)itemTime
atHostTime:(CMTime)hostClockTime
This means that you can specify WHEN you want the AVPlayer to start at kCMTimeZero. I have not actually used it, but I have an idea for how it can work.
You need to know exactly the moment you start the video the first time. I see you have your own -(void)playerStartPlaying which is called by the notificationCenter on didBecomeActive. I suggest using this method for app variations of [player play];, so put
[self playerStartPlaying];
inside viewDidAppear instead of [self.avplayer play];. It might be good enough.
If you here manage to find the device's hostClockTime, and add the length of the video, you should end up with the exact time when you want it to start from scratch. I am not testing any of this, and I'm typing from head, so you need to understand what I'm doing, and fix it yourself.
- (void)playerStartPlaying
{
//The device's hostClockTime. Basically a number indicating how long the device has been powered on.
CMTime hostClockTime = CMClockGetHostTimeClock;
//A CMTime indicating when you want the video to play the next time.
CMTime nextPlay = CMTimeAdd(hostClockTime, self.avplayer.currentItem.duration);
/* I don't know if that was correct or not, but you'll find out */
//Start playing if we're not already playing. There might be an avplayer.isPlaying or something, I don't know, this is probably working as well..
if(self.avplayer.rate != 1.0)
[self.avplayer play];
//Tell the player to restart the video at the correct time.
[self.avplayer setRate:1.0 time:kCMTimeZero atHostTime:nextPlay];
}
You'll have to remove the entire AVPlayerItemDidPlayToEndTimeNotification-thing. When the video has reached the end, it's already too late. What we're doing now is telling it when to play the second turn when we start the first. We want nothing to happen when didPlayToEndTime is fired, we're handling it manually.
So, if you understand what I have done above, you'll also notice that the video only will play twice. We tell the video to play, at the same time as we tell it to replay at time = now+videoLength. When that replay is done, nothing happens. It simply reaches end. To fix this, you'll need to somehow call -(void)playerStartPlaying at the same time as the setRate:time:atHostTime is executed on the AVPlayer. I guess you could start an NSTimer or dispatch_time and let it execute he method in exactly nextPlay-amount of time, but that would kinda defeat the purpose of this thing. Maybe not. You could try different stuff out. You probably CAN do this with some success, but I suggest finding a way to register for when the player started from the start. Maybe you can observe the rate or something, I don't know.. If you want to try it with a delayed method, you can try this:
double delayInSeconds = CMTimeGetSeconds(self.avplayer.currentItem.duration); //or something
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self playerStartPlaying];
});
Just keep in mind that this is some recursive shit, so even if you pause the video, this will still keep calling after that duration. In that case I suggest making a BOOL paused; and cancel the execution of the entire playerStartPlaying if it's set to YES. ..and of course set paused = YES; whenever you want to pause, next to wherever you say [player pause];
If this actually works, but you still get flashes, I know there are several ways to improve this. For instance, the CMTimeAdd() should probably be using some kind of synchronization-tool to make sure the times add up using the correct timeScale etc.
I have now spent way too much time writing this, and it might not even work. I have no idea. Good luck and good night.
I am building an app which needs to play a track list, but between each song the music should pause to execute some code, then once complete the music should resume. This needs to work when the app is in the background as well as in the foreground.
I have tried a couple of methods but none seem to be able to do everything I want.
AVQueuePlayer - I can't seem to identify when any one song has stopped, only when the whole queue has stopped.
AVPlayer - I can identify when the track has ended with a notification, then I can run my extra code then load the next track. This works fine as long as the app is not in the background, when the app is in the background the code executes fine except the [avPlayer play] command does not work. It does not throw an error, it simply does not play. I know it has moved to the next song and loaded it into AVPlayer as I output the meta data and it has moved on.
Just to be clear the initial track does run in the background, it is only starting the next track which does not run in the background.
Code below...
Any idea what I am doing wrong?
Thanks!
+(void) playItem {
//get the play item from the song array based on intSongIndex
MPMediaItem *currentSong = [songsNowPlaying objectAtIndex:intSongIndex];
AVPlayerItem * currentItem = [AVPlayerItem playerItemWithURL:[currentSong valueForProperty:MPMediaItemPropertyAssetURL]];
[avPlayer replaceCurrentItemWithPlayerItem:currentItem];
//add notification to the currentItem
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:currentItem];
//play
[avPlayer play];
NSArray *metadataList = [[avPlayer currentItem].asset commonMetadata];
for (AVMetadataItem *metaItem in metadataList) {
NSLog(#"%#: %#",[metaItem commonKey], [metaItem value]);
}
//increment song index so next time the next song is selected
intSongIndex ++;
if (intSongIndex >= songsNowPlaying.count) {
intSongIndex = 0;
}
}
+ (void)playerItemDidReachEnd:(NSNotification *)notification {
//add code to be executed before the next song plays
//call playItem to play the next song
[self playItem];
}
Solved, this needed adding to the initial viewDidLoad
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
I used AVPlayer to play youtube video in iOS, i created VideoViewController to manage play video.
When VideoViewController pop out NavigationController, i implement
- (void)viewDidDisappear:(BOOL)animated {
[self.player removeObserver:self forKeyPath:kCurrentItemKey context:MyStreamingMovieViewControllerCurrentItemObservationContext];
[self.player removeObserver:self forKeyPath:kRateKey context:MyStreamingMovieViewControllerRateObservationContext];
[self removePlayerTimeObserver];
self.playerLayerView = nil;
self.playerItem = nil;
self.player = nil;
}
but NetworkAcitivtyIndicator still loading in Status Bar, i want stop it. How to fix it?
Did you manage activity indicator by yourself? If so, insert this line in your pop method: [UIApplication sharedApplication].networkActivityIndicatorVisible = NO
Or try to stop player before you set it's reference to nil.
With ARC, objects with a nil value are released at the end of the block or scope in which they are nulled. Although AVPlayer is an object generated by a class cluster (i.e., many different objects), you don't have to release all of them individually,
just the player.
So, this single line will release the player and everything else underneath, including the asset and/or player item:
[self.player replaceCurrentItemWithPlayerItem:nil];
If, for some strange reason, you cannot wait until the end of the method to release the player (perhaps it is preceded by a long-running iterator or is retained by a C-inline block variable), encompass the line in an #autorelease pool:
#autorelease {
[self.player replaceCurrentItemWithPlayerItem:nil];
}
I am implementing Media Player in iOS Platform. I have a problem with UI Freezing, when streaming the videos from the internet using AVPlayer. Note: I'm not using AVAudioPlayer, AVQueuePlayer. Here following code for playing the media: UI Freeze is occurring only start Streaming.
if(_player.rate!=0.0)
{
[_player pause];
[ad.player.playerLayer removeFromSuperlayer];
[_player replaceCurrentItemWithPlayerItem:[ AVPlayerItem playerItemWithURL:_tempURL]];
}
else
{
_player = [_player initWithURL:mediaURL];
ad.player.playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
ad.player.playerLayer.frame=ad.player.bounds;
ad.player.playerLayer.frame=CGRectMake(0, 0, 320, 150);
[ad.player.playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
ad.player.playerLayer.needsDisplayOnBoundsChange = YES;
[ad.player.layer addSublayer:ad.player.playerLayer];
[_player play];
}
I referred the following Link:
AVPlayer "freezes" the app at the start of buffering an audio stream
But that link suggested for AVQueuePlayer. But my Requirement is to do in AVPlayer
When you start playing the video it hasn't downloaded any data yet, AVPlayer class has a method called prerollAtRate:completionHandler which loads data starting at the item’s current playback time, which then calls a completionHandler whens its finishes the load attempt.
__weak typeof(self) weakSelf = self;
[self prerollAtRate:0.0 completionHandler:^(BOOL finished) {
NSLog(#"ready: %d", finished);
// if ready call the play method
if (finished) {
dispatch_async(dispatch_get_main_queue(), ^{
// call UI on main thread
[weakSelf.player play];
});
}
}];