![](/img/trans.png)
[英]Play local video file in electron html5 video player using node.js fs.readStream()
[英]Streaming a video file to an html5 video player with Node.js so that the video controls continue to work?
使用 Node.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);
但是這種方法僅限於小於 1GB 的文件。
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);
});
這流視頻就好了! 但視頻控件不再起作用。
Accept Ranges
標頭( writeHead()
的位)是 HTML5 視頻控件工作所必需的。
我認為不是盲目地發送完整文件,您應該首先檢查 REQUEST 中的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);
這個問題的公認答案很棒,應該仍然是公認的答案。 但是,我遇到了讀取流並不總是結束/關閉的代碼問題。 部分解決方案是在第二個createReadStream
參數中發送autoClose: true
和start:start, end:end
。
該解決方案的另一部分是限制最大chunksize
在響應中發送的存在。 另一個答案集是這樣end
:
var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
...它的作用是從請求的起始位置到最后一個字節發送文件的其余部分,無論可能有多少字節。 然而,客戶端瀏覽器可以選擇只讀取該流的一部分,如果它不需要所有字節,也會這樣做。 這將導致讀取的流被阻塞,直到瀏覽器決定是時候獲取更多數據(例如,諸如搜索/清理之類的用戶操作,或僅通過播放流)。
我需要關閉此流,因為我在允許用戶刪除視頻文件的頁面上顯示<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;
}
這具有確保讀取流在每次請求后結束/關閉的效果,並且不會被瀏覽器保持活動狀態。
首先在要發布的目錄中創建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 端口上運行。除了視頻,它還可以流式傳輸各種文件。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.