How to detect if AVAudioRecorder is paused? - ios

By looking at reference, there is no explicit solution - some property or a delegate callback...
But maybe there could be some trick, how to reliably ask the recorder to tell me whether it is paused. Sometimes the required information can be derived from the state of other properties..etc..
Of course I can store that "paused" information myself throughout the management of the whole recording session. But it's less reliable and I want to be sure, that its not possible as described above.

You could simply check for isRecording to be false, that would either mean that it is stopped or paused.

In SWIFT 2 you just check Booll property called recording:
if audioRecorder.recording{
print("recording")
}else{
print("not recording")
}

You can check a boolean value isRecording that indicates whether the audio recorder is recording.
if audioRecorder.isRecording {
// AudioRecorder is recording.
} else {
// AudioRecorder is not recording.
}

Related

How to fast switch Audio Input in AVCaptureSession?

I am using AVCaptureSession to develop a camera app.
I need trigger some vibration feedback when touch some UI. using code like below:
private var _impact = UIImpactFeedbackGenerator()
private var _select = UISelectionFeedbackGenerator()
private init() {
_impact.prepare()
_select.prepare()
}
func impact() {
_impact.impactOccurred()
}
func select() {
_select.selectionChanged()
}
so I can invoke select() or impact() to trigger a feedback.
But, if I add Audio Device to AVCaptureSession, all feedbacks will be invalidated. so, for the feedback effect, I have to removed AudioDevice from AVCaptureSession first, when I need to record a video, then add the Audio Device to AVCaptureSession, this operation will lag captureOutput that make camera preview freezed in little time.
So, I found another way to try this, first always add Audio Device to AVCaptureSession, then get all AVCaptureConnection about Audio from AVCaptureSession.connections. and set isEnable to false or true, But this way didn't work. even AVCaptureConnection.isEnable is false, the feedbacks also invalidate.
I think maybe there is only way to make feedback worked is don't add audio device to AVCaptureSession.

AVAudioSession - How to switch between speaker and headphones output

I'm trying to mimic behaviour as in Phone app during calling. You can easily switch output sources from/to speaker or headphones.
I know I can force speaker as an output when headphones are connected by calling:
try! audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try! audioSession.overrideOutputAudioPort(.speaker)
However, when I do that, I don't see any way to detect if headphones are still connected to the device.
I initially thought outputDataSources on AVAudioSession would return all posible outputs but it always returns nil.
Is there something I'm missing
You need to change the outputDataSources, as when you overrode it,
now it contains only the .Speaker option
in the Documentation you can find the solution to this,
If your app uses the playAndRecord category, calling this method with the AVAudioSession.PortOverride.speaker option causes audio to be routed to the built-in speaker and microphone regardless of other settings. This change remains in effect only until the current route changes or you call this method again with the AVAudioSession.PortOverride.none option.
Therefore the audio is routed to the built-in speaker, This change remains in effect only untill the current route changes or you call this method again with .noneOption.
it's not possible to forcefully direct sound to headphone unless an accessory is plugged to headphone jack (which activates a physical switch to direct voice to headphone).
So when you want to switch back to headphone, this should work.
And if there is no headphone connected will switch the output device to the small speaker output on the top of the device instead of the big speaker.
let session: AVAudioSession = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayAndRecord)
try session.overrideOutputAudioPort(AVAudioSession.PortOverride.none)
try session.setActive(true)
} catch {
print("Couldn't override output audio port")
}
Read about this AVAdioSession/OverrideOutputAudioPort Here.
You can check if headset connected adding this extension,
extension AVAudioSession {
static var isHeadphonesConnected: Bool {
return sharedInstance().isHeadphonesConnected
}
var isHeadphonesConnected: Bool {
return !currentRoute.outputs.filter { $0.isHeadphones }.isEmpty
}
}
extension AVAudioSessionPortDescription {
var isHeadphones: Bool {
return portType == AVAudioSessionPortHeadphones
}
}
And simply use this line of code
session.isHeadphonesConnected

Keep AVAudioPlayer sound in the memory

I use AVAudioPlayer to play a click sound if the user taps on a button.
Because there is a delay between the tap and the sound, I play the sound once in viewDidAppear with volume = 0
I found that if the user taps on the button within a time period the sound plays immediately, but after a certain time there is a delay between the tap and the sound in this case also.
It seems like in the first case the sound comes from cache of the initial play, and in the second case the app has to load the sound again.
Therefore now I play the sound every 2 seconds with volume = 0 and when the user actually taps on the button the sound comes right away.
My question is there a better approach for this?
My goal would be to keep the sound in cache within the whole lifetime of the app.
Thank you,
To avoid audio lag, use the .prepareToPlay() method of AVAudioPlayer.
Apple's Documentation on Prepare To Play
Calling this method preloads buffers and acquires the audio hardware
needed for playback, which minimizes the lag between calling the
play() method and the start of sound output.
If player is declared as an AVAudioPlayer then player.prepareToPlay() can be called to avoid the audio lag. Example code:
struct AudioPlayerManager {
var player: AVAudioPlayer? = AVAudioPlayer()
mutating func setupPlayer(soundName: String, soundType: SoundType) {
if let soundURL = Bundle.main.url(forResource: soundName, withExtension: soundType.rawValue) {
do {
player = try AVAudioPlayer(contentsOf: soundURL)
player?.prepareToPlay()
}
catch {
print(error.localizedDescription)
}
} else {
print("Sound file was missing, name is misspelled or wrong case.")
}
}
Then play() can be called with minimal lag:
player?.play()
If you save the pointer to AVAudioPlayer then your sound remains in memory and no other lag will occur.
First delay is caused by sound loading, so your 1st playback in viewDidAppear is right.

Using Spotify/background music with camera open

I have an app that needs to have:
Background music playing while using the app (eg. spotify)
Background music playing while watching movie from AVPlayer
Stop the music when recording a video
Like Snapchat, the camera-viewcontroller is part of a "swipeview" and therefore always on.
However, when opening and closing the app, the music makes a short "crack" noise/sound that ruins the music.
I recorded it here:
https://soundcloud.com/morten-stulen/hacky-sound-ios
(3 occurrences)
I use these settings for changing the AVAudiosession in the appdelegate didFinishLaunchingWithOptions:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord,withOptions:
[AVAudioSessionCategoryOptions.MixWithOthers,
AVAudioSessionCategoryOptions.DefaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("error")
}
I use the LLSimpleCamera control for video recording and I've set the session there to:
_session.automaticallyConfiguresApplicationAudioSession = NO;
It seems others have the same problem with other camera libraries as well:
https://github.com/rFlex/SCRecorder/issues/127
https://github.com/rFlex/SCRecorder/issues/224
This guy removed the audioDeviceInput, but I kinda need that for recording video.
https://github.com/omergul123/LLSimpleCamera/issues/48
I also tried with Apple's code "AvCam", and I still have the same issue. How does Snapchat do this?!
Any help would be greatly appreciated, and I'll gladly provide more info or code!
I do something similar to what you're wanting, but without the camera aspect, but I think this will do what you want. My app allows background audio that will mix with non-fullscreen video/audio. When the user plays an audio file or a full screen video file, I stop the background audio completely.
The reason I do SoloAmbient then Playback is because I allow my audio to be played in the background when the device is locked. Going SoloAmbient will stop all background music playing and then switching to Playback lets my audio play in the app as well as in the background.
This is why you see a call to a method that sets the lock screen information in the Unload method. In this case, it is nulling it out so that there is no lock screen info.
In AppDelegate.swift
//MARK: Audio Session Mixing
func allowBackgroundAudio()
{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: .MixWithOthers)
} catch {
NSLog("AVAudioSession SetCategory - Playback:MixWithOthers failed")
}
}
func preventBackgroundAudio()
{
do {
//Ask for Solo Ambient to prevent any background audio playing, then change to normal Playback so we can play while locked
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch {
NSLog("AVAudioSession SetCategory - SoloAmbient failed")
}
}
When I want to stop background audio, for example when playing an audio track that should be alone, I do the following:
In MyAudioPlayer.swift
func playUrl(url: NSURL?, backgroundImageUrl: NSURL?, title: String, subtitle: String)
{
ForgeHelper.appDelegate().preventBackgroundAudio()
if _mediaPlayer == nil {
self._mediaPlayer = MediaPlayer()
_mediaPlayer!.delegate = self
}
//... Code removed for brevity
}
And when I'm done with my media playing, I do this:
private func unloadMediaPlayer()
{
if _mediaPlayer != nil {
_mediaPlayer!.unload()
self._mediaPlayer = nil
}
_controlView.updateForProgress(0, duration: 0, animate: false)
ForgeHelper.appDelegate().allowBackgroundAudio()
setLockScreenInfo()
}
Hope this helps you out!

AVAudioSession overrideOutputAudioPort & localNotifications in mute

Hello I tried the various solutions to similar questions but couldnt get my code to work. I have the following function that I call in my app:
func PlaySound (WhenToPlaySound:String) {
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
if WhenToPlaySound == "BeginningOfRound" {
if UIApplication.sharedApplication().applicationState == UIApplicationState.Background {
soundnotification.soundName = "BoxingBellStart.wav"
UIApplication.sharedApplication().scheduleLocalNotification(soundnotification)
println("timer is done in background mode")
} else {
// Load Sound
soundlocation = NSBundle.mainBundle().URLForResource("BoxingBellStart", withExtension: "wav")!
player = AVAudioPlayer(contentsOfURL: soundlocation, error: &Error)
player.volume = 1.0
// Play Sound
player.play()
println("timer is done in active mode")
}
} else {
if UIApplication.sharedApplication().applicationState == UIApplicationState.Background {
soundnotification.soundName = "Boxing.wav"
UIApplication.sharedApplication().scheduleLocalNotification(soundnotification)
println("timer is done in background mode")
} else {
// Load Sound
soundlocation = NSBundle.mainBundle().URLForResource("Boxing", withExtension: "wav")!
player = AVAudioPlayer(contentsOfURL: soundlocation, error: &Error)
player.volume = 1.0
// Play Sound
player.play()
println("timer is done in active mode")
}
}
}
Mostly it works except two things:
1. I can't seem to override the speaker volume. I want the system volume to be full volume before I play my sound.
The LocalNotifications that are set to activate when the app is in background mode only pay when the device isn't muted.
To address the first problem I wanted to the following but didnt know how to use it:
AVAudioSession.sharedInstance().overrideOutputAudioPort(<#portOverride: AVAudioSessionPortOverride#>, error: <#NSErrorPointer#>)
Thanks in advance,
Ace
overrideOutputAudioPort affects the audio routing, not the volume.
When you say that you want to "override the speaker volume", I assume that you mean you want to control the system output volume from you app code. This is not possible as Apple believes that output volume should remain in the control of the user at all times.
AVAudioPlayer's volume property sets the volume relative to the system output level. It defaults to 1.0 (player volume == system volume). You can't turn it up higher, spinal-tap style, to 1.1...
See also my answer here ... if you want to take control of the system volume, you will need the user interface provided by MPVolumeView.
Similarly regarding your notifications - if the user has muted the device, your app won't be able to ignore that.
update
regarding notifications, it isn't as straightforward as I suggested. It might work if you set the AVAudioSession category to AVAudioSessionCategoryPlayback (and read the apple docs on this setting).
When using this category, your app audio continues with the Silent switch set to silent or when the screen locks
You may also need to add 'audio' to UIBackgroundModes in your info.plist.

Resources