MPMusicPlayerController: combine iPodMusicPlayer and applicationMusicPlayer to prevent continuing to next track - ios

I have a bit of a strange problem. I have a music app that uses the [MPMusicPlayerController iPodMusicPlayer]. Everything is fine, notifications are fired for track changes and changes in playback state.
I have one screen where the user needs to review one single song, I don't want him to go on to the next song in his queue. Since there is no delegate method for when a track WILL change (only DID change), to prevent the music player from continuing to the next track I use a new [MPMusicPlayerController applicationMusicPlayer], give it iPodMusicPlayer's currently playing song and all is well. No new tracks to continue to, and I'm not touching the original iPodMusicPlayer queue so in theory, when I close this screen and use the iPodMusicPlayer again, all should be perfectly fine.
However, when the user is done on this screen and closes it, iPodMusicPlayer is now suddenly broken, notifications are not called and when I put the app to the background, music stops playing, causing me to believe that iPodMusicPlayer is now actually applicationMusicPlayer.
Okay so my question is basically: I need a way to prevent the music player to continue on to the next track in the queue. Switching to applicationMusicPlayer with one track seems to break stuff, as explained above. What's the best solution?
EDIT: because this might be a bit difficult to understand, I created a small project to show the problem: https://github.com/kevinrenskers/MPMusicPlayerControllerTest. Open the app while music is playing, see that the play button behaves correctly. Now open the popup, close it again and the play button is broken.

I found a solution to my problem: set the repeatMode to MPMusicRepeatModeOne and then catch the MPMusicPlayerControllerNowPlayingItemDidChangeNotification notification. You can stop the playback and you never continue to the next track. Once I'm done with the second screen I reset the repeatMode to the original value.

Related

AVAudioPlayer not playing after a while when app is in background

I am making an alarm clock app. To play the alarm sound at the appropriate time, I use myAudioPlayer.play(atTime: myAudioPlayer.deviceCurrentTime + secondsUntilAlarm). This way, even if the app is in the background, the audio player plays the alarm sound at the appropriate time.
Note: I got this idea from a different SO answer, which unfortunately I can't seem to find right now.
However, what I've noticed is that alarms are being played correctly if the secondUntilAlarm value is relatively soon, like maybe 20 minutes or less (converted to seconds of course since that's what the method requires). However, if it's longer than that, the sound does not play. Is there something I'm missing with how this method works in the background? Could the app be entering some sort of suspended state or something that disables the audio player from triggering the playback?
Any suggestions or comments would be appreciated--thanks!
So I've actually determined that it does not matter if the app enters the suspended state (in fact, this is expected). I've identified that the reason the alarm sound does not play sometimes because I open another app with audio that deactivates my app's audio session (therefore, it has nothing to do with the time the alarm is set for). To fix this, all I had to do was ensure I set .mixWithOthers for my app's audio session. That way, other audio sessions from other apps don't deactivate mine!

Audio voiceover when iPhone is locked

Solution below
I am working on a running app which gives voiceover instruction at the start of each exercise. It works as intended when the device is active but doesn't work when the phone is locked. [Current audio continues but no future audio plays.]
Question
How can I start playing an audio voiceover while the users iPhone is locked.
Currently I track the workout using Timer.scheduledTimer for 2 timers I display [current activity and the overall workout]. When the timer hits a pre-defined time towards the end of one activity, the voiceover audio plays to introduce the next activity. The timer firing is what starts the audio and I believe this is the issue. I can pause the workout, skip sections and all works fine - until the device is locked.
If an audio voiceover is playing when I press the home button or lock the iPhone it continues to play as expected. The issue is then that the timer doesn't fire so the next audio voiceover [in say 90 seconds] is never played.
Some of the answers and comments I've read have said that this functionality just isn't possible in iOS. The Couch to 5k app https://itunes.apple.com/gb/app/one-you-couch-to-5k/id1082307672?mt=8 uses this functionality so I know it's possible to achieve, I just don't know how they're doing it. [It will be a dedicated member that downloads it to see what I mean :)]
From searching SO I've read a lot of post saying that Timer or NSTimer can't run when the app is in the background or the phone is locked. Any posts that say it can work are old and based on iOS4/5
I've read about suggestions of using silence and essentially having 1 long audio file. While this would pose some new challenges for skipping the sections in the workout I've also read comments that say this behaviour would not pass Apple's testing of the app for the AppStore. The 3rd downside being that my audio file size would increase.
An option I've seen is local notifications, however I'm yet to see an example of one used to play audio voiceover when the app is in the background.
How can I achieve the same functionality as the Couch to 5k app?
Thanks
Ok, I worked up a solution to play audio prompts to the user throughout the course of a 30 minute workout, even if the iPhone is locked or the app is in the background. The app has been submitted and approved for the AppStore.
To get the app to perform as required I use 2 AVAudioPlayers; one plays the voiceover, the other plays a 15 minute track of silence [as it’s a separate track and just silent it’s a low quality .mp3 and only added 900kb to the app size]. 15 minutes well exceeds any interval between voiceovers so it works perfect.
When the user starts an exercise the first audio prompt is played. From that point, until the user stops the exercise there will be audio playing in the background. Because background audio is playing, the Timer still operates.
When a voiceover finishes, the AVAudioPlayerDelegate method audioPlayerDidFinishPlaying is called. Within this method I reset the audio properties [I’ll explain why shortly], instantiate my silent AVAudioPlayer, and start playing the 15 minute track of silence. Playing another track using this delegate method means that there is no real break in background audio so the app is kept alive. With the app ‘alive’ the timer continues to countdown. When it’s time for another voiceover the timer calls a method which resets the audio category, instantiates my voiceover AVAudioPlayer and plays a voiceover. This process is repeated until the final voiceover. When that completes no further silence is played and the app can safely be backgrounded.
The audio voiceover needs to duck the users music [reduces the music volume so that the audio can be clearly heard]. The silent audio shouldn’t do that, music should continue to play at full volume. As both AVAudioPlayers use the AVAudioSession.sharedInstance I found that I had to deactivate the sharedInstance, reset the properties, with or without .duckOthers, and then reactivate the sharedInstance before instantiating my AVAudioSession. That then gave me the desired result. Constant background audio that only ducked the users music when a voiceover played.
do{
try AVAudioSession.sharedInstance().setActive(false)
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.interruptSpokenAudioAndMixWithOthers, .duckOthers])
try AVAudioSession.sharedInstance().setActive(true)
} catch{
print(error.localizedDescription)
}
I know that some said Apple do not allow silent audio, however, in this circumstance the silent audio only plays for the duration of the exercise routine. As soon as it ends the app can terminate, thereby not keeping the app alive any longer than necessary.
My understanding of VoiceOver leads me to the way that only a foreground application can control what it reads out.
You need focusing with VoiceOver to make it speak and you focus nothing when the device is locked or your app is in the background mode.
I suggest to take a look at the speech synthesis or the AVAudioSession class to enable background audio: both might be good workarounds to take over in the background mode: just try and let us know. ;o)

iOS: AVPlayer play won't play sometimes

So I have an app built with a player that plays a video, I have a [player pause] and [player play] in the didBecomeActive and willResignActive methods. Most of the time works fine, but when I open the app, and press the home button and repeat again that process, around the 8th time the video will not play even though I see the play method getting called.
Does anyone have any idea what might be going on?
The app can be in several states that are not foreground. Before playing, check to see if you still have a player, that it still has a player.currentItem, and if it's status is AVPlayerStatusReadyToPlay.
If any of those conditions are not met, then the player and the item must be reinitialized using the code that you used to create it in the first place.
This is a good candidate for a lazy initializer for your player property.

Preventing my app's audio from interfering with other background music

I think what I'm looking for is the combination of existing settings that I can't quite put together in the right order. I have an app that plays sound clips using AVAudioPlayer. There is a start/stop button.
I would like any other apps' audio to continue until my app actually needs to play something.
When my app is launched with some iTunes music playing, that music continues correctly. When I press play in my app, the other audio correctly stops, and my app plays. I stop my player, switch back to the music app, and press play again. But now when I switch back to my own app, the music stops immediately.
I would like it to again wait until it needs to play something before killing the music app.
My best guess is that once I've used the player, it is in some kind of 'I need audio' state, and remains in that state. How do I resign this state?

What is the state of MPMusicPlayerController when the now playing item is skipping to outside the current playlist?

I'm using MPMusicPlayerController and I have implemented skip track and previous track controls to control the music playback. When I reach the end of an album/playlist that is in the now playing list. The App no longer skips track nor goes previous track.
Example: First track of album, tap on previous track the app would stop responding and skip track/play/previous track will no longer respond.
Example: Last track of album, tap on previous track the app would stop responding and skip track/play/previous track will no longer respond.
I noticed on the iPod app itself, after it finishes a playlist it'll return to the playlist selection view or it'll return to the album selection view. What state is the MPMusicPlayerController in at this point of time? How can I use it to launch the iPod app when I encounter such a state within my App?
http://developer.apple.com/library/ios/#documentation/mediaplayer/reference/MPMusicPlayerController_ClassReference/Reference/Reference.html
I've found that if repeatMode is set to MPMusicRepeatModeAll, next track will go back to the first item in the playlist. I think previous will still do nothing.
So, use notifications as described above, or set repeatMode to prevent this from happening.
Based on my experience, the state becomes MPMusicPlaybackStateStopped In this state, playlist is actually gone, and needs to be update by playing iPod Music player again. You may use MPMusicPlayerControllerPlaybackStateDidChangeNotification to evaluate the condition of the player and do the action of your choice. Notification Programming Topics: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html#//apple_ref/doc/uid/10000043i

Resources