• Publicidad

Cómo servir vídeo MP4 para HTML5

Todo lo relacionado con el desarrollo Web con Perl: desde CGI hasta Mojolicious

Cómo servir vídeo MP4 para HTML5

Notapor Abraham » 2013-08-27 12:55 @580

Tengo una página con vídeo en HTML5:
Sintáxis: [ Descargar ] [ Ocultar ]
Using html4strict Syntax Highlighting
  1. <html>
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  4. <title>Documento sin título</title>
  5. </head>
  6. <body>
  7. <video controls src="http://192.168.1.68/video.mp4">Tu navegador no soporta HTML5 </video>
  8. </body>
  9. </html>
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4

como ven es muy simple pero funciona. El problema está cuando cambia la URL del vídeo, de src="http://192.168.1.68/video.mp4" por un cgi src="http://192.168.1.68/descargas/d.pl", que usaba para servir imágenes, vídeos Flash, MP3, y distintas descargas. PERO parece no funciona con vídeo MP4.

EL d.pl es básicamente:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. use CGI qw(:standard);
  3. use CGI::Carp qw(fatalsToBrowser);
  4. use LWP::Simple;
  5. use URI::Escape;
  6. use LWP::UserAgent;
  7.  
  8. $archivo = "video.mp4";
  9. $peso    = -s $archivo;
  10.  
  11. print "Accept-Ranges:bytes\r\n";
  12. print "content-Length: 3621948\r\n";
  13. print "Content-Range:bytes 2331-3624278/3624279\r\n";
  14. print "Content-type: video/mp4\r\n";
  15. print "Last-Modified:Fri, 23 Aug 2013 16:34:09 GMT\r\n";
  16. print "ETag:\"2381da4-374d57-4e49ff9d138a7\"\r\n";
  17. print "\r\n";
  18. print "\r\n";
  19.  
  20. open IMAGE, "$archivo";
  21. my ($buff);
  22. while ( read IMAGE, $buff, 1024 ) {
  23.     print $buff;
  24. }
  25. close IMAGE;
  26.  
Coloreado en 0.003 segundos, usando GeSHi 1.0.8.4

Pero solo funciona los cinco primeros segundos del vídeo y todo pasa deprisa hasta los "4:20", el reproductor de HTML5 no detecta los 4:20, solo los 00:05...

Al parecer es la cabecera, ahí se muestra un intento de COPIA de la cabecera que manda el Apache cuando la URL del vídeo es directo.

Nuevamente, estoy perdido...
Abraham
Perlero nuevo
Perlero nuevo
 
Mensajes: 13
Registrado: 2013-07-08 19:10 @840

Publicidad

Re: Cómo servir vídeo MP4 para HTML5

Notapor explorer » 2013-08-27 14:02 @626

Antes de mirar la cabecera, insertaría estas líneas:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. binmode IMAGE;
  2. binmode STDOUT;
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
Prueba, y dinos qué tal.

Además (no lo sé), como Range está indicando que empezamos en el byte 2331 (no sé por qué) entonces necesitaremos un

seek IMAGE,2331,0;

antes de la lectura del archivo, para posicionarnos. ¿No es esto lo que quieres hacer?

Otra cosa... hay que indicar en la respuesta un código

HTTP/1.1 206 Partial content

Yo usaría la función header() del módulo CGI, ya que lo estás cargando.

(no probado)
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
print header(
    -type           => 'video/mp4',
    -status         => '206 Partial content',
    -Accept_Ranges  => 'bytes',
    -Content_Length => 3621948,
    -Content_Range  => 'bytes 2331-3624278/3624279',
    -Last_Modified  => 'Fri, 23 Aug 2013 16:34:09 GMT',
    -ETag           => '2381da4-374d57-4e49ff9d138a7',
);
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Cómo servir vídeo MP4 para HTML5

Notapor Abraham » 2013-08-27 16:51 @744

Gracias por responder...

Aclarando que la cabecera presentada es un intento de copia de lo que entrega Apache cuando es una URL directa...
(Yo tampoco sé el porqué de las cabeceras).

Probando insertar los binmode no parece afectar en lo mas mínimo, tendiendo el mismo resultado.

Aquí va el código que funciona parcialmente:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. use CGI       qw(:standard);
  3. use CGI::Carp qw(fatalsToBrowser);
  4. use LWP::Simple;
  5. use URI::Escape;
  6. use LWP::UserAgent;
  7.  
  8.         $archivo = "hola.mp4";
  9.         $peso    = -s $archivo;
  10.  
  11.         print "content-Length: ".$peso."\r\n";
  12.         print "Content-type: video/mp4\r\n";
  13.         print "\r\n";
  14.  
  15.         open IMAGE, "$archivo";
  16.                 my ($buff);
  17.                 while ( read IMAGE, $buff, 1024 ) {
  18.                         print $buff;
  19.                 }
  20.         close IMAGE;
  21.  
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4


Aquí con las modificaciones:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. use CGI       qw(:standard);
  3. use CGI::Carp qw(fatalsToBrowser);
  4. use LWP::Simple;
  5. use URI::Escape;
  6. use LWP::UserAgent;
  7.  
  8.         $archivo = "hola.mp4";
  9.         $peso    = -s $archivo;
  10.  
  11.         print header(
  12.      -type           => 'video/mp4',
  13.      -Content_Length => $peso ,
  14.     );
  15.  
  16.         open IMAGE, "$archivo";
  17.                 binmode IMAGE;
  18.                 binmode STDOUT;
  19.                 my ($buff);
  20.                 while ( read IMAGE, $buff, 1024 ) {
  21.                         print $buff;
  22.                 }
  23.         close IMAGE;
  24.  
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


Me he dado cuenta que la etiqueta <VIDEO> de HTML5 hace la petición del vídeo 3 veces. En la primera solo descarga 13 bytes; en la segunda 3.4 MB; y la tercera 2 MB (al parecer es por el PRELOAD del vídeo) y este tercero llega a descargarse por completo 3.5 MB y es de este tercero que COPIÉ las cabeceras.

Unas imágenes de Chrome, usando la URL DIRECTA del vídeo:
Imagen
se puede ver que la página cargó 3 veces el vídeo (no sé por qué es este comportamiento)
Imagen
y en esta imagen están las cabeceras copiadas.

Como podemos ver en las imágenes se carga por completo porque el tiempo se marca como 4:20 pero con CGI solo carga el vídeo dos veces pero aun cargando por completo el tiempo se marca como 00:05; en estos 5 segundos el vídeo va bien; luego pasando los 5 segundos el vídeo va sumamente corrido.

Aquí esta la imagen de la prueba:
Imagen

¡Y AHORA QUÉ PUEDE SER!

Mientras estaba redactando la respuesta, y como estaba mandando imágenes de Chrome hice la prueba en Firefox y todo FUNCIONA CORRECTAMENTE. ¿Qué puede estar mal en Chrome? Uso Chrome por la herramienta que se muestran en las imágenes, pero ahora descubro que Chrome es el causante de este dolor de cabeza... ¿Quién de los dos maneja adecuadamente la etiqueta <VIDEO> de HTML5?
Abraham
Perlero nuevo
Perlero nuevo
 
Mensajes: 13
Registrado: 2013-07-08 19:10 @840

Re: Cómo servir vídeo MP4 para HTML5

Notapor Abraham » 2013-08-27 20:44 @906

Al parecer el problema está en que el Chrome hace peticiones de rangos cuando lo ve "conveniente" y esto mi pequeño CGI no puede abastecerlo, no comprendo bien, pero pude satisfacer las 3 peticiones principales (obviamente con los datos sugeridos gracias a explorer), y ahora está funcionando al menos en la primera etapa, ya que si muevo el progreso del vídeo, el Chrome hace peticiones de rangos que falta satisfacer... Muestro mi intento de solución, espero mejorarlo:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. use CGI qw(:standard);
  3. use CGI::Carp qw(fatalsToBrowser);
  4. use LWP::Simple;
  5. use URI::Escape;
  6. use LWP::UserAgent;
  7.  
  8. $archivo = "hola.mp4";
  9. $peso    = -s $archivo;
  10.  
  11. if ( $ENV{'HTTP_RANGE'} eq "" ) {
  12.  
  13.     print header(
  14.         -type           => 'video/mp4',
  15.         -Content_Length => $peso
  16.     );
  17.  
  18.     open IMAGE, "$archivo";
  19.     binmode IMAGE;
  20.     binmode STDOUT;
  21.     my ($buff);
  22.  
  23.     #read IMAGE, $buff,2000000;
  24.     while ( read IMAGE, $buff, 211024 ) {
  25.         print $buff;
  26.     }
  27.     close IMAGE;
  28. }
  29. else {
  30.     if ( $ENV{'HTTP_RANGE'} =~ m/bytes=0-/i ) {
  31.  
  32.         print header(
  33.             -type           => 'video/mp4',
  34.             -Content_Length => $peso,
  35.             -Accept_Ranges  => 'bytes',
  36.             -Content_Range  => 'bytes 0-3624278/3624279'
  37.         );
  38.         open IMAGE, "$archivo";
  39.         my ($buff);
  40.         read IMAGE, $buff, 3624278;
  41.  
  42.         #while ( read IMAGE, $buff, 3624278 ) {
  43.         print $buff;
  44.  
  45.         #}
  46.         close IMAGE;
  47.  
  48.     }
  49.     else {
  50.         print header(
  51.             -type           => 'video/mp4',
  52.             -status         => '206 Partial content',
  53.             -Content_Length => '3621948',
  54.             -Accept_Ranges  => 'bytes',
  55.             -Content_Range  => 'bytes 2331-3624278/3624279'
  56.         );
  57.         open IMAGE, "$archivo";
  58.         my ($buff);
  59.         seek IMAGE, 2331, 0;
  60.         read IMAGE, $buff, 3624278;
  61.  
  62.         #while ( read IMAGE, $buff, 3624278 ) {
  63.         print $buff;
  64.  
  65.         #}
  66.         close IMAGE;
  67.     }
  68. }
  69.  
Coloreado en 0.003 segundos, usando GeSHi 1.0.8.4
Abraham
Perlero nuevo
Perlero nuevo
 
Mensajes: 13
Registrado: 2013-07-08 19:10 @840

Re: Cómo servir vídeo MP4 para HTML5

Notapor explorer » 2013-08-28 04:24 @225

Efectivamente: el distinto comportamiento de los dos navegadores es por el controlador (driver) de vídeo que utilizan. En un caso, solicita varios rangos distintos (quizás para ver primero las cabeceras y luego hacer una prueba de búfer), y en otro caso, se lo baja entero.

El cgi, entonces, debe estar preparado para responder a las peticiones de Ranges:.

En este hilo sobre Ubuntu he encontrado una solución completa, que te puede servir de inspiración. Si hay algo del código que no entiendes, te lo aclaramos. Fíjate que el autor utiliza una cabecera llamada 'Content_disposition', lo cual me sorprende, pues parece que está retransmitiendo un archivo como si fuera un adjunto (como en el correo), por lo que, entiendo, el navegador debería estar mostrando la opción de grabar el archivo a disco.

En fin, cuestión de probar.
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. #
  3. # MythWeb Streaming/Download module
  4. #
  5. # @url       $URL: http://svn.mythtv.org/svn/trunk/mythplu ... eam_raw.pl $
  6. # @date      $Date: 2010-05-31 20:52:27 +0100 (Mon, 31 May 2010) $
  7. # @version   $Revision: 24917 $
  8. # @author    $Author: kormoc $
  9. #
  10.  
  11. $| = 1;
  12.  
  13. use HTTP::Date;
  14.  
  15. # File size
  16. my $size = -s $filename;
  17.  
  18. # Zero bytes?
  19. if ( $size < 1 ) {
  20.     print header(), "$basename is an empty file.";
  21.     exit;
  22. }
  23.  
  24. # File type
  25. my $type   = 'text/html';
  26. my $suffix = '';
  27. if ( $basename =~ /\.mpe?g2?$/ ) {
  28.     $type   = 'video/mpeg';
  29.     $suffix = '.mpg';
  30. }
  31. elsif ( $basename =~ /\.nuv$/ ) {
  32.     $type   = 'video/nuppelvideo';
  33.     $suffix = '.nuv';
  34. }
  35. elsif ( $basename =~ /\.avi$/ ) {
  36.     $type   = 'video/mpeg';
  37.     $suffix = '.avi';
  38. }
  39. elsif ( $basename =~ /\.mkv$/ ) {
  40.     $type   = 'video/mpeg';
  41.     $suffix = '.mkv';
  42. }
  43. else {
  44.     print header(), "Unknown video type requested:  $basename\n";
  45.     exit;
  46. }
  47.  
  48. # Download filename
  49. my $name = $basename;
  50. if ( $name =~ /^\d+_\d+\.\w+$/ ) {
  51.     if ( $title =~ /\w/ ) {
  52.         $name = $title;
  53.         if ( $subtitle =~ /\w/ ) {
  54.             $name .= " - $subtitle";
  55.         }
  56.     }
  57.     $name .= $suffix;
  58. }
  59.  
  60. # Open the file for reading
  61. unless ( sysopen DATA, $filename, O_RDONLY ) {
  62.     print header(), "Can't read $basename:  $!";
  63.     exit;
  64. }
  65.  
  66. # Binmode, in case someone is running this from Windows.
  67. binmode DATA;
  68.  
  69. my $start      = 0;
  70. my $end        = $size;
  71. my $total_size = $size;
  72. my $read_size  = 1024;
  73. my $mtime      = ( stat($filename) )[9];
  74.  
  75. # Handle cache hits/misses
  76. if ( $ENV{'HTTP_IF_MODIFIED_SINCE'} ) {
  77.     my $check_time = str2time( $ENV{'HTTP_IF_MODIFIED_SINCE'} );
  78.     if ( $mtime <= $check_time ) {
  79.         print header(
  80.             -Content_type => $type,
  81.             -status       => "304 Not Modified"
  82.         );
  83.         exit;
  84.     }
  85. }
  86.  
  87. # Requested a range?
  88. if ( $ENV{'HTTP_RANGE'} ) {
  89.  
  90.     # Figure out the size of the requested chunk
  91.     ( $start, $end ) = $ENV{'HTTP_RANGE'} =~ /bytes\W+(\d*)-(\d*)\W*$/;
  92.     if ( $end < 1 || $end > $size ) {
  93.         $end = $size;
  94.     }
  95.     $size = $end - $start + 1;
  96.     if ( $read_size > $size ) {
  97.         $read_size = $size;
  98.     }
  99.     print header(
  100.         -status              => "206 Partial Content",
  101.         -type                => $type,
  102.         -Content_length      => $size,
  103.         -Accept_Ranges       => 'bytes',
  104.         -Content_Range       => "bytes $start-$end/$total_size",
  105.         -Last_Modified       => time2str($mtime),
  106.         -Content_disposition => " attachment; filename=\"$name\""
  107.     );
  108. }
  109. else {
  110.     print header(
  111.         -type                => $type,
  112.         -Content_length      => $size,
  113.         -Accept_Ranges       => 'bytes',
  114.         -Last_Modified       => time2str($mtime),
  115.         -Content_disposition => " attachment; filename=\"$name.$suffix\""
  116.     );
  117. }
  118.  
  119. # Seek to the requested position
  120. sysseek DATA, $start, 0;
  121.  
  122. # Print the content to the browser
  123. my $buffer;
  124. while ( sysread DATA, $buffer, $read_size ) {
  125.     print $buffer;
  126.     $size -= $read_size;
  127.     if ( $size <= 0 ) {
  128.         my $fileSize = -s $filename;
  129.         my $filePos  = tell DATA;
  130.         if ( ( $fileSize - $filePos ) > 0 ) {
  131.             $size = $fileSize - $filePos;
  132.         }
  133.         else {
  134.             last;
  135.         }
  136.     }
  137.     if ( $size < $read_size ) {
  138.         $read_size = $size;
  139.     }
  140.     if ( $read_size < 0 ) {
  141.         $read_size = 0;
  142.     }
  143. }
  144. close DATA;
  145.  
  146. 1;
  147.  
Coloreado en 0.004 segundos, usando GeSHi 1.0.8.4
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Cómo servir vídeo MP4 para HTML5

Notapor Abraham » 2013-08-28 15:56 @705

El comportamiento del navegador Chrome y todos los que se basan en Chromium (creo que es así) lo considero un GRAN error, si bien en alguna parte del mundo el ANCHO de banda casi, casi, lo regalan, existe una gran mayoría que tienen limitado acceso al mismo, y por eso no puede ser que se cargue repetidas veces el mismo vídeo.

Sobre el código sugerido, aunque no entiendo algunas partes, entiendo lo que se quiere hacer, esto me servirá un poco más adelante, ya que considero importante adecuar la página html en primer lugar, cambiar la forma en que se va a pedir el vídeo, ya que no todos los vídeos los tengo en mi servidor, es más parecido a un Spider de YouTube con caché de vídeos y mientras descargo el vídeo pretendía servir el vídeo, y con esto de los rangos no puedo servir rangos de lo que no tengo.

Por el momento mi batalla es con HTML5, Ajax y jQuery.

Gracias, explorer, por el interés de siempre, estaré por aquí un poco más adelante.
Abraham
Perlero nuevo
Perlero nuevo
 
Mensajes: 13
Registrado: 2013-07-08 19:10 @840

Re: Cómo servir vídeo MP4 para HTML5

Notapor explorer » 2013-08-28 16:19 @722

Es que, precisamente, lo hacen así, para que el controlador de vídeo tenga información suficiente para calcular el ancho de banda disponible, y de ahí calcular cuántos segundos de búfer debe almacenar antes de comenzar la reproducción. Todo ello va encaminado para que el usuario no vea cortes. O vea los mínimos cortes.

En cuanto a rangos y demás, piensa en el ejemplo de Youtube, que permite crear enlaces a vídeos, en un determinado punto de la reproducción. Eso permite a los usuarios intercambiarse enlaces de vídeos apuntando al momento justo en que el gato hace su monería. :lol:

En el código que te he puesto, sí que está contemplado todos los casos: existir o no el archivo que nos piden, y ajustar el puntero de lectura según el rango solicitado. Si no pide rango, se manda todo, claro.
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Cómo servir vídeo MP4 para HTML5

Notapor Abraham » 2013-08-29 10:42 @487

Pero en el código, al no existir el archivo, solo manda un mensaje:

unless ( sysopen DATA, $filename, O_RDONLY ) {

print header(), "Can't read $basename: $!";

exit;

}

En esa parte el código debería ser capaz de buscar el vídeo en Youtube, empezar la descarga y al mismo tiempo servir la descarga parcial. ¡OJO!, que esto lo tengo solucionado pero con VÍDEOS de Flash y un reproductor Flash en el lado del cliente, pero esto del HTML5 y su vídeo me ha dado nuevos retos que pienso solucionarlo con Ajax interactuando con el servidor y controlando al reproductor del vídeo, todo internamente.
Abraham
Perlero nuevo
Perlero nuevo
 
Mensajes: 13
Registrado: 2013-07-08 19:10 @840


Volver a Web

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 1 invitado