My ios app uses AVQueuePlayer to play audio.
The play/pause controls work properly from Control Center and from ios lock screen, but not for commands invoked from bluetooth device. E.g., the callbacks in my app are never invoked when double-tapping airpods or other third-party bluetooth headphones.
I'm running on an iphone 8 plus device with iOS 15.
I'm using https://developer.apple.com/documentation/avfoundation/media_playback_and_selection/creating_a_basic_video_player_ios_and_tvos/controlling_background_audio.
On app startup, it sets up the audio session like this:
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.playAndRecord,
mode: .spokenAudio,
options: [
.allowBluetooth,
.defaultToSpeaker
])
try audioSession.setActive(true)
The app listens for play/pause events like this.
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { event in
print("play")
return .success
}
commandCenter.pauseCommand.addTarget { event in
print("pause")
return .success
}
commandCenter.togglePlayPauseCommand.addTarget { event in
print("toggleplaypause")
return .success
}
Playback works correctly through all bluetooth devices. On lock screen or control center, play/pause cause "play" and "pause" to get printed. But nothing is printed when tapping airpods. (Tapping airpods works fine in any other audio app.)
Any ideas what might be missing?
Found the solution. Airpod controls started working when I changed the mode to .playback. Apparently they don't work with .playAndRecord. I also needed to remove the .allowBluetooth, which is not compatible with .playback mode.
For my app, since I need to both play and record, I will set the mode to .playAndRecord when the user needs to record, and otherwise keep it in .playback.
Related
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.
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.
Developing an iOS app which plays back audio messages in segments (not continuous playback). When the app is opened, I initialize the audio session with the following options.
func _initAudioSesh() {
let audioSession = AVAudioSession.sharedInstance()
do {
if #available(iOS 10.0, *) {
try audioSession.setCategory(
AVAudioSessionCategoryPlayAndRecord,
mode: AVAudioSessionModeVoiceChat,
options: [
AVAudioSessionCategoryOptions.defaultToSpeaker,
AVAudioSessionCategoryOptions.allowBluetooth,
AVAudioSessionCategoryOptions.allowBluetoothA2DP
]
)
} else {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
}
} catch {
print("Setting category to AVAudioSessionCategoryPlayback failed.")
}
}
Then, when ready to playback audio, I grab focus using setActive(true) and release focus using setActive(false)
The issue I'm encountering is that in the app, the hardware volume buttons only work when audio is playing, otherwise, the buttons do nothing. I was able to hack around this by ALWAYS holding setActive(true), but that hack is ugly and causes other issues. Has anyone else experienced volume buttons not working/adjusting in-app, and only working when audio is actively being played?
As soon as I leave the app, audio adjustment works, as soon as I bring it back into focus, it stops working unless I begin to play audio.
I've tried messing with how & when I create the audio session, with no success.
This ended up being a result of a specific library we're using (react-native-system-settings), which has now been patched. If other users encounter issues, the fix seems to be around allowing UI to show in VolumeView. Otherwise, it doesn't allow the hardware buttons to affect volume.
I have a custom class which plays an audio with AVPlayer. In the method of this class have added play, pause, previous and next track commands to MPRemoteCommandCenter instance like so:
let mediaPlayerCommandCenter = MPRemoteCommandCenter.shared()
mediaPlayerCommandCenter.playCommand.addTarget(self, action: #selector(playRemoteHandler(_:))) // .. and so on
I have added also MPNowPlayingInfoCenter details.
"Background mode" is turned on in the "Capabilities" section of project settings and app plays audio in background normally.
In the application(application:didFinishLaunchingWithOptions:) method AVAudioSession category selected and activated also:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print(error)
}
application.beginReceivingRemoteControlEvents() // in the documentation noticed that it is not necessary if we are using MPRemoteCommandCenter but anyways I tried it also
After all these codes, remote controls are visible neither in lock screen nor contol center. But audio plays normally in the background.
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.