Tl; Dr - Вопрос:
Как правильно обрабатывать потоковую передачу видеофайла в видеопроигрыватель html5 с помощью Node.js, чтобы элементы управления видео продолжали работать?
Я думаю, это связано с тем, как обрабатываются заголовки. В любом случае, вот справочная информация. Код немного длинно, однако, это довольно просто.
Транслировать небольшие видеофайлы в видео HTML5 с помощью Node очень просто
Я очень легко научился передавать небольшие видеофайлы в видеоплеер 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);
Но этот метод ограничен файлами размером <1 ГБ.
Потоковое видео (любого размера) с 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);
});
Это транслирует видео просто отлично! Но элементы управления видео больше не работают.
источник
writeHead()
код прокомментированным, но на тот случай, если это поможет. Следует ли мне удалить это, чтобы сделать фрагмент кода более читабельным?Ответы:
Accept Ranges
Заголовка (бит вwriteHead()
) требуется для управления HTML5 видео на работу.Я думаю, вместо того, чтобы просто слепо отправлять полный файл, вы должны сначала проверить
Accept Ranges
заголовок в ЗАПРОСЕ, затем прочитать и отправить только этот бит.fs.createReadStream
поддержкаstart
иend
возможность для этого.Итак, я попробовал пример, и он работает. Код некрасивый, но его легко понять. Сначала мы обрабатываем заголовок диапазона, чтобы получить начальную / конечную позицию. Затем мы используем,
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
аргументом.Другая часть решения заключалась в том, чтобы ограничить максимальное количество
chunksize
отправляемых в ответе. Другой ответ былend
таким:var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
... в результате чего остальная часть файла отправляется с запрошенной начальной позиции до последнего байта, независимо от того, сколько байтов это может быть. Однако клиентский браузер имеет возможность читать только часть этого потока, и будет, если ему еще не нужны все байты. Это приведет к блокировке чтения потока до тех пор, пока браузер не решит, что пора получить больше данных (например, действие пользователя, такое как поиск / очистка, или просто воспроизведение потока).
Мне нужно было закрыть этот поток, потому что я отображал
<video>
элемент на странице, которая позволяла пользователю удалить видеофайл. Однако файл не удалялся из файловой системы до тех пор, пока клиент (или сервер) не закрыл соединение, потому что это единственный способ завершения / закрытия потока.Мое решение заключалось в том, чтобы просто установить
maxChunk
переменную конфигурации, установить ее на 1 МБ и никогда не передавать поток чтения размером более 1 МБ за раз в ответ.// 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 по этой проблеме.
источник
Сначала создайте
app.js
файл в каталоге, который хотите опубликовать.var http = require('http'); var fs = require('fs'); var mime = require('mime'); http.createServer(function(req,res){ if (req.url != '/app.js') { var url = __dirname + req.url; fs.stat(url,function(err,stat){ if (err) { res.writeHead(404,{'Content-Type':'text/html'}); res.end('Your requested URI('+req.url+') wasn\'t found on our server'); } else { var type = mime.getType(url); var fileSize = stat.size; var range = req.headers.range; if (range) { var parts = range.replace(/bytes=/, "").split("-"); var start = parseInt(parts[0], 10); var end = parts[1] ? parseInt(parts[1], 10) : fileSize-1; var chunksize = (end-start)+1; var file = fs.createReadStream(url, {start, end}); var head = { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': type } res.writeHead(206, head); file.pipe(res); } else { var head = { 'Content-Length': fileSize, 'Content-Type': type } res.writeHead(200, head); fs.createReadStream(url).pipe(res); } } }); } else { res.writeHead(403,{'Content-Type':'text/html'}); res.end('Sorry, access to that file is Forbidden'); } }).listen(8080);
Просто запустите,
node app.js
и ваш сервер должен работать на порту 8080. Помимо видео, он может передавать все виды файлов.источник