AVFoundation playing audio in background/on lock screen in Swift - swift

I'm trying to find a way with AVFoundation to play audio when the user is on the lock screen or if they lock the app in the middle of using my app
class ViewController: UIViewController, AVAudioPlayerDelegate {
var avPlayer: AVAudioPlayer?
var error: NSError?
... other stuff ...
func playChime(fileName: String) -> Void {
let fileURL: NSURL! = NSBundle.mainBundle().URLForResource(fileName, withExtension: "wav")
self.avPlayer = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
self.avPlayer?.play()
}
... other stuff ...
}
What needs to be added to this to ensure sounds play when the user is on the lock screen as well?

To continue playing audio in the background when the device is locked or on the home screen (on Xcode 6.3):
Add to your ViewController:
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
Then in your Info.plist, add a new entry for UIBackgroundModes and set it to audio. Xcode will automatically fill it out as 'Require background modes' and 'App plays audio or streams audio/video using Airplay'

Related

Swiftui - Play background music with other sound effects and toggle audio button

I have following Code for Audio Player in SwiftUI.
import AVFoundation
var audioPlayer: AVAudioPlayer?
func playSound(sound: String, type: String) {
if let path = Bundle.main.path(forResource: sound, ofType: type) {
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
audioPlayer?.play()
} catch {
print("ERROR: Could not find and play the sound file!")
}
}
}
To Play the sound in Content view its called as.
playSound(sound: "filename", type: "mp3")
Issue is. I add background music mp3. It works fine. But now I assign a click button sound using same command, which stops the background music,
How can I continue playing the background music along with other one off sound effects.
As a bonus how can I create a button which mute/unmute background music within the app.
Thank you so much.

Using an AVPlayer returns a "non-Multipath connection" error

I'm using AVKit to play a youtube URL.
I have this code inside a button action:
#IBAction func trailerButtonAction(_ sender: Any) {
guard let youtubeUrl = youtubeURL else { return }
let player = AVPlayer(url: youtubeUrl)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
present(playerViewController, animated: true) {
player.play()
}
}
The URL is valid, but when I press the button, the video doesn't stop loading and I'm getting this message on Debug area:
nw_endpoint_flow_copy_multipath_subflow_counts Called on non-Multipath connection
Edit:
I found that AVPlayer doesn't support youtube URL
I would say this log isn't necessarily relevant. I was getting this error when trying to playback on the simulator but it wasn't happening on a real device.
One workaround would be to use a 12.4.x simulator as it does not exhibit this issue. Only the 13.x simulators are showing this error. It happens to repeatedly that it slows down Simulator to a craw until all requested tracks have been buffered.
To combat this while testing, I am either not turning on AVPlayer or I am only buffering a short track.
To cut down on the number of errors try initing your AVPlayer like so:
var avPlayer : AVPlayer = AVPlayer()
This may cut down the errors by 30%.

Swift 5.1 Error: [plugin] AddInstanceForFactory: No factory registered for id <CFUUID

App crashes with the following error message
2019-10-12 20:01:34.332334-0700 Awesome App[26368:3535170] [plugin] AddInstanceForFactory: No factory registered for id <CFUUID 0x600002903280> F8BB1C28-BAE8-11D6-9C31-00039315CD46
The breakpoint at crash seems to be related to AVAudioPlayer
let path = Bundle.main.path(forResource: "menu_background.mp3", ofType:nil)!
audioPlayer = try AwesomeAudioPlayer(contentsOf: URL(fileURLWithPath: path)) ---> breakpoint
I believe you all might have added the AVFoundation to the frameworks list in Project General Info tab.
Erroneous Code was as follows:
import SwiftUI
import AVFoundation
struct PlayerDetailView: View {
#State private var downloadedFilePath: URL = nil
var audioPlayer: AVAudioPlayer
var body: some View {
And after I moved the var audioPlayer: AVAudioPlayer declaration to just after the line of import AVFoundation line it seemed to be working.
So following code worked for me in a SwiftUI project:
import SwiftUI
import AVFoundation
var audioPlayer: AVAudioPlayer!
struct PlayerDetailView: View {
#State private var downloadedFilePath: URL = nil
var body: some View {
VStack {
Button("Play the Downloaded Track") {
if let downloadedPath = self.downloadedFilePath?.path, FileManager().fileExists(atPath: downloadedPath) {
do {
audioPlayer = try AVAudioPlayer(contentsOf: self.downloadedFilePath!)
guard let player = audioPlayer else { return }
player.prepareToPlay()
player.play()
} catch let error {
print(error.localizedDescription)
}
} else {
print("The file doesn not exist at path || may not have been downloaded yet")
}
}
}
}
}
I was initially following this tutorial of CodeWithChris and its discussion also led to above change. Also checkout following tutorial too if you need further examples.
Hope this will be helpful to someone of you out there!
Cheers!
I believe the error message is a warning for simulators hence it is not important.
I think your issue is a bug in your code. Should be something like this:
let path = Bundle.main.path(forResource: "menu_background", ofType:"mp3")
audioPlayer = try AwesomeAudioPlayer(contentsOf: URL(fileURLWithPath: path!)) ---> breakpoint
Where the forResource is the name of the file and ofType is the extension.
You can also use Bundle.main.url which will look like this:
let path = Bundle.main.url(forResource: "menu_background", withExtension:"mp3")
audioPlayer = try AwesomeAudioPlayer(contentsOf: URL(fileURLWithPath: path!)) ---> breakpoint
I think it has to do with the AVAudioPlayer going out of scope before the simulator device is able to queue up the sound and play it...or something along those lines. I'm brand new to Swift and I have zero experience with iOS APIs.
Here's what I discovered after experimenting with the placement of:
var player: AVAudioPlayer!
The sound will either PLAY or NOT PLAY depending on the placement of the line above.
Regardless, the following error will always occur on my Simulator devices:
AddInstanceForFactory: No factory registered for id
(I'm on a Late 2013 MacBook Pro + MacOS Catalina + Xcode 11.7 and I tested this on an iPhone SE 2 simulator running iOS 13.7)
Although the error keeps occurring, I'm happy that I at least got the sound to play on the Simulator...
Error occurs and sound DOES NOT play...
import UIKit
import AVFoundation
class ViewController: UIViewController {
func playTheSound() {
// initializing the "player" variable within the "playTheSound()" function will cause the "AddInstanceForFactory: No factory registered for id" error and the sound WILL NOT play on the simulator
var player: AVAudioPlayer!
let url = Bundle.main.url(forResource: "funny_sound", withExtension: "mp3")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
}
}
Error occurs and sound DOES play
import UIKit
import AVFoundation
class ViewController: UIViewController {
// initializing the "player" variable at the class level will still cause the "AddInstanceForFactory: No factory registered for id" error to happen, but the sound will still play on the simulator
var player: AVAudioPlayer!
func playTheSound() {
let url = Bundle.main.url(forResource: "funny_sound", withExtension: "mp3")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
}
}
In my case totally different...
The issue is solved after doing...
var player = AVAudioPlayer()
It didn't work in...
var player:AVAudioPlayer!
(Xcode 12.2)
I think I have solved the issue (hard to prove a negative, since the error was sporadic). I have my AVAudioPlayer in a separate class:
import SwiftUI
import AVFoundation
var audioPlayer: AVAudioPlayer?
class AudioManager {
var userSettings = UserSettings()
func playSoundEffect(_ assetName:String) {
if !userSettings.soundDisabled {
guard let audioData = NSDataAsset(name: assetName)?.data else {
fatalError("Unable to find asset \(assetName)")
}
do {
audioPlayer = try AVAudioPlayer(data: audioData)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch {
print(error.localizedDescription)
}
}
// audioPlayer?.stop()
}
func stopPlay() {
audioPlayer?.setVolume(0.0, fadeDuration: 0.25)
}
}
Then, in the SwiftUI views where I need to play audio, be certain to import AVFoundation.
import SwiftUI
import AVFoundation
struct MyView: View {
...
let audioManager = AudioManager()
...
}
I have found the solution in another stackoverflow thread about AVAudioPlayer, here it is :
If you initialize your AVAudioPlayer like
var wrongMusicPlayer: AVAudioPlayer = AVAudioPlayer() OR wrongMusicPlayer = AVAudioPlayer() in any method then please remove it and just Declare like var wrongMusicPlayer: AVAudioPlayer!.
You could use do/catch to avoid the crash and examine the exception, or ignore the issue all together with try?. For me, this was only showing up in the simulator when calling:
try? AVAudioSession.sharedInstance().setCategory(.playback)
I think it's safe to ignore it in my case.
Similar problem - using Preferences > Sound > Output different than Logictech USB Headset led to app that executed perfect & played sound w/o problems. Was never a problem outside of the Simulator - code on a device worked fine.
TL;DR
This is an especially gnarly & obtuse problem. I also encountered an issue with an unexpected:
No factory registered for id
After a short wait, it was also followed by several other console-reported errors, including:
HALC_ProxyIOContext::IOWorkLoop: the server failed to start, Error:
AQMEIO.cpp:182:AwaitIOCycle: timed out after 15.000s
CA_UISoundClient.cpp:244:StartPlaying_block_invoke: CA_UISoundClientBase::StartPlaying: AddRunningClient failed
error when trying to play sound. Xcode 12.2, Mac OSX Catalina 10.15.7, simulator running iOS 14.2.
Code had previously worked on prior versions of the simulator.
Always had proper import of AVFoundation & declaration of AVAudioPlayer class property as:
var audioPlayer: AVAudioPlayer!
In my case it seems to be related to audio drivers in Mac OSX. This problem ONLY happened when I had Mac System Preferences > Sound > Output set to my Logitech USB Headset. The code otherwise worked when: played through my LG Monitor, played through my AirPods Pro, and when executing outside the simulator and on a device > my iPhone 11 Pro.
Spent over an hour trying to diagnose the issue before restarting & noticing audio working when headset wasn't used for output. Switching the Preferences > Sound > Output settings to something other than the Logitech USB headset immediately fixed the problem in all other playback instances.
Not even sure where to file this issue as an Apple bug, but hoping it helps someone. Am assuming it's an OS-specific issue & not one that'll result in a problem w/the app or code.
I found a solution in Here2Huynh's answer.
let path = Bundle.main.path(forResource: "menu_background.mp3", ofType:nil)!
here change
ofType:nil
to
ofType: "mp3"
and then again.There is no error message when using the emulator.
By the way, I am using the swiftui project
Simulator -> I/O -> Keyboard -> uncheck all

Can play a sound effect on apple watch simulator now?

I want to play a sound when user press on the button on WKInterfaceController. Here is how I did in my project:
- Add a AVFoundation frame work to my watchkit app.
- Import AVFoundation on my WKInterfaceController
- Create 2 variable for audio session and player:
var audioSession:AVAudioSession!
var player:AVAudioPlayer!
- make 2 function for configure an audio session and configure Audio Player:
func configureAudioSession() {
self.audioSession = AVAudioSession.sharedInstance()
var categoryError:NSError?
var activeError:NSError?
// set category cho audio session
self.audioSession.setCategory(AVAudioSessionCategoryPlayback, error: &categoryError)
println("error: \(categoryError)")
// set active cho audio session
var success = self.audioSession.setActive(true, error: &activeError)
if !success {
println("error making audio session active :\(activeError)")
}
}
func configureAudioPlayer() {
// Lay song path
var songPath = NSBundle.mainBundle().pathForResource("Open Source - Sending My Signal", ofType: "mp3")
// Chuyen thanh URL
println("songpath: \(songPath)")
var songURL = NSURL(fileURLWithPath: songPath!)
println("songURL: \(songURL)")
//
var songError:NSError?
// Tao audioplayer
self.player = AVAudioPlayer(contentsOfURL: songURL!, error: &songError)
println("songerror:\(songError)")
self.player.numberOfLoops = 0
}
After that i finish my button press function like this:
#IBAction func startGameButtonPressed() {
self.configureAudioSession()
self.configureAudioPlayer()
self.player.play()
}
Every thing's working fine , I can saw the southPath although my button is working but I cannot hear the sound. I still use these steps on IOS app and it's working fine. May be we can't play a sound effect on Watchkit at this time? If we can, please help me to do that.
No. It is not possible to play sounds with WatchKit on the Apple Watch.
- Apple's WatchKit Evangelist
It is not currently possible to play sounds using the latest build of WatchKit.
I would suggest submitting a feature request.
YEs its possible to play a sound file in apple watch application.You need to add the sound file separately in apple watch application extension and the call the AVAudioPlayer to play the sound file.
e.g:
let path = NSBundle.mainBundle().pathForResource("coinFlip", ofType:"caf")
let fileURL = NSURL(fileURLWithPath: path!)
player = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
player.prepareToPlay()
player.delegate = self
player.play()

didEnterRegion - audio not playing - Swift

I am updating an existing app to Swift. The issue I am having is that the main purpose of the app is to play a sound file when the app gets to a specific location. Everything works in the simulator, but it does not play the sound on the device. If I add the background mode for audio and airplay, it works flawlessly. Apple has rejected this as it doesn't use the audio all the time, just when it enters a region. I did not have to have audio and airplay background mode enabled for iOS7.
I think it has something to do with the audioplayer being dealocated before it has a chance to be used. I cannot figure out how to get it set properly. There is this same question on StackOverflow for Objective C, but I cannot find anything similar for swift (and to me, this is what the answer to that question suggests and the way I had it set for iOS7).
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, CLLocationManagerDelegate, AVSpeechSynthesizerDelegate, AVAudioPlayerDelegate {
var myManager: CLLocationManager!
var audioPlayer = AVAudioPlayer()
var mySpeechSynthesizer = AVSpeechSynthesizer()
func playSound()
{
audioPlayer.prepareToPlay()
audioPlayer.play()
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool)
{
speak()
}
func speak()
{
var myString = "This is the phrase I want to speak"
var mySpeechUtterance:AVSpeechUtterance = AVSpeechUtterance(string:myString)
mySpeechUtterance.rate = 0.12
mySpeechUtterance.voice = AVSpeechSynthesisVoice(language: "en-US")
mySpeechSynthesizer .speakUtterance(mySpeechUtterance)
}
func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!)
{
playSound()
scheduleAllLocations()
}
override func viewDidLoad()
{
super.viewDidLoad()
myManager = CLLocationManager()
myManager.delegate = self
myManager.desiredAccuracy = kCLLocationAccuracyBest
myManager.requestAlwaysAuthorization()
var alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("SpeakMinder", ofType: "mp3")!)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.delegate = self
mySpeechSynthesizer.delegate = self
}
This is the complete code. It works flawlessly if I have audio/airplay enabled in the background. Doesn't play a sound or speak if I have the audio/airplay disabled in the background. (Apple has rejected the app for having audio/airplay enabled)
From the Technical Team
If you are attempting to use AVSpeechSynthesizer from the background you should have the background audio key enabled. AVSpeech simply uses the same audio plumbing as playing any sound.
Regarding it working in iOS7 without having audio in the background enabled
If this key was not required, you were most likely getting lucky due to a bug that we have since corrected.
And an app was approved today with the audio background enabled.
If you are rejected due to having audio enabled in the background for the Speech Synthesizer.
Our own Location Based sample “Breadcrumbs" has the following enabled in the plist. You may point to this sample in your appeal.