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
Related
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.
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.
I'm kind of lost on this one. I have a class that needs to play audio and video files or streams. It has a completely custom UI so I use AVPlayer for this.
There is one live audio stream that just won't play. Every single time the AVPlayerItem observer triggers AVPlayerItemStatusFailed, the error of AVPlayer is nil.
But when I try to play the same audio stream in MPMoviePlayerController or Safari or Chrome it works just fine. Which is extremely weird since internally MPMoviePlayerController uses AVPlayer.
This is the URL of the live audio stream that fails: http://bit.ly/1gIqjV6
My AVPlayer code (doesn't work with the URL)
_currentItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:#"http://bit.ly/1gIqjV6"]];
[self startObservingPlayerItem:self.currentItem];
_avPlayer = [[AVPlayer alloc] initWithPlayerItem:self.currentItem];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.currentItem];
The AVPlayerItem observer
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([object isEqual:[self.avPlayer currentItem]])
{
if ([keyPath isEqualToString:#"status"])
{
switch ([self.avPlayer currentItem].status) {
case AVPlayerItemStatusReadyToPlay:
NSLog(#"AVPlayerItemStatusReadyToPlay");
break;
case AVPlayerItemStatusFailed:
// The live audio stream always fails, error is always nil
NSLog(#"AVPlayerItemStatusFailed");
NSError* error = self.avPlayer.error;
break;
case AVPlayerItemStatusUnknown:
NSLog(#"AVPlayerItemStatusUnknown");
break;
}
}
}
}
With MPMoviePlayerController (works fine with the URL)
MPMoviePlayerViewController* controller = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL URLWithString:#"http://bit.ly/1gIqjV6"]];
[rootViewController presentViewController:controller animated:YES completion:nil];
Has anyone ever encountered an issue like this?
Thanks in advance.
It looks like it's your m3u8 playlist from here. I tried a blank project with a simple AVPlayer, and while the test stream ("http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"), as well as the 'hi' and 'lo' streams from your m3u8 file worked, creating the AVPlayerItem with your m3u8 file always failed.
The diff in the m3u8 is as follows:
Yours:
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=35200, CODECS="mp4a.40.2"
Apple's sample:
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000
Do you have any other m3u8 playlists to test?
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];
I've read quite a few posts on the subject but the answers are not 100% clear. I'm looking for clarity here.
My app plays a short AVAudioPlayer sound periodically. The problem is, I can only set the volume after the first sound is played.
After reading stackoverflow, everyone seems to suggest that I play a dummy (silent) AVAudioPlayer sound at the start of the app to "link" the device's volume buttons to the "app volume".
Said another way, when the app starts, it's the "Ringer" volume that is controlled by default and only after the first sound is played will the device's volume buttons finally control the "app volume" (AVAudioPlayer volume) (it's the image without any label). Unfortunately by the time this happens, the user doesn't hear the first sound and now sees the app as broken.
My question is, is this the answer? Do I simply play a short dummy sound once at the start of the app to "link" the device's volume buttons to the app?
You don't have to play a dummy sound. Using the AudioToolbox framework you can set the AudioSessionActive as follows:
AudioSessionInitialize (NULL, NULL, NULL, NULL);
UInt32 sessionCategory = kAudioSessionCategory_AmbientSound;
AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof (sessionCategory), &sessionCategory);
AudioSessionSetActive (true);
This will allow the volume buttons to control the app volume.
See this question: Cannot Control Volume of AVAudioPlayer via Hardware Buttons when AudioSessionActive is NO for more information on this approach.
Hey for future answer searchers, Since AudioSessionInitialize and AudioSessionSetActive are deprecated in iOS7 the recommended way of handling hardware audio and getting call backs is by using an AVAudioSession object. Set the session as active for your app and KVO on the #"outputVolume" property of the session.
- (id)init
{
self = [super init];
if (self)
{
self.audioSession = [AVAudioSession sharedInstance];
[_audioSession setActive:YES error:nil];
[_audioSession addObserver:self forKeyPath:#"outputVolume" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"outputVolume"])
{
[self setVolume:[change[#"new"] floatValue]];
}
}
- (void)dealloc
{
[_audioSession removeObserver:self forKeyPath:#"outputVolume"];
[_audioSession setActive:NO error:nil];
}