Sound not playing in simulator, playing during debug in Xcode - swift

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?

Related

Audio not playing in SwiftUI

I'm having an issue getting an audio file to play when the function is called.
I am using an AVAudioPlayer to try and play the file following the instructions form here:
https://www.hackingwithswift.com/example-code/media/how-to-play-sounds-using-avaudioplayer
After the button is pressed in the view, it calls a func to play the sound, but from what I can tell, nothing is played. There are no errors thrown, and the file is found. The app also uses a speech synthesizer when a button is pushed, and that plays fine.
I looked around stack overflow and followed the instructions from here:
Where to place code for audio playback in a SwiftUI app
But still the audio is not played when the button is pushed.
Here is the func:
func playSound() {
var sound = AVAudioPlayer()
if let path = Bundle.main.path(forResource: "TryAgain", ofType: "wav") {
do {
sound = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
print("Playing sound")
sound.play()
} catch {
print( "Could not find file")
}
}
Here is the class:
class Player: BindableObject {
let willChange = PassthroughSubject<Player, Never>()
var isPlaying: Bool = false {
willSet {
willChange.send(self)
}
}
func playSound() {
var sound = AVAudioPlayer()
if let path = Bundle.main.path(forResource: "TryAgainWav", ofType: "wav") {
do {
sound = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
print("Playing sound")
sound.play()
} catch {
print( "Could not find file")
}
}
}
}
Update: Not sure if this helps, but I built this in with IB instead of SwiftUI and noticed the same message is printed in the console when I click the button to play the audio file:
2019-07-22 11:29:41.075568-0700 PlaySoundPrac[13952:46549155] [plugin] AddInstanceForFactory: No factory registered for id F8BB1C28-BAE8-11D6-9C31-00039315CD46
Any help would be greatly appreciated
I'm pretty new myself, but I'll do my best. What happens if you move the declaration of AVAudioPlayer outside the function? For example:
import Combine
class Player: ObservableObject {
var sound: AVAudioPlayer!
let willChange = PassthroughSubject<Player, Never>()
var isPlaying: Bool = false {
willSet {
willChange.send(self)
}
}
func playSound() {
if let path = Bundle.main.path(forResource: "TryAgainWav", ofType: "wav") {
do {
sound = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
print("Playing sound")
sound.play()
} catch {
print( "Could not find file")
}
}
}
}
Try this out and let me know if it works or not! Thanks.
I had a problem that audio was not played on iOS 13 Simulator in Xcode 11.5 while it was okay in Xcode SwiftUI preview and physical device.

Playing sounds in Xcode while playing music from apps

I am currently using the following code to play music within my Xcode project. However, when I play music, other music apps (eg: spotify) stop playing. I understand there is AVAudioSessionCategoryAmbient, but i am not to sure how to set this function within my code.
How do i play music within my app without stopping music from other apps that are currently playing?
Any help would be much appreciated :D
Thanks!
import UIKit
import AVFoundation
let appDelegate = UIApplication.shared.delegate as! AppDelegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
/* MUSIC */
var musicIsPlaying: Bool = false
var bgMusicLoop: AVAudioPlayer!
func playBackgroundMusic(fileName: String, withExtenstion fileExtension: String) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
if appDelegate.musicIsPlaying == false {
appDelegate.musicIsPlaying = true
let filePath: String = Bundle.main.path(forResource: fileName, ofType: fileExtension)!
if (appDelegate.bgMusicLoop != nil) {
appDelegate.bgMusicLoop.stop()
}
appDelegate.bgMusicLoop = nil
do {
appDelegate.bgMusicLoop = try AVAudioPlayer(contentsOf: NSURL.fileURL(withPath: filePath))
} catch {
print(error)
}
//A negative means it loops forever
appDelegate.bgMusicLoop.numberOfLoops = -1
appDelegate.bgMusicLoop.prepareToPlay()
appDelegate.bgMusicLoop.play()
}
}
Add try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient) to your code.
AVAudioSessionCategoryAmbient "plays sounds that add polish or interest but are not essential to the app’s use."

Ringer volume slider, swift

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 :)

Playing one sound instance at a time

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.

Apple Music & AVAudioEngine in Swift

How can we access songs in the Apple Music library with AVAudioPlayerNode/AVAudioEngine for playback and processing?
I have asked this question in Apple forum.
"Apple Music" may refer to:
the music streaming service
its iOS client (known as "Music")
its macOS client, which doubles as a local media management and playback app (formerly known as iTunes, known as "Music" as of macOS Catalina).
Due to DRM restrictions it is not possible to play tracks from the Apple Music catalogue downloaded onto your device from the Apple Music macOS or iOS clients. However you can play audio files you own and which you've synced onto your device using the macOS Music app or the Finder app, as follows:
Add the NSAppleMusicUsageDescription key to your Info.plist file, and its corresponding value
Setup your AVAudioSession and AVAudioEngine
Find the URL of the media item you want to play (you can use MPMediaPickerController like in the example below or you can make your own MPMediaQuery)
Create an AVAudioFile from that URL
Create an AVAudioPlayerNode set to play that AVAudioFile
Connect the player node to the engine's output node
import UIKit
import AVFoundation
import MediaPlayer
class ViewController: UIViewController {
let engine = AVAudioEngine()
override func viewDidLoad() {
super.viewDidLoad()
let mediaPicker = MPMediaPickerController(mediaTypes: .music)
mediaPicker.allowsPickingMultipleItems = false
mediaPicker.showsItemsWithProtectedAssets = false // These items usually cannot be played back
mediaPicker.showsCloudItems = false // MPMediaItems stored in the cloud don't have an assetURL
mediaPicker.delegate = self
mediaPicker.prompt = "Pick a track"
present(mediaPicker, animated: true, completion: nil)
}
func startEngine(playFileAt: URL) {
do {
try AVAudioSession.sharedInstance().setCategory(.playback)
let avAudioFile = try AVAudioFile(forReading: playFileAt)
let player = AVAudioPlayerNode()
engine.attach(player)
engine.connect(player, to: engine.mainMixerNode, format: avAudioFile.processingFormat)
try engine.start()
player.scheduleFile(avAudioFile, at: nil, completionHandler: nil)
player.play()
} catch {
assertionFailure(String(describing: error))
}
}
}
extension ViewController: MPMediaPickerControllerDelegate {
func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
guard let item = mediaItemCollection.items.first else {
print("no item")
return
}
print("picking \(item.title!)")
guard let url = item.assetURL else {
return print("no url")
}
dismiss(animated: true) { [weak self] in
self?.startEngine(playFileAt: url)
}
}
func mediaPickerDidCancel(_ mediaPicker: MPMediaPickerController) {
dismiss(animated: true, completion: nil)
}
}