AVAudioSession overrideOutputAudioPort & localNotifications in mute - ios

Hello I tried the various solutions to similar questions but couldnt get my code to work. I have the following function that I call in my app:
func PlaySound (WhenToPlaySound:String) {
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
if WhenToPlaySound == "BeginningOfRound" {
if UIApplication.sharedApplication().applicationState == UIApplicationState.Background {
soundnotification.soundName = "BoxingBellStart.wav"
UIApplication.sharedApplication().scheduleLocalNotification(soundnotification)
println("timer is done in background mode")
} else {
// Load Sound
soundlocation = NSBundle.mainBundle().URLForResource("BoxingBellStart", withExtension: "wav")!
player = AVAudioPlayer(contentsOfURL: soundlocation, error: &Error)
player.volume = 1.0
// Play Sound
player.play()
println("timer is done in active mode")
}
} else {
if UIApplication.sharedApplication().applicationState == UIApplicationState.Background {
soundnotification.soundName = "Boxing.wav"
UIApplication.sharedApplication().scheduleLocalNotification(soundnotification)
println("timer is done in background mode")
} else {
// Load Sound
soundlocation = NSBundle.mainBundle().URLForResource("Boxing", withExtension: "wav")!
player = AVAudioPlayer(contentsOfURL: soundlocation, error: &Error)
player.volume = 1.0
// Play Sound
player.play()
println("timer is done in active mode")
}
}
}
Mostly it works except two things:
1. I can't seem to override the speaker volume. I want the system volume to be full volume before I play my sound.
The LocalNotifications that are set to activate when the app is in background mode only pay when the device isn't muted.
To address the first problem I wanted to the following but didnt know how to use it:
AVAudioSession.sharedInstance().overrideOutputAudioPort(<#portOverride: AVAudioSessionPortOverride#>, error: <#NSErrorPointer#>)
Thanks in advance,
Ace

overrideOutputAudioPort affects the audio routing, not the volume.
When you say that you want to "override the speaker volume", I assume that you mean you want to control the system output volume from you app code. This is not possible as Apple believes that output volume should remain in the control of the user at all times.
AVAudioPlayer's volume property sets the volume relative to the system output level. It defaults to 1.0 (player volume == system volume). You can't turn it up higher, spinal-tap style, to 1.1...
See also my answer here ... if you want to take control of the system volume, you will need the user interface provided by MPVolumeView.
Similarly regarding your notifications - if the user has muted the device, your app won't be able to ignore that.
update
regarding notifications, it isn't as straightforward as I suggested. It might work if you set the AVAudioSession category to AVAudioSessionCategoryPlayback (and read the apple docs on this setting).
When using this category, your app audio continues with the Silent switch set to silent or when the screen locks
You may also need to add 'audio' to UIBackgroundModes in your info.plist.

Related

Other audio being ducked even when device is silenced and category is ambient

I would like the audio in my app to duck music being played in the background, but also not be played when the ringer/silent switch is switched to silent mode. I am able to get this behavior with the following AVAudioSession configuration, with one caveat -- when music is playing and the device is silenced, it is still ducking the music, even though my app (correctly) doesn't actually play any audio.
Is there a configuration to make this work properly? It seems strange that the system would duck others when the app doesn't actually play audio. Is it possible this is a bug in iOS?
try AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default, options: [.duckOthers, .interruptSpokenAudioAndMixWithOthers])
This is what I have implemented and I just muted my iPad and it plays no sound. But I am not sure if the behaviour is same on iPhone as I generally always have my phone on mute and still hear audio...
do {
try AVAudioSession.sharedInstance().setCategory(.playback, options: .duckOthers)
} catch(let error) {
print(error.localizedDescription)
}
let utterance = AVSpeechUtterance(string: stringToSpeak)
// Set voice to male or female based on user settings
if defaults.integer(forKey: "Voice") == 1 {
utterance.voice = AVSpeechSynthesisVoice(identifier: "com.apple.ttsbundle.siri_male_ja-JP_compact")
}
else {
utterance.voice = AVSpeechSynthesisVoice(language: "ja-JP")
}
synthesizer.speak(utterance)

AVAudioSession - How to switch between speaker and headphones output

I'm trying to mimic behaviour as in Phone app during calling. You can easily switch output sources from/to speaker or headphones.
I know I can force speaker as an output when headphones are connected by calling:
try! audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try! audioSession.overrideOutputAudioPort(.speaker)
However, when I do that, I don't see any way to detect if headphones are still connected to the device.
I initially thought outputDataSources on AVAudioSession would return all posible outputs but it always returns nil.
Is there something I'm missing
You need to change the outputDataSources, as when you overrode it,
now it contains only the .Speaker option
in the Documentation you can find the solution to this,
If your app uses the playAndRecord category, calling this method with the AVAudioSession.PortOverride.speaker option causes audio to be routed to the built-in speaker and microphone regardless of other settings. This change remains in effect only until the current route changes or you call this method again with the AVAudioSession.PortOverride.none option.
Therefore the audio is routed to the built-in speaker, This change remains in effect only untill the current route changes or you call this method again with .noneOption.
it's not possible to forcefully direct sound to headphone unless an accessory is plugged to headphone jack (which activates a physical switch to direct voice to headphone).
So when you want to switch back to headphone, this should work.
And if there is no headphone connected will switch the output device to the small speaker output on the top of the device instead of the big speaker.
let session: AVAudioSession = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayAndRecord)
try session.overrideOutputAudioPort(AVAudioSession.PortOverride.none)
try session.setActive(true)
} catch {
print("Couldn't override output audio port")
}
Read about this AVAdioSession/OverrideOutputAudioPort Here.
You can check if headset connected adding this extension,
extension AVAudioSession {
static var isHeadphonesConnected: Bool {
return sharedInstance().isHeadphonesConnected
}
var isHeadphonesConnected: Bool {
return !currentRoute.outputs.filter { $0.isHeadphones }.isEmpty
}
}
extension AVAudioSessionPortDescription {
var isHeadphones: Bool {
return portType == AVAudioSessionPortHeadphones
}
}
And simply use this line of code
session.isHeadphonesConnected

MPRemoteCommandCenter not generating remote control events MPMusicPlayerController.applicationMusicPlayer()

I'm using MPRemoteCommandCenter and MPMusicPlayerController.applicationMusicPlayer on the iPhone.
I'm trying to receive remote control events when the user is playing music and double taps on the headphone button.
If I use AVAudioPlayer, the remote commands are received perfectly.
However, if I use MPMusicPlayerController with any of its players (systemMusicPlayer, applicationMusicPlayer, or applicationQueuePlayer) the the commands do not get received. They appear to get gobbled up. For example when I double tap the remote, the music will toggle between play and stop. Instead, I need the remote events sent to my app.
Below is a sample app with my code. In the info.plist I've specified the required background mode for an app that plays audio (although its not necessary).
import UIKit
import MediaPlayer
class ViewController: UIViewController {
var mpPlayer:MPMusicPlayerController!
func remoteHandler() {
print("success")
}
override func viewDidLoad() {
super.viewDidLoad()
mpPlayer = MPMusicPlayerController.applicationMusicPlayer()
//mpPlayer = MPMusicPlayerController.systemMusicPlayer()
assert(mpPlayer != nil)
let cc = MPRemoteCommandCenter.shared()
print("cc = \(cc)")
cc.nextTrackCommand.isEnabled = true
cc.nextTrackCommand.addTarget(self, action: #selector(ViewController.remoteHandler))
cc.previousTrackCommand.isEnabled = true
cc.previousTrackCommand.addTarget(self, action: #selector(ViewController.remoteHandler))
cc.playCommand.isEnabled = true
cc.playCommand.addTarget(self, action: #selector(ViewController.remoteHandler))
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
print("AVAudioSession successfully set AVAudioSessionCategoryPlayback")
} catch let error as NSError {
print("AVAudioSession setCategory error: \(error.localizedDescription)")
}
mpPlayer.setQueueWithStoreIDs(["270139033"]) // requires iOS 10.3
mpPlayer.play()
}
}
Output is:
cc = 0x123e086c0
AVAudioSession successfully set AVAudioSessionCategoryPlayback
remoteHandler is never called.
From the Apple Developer web site.
When you use either the system or application player, you do not get
event notifications. Those players automatically handle events.
So there is no way to receive remote control events if you use MPMusicPlayerController. Looking forward to see this feature! Right now MPMusicPlayerController is the only way to play Apple Music songs.

Using Spotify/background music with camera open

I have an app that needs to have:
Background music playing while using the app (eg. spotify)
Background music playing while watching movie from AVPlayer
Stop the music when recording a video
Like Snapchat, the camera-viewcontroller is part of a "swipeview" and therefore always on.
However, when opening and closing the app, the music makes a short "crack" noise/sound that ruins the music.
I recorded it here:
https://soundcloud.com/morten-stulen/hacky-sound-ios
(3 occurrences)
I use these settings for changing the AVAudiosession in the appdelegate didFinishLaunchingWithOptions:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord,withOptions:
[AVAudioSessionCategoryOptions.MixWithOthers,
AVAudioSessionCategoryOptions.DefaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("error")
}
I use the LLSimpleCamera control for video recording and I've set the session there to:
_session.automaticallyConfiguresApplicationAudioSession = NO;
It seems others have the same problem with other camera libraries as well:
https://github.com/rFlex/SCRecorder/issues/127
https://github.com/rFlex/SCRecorder/issues/224
This guy removed the audioDeviceInput, but I kinda need that for recording video.
https://github.com/omergul123/LLSimpleCamera/issues/48
I also tried with Apple's code "AvCam", and I still have the same issue. How does Snapchat do this?!
Any help would be greatly appreciated, and I'll gladly provide more info or code!
I do something similar to what you're wanting, but without the camera aspect, but I think this will do what you want. My app allows background audio that will mix with non-fullscreen video/audio. When the user plays an audio file or a full screen video file, I stop the background audio completely.
The reason I do SoloAmbient then Playback is because I allow my audio to be played in the background when the device is locked. Going SoloAmbient will stop all background music playing and then switching to Playback lets my audio play in the app as well as in the background.
This is why you see a call to a method that sets the lock screen information in the Unload method. In this case, it is nulling it out so that there is no lock screen info.
In AppDelegate.swift
//MARK: Audio Session Mixing
func allowBackgroundAudio()
{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: .MixWithOthers)
} catch {
NSLog("AVAudioSession SetCategory - Playback:MixWithOthers failed")
}
}
func preventBackgroundAudio()
{
do {
//Ask for Solo Ambient to prevent any background audio playing, then change to normal Playback so we can play while locked
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch {
NSLog("AVAudioSession SetCategory - SoloAmbient failed")
}
}
When I want to stop background audio, for example when playing an audio track that should be alone, I do the following:
In MyAudioPlayer.swift
func playUrl(url: NSURL?, backgroundImageUrl: NSURL?, title: String, subtitle: String)
{
ForgeHelper.appDelegate().preventBackgroundAudio()
if _mediaPlayer == nil {
self._mediaPlayer = MediaPlayer()
_mediaPlayer!.delegate = self
}
//... Code removed for brevity
}
And when I'm done with my media playing, I do this:
private func unloadMediaPlayer()
{
if _mediaPlayer != nil {
_mediaPlayer!.unload()
self._mediaPlayer = nil
}
_controlView.updateForProgress(0, duration: 0, animate: false)
ForgeHelper.appDelegate().allowBackgroundAudio()
setLockScreenInfo()
}
Hope this helps you out!

Mute other application sound when my application is playing sound ios

I am new in playing audio sound. I am stuck in problem where I wanted to mute other application sound when my application is playing music sound and resume back when I stop my application's sound. I searched a lot but I could not get any solution.
Below code is for playing audio sound:-
var url:NSURL = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("\(soundFile)", ofType: "mp3")!)!
let audioPlayer1 = AVAudioPlayer(contentsOfURL: url, error: nil)
audioPlayer1!.numberOfLoops = -1
if(audioPlayer1 == nil)
{
println("error in playing")
}
else
{
audioPlayer1!.play()
}
You need to configure the AVAudioSession category :
AVAudioSession Class Reference : http://goo.gl/rh7CX7 . Look for which fit the most for your application. You only need to set it once in your code (make sure it's called).
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient, error: nil) // AVAudioSessionCategorySoloAmbient is default
AVAudioSession.sharedInstance().setActive(true, error: nil)
If it didn't help, please provide more information (which app isn't mute ? what have you tried ? ...).

Resources