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];
}
}
Related
I start -play with AVAudioPlayer, and then set the nowPlaying dictionary like this:
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage: [UIImage imagedNamed:#"AlbumArt"]];
[songInfo setObject:#"Audio Title" forKey:MPMediaItemPropertyTitle];
[songInfo setObject:#"Audio Author" forKey:MPMediaItemPropertyArtist];
[songInfo setObject:#"Audio Album" forKey:MPMediaItemPropertyAlbumTitle];
[songInfo setObject:albumArt forKey:MPMediaItemPropertyArtwork];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
The lock screen always shows a pause button. I receive the remote control events correctly and I can toggle play/pause through the remote control events, but the lock screen keeps showing "pause" even when it is playing.
Now I seen this work with MPMoviePlayerController. Can someone explain how does the MPNowPlayingInfoCenter determine if it should show a play or a pause button?
Have you set the correct AVAudioSessionCategory on the AudioSession? it needs to be AVAudioSessionCategoryPlayback I believe to get it to work.
I'm not using MPNowPlaying at the moment, but apparently I have to, in order to get the audio info displayed on the lock screen.
However, in addition to what #user3061915 said, to manage the play/pause button, I've used UIEventTypeRemoteControl and it works perfect for controlling the play/pause button:
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
//if it is a remote control event handle it correctly
if (event.type == UIEventTypeRemoteControl)
{
if (event.subtype == UIEventSubtypeRemoteControlPlay)
{
[self playAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlPause)
{
[self pauseAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlTogglePlayPause)
{
[self togglePlayPause]; //This method will handle the toggling.
}
}
I just fixed a problem like this in my own app. I originally used [[AVAudioSession sharedInstance] setCategory:withOptions:error:] and supplied AVAudioSessionCategoryOptionMixWithOthers and AVAudioSessionCategoryOptionDuckOthers. This turned out to be my problem. If you set mix with others, you get no remote control events. They still go to the iPod app. If you set duck others, you get remote control events, but it appears as though it causes the problem you describe: the play/pause button shows the wrong thing. I'm not sure why. I got the play/pause button to behave by setting options to 0, or actually just calling setCategory:error:.
I am using Avplayer to show video clips and when i go back (app in background) video stop. How can i keep playing the video?
I have search about background task & background thread ,IOS only support music in background (Not video)
http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html
here is some discussion about play video in background
1) https://discussions.apple.com/thread/2799090?start=0&tstart=0
2) http://www.cocoawithlove.com/2011/04/background-audio-through-ios-movie.html
But there are many apps in AppStore, that play video in Background like
Swift Player : https://itunes.apple.com/us/app/swift-player-speed-up-video/id545216639?mt=8&ign-mpt=uo%3D2
SpeedUpTV : https://itunes.apple.com/ua/app/speeduptv/id386986953?mt=8
This method supports all the possibilities:
Screen locked by the user;
List item
Home button pressed;
As long as you have an instance of AVPlayer running iOS prevents auto lock of the device.
First you need to configure the application to support audio background from the Info.plist file adding in the UIBackgroundModes array the audio element.
Then put in your AppDelegate.m into
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions:
these methods
[[AVAudioSession sharedInstance] setDelegate: self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
and #import < AVFoundation/AVFoundation.h >
Then in your view controller that controls AVPlayer
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
and
- (void)viewWillDisappear:(BOOL)animated
{
[mPlayer pause];
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
then respond to the
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
switch (event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
if([mPlayer rate] == 0){
[mPlayer play];
} else {
[mPlayer pause];
}
break;
case UIEventSubtypeRemoteControlPlay:
[mPlayer play];
break;
case UIEventSubtypeRemoteControlPause:
[mPlayer pause];
break;
default:
break;
}
}
Another trick is needed to resume the reproduction if the user presses the home button (in which case the reproduction is suspended with a fade out).
When you control the reproduction of the video (I have play methods) set
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
and the corresponding method to be invoked that will launch a timer and resume the reproduction.
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
[mPlayer performSelector:#selector(play) withObject:nil afterDelay:0.01];
}
Its works for me to play video in Backgorund.
Thanks to all.
If you try to change the background mode:
Sorry, App store wont approve it.MPMoviePlayerViewController playback video after going to background for youtube
In my research, someone would take the sound track out to play in te background when it goes into background as the video would be pause and get the playbacktime for resume playing when go into foreground
It is not possible to play background music/video using Avplayer. But it is possible using
MPMoviePlayerViewController. I have done this in one of my app using this player & this app
is run successfully to appstore.
Try with this snippet, I've already integrated this with my app & it's being useful for me..hope this will work for you!!
Follow the steps given below:
Add UIBackgroundModes in the APPNAME-Info.plist, with the selection
App plays audio
Then add the AudioToolBox framework to the folder frameworks.
In the APPNAMEAppDelegate.h add:
-- #import < AVFoundation/AVFoundation.h>
-- #import < AudioToolbox/AudioToolbox.h>
In the APPNAMEAppDelegate.m add the following:
// Set AudioSession
NSError *sessionError = nil;
[[AVAudioSession sharedInstance] setDelegate:self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&sessionError];
/* Pick any one of them */
// 1. Overriding the output audio route
//UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
//AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute,
sizeof(audioRouteOverride), &audioRouteOverride);
========================================================================
// 2. Changing the default output audio route
UInt32 doChangeDefaultRoute = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker, sizeof(doChangeDefaultRoute), &doChangeDefaultRoute);
into the
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
but before the two lines:
[self.window addSubview:viewController.view];
[self.window makeKeyAndVisible];
Enjoy Programming!!
Swift version for the accepted answer.
In the delegate:
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
In the view controller that controls AVPlayer
override func viewDidAppear(animated: Bool) {
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
self.becomeFirstResponder()
}
override func viewWillDisappear(animated: Bool) {
mPlayer.pause()
UIApplication.sharedApplication().endReceivingRemoteControlEvents()
self.resignFirstResponder()
}
Don't forget to "import AVFoundation"
In addition to fattomhk's response, here's what you can do to achieve video forwarding to the time it should be after your application comes in foreground:
Get currentPlaybackTime of playing video when go to background and store it in lastPlayBackTime
Store the time when application goes to background (in userDefault probably)
Again get the time when application comes in foreground
Calculate the duration between background and foreground time
Set current playback time of video to lastPlayBackTime + duration
If you are Playing video using WebView you can handle using javascript to play video on background.
strVideoHTML = #"<html><head><style>body.........</style></head> <body><div id=\"overlay\"><div id=\"youtubelogo1\"></div></div><div id=\"player\"></div> <script> var tag = document.createElement('script'); tag.src = \"http://www.youtube.com/player_api\"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); var player; events: { 'onReady': onPlayerReady, } }); } function onPlayerReady(event) { event.target.playVideo(); } function changeBG(url){ document.getElementById('overlay').style.backgroundImage=url;} function manualPlay() { player.playVideo(); } function manualPause() { player.pauseVideo(); } </script></body> </html>";
NSString *html = [NSString stringWithFormat:strVideoHTML,url, width, height,videoid;
webvideoView.delegate = self;
[webvideoView loadHTMLString:html baseURL:[NSURL URLWithString:#"http://www.your-url.com"]];
On view diappear call
strscript = #"manualPlay();
[webvideoView stringByEvaluatingJavaScriptFromString:strscript];
I'd like to add something that for some reason ended up being the culprit for me. I had used AVPlayer and background play for a long time without problems, but this one time I just couldn't get it to work.
I found out that when you go background, the rate property of the AVPlayer sometimes seems to dip to 0.0 (i.e. paused), and for that reason we simply need to KVO check the rate property at all times, or at least when we go to background. If the rate dips below 0.0 and we can assume that the user wants to play (i.e. the user did not deliberately tap pause in remote controls, the movie ended, etc) we need to call .play() on the AVPlayer again.
AFAIK there is no other toggle on the AVPlayer to keep it from pausing itself when app goes to background.
I'm trying to do an app where a short sound sample is supposed to be played while the person using the app is dragging his/her finger(s) across the screen. When the finger(s) are lifted away from the screen - the sound will stop.
This is the function that triggers the sound:
-(IBAction) playSound:(id)sender{
NSString *soundPath = [[NSBundle mainBundle]pathForResource:#"sound" ofType:#"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:soundPath];
newPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:fileURL error:nil];
newPlayer.numberOfLoops = -1;
[newPlayer play];
}
This is the function that stops the sound:
-(IBAction) stopSound{
if ([newPlayer isPlaying]) {
[newPlayer stop];
}
[newPlayer release];
}
I first started out using the Touch Down event, which looped the sound seamlessly. At this point the stopSound worked together with the "Touch Up Inside" event.
The point, as I said, is the sound is supposed to be generated when the user drags his/her finger(s) across the screen. But when I tried to the tie the playSound function to the "Touch Drag Inside" the sound loops repeatedly over itself, instead of one at the time, making it hell to use. The stopSound function doesn't work after i changed to the Drag Event.
I tried to use NSTimer to create some kind of way to handle the loops, but without any success.
My advice would be to create to generate a method that loops your audio clip (whatever duration, overlap, pausing, etc you want). Setup the method so that when you call it to play, it plays the way you want it to indefinitely.
The method might look like:
-(void) beginOrContinuePlayingAudioLoop:(BOOL)shouldPlay {
//if touchesMoved called, shouldPlay will be YES
//if touchesEnded called, shouldPlay will be NO
if (shouldPlay == NO) {
//cease playing
currentlyPlaying = NO;
}
if (currentlyPlaying == YES) {
return;
} else {
//begin playing audio
currentlyPlaying = YES;
}
}
In your ViewController class, define a BOOL (currentlyPlaying) that indicates whether the audio loop is currently playing.
As opposed to using canned IBAction gesture recognizers, consider overriding the more generic touch responder calls, touchesMoved and touchesEnded on your view.
In touchesMoved, set your VC's BOOL to YES and then fire your audio looping method to start playing. The "playing" method should always check and see if the audio is already playing, and only start playing
Also place a check in your method that returns/exits your method when the audio loop is already playing, this will avoid overlapping.
In touchesEnded, kill your audio via whatever method you choose, and reset the BOOL to NO.
I have an app that streams music using AudioStreamer class by Matt Gallagher. This works fine as a background process except I want to be able to skip to the next song once the stream is finished. Unfortunately this part doesn't work.
Initially I had a timer that was monitoring the stream but realized that when the app backgrounds this timer no longer runs. So I tried adding a delegate callback in the packet read function:
void ASReadStreamCallBack(CFReadStreamRef aStream, CFStreamEventType eventType, void* inClientInfo)
{
AudioStreamer* streamer = (AudioStreamer *)inClientInfo;
double percent = [streamer progress]/[streamer duration];
if(percent>=0.98 || (percent>=0.95 && [streamer isIdle])){
if([streamer.delegate respondsToSelector:#selector(didFinishPlayingStream:)] ){
[streamer.delegate didFinishPlayingStream:streamer];
streamer.delegate = nil;
}
}
[streamer handleReadFromStream:aStream eventType:eventType];
}
This works fine when the app is in the foreground but no longer works when the app is backgrounding. The delegate method basically sends a request to get the stream URL for the next song, then once it has it creates a new AudioStreamer class
While the app is in background you can implemente the delegate to handle the different remote control states.
- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
if (player.isPlaying) {
[player pause];
} else {
[player start];
}
break;
case UIEventSubtypeRemoteControlPreviousTrack:
break;
case UIEventSubtypeRemoteControlNextTrack:
[self skipSong:nil];
break;
default:
break;
} }
Something like this works for me.
I've uploaded my AudioPlayer/streamer class inspired in part by Matt Gallagher's AudioStreamer to
https://code.google.com/p/audjustable.
One of the cooler features is its support for gapless playback. This means the AudioQueue is never closed between gaps; keeping iOS from suspending your app.
You can implement AudioPlayerDelegate:didFinishBufferingSourceWithQueueItemId and AudioPlayerDelegate:didFinishPlayingQueueItemId to queue up the next track by calling AudioPlayer:queueDataSource.
Let me know if you need help using it.
I have 2 views in my app, that are identical, they just load in a different array of sound files to use with their respective AVAudioPlayer.
However, ON THE DEVICE ONLY, on my second view, the sound seems to only play once after initialization, then it doesn't play until I initialize the player again.
This is my play button code:
- (IBAction)play:(id)sender {
if (((int)buttonCount % 2) == 0) {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
self.audioPlayer.currentTime = 0;
self.audioPlayer.volume = volumeSlider.value;
[self.audioPlayer play];
[playButton setTitle:#"Stop..." forState:UIControlStateNormal];
buttonCount++;
[self fadePickerAnimation:NO];
NSLog(#"Should play");
}
else {
[self initializeSoundAfterStop:YES];
NSLog(#"Shouldn't play");
}
NSLog(#"buttoncount %i",buttonCount);
NSLog(#"What is the playing state? %i", self.audioPlayer.playing);
}
and the function it calls under the else clause:
- (void)initializeSoundAfterStop:(BOOL)addToButtonCount {
//Initialize audioPlayer once completely stopped
[self.audioPlayer stop];
[self.audioPlayer prepareToPlay];
[playButton setTitle:#"Chime!" forState:UIControlStateNormal];
if (addToButtonCount == YES) {
buttonCount++;
}}
The NSLogs in the above code output this as I continuously press the play button:
Should play
buttoncount 1
What is the playing state? 1
Shouldn't play
buttoncount 2
What is the playing state? 0
Should play
buttoncount 3
What is the playing state? 0
Shouldn't play
buttoncount 4
What is the playing state? 0
Should play
buttoncount 5
What is the playing state? 0
Shouldn't play
buttoncount 6
What is the playing state? 0
So as you can see, it gets stuck in a non-playing mode for some reason.
This method is used in exactly the same fashion in my first view, and it works without any problem. And it all works fine on the simulator, just not on my iPhone 4. Cannot figure this out. The play method of audioPlayer gets called when I setup a breakpoint, so I dunno!
Thanks!
Couldn't find out why this was happening, however, I fixed it by simply adding in my AVAdioPlayer initializer function at the top of the play IBAction.
Might not be the most graceful solution, but it works.
Just beware of memory allocation if you decide to do this.