Потоковая передача видеофайла на видеоплеер html5 с узлом.js, чтобы видеоуправление продолжало работать?

Tl; Dr-Вопрос:

Как правильно обрабатывать потоковую передачу видеофайла на видеоплеер html5 с узлом.js так что видео управления продолжают работать?

Я думаю это связано с тем, что заголовки обрабатываются. В любом случае, вот справочная информация. Код мало длительный, однако, это довольно просто.

потокового видео файлов HTML5 Видео с узлом легко

я очень легко научился передавать небольшие видеофайлы на видеоплеер HTML5. С этой настройкой элементы управления работают без какой-либо работы с моей стороны, а видеопотоки безупречно. Рабочая копия полностью рабочего кода с образцом видео-это здесь, для загрузки на Google Docs.

клиент:

<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>

сервер:

// 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);

но этот метод ограничивается файлами размером

потоковое (любого размера) видео файлы с fs.createReadStream

С помощью fs.createReadStream(), сервер может читать файл в потоке, а не читать его все в память сразу. Это звучит как правильный способ сделать что-то, и синтаксис чрезвычайно прост:

Фрагмент Сервер:

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);
});

это потоки видео просто отлично! Но видеоуправление больше не работает.

2 ответов


на Accept Ranges заголовок (бит в writeHead()) требуется для работы элементов управления HTML5 video.

Я думаю, вместо того, чтобы просто вслепую отправить полный файл, вы должны сначала проверить Accept Ranges заголовок в запросе, затем прочитайте и отправьте только этот бит. fs.createReadStream поддержка start и для этого.

поэтому я попробовал пример, и он работает. Код не очень красивый, но его легко понять. Сначала мы обрабатываем заголовок диапазона, чтобы получить начальную / конечную позицию. Тогда мы используем fs.stat чтобы получить размер файла без чтения всего файла в память. Наконец, используйте fs.createReadStream для отправки запрошенной части клиенту.

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);

принятый ответ на этот вопрос является удивительным и должно быть принято отвечать. Однако я столкнулся с проблемой с кодом, где поток чтения не всегда заканчивался / закрывался. Частью решения было отправить autoClose: true вместе с start:start, end:end во втором createReadStream arg.

другой частью решения было ограничение max chunksize отправляется в ответ. Другой набор ответов end вот так:

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

...который имеет эффект отправки остальной части файла из запрошенной начальной позиции через последний байт, независимо от того, сколько байтов это может быть. Однако браузер клиента имеет возможность читать только часть этого потока и будет, если ему еще не нужны все байты. Это приведет к блокировке чтения потока, пока браузер не решит, что пришло время получить больше данных (например, действие пользователя, такое как seek/scrub, или просто играя в поток).

мне нужно было, чтобы этот поток был закрыт потому что я показывал <video> элемент на странице, который позволил пользователю удалить видеофайл. Однако файл не удалялся из файловой системы до тех пор, пока клиент (или сервер) не закрыл соединение, потому что это единственный способ завершить/закрыть поток.

моим решением было просто установить maxChunk переменная конфигурации, установите ее в 1MB, и никогда не передайте прочитанный поток больше чем 1MB одновременно к ответу.

// 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;
}

это эффект убеждаться что прочитанный поток закончен/закрыт после каждого запроса, и не сдержанный живым браузером.

Я также написал отдельный StackOverflow вопрос и ответ по этому вопросу.