Em suma
É fácil fazê-lo funcionar no Firefox e Chrome: você só precisa adicionar um codec de áudio à sua lista de codecs! video/webm;codecs=opus,vp8
Conseguir que ele funcione no Safari é significativamente mais complicado. MediaRecorder é um recurso "experimental" que deve ser ativado manualmente nas opções do desenvolvedor. Uma vez ativado, o Safari não possui um isTypeSupported
método, então você precisa lidar com isso. Por fim, não importa o que você solicite do MediaRecorder, o Safari sempre fornecerá um arquivo MP4 - que não pode ser transmitido da maneira que o WEBM pode. Isso significa que você precisa executar transmuxing em JavaScript para converter o formato do contêiner de vídeo rapidamente
O Android deve funcionar se o Chrome funcionar
O iOS não suporta Extensões de Origem de Mídia, portanto, SourceBuffer
não está definido no iOS e toda a solução não funcionará
Correio Original
Observando o JSFiddle que você postou, uma solução rápida antes de começarmos:
- Você faz referência a uma variável
errorMsgElement
que nunca é definida. Você deve adicionar um <div>
à página com um ID apropriado e criar uma const errorMsgElement = document.querySelector(...)
linha para capturá-lo
Agora, algo a ser observado ao trabalhar com o Media Source Extensions e o MediaRecorder é que o suporte será muito diferente por navegador. Mesmo que essa seja uma parte "padronizada" da especificação do HTML5, ela não é muito consistente entre as plataformas. Na minha experiência, conseguir que o MediaRecorder funcione no Firefox não exige muito esforço, fazê-lo funcionar no Chrome é um pouco mais difícil, fazê-lo funcionar no Safari é quase impossível, e fazê-lo funcionar no iOS não é literalmente algo que você pode fazer.
Analisei e depurei isso por navegador e registrei minhas etapas, para que você possa entender algumas das ferramentas disponíveis ao depurar problemas de mídia
Raposa de fogo
Quando fiz o check-out do seu JSFiddle no Firefox, vi o seguinte erro no console:
NotSupportedError: Uma faixa de áudio não pode ser gravada: video / webm; codecs = vp8 indica um codec não suportado
Lembro que o VP8 / VP9 foi um grande impulso do Google e, como tal, pode não funcionar no Firefox, então tentei fazer um pequeno ajuste no seu código. Eu removi o , options)
parâmetro da sua chamada para new MediaRecorder()
. Isso indica ao navegador para usar o codec que desejar, portanto, você provavelmente obterá uma saída diferente em cada navegador (mas deve pelo menos funcionar em todos os navegadores)
Como funcionou no Firefox, verifiquei o Chrome.
cromada
Desta vez, recebi um novo erro:
(índice): 409 Não detectado (em promessa) DOMException: falha ao executar 'appendBuffer' em 'SourceBuffer': este SourceBuffer foi removido da fonte de mídia pai. em MediaRecorder.handleDataAvailable ( https://fiddle.jshell.net/43rm7258/1/show/:409:22 )
Então, fui para o chrome: // media-internals / no meu navegador e vi o seguinte:
O opus do codec de fluxo de áudio não corresponde aos codecs do SourceBuffer.
No seu código, você está especificando um codec de vídeo (VP9 ou VP8), mas não um codec de áudio; portanto, o MediaRecorder permite que o navegador escolha qualquer codec de áudio que desejar. Parece que no MediaRecorder do Chrome, por padrão, escolhe "opus" como codec de áudio, mas o SourceBuffer do Chrome, por padrão, escolhe outra coisa. Isso foi trivialmente corrigido. Atualizei suas duas linhas que definem o seguinte options.mimeType
:
options = { mimeType: "video/webm;codecs=opus, vp9" };
options = { mimeType: "video/webm;codecs=opus, vp8" };
Como você usa o mesmo options
objeto para declarar o MediaRecorder e o SourceBuffer, adicionar o codec de áudio à lista significa que o SourceBuffer agora é declarado com um codec de áudio válido e o vídeo é reproduzido
Por uma boa medida, testei o novo código (com um codec de áudio) no Firefox. Isso funcionou! Então, somos 2 a 2 apenas adicionando o codec de áudio à options
lista (e deixando-o nos parâmetros para declarar o MediaRecorder)
Parece que o VP8 e o opus funcionam no Firefox, mas não são os padrões (embora, ao contrário do Chrome, o padrão para MediaRecorder e SourceBuffer sejam os mesmos, e é por isso que a remoção do options
parâmetro funcionou totalmente)
Safári
Dessa vez, ocorreu um erro que talvez não consiga solucionar:
Rejeição de promessa não tratada: ReferenceError: Não é possível encontrar a variável: MediaRecorder
A primeira coisa que fiz foi o Google "Safari MediaRecorder", que apareceu neste artigo . Eu pensei em tentar, então dei uma olhada. Com certeza:
Cliquei para ativar o MediaRecorder e recebi o seguinte no console:
Rejeição de promessa não tratada: TypeError: MediaRecorder.isTypeSupported não é uma função. (Em 'MediaRecorder.isTypeSupported (options.mimeType)', 'MediaRecorder.isTypeSupported' está indefinido)
Portanto, o Safari não tem o isTypeSupported
método. Não se preocupe, apenas diremos "se esse método não existir, assuma o Safari e defina o tipo de acordo"
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: "" };
}
Agora eu só precisava encontrar um mimeType compatível com o Safari. Alguma pesquisa leve sugere que o H.264 é suportado, então tentei:
options = { mimeType: "video/webm;codecs=h264" };
Isso me deu êxito MediaRecorder started
, mas falhou na linha addSourceBuffer
com o novo erro:
NotSupportedError: A operação não é suportada.
Vou continuar a tentar diagnosticar como fazer isso funcionar no Safari, mas por enquanto eu pelo menos lidei com o Firefox e o Chrome
Atualização 1
Continuei trabalhando no Safari. Infelizmente, o Safari não possui as ferramentas do Chrome e do Firefox para se aprofundar nas mídias internas, por isso há muitas suposições envolvidas.
Eu já havia descoberto que estávamos recebendo um erro "A operação não é suportada" ao tentar ligar addSourceBuffer
. Então, criei uma página única para tentar chamar apenas esse método em diferentes circunstâncias:
- Talvez adicione um buffer de origem antes de
play
ser chamado no vídeo
- Talvez adicione um buffer de origem antes que a fonte de mídia seja anexada a um elemento de vídeo
- Talvez adicione um buffer de origem com codecs diferentes
- etc
Eu descobri que o problema ainda era o codec e que a mensagem de erro sobre a "operação" não ser permitida era um pouco enganadora. Foram os parâmetros que não foram permitidos. O simples fornecimento de "h264" funcionou para o MediaRecorder, mas o SourceBuffer precisava que eu passasse os parâmetros do codec .
Uma das primeiras coisas que eu tentei estava indo para a página de amostra MDN e copiar os codecs que eles usaram lá: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
. Isso deu o mesmo erro "operação não permitida". Cavando o significado desses parâmetros de codec (como o que diabos 42E01E
ainda significa ?). Enquanto eu gostaria de ter uma resposta melhor, enquanto pesquisava no Google, me deparei com este post do StackOverflow que mencionava o uso 'video/mp4; codecs="avc1.64000d,mp4a.40.2"'
no Safari. Eu tentei e os erros do console sumiram!
Embora os erros do console tenham desaparecido agora, ainda não estou vendo nenhum vídeo. Portanto, ainda há trabalho a fazer.
Atualização 2
Uma investigação mais aprofundada no Depurador no Safari (colocando vários pontos de interrupção e inspecionando variáveis em cada etapa do processo) descobriu que isso handleDataAvailable
nunca estava sendo chamado no Safari. Parece que no Firefox e Chrome mediaRecorder.start(100)
seguirá adequadamente as especificações e chamará a ondatavailable
cada 100 milissegundos, mas o Safari ignora o parâmetro e armazena tudo em um Blob enorme. A chamada mediaRecorder.stop()
manual provocou ondataavailable
a chamada com tudo o que havia sido gravado até aquele momento
Tentei usar setInterval
para chamar a mediaRecorder.requestData()
cada 100 milissegundos, mas requestData
não foi definido no Safari (assim como isTypeSupported
não foi definido). Isso me deixou meio confuso.
Em seguida, tentei limpar todo o objeto MediaRecorder e criar um novo a cada 100 milissegundos, mas isso gerou um erro na linha await bufferedBlob.arrayBuffer()
. Eu ainda estou investigando por que aquele falhou
Atualização 3
Uma coisa que lembro sobre o formato MP4 é que o átomo "moov" é necessário para reproduzir qualquer conteúdo. É por isso que você não pode baixar a metade do meio de um arquivo MP4 e reproduzi-lo. Você precisa baixar o arquivo INTEIRO. Então, eu me perguntava se o fato de ter selecionado o MP4 era o motivo de eu não estar recebendo atualizações regulares.
Tentei mudar video/mp4
para alguns valores diferentes e obtive resultados variados:
video/webm
- A operação não é suportada
video/x-m4v
- Me comportei como MP4, só consegui dados quando .stop()
foi chamado
video/3gpp
- Comportou-se como MP4
video/flv
- A operação não é suportada
video/mpeg
- Comportou-se como MP4
Tudo se comportando como MP4 me levou a inspecionar os dados que realmente estavam sendo transmitidos handleDataAvailable
. Foi quando eu notei isso:
Não importa o que eu selecionei para o formato de vídeo, o Safari estava sempre me dando um MP4!
De repente, lembrei-me por que o Safari era um pesadelo e por que eu o havia classificado mentalmente como "quase impossível". Para unir vários MP4s seria necessário um transmuxer JavaScript
Foi quando me lembrei, foi exatamente o que eu havia feito antes . Trabalhei com o MediaRecorder e o SourceBuffer há pouco mais de um ano para tentar criar um player RTMP JavaScript. Depois que o player terminou, eu queria adicionar suporte ao DVR (procurando voltar para as partes do vídeo que já haviam sido transmitidas), o que fiz usando o MediaRecorder e mantendo um buffer de anel na memória dos blobs de vídeo de 1 segundo. No Safari, passei esses blobs de vídeo através do transmuxer que eu havia codificado para convertê-los de MP4 para ISO-BMFF, para concatená-los juntos.
Gostaria de poder compartilhar o código com você, mas tudo pertence ao meu antigo empregador - então, neste momento, a solução foi perdida para mim. Sei que alguém teve o problema de compilar o FFMPEG para JavaScript usando o emscripten, para que você possa tirar proveito disso.