web-dev-qa-db-fra.com

Streaming vidéo avec HTML 5 via node.js

J'essaie de configurer un serveur Web qui prendra en charge la diffusion de vidéo en continu sur une balise vidéo HTML5 à l'aide de node.js. Voici mon code jusqu'à présent:

var range = request.headers.range;
var total = file.length;

var parts = range.replace(/bytes=/, "").split("-");
var partialstart = parts[0];
var partialend = parts[1];

var start = parseInt(partialstart, 10);
var end = partialend ? parseInt(partialend, 10) : total-1;

var chunksize = (end-start)+1;

response.writeHead(206, { "Content-Range": "bytes " + start + "-" + end + "/" + total, "Accept-Ranges": "bytes", "Content-Length": chunksize, "Content-Type": type });
response.end(file);

Où "demande" représente la demande http, le type est soit "application/ogg" ou "vidéo/ogg" (j'ai essayé les deux) et "fichier" est le fichier .ogv qui a été lu à partir du système de fichiers. Voici les en-têtes de réponse:

Content-Range   bytes 0-14270463/14270464
Accept-Ranges   bytes
Content-Length   14270464
Connection     keep-alive
Content-Type     video/ogg

J'ai examiné les en-têtes de réponse et ce code semble fonctionner correctement, mais il y a quelques problèmes:

  1. La vidéo semble se charger très lentement pour être sur un réseau local. D'après ce que je peux dire en examinant la réponse à l'aide de Firebug, le fichier semble être diffusé à environ 150 kb/s.
  2. La vidéo ne joue pas du tout. Même si j'attends que le tout se charge, la balise vidéo HTML 5 affiche juste un grand "x" au lieu d'un film dans Firefox.

Quelqu'un a-t-il des idées sur ce que je peux faire pour que le streaming vidéo fonctionne via node.js?

Merci!
Chris

43
Chris Harrington

J'ai pu faire fonctionner cela avec l'aide des forums nodejs:

http://groups.google.com/group/nodejs/browse_thread/thread/8339e0dc825c057f/822b2dd48f36e89

Faits saillants du fil Google Groupes:

Google chrome est connu pour faire d'abord une demande avec la plage 0-1024, puis demander la plage "1024-".

response.end (file.slice (start, chunksize), "binary");

Ensuite:

J'ai réussi à faire jouer la vidéo sans problème dans Firefox en définissant l'en-tête "Connexion" sur "Fermer"

Ensuite:

Semble que vous calculez incorrectement la longueur du contenu:

var chunksize = (end-start) +1;

Si start est 0 et end est 1, dans votre cas, chunksize est 2 et devrait être 1.

18
Chris Harrington

Je sais que c'est une très vieille question, mais comme Google semble l'aimer, j'ai pensé qu'il valait la peine de souligner que j'avais écrit un Node.js module de streaming vidéo (Github, ou via NPM) qui, espérons-le, vaut le détour aussi.

23
meloncholy

Cette solution effectue une lecture asynchrone d'un fichier multimédia vidéo ou audio côté serveur ... elle fait tourner un serveur nodejs à l'URL visible à

http: // localhost: 8888 /

il gère également correctement les mouvements du curseur de widget UI avant/arrière HTML5 côté client (navigateur/application)

enregistrer l'extrait de code ci-dessous en tant que fichier côté serveur:

media_server.js

... l'exécuter côté serveur en utilisant

node media_server.js

prendre plaisir

var http = require('http'),
    fs = require('fs'),
    util = require('util');

var path = "/path/to/local/video/or/audio/file/on/server.mp4";

var port = 8888;
var Host = "localhost";

http.createServer(function (req, res) {

  var stat = fs.statSync(path);
  var total = stat.size;

  if (req.headers.range) {   // meaning client (browser) has moved the forward/back slider
                                         // which has sent this request back to this server logic ... cool
    var range = req.headers.range;
    var parts = range.replace(/bytes=/, "").split("-");
    var partialstart = parts[0];
    var partialend = parts[1];

    var start = parseInt(partialstart, 10);
    var end = partialend ? parseInt(partialend, 10) : total-1;
    var chunksize = (end-start)+1;
    console.log('RANGE: ' + start + ' - ' + end + ' = ' + chunksize);

    var file = fs.createReadStream(path, {start: start, end: end});
    res.writeHead(206, { 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4' });
    file.pipe(res);

  } else {

    console.log('ALL: ' + total);
    res.writeHead(200, { 'Content-Length': total, 'Content-Type': 'video/mp4' });
    fs.createReadStream(path).pipe(res);
  }
}).listen(port, Host);

console.log("Server running at http://" + Host + ":" + port + "/");
11
Scott Stensland

Sur la base de la réponse de Sam9291, j'ai réécrit la fonction en utilisant createReadStream() et en corrigeant certains problèmes:

/**
 * Sends a static file to the HTTP client, supporting partial transfers.
 * 
 * @req HTTP request object
 * @res HTTP response object
 * @fn Path to file that should be sent
 * @contentType MIME type for the response (defaults to HTML)
 */      
function sendFile(req, res, fn, contentType) {

  contentType = contentType || "text/html";

  fs.stat(fn, function(err, stats) {
    var headers;

    if (err) {
      res.writeHead(404, {"Content-Type":"text/plain"});
      res.end("Could not read file");
      return;
    }

    var range = req.headers.range || "";    
    var total = stats.size;

    if (range) {

      var parts = range.replace(/bytes=/, "").split("-");
      var partialstart = parts[0];
      var partialend = parts[1];

      var start = parseInt(partialstart, 10);
      var end = partialend ? parseInt(partialend, 10) : total-1;

      var chunksize = (end-start)+1;

      headers = { 
        "Content-Range": "bytes " + start + "-" + end + "/" + total, 
        "Accept-Ranges": "bytes", 
        "Content-Length": chunksize, 
        "Content-Type": contentType 
      };
      res.writeHead(206, headers);

    } else {

      headers = { 
        "Accept-Ranges": "bytes", 
        "Content-Length": stats.size, 
        "Content-Type": contentType 
      };
      res.writeHead(200, headers);

    }

    var readStream = fs.createReadStream(fn, {start:start, end:end});
    readStream.pipe(res);    

  });

}
5
Udo G

J'utilise le framework MVC sails.js en plus de Node.js et j'ai réussi à le faire fonctionner correctement avec le code suivant:

/**
 * VideoController
 *
 * @module      :: Controller
 * @description :: Contains logic for handling requests.
 */

 var fs = require('fs');

module.exports = {

  /* e.g.
  sayHello: function (req, res) {
    res.send('hello world!');
  }
  */

  /**
   * /video/stream
   */ 
  stream: function (req,res) {

    // This will render the view: 
    // C:\Users\sam\Documents\Dev\Fun\mymoviebank/views/video/stream.ejs
    res.view();

  },

  play: function (req,res) {

    fs.readFile('/Users/sam/Videos/big_buck_bunny.mp4', function (err, data) {
      if (err) throw err;

      var range = req.headers.range;
        var total = data.length;

        var parts = range.replace(/bytes=/, "").split("-");
        var partialstart = parts[0];
        var partialend = parts[1];

        var start = parseInt(partialstart, 10);
        var end = partialend ? parseInt(partialend, 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' });
        res.end(data);

    });

  }

};

J'espère que cela t'aides

4
Samuel Poirier

J'ai trouvé cette solution qui semble être plus simple et (contrairement à la réponse cochée) fonctionne pour moi. (J'ai essayé d'adapter la solution coffeescript à la fin de ce fil et cela a fonctionné une fois que j'ai traité le fait que la demande initiale (pour "bytes = 0-") l'a fait exploser.

http://elegantcode.com/2011/04/06/taking-baby-steps-with-node-js-pumping-data-between-streams/

Ma mise en œuvre réelle:

function stream_response( res, file_path, content_type ){
    var readStream = fs.createReadStream(file_path);

    readStream.on('data', function(data) {
        var flushed = res.write(data);
        // Pause the read stream when the write stream gets saturated
        console.log( 'streaming data', file_path );
        if(!flushed){
            readStream.pause();
        }
    });

    res.on('drain', function() {
        // Resume the read stream when the write stream gets hungry 
        readStream.resume();
    });

    readStream.on('end', function() {
        res.end();
    });

    readStream.on('error', function(err) {
        console.error('Exception', err, 'while streaming', file_path);
        res.end();
    });

    res.writeHead(200, {'Content-Type': content_type});
}
3
podperson

lorsque vous utilisez express, placez-le dans votre media_server.js ou index.js qui servira les médias sur le port 3000

const express = require('express')
const fs = require('fs')
const path = require('path')
const app = express()

app.use(express.static(path.join(__dirname, 'public')))

app.get('/', function(req, res) {
  res.sendFile(path.join(__dirname + '/index.html'))
})

app.get('/video', function(req, res) {
  const path = 'assets/sample.mp4'// your video path
  const stat = fs.statSync(path)
  const fileSize = stat.size
  const range = req.headers.range

  if (range) {
    const parts = range.replace(/bytes=/, "").split("-")
    const start = parseInt(parts[0], 10)
    const end = parts[1]
      ? parseInt(parts[1], 10)
      : fileSize-1

    const chunksize = (end-start)+1
    const file = fs.createReadStream(path, {start, end})
    const head = {
      'Content-Range': `bytes ${start}-${end}/${fileSize}`,
      'Accept-Ranges': 'bytes',
      'Content-Length': chunksize,
      'Content-Type': 'video/mp4',
    }

    res.writeHead(206, head)
    file.pipe(res)
  } else {
    const head = {
      'Content-Length': fileSize,
      'Content-Type': 'video/mp4',
    }
    res.writeHead(200, head)
    fs.createReadStream(path).pipe(res)
  }
})

app.listen(3000, function () {
  console.log('Listening on port 3000!')
})

puis dans votre index.html

<html>
  <head>
    <title>Video stream sample</title>
  </head>
  <body>
    <video id="videoPlayer" controls muted="muted" autoplay> 
      <source src="http://localhost:3000/video" type="video/mp4">
    </video>
  </body>
</html>
0
Kevin Muchwat