🔊

【Swift】AVAudioEngineで音声の再生終了前にコールバックされる問題の解決方法

に公開

AVAudioEngineを使用して音声を再生するとき、音声が再生し終わる前にコールバックが呼ばれる問題に遭遇しました。その原因と解決策についてまとめます。

特に、コールバックでUIを更新したり次の処理を行おうとすると、音声の再生が終了する前に動作してしまう可能性があるため注意が必要です。

AVAudioEngineで音声を再生する基本コード

AVAudioEngineで音声を再生する際、AVAudioPlayerNodeを以下のように利用します。

import AVFoundation

class AudioPlayerManager {
    private let engine = AVAudioEngine()
    private let player = AVAudioPlayerNode()

    init() {
        engine.attach(player)
        let mixer = engine.mainMixerNode
        engine.connect(player, to: mixer, format: nil)

        do {
            try engine.start()
        } catch {
            print("エンジンの起動に失敗しました: \(error)")
        }
    }

    func playAudioFile(_ audioFile: AVAudioFile) {
        player.scheduleFile(audioFile, at: nil) {
            print("再生終了")
        }
        player.play()
    }
}

問題

上記コードでは、音声の再生が終わる前に「再生終了」と表示されることがあります。

これはscheduleFileのデフォルトのcompletionCallbackType.dataConsumedであることに起因しています。この設定では音声データが再生バッファに読み込まれた時点でコールバックされるため、実際にスピーカーから音声が再生され終える前に完了扱いになります。

原因

scheduleFileのデフォルト引数は以下のようになっています。

public func scheduleFile(
    _ file: AVAudioFile, 
    at when: AVAudioTime? = nil, 
    completionCallbackType callbackType: AVAudioPlayerNodeCompletionCallbackType = .dataConsumed, 
    completionHandler: AVAudioPlayerNodeCompletionHandler? = nil
)
  • .dataConsumed: 音声データがバッファに読み込まれた時点で完了
  • .dataRendered: 音声が実際に再生され、レンダリングが完了した時点で完了
  • .dataPlayedBack: 音声がデバイスから完全に再生され終えた時点で完了

デフォルト設定の.dataConsumedでは、再生終了のコールバックが実際の再生終了より早く呼ばれる問題が発生します。

解決方法

解決方法として、completionCallbackType.dataRenderedまたは.dataPlayedBackに変更します。

player.scheduleFile(audioFile, at: nil, completionCallbackType: .dataPlayedBack) {
    print("音声が完全に再生されました")
}

または

player.scheduleFile(audioFile, at: nil, completionCallbackType: .dataRendered) {
    print("音声のレンダリングが完了しました")
}

おすすめの設定

  • .dataRendered: AVAudioEngine内でレンダリングが終了したタイミングでイベントを発火したい場合。
  • .dataPlayedBack: スピーカーから音声が完全に再生されたことを確実に検知したい場合。

多くの場合、実際に音声が完全に再生されたことを検知する.dataPlayedBackが確実でしょう。

まとめ

AVAudioEngineを利用する際、音声の再生が終了する前にコールバックされる問題を防ぐには、completionCallbackTypeを明示的に.dataRenderedまたは.dataPlayedBackに設定することが重要です。

Discussion