I have a volume toggle button. When I press the button, then pause or play is called with some fading. The problem is that if I quickly press the button twice, for example, pause and then playback, I will hear a jump in volume. Moreover, if I quickly call play, and then press sharply and call pause, then in playback mode the audio will stop due to calling DispatchQueue
with a delay. How to fix this?
ViewController:
class ViewController: UIViewController {
@objc func soundState() {
if defaults.string(forKey: "sound") == "false" {
audio.pause(volume: 0.0, duration: 3.0)
} else if defaults.string(forKey: "sound") == "true" {
audio.play(volume: 1.0, duration: 3.0)
}
}
}
AudioController:
class Audio: NSObject, AVAudioPlayerDelegate {
var audioPlayer: AVAudioPlayer!
func loopedAudio(fileName: String, fileExtension: String) {
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
try? AVAudioSession.sharedInstance().setActive(true)
let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension)
do {
audioPlayer = try AVAudioPlayer(contentsOf: url!)
audioPlayer.prepareToPlay()
audioPlayer.delegate = self
audioPlayer.numberOfLoops = -1
} catch {
print("error")
}
}
func play(volume: Float, duration: Double) {
audioPlayer.volume = 0
audioPlayer.play()
audioPlayer.setVolume(volume, fadeDuration: duration)
}
func pause(volume: Float, duration: Double) {
audioPlayer.volume = 1
audioPlayer.setVolume(volume, fadeDuration: duration)
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
self.audioPlayer.pause()
}
}
}
I have a volume toggle button. When I press the button, then pause or play is called with some fading. The problem is that if I quickly press the button twice, for example, pause and then playback, I will hear a jump in volume. Moreover, if I quickly call play, and then press sharply and call pause, then in playback mode the audio will stop due to calling DispatchQueue
with a delay. How to fix this?
ViewController:
class ViewController: UIViewController {
@objc func soundState() {
if defaults.string(forKey: "sound") == "false" {
audio.pause(volume: 0.0, duration: 3.0)
} else if defaults.string(forKey: "sound") == "true" {
audio.play(volume: 1.0, duration: 3.0)
}
}
}
AudioController:
class Audio: NSObject, AVAudioPlayerDelegate {
var audioPlayer: AVAudioPlayer!
func loopedAudio(fileName: String, fileExtension: String) {
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
try? AVAudioSession.sharedInstance().setActive(true)
let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension)
do {
audioPlayer = try AVAudioPlayer(contentsOf: url!)
audioPlayer.prepareToPlay()
audioPlayer.delegate = self
audioPlayer.numberOfLoops = -1
} catch {
print("error")
}
}
func play(volume: Float, duration: Double) {
audioPlayer.volume = 0
audioPlayer.play()
audioPlayer.setVolume(volume, fadeDuration: duration)
}
func pause(volume: Float, duration: Double) {
audioPlayer.volume = 1
audioPlayer.setVolume(volume, fadeDuration: duration)
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
self.audioPlayer.pause()
}
}
}
Share
Improve this question
asked Jan 19 at 6:37
user1342352532user1342352532
119 bronze badges
8
|
Show 3 more comments
1 Answer
Reset to default 1Your issue arises because one of your actions,pause
is delayed, while your other action play
is instant. This means that if you change state rapidly, they delayed action may no longer be required.
The solution is to explicitly track the desired state in order to avoid unnecessary delayed actions:
enum PlaybackState {
case playing
case paused
}
class Audio: NSObject, AVAudioPlayerDelegate {
var playbackState: PlaybackState = .paused
var audioPlayer: AVAudioPlayer!
func loopedAudio(fileName: String, fileExtension: String) {
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
try? AVAudioSession.sharedInstance().setActive(true)
let url = Bundle.main.url(forResource: fileName, withExtension: fileExtension)
do {
audioPlayer = try AVAudioPlayer(contentsOf: url!)
audioPlayer.prepareToPlay()
audioPlayer.delegate = self
audioPlayer.numberOfLoops = -1
} catch {
print("error")
}
}
func play(volume: Float, duration: Double) {
audioPlayer.volume = 0
audioPlayer.play()
audioPlayer.setVolume(volume, fadeDuration: duration)
self.playbackState = .playing
}
func pause(volume: Float, duration: Double) {
audioPlayer.volume = 1
audioPlayer.setVolume(volume, fadeDuration: duration)
self.playbackState = paused
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
guard self.playbackState == .paused else {
return
}
self.audioPlayer.pause()
}
}
}
Now, when your dispatchAfter
executes, it will first check to see if the pause
state is still required. If not, it will simply return, leaving the audio playing.
pause()
after delay. Maybe I can somehow track this call and ifplay func
is called quickly, then cancel thepause()
delay call? – user1342352532 Commented Jan 19 at 7:32