Tl; Dr - Die Frage:
Was ist der richtige Weg, um eine Videodatei mit Node.js auf einen HTML5-Videoplayer zu streamen, damit die Videosteuerelemente weiterhin funktionieren?
Ich denke , das hat damit zu tun, wie die Header behandelt werden. Wie auch immer, hier sind die Hintergrundinformationen. Der Code ist etwas langwierig, aber ziemlich einfach.
Das Streamen kleiner Videodateien zu HTML5-Videos mit Node ist einfach
Ich habe gelernt, wie man sehr einfach kleine Videodateien auf einen HTML5-Videoplayer streamen kann. Mit diesem Setup funktionieren die Steuerelemente ohne meinerseits und die Videostreams funktionieren einwandfrei. Eine Arbeitskopie des voll funktionsfähigen Codes mit Beispielvideo steht hier zum Download in Google Text & Tabellen zur Verfügung .
Klient:
<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);
Diese Methode ist jedoch auf Dateien mit einer Größe von <1 GB beschränkt.
Streaming von Videodateien (beliebiger Größe) mit fs.createReadStream
Durch die Verwendung fs.createReadStream()
kann der Server die Datei in einem Stream lesen, anstatt alles auf einmal in den Speicher einzulesen. Das klingt nach der richtigen Vorgehensweise, und die Syntax ist äußerst einfach:
Server-Snippet:
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);
});
Dies überträgt das Video ganz gut! Die Videosteuerung funktioniert jedoch nicht mehr.
quelle
writeHead()
Code kommentiert, aber für den Fall, dass es hilft. Sollte ich das entfernen, um das Code-Snippet besser lesbar zu machen?Antworten:
Der
Accept Ranges
Header (das Bit inwriteHead()
) ist erforderlich, damit die HTML5-Videosteuerelemente funktionieren.Ich denke, anstatt nur blind die vollständige Datei zu senden, sollten Sie zuerst den
Accept Ranges
Header in der ANFRAGE überprüfen , dann einlesen und genau dieses Bit senden.fs.createReadStream
Unterstützungstart
undend
Option dafür.Also habe ich ein Beispiel ausprobiert und es funktioniert. Der Code ist nicht schön, aber leicht zu verstehen. Zuerst verarbeiten wir den Bereichskopf, um die Start- / Endposition zu erhalten. Dann verwenden wir
fs.stat
, um die Größe der Datei zu ermitteln, ohne die gesamte Datei in den Speicher einzulesen. Verwenden Sie schließlich,fs.createReadStream
um das angeforderte Teil an den Client zu senden.quelle
Die akzeptierte Antwort auf diese Frage ist fantastisch und sollte die akzeptierte Antwort bleiben. Ich bin jedoch auf ein Problem mit dem Code gestoßen, bei dem der Lesestream nicht immer beendet / geschlossen wurde. Ein Teil der Lösung war zu senden
autoClose: true
zusammen mitstart:start, end:end
in der zweitencreateReadStream
arg.Der andere Teil der Lösung bestand darin, das Maximum zu begrenzen, das
chunksize
in der Antwort gesendet wird. Die andere Antwort lautetend
wie folgt:... was bewirkt, dass der Rest der Datei von der angeforderten Startposition durch ihr letztes Byte gesendet wird, egal wie viele Bytes das sein mögen. Der Client-Browser hat jedoch die Möglichkeit, nur einen Teil dieses Streams zu lesen, und wird dies tun, wenn noch nicht alle Bytes benötigt werden. Dies führt dazu, dass der Stream-Lesevorgang blockiert wird, bis der Browser entscheidet, dass es Zeit ist, mehr Daten abzurufen (z. B. eine Benutzeraktion wie Suchen / Scrubben oder einfach durch Abspielen des Streams).
Ich musste diesen Stream schließen, weil ich das
<video>
Element auf einer Seite anzeigte, auf der der Benutzer die Videodatei löschen konnte. Die Datei wurde jedoch erst aus dem Dateisystem entfernt, als der Client (oder Server) die Verbindung geschlossen hat, da nur so der Stream beendet / geschlossen wurde.Meine Lösung bestand nur darin, eine
maxChunk
Konfigurationsvariable festzulegen, auf 1 MB festzulegen und niemals einen Lese-Stream von mehr als 1 MB gleichzeitig an die Antwort weiterzuleiten.Dadurch wird sichergestellt, dass der Lesestream nach jeder Anforderung beendet / geschlossen wird und vom Browser nicht am Leben gehalten wird.
Ich habe auch eine separate Frage und Antwort zu StackOverflow zu diesem Thema geschrieben.
quelle