¿Te gustaría colaborar en un proyecto de software libre? Conoce P2PSP

Streaming de vídeo entre navegadores vía WebRTC Datachannel y Media Source Extensions

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

Comentarios   

0 # Jose 10-06-2014 23:01
Hola Cristóbal, felicitarte por este proyecto tan interesante y por los reconocimientos que veo que estás recibiendo.

Me he descargado el código de WebRTCStreaming y lo he montado en un servidor local XAMPP. Al ejecutarlo (localhost/webrtc/peer.html) no me da código para conectar. ¿Sabes porqué puede ser?

Un saludo.
Responder | Responder con una citación | Citar
0 # Cristóbal Medina 10-06-2014 23:25
Hola Jose,
En primer lugar, gracias por visitar el blog y por el interés en el proyecto. Espero que te resulte útil todo lo que encuentres por aquí.

Con respecto a tu pregunta, el problema que comentas seguramente se deba a que: (1) no has lanzado el servidor de señalización (server.py) y/o (2) la dirección que apunta a dicho servidor en el fichero peer.js (la primera línea de código) es incorrecta, es decir, no está apuntando al signaling server.

Aún no tengo documentado todo esto, pero si tienes cualquier duda o pregunta, estaré encantado de ayudarte.

Un Saludo!
Responder | Responder con una citación | Citar
+2 # Jose 11-06-2014 15:06
Efectivamente, los problemas que tenía era por los 2 puntos que has señalado. A continuación voy a recopilar todos los pasos que he ejecutado para tener la aplicación ejecutándose en mi entorno local.

Parto de un servidor local XAMPP instalado en una máquina con Windows 7.

Primero apunté a la dirección correcta en el fichero peer.js, quedando de la siguiente forma:

var signalingChanne l = new WebSocket("ws://127.0.0.1:9876/");

Al tratarse de un servidor local, he puesto la dirección IP de loopback.

Respecto al lanzamiento del servidor de señalización, no tenía Python instalado, por lo que me he descargado la versión 2.7.

Como estamos en un entorno Windows, para que el comando "python" funcione independienteme nte de la ruta en que lo lancemos, tendremos que poner en la variable del sistema PATH (dentro de panel de Control, Sistema, Configuración avanzada del sistema, Variables de entorno...), lo siguiente:
C:\Python27;C:\Python27\Tools\Scripts

Y en la variable del sistema PATHTEXT, lo siguiente: .PY;.PYW

Con esto, ya estamos en condiciones de ejecutar "python server.py" para lanzar el servidor de señalización.

La primera vez que lo he intentado me ha fallado por el siguiente error:

File "server.py", line 28, in import simplejson as json ImportError: No module named simplejson

Efectivamente, en la línea 28 del fichero server.py aparecía:

import simplejson as json

pero buscando en la red, he encontrado que desde la versión 2.6 de Python, simplejson ha sido sustituido por json. Por tanto, he cambiado la línea 28 de server.py por la siguiente:

import json

Luego se ha ejecutado correctamente python server.py

Et Voilà!, ya he obtenido código para el receptor y he conseguido hacer streaming navegador a navegador del vídeo de big buck bunny en dos sesiones de Google Chrome, usando un servidor local XAMPP en entorno Windows.

Por último, no olvidéis lanzar el script server.py siempre que queráis probar y hayáis reiniciado Windows, ya que no he conseguido ponerlo como un servicio (aunque podemos crear un bat para lanzar el comando).
Responder | Responder con una citación | Citar
0 # Felipe 27-06-2014 15:41
Hola muchas gracias por la info!!! ¿cómo se pudiera transmitir desde una webcam?

Saludos"""
Responder | Responder con una citación | Citar
0 # Cristóbal Medina 29-06-2014 11:22
Hola Felipe,
Para transmitir desde la webcam con WebRTC no es necesario usar Datachannel. Bastaría con hacer uso de PeerConnection y MediaStream. Puedes encontrar un ejemplo aquí: goo.gl/OB9Har

Gracias por visitar el blog,
Un Saludo!
Responder | Responder con una citación | Citar
0 # Luigi 25-02-2015 20:22
Hi, Christopher!
Thank you for your work and congratulations!
I've done all the steps written by Jose but I still cannot make the website work.

My configuration is:
- XAMPP: it serves your site (html, js, py...) on 80 port at 127.0.0.1\peer.html
- Stuntman: it is a server running on localhost for Stun (www.stunprotocol.org/)

Before access to 127.0.0.1/peer.html I run server.py by double clicking on it and also run the Stuntman server. But I cannot see the ID when I click on the "Sender" button in Chrome

Note that:
1) It doesn't work even if i use the default Stun server (google)
2) Stuntman runs perfectly (I have made a test)
3) Lines modified by me in peer.js are:
var signalingChanne l = new WebSocket("ws:/ /127.0.0.1:9876 /");
var configuration = {iceServers: [{ url: 'stun:127.0.0.1 :3478' }]};

Thank you for the help! :)
Responder | Responder con una citación | Citar
0 # Cristóbal Medina 26-02-2015 11:56
Hi Luigi,

I think the problem is that server.py is not working properly. Don't worry about the Stun server, it doesn't seem to have any connection with the problem.

On the one hand when you run server.py:
Can you do it from the console? What info can you see?

On the other hand, when you access to 127.0.0.1/peer. html in Chrome, What info can you see in the console (F12 to show it)?

Thanks for visiting the blog!
Responder | Responder con una citación | Citar
0 # Luigi 26-02-2015 13:03
Hi, thank you for the answer!
I've seen by using Chrome console (F12) that the peer.js file loaded was an older version than the actual on my hard disk!
I've cleaned cache with Ccleaner and now all works.

Just a trick that could be helpful: if you want that an host on your LAN could be able to see the stream of an other host on your LAN, you must specifie in the peer.js file:

var signalingChanne l = new WebSocket("ws:/ /myip:9876 /");

where myip is not 127.0.0.1 but the one associated to your network card. You can find it by writing ipconfig in cmd.

Thank you so much! :D
Responder | Responder con una citación | Citar
0 # Javier 01-07-2015 12:28
Hola Cristobal, antes de nada gracias por tu aportacion a este tema.
Estoy intentando aprender sobre la utilizacion del webRTC y algo he avanzado.
Pero me encuentro con un problema que me para todo el desarrollo, es que cuando intento crear la comunicacion entre dos paginas web utilizando este sistema (uno es un portatil conectado a ADSL con un router de telefonica y el otro es un telefono movil que usa su propia linea de datos ) ambas paginas no llegan a conectarse. En cambio si ambos equipos si estan conectados a un router si lo consigo. Estoy transmitiendo video con la camara del telefono movil
¿Puedes orientarme a que es debido el fallo de conexion?.
EL error que retornan peer.js es un error no definido.
Responder | Responder con una citación | Citar
0 # Cristóbal Medina 01-07-2015 13:36
Hola Javier,
El problema seguramente está en que cuando usas la línea de datos de telefonía móvil el navegador se encuentra detrás de un NAT simétrico que impide la conexión punto a punto. Podrías "resolver"¹ este problema usando un servidor TURN. Solo tendrías que indicar la URL (y los credenciales si es el caso) en iceServer al igual que se hace con el servidor STUN.

Espero que te sirva de ayuda. Por cierto, no dudes en compartir con nosotros tu proyecto ;-)

Muchas gracias por visitar el blog.
Saludos.

¹ Pongo resolver entre comillas porque es un parche, realmente TURN actúa en modo cliente/servido r cuando el p2p no es posible.
Responder | Responder con una citación | Citar
0 # Felipe 27-08-2015 23:09
Hola Cristóbal,
Primero gracias por compartir tu experiencia con todos y por responder los comentarios.
Sabes me he pasado de vueltas configurando un servidor TURN en amazon, a mi entender está 100% operativo, pero no logro establecer una comunicación con video desde dos conexiones distintas.
Según este sitio que encontré de prueba el servidor está funcionando:
webrtc.github.io/.../...

Estos son los datos del servidor TURN
52.27.48.135:3478?transport=udp
52.27.48.135:3478?transport=tcp
username1 / password1

No sé si es problema del socket o algo.

Me puedes guiar?

Saludos,
Felipe
Responder | Responder con una citación | Citar
0 # Cristóbal Medina 28-08-2015 16:21
Hola Felipe,

Acabo de hacer una prueba usando tu servidor TURN y me ha funcionado correctamente :)

Simplemente he sustituido la linea de configuración del servidor que estaba usando por el tuyo, quedando tal que así:

var configuration ={"iceServers": [{"url":"turn::3478", "credential":"password1"}]};

Comprueba que tu código JavaScript es correcto y si te sigue dando problemas no dudes en comentarlo.

Espero que te sea de utilidad.
Gracias por visitar el blog.
Saludos!
Responder | Responder con una citación | Citar

Escribir un comentario


Código de seguridad
Refescar

Suscripción RSS

feed-image Blog