Skip to content

Instantly share code, notes, and snippets.

@mharsch
Last active February 8, 2024 02:43
Show Gist options
  • Save mharsch/5188206 to your computer and use it in GitHub Desktop.
Save mharsch/5188206 to your computer and use it in GitHub Desktop.
serve HLS (HTTP Live Streaming) content from node.js

HLS streaming from node

Provided that you already have a file or stream segmenter generating your .m3u8 playlist and .ts segment files (such as the ffmpeg 'hls' muxer), this little node server will serve up those files to an HLS compatible client (e.g. Safari). If you're using node for your streaming app already, this obviates the need to serve the HLS stream from a separate web server.

loosely based on https://gist.github.com/bnerd/2011232

// loosely based on https://gist.github.com/bnerd/2011232
// requires node.js >= v0.10.0
// assumes that HLS segmenter filename base is 'out'
// and that the HLS playlist and .ts files are in the current directory
// point Safari browser to http://<hostname>:PORT/player.html

var http = require('http');
var fs = require('fs');
var url = require('url');
var path = require('path');
var zlib = require('zlib');

PORT = 8000;

http.createServer(function (req, res) {
	var uri = url.parse(req.url).pathname;

	if (uri == '/player.html') {
		res.writeHead(200, { 'Content-Type': 'text/html' });
		res.write('<html><head><title>HLS Player fed by node.js' +
		    '</title></head><body>');
		res.write('<video src="http://' + req.socket.localAddress +
		    ':' + PORT + '/out.M3U8" controls autoplay></body></html>');
		res.end();
		return;
	}

	var filename = path.join("./", uri);
	fs.exists(filename, function (exists) {
		if (!exists) {
			console.log('file not found: ' + filename);
			res.writeHead(404, { 'Content-Type': 'text/plain' });
			res.write('file not found: %s\n', filename);
			res.end();
		} else {
			console.log('sending file: ' + filename);
			switch (path.extname(uri)) {
			case '.M3U8':
				fs.readFile(filename, function (err, contents) {
					if (err) {
						res.writeHead(500);
						res.end();
					} else if (contents) {
						res.writeHead(200,
						    {'Content-Type':
						    'application/vnd.apple.mpegurl'});
						var ae = req.headers['accept-encoding'];
						if (ae.match(/\bgzip\b/)) {
							zlib.gzip(contents, function (err, zip) {
								if (err) throw err;

								res.writeHead(200,
								    {'content-encoding': 'gzip'});
								res.end(zip);
							});
						} else {
							res.end(contents, 'utf-8');
						}
					} else {
						console.log('emptly playlist');
						res.writeHead(500);
						res.end();
					}
				});
				break;
			case '.ts':
				res.writeHead(200, { 'Content-Type':
				    'video/MP2T' });
				var stream = fs.createReadStream(filename,
				    { bufferSize: 64 * 1024 });
				stream.pipe(res);
				break;
			default:
				console.log('unknown file type: ' +
				    path.extname(uri));
				res.writeHead(500);
				res.end();
			}
		}
	});
}).listen(PORT);
@renatoargh
Copy link

this works great! 😄 thank you!

@TomReddish-MVC
Copy link

Actually it seems to be having an error writing the output file. When I refresh the page http://localhost:8000/player.html on my other machine (mac) it throws this error on the node js machine

file not found: out.M3U8

buffer.js:377
      throw new TypeError('Unknown encoding: ' + encoding);
            ^
TypeError: Unknown encoding: out.m3u8
    at Buffer.write (buffer.js:377:13)
    at new Buffer (buffer.js:215:28)
    at createWriteReq (net.js:675:33)
    at Socket._write (net.js:640:18)
    at doWrite (_stream_writable.js:223:10)
    at writeOrBuffer (_stream_writable.js:213:5)
    at Socket.Writable.write (_stream_writable.js:180:11)
    at Socket.write (net.js:613:40)
    at ServerResponse.OutgoingMessage._writeRaw (http.js:532:28)
    at ServerResponse.OutgoingMessage._send (http.js:507:15)

@TomReddish-MVC
Copy link

for anyone that might get this error, figured it out... ffmpeg was outputting out.m3u8 not out.M3U8

@JordanMajd
Copy link

Excellent example!

@bayarja
Copy link

bayarja commented Sep 26, 2016

doesn't work on vlc player. ae variable undefined and already sent headers errors.

@gold256
Copy link

gold256 commented Mar 8, 2018

if (ae && ae.match(/\bgzip\b/)) {

@angrybaracuda
Copy link

angrybaracuda commented Jul 5, 2018

Doesn't works with latest Node version. Not sure whats wrong in here. Can anyone help :( ?

@jackesdavid
Copy link

jackesdavid commented Dec 15, 2018

res.writeHead(200, { 'Content-Type': 'video/MP2T' });
//bufferSize is now highWaterMark
const stream = fs.createReadStream(file, { highWaterMark: 64 * 1024 });
stream.pipe(res);

@renedufe
Copy link

renedufe commented Apr 2, 2021

Shouldn't the code for player.html be included somewhere in this?

@DanieleSalatti
Copy link

It's in there:

res.writeHead(200,` { 'Content-Type': 'text/html' });
res.write('<html><head><title>HLS Player fed by node.js' + '</title></head><body>');
res.write('<video src="http://' + req.socket.localAddress + ':' + PORT + '/out.M3U8" controls autoplay></body></html>');
res.end();
return;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment