AVPlayer streaming works and yet it doesn't - ios

Running Xcode 7.1.1 under El Capitan 10.11.2 an IOS 9.2 app
Trying to understand the minimum code I need to implement playback for a Video stream, and crafted this very simply piece here ... don't need the Observer strictly speaking, but it crept in so I left it.
static const NSString *ItemStatusContext;
// a class static
self.avPlayer = [AVPlayer playerWithURL:[NSURL URLWithString:#"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"]];
[self.avPlayer addObserver:self forKeyPath:#"status" options:0 context:&ItemStatusContext];
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
self.avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
avPlayerLayer.frame = CGRectMake(128, 128, 512, 386);
[self.view.layer addSublayer: avPlayerLayer];
[self.avPlayer play];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (context == &ItemStatusContext ) {
AVPlayer *thePlayer = (AVPlayer *)object;
if ([thePlayer status] == AVPlayerStatusFailed) {
NSError *error = [self.avPlayer error];
// Respond to error: for example, display an alert sheet.
NSLog(#"error %#",error);
return;
}
NSLog(#"player status %ld",(long)[thePlayer status]);
// Deal with other status change if appropriate.
}
// Deal with other change notifications if appropriate.
//[super observeValueForKeyPath:keyPath ofObject:object
// change:change context:context];
return;
}
It works, but... only on the demo stream provided by Apple, nothing else I give it plays ...
** TRIED **
Tried adding this code into the mix too, which also works with the Apple demo stream, but none of the others I have tried.
NSURL *url = [NSURL URLWithString:#"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"];
AVURLAsset *avasset = [[AVURLAsset alloc] initWithURL:url options:nil];
avPlayerItem = [[AVPlayerItem alloc] initWithAsset:avasset];
self.avPlayer = [[AVPlayer alloc] initWithPlayerItem:avPlayerItem];
.......
** MORE UPDATES ** ... reworked observer since I wasn't getting useful info from it, now it tells me the Apple m3u8 is really to play; and "fails" on everything else I try...
So .... all these fail for example ...
//self.avPlayer = [AVPlayer playerWithURL:[NSURL URLWithString:#"http://content.uplynk.com/209da4fef4b442f6b8a100d71a9f6a9a.m3u8"]];
//self.avPlayer = [AVPlayer playerWithURL:[NSURL URLWithString:#"http://content.jwplatform.com/manifests/vM7nH0Kl.m3u8"]];
//self.avPlayer = [AVPlayer playerWithURL:[NSURL URLWithString:#"http://walterebert.com/playground/video/hls/sintel-trailer.m3u8"]];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == self.avPlayer.currentItem && [keyPath isEqualToString:#"status"]) {
if (avPlayer.currentItem.status == AVPlayerStatusFailed) {
NSError *error = [self.avPlayer error];
// Respond to error: for example, display an alert sheet.
NSLog(#"AVPlayerStatusFailed error %#",error);
return;
}
if (avPlayer.currentItem.status == AVPlayerStatusUnknown) {
NSError *error = [self.avPlayer error];
NSLog(#"AVPlayerStatusUnknown error %#",error);
}
if (avPlayer.currentItem.status == AVPlayerStatusReadyToPlay) {
NSLog(#"AVPlayerStatusReadyToPlay");
[self.avPlayer play];
}
//[super observeValueForKeyPath:keyPath ofObject:object
// change:change context:&ItemStatusContext];
// Deal with other status change if appropriate.
}
// Deal with other change notifications if appropriate.
//[super observeValueForKeyPath:keyPath ofObject:object
// change:change context:context];
return;
}

Phew, recall something about this before; no excuses really. Managed to fix it by looking somewhere completely different; in info.plist which needs this key in it to play arbitrary streams.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
I know it is bit lax, but I leave the reader to do so some more research if they want to make their app more bullet proof that I care about right now :) Do this, use the observer code in the EDITED section and cut out the [self.avPlayer play] (line 8) in the main code your be in business.

Related

How to force AVPlayer to fail when AVAssetResourceLoadingRequest's finishLoadingWithError(err) is called

I want to force AVPlayer to throw the player error, either through the playerFailedToReachEnd notification or observe player.status via KVO, when during the process of loading resource request via AVAssetResourceLoader that the request is finished loading with error.
It should not do the manual playback stop on AVPlayer to avoid dealing with the race condition between the manual stop and the KVO/notifications
Manual playback stop on AVPlayer when error occurred is refrained to avoid race condition.
Tried the part to return the callback 'resourceLoader:shouldWaitForLoadingOfRequestedResource:' to return NO it doesn't make AVPlayer change state to Failure nor does it send notification about player failure
#implementation AssetLoader <AVAssetResourceLoaderDelegate>
- (AVPlayerItem *)setupLoader {
NSURL *playbackUrl = [NSURL URLWithString:#"example-url"];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:playbackUrl options:nil];
[asset.resourceLoader setDelegate:self queue:_sample_queue];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
return playerItem;
}
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
if ([url.scheme isEqual:#"skd"] == NO) {
QPLogError(#"Unexpected url scheme: %#", url.absoluteString);
return NO;
}
LicenseAction *action = [[LicenseAction alloc] initWithLoadingRequest:loadingRequest];
[action execute:^(NSData *ckcData, NSError *error) {
if (error) {
[loadingRequest finishLoadingWithError:error]; //This should prompt AVPlayer to fail
} else {
[loadingRequest.dataRequest respondWithData:ckcData];
[loadingRequest finishLoading];
}
}];
return YES;
}
...
#end
#implementation Player {
AVPlayer *_player;
}
- (void)prepare {
[_player replaceCurrentItemWithPlayerItem:playerItem];
NSKeyValueObservingOptions options = (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial);
[_player addObserver:self forKeyPath:#"status" options:options context:&QPClearPlayerAVPlayerKVOContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerItemFailedToEnd:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:_player.currentItem];
}
- (void)stopWithError {
...
[self reportPlayerError];
}
...
- (void)playerItemFailedToEnd:(NSNotification *)notification {
...
[self reportPlayerError];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if(object == _player && keyPath == #"status") {
...
if (_player.status == AVPlayerStatusFailed) {
[self reportPlayerError];
}
}
}
...
#end
Expected
upon invoking AVAssetResourceLoadingRequest.finishLoadingWithError() that the AVPlayer would send a failed notification or KVO status changes
Actual
AVPlayer doesn't have status change nor failed notification

AvPlayer streaming, all the metadata information are in timedMetadata?

I'm creating an ios app with a streaming player maded with AvPlayer. This is my code:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self InitPlayer];
[self ReadMetaData];
}
-(void)InitPlayer{
NSURL *url = [[NSURL alloc] initWithString:#"http://www.fakeurl.com/stream"];
// create a player view controller
self.player = [AVPlayer playerWithURL:url];
player.closedCaptionDisplayEnabled = NO;
}
-(void)ReadMetaData{
[self.player.currentItem addObserver:self forKeyPath:#"timedMetadata" options:NSKeyValueObservingOptionNew context:nil];
}
- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object
change:(NSDictionary*)change context:(void*)context {
if ([keyPath isEqualToString:#"timedMetadata"])
{
AVPlayerItem* playerItem = object;
for (AVMetadataItem* metadata in playerItem.timedMetadata)
{
if([metadata.commonKey isEqualToString:#"title"]){
TitleLabel.text=metadata.stringValue;
}
}
}
}
With this code i can successfully play and pause the stream. I can also print the title of the track.
If i try to log the timedMetadata with something like this:
NSLog(#"%#",player.currentItem.timedMetadata);
I retrive that:
"<AVMetadataItem: 0x15649500, identifier=common/title, keySpace=comn, key class = __NSCFConstantString, key=title, commonKey=title, extendedLanguageTag=(null), dataType=(null), time={21888/44100 = 0.496}, duration={INVALID}, startDate=(null), extras={\n}, value=Keepin-'fake song title>"
Now my question is: for that specific stream url the timedMetadata i logged are the only metadata i can retrive? If yes how i can achive a more complex player type (something like "go to next track button","go to previous track button","an history of tracks",ecc...) ? That's the first time i work with stream data and in my expectations there was a lot of information in audio metadata. In real life seems i can get only the track title. There's a problem with my code or the stream source is poor in metadata info?
I tried to find best solution to get metadata from AVPlayer and found it:
-(IBAction) BtnGoClick:(id)sender {
NSURL *url = [[NSURL alloc] initWithString:#"http://cast.loungefm.com.ua/loungefm"];
[self setupAVPlayerForURL:url];
}
-(void) setupAVPlayerForURL: (NSURL*) url {
AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
AVPlayerItem *anItem = [AVPlayerItem playerItemWithAsset:asset];
player = [AVPlayer playerWithPlayerItem:anItem];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
[anItem addObserver:self forKeyPath:#"timedMetadata" options:NSKeyValueObservingOptionNew context:NULL];
}
- (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 Ready to Play");
} else if (player.status == AVPlayerItemStatusUnknown) {
NSLog(#"AVPlayer Unknown");
}
}
if ([keyPath isEqualToString:#"timedMetadata"])
{
AVPlayerItem* playerItem = object;
for (AVMetadataItem* metadata in playerItem.timedMetadata)
{
if([metadata.commonKey isEqualToString:#"title"]){
NSLog(#"%#",metadata.stringValue);
}
}
}
}
Result:

Sometimes AVPlayer stalling and seekToTime: not respondin

I'm playing youtube videos using AVPlayer as follows,
- (void)startYoutubeVideoAtUrl:(NSURL *)videoUrl
{
NSLog(#"start player at url : %#", videoUrl);
[HCYoutubeParser h264videosWithYoutubeURL:videoUrl completeBlock:^(NSDictionary *videoDictionary, NSError *error) {
if (videoDictionary && videoDictionary.count > 0) {
NSString *URLString = [self chooseYoutubeUrlFromUrlList:videoDictionary];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[self videoURLWithCustomScheme:#"streaming" uRLString:URLString] options:nil];
[asset.resourceLoader setDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
pendingRequests = [NSMutableArray array];
avPlayerItem = [AVPlayerItem playerItemWithAsset:asset];
[self startVideoPlayBack];
}
else {
[_delegate failedStartPalyInlineVideo];
}
}];
}
-(void)startVideoPlayBack
{
startTime = CFAbsoluteTimeGetCurrent();
avPlayer = [[AVQueuePlayer alloc] initWithPlayerItem:avPlayerItem];
avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:avPlayer];
[avPlayerItem addObserver:self forKeyPath:#"status" options:0 context:nil];
[avPlayerLayer addObserver:self forKeyPath:#"readyForDisplay" options:NSKeyValueObservingOptionNew context:nil];
avPlayerLayer.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self.view.layer addSublayer:avPlayerLayer];
[self watchApiCall];
avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[avPlayer currentItem]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemPlaybackStalled:)
name:AVPlayerItemPlaybackStalledNotification
object:[avPlayer currentItem]];
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
#try {
if (!avPlayer) {
return;
}
if (avPlayerItem.status == AVPlayerStatusReadyToPlay) {
}
else if (avPlayerItem.status == AVPlayerStatusFailed) {
NSLog(#"----- AVPlayerStatusFailed ----");
[self playbackVideo];
}
if (object == avPlayerLayer && [keyPath isEqualToString:#"readyForDisplay"]) {
[self resetNetworkSpeedUsingLoadingTime];
if (avPlayerLayer.readyForDisplay) {
id<LoopingVideoDelegate> strongDelegate = self.delegate;
if([strongDelegate readyForVideoDisplay]) {
[avPlayer play];
}
else {
[self removePlayer];
}
}
}
}
#catch(NSException *ex) {
NSLog(#"EXCEPTION : %#", ex);
}
}
My issue is some times the video getting stalled and fires AVPlayerItemPlaybackStalledNotification. Also it's not responding [avPlayerItem seekToTime:kCMTimeZero]; sometimes after called AVPlayerItemDidPlayToEndTimeNotification selector. I wasn't able to find a solution for this. I checked with,
[HCYoutubeParser thumbnailForYoutubeURL:videoUrl thumbnailSize:YouTubeThumbnailDefaultHighQuality completeBlock:^(UIImage *image, NSError *error) {
if (!error) {
[HCYoutubeParser h264videosWithYoutubeURL:videoUrl completeBlock:^(NSDictionary *videoDictionary, NSError *error) {
NSString *URLString = [self chooseYoutubeUrlFromUrlList:videoDictionary];
NSURL *urlToLoad = [NSURL URLWithString:URLString];
avPlayerItem = [[AVPlayerItem alloc] initWithURL:urlToLoad];
[self startVideoPlayBack];
}];
}
else {
NSLog(#"error in youtube parser");
}
}];
and there is no any player stall issue or seekToTime: not responding issue with that. Please help.
Observe your AVPlayerItem's loadedTimeRanges and seekableTimeRanges property to make make sure that the AVPlayer has loaded playable data yet. When steaming, the AVPlayer often pause falling short of playable(data that can be played by AVPlayer) data. This should help investigate further into the issue. You can also try to start playback when you get avPlayerItem.status == AVPlayerStatusReadyToPlay by calling play on your AVPlayer
From AV Foundation Programming Guide
Monitoring Playback
You can monitor a number of aspects of both the presentation state of a player and the player item being played. This is particularly useful for state changes that are not under your direct control. For example:
If the user uses multitasking to switch to a different application, a player’s rate property will drop to 0.0.
If you are playing remote media, a player item’s loadedTimeRanges and seekableTimeRanges properties will change as more data becomes available.
These properties tell you what portions of the player item’s timeline are available.
A player’s currentItem property changes as a player item is created for an HTTP live stream.
A player item’s tracks property may change while playing an HTTP live stream.
This may happen if the stream offers different encodings for the content; the tracks change if the player switches to a different encoding.
A player or player item’s status property may change if playback fails for some reason.
You can use key-value observing to monitor changes to values of these properties.

AVPlayerViewController not playing video and showing Quicktime logo

I am trying to play video with new AVPlayerViewController introduced in XCode 6.
To play video i have done this setup.
Using local mp4 file to play video
Extended AVPlayerViewController
Player setup code :
-(void)setupPlayer
{
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"exodus_trailer" ofType:#"mp4"];
NSLog(#"File Path : %#",filePath);
AVAsset *avAsset = [AVAsset assetWithURL:[NSURL URLWithString:filePath]];
AVPlayerItem *avPlayerItem =[[AVPlayerItem alloc]initWithAsset:avAsset];
self.player = [[AVPlayer alloc]initWithPlayerItem:avPlayerItem];
[self.player addObserver:self forKeyPath:#"status" options:0 context:nil];
[self.player play];
}
KVO handling :
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == self.player && [keyPath isEqualToString:#"status"])
{
if (self.player.status == AVPlayerStatusFailed)
{
NSLog(#"AVPlayer Failed");
}
else if (self.player.status == AVPlayerStatusReadyToPlay)
{
NSLog(#"AVPlayerStatusReadyToPlay");
[self.player play];
}
else if (self.player.status == AVPlayerItemStatusUnknown)
{
NSLog(#"AVPlayer Unknown");
}
}
}
Issue :
KVO log is printing AVPlayerStatusReadyToPlay and file path seems ok. Previously view was at least showing player with all default controls, but now without any changes it started to show Quick time logo without any control. What is meaning of showing this logo ? what i am doing wrong here ?
Screenshot :
Here is exactly like what you wanted to do. The problem with your code is that you are not using file path, use this to load the file path NSURL *url = [NSURL fileURLWithPath:urlString]
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *urlString = [[NSBundle mainBundle] pathForResource:#"testfile" ofType:#"mp4"];
NSURL *url = [NSURL fileURLWithPath:urlString];
self.player = [[AVPlayer alloc] initWithURL:url];
[self.player addObserver:self forKeyPath:#"status"
options:NSKeyValueObservingOptionNew
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == self.player) {
AVPlayerStatus status = [change[NSKeyValueChangeNewKey] integerValue];
if (status == AVPlayerStatusReadyToPlay) {
[self.player play];
[self.player removeObserver:self forKeyPath:#"status"];
}
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#end

avplayer is not playing the URL

I am using avplayer for play audio url, but it is not working, I don't know where i am wrong
NSString *radioURL = #"https://www.example.com";
radioPlayer = [AVPlayer playerWithURL:[NSURL URLWithString:radioURL]] ;
// [radioPlayer seekToTime:kCMTimeZero];
NSLog(#"radio player %#",radioPlayer.currentItem);
[radioPlayer play];
Any help would be appreciated.
I strongly recommended the code below to play radio streaming: please take a look also AVPlayer_Class
-(void)Play{
NSString *radioURL = #"https://www.example.com"; //this url must valid
AVPlayer *player = [[AVPlayer alloc]initWithURL:[NSURL URLWithString:radioURL]];
self.songPlayer = player; //self.songPlayer is a globle object of avplayer
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[songPlayer currentItem]];
[self.songPlayer addObserver:self forKeyPath:#"status" options:0 context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == songPlayer && [keyPath isEqualToString:#"status"]) {
if (songPlayer.status == AVPlayerStatusFailed) {
NSLog(#"AVPlayer Failed");
} else if (songPlayer.status == AVPlayerStatusReadyToPlay) {
NSLog(#"AVPlayerStatusReadyToPlay");
[self.songPlayer play];
} else if (songPlayer.status == AVPlayerItemStatusUnknown) {
NSLog(#"AVPlayer Unknown");
}
}
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
// code here to play next sound file
}
Ref link is - Streaming mp3 audio with AVPlayer
I had the same issue and just realized that I wasn't retaining the player (using ARC)! So it gets deallocated and stop playing immediately after start.
You need to make sure that you have a strong property radioPlayer and use self.radioPlayer instead of radioPlayer.
You are doing good but your method need to correct on one place.Check using the below method:
NSURL *audioURL = [NSURL URLWithString:radioURL];
NSData *audioData = [NSData dataWithContentsOfURL:audioURL];
self.audioPlayer = [[AVAudioPlayer alloc] initWithData:audioData error:nil];
self.audioPlayer.numberOfLoops = -1;
self.audioPlayer.delegate = self;
[self.audioPlayer prepareToPlay];
self.audioPlayer.volume=1.0;
[self.audioPlayer autorelease];
[self.audioPlayer Play];
and add proper delegate methods for AVAudioPlayer.
Make Sure URL is AddingPercentEscapes.
NSURL *audioURL = // Your Url;
NSString *encodedString = [#"Your Url" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *myURL = [[NSURL alloc] initWithString:encodedString]
AVPlayer *player = [AVPlayer playerWithURL:myURL];
[player prepareToPlay];
player play];
Did you forgot to set the attribute App Transport Security Settings -> Allow Arbitrary Loads (Yes)?

Resources