iOS: How do alarm Apps play sound after App is inactive? - ios

Many alarm apps managed to play sound with locked screen after several hours (examples: Rise, Wave alarm clock)
All you need to do is set the alarm and lock your screen while the app is open and an alarm will go off hours after the screen has been locked.
I managed to figure out how to play audio with AVPlayer while my device is locked:
Set background capabilities to audio
Set 'Application does not run in background' to YES
Code:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
When I try to set a NSTimer or use dispatch_after to run the code that plays my audio file and lock my iPhone the Timer stops pretty quickly because apparently the app gets inactive after just a few seconds
(But only when I start the App from the home screen and doesn't when I run the App from xcode, btw)
How do those alarm clock apps manage to delay audio?

These apps achieve this by opting out of the multi-tasking mode. If an app opts out of multi-tasking mode, the app is not suspended if the app was in the foreground when the device got locked and hence they are able to play the alarm sound when the time arrives. You can read more here

(Posted answer on behalf of the question author to move it to the answer space).
I figured out how they do it. I set an alarm and listened to the speakers. Those apps play silent sound to stay active. I don't know how they passed the checks by Apple but apparently many alarm clock apps do this and pass.

Related

App with playback audio category stops other apps sound

I added radio functionality to my application recently.
I wanted it to play music even when it is in the background.
So I implemented this piece of code
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .default, options: [.duckOthers, .allowBluetooth])
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError {
print(error)
}
I got the app to play music when in the background now but the problem is that the app stops all other app's sounds when launched.
For example when SoundCloud is playing music and when I launch my app Soundcloud stops the music.
What I want to achieve is to make my app play music when it is in the background and also allow other apps to play music when my app is not playing music.
How can I fix that issue?
Thanks
do {
try AVAudioSession.sharedInstance().setCategory(.ambient)
try AVAudioSession.sharedInstance().setActive(true)
} catch { print(error) }
Above code add in AppDelegate.swift files didFinishLaunchingwithOptions method. This will solve your problem
To partially answer your question, you can allow your app to play audio while other apps are playing audio with the following configuration:
do {
try AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers)
} catch(let error) {
print(error.localizedDescription)
}
.playback means the audio playing is central to the use of your app. So if you're playing music, background sounds like white noise, etc. you probably are using playback mode. .mixWithOthers is 'An option that indicates whether audio from this session mixes with audio from active sessions in other audio apps.' - in other words allows your primary audio to mix with audio coming from other apps.
The one thing I haven't figured out - is why on the first launch of my app other background audio pauses. Subsequent launches seem to work fine.

Does having FaceID or touchID in your app when app enters background stop audio playing in background?

I have a Biometrics for FaceID/TouchID that activities whenever the app enters background. I already have the app configured to play audio in the background, but FaceID/TouchID seems to stop all background activity, such as audio. Is this the case?
By default, audio is always stopped when entering the background. To enable it, you must configure your app Capabilities.
Click on your app target > Capabilites > Background Modes
then set on Audio, Airplay, and Picture in Picture.
Add this code on the line before the audio is played:
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay]) // enable player back
try AVAudioSession.sharedInstance().setActive(true) // set session to active
} catch {
print(error)
}
Also check the Background fetch mode in capabilities.
Short answer yes it does. If FaceID and TouchID is activated at AppDidEnterBackground; basically whenever the user leaves the app, it will stop the audio. Has not nothing to do with background fetch since the audio file was already downloaded. codeherk's answer is accurate if anyone is looking for how to play audio in the background.

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)

How are some alarm apps such as Alarmy able to play a sound on iPhone when the app is in the background and the phone is on vibrate

I am working on app that can alert users for some critical things. I use local notifications to alert the user. On iOS, I find that the notifications will not ring if the phone is on vibrate. This is a deal-breaker for many users of the app but I have been putting that question off till now since I thought that iOS doesn't allow an app to play a sound if the app is in the background.
Music apps are able to play songs even when the phone is on vibrate by enabling the audio background mode but it doesn't allow you to schedule a song to be played at a certain time.
Lately I have seen that some apps are able to play a sound at a certain time even though the app is in the background. One such app is Alarmy alarm app. I don't think that they are playing the music via the local notification when the alarm expires because the music continues to play even after I clear the notification. From the local notification documentation, I understood that I am can't run any code when local notification fires till the user clicks on the notification. So, I can't start an audio player which may be able to play the sound in vibrate.
How are such apps able to play a sound even though the phone is on vibate and the app is in background in iOS?
There are few methods to implement this kind of functionality.For reference I recommend this link.
For actually playing the sound while the device’s ringer switch is set to vibrate
First off make sure to include the audio background mode in the capabilities, in order to play audio in the background.
Then,
Swift 4
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.duckOthers, .defaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true)
UIApplication.shared.beginReceivingRemoteControlEvents()
} catch {
NSLog("Audio Session error: \(error)")
}
Here we set the shared audio session’s category to AVAudioSessionCategoryPlayAndRecord, so that we can play sound, while the device’s ringer switch is set to vibrate.
The .duckOthers is specified to make other audio quieter, if there’s any mixable audio playing, so that our alarm can be heard. You can leave that out or use another option, if you prefer a different behavior.
The .defaultToSpeaker is specified, so that the volume would go to the speaker, where it’ll be much louder and should wake up our user with ease.
beginReceivingRemoteControlEvents makes it so that the app handles the remote control options, like the play/pause buttons on the lock screen, in order to make it easier for our user to mute their alarm, once they wake up.
The way this can be done (I have implemented this in my app) is by starting an AVAudioPlayer and specifying a specific time to play. So:
Enable background audio in the app capabilities.
Start and audio session with .playback mode, and start a player at the time you like it to play:
do {
//set up audio session
try AVAudioSession.sharedInstance().setCategory(.playback, options: [.defaultToSpeaker, .duckOthers])
try AVAudioSession.sharedInstance().setActive(true)
//Start AVAudioPlayer
player.play(at: time) //time is a TimeInterval after which the audio will start
}
catch {
...
}
This does not play silence in the background, which violates Apple's rules. It actually starts the player, but the audio will only start at the right time. I think this is probably how Alarmy implemented their alarm, given that it's not a remote notification that triggers the audio nor is the audio played by a local notification (as its not limited to 30 seconds or silenced by the ringer switch).

Play some sound in background, some sound only in foreground

I want to play sound in background for a feature but I want to play sound only in foreground for another feature.
Also, I want to play sound even when the silent switch is turned on.
To play sound in background, the following 2 code is needed:
[Info.plist] UIBackgroundModes = audio
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: [])
To play sound only in foreground, I think I have to set UIBackgroundModes to empty in the Info.plist.
But it is not possible while the app is running.
How can I play sound only in foreground for a feature and sound in background for another feature?
* playing sound in foreground means that sound is paused when the user push the home button of the iPhone and it is resumed when the user open the app again.
How can I play sound only in foreground for a feature and sound in background for another feature
Detect when the app goes into the background, and stop playing the foreground sound and switch to the background sound.

Resources