Como gravar webcam e áudio usando webRTC e uma conexão Peer baseada em servidor

91

Gostaria de gravar a webcam e o áudio dos usuários e salvá-los em um arquivo no servidor. Esses arquivos podem ser disponibilizados a outros usuários.

Não tenho problemas com a reprodução, mas estou tendo problemas para gravar o conteúdo.

Meu entendimento é que a .record()função getUserMedia ainda não foi escrita - apenas uma proposta foi feita para ela até agora.

Eu gostaria de criar uma conexão de mesmo nível no meu servidor usando o PeerConnectionAPI. Eu entendo que isso é um pouco hacky, mas estou pensando que deveria ser possível criar um par no servidor e registrar o que o cliente-par envia.

Se isso for possível, devo ser capaz de salvar esses dados em flv ou qualquer outro formato de vídeo.

Minha preferência é, na verdade, gravar a webcam + áudio do lado do cliente, para permitir que o cliente regravar os vídeos se não gostou da primeira tentativa antes de enviar. Isso também permitiria interrupções nas conexões de rede. Eu vi um código que permite a gravação de 'imagens' individuais da webcam enviando os dados para a tela - isso é legal, mas eu preciso do áudio também.

Aqui está o código do lado do cliente que tenho até agora:

  <video autoplay></video>

<script language="javascript" type="text/javascript">
function onVideoFail(e) {
    console.log('webcam fail!', e);
  };

function hasGetUserMedia() {
  // Note: Opera is unprefixed.
  return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
            navigator.mozGetUserMedia || navigator.msGetUserMedia);
}

if (hasGetUserMedia()) {
  // Good to go!
} else {
  alert('getUserMedia() is not supported in your browser');
}

window.URL = window.URL || window.webkitURL;
navigator.getUserMedia  = navigator.getUserMedia || navigator.webkitGetUserMedia ||
                          navigator.mozGetUserMedia || navigator.msGetUserMedia;

var video = document.querySelector('video');
var streamRecorder;
var webcamstream;

if (navigator.getUserMedia) {
  navigator.getUserMedia({audio: true, video: true}, function(stream) {
    video.src = window.URL.createObjectURL(stream);
    webcamstream = stream;
//  streamrecorder = webcamstream.record();
  }, onVideoFail);
} else {
    alert ('failed');
}

function startRecording() {
    streamRecorder = webcamstream.record();
    setTimeout(stopRecording, 10000);
}
function stopRecording() {
    streamRecorder.getRecordedData(postVideoToServer);
}
function postVideoToServer(videoblob) {
/*  var x = new XMLHttpRequest();
    x.open('POST', 'uploadMessage');
    x.send(videoblob);
*/
    var data = {};
    data.video = videoblob;
    data.metadata = 'test metadata';
    data.action = "upload_video";
    jQuery.post("http://www.foundthru.co.uk/uploadvideo.php", data, onUploadSuccess);
}
function onUploadSuccess() {
    alert ('video uploaded');
}

</script>

<div id="webcamcontrols">
    <a class="recordbutton" href="javascript:startRecording();">RECORD</a>
</div>
Dave Hilditch
fonte
Eu tenho o mesmo problema. O método getRecordedData () está funcionando para você? Não está em meus navegadores atualizados.
Firas de
Não - também experimentei o 'Google Canary'.
Dave Hilditch
Sim, estou de olho nisso - atualizarei este tópico quando houver uma solução adequada.
Dave Hilditch
2
se você conseguiu a solução da questão acima, por favor, compartilhe comigo, Obrigado
Muhammad
2
Alguém conseguiu obter os bytes do MediaStream por meio de alguma mágica RTC do lado do servidor?
Vinay

Respostas:

45

Você definitivamente deveria dar uma olhada no Kurento . Ele fornece uma infraestrutura de servidor WebRTC que permite gravar a partir de um feed WebRTC e muito mais. Você também pode encontrar alguns exemplos para o aplicativo que está planejando aqui . É realmente fácil adicionar recursos de gravação a essa demonstração e armazenar o arquivo de mídia em um URI (disco local ou qualquer outro lugar).

O projeto está licenciado sob LGPL Apache 2.0


EDITAR 1

Desde esta postagem, adicionamos um novo tutorial que mostra como adicionar o gravador em alguns cenários

Disclaimer: Faço parte da equipe que desenvolve o Kurento.

igracia
fonte
2
@Redtopia Em alguns testes de carga recentes, fomos capazes de obter 150 conexões one2one de webrtc em um i5 / 16GB RAM. Você pode esperar que esses números sejam melhores no futuro, mas não espere milagres: há muita criptografia acontecendo para SRTP, e isso é exigente. Estamos analisando a criptografia / descriptografia acelerada por hardware e os números irão aumentar, e embora eu não possa garantir a você o quão melhor será até que
testemos
2
@ user344146 Era provavelmente eu respondendo. Você se importaria de compartilhar um link para essa postagem? Se você obteve essa resposta, provavelmente é porque perguntou algo que já estava lá ou na lista. Parece que você está tentando compilar uma versão do SNAPSHOT. Esses artefatos não são publicados na central, então você confere uma versão dos tutoriais ou usa nosso dev repo interno. Isso foi respondido na lista muitas vezes, há uma entrada na documentação sobre como trabalhar com versões de desenvolvimento ... Dedicamos um tempo para escrevê-lo, então seria bom você dedicar um tempo para lê-lo.
igracia
2
Só estou usando o Kurento para fazer essa gravação. Não é complicado, mas preciso de um pouco de tempo para entender o conceito - porque alguns dos documentos são realmente significantes - e encontrar o que posso enviar para o Kurento, ou a descrição de eventos e assim por diante, às vezes pode ser muito frustrante. Mas de qualquer forma, um projeto aberto como este é realmente um ótimo trabalho e vale a pena usar. O Kurento está funcionando apenas no Linux (a versão do Windows não é oficial e não funciona com todas as funcionalidades).
Krystian
1
Encontrei respostas para as perguntas acima (postando aqui para outras), Kurento atualmente suporta JDK 7.0, não é que ele precise ser dependente do Ubuntu 14.04, ele deve suportar versões posteriores também, mas Kurento não é testado oficialmente em outras versões do Ubuntu / outra versão do linux. Além disso, o Kurento lança versões de 64 bits prontamente disponíveis para instalação, no entanto, você pode instalar a versão de 32 bits do servidor, mas primeiro é necessário compilá-la.
Bilbo Baggins
1
Infelizmente, como afirmado em minha resposta, o desenvolvimento do Kurento desacelerou severamente após a aquisição do Twilio. Eu recomendo usar Janus em vez disso.
Jamix
18

Por favor, verifique o RecordRTC

RecordRTC é licenciado pelo MIT no github .

Dmitry
fonte
2
Isso é incrível - minha pergunta: isso pode gravar vídeo e áudio juntos (ao vivo um vídeo real em vez de duas coisas separadas?)
Brian Dear
Concordo - incrível, mas parece que só registra os dados separadamente.
Dave Hilditch
3
@BrianDear há um RecordRTC-together
Mifeng
3
Essa abordagem funciona por meio do Whammy.js no Chrome. Isso é problemático, pois a qualidade tende a ser muito inferior à emulação que o Whammy fornece devido à falta de um MediaStreamRecorder do Chrome. O que essencialmente acontece é que WhammyRecorder aponta uma tag de vídeo para a URL do objeto MediaStream e, em seguida, tira instantâneos webp de um elemento de tela em uma determinada taxa de quadros. Em seguida, ele usa o Whammy para colocar todos esses quadros juntos em um vídeo webm.
Vinay
15

Eu acredito usando kurento ou outro MCUs apenas para a gravação de vídeos seria pouco de exagero, especialmente considerando o fato de que o Chrome tem MediaRecorder suporte API de V47 e Firefox desde v25. Então, nesta junção, você pode nem precisar de uma biblioteca js externa para fazer o trabalho, tente esta demonstração que fiz para gravar vídeo / áudio usando MediaRecorder:

Demo - funcionaria no Chrome e Firefox (intencionalmente deixado de fora empurrando blob para o código do servidor)

Fonte do código Github

Se estiver executando o Firefox, você pode testá-lo aqui (necessidades do Chrome https):

'use strict'

let log = console.log.bind(console),
  id = val => document.getElementById(val),
  ul = id('ul'),
  gUMbtn = id('gUMbtn'),
  start = id('start'),
  stop = id('stop'),
  stream,
  recorder,
  counter = 1,
  chunks,
  media;


gUMbtn.onclick = e => {
  let mv = id('mediaVideo'),
    mediaOptions = {
      video: {
        tag: 'video',
        type: 'video/webm',
        ext: '.mp4',
        gUM: {
          video: true,
          audio: true
        }
      },
      audio: {
        tag: 'audio',
        type: 'audio/ogg',
        ext: '.ogg',
        gUM: {
          audio: true
        }
      }
    };
  media = mv.checked ? mediaOptions.video : mediaOptions.audio;
  navigator.mediaDevices.getUserMedia(media.gUM).then(_stream => {
    stream = _stream;
    id('gUMArea').style.display = 'none';
    id('btns').style.display = 'inherit';
    start.removeAttribute('disabled');
    recorder = new MediaRecorder(stream);
    recorder.ondataavailable = e => {
      chunks.push(e.data);
      if (recorder.state == 'inactive') makeLink();
    };
    log('got media successfully');
  }).catch(log);
}

start.onclick = e => {
  start.disabled = true;
  stop.removeAttribute('disabled');
  chunks = [];
  recorder.start();
}


stop.onclick = e => {
  stop.disabled = true;
  recorder.stop();
  start.removeAttribute('disabled');
}



function makeLink() {
  let blob = new Blob(chunks, {
      type: media.type
    }),
    url = URL.createObjectURL(blob),
    li = document.createElement('li'),
    mt = document.createElement(media.tag),
    hf = document.createElement('a');
  mt.controls = true;
  mt.src = url;
  hf.href = url;
  hf.download = `${counter++}${media.ext}`;
  hf.innerHTML = `donwload ${hf.download}`;
  li.appendChild(mt);
  li.appendChild(hf);
  ul.appendChild(li);
}
      button {
        margin: 10px 5px;
      }
      li {
        margin: 10px;
      }
      body {
        width: 90%;
        max-width: 960px;
        margin: 0px auto;
      }
      #btns {
        display: none;
      }
      h1 {
        margin-bottom: 100px;
      }
<link type="text/css" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<h1> MediaRecorder API example</h1>

<p>For now it is supported only in Firefox(v25+) and Chrome(v47+)</p>
<div id='gUMArea'>
  <div>
    Record:
    <input type="radio" name="media" value="video" checked id='mediaVideo'>Video
    <input type="radio" name="media" value="audio">audio
  </div>
  <button class="btn btn-default" id='gUMbtn'>Request Stream</button>
</div>
<div id='btns'>
  <button class="btn btn-default" id='start'>Start</button>
  <button class="btn btn-default" id='stop'>Stop</button>
</div>
<div>
  <ul class="list-unstyled" id='ul'></ul>
</div>
<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>

mido
fonte
O Chrome 49 é o primeiro a oferecer suporte à API MediaRecorder sem a bandeira.
Octavian Naicu
7

sim, como você entendeu, MediaStreamRecorder não está implementado no momento.

MediaStreamRecorder é uma API WebRTC para gravar streams getUserMedia (). Ele permite que os aplicativos da web criem um arquivo a partir de uma sessão de áudio / vídeo ao vivo.

como alternativa, você pode fazer isso http://ericbidelman.tumblr.com/post/31486670538/creating-webm-video-from-getusermedia, mas falta parte do áudio.

Konga Raju
fonte
1
Sim, e você pode capturar o arquivo de áudio, enviá-lo para o servidor e combiná-los para criar um arquivo de vídeo real no servidor. Mas esta solução pode ser muito lenta do lado do cliente dependendo da configuração do computador, já que ele tem que criar arquivos de imagem usando um canvas E capturar o áudio, e tudo isso na RAM ... A propósito, a equipe do firefox está trabalhando nisso , então, esperançosamente, eles o lançarão em breve.
Firas de
4

Você pode usar RecordRTC junto , que é baseado em RecordRTC.

Ele suporta a gravação de vídeo e áudio juntos em arquivos separados. Você precisará de uma ferramenta como ffmpegmesclar dois arquivos em um no servidor.

Mifeng
fonte
2
Esta é uma solução de navegador, não do lado do servidor.
Brad
2

O Web Call Server 4 pode gravar áudio e vídeo WebRTC no contêiner WebM. A gravação é feita usando o codec Vorbis para áudio e o codec VP8 para vídeo. Os codecs WebRTC iniciais são Opus ou G.711 e VP8. Portanto, a gravação do lado do servidor requer transcodificação Opus / G.711 para Vorbis do lado do servidor ou transcodificação VP8-H.264 se for necessário usar outro contêiner, ou seja, AVI.

Bob42
fonte
isso é coisa comercial?
Stepan Yakovenko
0

Só para constar, também não tenho conhecimento suficiente sobre isso,

Mas eu encontrei isso no hub Git-

<!DOCTYPE html>
 <html>
<head>
  <title>XSockets.WebRTC Client example</title>
  <meta charset="utf-8" />


<style>
body {

  }
.localvideo {
position: absolute;
right: 10px;
top: 10px;
}

.localvideo video {
max-width: 240px;
width:100%;
margin-right:auto;
margin-left:auto;
border: 2px solid #333;

 }
 .remotevideos {
height:120px;
background:#dadada;
padding:10px; 
}

.remotevideos video{
max-height:120px;
float:left;
 }
</style>
</head>
<body>
<h1>XSockets.WebRTC Client example </h1>
<div class="localvideo">
    <video autoplay></video>
</div>

<h2>Remote videos</h2>
<div class="remotevideos">

</div>
<h2>Recordings  ( Click on your camera stream to start record)</h2>
<ul></ul>


<h2>Trace</h2>
<div id="immediate"></div>
<script src="XSockets.latest.js"></script>
<script src="adapter.js"></script>
<script src="bobBinder.js"></script>
<script src="xsocketWebRTC.js"></script>
<script>
    var $ = function (selector, el) {
        if (!el) el = document;
        return el.querySelector(selector);
    }
    var trace = function (what, obj) {
        var pre = document.createElement("pre");
        pre.textContent = JSON.stringify(what) + " - " + JSON.stringify(obj || "");
        $("#immediate").appendChild(pre);
    };
    var main = (function () {
        var broker;
        var rtc;
        trace("Ready");
        trace("Try connect the connectionBroker");
        var ws = new XSockets.WebSocket("wss://rtcplaygrouund.azurewebsites.net:443", ["connectionbroker"], {
            ctx: '23fbc61c-541a-4c0d-b46e-1a1f6473720a'
        });
        var onError = function (err) {
            trace("error", arguments);
        };
        var recordMediaStream = function (stream) {
            if ("MediaRecorder" in window === false) {
                trace("Recorder not started MediaRecorder not available in this browser. ");
                return;
            }
            var recorder = new XSockets.MediaRecorder(stream);
            recorder.start();
            trace("Recorder started.. ");
            recorder.oncompleted = function (blob, blobUrl) {
                trace("Recorder completed.. ");
                var li = document.createElement("li");
                var download = document.createElement("a");
                download.textContent = new Date();
                download.setAttribute("download", XSockets.Utils.randomString(8) + ".webm");
                download.setAttribute("href", blobUrl);
                li.appendChild(download);
                $("ul").appendChild(li);
            };
        };
        var addRemoteVideo = function (peerId, mediaStream) {
            var remoteVideo = document.createElement("video");
            remoteVideo.setAttribute("autoplay", "autoplay");
            remoteVideo.setAttribute("rel", peerId);
            attachMediaStream(remoteVideo, mediaStream);
            $(".remotevideos").appendChild(remoteVideo);
        };
        var onConnectionLost = function (remotePeer) {
            trace("onconnectionlost", arguments);
            var peerId = remotePeer.PeerId;
            var videoToRemove = $("video[rel='" + peerId + "']");
            $(".remotevideos").removeChild(videoToRemove);
        };
        var oncConnectionCreated = function () {
            console.log(arguments, rtc);
            trace("oncconnectioncreated", arguments);
        };
        var onGetUerMedia = function (stream) {
            trace("Successfully got some userMedia , hopefully a goat will appear..");
            rtc.connectToContext(); // connect to the current context?
        };
        var onRemoteStream = function (remotePeer) {
            addRemoteVideo(remotePeer.PeerId, remotePeer.stream);
            trace("Opps, we got a remote stream. lets see if its a goat..");
        };
        var onLocalStream = function (mediaStream) {
            trace("Got a localStream", mediaStream.id);
            attachMediaStream($(".localvideo video "), mediaStream);
            // if user click, video , call the recorder
            $(".localvideo video ").addEventListener("click", function () {
                recordMediaStream(rtc.getLocalStreams()[0]);
            });
        };
        var onContextCreated = function (ctx) {
            trace("RTC object created, and a context is created - ", ctx);
            rtc.getUserMedia(rtc.userMediaConstraints.hd(false), onGetUerMedia, onError);
        };
        var onOpen = function () {
            trace("Connected to the brokerController - 'connectionBroker'");
            rtc = new XSockets.WebRTC(this);
            rtc.onlocalstream = onLocalStream;
            rtc.oncontextcreated = onContextCreated;
            rtc.onconnectioncreated = oncConnectionCreated;
            rtc.onconnectionlost = onConnectionLost;
            rtc.onremotestream = onRemoteStream;
            rtc.onanswer = function (event) {
            };
            rtc.onoffer = function (event) {
            };
        };
        var onConnected = function () {
            trace("connection to the 'broker' server is established");
            trace("Try get the broker controller form server..");
            broker = ws.controller("connectionbroker");
            broker.onopen = onOpen;
        };
        ws.onconnected = onConnected;
    });
    document.addEventListener("DOMContentLoaded", main);
</script>

Na linha número 89 no meu código de caso OnrecordComplete, na verdade, anexar um link do arquivo do gravador, se você clicar nesse link, ele iniciará o download, você pode salvar esse caminho para o seu servidor como um arquivo.

O código de gravação se parece com isto

recorder.oncompleted = function (blob, blobUrl) {
                trace("Recorder completed.. ");
                var li = document.createElement("li");
                var download = document.createElement("a");
                download.textContent = new Date();
                download.setAttribute("download", XSockets.Utils.randomString(8) + ".webm");
                download.setAttribute("href", blobUrl);
                li.appendChild(download);
                $("ul").appendChild(li);
            };

O blobUrl contém o caminho. Resolvi meu problema com isso, espero que alguém ache isso útil

uniqueNt
fonte
-4

Tecnicamente, você pode usar FFMPEG no back-end para misturar vídeo e áudio

EugeneB
fonte
7
sim, mas como você os leva lá?
Eddie Monge Jr