6

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
Community
  • 1
  • 1
SimplGy
  • 20,079
  • 15
  • 107
  • 144

2 Answers2

9

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. 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.

XCode, Capabilities tab

(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.

Andrea Gorrieri
  • 1,704
  • 2
  • 22
  • 36
  • I think this solution would most likely be rejected by Apple as the app isn't streaming audio. This background mode category is really for media apps like Spotify, YouTube, etc. – Ron Srebro Nov 09 '18 at 15:25
  • I have an app already accepted which uses this code ;) In that case it's an app used to trigger a sound in order to warn the user in a particular situation related to BLE peripheral connection (not a "media" app). If your app really needs the background functionality in order to work the app won't be rejected. – Andrea Gorrieri Nov 09 '18 at 15:29
  • In that case +1 for the answer. I'm curios though - Apple says that the app will be suspended if it's not actively playing something. How do you keep your app alive while its not playing – Ron Srebro Nov 09 '18 at 15:37
  • In my case the application is alive in background since it’s connected to a BLE peripheral. The sound is played in order to warn the user about peripheral connection or disconnection. I think it’s quite similar to the cited “Tile” case. – Andrea Gorrieri Nov 10 '18 at 11:58
  • Well then it sounds to me the reason you can play audio in the background is really because you also have the BLE background mode. My guess is that without you will not be able to play audio in the background and your app will be suspended – Ron Srebro Nov 10 '18 at 22:56
  • You're right. With the proposed approach you can trigger a sound assuming that your application is able to launch the code responding to callbacks events in background. However, only specific app types are allowed to respond to callbacks events in the background. In the "Tile" application, background callbacks events are related to the BLE peripheral connected. Conversely, a simple timer is suspended when application goes in background, the timer-expiration callback won't be triggered so the sound can't be played in this way. I'll edit my response in order to clarify this point. – Andrea Gorrieri Nov 12 '18 at 08:19
2

First thing first I think we need to clear up a few things (correct me if I'm wrong):

  1. You are scheduling a local notification when your timer is expired
  2. Your app isn't running in the background at all
  3. 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.

Ron Srebro
  • 6,663
  • 3
  • 27
  • 40
  • do you rec Tile has a special arrangement with Apple? – Woodstock Nov 09 '18 at 11:45
  • 1
    @Woodstock No I don't believe they do, I think they're using the standard background execution mode designed for exactly this scenario. Apple described the Bluetooth LE mode as "The app works with a Bluetooth accessory that needs to deliver updates on a regular schedule through the Core Bluetooth framework." See more here https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html – Ron Srebro Nov 09 '18 at 15:28