style attribute javascript html5 streaming html5-audio

javascript - attribute - Transmisión de audio HTML5: ¿medir con precisión la latencia?



html title attribute (3)

Estoy creando una aplicación web multiplataforma donde el audio se genera sobre la marcha en el servidor y se transmite en vivo a un cliente de navegador, probablemente a través del elemento de audio HTML5. En el navegador, tendré animaciones controladas por Javascript que deben sincronizarse con precisión con el audio reproducido. "Preciso" significa que el audio y la animación deben estar dentro de un segundo el uno del otro, y con suerte dentro de 250 ms (piense en la sincronización de labios). Por varias razones, no puedo hacer el audio y la animación en el servidor y transmitir en vivo el video resultante.

Idealmente, habría poca o ninguna latencia entre la generación de audio en el servidor y la reproducción de audio en el navegador, pero entiendo que la latencia será difícil de controlar y probablemente en el rango de 3-7 segundos (navegador, entorno- , dependiente de la red y la fase de la luna). Sin embargo, puedo manejar eso si puedo medir con precisión la latencia real sobre la marcha para que mi Javascript del navegador sepa cuándo presentar el marco animado adecuado.

Entonces, necesito medir con precisión la latencia entre mi entrega de audio al servidor de transmisión (Icecast?) Y el audio que sale de los altavoces en la computadora que aloja el altavoz. Algunas posibilidades de cielo azul:

  • Agregue metadatos al flujo de audio y analícelo desde el audio en reproducción (entiendo que esto no es posible usando el elemento de audio estándar)

  • Agregue breves períodos de silencio puro al audio y luego detecte en el navegador (¿pueden los elementos de audio producir las muestras de audio reales?)

  • Consulte al servidor y al navegador sobre las diferentes profundidades del búfer

  • Decodifique el audio transmitido en Javascript y luego tome los metadatos

¿Alguna idea de cómo podría hacer esto?


Lo que intentaría es crear primero una marca de tiempo con performance.now , procesar los datos y grabarlos en un blob con la nueva API de grabadora web.

La grabadora web le pedirá acceso al usuario a su tarjeta de audio, esto puede ser un problema para su aplicación, pero parece obligatorio obtener la latencia real.

Tan pronto como se haga esto, hay muchas maneras de medir la latencia real entre la generación y el renderizado real. Básicamente, un evento de sonido.

Para mayor referencia y ejemplo:

Demo de grabadora

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

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


No hay forma de medir la latencia directamente, pero cualquier AudioElement genera eventos como ''reproducir'' si solo se reproduce (se dispara con bastante frecuencia), o ''se detiene'' si se detiene la transmisión, o ''espera'' si se están cargando datos. Entonces, lo que puede hacer es manipular su video en función de estos eventos.

Por lo tanto, juegue mientras está parado o esperando se dispara, luego continúe reproduciendo video si se vuelve a disparar.

Pero le aconsejo que verifique otros eventos que puedan afectar su flujo (el error, por ejemplo, sería importante para usted).

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


Utilice el evento timeupdate del elemento <audio> , que se dispara de tres a cuatro veces por segundo, para realizar animaciones precisas durante la transmisión de medios al verificar .currentTime del elemento <audio> . Donde se pueden iniciar o detener animaciones o transiciones hasta varias veces por segundo.

Si está disponible en el navegador, puede usar fetch() para solicitar un recurso de audio, en .then() return response.body.getReader() que devuelve un ReadableStream del recurso; cree un nuevo objeto MediaSource , establezca <audio> o new Audio() .src en objectURL de MediaSource ; agregue los fragmentos de la primera secuencia en .read() encadenados .then() a sourceBuffer de MediaSource con .mode establecido en "sequence" ; agregue el resto de fragmentos a sourceBuffer en los eventos de sourceBuffer updateend .

Si fetch() response.body.getReader() no está disponible en el navegador, aún puede utilizar timeupdate o evento de progress del elemento <audio> para verificar .currentTime , iniciar o detener animaciones o transiciones en el segundo requerido de reproducción de medios de transmisión.

Use el evento canplay del elemento <audio> para reproducir medios cuando la transmisión haya acumulado buffers adecuados en MediaSource para continuar con la reproducción.

Puede usar un objeto con propiedades establecidas en números correspondientes a .currentTime de <audio> donde debe producirse la animación, y valores establecidos en la propiedad css del elemento que debe animarse para realizar animaciones precisas.

En javascript continuación, las animaciones ocurren cada veinte segundos, comenzando en 0 y cada sesenta segundos hasta que finaliza la reproducción de medios.

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