I decided to rewrote audiorecorder class from Objective C to Swift.
In Objective C recording works, but in Swift AVAudioRecorderDelegate delegate methods not called, recorder starts successfully.
How can I fix this?
class VoiceRecorder: NSObject, AVAudioPlayerDelegate, AVAudioRecorderDelegate {
var audioRecorder: AVAudioRecorder!
var audioPlayer: AVAudioPlayer?
override init() {
super.init()
var error: NSError?
let audioRecordingURL = self.audioRecordingPath()
audioRecorder = AVAudioRecorder(URL: audioRecordingURL,
settings: self.audioRecordingSettings(),
error: &error)
audioRecorder.meteringEnabled = true
/* Prepare the recorder and then start the recording */
audioRecorder.delegate = self
if audioRecorder.prepareToRecord(){
println("Successfully prepared for record.")
}
}
func audioRecorderDidFinishRecording(recorder: AVAudioRecorder!, successfully flag: Bool) {
println("stop")
if flag{
println("Successfully stopped the audio recording process")
if completionHandler != nil {
completionHandler(success: flag)
}
} else {
println("Stopping the audio recording failed")
}
}
}
func record(){
audioRecorder.record()
}
//UPD.
func stop(#completion:StopCompletionHandler){
self.completionHandler = completion
self.audioRecorder?.stop
}
The problem is this line:
self.audioRecorder?.stop
That is not a call to the stop method. It merely mentions the name of the method. You want to say this:
self.audioRecorder?.stop()
Those parentheses make all the difference.
Related
I'm using AudioKit, and trying to set audio session inactive, when I don't need it. So, I wrote some simple code to understand mechanism of this process, and faced one unexpectable trouble. With attempts of deactivating session, I'm getting error:
[avas] AVAudioSession.mm:1079:-[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.
ViewController.swift:deactivateAudioSession():73:Error Domain=NSOSStatusErrorDomain Code=560030580 "(null)"
This is my sample code:
import UIKit
import AudioKit
class ViewController: UIViewController {
var reverb: AKReverb?
var delay: AKDelay?
var chorus: AKChorus?
var mic: AKMicrophone!
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive),
name: UIApplication.willResignActiveNotification,
object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive),
name: UIApplication.didBecomeActiveNotification,
object: nil)
super.viewDidLoad()
AKSettings.useBluetooth = true
guard let mic = AKMicrophone() else { return }
self.mic = mic
chorus = AKChorus(mic)
chorus?.depth = 1
chorus?.frequency = 44000
let delay = AKDelay(chorus)
delay.feedback = 0.2
reverb = AKReverb(delay)
let mix = AKMixer(reverb)
AudioKit.output = mix
}
#IBAction func launchEngine() {
do {
try AudioKit.start()
} catch {
AKLog(error)
}
}
#IBAction func deactivateAudioSession() {
// mic.stop()
// reverb?.stop()
// delay?.stop()
// chorus?.stop()
do {
try AKSettings.session.setActive(false)
} catch {
AKLog(error)
}
}
#objc func applicationDidBecomeActive() {
launchEngine()
}
#objc func applicationWillResignActive() {
deactivateAudioSession()
}
}
As you can see, basically, I wanted to handle app state changing, but this error happens even if I call deactivateAudioSession() method using UI. I've tried to stop() all node objects (commented code) before deactivation, but error stays. What I'm doing wrong?
I'm using AudioKit to set up a sounds player. My sounds engine is set up in a singleton with these functions:
override func viewDidLoad() {
super.viewDidLoad()
configureAudioPlayer()
}
func configureAudioPlayer() {
leftPanner = AKPanner(leftOscillator)
rightPanner = AKPanner(rightOscillator)
//Set up rain and rainPlayer
do {
rain = try AKAudioFile(readFileName: "rain.wav")
rainPlayer = try AKAudioPlayer(file: rain, looping: true, deferBuffering: false, completionHandler: nil)
} catch {
print(error)
}
mixer = AKMixer(leftPanner, rightPanner, rainPlayer)
envelope = AKAmplitudeEnvelope(mixer)
AudioKit.output = envelope
AudioKit.start()
}
func startPlaying() {
leftOscillator.start()
rightOscillator.start()
rainPlayer.start() //CRASHES HERE AND SAYS NIL OPTIONAL VALUE
envelope.start()
soundIsPlaying = true
}
In a separate view controller I have a play button that calls the following function when pressed:
let player = AudioPlayer.sharedPlayer
#IBAction func playSound(_ sender: Any) {
player.leftOscillator.frequency = 220
player.rightOscillator.frequency = 230
if player.soundIsPlaying == false {
player.startPlaying()
} else {
player.stopPlaying()
}
}
When I press this button the app crashes and says that the rainPlayer is nil. Did I set something up wrong? Why would the rainPlayer be nil when I configured it in the configureAudioPlayer() function?
UPDATE:
Moving the singleton into its own class that inherits from NSObject and calling configureAudioPlayer() in its init() function solves my problem. configureAudioPlayer() in the viewDidLoad() method was never being called.
At the moment I have two functions which are almost identical, but only one of them works and I can't figure out why.
The playSound() function works and uses the class's audioPlayer: AVAudioPlayer object to play one audio file at a time.
To make it so I could play multiple sounds simultaneously, I made a new function called playMySound() which creates its own instance of AVAudioPlayer instead of using the class's audioPlayer: AVAudioPlayer object.
The weird thing is, the sound plays fine when I call playSound(), but it doesn't play when I call playMySound().
Here's the code that works:
var audioPlayer: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
audioPlayer = AVAudioPlayer()
}
func playSound(audioFileName: String) {
if let sound = NSDataAsset(name: audioFileName) {
do {
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try! AVAudioSession.sharedInstance().setActive(true)
try audioPlayer = AVAudioPlayer(data: sound.data)
audioPlayer.delegate = self
audioPlayer.play()
} catch {
print("Error playing sound: \(audioFileName)")
}
}
}
And the code that doesn't work:
func playMySound(audioFileName: String) {
if let sound = NSDataAsset(name: audioFileName) {
do {
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try! AVAudioSession.sharedInstance().setActive(true)
var myAudioPlayer: AVAudioPlayer = AVAudioPlayer()
myAudioPlayer = try AVAudioPlayer(data: sound.data)
myAudioPlayer.delegate = self
// Calling prepareToPlay() doesn't seem to fix it
// myAudioPlayer.prepareToPlay()
print("Trying to play my audio player")
myAudioPlayer.play()
} catch {
print("Error playing sound: \(audioFileName)")
}
}
}
"Trying to play my audio player" gets outputted to the console, so it's definitely running through the function, but for some reason calling myAudioPlayer.play() doesn't seem to work.
Can anyone help? Thanks in advance!
I am following instructions here, I've put together this test project to handle interruptions to audio play. Specifically, I'm using the alarm from the default iphone clock app as interruption. It appears that the interruption handler is getting called but is not getting past the let = interruptionType line as "wrong type" showed up twice.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player = AVAudioPlayer()
let audioPath = NSBundle.mainBundle().pathForResource("rachmaninov-romance-sixhands-alianello", ofType: "mp3")!
func handleInterruption(notification: NSNotification) {
guard let interruptionType = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? AVAudioSessionInterruptionType else { print("wrong type"); return }
switch interruptionType {
case .Began:
print("began")
// player is paused and session is inactive. need to update UI)
player.pause()
print("audio paused")
default:
print("ended")
/**/
if let option = notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? AVAudioSessionInterruptionOptions where option == .ShouldResume {
// ok to resume playing, re activate session and resume playing
// need to update UI
player.play()
print("audio resumed")
}
/**/
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
do {
try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath))
player.numberOfLoops = -1 // play indefinitely
player.prepareToPlay()
//player.delegate = player
} catch {
// process error here
}
// enable play in background https://stackoverflow.com/a/30280699/1827488 but this audio still gets interrupted by alerts
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
// add observer to handle audio interruptions
// using 'object: nil' does not have a noticeable effect
let theSession = AVAudioSession.sharedInstance()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.handleInterruption(_:)), name: AVAudioSessionInterruptionNotification, object: theSession)
// start playing audio
player.play()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Furthermore, following an idea here, I have modified the handler to
func handleInterruption(notification: NSNotification) {
//guard let interruptionType = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? AVAudioSessionInterruptionType else { print("wrong type"); return }
if notification.name != AVAudioSessionInterruptionNotification
|| notification.userInfo == nil{
return
}
var info = notification.userInfo!
var intValue: UInt = 0
(info[AVAudioSessionInterruptionTypeKey] as! NSValue).getValue(&intValue)
if let interruptionType = AVAudioSessionInterruptionType(rawValue: intValue) {
switch interruptionType {
case .Began:
print("began")
// player is paused and session is inactive. need to update UI)
player.pause()
print("audio paused")
default:
print("ended")
/** /
if let option = notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? AVAudioSessionInterruptionOptions where option == .ShouldResume {
// ok to resume playing, re activate session and resume playing
// need to update UI
player.play()
print("audio resumed")
}
/ **/
player.play()
print("audio resumed")
}
}
}
Results are that all of "began", "audio paused", "ended" and "audio resumed" show up in console but audio play is not actually resumed.
Note: I moved the player.play() outside of the commented out where option == .ShouldResume if statement because that if condition is not true when the .Ended interruption occurs.
(Posted on behalf of the question author, after it was posted in the question).
Solution found! Following discussion here, inserted this in viewDidLoad()
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: AVAudioSessionCategoryOptions.MixWithOthers)
} catch {
}
After clicking "ok" on the alarm interruption, the audio play continued. Unlike previously noted, the solution does NOT require an interruption handler (which #Leo Dabus has since removed).
However if you are using an interruption handler, .play() must NOT be invoked within handleInterruption() as doing so does NOT guarantee play to resume & seems to prevent audioPlayerEndInterruption() to be called (see docs). Instead .play() must be invoked within audioPlayerEndInterruption() (any of its 3 versions) to guarantee resumption.
Furthermore, AVAudioSession must be give option .MixWithOthers noted by #Simon Newstead if you want your app to resume play after interruption when your app is in the background. It seems that if a user wants the app to continue playing when it goes into the background, it is logical to assume the user also wants the app to resume playing after an interruption while the app is in the background. Indeed that is the behaviour exhibited by the Apple Music app.
#rockhammers suggestion worked for me. Here
before class
let theSession = AVAudioSession.sharedInstance()
in viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.handleInterruption(notification:)), name: NSNotification.Name.AVAudioSessionInterruption, object: theSession)
And then the Function
func handleInterruption(notification: NSNotification) {
print("handleInterruption")
guard let value = (notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? NSNumber)?.uintValue,
let interruptionType = AVAudioSessionInterruptionType(rawValue: value)
else {
print("notification.userInfo?[AVAudioSessionInterruptionTypeKey]", notification.userInfo?[AVAudioSessionInterruptionTypeKey])
return }
switch interruptionType {
case .began:
print("began")
vox.pause()
music.pause()
print("audioPlayer.playing", vox.isPlaying)
/**/
do {
try theSession.setActive(false)
print("AVAudioSession is inactive")
} catch let error as NSError {
print(error.localizedDescription)
}
pause()
default :
print("ended")
if let optionValue = (notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? NSNumber)?.uintValue, AVAudioSessionInterruptionOptions(rawValue: optionValue) == .shouldResume {
print("should resume")
// ok to resume playing, re activate session and resume playing
/**/
do {
try theSession.setActive(true)
print("AVAudioSession is Active again")
vox.play()
music.play()
} catch let error as NSError {
print(error.localizedDescription)
}
play()
}
}
}
some reasons interruptionNotification is not working correctly on iOS 12.x So I added silenceSecondaryAudioHintNotification
With alarm notification incoming, you can try to use silenceSecondaryAudioHintNotification.
#objc func handleSecondaryAudioSilence(notification: NSNotification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey] as? UInt,
let type = AVAudioSession.SilenceSecondaryAudioHintType(rawValue: typeValue) else {
return
}
if type == .end {
// Other app audio stopped playing - restart secondary audio.
reconnectAVPlayer()
}
}
This is my function:
func playMusic(filename :String!) {
var playIt : AVAudioPlayer!
let url = NSBundle.mainBundle().URLForResource(filename, withExtension: nil)
if url == nil {
println("could not find \(filename)")
return
}
var error : NSError?
playIt = AVAudioPlayer(contentsOfURL: url, error: &error)
if playIt==nil {
println("could not create audio player")
return
}
playIt.numberOfLoops = -1
playIt.prepareToPlay()
playIt.play()
}
I debugged my app and i saw that the console tells me: could't create audio player
it looks like my playIt var is nil
how do i fix it?
There's another problem with your code: once you find out why playIt is nil and fix that, you'll discover that playMusic runs without errors, but no sound plays. That's because you've declared playIt as a local variable inside playMusic. Just as it starts playing, you reach the end of playMusic, when all its local variables go out of scope and cease to exist. Microseconds after playIt starts to play, it gets wiped out of existence.
To fix this, declare playIt outside playMusic, as an instance variable. Here's the code for a view controller that uses your playMusic method with my one suggested change:
import UIKit
import AVFoundation
class ViewController: UIViewController {
// Declare playIt here instead
var playIt : AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
playMusic("sad trombone.mp3")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonPressed(sender: AnyObject) {
}
func playMusic(filename :String!) {
// var playIt : AVAudioPlayer! *** this is where you originally declared playIt
let url = NSBundle.mainBundle().URLForResource(filename, withExtension: nil)
if url == nil {
println("could not find \(filename)")
return
}
var error : NSError?
playIt = AVAudioPlayer(contentsOfURL: url, error: &error)
if playIt==nil {
println("could not create audio player")
return
}
playIt.numberOfLoops = -1
playIt.prepareToPlay()
playIt.play()
}
}
Try it both ways -- with playIt declared as an instance variable, and playIt as a local variable inside playMusic. You'll want to go with the former.
I'm also seconding nhgrif's suggestion: playMusic should take a String or String? parameter; not String!