I want to test various aspects of background music and speech in iOS UI Testing;
is background music playing?
did music change on new scene?
does music mute on interruptions from phone call, FaceTime etc?
is speech uttered when required ?
Can't seem to find any api documentation or tutorials that show how to do these types of tests.
Have tried the following code in my UI test class just to test if music is playing. The code attempts to either detect audioSession state (audioSession.isOtherAudioPlaying) or events related to playback (AVAudioSessionInterruption). Neither approach is detecting the music.
override func setUp() {
super.setUp()
continueAfterFailure = false
XCUIApplication().launch()
app = XCUIApplication();
NotificationCenter.default.addObserver(self, selector: #selector(self.handleInterruption(_:)), name: NSNotification.Name.AVAudioSessionInterruption, object: nil)
}
#objc func handleInterruption(_ notification: Notification) {
print("Received audio interrupt", notification);
}
override func tearDown() {
super.tearDown()
}
func testIsBackgroundMusicPlaying() {
let audioSession: AVAudioSession = AVAudioSession.sharedInstance();
if (audioSession.isOtherAudioPlaying) {
print ("Other audio is playing");
}
}
Any suggestions?
Related
My audio starts and stops as I would expect. When I background the app the music keeps playing, if I activate Siri, the music interrupts but then resumes as I would expect.
The issue I have is that, if my sounds are playing in the background, and I start up Apple Music or Podcasts, the audio mixes together which I don't want however if I use Siri, my audio stops then resumes after.
I want my music to stop and the other to take control of the audio just like it does with Siri. I have tried removing .mixWithOthers but when I do that, it seems that once I background my app and I start Siri, afterwards my audio is no longer able to start again even though the code within the .ended case is called.
func commonInit() {
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)
NotificationCenter.default.addObserver(self, selector: #selector(handleInterruption), name: .AVAudioSessionInterruption, object: nil)
}
var shouldResume: Bool = false
#objc func handleInterruption(_ notification: Notification) {
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeValue)
else { return }
switch type {
case .began:
player?.pause()
case .ended:
guard let optionsValue = info[AVAudioSessionInterruptionOptionKey] as? UInt
else { return }
let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
player?.play()
}
}
}
Ideally I want my app to resume after any interruptions, but I also want my app to stop playing if there are any interruptions.
Thanks
Adding UIApplication.shared.beginReceivingRemoteControlEvents() after setting the category fixed this issue but I'm not sure why.
I'm building an iOS voip app using PJSip library. I am using CallKit also. During an active call, if another call comes, the call waiting screen with hold & accept, end & accept.. is shown. But there is no beep sound so that the user can get notified that a new call is coming. I want to make that beep sound some how. Somebody please help.
I found the solution by just add some audio configuration when second call comes and then there will be beeping sound come up. This is worked for me.
extension CallManager: CXCallObserverDelegate {
func callObserver(_ CallObserver: CallObserver: CXCallObserver, callChanged call: CXCall) {
if call.uuid != firstcall {
configureAudioSession()
}
}
}
func configureAudioSession() {
print("Configuring audio session")
let session = AVAudioSession.sharedInstance()
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: [.mixWithOthers])
try session.setMode(AVAudioSession.Mode.voiceChat)
} catch (let error) {
print("Error while configuring audio session: \(error)")
}
}
My app continues to play music even when the app is in the background and I am hoping to send the user a notification when the AVPlayer gets overridden (e.g. if the user uses another app that over rides it).
Currently my solution is to use a checkInTimer that sets up a notification and if the app does not checkIn after x amount of time, then the notification goes off, but if it does checkIn it deletes the notification and sets up another one. But this solution sucks..
Any ideas?
You need to observe audio interruptions:
import AVFoundation
func setup() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(myInterruptionHandler), name: AVAudioSessionInterruptionNotification, object: nil)
}
func myInterruptionHandler(notification: NSNotification) {
var info = notification.userInfo!
var intValue: UInt = 0
(info[AVAudioSessionInterruptionTypeKey] as! NSValue).getValue(&intValue)
if let type = AVAudioSessionInterruptionType(rawValue: intValue) {
switch type {
case .Began:
// interruption began
case .Ended:
// interruption ended
}
}
}
I use AVAudioPlayer to play audio. I have background audio enabled and the audio sessions are configured correctly.
I implemented the audioSessionGotInterrupted method to be informed if the audio session gets interrupted. This is my current code:
#objc private func audioSessionGotInterrupted(note: NSNotification) {
guard let userInfo = note.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
return
}
if type == .began {
print("interrupted")
// Interruption began, take appropriate actions
player.pause()
saveCurrentPlayerPosition()
}
else if type == .ended {
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
if options == .shouldResume {
print("restored")
// Interruption Ended - playback should resume
setupPlayer()
player.play()
} else {
// Interruption Ended - playback should NOT resume
// just keep the player paused
}
}
}
}
Now I do the following:
Play some audio
Lock the phone
Pause the audio
Wait for some seconds until I see in the XCode debugger that the app has been stopped in background
I hit play in the lockscreen
My commandCenter play() methods gets called as expected. However also the audioSessionGotInterrupted method gets called with type == .began.
How is that possible? I expect to see no notification of that kind or at least .ended
I use iOS 10 beta 8.
Check this
https://developer.apple.com/documentation/avfoundation/avaudiosession/1616596-interruptionnotification
Starting in iOS 10, the system will deactivate the audio session of most apps in response to the app process being suspended. When the app starts running again, it will receive an interruption notification that its audio session has been deactivated by the system. This notification is necessarily delayed in time because it can only be delivered once the app is running again. If your app's audio session was suspended for this reason, the userInfo dictionary will contain the AVAudioSessionInterruptionWasSuspendedKey key with a value of true.
If your audio session is configured to be non-mixable (the default behavior for the playback, playAndRecord, soloAmbient, and multiRoute categories), it's recommended that you deactivate your audio session if you're not actively using audio when you go into the background. Doing so will avoid having your audio session deactivated by the system (and receiving this somewhat confusing notification).
if let reason = AVAudioSessionInterruptionType(rawValue: reasonType as! UInt) {
switch reason {
case .began:
var shouldPause = true
if #available(iOS 10.3, *) {
if let _ = notification.userInfo?[AVAudioSessionInterruptionWasSuspendedKey] {
shouldPause = false
}
}
if shouldPause {
self.pause()
}
break
case .ended:
break
}
}
While the answer above is not wrong it still caused a lot of trouble in my app and a lot of boilerplate code for checking multiple cases.
If you read the description of AVAudioSessionInterruptionWasSuspendedKey it says that the notification is thrown if you didn't deactivate your audio session before your app was sent to the background (which happens every time you lock the screen)
To solve this issue you simply have to deactivate your session if there is no sound playing when app is sent to background and activate it back if the sound is playing. After that you will not receive the AVAudioSessionInterruptionWasSuspendedKey notification.
NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: .main) { sender in
guard self.player.isPlaying == false else { return }
self.setSession(active: false)
}
NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main) { sender in
guard self.player.isPlaying else { return }
self.setSession(active: true)
}
func setSession(active: Bool) -> Bool {
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playback, mode: .default)
try session.setActive(active)
return true
} catch let error {
print("*** Failed to activate audio session: \(error.localizedDescription)")
return false
}
}
Note: Activating session is probably not necessary because it is handled by Apple's internal playback classes (like AVPlayer for example) but it is a good practice to do it manually.
Hi I build a basic game with swift that has background music. The game has multiple image views. I have the music playing with with the code below. How do I stop the music if the user exits out of the app using the home button. as it is now the music continues to play even when the user presses the home button. The background music continues through out all image views which is what i want.
func playBackgroundMusic(thesong: String) {
let url = NSBundle.mainBundle().URLForResource(thesong, withExtension: nil)
guard let newURL = url else {
print("Could not find file: \(thesong)")
return
}
do {
backgroundMusicPlayer = try AVAudioPlayer(contentsOfURL: newURL)
backgroundMusicPlayer.numberOfLoops = -1
backgroundMusicPlayer.prepareToPlay()
backgroundMusicPlayer.play()
} catch let error as NSError {
print(error.description)
}
}
playBackgroundMusic("thesong.mp3")
You can use an observer in your controller like this:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("pauseSong:"), name:UIApplicationDidEnterBackgroundNotification, object: nil)
And call this function when your controller detect when your app is running in background:
func pauseSong(notification : NSNotification) {
backgroundMusicPlayer.pause()
}
Then, when your app is opened again, add another observer to play the song:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("playSong:"), name:UIApplicationWillEnterForegroundNotification, object: nil)
Then call this function:
func playSong(notification : NSNotification) {
backgroundMusicPlayer.play()
}
Hope it helps you ;)
You have to use this function in your controller:
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
backgroundMusicPlayer.pause()
}