javascript - HTML5 音频流 : precisely measure latency?

标签 javascript html streaming html5-audio

我正在构建一个跨平台网络应用程序,其中音频在服务器上即时生成,并可能通过 HTML5 音频元素实时流式传输到浏览器客户端。在浏览器上,我将拥有必须与播放的音频精确同步的 Javascript 驱动的动画。 “精确”意味着音频和动画之间的间隔必须在一秒以内,最好在 250 毫秒以内(想想口型同步)。由于各种原因,我无法在服务器上制作音频和动画并直播生成的视频。

理想情况下,服务器上的音频生成和浏览器上的音频播放之间几乎没有或没有延迟,但我的理解是延迟将难以控制,可能在 3-7 秒范围内(浏览器- 、环境、网络和月相)。不过,如果我可以精确地实时测量实际延迟,以便我的浏览器 Javascript 知道何时显示正确的动画帧,我就可以处理这个问题。

因此,我需要精确测量将音频传输到流媒体服务器(Icecast?)与托管扬声器的计算机上的扬声器发出的音频之间的延迟。一些蓝天的可能性:

  • 将元数据添加到音频流,并从播放的音频中解析它(我知道使用标准音频元素这是不可能的)

  • 在音频中添加短暂的纯静音,然后在浏览器上检测它们(音频元素可以产生实际的音频样本吗?)

  • 向服务器和浏览器查询各种缓冲区深度

  • 用 Javascript 解码流式音频,然后获取元数据

关于如何做到这一点有什么想法吗?

最佳答案

利用timeupdate <audio>的事件元素,每秒触发三到四次,通过检查 .currentTime 在媒体流期间执行精确的动画。的 <audio>元素。动画或过渡每秒可以启动或停止数次。

如果浏览器可用,您可以使用 fetch()请求音频资源,地址为 .then()返回 response.body.getReader()返回 ReadableStream资源的;创建一个新的 MediaSource对象,设置 <audio>new Audio() .srcobjectURLMediaSource ;在 .read() 附加第一个流 block 链接.then()sourceBufferMediaSource.mode设置为 "sequence" ;将剩余的 block 附加到 sourceBuffersourceBuffer updateend事件。

如果fetch() response.body.getReader()在浏览器中不可用,您仍然可以使用 timeupdateprogress <audio>的事件要检查的元素 .currentTime ,在流媒体播放所需的秒数开始或停止动画或过渡。

使用 canplay <audio>的事件当流在 MediaSource 处积累了足够的缓冲区时播放媒体的元素继续播放。

您可以使用属性设置为对应于 .currentTime 的数字的对象的 <audio>动画应该出现的位置,值设置为 css应设置动画以执行精确动画的元素的属性。

javascript下面,动画每二十秒出现一次,从0开始。 ,每六十秒一次,直到媒体播放结束。

<!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

关于javascript - HTML5 音频流 : precisely measure latency?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38768375/

相关文章:

javascript - 加载内容和 css 异步时闪烁/缓慢的 css

javascript - 我可以在 IE 中打开对象 URL 吗?

javascript - 如何在js中添加target=blank?

haskell - 概括Haskell "Streaming"库的合并功能

streaming - 红5直播

javascript - Node.js 模块与对象

javascript - 如何在nvd3图表上显示不同范围的2条线

javascript - 如何防止 HTML 输入字段中的按键 UP 和 DOWN 行为?

javascript - 检测何时加载图像列表

scala - 使用Flink获取DataStream的文件名