m3u8 audio stream plays in MPMoviePlayerController but not AVPlayer? - ios

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?

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
}

iOS add audio playing via AVPlayer to OS default player?

So the idea is - when we play audio in Music app in iPhone and press home button then swipe from bottom of the screen, we can see the audio playing in default OS player with play/pause controls and audio continue play.
Now what i need to do, i play an audio by using AVAudioPlayer in my app and if user press home button, the audio need to play continue as in case of music app.
But i have no idea how to add audio in OS default player? Any help or suggestion would be highly appreciated.
Edit: I need to play audio using streaming so i guess i need to use AVPlayer instead of AVAudioPlayer
You could use an MPMovieplayerController since you'll be more than likely be using an M3U8 playlist.
Here's the documentation http://developer.apple.com/library/ios/documentation/MediaPlayer/Reference/mpmovieplayercontroller_class/index.html
But it basically goes like this:
Initialize the MPMoviePlayerController, assign a file type (stream), put the source url, and present it in the view stack.
For the background audio, you'd have to use AudioSession like in this answer iOS MPMoviePlayerController playing audio in background
You can do this as a background audio task so that the audio keeps playing even after the user presses the home button and your app goes into the background. First you create an AVAudioSession. Then you set up an array of AVPlayerObjects and a AVQueuePlayer in your viewDidLoad method. There's a great tutorial by Ray Wenderlich that discusses all of this in detail http://www.raywenderlich.com/29948/backgrounding-for-ios. You can set up a callback method (observer method) so that the app is sent additional audio data as it streams in - (void)observeValueForKeyPath.
Here's how the code looks (from Ray Wenderlich's tutorial):
in viewDidLoad:
// Set AVAudioSession
NSError *sessionError = nil;
[[AVAudioSession sharedInstance] setDelegate:self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&sessionError];
// Change the default output audio route
UInt32 doChangeDefaultRoute = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker,
sizeof(doChangeDefaultRoute), &doChangeDefaultRoute);
NSArray *queue = #[
[AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:#"IronBacon" withExtension:#"mp3"]],
[AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:#"FeelinGood" withExtension:#"mp3"]],
[AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:#"WhatYouWant" withExtension:#"mp3"]]];
self.player = [[AVQueuePlayer alloc] initWithItems:queue];
self.player.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
[self.player addObserver:self
forKeyPath:#"currentItem"
options:NSKeyValueObservingOptionNew
context:nil];
in a callback method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"currentItem"])
{
AVPlayerItem *item = ((AVPlayer *)object).currentItem;
self.lblMusicName.text = ((AVURLAsset*)item.asset).URL.pathComponents.lastObject;
NSLog(#"New music name: %#", self.lblMusicName.text);
}
}
Don't forget to add the member variables in your view controller's private API in the implementation file:
#interface TBFirstViewController ()
#property (nonatomic, strong) AVQueuePlayer *player;
#property (nonatomic, strong) id timeObserver;
#end

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];

Resources