AVQueuePlayer not advancing to next item in iOS 8 - ios

I am using AVQueuePlayer to play a few videos, everything works fine in iOS 6 & 7. However, in iOS 8, when the current AVPlayerItem finishes playing the next video does not play. Placing an observer on the queue's currentItem property shows the the queue has the next video set as its current item as expected, it just does not play (even with explicit play calls).
Does anyone have any insight into what might be happening and how to fix it? Has anyone else come across this issue?

I had exactly the same issue and I wasted ten hours on this...
But it fixed now by adding the "insertItem" method into the main thread.
So I have my player as a property :
#property AVQueuePlayer * player;
I just init it like this :
_player = [[AVQueuePlayer alloc] init];
And here is my method to add items from a path :
- (void)addInQueuePlayerFile:(NSString *)path {
AVAsset * asset = [AVAsset assetWithURL:[[NSURL alloc] initFileURLWithPath:path]];
AVPlayerItem * playerItem = [AVPlayerItem playerItemWithAsset:asset];
dispatch_async(dispatch_get_main_queue(), ^{
[_player insertItem:playerItem afterItem:nil];
});
}
So its working but I don't really understand why... So if someone has an answer...
But also if this doesn't work for you, try to add an observer to your player (and/or your playerItem). You can add an observer like that :
[_player addObserver:self forKeyPath:#"status" options:0 context:nil]
And catch it with this method :
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == _player && [keyPath isEqualToString:#"status"]) {
if (_player.status == AVPlayerStatusFailed) {
NSLog(#"AVPlayer Failed");
} else if (_player.status == AVPlayerStatusReadyToPlay) {
NSLog(#"AVPlayer item Ready to Play");
} else if (_player.status == AVPlayerStatusUnknown) {
NSLog(#"AVPlayer item Unknown");
}
}
}
Let me know if this work for you.

The avplayer should always be accessed on the main thread. This is nothing new to iOS8 but perhaps Apple changed how threads are used so you're seeing the issue more often than in iOS7.
To ensure safe access to a player’s nonatomic properties while dynamic changes in playback state may be reported, you must serialize access with the receiver’s notification queue. In the common case, such serialization is naturally achieved by invoking AVPlayer’s various methods on the main thread or queue.
https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVPlayer_Class/index.html

I had the same problem. After several hours of trial, I found that if some of the items in queue have different encryption methods, the player would stall.
For example, if the player just finished playing an encrypted stream, and the next item in queue was unencrypted, the player is not going to move on to the next item.

Related

AVPlayer stops playing when buffer is full and dosent resume

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

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.

Can’t observe AVPlayerItem for #“status” key

I’m trying to play stream from URL, but here is an issue. ObserveValueForKeyPath:ofObject:change:context just doesn’t execute. As I understand, it is not depends on streamURL, nether it is correct or not. It must change status to AVPlayerItemStatusReadyToPlay or AVPlayerItemStatusFailed from AVPlayerItemStatusUnknown. But nonetheless I checked that URL in browser, and it is working fine. So, what’s the problem? I watched WWDC video about it, and it says, if you have audio stream, use AVPLayerItem before AVAsset.
Here is my code:
- (void) prepare
{
NSURL * trackStreamURL = [NSURL URLWithString:streamURL];
NSLog(#"STREAM URL: %#",trackStreamURL);
_playerItem = [AVPlayerItem playerItemWithURL:trackStreamURL];
[_playerItem addObserver:self forKeyPath:#"status" options:0 context:nil];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"KEY PATH : %#", keyPath);
NSLog(#"CHANGE : %#", change);
}
Thanks in advance!
I've always observed the status property on the AVPlayer instead of the AVPlayerItem since you need a player to play the player item anyway. In fact, I had never even looked at the status property of the player item.
The status of the player item probably never changes because there is no player that is ready to play it. So, Create a player with the player item and observe the status of the player (or possibly, still the status of the player item).
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];

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

AVPlayerItemStatusUnknown showing up when doing Live HTTP streaming

I am using AVPlayer to view videos stored on Amazon's CloudFront -- Live HTTP protocol is used and the playlist and segments are stored on S3 and hosted using CloudFront.
After playing a few videos I start getting a status of AVPlayerItemStatusUnknown from the AVPlayer item
AVPlayer.currentItem.status == AVPlayerItemStatusUnknown
This status persists when a new video playlist is selected -- I've tried de-allocating the AVPlayer before setting a new playlist URL and still the AVPlayerItemStatusUnknown status remains until I terminate the application.
Two questions. Is anybody using Amazaon CloudFront to host video experiencing the same issue? Does anybody know a work around so i can recover the state of the AVPlayer to prevent the user from having to terminate the app to view any more videos?
Many Thanks,
//aaron
For streaming media this looks like a normal behavior to me. Do you add an observer for the 'status' property? You should start playback when status changes to AVPlayerItemStatusReadyToPlay.
[item addObserver:self forKeyPath:#"status" options:0 context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"status"]) {
AVPlayerItem *item = (AVPlayerItem *)object;
if (item.status == AVPlayerItemStatusReadyToPlay) {
//Ready
}
}
}
I found the problem. Short answer is it was an over retained AVPlayer, which by the way, was not noticed by the Instruments tool using the leaks template. Sorry for the false alarm.
//aaron

Resources