Swift, Continue recording after interruption - ios

I am trying to allow the user to record audio for long duration.
So what am trying to achieve?
Start recording will stop any other media like music
If the AVAudioRecorder gets interrupted, it pauses.
When the interruption ended, or the user resume the recording manually the AVAudioRecorder stops any other media and resumes and continue recording.
My problem is when the AVAudioRecorder gets interrupted and resumed few times, it loses the recoded data, for example if I recorded for 30 seconds when I play it back, its duration becomes 10 seconds.
Amazingly, this happen only when it get's interrupted, but if the user pauses and resumes the recoding many times, no problem.
Any suggestions?
Thanks.
func startRecording() {
if !audioRecord!.recording {
do {
secondCounter = 0
try self.audioSession?.setCategory(AVAudioSessionCategoryRecord)
try self.audioSession?.setActive(true)
self.stopAudioPlayer()
self.playButton.enabled = false
self.playButtonInPlayingMode(false)
self.hideAudioContainer(true)
audioRecord?.prepareToRecord()
audioRecord?.meteringEnabled = true
audioRecord!.record()
self.micButton.tag = recording_state;
self.monitorRecoderTime()
} catch {
}
}
}
func pauseRecording() {
if audioRecord != nil && audioRecord!.recording {
self.recordingPauseMode()
audioRecord?.pause()
}
}
func recordingPauseMode() {
self.micButton.setImage(UIImage(named: "micro.png"), forState: .Normal);
self.micButton.tag = pause_state;
self.stopMonitoring()
}
func resumeRecoding() {
if audioRecord != nil && !audioRecord!.recording {
self.micButton.setImage(UIImage(named: "micro_filled.png"), forState: .Normal);
self.micButton.tag = recording_state;
if audioRecord!.record() {
print("Recording Resumed")
}else{
print("Unable to resume recording")
}
self.monitorRecoderTime()
}
}

Related

AVAudioPlayer stops returning correct currentTime after app goes to background

I have an issue which has been bugging me for few months now.
I have a running Timer which prints the AVAudioPlayer's currentTime, it all goes well unless I
pause the player
minimise the app or lock the screen
resume playback
The time that is printed is suddenly constant. Sound is properly playing but the currentTime is not changing anymore unless I pause the player and call play() again.
Also, the worst part is that it does not happen every time, sometimes I have to repeat those steps 2/3 times but the bug happens eventually.
I have read the documentation and searched for some answers but there is nothing.
private func setTimer(_ on: Bool) {
guard on else {
timer?.invalidate()
timer = nil
return
}
guard timer == nil else { return }
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { _ in
self.currentPlaybackTime = self.player.currentTime
print("Delegate newTime: \(self.player.currentTime)")
})
}
func play() {
print("Tries playing")
guard player.prepareToPlay(),
player.play() else {
playbackState = .failed
return
}
print("Playing OK")
setTimer(true)
playbackState = .playing
}
Console prints
Delegate newTime: 68.74916099773243 Delegate newTime:
68.74916099773243 Delegate newTime: 68.74916099773243 Delegate newTime: 68.74916099773243 Delegate newTime: 68.74916099773243
Delegate newTime: 68.74916099773243 ...
EDIT: player.isPlaying returns false when the time stays the same, it returns correctly true when time is correctly increasing
Why would AVAudioPlayer do this to me? It's clear that the timer works in the background and player causes the issue here.
The solution is to deactivate and activate the 'AVAudioSession'.
When the app goes in to the background and when the playback is stopped iOS is setting the AVAudioSession's active state to false so you need to set it back to true if you use the lock screen controls to restart playback.
What I did (following the advice from Adam in the comment above) is set the false status when I stop playback then set to true when the user starts playback from the lock screen controls or via the play button on earbuds.
do
{
try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
}
catch
{
NSLog(error.localizedDescription)
}
I have tested this in my own app and after weeks of attempts the functionality is now correct.

How can I know if AVAudioPlayer is paused?

To discover whether an AVAudioPlayer object is playing, I can simply use the isPlaying property:
if audioPlayer.isPlaying {
// ...
}
If the audio player is not playing however, how can I distinguish between it being paused and it being stopped?
I can't use the currentTime property for this purpose. This is because if I call stop(), then the current time stays at whatever time it was at the time of the call, which is the same behaviour as when pause() is called.
I think it's as simple as keep a BOOL property in view controller class which set to YES while play/resume buttons clicked and set to NO while stop/pause button pressed.
Its made from this this link:
if ((player.rate != 0) && (player.error == nil)) {
// player is playing
}else {
//notplaying
}
Simply add Bool variable and based on that you check is audio paused or not.
Ex:
var isAudioPaused = false//Add this on top
//Pause audio
func pauseAudio() {
if isAudioPaused == false {
if audioPlayer?.isPlaying == true {//Check is audio playing
audioPlayer?.pause()//Pause audio
isAudioPaused = true
btnPauseAudio.setTitle("Play Audio", for: .normal)
}
} else {
audioPlayer?.play()//Play audio
isAudioPaused = false
btnPauseAudio.setTitle("Pause Audio", for: .normal)
}
}
You can check player that is paused or not though code that is player.isPlaying && player.duration != 0
func buttonOneClicked(button: UIButton) {
if player.isPlaying && player.duration != 0 {
button.setImage(UIImage(systemName: "stop"), for: .normal)
player.pause()
}
else{
button.setImage(UIImage(systemName: "play"), for: .normal)
player.play()
}
}

AVPlayer audioSessionGotInterrupted notification when waking up from background

I use AVAudioPlayer to play audio. I have background audio enabled and the audio sessions are configured correctly.
I implemented the audioSessionGotInterrupted method to be informed if the audio session gets interrupted. This is my current code:
#objc private func audioSessionGotInterrupted(note: NSNotification) {
guard let userInfo = note.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
return
}
if type == .began {
print("interrupted")
// Interruption began, take appropriate actions
player.pause()
saveCurrentPlayerPosition()
}
else if type == .ended {
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
if options == .shouldResume {
print("restored")
// Interruption Ended - playback should resume
setupPlayer()
player.play()
} else {
// Interruption Ended - playback should NOT resume
// just keep the player paused
}
}
}
}
Now I do the following:
Play some audio
Lock the phone
Pause the audio
Wait for some seconds until I see in the XCode debugger that the app has been stopped in background
I hit play in the lockscreen
My commandCenter play() methods gets called as expected. However also the audioSessionGotInterrupted method gets called with type == .began.
How is that possible? I expect to see no notification of that kind or at least .ended
I use iOS 10 beta 8.
Check this
https://developer.apple.com/documentation/avfoundation/avaudiosession/1616596-interruptionnotification
Starting in iOS 10, the system will deactivate the audio session of most apps in response to the app process being suspended. When the app starts running again, it will receive an interruption notification that its audio session has been deactivated by the system. This notification is necessarily delayed in time because it can only be delivered once the app is running again. If your app's audio session was suspended for this reason, the userInfo dictionary will contain the AVAudioSessionInterruptionWasSuspendedKey key with a value of true.
If your audio session is configured to be non-mixable (the default behavior for the playback, playAndRecord, soloAmbient, and multiRoute categories), it's recommended that you deactivate your audio session if you're not actively using audio when you go into the background. Doing so will avoid having your audio session deactivated by the system (and receiving this somewhat confusing notification).
if let reason = AVAudioSessionInterruptionType(rawValue: reasonType as! UInt) {
switch reason {
case .began:
var shouldPause = true
if #available(iOS 10.3, *) {
if let _ = notification.userInfo?[AVAudioSessionInterruptionWasSuspendedKey] {
shouldPause = false
}
}
if shouldPause {
self.pause()
}
break
case .ended:
break
}
}
While the answer above is not wrong it still caused a lot of trouble in my app and a lot of boilerplate code for checking multiple cases.
If you read the description of AVAudioSessionInterruptionWasSuspendedKey it says that the notification is thrown if you didn't deactivate your audio session before your app was sent to the background (which happens every time you lock the screen)
To solve this issue you simply have to deactivate your session if there is no sound playing when app is sent to background and activate it back if the sound is playing. After that you will not receive the AVAudioSessionInterruptionWasSuspendedKey notification.
NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: .main) { sender in
guard self.player.isPlaying == false else { return }
self.setSession(active: false)
}
NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main) { sender in
guard self.player.isPlaying else { return }
self.setSession(active: true)
}
func setSession(active: Bool) -> Bool {
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playback, mode: .default)
try session.setActive(active)
return true
} catch let error {
print("*** Failed to activate audio session: \(error.localizedDescription)")
return false
}
}
Note: Activating session is probably not necessary because it is handled by Apple's internal playback classes (like AVPlayer for example) but it is a good practice to do it manually.

stopping an AVPlayer in swift

My Audio Streaming App which has a single Play / Pause Button and works fine. However I have little problem while using the player.pause() property. Sometimes the stream does not continue after pressing play Button and especially (less often) it happens when application comes to foreground after being sent to background ( for example user opened another music app, now he wants to resume my app and wants to continue again). User may have to kill and restart the app in order to start the player (audio stream).
In short, Can I use any property such as player.stop() to make a full stop to the steaming link and resume after with player.play()
This is what I tries as well, but same problem,:
func playPause() {
if (player.rate == 1.0) {
player.rate = 0.0
self.titleLabel.text = "Paused"
}
else {
player.play()
self.titleLabel.text = "Now Playing Live!"
}
}
Thanks for any suggestions
I have tested this and it seems to give no problems at all:
#IBAction func playPauseAction(sender: UIButton) {
if audioPlayer.rate > 0.0 {
audioPlayer.pause()
} else {
audioPlayer.play()
}
}

Playing video using AVPlayer does not get resume when come in foreground ios

I am playing video using AVPlayer. When I go in background player is paused and when it is brought in foreground player is played. But still video is not appearing though it contains URL in its current Item. I am stuck and not getting and solution.Please help me to resolve. Thanks in advance.
Below code is used to play initially:-
let item1 = AVPlayerItem.init(URL: NSURL(string:path))
player = AVPlayer(playerItem: item1)
layer?.player = player;
player?.play()
Below code is used to pause and resume:-
func pausePlayerOnBackgroundAppearance()
{
if(player != nil)
{
player?.pause()
}
}
func resumePlayerOnForegroundAppearance()
{
if(player != nil)
{
player?.play()
}
}
If I am seeking some time to play video at some time where it was paused before then also it is not playing video
func pausePlayerOnBackgroundAppearance()
{
if(player != nil){
let currentItem:AVPlayerItem = player!.currentItem
currentTime = CMTimeGetSeconds(currentItem.currentTime())
player?.pause()
}
}
func resumePlayerOnForegroundAppearance()
{
if(player != nil){
player!.seekToTime(CMTimeMake(Int64(currentTime!), 1))
player?.play()
}
}

Resources