Cómo grabar cámara web y audio usando webRTC y una conexión Peer basada en servidor

90

Me gustaría grabar la cámara web y el audio de los usuarios y guardarlo en un archivo en el servidor. Estos archivos podrían luego ser entregados a otros usuarios.

No tengo problemas con la reproducción, sin embargo, tengo problemas para grabar el contenido.

Tengo entendido que la .record()función getUserMedia aún no se ha escrito, solo se ha hecho una propuesta hasta ahora.

Me gustaría crear una conexión entre pares en mi servidor usando PeerConnectionAPI. Entiendo que esto es un poco hack, pero creo que debería ser posible crear un par en el servidor y registrar lo que envía el cliente-par.

Si esto es posible, debería poder guardar estos datos en flv o en cualquier otro formato de video.

Mi preferencia es grabar la cámara web + audio del lado del cliente, para permitir que el cliente vuelva a grabar videos si no les gustó su primer intento antes de cargarlos. Esto también permitiría interrupciones en las conexiones de red. He visto un código que permite la grabación de 'imágenes' individuales desde la cámara web enviando los datos al lienzo; eso es genial, pero también necesito el audio.

Aquí está el código del lado del cliente que tengo hasta ahora:

  <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
fuente
Tengo el mismo problema. ¿El método getRecordedData () funciona para usted? No está en mis navegadores recién actualizados.
Firas
No, también probé 'Google Canary'.
Dave Hilditch
Sí, lo estoy vigilando de cerca: actualizaré este hilo cuando haya una solución adecuada.
Dave Hilditch
2
si tiene la solución de la pregunta anterior, por favor compártala conmigo, gracias
Muhammad
2
¿Alguien ha podido obtener los bytes de MediaStream a través de alguna magia RTC del lado del servidor?
Vinay

Respuestas:

44

Definitivamente deberías echar un vistazo a Kurento . Proporciona una infraestructura de servidor WebRTC que le permite grabar desde una fuente WebRTC y mucho más. También puede encontrar algunos ejemplos de la aplicación que está planeando aquí . Es realmente fácil agregar capacidades de grabación a esa demostración y almacenar el archivo multimedia en un URI (disco local o donde sea).

El proyecto tiene licencia LGPL Apache 2.0


EDITAR 1

Desde esta publicación, hemos agregado un nuevo tutorial que muestra cómo agregar la grabadora en un par de escenarios

Descargo de responsabilidad: soy parte del equipo que desarrolla Kurento.

igracia
fuente
2
@Redtopia En algunas pruebas de carga recientes pudimos obtener 150 conexiones one2one de webrtc en un i5 / 16GB RAM. Puede esperar que estos números sean mejores en el futuro, pero no espere milagros: hay mucho cifrado para SRTP, y eso es exigente. Estamos investigando el cifrado / descifrado acelerado por hardware, y los números aumentarán, y aunque no puedo asegurarles cuánto mejor será hasta que lo
probemos
2
@ user344146 Probablemente fui yo quien respondía. ¿Te importaría compartir un enlace a esa publicación? Si obtuvo esa respuesta, probablemente sea porque preguntó algo que ya estaba allí o en la lista. Parece que estabas intentando compilar una versión SNAPSHOT. Esos artefactos no se publican en Central, por lo que puede consultar una versión de los tutoriales o utilizar nuestro repositorio de desarrollo interno. Esto ha sido respondido en la lista muchas veces, hay una entrada en la documentación sobre cómo trabajar con versiones de desarrollo ... Nos tomamos el tiempo para escribirlo, por lo que sería bueno que se tomara el tiempo para leerlo.
igracia
2
Solo estoy usando Kurento para hacer tal grabación. No es complicado, pero necesito un poco de tiempo para entender el concepto, porque algunos documentos son realmente malos, y encontrar lo que puedo enviar a kurento, la descripción de eventos, etc., puede ser a veces muy frustrante. Pero de todos modos, un proyecto abierto como este es realmente un gran trabajo y vale la pena usarlo. Kurento solo funciona en linux (la versión de Windows no es oficial y no funciona con la funcionalidad completa).
Krystian
1
Encontré respuestas para las preguntas anteriores (publicadas aquí para otros), Kurento actualmente es compatible con JDK 7.0, no es que tenga que depender de Ubuntu 14.04, también debería ser compatible con versiones posteriores, pero Kurento no se ha probado oficialmente en otras versiones de Ubuntu / otra versión de Linux. Además, Kurento lanza versiones de 64 bits como disponibles para instalación, sin embargo, puede instalar la versión de 32 bits del servidor, pero primero debe compilarla.
Bilbo Baggins
1
Desafortunadamente, como dije en mi respuesta, el desarrollo de Kurento se ralentizó severamente después de la adquisición de Twilio. Recomiendo usar Janus en su lugar.
Jamix
17

Por favor, consulte el RecordRTC

RecordRTC tiene licencia MIT en github .

Dmitry
fuente
2
Eso es bastante impresionante, mi pregunta: ¿se puede grabar video y audio juntos (en vivo un video real en lugar de dos cosas separadas?)
Brian Dear
De acuerdo, increíble, pero parece que solo registra los datos por separado.
Dave Hilditch
3
@BrianDear hay un RecordRTC-together
Mifeng
2
Este enfoque funciona a través de Whammy.js en Chrome. Esto es problemático ya que la calidad tiende a ser mucho menor que la emulación que proporciona Whammy por la falta de un MediaStreamRecorder de Chrome. Lo que esencialmente sucede es que WhammyRecorder apunta una etiqueta de video a la URL del objeto MediaStream y luego toma instantáneas webp de un elemento de lienzo a una cierta velocidad de cuadros. Luego usa Whammy para juntar todos esos cuadros en un video webm.
Vinay
15

Creo que usar kurento u otras MCU solo para grabar videos sería un poco exagerado, especialmente considerando el hecho de que Chrome tiene soporte para MediaRecorder API de v47 y Firefox desde v25. Entonces, en este cruce, es posible que ni siquiera necesite una biblioteca js externa para hacer el trabajo, pruebe esta demostración que hice para grabar video / audio usando MediaRecorder:

Demostración : funcionaría en Chrome y Firefox (se omitió intencionalmente al enviar blob al código del servidor)

Fuente del código de Github

Si ejecuta Firefox, puede probarlo aquí mismo (Chrome necesita 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
fuente
Chrome 49 es el primero en admitir la API MediaRecorder sin la bandera.
Octavian Naicu
7

sí, como entendió, MediaStreamRecorder no está implementado actualmente.

MediaStreamRecorder es una API de WebRTC para grabar transmisiones getUserMedia (). Permite que las aplicaciones web creen un archivo a partir de una sesión de audio / video en vivo.

alternativamente, puede hacer como esto http://ericbidelman.tumblr.com/post/31486670538/creating-webm-video-from-getusermedia pero falta parte del audio.

Konga Raju
fuente
1
Sí, y puede capturar el archivo de audio, enviarlo al servidor y combinarlos allí para crear un archivo de video real en el lado del servidor. Pero esta solución puede ser muy lenta en el lado del cliente dependiendo de la configuración de su computadora, ya que tiene que crear archivos de imagen usando un lienzo Y capturar el audio, y todo esto en la RAM ... Por cierto, el equipo de Firefox está trabajando en ello , así que con suerte lo lanzarán pronto.
Firas
4

Puede utilizar RecordRTC-together , que se basa en RecordRTC.

Admite la grabación de video y audio juntos en archivos separados. Necesitará una herramienta como ffmpegpara fusionar dos archivos en uno en el servidor.

Mifeng
fuente
2
Esta es una solución de navegador, no del lado del servidor.
Brad
2

Web Call Server 4 puede grabar audio y video de WebRTC en un contenedor WebM. La grabación se realiza utilizando el códec Vorbis para audio y el códec VP8 para video. Los códecs iniciales de WebRTC son Opus o G.711 y VP8. Por lo tanto, la grabación del lado del servidor requiere la transcodificación del lado del servidor de Opus / G.711 a Vorbis o la transcodificación VP8-H.264 si es necesario utilizar otro contenedor, es decir, AVI.

Bob42
fuente
es esto comercial?
Stepan Yakovenko
0

Para que conste, tampoco tengo suficiente conocimiento sobre esto,

Pero encontré esto en Git hub-

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

En la línea número 89 en mi caso, el código OnrecordComplete en realidad agrega un enlace del archivo de la grabadora, si hace clic en ese enlace, se iniciará la descarga, puede guardar esa ruta en su servidor como un archivo.

El código de grabación se parece a esto

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);
            };

BlobUrl contiene la ruta. Resolví mi problema con esto, espero que alguien lo encuentre útil

UniqueNt
fuente
-4

Técnicamente, puede usar FFMPEG en el backend para mezclar video y audio

EugeneB
fuente
7
sí, pero ¿cómo los llevas allí?
Eddie Monge Jr