so my problem is that I'm creating an app which uses local notifications as an alarm. The only issue is that if the device has its ringer volume down or on silent, it will not play any sound. I am aware that you are unable to do this in the background however is it possible to have a slider linked up to the ringer volume (like a slider for MPVolume)? Also is it possible to check whether the device is muted? Again I just want to point out I am aware that you cannot simply change it without the user knowing but I was wondering whether the two methods stated earlier are possible. Any help on the topic will be greatly appreciated.
It is not possible to detect if an iPhone has its silence switch on or to change the "Ringer" (Not Volume) as Apple does not provide access to them.
There used to be a workaround a while back using Objective-C but I'm not quite sure if it still works, you are welcome to try if you want to.
AVSystemController
Also is it possible to check whether the device is muted?
This issue is really frustrating and I can't believe Apple makes it so hard :(
Checking the volume itself is rather simple:
let audioSession = AVAudioSession.sharedInstance()
let volume: Float = audioSession.outputVolume
If volume is 0.0, it's silent. But the real problem is the ringer switch.
My solution is to play a silent sound (called "silence.mp3" which is 1.5 sec long and all silent) and check after 0.5 sec if it's still being played.
This is totally inspired by the SoundSwitch: https://github.com/moshegottlieb/SoundSwitch
In usage:
MyAudioPlayer.isLoudCheck()
{
isLoud in
print("%%% - device is loud = \(isLoud)")
}
I changed the audio player class to this (usually I just use it to play sound files):
import AVFoundation
class MyAudioPlayer: NSObject, AVAudioPlayerDelegate
{
private static let sharedPlayer: MyAudioPlayer =
{
return MyAudioPlayer()
}()
public var container = [String : AVAudioPlayer]()
static func isLoudCheck(completionHandler: #escaping (Bool?) -> ())
{
let name = "silence"
let key = name
var player: AVAudioPlayer?
for (file, thePlayer) in sharedPlayer.container
{
if file == key
{
player = thePlayer
break
}
}
if player == nil, let resource = Bundle.main.path(forResource: name, ofType: "mp3")
{
do
{
player = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: resource), fileTypeHint: AVFileTypeMPEGLayer3)
}
catch
{
print("%%% - audio error?")
}
}
if let thePlayer = player
{
print("%%% - the player plays")
thePlayer.delegate = sharedPlayer
sharedPlayer.container[key] = thePlayer
thePlayer.play()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute:
{
if let thePlayer = player
{
if thePlayer.isPlaying
{
print("%%% - check: playing")
completionHandler(true)
}
else
{
print("%%% - check: not playing")
completionHandler(false)
}
}
})
}
static func isPlaying(key: String) -> Bool?
{
return sharedPlayer.container[key]?.isPlaying
}
}
Hope this helps someone :)
Related
I'm trying to face with the book "Intro to app development" by apple. I'm stuck on exercise AnimalSounds.
There is a SimpleSound class provided by the book, and our job is to use a sound object to reproduce a sound.
The simulator does not play any sound.
The audio files are in the build bundle and are found by the class.
I checked the simulator with other apps (e.g. youtube on safari) and sound works.
SimpleSound class (provided by example, not written by me)
import Foundation
import AudioToolbox
class SimpleSound {
private var soundID: SystemSoundID = 0
public init(named name: String) {
if let soundURL = soundURL(forName: name) {
let status = AudioServicesCreateSystemSoundID(soundURL as CFURL, &soundID)
if status != noErr {
print("Unable to create sound at URL: '\(name)'")
soundID = 0
}
}
}
public func play() {
if soundID != 0 {
print("Playing sound \(soundID)")
AudioServicesPlaySystemSound(soundID)
//AudioServicesPlaySystemSound(1001)
}
}
private func soundURL(forName name: String) -> URL? {
let fileExtensions = ["m4a", "wav", "mp3", "aac", "adts", "aif", "aiff", "aifc", "caf", "mp4"]
for fileExtention in fileExtensions {
if let soundURL = Bundle.main.url(forResource: name, withExtension: fileExtention) {
return soundURL
}
}
print("Unable to find sound file with name '\(name)'")
return nil
}
deinit {
if soundID != 0 {
AudioServicesDisposeSystemSoundID(soundID)
}
}
}
Code used in my viewController (written by me) in a button tapped action:
let meowSound = SimpleSound(named: "meow")
meowSound.play()
When debugging from Xcode line by line, when the function play() is invoked, the sound is reproduced. When app is running in simulator (with no debugging) or in actual iPhone sound does not play.
If standard system sound 1001 is uncommented, sound is played for both simulator and iPhone.
Any ideas?
New code
class SceneTwo: SKScene, SKPhysicsContactDelegate {
let flap = SKAction.playSoundFileNamed("flap.caf", waitForCompletion: false)
let whack = SKAction.playSoundFileNamed("whack.caf", waitForCompletion: false)
let tap = SKAction.playSoundFileNamed("tap.caf", waitForCompletion: false)
Then I simply have put
run(tap)
run(flap) etc
where necessary..
Hi just wondering if I am using the correct coding to play sounds in my game. For some context my game is similar to Flappy bird. One sound is played each time the screen is touched (when the bird has impulse upwards) the second sound is when the bird collects a coin in between each wall.
I have noticed that both of these sounds are causing my game to lag.
Below is my relative sound code for the game.
import AVFoundation
var flap: AVAudioPlayer?
var tap: AVAudioPlayer?
override func didMove(to view: SKView) {
tap?.prepareToPlay()
flap?.prepareToPlay()
func playFlap() {
let url = Bundle.main.url(forResource: "flap", withExtension: "caf")!
do {
flap = try AVAudioPlayer(contentsOf: url)
guard let flap = flap else { return }
flap.play()
} catch let error {
print(error.localizedDescription)
}
}
func playTap() {
let url = Bundle.main.url(forResource: "tap", withExtension: "caf")!
do {
tap = try AVAudioPlayer(contentsOf: url)
guard let tap = tap else { return }
tap.play()
} catch let error {
print(error.localizedDescription)
}
}
After this I have simply
playTap()
playFlap()
to where they are needed.
The sound is clear it just seems to make my spawning walls jump a little bit when the sound is made.
Is there something I am doing that is wrong?
You are getting lag because you are not preloading the sound files. You can preload them at App Launch, and then when you need just play them. For reference look into this stackoverflow's post
And if you still face the same issue then you can add sound in background queue, as demostrated here
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
audioPlayer.play()
})
I have an application that is constantly receiving integer data from a bluetooth sensor and I made it so that if the integer is less than 50, then it should play the MP3.
The problem is that the sensor is very rapidly checking and sending the integers, which is resulting in too many audio instances, basically the the mp3 file is being played too many times at the same time. How can I have it so that it finishes the audio before starting again?
This is the main code:
var player: AVAudioPlayer?
if let unwrappedString = Reading {
let optionalInt = Int(unwrappedString)
if let upwrappedInt = optionalInt {
if(upwrappedInt < 50){
DispatchQueue.global(qos: .background).async {
self.playSound()
}
}
}
}
Sound function:
func playSound() {
guard let url = Bundle.main.url(forResource: "beep1", withExtension: "mp3") else {
print("url not found")
return
}
do {
/// this codes for making this app ready to takeover the device audio
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
/// change fileTypeHint according to the type of your audio file (you can omit this)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3)
// no need for prepareToPlay because prepareToPlay is happen automatically when calling play()
player!.play()
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
}
If the audio player is already playing (isPlaying), don't start playing!
https://developer.apple.com/reference/avfoundation/avaudioplayer/1390139-isplaying
I believe AVAudioPlayer has a delegate method to check if the audio has finished playing:
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
// ----------------------------------------------
// set your custom boolean flag 'isPlayingAudio'
// to false so you can play another audio again
// ----------------------------------------------
}
...
-(void)monitorBluetoothNumber
{
if(bluetoothNumber < 50 && !self.isPlayingAudio)
{
[self playMusic];
self.isPlayingAudio = YES;
}
}
You'll need to setup your audio player and set its delegate obviously.
The code is Objective C but you can easily adapt to Swift.
This code plays the sound when the button is tapped but cancels the previous if it is pressed again. I do not want this to happen I want the same sound to overlap when repeatedly pressed. I believe it might be due to using the same AVAudioPlayer as I have looked on the internet but I am new to swift and want to know how to create a new AVAudioPlayer everytime the method runs so the sounds overlap.
func playSound(sound:String){
// Set the sound file name & extension
let soundPath = NSURL(fileURLWithPath:NSBundle.mainBundle().pathForResource(sound, ofType: "mp3")!)
do {
//Preperation
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch _{
}
do {
try AVAudioSession.sharedInstance().setActive(true)
}
catch _ {
}
//Play the sound
var error:NSError?
do{
audioPlayer = try AVAudioPlayer(contentsOfURL: soundPath)
}catch let error1 as NSError {
error = error1
}
audioPlayer.prepareToPlay()
audioPlayer.play()
}
To play two sounds simultaneously with AVAudioPlayer you just have to use a different player for each sound.
In my example I've declared two players, playerBoom and playerCrash, in the Viewcontroller, and I'm populating them with a sound to play via a function, then trigger the play at once:
import AVFoundation
class ViewController: UIViewController {
var playerBoom:AVAudioPlayer?
var playerCrash:AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
playerBoom = preparePlayerForSound(named: "sound1")
playerCrash = preparePlayerForSound(named: "sound2")
playerBoom?.prepareToPlay()
playerCrash?.prepareToPlay()
playerBoom?.play()
playerCrash?.play()
}
func preparePlayerForSound(named sound: String) -> AVAudioPlayer? {
do {
if let soundPath = NSBundle.mainBundle().pathForResource(sound, ofType: "mp3") {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
return try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: soundPath))
} else {
print("The file '\(sound).mp3' is not available")
}
} catch let error as NSError {
print(error)
}
return nil
}
}
It works very well but IMO is not suitable if you have many sounds to play. It's a perfectly valid solution for just a few ones, though.
This example is with two different sounds but of course the idea is exactly the same for two identic sounds.
I could not find a solution using just AVAudioPlayer.
Instead, I have found a solution to this problem with a library that is built on top of AVAudioPlayer.
The library allows same sounds to be played overlapped with each other.
https://github.com/adamcichy/SwiftySound
I'm trying to play a short sound in my Swift app.
The sound I want to play :
is very short (less than five seconds) ;
is one of the system sound (ex: sms received) ;
will most likely be played multiple time during the life cycle of my application ;
can be fired two time in less than the sound length time (i.e. fire the first one and a second one is fired before the first finish playing) ;
In addition to this, I would like to :
hear the sound only if the application is active and the "silent mode" is off ;
make the phone vibrate (regardless if silent mode is on or off).
So far, here is what I have :
import AVFoundation
class SoundPlayer {
// Singleton
class var sharedInstance : SoundPlayer {
struct Static {
static let instance : SoundPlayer = SoundPlayer()
}
return Static.instance
}
// Properties
var error:NSError?
var audioPlayer = AVAudioPlayer()
let soundURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("sound", ofType: "wav")!) // Working
//let soundURL = NSURL(string:"/System/Library/Audio/UISounds/sms-received1.caf") // Working
//let soundURL = NSURL(fileURLWithPath: NSBundle(identifier: "com.apple.UIKit")!.pathForResource("sms-received1", ofType: "caf")!) // Not working (crash at runtime)
init() {
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
audioPlayer = AVAudioPlayer(contentsOfURL: soundURL, error: &error)
if (error != nil) {
println("[SoundPlayer] There was an error: \(error)")
}
}
func playSound() {
if (audioPlayer.playing) {
audioPlayer.stop()
}
audioPlayer.play()
}
}
What I understand there are several ways to play a sound.
The two most common ways seems to be : AVAudioPlayer (like I did) and AudioToolbox.
From there, I'm asking myself three questions :
Which of those two is the most adapted (best / proper) for my situation ? (AVAudioPlayer is working correctly at the moment)
Are we allowed to play a system sound ? (Very small gain in size if in don't add my own sound to the project)
If so, is NSURL(string:"/System/Library/Audio/UISounds/sms-received1.caf") a good way to retrieve the sound ? Any better / proper solution ?
Is there a way to vibrate the phone at the same time the sound is fired ?