Подтвердить что ты не робот

Текстовая потоковая передача HTML5: точно измерять задержку?

Я создаю кросс-платформенное веб-приложение, где звук генерируется "на лету" на сервере и транслируется потоковым потоком к клиенту браузера, возможно, через аудио-элемент HTML5. В браузере у меня будут анимации с поддержкой Javascript, которые должны точно синхронизироваться с воспроизводимым звуком. "Точный" означает, что звук и анимация должны быть в пределах секунды друг от друга, и, надеюсь, в течение 250 мс (подумайте о синхронизации губ). По разным причинам я не могу делать аудио и анимацию на сервере и воспроизводить видео в реальном времени.

В идеале между генерацией звука на сервере и воспроизведением звука в браузере будет мало или совсем нет, но я понимаю, что время задержки будет сложно контролировать и, вероятно, в диапазоне 3-7 секунд (браузер-, зависимость от окружающей среды, сети и фазы луны). Я могу справиться с этим, хотя, если я могу точно измерить фактическую задержку "на лету", чтобы мой браузер Javascript знал, когда представить соответствующий анимированный фрейм.

Итак, мне нужно точно измерить задержку между моим передающим звуком потоковым сервером (Icecast?) и звуком, выходящим из динамиков на компьютере, на котором размещен динамик. Некоторые возможности голубого неба:

  • Добавьте метаданные в аудиопоток и проанализируйте его с воспроизводимого звука (я понимаю, что это невозможно с использованием стандартного аудио-элемента)

  • Добавить короткие периоды чистой тишины в аудио, а затем обнаружить их в браузере (могут ли аудио-элементы выдавать фактические звуковые сэмплы?)

  • Запросить сервер и браузер относительно различных глубин буфера

  • Декодирование потокового аудио в Javascript, а затем захват метаданных

Любые мысли о том, как я могу это сделать?

4b9b3361

Ответ 1

Использовать событие timeupdate элемента <audio>, которое запускается три-четыре раза в секунду для выполнения точной анимации во время потоковой передачи медиа, путем проверки .currentTime элемента <audio>. Если анимации или переходы можно запускать или останавливать до нескольких раз в секунду.

Если доступно в браузере, вы можете использовать fetch() для запроса звукового ресурса, .then() return response.body.getReader(), который возвращает ReadableStream ресурса; создайте новый объект MediaSource, установите <audio> или new Audio() .src на objectURL MediaSource; добавьте первые куски потока в .read() с цепью .then() до sourceBuffer из MediaSource с .mode, установленным в "sequence"; добавьте оставшиеся куски к sourceBuffer в sourceBuffer updateend событиях.

Если fetch() response.body.getReader() недоступно в браузере, вы можете использовать событие timeupdate или progress элемента <audio> для проверки .currentTime, запускать или останавливать анимацию или переходы в требуемую секунду потоковой передачи воспроизведения мультимедиа.

Используйте canplay событие элемента <audio> для воспроизведения медиафайлов, когда поток накапливает соответствующие буферы в MediaSource, чтобы продолжить воспроизведение.

Вы можете использовать объект со свойствами, установленными на номера, соответствующие .currentTime of <audio>, где должна произойти анимация, а значениями установлено значение css свойства элемента, которое должно быть анимировано для выполнения точной анимации.

В javascript ниже анимации происходят каждые 20 секунд, начиная с 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

Ответ 2

Невозможно напрямую измерить задержку, но любой AudioElement генерирует такие события, как "воспроизведение", если он просто играл (запускался довольно часто), или "остановился", если остановлена ​​потоковая передача, или "ждет", если данные загружаются. Итак, что вы можете сделать, это манипулировать вашим видео на основе этих событий.

Итак, игра при останове или ожидании уволена, а затем продолжайте воспроизведение видео, если игра снова запущена.

Но я советую вам проверить другие события, которые могут повлиять на ваш поток (например, ошибка будет важна для вас).

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

Ответ 3

Что бы я попробовал, сначала создайте метку времени с performance.now, обработайте данные и запишите их в блобе с помощью новой веб-страницы регистратор api.

Веб-рекордер будет запрашивать у пользователя доступ к его звуковой карте, это может быть проблемой для вашего приложения, но оно выглядит как обязательное, чтобы получить реальную задержку.

Как только это будет сделано, существует много способов измерения фактической задержки между генерацией и фактическим рендерингом. В принципе, звуковое событие.

Для дополнительной справки и примера:

демо-версия рекордера

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

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