AVPlayer stops playing when buffer is full and dosent resume - ios

I have a collection view with an AVPlayer inside the cell and the AVPlayer starts playing the AVPlayerItem in a loop when
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
gets called. this works well but the problem is that after the AVPlayer is playing the item a few times the video is no longer shown but i can hear its sound.
I also add an observer for the value #"playbackBufferFull" for each item that is played like that:
[item addObserver:self forKeyPath:#"playbackBufferFull" options:NSKeyValueObservingOptionNew context:nil];
i noticed that when the video stops the observer method of the value #"playbackBufferFull" gets called, first of all i would like to know what causes the buffer the get full, the second and most important is how can i resume the AVPlayer when the video stops;
i tried calling [cell.videoPlayer play]; and to replace the item with a new one and it didnt work, the observer method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if ([object isKindOfClass:[AVPlayerItem class]] && [keyPath isEqualToString:#"playbackBufferFull"])
{
//this method is get called when the video stop showing but i can still hear it
//how can i resume the video?
}
}

my solution is :
first add observe for AVPlayerItemPlaybackStalledNotification in viewDidLoad or ...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemPlaybackStalledNotification
object:self.avPlayer.currentItem];
-(void)playerItemDidReachEnd:(NSNotification*)noti
{
//thisisn't good way but i can't find the best way for detect best place for resume again.
NSLog(#"\n\n give Error while Streaminggggg");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.avPlayer play];
});
}
BUT
maybe you can find the best way to call play method again for resume!
please check do you get AVPlayerStatusReadyToPlay keypatch ?
if you get , you can call play method there.
please notify me about the result

Related

Resume AVPlayer stream playback last sample

I am trying to use the native player (AVPlayer) to reproduce a live stream on iOS. However, I have trouble resuming the playback. When I stop the playback and resume it after few seconds, the playback starts from the moment I paused instead of reproducing the current (last) sample of the live stream.
Is there a way to get the last sample, o configure AVPlayer to reproduce from last sample when tapping on Play Button?
My solution is based on denying user to keep the player paused. This is, destroying the player each time playback is resumed. And creating a new instance each time, playback is intended to be resumed.
According to Apple recommendation, the only solution to know if the AVPlayer was stopped is to add KVO. This is:
- (void)setupPlayer {
AVPlayer *player = [AVPlayer playerWithURL:streamURL];
AVPlayerViewController *playerViewController = [[AVPlayerViewController alloc] init];
playerViewController.player = player;
self.playerViewController = playerViewController;
[self configureConstraintsForView:self.playerViewController.view]; //Add Player to View
[self setupObservers];
[player play];
}
- (void)setupObservers {
[self.playerViewController.player addObserver:self
forKeyPath:#"rate"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
if ([keyPath isEqualToString:#"rate"] && self.playerViewController.player.rate == CGPointZero.x) {
[self.playerViewController.view removeFromSuperview];
[self.playerViewController.player removeObserver:self forKeyPath:#"rate"];
[self.playerViewController.player pause];
self.playerViewController = nil;
}
}
Then, when user wants to re-engage the player, just call -(void)setupPlayer which start the playback from the last live sample.

Now that MPMoviePlayerPlaybackDidFinishReasonUserInfoKey is deprecated, what is the best way to find why playback ended?

In iOS9, the MPMoviePlayer classes have all been deprecated in favor of AVPlayer. I have an existing app using MPMoviePlayerPlaybackDidFinishReasonUserInfoKey to determine how to log events on how the video player ended. How do I do the same with AVPlayer?
The following are the ending reason keys:
MPMovieFinishReasonPlaybackEnded
MPMovieFinishReasonPlaybackError
MPMovieFinishReasonUserExited
There is no equivalent to MPMoviePlayerPlaybackDidFinishReasonUserInfoKey and MPMoviePlayerPlaybackDidFinishNotification in AVKit. To accomplish the same functionality in AVKit, you must listen to three notifications separately, rather than one notification with different possible reasons.
MPMovieFinishReasonPlaybackEnded >>> AVPlayerItemDidPlayToEndTimeNotification
MPMovieFinishReasonPlaybackError >>> AVPlayerItemFailedToPlayToEndTimeNotification
MPMovieFinishReasonUserExited. No conversion. There are multiple ways to detect that the user has killed the player. One is detecting a modal has closed.
If you want to know if the video is playing or not, you can do a KVO:
[self.player addObserver:self forKeyPath:#"rate" options:0 context:nil];
Then add this method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"rate"]) {
if ([self.player rate]) {
[self changeToPause]; // This changes the button to Pause
}
else {
[self changeToPlay]; // This changes the button to Play
}
}
}
Try, in viewDidLoad:
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:#"yoururl"]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
AVPlayer* player = [[[AVPlayer alloc] initWithPlayerItem:playerItem] autorelease];
[player play]
And
-(void)itemDidFinishPlaying:(NSNotification *) notification {
// Will be called when AVPlayer finishes playing playerItem
}

How do I correctly use AVPlayer so it doesn't show white screen before playing video?

Every time, when I try to playing a megabyte video using AVPlayer, it initially shows a white screen for a second and then starts the video.
Why is this happening if the video is already cached? Is there a way to stop this from happening, so that it goes straight to the video without displaying a white screen?
I tried using AVPlayer's isReady to check the status of AVPlayer and play video only when it's ready, but it still displays the white screen.
Also every time when I try to get the video duration of the video that's about to play through AVPlayer I keep getting 0.0 seconds initially, so I am not able to add a timer to the video either because I can't get the video duration because it keeps displaying a white screen for a second.
Firstly, AVPlayer doesn't show any white screen, its your background which is white. So, basically your AVPlayer is starting late. I guess you press a UIButton and then it loads the file in AVPlayer and immediately start playing it. Thats where the problem is. It may take some time for the AVPlayer to buffer enough data and be ready to play the file. Using KVO, it is possible to be notified for changes of the player status.
So first you need to disable the play button, load the AVPlayer and add an observer:
play.enabled = NO;
player = [AVPlayer playerWithURL:URL];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
Then enable it after checking AVPlayerStatusReadyToPlay:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == player && [keyPath isEqualToString:#"status"]) {
if (player.status == AVPlayerStatusReadyToPlay) {
play.enabled = YES;
}
}
}
I know this is an old question, but I get the same issue even when properly detecting when the AVPlayer is ready to play.
I wanted it to play over an image so that there was a smooth transition between an initial static image, and then moving video.
The trick for me was to set a clear background with:
AVPlayerViewController *controller = [[AVPlayerViewController alloc] init];
[controller.view setBackgroundColor:[UIColor clearColor]];
This way, if I toggle the visibility of the player when it's ready to play, I never see a black or white screen, because the player has a clear background, making for a smooth transition!
Blancos is right. AVPlayer is taking time to achieve state AVPlayerStatusReadyToPlay.
So, initialize the player with url and play it only when it is AVPlayerStatusReadyToPlay.
player = [AVPlayer playerWithURL:URL];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (object == player && [keyPath isEqualToString:#"status"]) {
if (player.status == AVPlayerStatusReadyToPlay) {
player.play()
}
}
}
I had the same problem. To avoid adding a KVO, I just set the AVPlayer up when the url is set like so...
var urlToUse: NSURL?
{
didSet
{
guard let urlToUse = urlToUse else { return }
replayPlayer = AVPlayer(URL: urlToUse)
}
}
That way the AVPlayer status will be ready when needed.

How can I register for when AVPlayer actually starts playing (from external source)

I'm having some trouble with registering WHEN the player is starting to play external videos (over internet) using AVPlayer. Please read the question before suggesting solutions.
I initialize the player like this:
player = [[AVPlayer alloc] initWithURL:[[NSURL alloc] initWithString:#"http://example.com/video.mp4"]];
playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[playerLayer setFrame:[videoView bounds]];
[videoView.layer addSublayer:playerLayer];
This adds the player to the view correctly. I have added the following two lines of code to keep track of when the player is ready, and what the status/rate is;
[player addObserver:self forKeyPath:#"rate" options:0 context:nil];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
These two line will call the method - (void)observeValueForKeyPath:.... when something changes with the status or the rate of the AVPlayer.
So far it looks like this:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
//To print out if it is 'rate' or 'status' that has changed:
NSLog(#"Changed: %#", keyPath);
if ([keyPath isEqualToString:#"rate"]) //If rate has changed:
{
if ([player rate] != 0) //If it started playing
{
NSLog(#"Total time: %f", CMTimeGetSeconds([[player currentItem] duration]));
// This NSLog is supposed to print out the duration of the video.
[self setControls];
// This method (setControls) is supposed to set play/pause-buttons
// as well as labels for the current and total time of the current video.
}
}
else if ([keyPath isEqualToString:#"status"]) // If the status changed
{
if(player.status == AVPlayerStatusReadyToPlay) //If "ReadyToPlay"
{
NSLog(#"ReadyToPlay");
[player play]; //Start the video
}
}
}
The state of the AVPlayer changes to readyToPlay almost immediately after initializing it, and I then call [player play]. When this happens, the rate changes to 1.00000, meaning it's actually playing at that rate, but the video is now just starting to buffer, not playing. The screen is black, and it takes a couple of seconds, and then it starts playing. The rate, however, indicates it starts playing before it does. The rate stays at 1.00000, not going down to 0 when start-buffering, which makes it very difficult for me to know when the player has enough information to start setting the controls (I.E time stamps etc).
The NSLog() printing out the duration of the video above prints out nan (Not A Number), which leads me to think that the item isn't ready to be played, however, the rate stays at 1.0000 until it has buffered a while, then it will actually play, still with rate at 1.0000.
It does, however, get called twice. The rate "changes" to 1.0000 twice without being anything else in between. In neither calls, the duration of the video is an available variable.
My goal is to fetch the current and total timestamp of the video as fast as possible (I.E 0:00/3:52). This will also be used to register the scrubbing of a slider (for fast-forward etc.).
These values are not ready when the player notifies me it's playing at a rate of 1.0000, twice. If I manually click "play" after a second or so (and call [player play]), then it's working. How can I register to know when the video is ready, not just 'ready to get ready'?
See addBoundaryTimeObserverForTimes:queue:usingBlock: on AVPlayer and this example from Apple.
AVPlayer *player = [AVPlayer playerWithURL:[NSURL URLWithString:#"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"]];
[player play];
// Assumes a property: #property (strong) id playerObserver;
// Cannot use kCMTimeZero so instead use a very small period of time
self.playerObserver = [player addBoundaryTimeObserverForTimes:#[[NSValue valueWithCMTime:CMTimeMake(1, 1000)]] queue:NULL usingBlock:^{
//Playback started
[player removeTimeObserver:self.playerObserver];
}];
I think the nearest you'll get to is to observe player.currentItem.playbackLikelyToKeepUp

Is It possible to track AVAudioPlayer object by using observer?

I followed the documents about how to set up and observer using KVO mechanism
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html
It's suppose to be very easy.
I created an AVAudioPlayer object and I want to track after every change in it's current time.
I use this code to set up the observer:
[_player addObserver:self forKeyPath:#"currentTime" options:NSKeyValueObservingOptionNew context:NULL];
This code to handle the changes:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"currentTime"]) {
//Do something
}}
And this code when the audio ends:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
// Remove the observation
[_player removeObserver:self forKeyPath:#"currentTime"];}
For some reason the observer doesn't call to the -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
I know that I can use NSTImer and trigger it when the audio starts playing but I'm looking for smoother way to do this.
I also can use AVPlayer object instead and track it by using it's addPeriodicTimeObserverForInterval:queue:usingBlock:
But I don't want to lose all the advantages of the AVAudioPlayer object.
What am I doing wrong with the observer?
Do you have another suggestion how to use AVAudioPlayer and manage tracking after it's currentTime property?
Thanks in advance,
You're doing nothing wrong.
I've tried to do this as well and it just doesn't fire.
I had to use an NSTimer instead that polled the currentTime :(
KVO will fire for the currentTime property on AVAudioPlayer only when a caller changes the value directly. It will not fire when as the audio clip progresses. To track that, you will have to use NSTimer, as has already been suggested by deanWombourne.
Have you tried
//KVO [self setValue:[NSNumber numberWithFloat:currentTime] forKey:#"currentTime"];
I did this when currentTime changing,and the KVO work fine.but it's still need a timer to tell setting value =.=

Resources