😇

Mobile Safari(iPad,iPhone)でRecordRTCで無音の音声しか録音できない現象に遭遇した件

2022/06/17に公開
  • RecordRTCを使い、ブラウザ上で音声の録音をしようと思ったところ、iPad, iPhoneのSafariでのみ無音の音声が録音されるという現象が発生したので、解決策のメモ

現象が発生するときの実装

音声を録音モジュールの実装は以下

export default clas AudioRecordingModule{

  public asyn setAudioStream(){
    private stream?: MediaStream
    private recorder?: RecordRTC
    private mediaDevice?: MediaDevices
    
    try {
      this.stream = await navigator.mediaDevices.getUserMedia({
        audio: true,
      })
      this.recorder = new RecordRTC(stream.clone(), {
        type: 'audio',
        mimeType: 'audio/wav',
        disableLogs: true,
        recorderType: StereoAudioRecorder,
        numberOfAudioChannels: 2,
      })
    } catch (e) {
      console.log(e)
      return false
    }
  }
  
  public async recStart() {
    if (state == 'recording') {
      this.recorder?.stopRecording()
    }
    
    this.recorder.reset()

    this.recorder = new RecordRTC(stream.clone(), {
      type: 'audio',
      mimeType: 'audio/wav',
      disableLogs: true,
      recorderType: StereoAudioRecorder,
      numberOfAudioChannels: 2,
    })
    this.recorder.startRecording()
  }
  
}
  • 録音する直前ではなく、ページを表示した際にデバイスアクセスの許可をとり、取得したMediaStreamを使ってrecorderを起動。
  • RecordRTCのインスタンスは一度作ったら流用。
  • AndroidのChrome,WindowsのChrome,Macのsafariなどでは問題なく動作をしましたが、iOS(iPadOS)のsafariだと無音の音声が録音されるという現象が発生。
  • 無音ですが、録音時にはエラーメッセージもなく、録音した時間分は確保されている状態。

原因の調査

  • 現象として、調査をしている中分かったことは
    1. 録音開始時等にエラーは表示されない
    2. 無音だけれども、録音時間分のデータは作成されている
    1. の現象として、さらに細かく分けると以下の3つの可能性を考えました。
    • 録音後に、実際にはwav->mp3変換を行っているが、そこでデータが落ちている(変換の問題)
    • 時間分のデータが作成されていることから、録音は実際にできているものの、ブラウザ上(またはデバイス上)で何かしらの問題で再生がされていない(ブラウザとデータの問題)
    • 録音時にMediaStreamとの接続がうまくいっていない(録音時のデバイス接続の問題)
  • 結果的には3が正しかったのですが、前述のコードでもRecordRTCが読み取っているMediaStreamはちゃんととっているし問題は無い…はず…
  • RecordRTCは再利用不可だったとして、録音時にsetAudioStreamを叩くようにしたときに動作をしないのが解せない…

問題を解決した実装

export default clas AudioRecordingModule{
  private stream?: MediaStream
  private recorder?: RecordRTC
  private mediaDevice?: MediaDevices
  public asyn setAudioStream(){
    try {
      this.getStream()
    } catch (e) {
      console.log(e)
      return false
    }
  }

  private async getStream(){
    if(!this.stream){
      this.stream = await navigator.mediaDevices.getUserMedia({
        audio: true,
      })
    }
    return this.stream
  }

  public async recStart() {
    if (state == 'recording') {
      this.recorder?.stopRecording()
    }
    
    if(this.recorder){
      this.recorder.destroy()
    }
    
    const stream = this.getStream()
     
    this.recorder = new RecordRTC(stream.clone(), {
        type: 'audio',
        mimeType: 'audio/wav',
        disableLogs: true,
        recorderType: StereoAudioRecorder,
        numberOfAudioChannels: 2,
    })
    this.recorder.startRecording()
  }
}
  • 変更としては
    • Streamは再利用
    • RecordRTCは録音時に毎回インスタンスを破棄し作成をする

解決した実装を見て

  • 解せぬ…が、streamは読み込む度にIDが変更される。iOSでは、複数のタブでMediaStreamを読み込むと、最期にアクティブになったメディアストリームが生き、他は一時停止される。
  • ページ内でMediaStreamを読み込むと、実際に録音に使っているMediaStreamが停止され、マイクにアクセスできないという現象はあり得るかもしれない。
  • であれば、最初の実装でなぜ動かないのか…はよくわからない…
  • RecordRTCは録音の直前にインスタンスを起動し、Streamは必ず使いまわしたほうが良い。
  • ちゃんと接続ができていないなら、ちゃんとエラーを表示してほしい…(切実

Discussion