Detecting When An Apple Music Song Has Finished Playing - ios

I'm writing an app that needs to enact an action when a song has finished playing. I'm using MPMusicPlayerController.applicationQueuePlayer() as my music player to play the user's apple music. I was wondering if there was a way I could detect when a users song has finished playing or when the queue has finished (either would be helpful)

MPMusicPlayerController has an instance method beginGeneratingPlaybackNotifications(). There are three Notifications that will be delivered to subscribers:
MPMusicPlayerControllerNowPlayingItemDidChange
MPMusicPlayerControllerPlaybackStateDidChange
MPMusicPlayerControllerVolumeDidChange
To detect when playing a song or a queue has finished, you can use MPMusicPlayerControllerNowPlayingItemDidChange.
When you receive that notification, check the MPMusicPlayerControllers nowPlayingItem (see Documentation). If the song finished and another one is played nowPlayingItem will have changed. If the whole queue finished and there is nothing playing, nowPlayingItem will have a value nil.

Related

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)

Detect end of playback on lock screen - AVPlayer

I've the array of audio file and would like to play them consecutively. Is there any way to detect when AVPlayer ending playing on lock screen so that I could call a completion handler and play next sound? I want to call nextPlayAudio() from my PlayerViewController class. I am fire notification after finishing current playback audio but nextPlayAudio() not get called after finishing.
Use the AVPlayerItemDidPlayToEndTime notification to manage this.
Example as a service here: https://github.com/noreasonprojects/ModernAVPlayer/blob/develop/Sources/Core/Services/PlaybackObservingService.swift
| Check the PlayingState file, where the service is used.

How to play longer music as Notification alert?

I want to set a music which is longer than 30sec as notification soound. But after searching about this I came to know that it is not possible to add these type of music as notification sound. notification sound must be in 30sec longer otherwise it will not play music. Now how can I add more than 30sec longer music as notification sound?
I had a similar issue for my alarm app as well. What I did was a workaround when your app is in background. You play a music file in your code in background and increase the volume of the device to maximum. You can play as much longer music file as you want. Stop playing the music when someone clicks on the notification.
The only problem with this is that your app should not be killed or terminated.

MPMusicPlayerControllerNowPlayingItemDidChange in background

Is there a way to get the MPMusicPlayerControllerNowPlayingItemDidChange notification while the app is in the background?
I have an app that needs to be able to pause the music once a song has ended while in the background or when the screen is locked. I'm using a systemMusicPlayer to play the music.
I've tried adding the audio background capability and including a call to beginBackgroundTask in my applicationDidEnterBackground but that doesn't work for extended periods of time.
When in background, your app might be killed at any time, so you do not want to depend on acting in background.
How I understood, what you want to achieve is that when your app goes to background, the currently playing music goes on but stops after the currently playing song finished.
MPMusicPlayerController.systemMusicPlayer() is playing a queue of songs ("Playlist"). So I would try to manipulate this queue in applicationWillResignActive() to not have a song after the currently playing one .
I did not test this and I am not sure whether or not this is possible through public API.

notification when AVPlayerItem is not stalled

There is a notification for when the player has stalled, called AVPlayerItemPlaybackStalled, but there doesn't seem to be one for when the video is playing again. What's a good way to know that the video has unstalled? I could look for when the player current time changes but that sounds not great...

Resources