Tl; Dr - Pertanyaan:
Apa cara yang tepat untuk menangani streaming file video ke pemutar video html5 dengan Node.js sehingga kontrol video terus berfungsi?
Saya pikir itu ada hubungannya dengan cara penanganan header. Berikut informasi latar belakangnya. Kode ini agak panjang, namun cukup mudah.
Streaming file video kecil ke video HTML5 dengan Node itu mudah
Saya belajar cara melakukan streaming file video kecil ke pemutar video HTML5 dengan sangat mudah. Dengan pengaturan ini, kontrol bekerja tanpa ada pekerjaan di pihak saya, dan video mengalir dengan sempurna. Salinan yang berfungsi dari kode yang berfungsi sepenuhnya dengan video contoh ada di sini, untuk diunduh di Google Documents .
Klien:
<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>
Server:
// 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);
Tetapi metode ini terbatas pada file berukuran <1GB.
Streaming file video (ukuran apa saja) dengan fs.createReadStream
Dengan memanfaatkan fs.createReadStream()
, server dapat membaca file dalam aliran daripada membaca semuanya ke dalam memori sekaligus. Ini terdengar seperti cara yang tepat untuk melakukan sesuatu, dan sintaksnya sangat sederhana:
Cuplikan Server:
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);
});
Ini mengalirkan video dengan baik! Tetapi kontrol video tidak lagi berfungsi.
sumber
writeHead()
kode itu berkomentar, tetapi di sana jika itu membantu. Haruskah saya menghapusnya agar cuplikan kode lebih mudah dibaca?Jawaban:
The
Accept Ranges
header (bit diwriteHead()
) diperlukan untuk kontrol HTML5 video ke pekerjaan.Saya pikir daripada hanya mengirim file lengkap secara membabi buta, Anda harus terlebih dahulu memeriksa
Accept Ranges
header di REQUEST, kemudian membaca dan mengirim sedikit itu.fs.createReadStream
dukunganstart
, danend
opsi untuk itu.Jadi saya mencoba contoh dan berhasil. Kodenya tidak bagus tapi mudah dimengerti. Pertama kami memproses header rentang untuk mendapatkan posisi awal / akhir. Kemudian kami gunakan
fs.stat
untuk mendapatkan ukuran file tanpa membaca seluruh file ke dalam memori. Terakhir, gunakanfs.createReadStream
untuk mengirim bagian yang diminta ke klien.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);
sumber
Jawaban yang diterima untuk pertanyaan ini luar biasa dan harus tetap menjadi jawaban yang diterima. Namun saya mengalami masalah dengan kode di mana aliran baca tidak selalu diakhiri / ditutup. Bagian dari solusi adalah untuk mengirim
autoClose: true
bersama denganstart:start, end:end
di keduacreateReadStream
arg.Bagian lain dari solusi itu adalah membatasi maksimal
chunksize
yang dikirim dalam respons. Jawaban lainnya diaturend
seperti ini:var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
... yang memiliki efek mengirim sisa file dari posisi awal yang diminta hingga byte terakhirnya, tidak peduli berapa banyak byte yang mungkin. Namun browser klien memiliki opsi untuk hanya membaca sebagian dari aliran itu, dan akan, jika belum membutuhkan semua byte. Ini akan menyebabkan pembacaan aliran diblokir hingga browser memutuskan waktunya untuk mendapatkan lebih banyak data (misalnya tindakan pengguna seperti seek / scrub, atau hanya dengan memutar aliran).
Saya membutuhkan aliran ini ditutup karena saya menampilkan
<video>
elemen pada halaman yang memungkinkan pengguna untuk menghapus file video. Namun file tersebut tidak dihapus dari sistem file sampai klien (atau server) menutup koneksi, karena itulah satu-satunya cara streaming diakhiri / ditutup.Solusi saya hanya mengatur
maxChunk
variabel konfigurasi, mengaturnya ke 1MB, dan tidak pernah menyalurkan aliran baca lebih dari 1MB pada satu waktu ke respons.// 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; }
Ini memiliki efek memastikan bahwa aliran baca diakhiri / ditutup setelah setiap permintaan, dan tidak tetap hidup oleh browser.
Saya juga menulis pertanyaan dan jawaban StackOverflow terpisah yang mencakup masalah ini.
sumber
Pertama buat
app.js
file di direktori yang ingin Anda terbitkan.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);
Cukup jalankan
node app.js
dan server Anda akan berjalan pada port 8080. Selain video, ini dapat mengalirkan semua jenis file.sumber