Comparte este artículo

WebRTC Streaming: Chrome to ChromeHoy vamos a llevar a cabo un experimento que nos permitirá enviar un vídeo en streaming entre uno o varios navegadores Web. En este ejemplo con WebRTC, la aplicación Web nos permitirá cargar un fichero de vídeo en el navegador, dividirlo en bloques, enviar los bloques a otros navegadores y reproducir el vídeo en todos ellos. ¡Todo en tiempo real!

 

 

Para conseguir esto, que puedes probar en http://www.p2psp.org/webrtc-streaming/, necesitamos echar un vistazo a los elementos Datachannel de la API WebRTC y Media Source Extensions (MSE). El primero nos permite compartir datos binarios entre los navegadores a través de peer to peer y el segundo nos permite enviar los trozos de vídeo directamente a la etiqueta <video> mediante un modelo de buffering.

Nota: Puedes ver otro ejemplo de Datachannel en Entendiendo WebRTC PeerConnection con un chat multiusuario

Leyendo el fichero desde el Navegador Web

Para cargar el vídeo al navegador vamos a hacer uso de la API de archivos que nos permitirá cargar en memoria trozos del fichero de vídeo de una longitud determinada e ir enviándolos al resto de navegadores para que puedan reproducirlo en tiempo real. 

function readBlob(size) {
 
    var files = document.getElementById('files').files;
    if (!files.length) {
      alert('Please select a file!');
      return;
    }
 
    var file = files[0];
    var start = size;
    var stop = file.size - 1;
    if (size>file.size)
  return;
    var reader = new FileReader();
 
    //Se dispara cuando se ha completado la solicitud.
    reader.onloadend = function(evt) {
      //Es necesario comprobar el estado
      if (evt.target.readyState == FileReader.DONE) { // DONE == 2
  handleChunk(evt.target.result); //Enviamos el bloque de video al reproductor (etiqueta video)
  sendChatMessage(evt.target.result); //Comparitmos el bloque de vídeo con el resto de navegadores (via DataChannel)        
      }
     };
 
     reader.onload = function(e) {
    feedIt(); //Continuamos leyendo
     };
 
     //Extraemos un trozo de vídeo (1024 bytes)
     var blob;
     blob = file.slice(start, start + 1024)
     reader.readAsArrayBuffer(blob);
 
  }
 
  //Solicitar leer los siguientes 1024 bytes.
  function feedIt(){
  readBlob(chunksize);
  chunksize+=1024;
  }
 

Alimentando la etiqueta vídeo mediante MSE

En la versión regular de Chrome ya está disponible Media Source Extensions aunque por el momento sólo permite el uso de vídeos en formato WebM. Veámos como funciona:

function handleChunk(chunk){
    //Almacenamos los bloques en un buffer temporal
  queue.push(chunk);
  current++;
 
    //Cargamos la etiqueta video con mediaSource
    if (current==1){
    video.src = window.URL.createObjectURL(mediaSource);
    video.pause();
  }
 
    //Alimentamos Media Source
  if (current>=2){ 
    appendNextMediaSegment(mediaSource);
  }
 
    //Después de un pequeño buffer comenzamos a reproducir
  if (current==128){
    video.play();      
  }
 
 
}
 
function onSourceOpen(videoTag, e) {
    var mediaSource = e.target;
 
    if (mediaSource.sourceBuffers.length > 0){
    console.log("SourceBuffer.length > 0");
         return;
  }
 
    //Indicamos el tipo de contenido de mediasource
    var sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
 
    //Obtenemos el primer bloque en el buffer (cabecera?)
    var initSegment = new Uint8Array(queue.shift());
 
    if (initSegment.length==0) {
    //Si el bloque está vacío hay un error
      mediaSource.endOfStream("network");
      return;
    }
 
    // Añadimos el primer bloque a Media Source
    var firstAppendHandler = function(e) {
      var sourceBuffer = e.target;
      sourceBuffer.removeEventListener('updateend', firstAppendHandler);
      appendNextMediaSegment(mediaSource);
    };
 
    sourceBuffer.addEventListener('updateend', firstAppendHandler);
    sourceBuffer.addEventListener('update', onProgress.bind(videoTag, mediaSource));
    sourceBuffer.appendBuffer(initSegment);
  }
 
function appendNextMediaSegment(mediaSource) {
    //Comprobamos si media source esta listo
    if (mediaSource.readyState == "closed"){
    console.log("readyState is closed");    
    return;
  }
 
    // NOs aseguramos que el bloque anterior no está pendiente
    if (mediaSource.sourceBuffers[0].updating){
      return;
  }
 
  //No hay nada en la cola?
    if (queue.length==0) {
      return;
    }
 
    //Enviamos el siguiente bloque a Media Source
    var mediaSegment = new Uint8Array(queue.shift());
    mediaSource.sourceBuffers[0].appendBuffer(mediaSegment);
 
  }
 
  //Cada vez que Source Buffer actualize intentamos añadir el siguiente bloque
  function onProgress(mediaSource,e) {
     appendNextMediaSegment(mediaSource);
  }

Enviando los bloques al resto de peers

Partiendo del ejemplo del Chat con WebRTC, esta parte es muy sencilla ya que sólo hay que controlar lo que se recibe y se envía desde los DataChannels:

function setupChat(i) {
    channel[i].onopen = function () {
      btnStream.disabled=false;
    };
 
    //Cada vez que recibimos un bloque lo enviamos al reproductor
    channel[i].onmessage = function (evt) {
       handleChunk(evt.data);
    };
}
 
//Enviamos el bloque al resto de navegadores (peers)
function sendChatMessage(chunk) {
  for (i in peerlist){  
    if (peerlist[i]!=idpeer){
      try{
        channel[peerlist[i]].send(chunk);
      }catch(e){
        console.log(i+" said bye!");
      }
 
    }
  }
}
 

Nota: Puedes encontrar el código completo de este experimento en Streaming de vídeo con WebRTC. Recuerda que es un experimento y puede contener errores.

Este experimento, por el momento, sólo funciona en Google Chrome (incluido Chrome for Android)

¡Participa!, si tienes dudas o sugerencias déjalas en los comentarios.

Comparte este artículo