ブラウザが非表示になった時にマイク入力を解放する


困っている事

webAudioAPIを利用してマイク入力データを利用している中で

  • 別タブに切り替えた時に、タブの録音中マークが消えない(PC)
  • ホームボタンを押した時に画面上に赤い帯が残る(iOS)

タブはこういう丸いやつ↓

赤い帯はこういうやつ↓

環境

PC

  • MacBook Pro 2016
  • macOS Mojave 10.14.6
  • Chrome 79.0.3945.88(Official Build) (64 ビット)

スマホ

  • iPhone 6s
  • iOS13.3
  • safari

手順

  1. タブがアクティブじゃなくなった事を検知して
  2. マイクの入力トラックを解放する

タブがアクティブじゃなくなった事を検知

コード

function setEventListener() {
  let hidden, visibilityChange;
  if (typeof document.hidden !== "undefined") { // Opera 12.10 や Firefox 18 以降でサポート
    hidden = "hidden";
    visibilityChange = "visibilitychange";
  } else if (typeof document.msHidden !== "undefined") {
    hidden = "msHidden";
    visibilityChange = "msvisibilitychange";
  } else if (typeof document.webkitHidden !== "undefined") {
    hidden = "webkitHidden";
    visibilityChange = "webkitvisibilitychange";
  }
  document.hiddenStatus = hidden;

  if (hidden === undefined) {
    console.log("This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.");
  } else {
    document.addEventListener(visibilityChange, browserBlorFunction, {once: true});
  }
}

async function browserBlurFunction() {
  if (document.hidden) {
    // 非表示状態になった時の動作
    await stopRecording();
  }
}

解説

この記事に全て書いてあります!
[iOS/Android]ブラウザでページが非表示になったことを検知する方法

今回はタブがアクティブではない時の体験を損なわない事を目的としていたので、
Page Visibility APIのみの利用をしています。

簡単に言うと、「ページが表示されているかどうか」を判定してくれるAPIです。
EventListenerに登録して利用可能です!

この機能によって追加されたプロパティであるdocument.hiddenで現在のブラウザの表示状態を取得できるので、
document.hidden === trueの時に、マイク入力解放関数を実行します。

ちなみに

同じような動作をさせたい時のグローバルイベントハンドラーとしてwindow.onblurがあります。
これはウィンドウの切り替えを意味するものでありページの表示を検知するものではありません。
そのためwindow.onblurにマイク入力解放のコードをセットしても、
iPhoneでホームボタンを押した後は冒頭で紹介した赤い帯が出るし、
タブを切り替えてもブラウザの丸いマークは残ったままです。

マイクの入力を解放する

コード

//streamsには、mediaStreamそのものが入っています。
function stopRecording() {
  let tracks = streams.getTracks();
  tracks.forEach(function (track) {
    track.stop();
  });
}

//streamsへの格納
navigator.mediaDevices.getUserMedia({audio: true})
    .then(mediaStream => {
      gotStream(mediaStream);
    }).catch(e => {
    alert('Error getting audio');
  });
}

let streams = null;
function gotStream(stream){
  streams = stream;
  //以下音声の処理...
}

解説

マイク入力はmediaDevices.getUserMediaより取得しており、その際に取得したmediaStreamTrackを停止させる事により、入力を解放します。

trackの停止

mediaStreamTrack.readyStateと言うプロパティの値が"ended"になる時、入力デバイスからのデータを受け取らなくなります。

"ended"は入力デバイスがこれ以上データを提供することがなく、新しいデータも一切提供されないことを示します。
引用元:mediaStreamTrack.readyState

そのための関数として、

MediaStreamTrack.stop()
トラックに関連付けられたソースの再生を停止し、ソースとトラックの関連付けを解除します。トラックの状態はendedに設定されます。

こちらが用意されているので、それを使いましょう!
mediaStream.getTracks

function stopRecording() {
  let tracks = streams.getTracks();
  tracks.forEach(function (track) {
    track.stop();
  });
}

これでmediaStreamTrack.readyStateがendedになるため、記事冒頭で紹介した動作は起こらなくなります。

最後に

人生初Qiita記事書いてみました!
ちなみにsafariのアプリを完全に落とせば同じ事は達成できます()

今年は頑張ってアウトプットしていくぞぉ