tutorial nodejs node example javascript html5 node.js video

javascript - nodejs - Transmitir un archivo de video a un reproductor de video html5 con Node.js para que los controles de video sigan funcionando.



video streaming nodejs (2)

El encabezado Accept Ranges (el bit en writeHead() ) es necesario para que los controles de video HTML5 funcionen.

Creo que en lugar de enviar el archivo completo a ciegas, primero debes verificar el encabezado Accept Ranges en SOLICITUD, luego leer y enviar ese bit. fs.createReadStream support start y la opción de end para eso.

Así que probé un ejemplo y funciona. El código no es bonito, pero es fácil de entender. Primero procesamos el encabezado del rango para obtener la posición de inicio / finalización. Luego usamos fs.stat para obtener el tamaño del archivo sin leer todo el archivo en la memoria. Finalmente, use fs.createReadStream para enviar la parte solicitada al cliente.

var fs = require("fs"), http = require("http"), url = require("url"), path = require("path"); http.createServer(function (req, res) { if (req.url != "/movie.mp4") { res.writeHead(200, { "Content-Type": "text/html" }); res.end(''<video src="http://localhost:8888/movie.mp4" controls></video>''); } else { var file = path.resolve(__dirname,"movie.mp4"); fs.stat(file, function(err, stats) { if (err) { if (err.code === ''ENOENT'') { // 404 Error if file not found return res.sendStatus(404); } res.end(err); } var range = req.headers.range; if (!range) { // 416 Wrong range return res.sendStatus(416); } var positions = range.replace(/bytes=/, "").split("-"); var start = parseInt(positions[0], 10); var total = stats.size; var end = positions[1] ? parseInt(positions[1], 10) : total - 1; var chunksize = (end - start) + 1; res.writeHead(206, { "Content-Range": "bytes " + start + "-" + end + "/" + total, "Accept-Ranges": "bytes", "Content-Length": chunksize, "Content-Type": "video/mp4" }); var stream = fs.createReadStream(file, { start: start, end: end }) .on("open", function() { stream.pipe(res); }).on("error", function(err) { res.end(err); }); }); } }).listen(8888);

Tl; Dr - La pregunta:

¿Cuál es la forma correcta de manejar la transmisión de un archivo de video a un reproductor de video html5 con Node.js para que los controles de video sigan funcionando?

Creo que tiene que ver con la forma en que se manejan los encabezados. De todos modos, aquí está la información de fondo. El código es un poco largo, sin embargo, es bastante sencillo.

Transmitir pequeños archivos de video a video HTML5 con Node es fácil

Aprendí a transmitir pequeños archivos de video a un reproductor de video HTML5 muy fácilmente. Con esta configuración, los controles funcionan sin ningún trabajo de mi parte, y el video fluye sin problemas. Una copia de trabajo del código completamente operativo con video de muestra está aquí, para descargar en Google Docs .

Cliente:

<html> <title>Welcome</title> <body> <video controls> <source src="movie.mp4" type="video/mp4"/> <source src="movie.webm" type="video/webm"/> <source src="movie.ogg" type="video/ogg"/> <!-- fallback --> Your browser does not support the <code>video</code> element. </video> </body> </html>

Servidor:

// Declare Vars & Read Files var fs = require(''fs''), http = require(''http''), url = require(''url''), path = require(''path''); var movie_webm, movie_mp4, movie_ogg; // ... [snip] ... (Read index page) fs.readFile(path.resolve(__dirname,"movie.mp4"), function (err, data) { if (err) { throw err; } movie_mp4 = data; }); // ... [snip] ... (Read two other formats for the video) // Serve & Stream Video http.createServer(function (req, res) { // ... [snip] ... (Serve client files) var total; if (reqResource == "/movie.mp4") { total = movie_mp4.length; } // ... [snip] ... handle two other formats for the video var range = req.headers.range; var positions = range.replace(/bytes=/, "").split("-"); var start = parseInt(positions[0], 10); var end = positions[1] ? parseInt(positions[1], 10) : total - 1; var chunksize = (end - start) + 1; if (reqResource == "/movie.mp4") { res.writeHead(206, { "Content-Range": "bytes " + start + "-" + end + "/" + total, "Accept-Ranges": "bytes", "Content-Length": chunksize, "Content-Type": "video/mp4" }); res.end(movie_mp4.slice(start, end + 1), "binary"); } // ... [snip] ... handle two other formats for the video }).listen(8888);

Pero este método está limitado a archivos <1GB de tamaño.

Transmisión de archivos de video (de cualquier tamaño) con fs.createReadStream

Al utilizar fs.createReadStream() , el servidor puede leer el archivo en una secuencia en lugar de leerlo todo en la memoria a la vez. Esto suena como la manera correcta de hacer las cosas, y la sintaxis es extremadamente simple:

Fragmento de servidor:

movieStream = fs.createReadStream(pathToFile); movieStream.on(''open'', function () { res.writeHead(206, { "Content-Range": "bytes " + start + "-" + end + "/" + total, "Accept-Ranges": "bytes", "Content-Length": chunksize, "Content-Type": "video/mp4" }); // This just pipes the read stream to the response object (which goes //to the client) movieStream.pipe(res); }); movieStream.on(''error'', function (err) { res.end(err); });

Esto transmite el video muy bien! Pero los controles de video ya no funcionan.


La respuesta aceptada a esta pregunta es increíble y debe seguir siendo la respuesta aceptada. Sin embargo, me encontré con un problema con el código donde la secuencia de lectura no siempre se terminaba o cerraba. Parte de la solución fue enviar autoClose: true junto con start:start, end:end en el segundo createReadStream arg.

La otra parte de la solución fue limitar el max chunksize se envía en la respuesta. La otra respuesta se establece así:

var end = positions[1] ? parseInt(positions[1], 10) : total - 1;

... que tiene el efecto de enviar el resto del archivo desde la posición de inicio solicitada hasta su último byte, sin importar la cantidad de bytes que pueda haber. Sin embargo, el navegador del cliente tiene la opción de leer solo una parte de esa transmisión, y lo hará, si aún no necesita todos los bytes. Esto hará que la lectura de la secuencia se bloquee hasta que el navegador decida que es hora de obtener más datos (por ejemplo, una acción del usuario como buscar / borrar, o simplemente reproducir la transmisión).

Necesitaba cerrar esta secuencia porque estaba mostrando el elemento <video> en una página que permitía al usuario eliminar el archivo de video. Sin embargo, el archivo no se eliminó del sistema de archivos hasta que el cliente (o servidor) cerró la conexión, porque esa es la única forma en que la transmisión se cerró / finalizó.

Mi solución fue solo establecer una variable de configuración maxChunk , configurarla en 1MB, y nunca maxChunk una secuencia de lectura de más de 1MB a la respuesta.

// same code as accepted answer var end = positions[1] ? parseInt(positions[1], 10) : total - 1; var chunksize = (end - start) + 1; // poor hack to send smaller chunks to the browser var maxChunk = 1024 * 1024; // 1MB at a time if (chunksize > maxChunk) { end = start + maxChunk - 1; chunksize = (end - start) + 1; }

Esto tiene el efecto de asegurarse de que la secuencia de lectura finaliza / cierra después de cada solicitud y el buscador no la mantiene activa.

También escribí una question y answer separada de sobre este tema.