stopping an AVPlayer in swift - ios

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()
}
}

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 to play sounds in iOS 10 when app is in the background with Swift 3

I'm working on an exercise app that needs to be able to play sounds when the app goes into the background and also when the screen is locked. I also need to allow sounds from other apps to play (such as music) while also allowing my sound effects to come through.
I had this working in iOS 9 by using AVAudioSessionCategoryAmbient, but as soon as my app becomes inactive or the screen locks, the sounds stop.
Here's a simplified version of my code in a demo app:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var session: AVAudioSession = AVAudioSession.sharedInstance()
var timer = Timer()
let countdownSoundFile = "154953__keykrusher__microwave-beep"
let countdownSoundExt = "wav"
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
activateAudio()
}
func activateAudio() {
_ = try? session.setCategory(AVAudioSessionCategoryAmbient, with: [])
_ = try? session.setActive(true, with: [])
}
#IBAction func play() {
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(ViewController.playSound), userInfo: nil, repeats: true)
}
func playSound() {
if let soundURL = Bundle.main.url(forResource: countdownSoundFile, withExtension: countdownSoundExt) {
do {
print("sound playing")
try audioPlayer = AVAudioPlayer(contentsOf: soundURL)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print("no sound found")
}
}
}
}
I also checked the Audio, Airplay, and Picture in Picture capability in Background Modes, which added the Required background mode to my plist.
I'm testing this on a device, and as soon as I lock the screen or press the home button, my sounds stop, and they resume once my app becomes active again.
I found a workaround by using:
var mySound: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL as CFURL, &mySound)
AudioServicesPlaySystemSound(mySound);
but this doesn't allow the volume of the sounds to be changed, and I also need to use AVSpeechSynthesizer, and this workaround doesn't work for that.
Any ideas on what I can do to make this work?
EDIT:
I changed the category line to _ = try? session.setCategory(AVAudioSessionCategoryPlayback, with: [.mixWithOthers]) and this allows music to be played while the app is running, however the sounds still stop when the screen locks or goes into the background. I need the sounds to continue to play in these scenarios.
EDIT:
As the answer pointed out, I cannot play sounds when my app is backgrounded, but using _ = try? session.setCategory(AVAudioSessionCategoryPlayback, with: [.mixWithOthers]) does allow sounds to play when the screen turns off and mixes with sounds (music) from other apps.
You cannot play in the background with an Ambient audio session category. Your category must be Playback.
To allow sounds from other apps to play, modify your Playback category with the Mixable option (.mixWithOthers).
(Also keep in mind that you can change your category at any time. You could have it be Ambient most of the time but switch to Playback when you know you're about to go into the background so that you can keep playing.)
EDIT Also it occurs to me that there is another possible misconception that might need clearing up. You cannot (easily) start a new sound while in the background. All that background audio allows you to do is continue playing the current sound when your app goes into the background. Once that sound stops (in the background), your app suspends and that's the end of that.
check here this answer in detail. You need to enable Audio Mode as well from the app settings.
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)
print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
} catch {
print(error)
}
} catch {
print(error)
}

Swift, Continue recording after interruption

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()
}
}

Resume sound in AVAudioPlayer with swift

I am trying to play a clip of sound that can be paused, resumed or stopped. My pause button works, and the action function for this button is shown below. However I cannot get my resume function to work. When I press the resume button no audio is played. I have read around online and the only tips I can find are to use the prepareToPlay() function and to set shortStartTimeDelay to a value greater than 0.0. I have tried both of these to no avail.
timeAtPause is a global variable of type NSTimeInterval
The action function for the pause button is as follows:
#IBAction func pauseAllAudio(sender: UIButton) {
timeAtPause = audioPlayer.currentTime
audioPlayer.pause()
}
The action function for the resume button is as follows:
#IBAction func resumeAllAudio(sender: UIButton) {
let shortStartDelay = 0.01
audioPlayer.prepareToPlay()
audioPlayer.playAtTime(timeAtPause + shortStartDelay)
}
Any tips on how to resume the audio would be really appreciated.
Thank you for your time.
You should not use playAtTime() to resume the AVAudioPlayer, as documentation states:
Plays a sound asynchronously, starting at a specified point in the
audio output device’s timeline.
and
Use this method to precisely synchronize the playback of two or more
AVAudioPlayer objects.
And, even if you use it, it should be used in conjunction with deviceCurrentTime plus the time in seconds to have the delay. In one word, it's not meant to be used to resume the paused player. Instead, just use play() to resume the playback.
You probably did what I did. Upon calling my play() function, I created a new player every time:
// The Wrong Way
#IBAction func playAction(sender: AnyObject) {
// do and catch omitted for brevity
player = try AVAudioPlayer(contentsOf: goodURL)
player.play()
}
However this should be done at some other initialization time, such as when you have the user choose the file to play.
Or, if you're hard-coding the file to play, you could initialize at an earlier time, such as viewDidLoad().
Then, once your AVAudioPlayer is initialized, you can call .play() and pause() on it and they will work correctly. And .play() will resume after a pause.
To simply pause and restart at the same point in the audio file, the following works:
#IBAction func playAction(sender: AnyObject) {
player.play()
}
#IBAction func pauseAction(sender: AnyObject) {
player.pause()
}
I found these to be helpful. If you are doing this in Swift, I did this a little differently. I created an AudioPlayer class after importing AVFoundation and used these two functions to pause and resume the music. In the SwiftUI call, I used an #State boolean that toggled between the button states. Here is the code that I used:
import AVFoundation
class MusicPlayer {
static let shared = MusicPlayer()
var audioPlayer: AVAudioPlayer?
// Pause music
func pauseBackgroundMusic() {
guard let audioPlayer = audioPlayer else { return }
audioPlayer.pause()
}
// Resume music
func resumeBackgroundMusic() {
guard let audioPlayer = audioPlayer else { return }
audioPlayer.play()
}
}
And in the View:
Define an #State variable:
#State var pauseMode = false
And call the music player as follows in the Button action code:
Button(action: {
if pauseMode {
MusicPlayer.shared.resumeBackgroundMusic()
self.pauseMode.toggle()
} else {
MusicPlayer.shared.pauseBackgroundMusic()
self.pauseMode.toggle()
}
})
{
Image(systemName: "playpause")
}
FYI - I posted this in SwiftUI since newer programmers (and Apple) seem to be moving in this direction. I figured this might help someone (like me!) who came to this question seeking a Swift / SwiftUI answer.

Play sound repeatedly when tapping a button

I am trying to add sound to my app. I would like it for when I tap a button, it plays a quick sound. However, when the button is tapped quickly and repeatedly, the sound does not work as well (It only plays 1 or 2 times when I tap the button 5 or 6 times). Here is my code in the button
player.play()
I have this outside
var player = AVAudioPlayer()
let audioPath = NSBundle.mainBundle().pathForResource("illuminati", ofType: "wav")
Viewdidload:
do {
try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath!))
} catch {}
How can I play the sound repeatedly better? Thanks.
The problem is that you are calling the play multiple time before the previous call finished. You need to keep track of how many times the user click the button and the play song one by one.
This is what you can do:
Use an integer in you class to keep track of number of times that the button is clicked
var numClicks = 0
var buttonClickTime:NSDate? = nil // The last time when the button is clicked
#IBAction func yourbuttonclickfunction() {
numClicks++;
buttonClickTime = NSDate()
player.play()
}
Register the delegate of AVAudioPlayerDelegate
do {
try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath!))
// Add this
player.delegate = self
} catch {}
In the delegate function, play the song again when the previous one reach the end:
optional func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
successfully flag: Bool)
{
if --numClicks > 0
{
let now = NSDate()
let duration = now.timeIntervalSinceDate(buttonClickTime!)
// If the button click was less than 0.5 seconds before
if duration < 0.5
{
// Play the song again
player.play();
}
}
}
The reason your sound only plays a few time is because the song plays until finished or until you stop it, even if you press the button multiple times in succession.
You could just stop the music manually, so before you press a button that plays a sound you say
player.stop()
and than
player.play()
Is that helping?

Resources