I've built a timer app and one problem I have is when it's backgrounded, I'm unable to ring the timer if the user has volume off. Turning the volume or sound off also mutes notifications, which is the method I was using for ringing the timer in the background.
I just bought a tile and discovered it can ring your phone even on silent. I have tested this and it works in iOS 9, but I'm not sure how to duplicate this behavior.
How is ringing the iPhone in silent mode accomplished? Background refresh? Motion and activity? Something else?
Prior art:
- this answer plays in silent mode, but does not address when the app is closed. same here
First of all, in order to play a sound in background your application must be able to launch the related code responding to a callback event when in the background. Only specific app types are allowed to respond to callbacks events in the background as indicated here. For example, the "Tile" application can play sound in background responding to BLE-related callbacks events which are fired when the application is in background (e.g., BLE peripheral connections and disconnections) . Conversely, a simple timer is suspended when application goes in background thus the timer-expiration callback won't be triggered and there is no way to launch any code to play sound in this case. (You can read here for some details and a possibile approach to build alarm clock in iOS)
If your application type is within the set of special background-mode applications you can play a sound responding to events in background even when the phone is in silent mode by using AVAudioPlayer. In your view controller import the AVFoundation framework:
import AVFoundation
than declare an AVAudioPlayer variable
var player: AVAudioPlayer?;
in order to play an mp3 file placed in the app bundle:
func playSound() {
guard let url = Bundle.main.url(forResource: "MY_MP3_FILE", withExtension: "mp3") else {
print("error");
return;
}
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.mixWithOthers);
player = try AVAudioPlayer(contentsOf: url);
guard let player = player else {
print("error");
return;
}
player.play();
} catch let error {
print(error.localizedDescription);
}
}
Important: in Xcode in the "Capabilities" tab you also have to enable the "Audio, AirPlay and Picture in Picture" feature in the "Background Modes" section.
(Swift 4 solution tested in iOS 11 and 12 with different iPhone models).
Warning: Following this approach the sound will play at the current volume set in the phone, thus if the sound volume is equal to zero the sound will not be played. You can force the volume sound to a given value programmatically in this way:
let volumeView = MPVolumeView();
if let view = volumeView.subviews.first as? UISlider{
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) {
view.value = 0.5; //Set the volume level between 0 and 1
}
}
however, take into account that this approach shows the system sound volume bar and it could lead to a bad user experience.
First thing first I think we need to clear up a few things (correct me if I'm wrong):
- You are scheduling a local notification when your timer is expired
- Your app isn't running in the background at all
- You want your local notification to play a sound even if the phone is on mute.
I'm not actually sure this is possible, you mentioned the Tile app, and the important distinction is that the Tile app actually does run in the background where your app doesn't.
Apple defines specific use cases that are allowed to run in the background. One of those allowed modes are to communicate with a Bluetooth LE device. So the way that works is the iPhone identifies the tile device, and send a notification to the tile app, at that point the app is actually executing. See more about background execution here
I don't believe any of the approved backgrounds modes can be used by you to implement a solution that will allow you to workaround this.
My suggestion is that when the user sets a timer in your app, check if the phone is on silent (see here), and if it is, alert the user that the app won't play a sound when the timer completes.
Also download a few other timer apps and see how they deal with it.