Torrent Streaming service with WebTorrent and Express

Torrent Streaming service with WebTorrent and Express

Comments 9

Beware This post is more than 3 years old, it may be outdated or incorrect! Please check elsewhere for accurate information!

This tutorial is meant for people who already have an basic knowledge of programming, and an intermediate knowledge of Windows or Ubuntu. This tutorial is written mainly for Windows, but provides alternatives for Linux where applicable.

Step 1: Install nodejs and npm

To install Node and npm on Windows, you should go and download the Windows Installer from here. Run the Installer you’ve downloaded and follow the instructions on screen.

To verify you have installed Node, open CMD and run “node -v” and “npm -v”. You should see something like this:

C:\Users\Milan>node -v
C:\Users\Milan>npm -v

To install Node on Ubuntu or Debian, type in “sudo apt-get install nodejs npm” (ommit sudo for Debian, run as root).

Step 3: Initialize a project

Create a folder somewhere, I usually have a dedicated folder for my Projects, and name it whatever you want, for example “BrowserTorrent”.

Then, create a file named “package.json” in it and put in the following contents:

 	"name": "BrowserTorrent",
 	"version": "1.0.0",
 	"scripts": {
 		"start": "node ./index.js"
 	"dependencies": {
 	"express": "~4.13.1",
 	"webtorrent": "~0.62.3"

This file will tell Node what’s our project name, what dependencies does our project require, how to start it, etc. To learn more about the package.json format, go here.

Now, go into the folder that you’ve made, and run “npm install”. You should see something like this:

npm install result

Step 3: The HTML application

Make a folder called “app” in the BrowserTorrent folder. Inside of it, create a new file, named index.html, and paste the following contents:

<!DOCTYPE html>
	<meta charset="utf-8" />
	<script src=""></script>
	$(function() {
		var api = 'http://localhost:9111/';
		$('.stream').click(function() {
			window.infoHash = $('input').val().split('btih:')[1].split('&')[0];
			$.get(api + 'api/add/' + infoHash, function(data) {
				var video = api + 'stream/' + infoHash + '.mp4';
				$('video').attr('src', video);
		$('.stop').click(function() {
			$.get(api + 'api/delete/' + infoHash, function(data) {
				$('video').attr('src', '');
				window.infoHash = '';
	<div class="container">
		<div class="video" style="display:none">
			<video width="640" height="360" autoplay preload controls type="video/mp4" src="" style="background:black"></video>
			<button class="stop">Stop</button>
		<div class="input">
			<p>Sample torrent: magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d&amp;dn=sintel.mp4 (Sintel Open Source Movie, H.264 MP4, ~120 MiB). </p>
			<input type="text" style="width:350px" name="torrent" placeholder="Enter torrent URL. " />
			<button class="stream">Stream!</button>

This file will be our Web User Interface, with which our user will interact. For the purpose of this demo, it’s a very simple HTML file with JavaScript, CSS and HTML all mashed together, but that’s just for simplicity’s sake.

In a “real” application you would of course separate everything into different files and format it better.

Step 4: The Node Application

In the BrowserTorrent folder, create a new file, named “index.js”. Note the JS at the end, this is different from HTML in the 3rd step. This will be our node application that acts as a web server and the torrent streaming API. Inside, paste the following content:

var express = require('express');
var webtorrent = require('webtorrent');
var path = require('path');
var http = require('http');
var app = express();

var port = 9111;

var client = new webtorrent();

// Allow Cross-Origin requests
app.use(function(req, res, next) {
	res.header('Access-Control-Allow-Origin', '*');
	res.header('Access-Control-Allow-Methods', 'OPTIONS, POST, GET, PUT, DELETE');
	res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');

app.use(express.static(path.join(__dirname, 'app')));

var getLargestFile = function (torrent) {
	var file;
	for(i = 0; i < torrent.files.length; i++) {
		if (!file || file.length < torrent.files[i].length) {
			file = torrent.files[i];
	return file;

var buildMagnetURI = function(infoHash) {
	return 'magnet:?xt=urn:btih:' + infoHash + '&';

app.get('/api/add/:infoHash', function(req, res) {
	if(typeof req.params.infoHash == 'undefined' || req.params.infoHash == '') {
		res.status(500).send('Missing infoHash parameter!'); return;
	var torrent = buildMagnetURI(req.params.infoHash);
	try {
		client.add(torrent, function (torrent) {
			var file = getLargestFile(torrent);
			torrent.swarm.on('upload', function() {
				if(torrent.length == torrent.downloaded) {
			res.status(200).send('Added torrent!');
	} catch (err) {
		res.status(500).send('Error: ' + err.toString());

app.get('/stream/:infoHash.mp4', function(req, res, next) {
	if(typeof req.params.infoHash == 'undefined' || req.params.infoHash == '') {
		res.status(500).send('Missing infoHash parameter!'); return;
	var torrent = buildMagnetURI(req.params.infoHash);
	try {
		var torrent = client.get(torrent);
		var file = getLargestFile(torrent);
		var total = file.length;

		if(typeof req.headers.range != 'undefined') {
			var range = req.headers.range;
			var parts = range.replace(/bytes=/, "").split("-");
			var partialstart = parts[0];
			var partialend = parts[1];
			var start = parseInt(partialstart, 10);
			var end = partialend ? parseInt(partialend, 10) : total - 1;
			var chunksize = (end - start) + 1;
		} else {
			var start = 0; var end = total;
		var stream = file.createReadStream({start: start, end: end});
		res.writeHead(206, { 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4' });
	} catch (err) {
		res.status(500).send('Error: ' + err.toString());

app.get('/api/delete/:infoHash', function(req, res, next) {
	if(typeof req.params.infoHash == 'undefined' || req.params.infoHash == '') {
		res.status(500).send('Missing infoHash parameter!'); return;
	var torrent = buildMagnetURI(req.params.infoHash);
	try {
		var torrent = client.remove(torrent);
		res.status(200).send('Removed torrent. ');
	} catch (err) {
		res.status(500).send('Error: ' + err.toString());

var server = http.createServer(app);
server.listen(port, function() {
	console.log('Listening on' + port);

Now, we’re almost there. Save the file and move on to the next step.

Step 5: The demo

I have made a quick demo that showcases the application. It’s embedded below:

Now, to try it for yourself on your PC, open cmd or any shell, and navigate to the BrowserPopcorn directory. Inside, run “npm start”.

It should write out something like this:

C:\Users\Milan\Documents\Projects\BrowserTorrent>npm start

> [email protected] start C:\Users\Milan\Documents\Projects\BrowserTorrent
> node ./index.js

Listening on

Now, open your browser to the URL in the command line window, most probably

Copy the magnet link of Sintel, the open source movie, and paste it into the text field and press Start!

Be patient, it might take a few seconds for the stream to start, but soon, a video player will appear and Sintel will start playing!

Congratulations! You’ve made your very own torrent streaming engine for the browser. You can design it nicer, add some more features, package it up and install it on your server, and offer your own BrowserTorrent in the cloud!

If you wish to download the full source code of this project, you can do so here.

Thanks a lot for reading this tutorial. If you encounter any issues, don’t hesitate to comment or contact me.


Comments (9)

Please be civil when commenting. Think before writing, don't spam, self promote, bully, harass or harm anyone. Please read the Comment Policy before posting. Comments are moderated.

André Cavalheiro

André Cavalheiro

Hey there, i lnow it's been a while but i hope you're still answering
questions. i'd like to understand why is it that you do not use the
magnet URL provided by the user, but instead build it yourself. I know
that the infoash is the true 'unique ID' of the torrent, but isn't the
rest of the magnet link important? You add your own trackers to the
link, does this work 100% of the time?? And if so why do it?

Reply · 3 years ago · #58

Linker Linker

Linker Linker

Maybe the one post where I can see some code examples! ARE there any other posts about webtorrent?

Reply · 4 years ago · #9

Milan Kragujevic

...replying to Brian

Milan Kragujevic Admin

Sorry for the delay, but the file has been uploaded. I lost it but today I found it on my old HDD.

Reply · 3 years ago · #10

Mehdi Toumi

Mehdi Toumi

Hello Milan,

I tried your solution and it is working like a charm. I was thinking about a solution to the mkv problem. Since HTML5 don't support it and that npapi plugin are also no longer supported, we need to transcode what need to be transcoded.

You were talking about making a tutorial for this particular issue in order to stream files into webm to the client. Do you have exemples to provide, or a link to a github representing this project ?

Thank you

Reply · 4 years ago · #4

Milan Kragujevic

...replying to Mehdi Toumi

Milan Kragujevic Admin


Later today I will publish two tutorials. One is an updated version of this one, for Apple macOS, with updated user interface, and the second is the transcoding one.

Please note, if you transcode the videos, the user cannot seek because the video isn't yet done rendering. Also, I recommend using Flash Video (FLV, H264+AAC), because it doesn't have a MOOV atom like MP4. Web Video (WEBM, VP8+Vorbis, VP9+Opus) is possible, however it is buggy and sometimes crashes.

Thank you for your comment.

Reply · 4 years ago · #5

Filipe Aguiar

Filipe Aguiar

Nice tutorial. But it only works with mp4 files?

Reply · 4 years ago · #1

Milan Kragujevic

...replying to Filipe Aguiar

Milan Kragujevic Admin

Thank you for your comment. It only works for mp4 files, unfortunately. I will be publishing a new tutorial with built in transcoding via FFmpeg into webm (vp8+vorbis), so make sure to check my blog in a few days.

Reply · 4 years ago · #2

Filipe Aguiar

...replying to Milan Kragujevic

Filipe Aguiar

Thank you for your quick response. I'm a old school php programmer and I'm trying to learn the newer technologies like electron and node + express. But if I'm learning, why not build something cool, uh?
So I thought: why not build an Electron app that, given the magnet, streams a video (and maybe download the subtitle using OpenSubtitles API) ?

Reply · 4 years ago · #3

Scroll to top