I really new to Obj-C and iOS development, i found very much useful information here, but here is a question I didn't find the answer.
I got instance of AVQueuePlayer which plays audio stream from url.
How can I know that audio stream is loaded? For example when I press "Play" button, there is couple of seconds delay between a button press and actual start of streaming.
I looked at developer.apple.com library and didn't find any method that I can use to check status of AVQueuePlayer. There is one in AVPLayer, but AVPlayer is not supporting stream over http as far as i know.
Thank you.
I am not sure what you mean by "loaded": do you mean when the item is fully loaded or when the item is ready to play?
AVQueuePlayer supports http streams (HTTP Live and files) in the same way as AVPlayer. You should review the AVFoundation Programming Guide, Handling Different Types of Asset.
The most common case is when an item is ready to play, so I'll answer that one. If you are working with iOS with AVQueuePlayer < 4.3, you need to check the status of AVPlayerItem by observing the value of the AVPlayerItem status key:
static int LoadingItemContext = 1;
- (void)loadExampleItem
{
NSURL *remoteURL = [NSURL URLWithString:#"http://media.example.com/file.mp3"];
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:remoteURL];
// insert the new item at the end
if (item) {
[self registerAVItemObserver:item];
if ([self.player canInsertItem:item afterItem:nil]) {
[self.player insertItem:item afterItem:nil];
// now observe item.status for when it is ready to play
}
}
}
- (void)registerAVItemObserver:(AVPlayerItem *)playerItem
{
[playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:(void*)&LoadingItemContext];
}
- (void)removeAVItemObserver:(AVPlayerItem *)playerItem
{
#try {
[playerItem removeObserver:self forKeyPath:#"status"];
}
#catch (...) { }
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == &LoadingItemContext) {
AVPlayerItem *item = (AVPlayerItem*)object;
AVPlayerItemStatus status = item.status;
if (status == AVPlayerItemStatusReadyToPlay) {
// now you know you can set your player to play, update your UI...
} else if (status == AVPlayerItemStatusFailed) {
// handle error here, i.e., skip to next item
}
}
}
That is just a pre-4.3 example. After 4.3 you can load a remote file (or HTTP Live playlist) using the code example in AVFoundation Programming Guide, Preparing an Asset For Use, with loadValuesAsynchronouslyForKeys:completionHandler. If you are using loadValuesAsynchronouslyForKeys for a HTTP Live stream you should observe the #"tracks" property.
Related
I'm using AVPlayer to play audio from remote server.
I want to display a progress bar showing the buffering progress and the "time played" progress, like the MPMoviePlayerController does when you play a movie.
Does the AVPlayer has any UI component that display this info? If not, how can I get this info (buffering state)?
Thanks
Does the AVPlayer has any UI component that display this info?
No, there's no UI component for AVPlayer.
If not, how can I get this info (buffering state)?
You should observer AVPlayerItem.loadedTimeRanges
[yourPlayerItem addObserver:self forKeyPath:#"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
then using KVO to watch
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if(object == player.currentItem && [keyPath isEqualToString:#"loadedTimeRanges"]){
NSArray *timeRanges = (NSArray*)[change objectForKey:NSKeyValueChangeNewKey];
if (timeRanges && [timeRanges count]) {
CMTimeRange timerange=[[timeRanges objectAtIndex:0]CMTimeRangeValue];
}
}
}
timerange.duration is what you expect for.
And you have to draw buffer progress manually.
Refs here.
I have some questions related to AVPlayer which are:
When we pause the AVPlayer through [player pause] does the AVPlayer keep buffering the video from the network or does it just stop? I couldn't get any info related to this in apple's documentation. Also, is it possible to force the AVPlayer to keep buffering while in pause, so that if we have the paused video is in waiting for the first video to be ended then we wouldn't find any gap in between the videos?
On pausing the AVPlayer can we have any event on [player pause].
Can we show still image on AVPlayer for some seconds?
Thanks
1) AVPlayer will buffer the video in several cases, none cleary documented. I'd say you can expect buffering when you init the video, and when you replace the current item.
You can observe currentItem.loadedTimeRanges to know what's going on. That property will tell you which video time ranges has been loaded.
Also, there is a few other currentItem properties that may help you: playbackLikelyToKeepUp, playbackBufferFull and playbackBufferEmpty.
Achieving a perfect gapless playback is not easy.
/* player is an instance of AVPlayer */
[player addObserver:self
forKeyPath:#"currentItem.loadedTimeRanges"
options:NSKeyValueObservingOptionNew
context:kTimeRangesKVO];
In observeValueForKeyPath:ofObject:change:context::
if (kTimeRangesKVO == context) {
NSArray *timeRanges = (NSArray *)[change objectForKey:NSKeyValueChangeNewKey];
if (timeRanges && [timeRanges count]) {
CMTimeRange timerange = [[timeRanges objectAtIndex:0] CMTimeRangeValue];
NSLog(#" . . . %.5f -> %.5f", CMTimeGetSeconds(timerange.start), CMTimeGetSeconds(CMTimeAdd(timerange.start, timerange.duration)));
}
}
2) Just keep an eye on player.rate.
[player addObserver:self
forKeyPath:#"rate"
options:NSKeyValueObservingOptionNew
context:kRateDidChangeKVO];
Then in your observeValueForKeyPath:ofObject:change:context::
if (kRateDidChangeKVO == context) {
NSLog(#"Player playback rate changed: %.5f", player.rate);
if (player.rate == 0.0) {
NSLog(#" . . . PAUSED (or just started)");
}
}
3) You can build a movie of a given length using a still image but it's easier to use a regular UIImageView on top of the player. Hide/show it when needed.
Sample project: feel free to play with the code I wrote to support my answer.
i am getting wrong playback state in MP music player.
while playing a song i am getting pause state.
My app is working fine in ios 4.but i am having this issue in ios 5.
can anybody Help me ??
My code is here.
[musicPlayer stop];
if (userMediaItemCollection)
{
userMediaItemCollection=nil;
}
musicPlayer.nowPlayingItem=nil;
userMediaItemCollection=[MPMediaItemCollection collectionWithItems:[mediaItemCollection items]];
[musicPlayer setQueueWithItemCollection:userMediaItemCollection];
[musicPlayer setNowPlayingItem:
[[userMediaItemCollectionitems]objectAtIndex:indexOfCurrentObject]];
[self enablePrevAndNextButtons];
[musicPlayer play];
}
-(void)playbackStateDidChanged:(NSNotification *)notification
{
if (musicPlayer.playbackState!=MPMusicPlaybackStatePlaying)
{
[playPauseButton setBackgroundImage:[UIImage imageNamed:#"play_iPad.png"] forState:UIControlStateNormal];
}
else if(musicPlayer.playbackState==MPMusicPlaybackStatePlaying)
{
[playPauseButton setBackgroundImage:[UIImage imageNamed:#"pause_iPad.png"] forState:UIControlStateNormal];
}
I have also reported this bug to Apple. I was able to reproduce it 100% of the time by doing the following:
Launch application that uses MPMusicPlayerController.
Launch the "Music" App.
Hit Play, Skip, Skip, Pause, Play, Pause
Open the original application and the MPMusicPlaybackState of MPMusicPlayerController will be incorrect.
None of the proposed solutions here worked for me. The solution that did work was to keep track of when the bug was occurring and updating the UI specially in these cases.
When the UIApplicationDidBecomeActiveNotification notification is received (see matbur post for more details on this), see if audio is actually not playing when the MPMusicPlaybackState said it was:
-(BOOL) isPlaybackStateBugActive {
MPMusicPlaybackState playbackState = self.musicPlayer.playbackState;
if (playbackState == MPMusicPlaybackStatePlaying) {
AudioSessionInitialize (NULL, NULL, NULL, NULL);
UInt32 sessionCategory = kAudioSessionCategory_AmbientSound;
AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof (sessionCategory), &sessionCategory);
AudioSessionSetActive (true);
UInt32 audioIsPlaying;
UInt32 size = sizeof(audioIsPlaying);
AudioSessionGetProperty(kAudioSessionProperty_OtherAudioIsPlaying, &size, &audioIsPlaying);
if (!audioIsPlaying){
NSLog(#"PlaybackState bug is active");
return YES;
}
}
return NO;
}
Don't forget to import the AudioToolbox framework.
None of these workarounds fix the issue for my app. It is a bug in iOS, and my app will never function properly until Apple fixes it.
I have a music player with a play/pause button. When music is playing, the button shows the "pause" icon. When music is paused, the button shows the "play" icon - just like all music apps. I can replicate the bug at any time by doing the following:
1. Play music in my app (the play/pause button shows the "pause" icon correctly)
2. Background my app and lock my phone for ~10 minutes
3. Double tap home and hit the pause button from the iPod controls
4. Unlock my phone and open my app again
5. Music will be stopped, but my app still shows the "pause" icon when it should so "play"
I've done extensive debugging and logging to ensure that the method that updates my play/pause button is always called when my application becomes active. The issue is that when I re-enter my app, the playback state of MPMusicPlayer is still set to MPMusicPlaybackStatePlaying even when music is stopped/paused.
I filed a bug report for this about a year ago and haven't heard anything from Apple. If someone else would file one it would be greatly appreciated.
I have the same problem but when I play/pause many times when my app is in background, I reported the bug to Apple and hope to get an amswer soon, I want to know if my coding is wrong or there is an API problem. If this was the error you were having, this may be helpful. I came to a workaround which even though isnt the best solution, for my app is acceptable:
In the viewDidLoad, add this:
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver: self
selector: #selector (handle_ApplicationDidBecomeActive:)
name: UIApplicationDidBecomeActiveNotification
object: nil];
Then, create a handle_ApplicationDidBecomeActive method, and add this:
- (void) handle_ApplicationDidBecomeActive: (id) notification
{
if (musicPlayer.playbackState!=MPMusicPlaybackStatePlaying)
{
[playPauseButton setBackgroundImage:[UIImage imageNamed:#"play_iPad.png"] forState:UIControlStateNormal];
[musicPlayer pause];
}
else if(musicPlayer.playbackState==MPMusicPlaybackStatePlaying)
{
[playPauseButton setBackgroundImage:[UIImage imageNamed:#"pause_iPad.png"] forState:UIControlStateNormal];
[musicPlayer pause];
}
}
(dont put this code inside your playbackStateDidChanged method as this may generate an endless loop)
This will sync the state of your buttons and music player to the one reported by the API. in the cases in which there is a coincidence, there will be no impact of any type, in the other cases, the player will pause/play accordingly.
I experienced the same problem with the release of iOS 5. I found that the playbackState property does get updated, but after a delay, so it's not yet set when your playbackStateDidChanged method runs.
My workaround was to set my own instance variable called musicPlayerRecentlyStartedPlaying whenever I start playback. Then I use a method called from a timer to check both that variable and the playbackState property to find out if the player is really playing:
- (void)playMusic {
[self.musicPlayer play];
self.musicPlayerRecentlyStartedPlaying = TRUE;
self.musicControlsUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateMusicControls:) userInfo:nil repeats:TRUE];
}
- (void)stopMusic {
[self.musicPlayer stop];
self.musicPlayerRecentlyStartedPlaying = FALSE;
[self.musicControlsUpdateTimer invalidate];
}
- (void)updateMusicControls:(NSTimer *)timer {
BOOL playing = (([self.musicPlayer playbackState] == MPMusicPlaybackStatePlaying)&&(self.musicPlayer.nowPlayingItem));
if (!playing) {
// check to see if we recently started playing
if (self.musicPlayerRecentlyStartedPlaying) {
playing = TRUE;
}
} else {
// once the property is updated, we no longer need this
self.musicPlayerRecentlyStartedPlaying = FALSE;
}
}
You might not need to call updateMusicControls from a timer, but I do because I'm also updating the position of a progress bar as the music plays.
This code using when previous Button click Previous song will be play
- (IBAction)playPreviousSongInList:(id)sender {
static NSTimeInterval skipToBeginningOfSongIfElapsedTimeLongerThan = 3.5;
NSTimeInterval playbackTime = self.musicPlayer.currentPlaybackTime;
if (playbackTime <= skipToBeginningOfSongIfElapsedTimeLongerThan) {
[self.musicPlayer skipToPreviousItem];
} else {
[self.musicPlayer skipToBeginning];
}
}
I'm trying to create an iPad app where i need to playback a quicktime movie which contains some chapter markers. When each marker is reached i need a small overlay to be shown on top of the video.
Is there a way to trigger an event/function each time a marker is reached? And if so, how?
I'm not interested in having to develop an entire movieplayer with codec handling from scratch, since this is out of my comfortzone - So I'm hoping this is possible using MPMoviePlayer or something similar.
Any help is greatly appreciated! :)
Register to receive the following notification:
#define MPAVControllerTimeDidJumpNotification #"MPAVControllerTimeDidJumpNotification"
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleTimeChanged:) name:MPAVControllerTimeDidJumpNotification object:nil];
-(void)handleTimeChanged:(NSNotification *)notification
{
static int i = 0;
NSDictionary * userInfo = notification.userInfo;
int lastPositionInSeconds = [[userInfo valueForKey:#"MPAVControllerTimeParameter"] intValue];
if(lastPositionInSeconds > markers[i])
{
i++;
[self showOverlay: i];
}
}
Also register to receive MPMoviePlayerPlaybackDidFinishNotification notification to stop listening for the MPAVControllerTimeDidJumpNotification notification.
how do I play a remote .mp3 in an iPhone app with full control over the playback controls and visuals shown? I know MPMoviePlayerController can stream .mp3s but it takes over the whole screen.
If you could just point me in the direction of an appropriate guide or class reference that would be great.
I dont think AVAudioPlayer supports streaming mp3s. From my experience, i am reasonably confident in saying that it only plays local files.
I have played remote mp3s using AVPlayer.
self.player = [[AVPlayer alloc] initWithURL:fileURL];
//inits the AVPlayer object.
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
// You need to watch for for when the player obeject becomes ready to play, using key-value observation
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if(object == player && [keyPath isEqualToString:#"status"])
{
playButton.enabled = YES;
playButton.titleLabel.text = #"Play";
.....
//do whatever you need to do here to get the object ready to play.
}
}
a simple [player play] will start playing the file.
//metadata is available at
player.currentItem.asset.commonMetadata
//and time played can be calculated by dividing x by y
UInt64 x = self.player.currentTime.value;
int32_t y = self.player.currentTime.timescale;
int secs = x/y;
You might want to use AVAudioPlayer and create your own controls.
I don't know if MPMediaPlayer can play a streamed mp3, but it can play local media in an "app" player instead of an "ipod" player (which takes over the screen). MPMediaPlayer is similar to AVAudioPlayer in that you can play with or without any UI, which you can create. Play, pause, stop, rewind, forward, etc.