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 isTypeSupported
mé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 SourceBuffer
lo 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
errorMsgElement
que 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.mimeType
mismo modo:
options = { mimeType: "video/webm;codecs=opus, vp9" };
options = { mimeType: "video/webm;codecs=opus, vp8" };
Como usa el mismo options
objeto 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 options
lista (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 options
pará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:
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 isTypeSupported
mé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 addSourceBuffer
con 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
play
que 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 42E01E
incluso 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 handleDataAvailable
nunca se había llamado en Safari. Parece que en Firefox y Chrome mediaRecorder.start(100)
seguirá correctamente las especificaciones y llamará ondatavailable
cada 100 milisegundos, pero Safari ignora el parámetro y almacena todo en un Blob masivo. Llamar mediaRecorder.stop()
manualmente provocó ondataavailable
que se llamara con todo lo que se había grabado hasta ese momento
Traté de usar setInterval
para llamar mediaRecorder.requestData()
cada 100 milisegundos, pero requestData
no estaba definido en Safari (al igual que isTypeSupported
no 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/mp4
a 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:
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.