2016-08-04 21 views
19

私は、オーディオがサーバー上でオンザフライで生成され、おそらくHTML5オーディオ要素を介してブラウザクライアントにストリーミングされるクロスプラットフォームのWebアプリケーションを構築しています。ブラウザでは、再生されたオーディオと正確に同期する必要があるJavaScriptによるアニメーションを作成します。 「正確」とは、オーディオとアニメーションが互いに2分の1以内でなければならないことを意味し、うまくいけば250ms以内である(リップシンクを考える)。いろいろな理由で、私はサーバー上でオーディオやアニメーションを行い、結果ビデオをライブストリーミングすることはできません。HTML5オーディオストリーミング:正確に待ち時間を測定しますか?

サーバでのオーディオ生成とブラウザでのオーディオ再生の間に遅延がほとんどないことが理想的ですが、私の理解では、遅延を制御するのが難しく、おそらく3-7秒の範囲 - 、環境、ネットワーク、およびフェーズ・オブ・ザ・ムーンに依存)。実際のレイテンシを正確に測定して、ブラウザのJavascriptがいつ適切なアニメーションフレームを表示するかを知ることができれば、それを処理できます。

私は、自分のオーディオをストリーミングサーバー(Icecast?)とスピーカーをホストしているコンピューターのスピーカーから出てくるオーディオの間のレイテンシを正確に測定する必要があります。いくつかの青空の可能性:

  • は、オーディオストリームにメタデータを追加し、再生オーディオからそれを解析し(私はこれが標準的なオーディオ要素を使用しては不可能であることを理解)

  • は、短期間のを追加します。次いで、純粋なオーディオに沈黙し、ブラウザ上にそれらを検出(オーディオ要素は、実際のオーディオサンプルを得ることができる?)

  • クエリサーバとブラウザの様々なバッファの深さについての

  • ストリームオーディオをJavascriptでデコードしてメタデータを取得する

私はこれをどのように行うことができると思いますか?

+1

私は違いを測定する方法があるとは思わないので、私は通常、ストリームを分析クライアント - WebAudioAPIを使用してブラウザ内に表示します。このテクニックを使用すると、ブラウザで再生されているIcecastストリームと密接に関連するアニメーション(イコライザなど)を作成できます。 –

答えて

1

遅延を直接測定する方法はありませんが、再生したばかりの(再生が頻繁に行われる)場合は「再生」、ストリーミングがストップの場合は「停止」、データがロードされている場合は「待機」などのイベントが生成されます。あなたができることは、このイベントに基づいてビデオを操作することです。

ストールまたは待機中に再生するので、もう一度再生すると動画が再生されます。

しかし、私はあなたの流れに影響する可能性のある他の出来事をチェックすることをアドバイスします(例えば、あなたにとってエラーが重要です)。

https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement

6

.currentTime<audio>の要素をチェックすることで、メディアのストリーミング中に正確なアニメーションを実行するために、1秒間に3〜4回発射される<audio>要素のtimeupdateイベントを利用しています。アニメーションやトランジションは、1秒間に数回まで起動または停止できます。

ブラウザで利用できる場合は、リソースのReadableStreamを返し.then()リターンresponse.body.getReader()で、オーディオリソースを要求するためにfetch()を使用することができます。新しいMediaSourceオブジェクトを作成し、MediaSource<audio>またはnew Audio().srcobjectURLを設定します。 .read().then()からsourceBufferMediaSource.mode"sequence"に設定されている最初のストリームチャンクを追加します。残りのチャンクをsourceBuffersourceBufferupdateendイベントに追加します。

もしfetch()response.body.getReader()は、あなたはまだメディアストリーミング再生の必要な第2のアニメーションやトランジションを開始または停止、.currentTimeをチェックする<audio>要素のtimeupdateまたはprogressイベントを使用することができ、ブラウザでは利用できません。

canplayのイベントを<audio>要素に使用すると、ストリームが適切なバッファを蓄積したときにメディアを再生して、再生を続行することができます(MediaSource)。

あなたが正確なアニメーションを実行するようにアニメーション化されるべきアニメーションが発生した場合<audio>.currentTimeに対応する番号、および要素のcssプロパティに設定された値に設定するプロパティを持つオブジェクトを使用することができます。

以下のjavascriptでは、アニメーションは、0で始まり、20秒ごとに、メディアの再生が終了するまで60秒ごとに発生します。

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml">  
<head> 
    <meta charset="utf-8" /> 
    <title></title> 
    <style> 
    body { 
     width: 90vw; 
     height: 90vh; 
     background: #000; 
     transition: background 1s; 
    } 

    span { 
     font-family: Georgia; 
     font-size: 36px; 
     opacity: 0; 
    } 
    </style> 
</head> 

<body> 
    <audio controls></audio> 
    <br> 
    <span></span> 
    <script type="text/javascript"> 
    window.onload = function() { 
     var url = "/path/to/audio"; 
     // given 240 seconds total duration of audio 
     // 240/12 = 20 
     // properties correspond to `<audio>` `.currentTime`, 
     // values correspond to color to set at element 
     var colors = { 
     0: "red", 
     20: "blue", 
     40: "green", 
     60: "yellow", 
     80: "orange", 
     100: "purple", 
     120: "violet", 
     140: "brown", 
     160: "tan", 
     180: "gold", 
     200: "sienna", 
     220: "skyblue" 
     }; 
     var body = document.querySelector("body"); 
     var mediaSource = new MediaSource; 
     var audio = document.querySelector("audio"); 
     var span = document.querySelector("span"); 
     var color = window.getComputedStyle(body) 
        .getPropertyValue("background-color"); 
     //console.log(mediaSource.readyState); // closed 
     var mimecodec = "audio/mpeg"; 

     audio.oncanplay = function() { 
     this.play(); 
     } 

     audio.ontimeupdate = function() {   
     // 240/12 = 20 
     var curr = Math.round(this.currentTime); 

     if (colors.hasOwnProperty(curr)) { 
      // set `color` to `colors[curr]` 
      color = colors[curr] 
     } 
     // animate `<span>` every 60 seconds 
     if (curr % 60 === 0 && span.innerHTML === "") { 
      var t = curr/60; 
      span.innerHTML = t + " minute" + (t === 1 ? "" : "s") 
          + " of " + Math.round(this.duration)/60 
          + " minutes of audio"; 
      span.animate([{ 
       opacity: 0 
      }, { 
       opacity: 1 
      }, { 
       opacity: 0 
      }], { 
       duration: 2500, 
       iterations: 1 
      }) 
      .onfinish = function() { 
       span.innerHTML = "" 
      } 
     } 
     // change `background-color` of `body` every 20 seconds 
     body.style.backgroundColor = color; 
     console.log("current time:", curr 
        , "current background color:", color 
        , "duration:", this.duration); 
     } 
     // set `<audio>` `.src` to `mediaSource` 
     audio.src = URL.createObjectURL(mediaSource); 
     mediaSource.addEventListener("sourceopen", sourceOpen); 

     function sourceOpen(event) { 
     // if the media type is supported by `mediaSource` 
     // fetch resource, begin stream read, 
     // append stream to `sourceBuffer` 
     if (MediaSource.isTypeSupported(mimecodec)) { 
      var sourceBuffer = mediaSource.addSourceBuffer(mimecodec); 
      // set `sourceBuffer` `.mode` to `"sequence"` 
      sourceBuffer.mode = "sequence"; 

      fetch(url) 
      // return `ReadableStream` of `response` 
      .then(response => response.body.getReader()) 
      .then(reader => { 

      var processStream = (data) => { 
       if (data.done) { 
        return; 
       } 
       // append chunk of stream to `sourceBuffer` 
       sourceBuffer.appendBuffer(data.value); 
      } 
      // at `sourceBuffer` `updateend` call `reader.read()`, 
      // to read next chunk of stream, append chunk to 
      // `sourceBuffer` 
      sourceBuffer.addEventListener("updateend", function() { 
       reader.read().then(processStream); 
      }); 
      // start processing stream 
      reader.read().then(processStream); 
      // do stuff `reader` is closed, 
      // read of stream is complete 
      return reader.closed.then(() => { 
       // signal end of stream to `mediaSource` 
       mediaSource.endOfStream(); 
       return mediaSource.readyState; 
      }) 
      }) 
      // do stuff when `reader.closed`, `mediaSource` stream ended 
      .then(msg => console.log(msg)) 
     } 
     // if `mimecodec` is not supported by `MediaSource` 
     else { 
      alert(mimecodec + " not supported"); 
     } 
     }; 
    } 
    </script> 
</body> 
</html> 

plnkr http://plnkr.co/edit/fIm1Qp?p=preview

+0

@DanielGriscom関連項目[Media Source Extensions](https://w3c.github.io/media-source/) – guest271314

0

何私がしようとすると、最初のデータ、performance.nowでプロセスをタイムスタンプを作成し、新しいWebレコーダーAPIでブロブに記録されます。

ウェブレコーダーはユーザーにオーディオカードへのアクセスを要求しますが、これはアプリにとっては問題になる可能性がありますが、実際の待ち時間を取得する必要があります。

これが完了すると、世代と実際のレンダリングとの間の実際の待ち時間を測定する多くの方法があります。基本的に、健全な出来事。さらに参照し、例えば

Recorder demo

https://github.com/mdn/web-dictaphone/

https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder_API/Using_the_MediaRecorder_API

関連する問題