I have an observable object which provides functionality to my app to play the same chime sound whenever I need (repeatedly).
The issue is that it plays when the iPhone is on silent and I cannot stop it. I Googled as much as I could and found that people were advising to set the audio category to 'ambient', however, this still does not appear to fix the issue for me.
Here's my Chime class:
class Chime: ObservableObject {
let chimeSoundUrl = URL(fileURLWithPath: Bundle.main.path(forResource: "chime.wav", ofType: nil)!)
private var audioPlayer: AVAudioPlayer?
init() {
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.ambient, options: .duckOthers)
try audioSession.setActive(true)
} catch {
print("Failed to activate audio session: \(error.localizedDescription)")
}
do {
audioPlayer = try AVAudioPlayer(contentsOf: chimeSoundUrl)
audioPlayer?.prepareToPlay()
audioPlayer?.volume = 1.0
} catch {
print("Failed to prepare audio player: \(error.localizedDescription)")
}
}
func play() {
audioPlayer?.currentTime = 0
audioPlayer?.play()
}
}
I have tried setting the audio category in the Chime class' init function, at the top-level of the app in the onAppear of the view playing the chime, and in the Chime class' 'play' method (which actually causes app performance issues) - none of these seem to work.
I'd really appreciate any help. Thanks
Not sure whether you've tried this on a real device, but here's what I found:
Using the simulator, if the silent switch is already active when the app is opened it behaves as if it isn't on silent and the sound plays. If silent switch is activated after the app is opened it works as expected.
When I tried running it on a real device it worked correctly in both cases, so maybe it's a bug in the simulator.
If you're just playing short alert-type sounds you should probably use Audio Services rather than an AVAudioPlayer. That might look like this:
class Chime {
let chimeSoundUrl = URL(fileURLWithPath: Bundle.main.path(forResource: "alert.mp3", ofType: nil)!)
let chimeSoundID: SystemSoundID
init(){
var soundID:SystemSoundID = 0
AudioServicesCreateSystemSoundID(chimeSoundUrl as CFURL, &soundID)
chimeSoundID = soundID
}
func play(){
AudioServicesPlaySystemSound(chimeSoundID)
}
}
Running the above in the simulator the silent switch has no effect and sound always plays, on a real device it works as expected.
I'm using AVAudio to play a sound, but when I do so, the music (In the music app) stops.
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")
}
}
}
// Some code here
playSound(sound: "resume", type: "m4a")
I want to make the sound act like a notification soud and that the music keeps playing. Any way to do this?
Set your AVAudioSession to .duckOthers or .mixWithOthers:
(Before you play sounds):
do {
try AVAudioSession.sharedInstance()
.setCategory(.playback, options: .duckOthers)
try AVAudioSession.sharedInstance()
.setActive(true)
} catch {
print(error)
}
You will first have to define properties of an AVAudioSession. This lets you chose how to play the sound with the help of setCategory(_:mode:options:). What you need is this:
var audioPlayer: AVAudioPlayer?
func playSound(sound: String, type: String) {
if let path = Bundle.main.path(forResource: sound, ofType: type) {
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
try AVAudioSession.sharedInstance().setActive(true)
audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
audioPlayer?.play()
} catch {
print("ERROR")
}
}
}
// Some code here
playSound(sound: "resume", type: "m4a")
Feel free to experiment with the setCategory function by passing different configuration options. You can also read more about mixWithOthers, but the key point is:
An option that indicates whether audio from this session mixes with audio from active sessions in other audio apps.
While trying to build a xylophone in xcode 10.1, using swift 4.2 on iOS12, i used a button to play a .wav file and entered the following code but the following error shows up:
"type 'String' has no member 'playback'"
func playSound() {
guard let url = Bundle.main.url(forResource: "soundName", withExtension: "mp3") else { return }
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
try AVAudioSession.sharedInstance().setActive(true)
/* The following line is required for the player to work on iOS 11. Change the file type accordingly*/
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue)
/* iOS 10 and earlier require the following line:
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3) */
guard let player = player else { return }
player.play()
} catch let error {
print(error.localizedDescription)
}
}
I was looking around StackOverflow and I borrowed this piece of code which works, I don't know if its the conventional way of doing it though.
Swift 4.2
import AVFoundation
var player = AVAudioPlayer()
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "note1", ofType: "wav")!)
do {
player = try AVAudioPlayer(contentsOf: url)
player.play()
} catch {
print("couldn't load file :(")
}
func playSound() {
let soundURL = Bundle.main.url(forResource: selectedSoundFileName, withExtension: "wav")
do{
audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
audioPlayer.play()
} catch {
}
}
This is another variation that you might be able to try! Let me know if this format works for you. I built a xylophone app myself a long time ago inside of a Udemy course. Pretty much would be easier to put the sound files directly into your project and run them through there, than to pull them from anywhere in my opinion.
this is the most "Swifty" way I could implement this for you.
If you click jump to definition on AVAudioSession Xcode will open the raw source file for the class and from there you can view the structure of each method in the class. This is helpful for viewing the structure of each function and for determining the data parameters of each function. Additionally there are annotations above each variation of the class functions compatible for different iOS deployment targets, indicated by the #available(iOS 11.0, *) annotations. We're focusing on the open func setCategory function in this class.
Helpful information in the raw source file
Allowed categories: AVAudioSessionCategoryPlayback
Allowed modes: AVAudioSessionModeDefault, AVAudioSessionModeMoviePlayback, AVAudioSessionModeSpokenAudio
Allowed options: None. Options are allowed when changing the routing policy back to Default
I edited the category and mode parameters in my function as follows:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, mode: AVAudioSessionModeDefault, options: [.mixWithOthers, .allowAirPlay])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
So my current situation is that whenever I press a button, the sound will restart from the beginning and cut off the already playing sound. What I want is the sound to play over itself when I push the button, so if I push it a second time right after the first time, the second sound would play over the first one and not cut it off. Please let me know if you need clarification on what I need. Here is my code so far:
#IBAction func bull(_ sender: Any) {
let url = Bundle.main.url(forResource: "Bull", withExtension: "mp3")!
do
{
player = try AVAudioPlayer(contentsOf: url)
guard let player = player else { return }
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
player.prepareToPlay()
player.play()
}
catch let error
{
print(error.localizedDescription)
}
}
Thanks.
One player can play one sound at a time only. You probably have to do following
Create an array of players
var arrPlayer: [AVAudioPlayer] = []
And then inside your method do the following
let url = Bundle.main.url(forResource: "Bull", withExtension: "mp3")!
do
{
player = try AVAudioPlayer(contentsOf: url)
arrPlayer.append(player)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
try AVAudioSession.sharedInstance().setActive(true)
arrPlayer.last?.prepareToPlay()
arrPlayer.last?.play()
}
catch let error
{
print(error.localizedDescription)
}
I created an app and I am attempting to allow the user to continue to listen to their music while playing my game, but whenever they hit "play" and the ingame sounds occur it will stop the background music. I am developing on iOS using Swift. Here is a piece of the code that initiates the ingame sounds.
func playSpawnedDot() {
var alertSound: NSURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("spawnDot", ofType: "mp3")!)!
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.prepareToPlay()
if volumeBool {
audioPlayer.play()
}
}
You need to set the AVAudioSession category, with one of the following value: https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioSession_ClassReference/index.html (AVAudioSession Class Reference).
The default value is set to AVAudioSessionCategorySoloAmbient. As you can read :
[...] using this category implies that your app’s audio is nonmixable—activating your session will interrupt any other audio sessions which are also nonmixable. To allow mixing, use the AVAudioSessionCategoryAmbient category instead.
You have to change the category, before you play your sound. To do so :
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
You don't need to call those line each time you play the sound. You might want to do it only once.
Swift 4 version:
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
try? AVAudioSession.sharedInstance().setActive(true)
This is how I do it in Swift 3.0
var songPlayer : AVAudioPlayer?
func SetUpSound() {
if let path = Bundle.main.path(forResource: "TestSound", ofType: "wav") {
let filePath = NSURL(fileURLWithPath:path)
songPlayer = try! AVAudioPlayer.init(contentsOf: filePath as URL)
songPlayer?.numberOfLoops = -1 //logic for infinite loop
songPlayer?.prepareToPlay()
songPlayer?.play()
}
let audioSession = AVAudioSession.sharedInstance()
try!audioSession.setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.duckOthers) //Causes audio from other sessions to be ducked (reduced in volume) while audio from this session plays
}
You can see more of AVAudioSessionCategoryOptions here: https://developer.apple.com/reference/avfoundation/avaudiosessioncategoryoptions
Here's what I am using for Swift 2.0:
let sess = AVAudioSession.sharedInstance()
if sess.otherAudioPlaying {
_ = try? sess.setCategory(AVAudioSessionCategoryAmbient, withOptions: .DuckOthers)
_ = try? sess.setActive(true, withOptions: [])
}
Please note that you can replace .DuckOthers with [] if you don't want to lower background music and instead play on top to it.
Since they can't seem to make up their minds from version to version. Here it is in Swift 5.0
do{
try AVAudioSession.sharedInstance().setCategory(.ambient)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
} catch {
NSLog(error.localizedDescription)
}
lchamp's solution worked perfectly for me, adapted for Objective-C:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
**
Updated for Swift 3.0
**
The name of the sound I am playing is shatter.wav
func shatterSound() {
if let soundURL = Bundle.main.url(forResource: "shatter", withExtension: "wav") {
var mySound: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL as CFURL, &mySound)
AudioServicesPlaySystemSound(mySound);
}
}
Then where ever you want to play the sound call
shatterSound()
If you want to play an alert sound:
public func playSound(withFileName fileName: String) {
if let soundUrl = Bundle.main.url(forResource: fileName, withExtension: "wav") {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, with:[.duckOthers])
try AVAudioSession.sharedInstance().setActive(true)
var soundId: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundId)
AudioServicesAddSystemSoundCompletion(soundId, nil, nil, { (soundId, clientData) -> Void in
AudioServicesDisposeSystemSoundID(soundId)
do {
// This is to unduck others, make other playing sounds go back up in volume
try AVAudioSession.sharedInstance().setActive(false)
} catch {
DDLogWarn("Failed to set AVAudioSession to inactive. error=\(error)")
}
}, nil)
AudioServicesPlaySystemSound(soundId)
} catch {
DDLogWarn("Failed to create audio player. soundUrl=\(soundUrl) error=\(error)")
}
} else {
DDLogWarn("Sound file not found in app bundle. fileName=\(fileName)")
}
}
And if you want to play music:
import AVFoundation
var audioPlayer:AVAudioPlayer?
public func playSound(withFileName fileName: String) {
if let soundUrl = Bundle.main.url(forResource: fileName, withExtension: "wav") {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, with:[.duckOthers])
try AVAudioSession.sharedInstance().setActive(true)
let player = try AVAudioPlayer(contentsOf: soundUrl)
player.delegate = self
player.prepareToPlay()
DDLogInfo("Playing sound. soundUrl=\(soundUrl)")
player.play()
// ensure the player does not get deleted while playing the sound
self.audioPlayer = player
} catch {
DDLogWarn("Failed to create audio player. soundUrl=\(soundUrl) error=\(error)")
}
} else {
DDLogWarn("Sound file not found in app bundle. fileName=\(fileName)")
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
self.audioPlayer?.stop()
do {
// This is to unduck others, make other playing sounds go back up in volume
try AVAudioSession.sharedInstance().setActive(false)
} catch {
DDLogWarn("Failed to set AVAudioSession inactive. error=\(error)")
}
}
For Swift (Objective-C like this too)
you can use this link for best answer and if you don't have any time for watching 10 minutes the best action is that you just copy below code in your AppDelegate in didFinishLaunchingWithOptions and then select your project's target then go to Capabilities and at last in Background modes check on Audio, AirPlay and Picture in Picture
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayback)
}
catch {
}
}
I didn't think that just setting the AVAudioSession to AVAudioSessionCategoryOptions.duckOthers would work, but it did. Here is my full code to help the rookies like myself.
Swift 4 - Full Example:
var audioPlayer = AVAudioPlayer()
func playSound(sound: String){
let path = Bundle.main.path(forResource: sound, ofType: nil)!
let url = URL(fileURLWithPath: path)
let audioSession = AVAudioSession.sharedInstance()
try!audioSession.setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.duckOthers)
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer.play()
} catch {
print("couldn't load the file")
}
}
I still need to figure out setActive (I was looking at this blog post) but the audio stops ducking when I leave the app, so it works for my app.
Swift 5 Version based on lchamp`s answer.
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient)
try? AVAudioSession.sharedInstance().setActive(true)
This works perfectly for me on iOS 14.4 and Swift 5. It's a bit different answer from others by using the .duckOthers option (and you can also mix directly with sound if you'd like with .mixWithOthers), but it works perfectly while music plays. It will lower the volume of the music, play the "beep" sound, and then raise the music volume back up to normal. It also captures error data using Google Firebase Crashlytics if there is an issue, and tries to raise the volume to normal even on an error.
This code will also work perfectly on the first, and all other, plays of your sound without it stopping the music ever.
func playSound() {
do {
if let path = Bundle.main.path(forResource: "beep", ofType: "mp3") {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: .duckOthers)
try AVAudioSession.sharedInstance().setActive(true)
let filePath = NSURL(fileURLWithPath:path)
songPlayer = try AVAudioPlayer.init(contentsOf: filePath as URL)
songPlayer?.numberOfLoops = 0
songPlayer?.prepareToPlay()
songPlayer?.play()
try AVAudioSession.sharedInstance().setActive(false)
}
} catch (let error) {
try? AVAudioSession.sharedInstance().setActive(false)
Crashlytics.crashlytics().setCustomValue(error.localizedDescription, forKey: "audio_playback_error")
}
}
An important thing the others answers do not have is that you need to call false with the .notifyOthers flag when deactivating:
try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
The reason for this is that other apps playing music in the background will know when to turn their audio back on when you deactivate yours. Otherwise your background music won't turn back on if you turned off your session.