AVAudioPlayer enters an infinite loop if pause() is called very close to the end, and then playback is resumed with play()

Originator:digipom
Number:rdar://32664362 Date Originated:June 8th, 2017
Status:New Resolved:
Product:iOS SDK Product Version:10.3.2
Classification:Serious bug Reproducible:Yes
 
Area:
AVFoundation

Summary:
When stopping an AVAudioPlayer close to the end and then playing it again, it will enter into an infinite loop but without any audio being heard. The audio will not come back even after repeated play()/pause() attempts unless the player is de-inited and recreated.

Steps to Reproduce:
This can be reproduced by playing back audio, and then tapping pause just before the end. If done at the right moment, the playback will enter into an infinite loop without anything being heard.

This code will reproduce it:

import UIKit
import AVFoundation

class ViewController: UIViewController, AVAudioPlayerDelegate {
    var player: AVAudioPlayer?
    var intercept: Bool = true
    
    @IBOutlet weak var progressView: UIProgressView!
    
    @IBOutlet weak var playButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let displayLink = CADisplayLink.init(target: self, selector: #selector(pollCurrentTime))
        displayLink.add(to: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func onPlayButtonTapped(_ sender: UIButton) {
        let asset = NSDataAsset(name:"TestRecording")!
        player = try! AVAudioPlayer(data: asset.data, fileTypeHint: "m4a")
        player!.delegate = self
        player!.play()
        playButton.isEnabled = false
    }
    
    func pollCurrentTime() {
        if let player = player {
            progressView.progress = Float(player.currentTime / player.duration)
            if intercept && progressView.progress > 0.97 {
                intercept = false
                player.pause()
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
                    player.play()
                }
            }
        } else {
            progressView.progress = 0
        }
    }
    
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        cleanup()
    }
    
    func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
        cleanup()
    }
    
    private func cleanup() {
        self.player = nil
        playButton.isEnabled = true
        intercept = true
    }
}

Expected Results:
This should not happen.

Observed Results:
Happens on iPhone 6, iOS 10.3.2

Version:
10.3.2 (14F89)

Notes:
Additional notes: Seems that currentTime goes to 0 even though we paused close to the end, but neither of the two delegate methods were called.

Configuration:
iPhone 6s

Comments

I actually have the same issue, just want to know if you have fix it? ----- edit: Based on my test, if you pause() or stop() the player at the very last 0.2s, it will enter the loop.

By macintosh711 at Nov. 5, 2020, 6:27 a.m. (reply...)

Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!