비디오 컨트롤이 계속 작동하도록 Node.js를 사용하여 비디오 파일을 html5 비디오 플레이어로 스트리밍 하시겠습니까?
Tl; Dr-질문 :
비디오 컨트롤이 계속 작동하도록 Node.js를 사용하여 비디오 파일을 html5 비디오 플레이어로 스트리밍하는 올바른 방법은 무엇입니까 ?
나는 생각 이 헤더를 처리하는 방식과 관련이있다. 어쨌든, 여기에 배경 정보가 있습니다. 코드는 약간 길지만 매우 간단합니다.
Node를 사용하여 작은 비디오 파일을 HTML5 비디오로 스트리밍하는 것은 쉽습니다.
작은 비디오 파일을 HTML5 비디오 플레이어로 매우 쉽게 스트리밍하는 방법을 배웠습니다. 이 설정을 사용하면 컨트롤이 내 작업없이 작동하며 비디오가 완벽하게 스트리밍됩니다. Google 문서에서 다운로드 할 수 있는 샘플 비디오와 함께 완전히 작동하는 코드의 작업 사본이 여기에 있습니다 .
고객:
<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 비디오 컨트롤이 필요합니다.
I think instead of just blindly send the full file, you should first check the Accept Ranges
header in the REQUEST, then read in and send just that bit. fs.createReadStream
support start
, and end
option for that.
So I tried an example and it works. The code is not pretty but it is easy to understand. First we process the range header to get the start/end position. Then we use fs.stat
to get the size of the file without reading the whole file into memory. Finally, use fs.createReadStream
to send the requested part to the client.
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);
The accepted answer to this question is awesome and should remain the accepted answer. However I ran into an issue with the code where the read stream was not always being ended/closed. Part of the solution was to send autoClose: true
along with start:start, end:end
in the second createReadStream
arg.
The other part of the solution was to limit the max chunksize
being sent in the response. The other answer set end
like so:
var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
...which has the effect of sending the rest of the file from the requested start position through its last byte, no matter how many bytes that may be. However the client browser has the option to only read a portion of that stream, and will, if it doesn't need all of the bytes yet. This will cause the stream read to get blocked until the browser decides it's time to get more data (for example a user action like seek/scrub, or just by playing the stream).
I needed this stream to be closed because I was displaying the <video>
element on a page that allowed the user to delete the video file. However the file was not being removed from the filesystem until the client (or server) closed the connection, because that is the only way the stream was getting ended/closed.
My solution was just to set a maxChunk
configuration variable, set it to 1MB, and never pipe a read a stream of more than 1MB at a time to the response.
// 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;
}
This has the effect of making sure that the read stream is ended/closed after each request, and not kept alive by the browser.
I also wrote a separate StackOverflow question and answer covering this issue.
'programing' 카테고리의 다른 글
Hibernate의 열거 형 (0) | 2020.09.25 |
---|---|
Sass에서 동적으로 변수 생성 또는 참조 (0) | 2020.09.25 |
Python 사전에 해당하는 Java (0) | 2020.09.25 |
개체가 Entity Framework의 데이터 컨텍스트에 이미 연결되어 있는지 확인할 수 있습니까? (0) | 2020.09.25 |
HTML 본문에 JavaScript를 포함하는 것이 나쁜 습관입니까? (0) | 2020.09.25 |