Cómo usar MediaRecorder como MediaSource

8

Como ejercicio para aprender WebRTC, estoy tratando de mostrar la cámara web local y lado a lado con una reproducción retrasada de la cámara web. Para lograr esto, estoy tratando de pasar los blobs grabados a un BufferSource y usar el MediaSource correspondiente como fuente para un elemento de video.

// the ondataavailable callback for the MediaRecorder
async function handleDataAvailable(event) {
  // console.log("handleDataAvailable", event);
  if (event.data && event.data.size > 0) {
    recordedBlobs.push(event.data);
  }

  if (recordedBlobs.length > 5) {
    if (recordedBlobs.length === 5)
      console.log("buffered enough for delayed playback");
    if (!updatingBuffer) {
      updatingBuffer = true;
      const bufferedBlob = recordedBlobs.shift();
      const bufferedAsArrayBuffer = await bufferedBlob.arrayBuffer();
      if (!sourceBuffer.updating) {
        console.log("appending to buffer");
        sourceBuffer.appendBuffer(bufferedAsArrayBuffer);
      } else {
        console.warn("Buffer still updating... ");
        recordedBlobs.unshift(bufferedBlob);
      }
    }
  }
}
// connecting the media source to the video element
recordedVideo.src = null;
recordedVideo.srcObject = null;
recordedVideo.src = window.URL.createObjectURL(mediaSource);
recordedVideo.controls = true;
try {
  await recordedVideo.play();
} catch (e) {
  console.error(`Play failed: ${e}`);
}

Todo el código: https://jsfiddle.net/43rm7258/1/

Cuando ejecuto esto en cromo 78 me sale un NotSupportedError: Failed to load because no supported source was found.del playelemento del elemento de vídeo.

No tengo idea de lo que estoy haciendo mal o cómo proceder en este momento.

Esto se trata de algo similar, pero no me ayuda: MediaSource detiene aleatoriamente el video

Este ejemplo fue mi punto de partida: https://webrtc.github.io/samples/src/content/getusermedia/record/

André
fuente

Respuestas:

9

En resumen

Hacer que funcione en Firefox y Chrome es fácil: ¡solo necesita agregar un códec de audio a su lista de códecs! video/webm;codecs=opus,vp8

Hacer que funcione en Safari es significativamente más complicado. MediaRecorder es una función "experimental" que debe habilitarse manualmente en las opciones del desarrollador. Una vez habilitado, Safari carece de un isTypeSupportedmétodo, por lo que debe manejarlo. Finalmente, no importa lo que solicite del MediaRecorder, Safari siempre entregará un archivo MP4, que no puede transmitirse de la manera en que WEBM sí. Esto significa que debe realizar la transmuxing en JavaScript para convertir el formato del contenedor de video sobre la marcha

Android debería funcionar si Chrome funciona

iOS no admite extensiones de origen de medios, por SourceBufferlo que no está definido en iOS y la solución completa no funcionará

Publicación original

Mirando el JSFiddle que publicaste, una solución rápida antes de comenzar:

  • Hace referencia a una variable errorMsgElementque nunca se define. Debe agregar un <div>a la página con un ID apropiado y luego crear una const errorMsgElement = document.querySelector(...)línea para capturarlo

Ahora, algo a tener en cuenta al trabajar con Media Source Extensions y MediaRecorder es que el soporte será muy diferente por navegador. Aunque esta es una parte "estandarizada" de la especificación HTML5, no es muy consistente en todas las plataformas. En mi experiencia, hacer que MediaRecorder funcione en Firefox no requiere demasiado esfuerzo, hacer que funcione en Chrome es un poco más difícil, hacer que funcione en Safari es casi imposible, y hacer que funcione en iOS literalmente no es algo que puedes hacer

Revisé y depuré esto por navegador y grabé mis pasos, para que pueda comprender algunas de las herramientas disponibles para depurar problemas de medios

Firefox

Cuando revisé su JSFiddle en Firefox, vi el siguiente error en la consola:

NotSupportedError: no se puede grabar una pista de audio: video / webm; codecs = vp8 indica un códec no compatible

Recuerdo que VP8 / VP9 fueron grandes empujones de Google y, como tal, pueden no funcionar en Firefox, así que intenté hacer un pequeño ajuste a su código. Eliminé el , options)parámetro de tu llamada a new MediaRecorder(). Esto le dice al navegador que use el códec que quiera, por lo que es probable que obtenga una salida diferente en cada navegador (pero al menos debería funcionar en cada navegador)

Esto funcionó en Firefox, así que revisé Chrome.

Cromo

Esta vez recibí un nuevo error:

(índice): 409 No capturado (en promesa) DOMException: Error al ejecutar 'appendBuffer' en 'SourceBuffer': Este SourceBuffer se ha eliminado de la fuente de medios principal. en MediaRecorder.handleDataAvailable ( https://fiddle.jshell.net/43rm7258/1/show/:409:22 )

Así que me dirigí a chrome: // media-internals / en mi navegador y vi esto:

El códec de flujo de audio opus no coincide con los códecs SourceBuffer.

En su código, está especificando un códec de video (VP9 o VP8) pero no un códec de audio, por lo que MediaRecorder le permite al navegador elegir el códec de audio que desee. Parece que en MediaRecorder de Chrome por defecto elige "opus" como el códec de audio, pero SourceBuffer de Chrome elige por defecto otra cosa. Esto fue arreglado trivialmente. Actualicé sus dos líneas que establecen el options.mimeTypemismo modo:

  • options = { mimeType: "video/webm;codecs=opus, vp9" };
  • options = { mimeType: "video/webm;codecs=opus, vp8" };

Como usa el mismo optionsobjeto para declarar MediaRecorder y SourceBuffer, agregar el códec de audio a la lista significa que SourceBuffer ahora se declara con un códec de audio válido y se reproduce el video

Por si acaso, probé el nuevo código (con un códec de audio) en Firefox. Esto funcionó! Así que somos 2 por 2 simplemente agregando el códec de audio a la optionslista (y dejándolo en los parámetros para declarar el MediaRecorder)

Parece que VP8 y opus funcionan en Firefox, pero no son los valores predeterminados (aunque a diferencia de Chrome, el valor predeterminado para MediaRecorder y SourceBuffer es el mismo, por lo que eliminar el optionsparámetro funcionó por completo)

Safari

Esta vez recibimos un error que quizás no podamos solucionar:

Rechazo de promesa no controlado: Error de referencia: No se puede encontrar la variable: MediaRecorder

Lo primero que hice fue Google "Safari MediaRecorder", que apareció en este artículo . Pensé en intentarlo, así que eché un vistazo. Bastante seguro:

Propiedades de Safari

Hice clic en esto para habilitar MediaRecorder y me encontré con lo siguiente en la consola:

Rechazo de promesa no manejado: TypeError: MediaRecorder.isTypeSupported no es una función. (En 'MediaRecorder.isTypeSupported (options.mimeType)', 'MediaRecorder.isTypeSupported' no está definido)

Entonces Safari no tiene el isTypeSupportedmétodo. No se preocupe, solo diremos "si este método no existe, suponga que es Safari y configure el tipo en consecuencia"

  if (MediaRecorder.isTypeSupported) {
    options = { mimeType: "video/webm;codecs=vp9" };
    if (!MediaRecorder.isTypeSupported(options.mimeType)) {
      console.error(`${options.mimeType} is not Supported`);
      errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
      options = { mimeType: "video/webm;codecs=vp8" };
      if (!MediaRecorder.isTypeSupported(options.mimeType)) {
        console.error(`${options.mimeType} is not Supported`);
        errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
        options = { mimeType: "video/webm" };
        if (!MediaRecorder.isTypeSupported(options.mimeType)) {
          console.error(`${options.mimeType} is not Supported`);
          errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
          options = { mimeType: "" };
        }
      }
    }
  } else {
    options = { mimeType: "" };
  }

Ahora solo tenía que encontrar un mimeType que admitiera Safari. Algunos Google ligeros sugieren que H.264 es compatible, así que probé:

options = { mimeType: "video/webm;codecs=h264" };

Esto me dio éxito MediaRecorder started, pero falló en la línea addSourceBuffercon el nuevo error:

NotSupportedError: la operación no es compatible.

Continuaré intentando diagnosticar cómo hacer que esto funcione en Safari, pero por ahora al menos abordo Firefox y Chrome

Actualización 1

He seguido trabajando en Safari. Desafortunadamente, Safari carece de las herramientas de Chrome y Firefox para profundizar en los elementos internos de los medios, por lo que hay muchas conjeturas involucradas.

Anteriormente había descubierto que recibíamos un error "La operación no es compatible" al intentar llamar addSourceBuffer. Así que creé una página única para intentar llamar solo a este método en diferentes circunstancias:

  • Tal vez agregue un buffer de origen antes de playque aparezca en el video
  • Tal vez agregue un búfer de origen antes de que la fuente de medios se haya adjuntado a un elemento de video
  • Tal vez agregue un buffer de origen con diferentes códecs
  • etc.

Descubrí que el problema seguía siendo el códec y que el mensaje de error sobre la "operación" no permitida era un poco engañoso. Eran los parámetros que no estaban permitidos. Simplemente el suministro de "h264" funcionó para MediaRecorder, pero SourceBuffer necesitaba que pasara los parámetros del códec .

Una de las primeras cosas que intenté fue dirigirse a la página de muestra MDN y la copia de los códecs que utilizan allí: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'. Esto dio el mismo error de "operación no permitida". Profundizando en el significado de estos parámetros de códec (¿qué significa 42E01Eincluso qué diablos significa ?). Si bien desearía tener una mejor respuesta, mientras buscaba en Google me topé con esta publicación de StackOverflow que mencionaba el uso 'video/mp4; codecs="avc1.64000d,mp4a.40.2"'en Safari. ¡Lo intenté y los errores de la consola desaparecieron!

Aunque los errores de la consola se han ido ahora, todavía no veo ningún video. Así que aún queda trabajo por hacer.

Actualización 2

Una investigación adicional en el depurador en Safari (colocando múltiples puntos de interrupción e inspeccionando variables en cada paso del proceso) descubrió que handleDataAvailablenunca se había llamado en Safari. Parece que en Firefox y Chrome mediaRecorder.start(100)seguirá correctamente las especificaciones y llamará ondatavailablecada 100 milisegundos, pero Safari ignora el parámetro y almacena todo en un Blob masivo. Llamar mediaRecorder.stop()manualmente provocó ondataavailableque se llamara con todo lo que se había grabado hasta ese momento

Traté de usar setIntervalpara llamar mediaRecorder.requestData()cada 100 milisegundos, pero requestDatano estaba definido en Safari (al igual que isTypeSupportedno estaba definido). Esto me puso en apuros.

Luego intenté limpiar todo el objeto MediaRecorder y crear uno nuevo cada 100 milisegundos, pero esto arrojó un error en la línea await bufferedBlob.arrayBuffer(). Todavía estoy investigando por qué ese falló

Actualización 3

Una cosa que recuerdo del formato MP4 es que se requiere el átomo "moov" para reproducir cualquier contenido. Es por eso que no puede descargar la mitad del medio de un archivo MP4 y reproducirlo. Necesita descargar el archivo ENTERO. Así que me preguntaba si el hecho de haber seleccionado MP4 era la razón por la que no recibía actualizaciones periódicas.

Intenté cambiar video/mp4a algunos valores diferentes y obtuve resultados variables:

  • video/webm - La operación no es compatible
  • video/x-m4v- Comportado como MP4, solo obtuve datos cuando .stop()se llamó
  • video/3gpp - Se comportó como MP4
  • video/flv - La operación no es compatible
  • video/mpeg - Se comportó como MP4

Todo lo que se comportó como MP4 me llevó a inspeccionar los datos que realmente se estaban pasando handleDataAvailable. Fue entonces cuando me di cuenta de esto:

ingrese la descripción de la imagen aquí

No importa lo que seleccioné para el formato de video, ¡Safari siempre me daba un MP4!

De repente recordé por qué Safari era una pesadilla y por qué lo había clasificado mentalmente como "malditamente casi imposible". Para unir varios MP4 requeriría un transmuxer JavaScript

Entonces recordé, eso es exactamente lo que había hecho antes . Trabajé con MediaRecorder y SourceBuffer hace poco más de un año para intentar crear un reproductor JavaScript RTMP. Una vez que el reproductor estuvo listo, quise agregar soporte para DVR (buscando partes del video que ya habían sido transmitidas), lo que hice usando MediaRecorder y manteniendo un búfer de anillo en la memoria de blobs de video de 1 segundo. En Safari ejecuté estos blobs de video a través del transmuxer que había codificado para convertirlos de MP4 a ISO-BMFF para poder concatenarlos juntos.

Desearía poder compartir el código con usted, pero todo es propiedad de mi antiguo empleador, por lo que en este punto la solución se me ha perdido. Sé que alguien tuvo problemas para compilar FFMPEG a JavaScript usando emscripten, por lo que puede aprovechar eso.

stevendesu
fuente
¡Guauu! muchas gracias por esto! Estoy realmente sorprendido por el esfuerzo que pones en esto. Revisaré tus hallazgos y veré si puedo hacer que las cosas funcionen.
André
1
Con sus punteros, de hecho lo tengo funcionando en Chrome, estaba cerca de la solución, pero no tenía idea de cómo continuar con ella. Gracias por explicar los pasos que tomaste para localizarlo. Gracias de nuevo, su respuesta fue realmente útil y aprendí mucho.
André