I play video streams with AVPlayer.
Some of streams plays only if click button "Play" for 2-5 times. (sorry, I can't provide link, and don't sure what especial in this streams, but may be problem only with full hd streams)
I do like that:
fields:
AVPlayerItem *playerItem;
AVPlayer *player;
AVPlayerViewController *playerViewController;
in viewDidLoad:
player = [[AVPlayer alloc] init];
playerViewController = [[AVPlayerViewController alloc] init];
playerViewController.player = player;
when click "Play":
NSURL *videoURL = [NSURL URLWithString:strUrl];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:videoURL options:nil];
playerItem = [AVPlayerItem playerItemWithAsset:asset];
[playerItem addObserver:self
forKeyPath:#"status"
options: NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:nil];
[player replaceCurrentItemWithPlayerItem:playerItem];
and observing:
NSLog(#"status changed");
if (((AVPlayerItem *)object).status == AVPlayerStatusReadyToPlay)
{
NSLog(#"ready to play");
[playerItem removeObserver:self forKeyPath:#"status"];
[self presentViewController:playerViewController animated:YES completion:nil];
[player play];
}
else if (((AVPlayerItem *)object).status == AVPlayerStatusFailed)
{
NSLog(#"failed to ready");
}
else if (((AVPlayerItem *)object).status == AVPlayerStatusUnknown)
{
NSLog(#"unknown");
}
First always "unknown" but in problem cases no messages after (but most streams play normally and I see "ready to play" message)
I've tried to observe player.status but when it ReadyToPlay and I start play I see only first frame and endless message "Loading" in top of screen.
Related
Here is code for initialise AVQueuePlayer.
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] init];
queuePlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
if ([queuePlayer respondsToSelector:#selector(automaticallyWaitsToMinimizeStalling)])
queuePlayer.automaticallyWaitsToMinimizeStalling = NO;
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url];
if (playerItem)
[queuePlayer insertItem:playerItem afterItem:nil];
if ([queuePlayer respondsToSelector:#selector(playImmediatelyAtRate:)])
[queuePlayer playImmediatelyAtRate:1.0];
else [queuePlayer play];
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.
Im working on a app which stream a audio file online. The code below is working, but when the audio file ends, the play button don't reset. The button is still pressed, and before the user can play the audio file again, the user have to press the button 2 times.
My question is: How do i reset the play button automatically after the audio file ends?
Here is my code:
-(IBAction)togglePlayPauseTapped:(id)sender
{
if(self.togglePlayPause.selected) {
[self.audioPlayer pause];
[self.togglePlayPause setSelected:NO];
} else {
[self.audioPlayer play];
[self.togglePlayPause setSelected:YES];
self.audioPlayer = [[AVPlayer alloc] init];
NSString *urlString = #"MYURL";
NSURL* urlStream = [NSURL URLWithString:urlString];
AVPlayerItem * currentItem = [AVPlayerItem playerItemWithURL:urlStream];
[self.audioPlayer replaceCurrentItemWithPlayerItem:currentItem];
[self.audioPlayer play];
}
}`
Set the delegate of audioPlayer to self and make sure the designated ViewController can receive AVAudioPlayer Delegate calls. Then implement the audioPlayerDidFinishPlaying method and call your togglePlayPauseTapped method:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
[self togglePlayPauseTapped:nil];
}
The total should look something like this:
Edit:
And change this
self.audioPlayer = [[AVPlayer alloc] init];
To this:
self.audioPlayer = [[AVAudioPlayer alloc] init];
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)?
I have an iOS app which works like this:
First view
3 buttons that link to different views, each has its own audio file within
Imagine user opens one view, and starts to play the audio file. When they navigate around the app, the audio file still plays which is fine, Im happy with that.
However, if user then opens the second audio file and presses play, it plays on top of the first one so 2 audios are playing together.
How do I tell the code that if another play button is pressed it should stop the first audio file that is playing??
I am using AVFoundation framework to do this
Code is as follows:
NSString *stringPath = [[NSBundle mainBundle]pathForResource:#"01" ofType:#"MP3"];
NSURL *url = [NSURL fileURLWithPath:stringPath];
NSError *error;
avPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
[avPlayer setNumberOfLoops:2];
[avPlayer setVolume:self.sliderVolumeOutlet.value];
[NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:#selector(updateMyProgress) userInfo:nil repeats:YES];
-(void)updateMyProgress
{
float progress = [avPlayer currentTime]/[avPlayer duration];
self.myProgressView.progress = progress;
}
- (IBAction)sliderVolumeAction:(id)sender {
UISlider *mySlider = sender;
[avPlayer setVolume:mySlider.value];
}
- (IBAction)stopButton:(id)sender {
[avPlayer stop];
[avPlayer setCurrentTime:0];
}
- (IBAction)pauseButton:(id)sender {
[avPlayer pause];
}
- (IBAction)playButton:(id)sender {
[avPlayer play];
}
- (IBAction)backButtonEvent:(id)sender {
[self dismissViewControllerAnimated:YES completion:NULL];
}
You can check whether it is playing the previous track or not by if ([AVPlayer rate] != 0.0)
If it is playing then you can either add the next track into queue or just use this function to replace current track with the new one - (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item
Perhaps creating some sort of playSound function would be better suited for this. Something like this:
- (void)playSoundWithFileName:(NSString *)fileName andExtension:(NSString *)extension {
NSString *stringPath = [[NSBundle mainBundle]pathForResource:fileName ofType:extension];
NSURL *url = [NSURL fileURLWithPath:stringPath];
NSError *error;
if (avPlayer) {
[avPlayer stop];
avPlayer = nil;
}
avPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
if (error)
NSLog(#"Error");
else {
[avPlayer setNumberOfLoops:2];
[avPlayer setVolume:self.sliderVolumeOutlet.value];
[avPlayer play];
}
}
Use:
[self playSoundWithFileName:#"01" andExtension:#"MP3"];
[NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:#selector(updateMyProgress) userInfo:nil repeats:YES];
and call -(void)playSoundWithFileName whenever you want to play another sound.
Or if you were using the AVPlayer class:
- (void)playSoundWithFileName:(NSString *)fileName andExtension:(NSString *)extension {
NSString *stringPath = [[NSBundle mainBundle]pathForResource:fileName ofType:extension];
NSURL *url = [NSURL fileURLWithPath:stringPath];
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
if (avPlayer)
[avPlayer replaceCurrentItemWithPlayerItem:item];
else
avPlayer = [AVPlayer playerWithPlayerItem:item];
[avPlayer setVolume:self.sliderVolumeOutlet.value];
[avPlayer play];
}
Though the latter doesn't allow you set a number of loops.