├── .dockerignore
├── .env
├── .github
└── FUNDING.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Procfile
├── README.md
├── app.js
├── app.json
├── bin
├── pre_www
└── www
├── package.json
├── routes
├── index.js
└── video.js
└── views
└── index.hjs
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidgatti/How-to-Stream-Torrents-using-NodeJS/4bf801e105fee7464e17d3b599922b54da5cafb2/.env
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: https://paypal.me/gattidavid
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 | *.DS_Store
39 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at github@gatti.pl. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # First off
2 |
3 | Thank you for considering contributing to this project 😀. All my project have one goal, to teach how to write simpler, easier to understand code to the point that a non technical person will understand it. For example:
4 |
5 | - The way I write my comments is on purpose, because I found that this way is the easiest way for a brain to filter out and understand
6 | - In JavaScript code I just use `let` instead of `const` so no one will get confused, I want people to focus on the article or example, instead of why I used `let` here, and `const` there.
7 | - On purpose I use as little variables as possible to yet again, not confuse anybody.
8 |
9 | # What I believe in
10 |
11 | - Making everything simpler for everyone to understand.
12 | - Frameworks force you to be organized, instead of teaching you how to be organized.
13 |
14 | # Childish Code
15 |
16 | A way to say that code should be understood even by a child. Write simple to understand code. Don't make it obscure, and be the only one that can understand it. Be mindful of other people, and their time.
17 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | #
2 | # Base Image
3 | #
4 | FROM debian:8.6
5 |
6 | #
7 | # Basic Setup
8 | #
9 | MAINTAINER David Gatti
10 |
11 | #
12 | # preparing the environment
13 | #
14 | RUN apt-get update
15 | RUN apt-get upgrade -y
16 | RUN apt-get install -y curl
17 | RUN apt-get install -y sudo
18 | RUN curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
19 | RUN apt-get install -y nodejs
20 |
21 | #
22 | # Create app directory
23 | #
24 | RUN mkdir -p /home/app
25 |
26 | #
27 | # Copy the content of the app
28 | #
29 | COPY . /home/app
30 |
31 | #
32 | # Switch working directory
33 | #
34 | WORKDIR /home/app
35 |
36 | #
37 | # Prepare the app by installing all the dependencies
38 | #
39 | RUN npm install
40 |
41 | #
42 | # Run the app
43 | #
44 | CMD [ "npm", "start" ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 David Gatti
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: bin/pre_www
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🍿 How to stream Torrent movies using NodeJS and the HTML5 video tag
2 |
3 | After working on the [Understanding Streams in NodeJS](https://github.com/davidgatti/Understanding-Streams-in-NodeJS) article, I moved on to [How to Stream Movies using NodeJS](https://github.com/davidgatti/How-to-Stream-Movies-using-NodeJS). Then I thought... *Can I stream a Torrent in real time*? Turns out that yes, I can! And it's surprisingly easy, thanks to the [WebTorrent](https://webtorrent.io) npm module.
4 |
5 | And, to be honest, I'm completely blown away by this technology. I mean, the potential here is endless. I still can't believe that I didn't get it sooner. But better now than never. ;)
6 |
7 | # What is this repository
8 |
9 | This repo is a tech demo/proof of concept/article showcasing what can you do using NodeJS and the WebTorrent module. This is also a good example of how powerful the idea of streams is in general. Because you'll see that even before the file is completely downloaded to the disk, we can read the chunks of data that we have. Proving that we don't need the whole thing to stream data to the client.
10 |
11 | # Deployment
12 |
13 |
14 |
15 |
16 | Follow the instructions on the Heroku main app page, and then once the app is deployed, visit the main page. Once you do that, you should see the site, with some examples of free Magnet Hashes so you can test out the live streaming.
17 |
18 | # Where to start
19 |
20 | 1. Visit the home page of your deployment
21 | 1. Click on one of the demo Hashes
22 | 1. You should see the Client Stats start to show some stats
23 | 1. In a moment, you should see the content of the Magnet Hash
24 | 1. Select a movie file from the Magnet Contents section
25 | 1. After few seconds, you should see the video player buffering the movie
26 | 1. It should start playing shortly
27 |
28 | And there you have it! Proof that you are streaming a Torrent live.
29 |
30 | # High level explanation
31 |
32 | We know how we can stream binary files, thanks to the `.createReadStream()` method, which gives us the possibility of specifying how much data should be read by setting the starting and finishing position of the chunk that we are interested in.
33 |
34 | With `.createReadStream()`, we don't care if the whole file is actually on the local drive, until we have the right section, we can read it and send it, while in the background the file is being constantly downloaded.
35 |
36 | This also proves that we're actually dealing with only chunks, and are not loading the whole file into memory. If you want to learn more about streams in NodeJS, check out my previous article titled [How to Stream Movies using NodeJS](https://github.com/davidgatti/How-to-Stream-Movies-using-NodeJS).
37 |
38 |
39 | # How to understand the code
40 |
41 | The code is composed of two parts: We have the front-end and we have the back-end. Shocking, I know. The good part is that the front has no UI design and only two lines of CSS. You can focus on the code itself. Aside from having very detailed comments, the code is also numbered to help you follow what is happening within each step.
42 |
43 | The back-end is similar. If you read the [How to Stream Movies using NodeJS](https://github.com/davidgatti/How-to-Stream-Movies-using-NodeJS) article first, you see only new code related to the [WebTorrent](https://webtorrent.io) module. Meaning that if you feel confused, you should check [How to Stream Movies using NodeJS](https://github.com/davidgatti/How-to-Stream-Movies-using-NodeJS) first to understand the basics of streaming videos with NodeJS.
44 |
45 | # The End
46 |
47 | If you enjoyed this project, please consider giving it a 🌟. And check out my [GitHub account](https://github.com/davidgatti), where you'll find additional resources you might find useful or interesting.
48 |
49 | ## Sponsor 🎊
50 |
51 | This project is brought to you by 0x4447 LLC, a software company specializing in building custom solutions on top of AWS. Follow this link to learn more: https://0x4447.com. Alternatively, send an email to [hello@0x4447.email](mailto:hello@0x4447.email?Subject=Hello%20From%20Repo&Body=Hi%2C%0A%0AMy%20name%20is%20NAME%2C%20and%20I%27d%20like%20to%20get%20in%20touch%20with%20someone%20at%200x4447.%0A%0AI%27d%20like%20to%20discuss%20the%20following%20topics%3A%0A%0A-%20LIST_OF_TOPICS_TO_DISCUSS%0A%0ASome%20useful%20information%3A%0A%0A-%20My%20full%20name%20is%3A%20FIRST_NAME%20LAST_NAME%0A-%20My%20time%20zone%20is%3A%20TIME_ZONE%0A-%20My%20working%20hours%20are%20from%3A%20TIME%20till%20TIME%0A-%20My%20company%20name%20is%3A%20COMPANY%20NAME%0A-%20My%20company%20website%20is%3A%20https%3A%2F%2F%0A%0ABest%20regards.).
52 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | let path = require('path');
2 | let cors = require('cors');
3 | let logger = require('morgan');
4 | let express = require('express');
5 | let bodyParser = require('body-parser');
6 |
7 | let app = express();
8 |
9 | //
10 | // Set public paths
11 | //
12 | app.set('views', path.join(__dirname, 'views'));
13 |
14 | //
15 | // Set the front end rendering engine
16 | //
17 | app.set('view engine', 'hjs');
18 |
19 | //
20 | // Add cors to make jQuery API requests
21 | //
22 | app.use(cors());
23 |
24 | //
25 | // Check for HTTPS
26 | //
27 | app.use(force_https);
28 |
29 | //
30 | // Expose the public folder to the world
31 | //
32 | app.use(express.static(path.join(__dirname, 'public')));
33 |
34 | //
35 | // Remove the information about what type of framework is the site running on
36 | //
37 | app.disable('x-powered-by');
38 |
39 | //
40 | // HTTP request logger middleware for node.js
41 | //
42 | app.use(logger('dev'));
43 |
44 | //
45 | // Parse all request as regular text, and not JSON objects
46 | //
47 | app.use(bodyParser.json());
48 |
49 | //
50 | // Parse application/x-www-form-urlencoded
51 | //
52 | app.use(bodyParser.urlencoded({ extended: false }));
53 |
54 | //////////////////////////////////////////////////////////////////////////////
55 |
56 | app.use('/', require('./routes/index'));
57 | app.use('/video', require('./routes/video'));
58 |
59 | //////////////////////////////////////////////////////////////////////////////
60 |
61 | //
62 | //
63 | // If nonce of the above routes matches, we create an error to let the
64 | // user know that the URL accessed doesn't match anything.
65 | //
66 | app.use(function(req, res, next) {
67 |
68 | let err = new Error('Not Found');
69 | err.status = 404;
70 |
71 | next(err);
72 |
73 | });
74 |
75 | //
76 | // Display any error that occurred during the request.
77 | //
78 | app.use(function(err, req, res, next) {
79 |
80 | //
81 | // 1. Set the basic information about the error, that is going to be
82 | // displayed to user and developers regardless.
83 | //
84 | let obj_message = {
85 | message: err.message
86 | };
87 |
88 | //
89 | // 2. Check if the environment is development, and if it is we
90 | // will display the stack-trace
91 | //
92 | if(process.env.NODE_ENV == 'development')
93 | {
94 | //
95 | // 1. Set the variable to show the stack-trace to the developer
96 | //
97 | obj_message.error = err;
98 |
99 | //
100 | // -> Show the error in the console
101 | //
102 | console.error(err);
103 | }
104 |
105 | //
106 | // 3. Display a default status error, or pass the one from
107 | // the error message
108 | //
109 | res.status(err.status || 500);
110 |
111 | //
112 | // -> Show the error
113 | //
114 | res.json(obj_message);
115 |
116 | });
117 |
118 | // _ _ ______ _ _____ ______ _____ _____
119 | // | | | | ____| | | __ \| ____| __ \ / ____|
120 | // | |__| | |__ | | | |__) | |__ | |__) | (___
121 | // | __ | __| | | | ___/| __| | _ / \___ \
122 | // | | | | |____| |____| | | |____| | \ \ ____) |
123 | // |_| |_|______|______|_| |______|_| \_\_____/
124 | //
125 |
126 | //
127 | // Check if the connection is secure, if not, redirect to a secure one.
128 | //
129 | function force_https(req, res, next)
130 | {
131 | //
132 | // 1. Redirect only in the production environment
133 | //
134 | if(process.env.NODE_ENV == 'production')
135 | {
136 | //
137 | // 1. Check what protocol are we using
138 | //
139 | if(req.headers['x-forwarded-proto'] !== 'https')
140 | {
141 | //
142 | // -> Redirect the user to the same URL that he requested, but
143 | // with HTTPS instead of HTTP
144 | //
145 | return res.redirect('https://' + req.get('host') + req.url);
146 | }
147 | }
148 |
149 | //
150 | // 2. If the protocol is already HTTPS the, we just keep going.
151 | //
152 | next();
153 | }
154 |
155 | module.exports = app;
156 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stream-torrents-in-nodejs",
3 | "description": "How to Stream Torrent Movies using NodeJS and the HTML5 Video tag",
4 | "repository": "https://github.com/davidgatti/How-to-Stream-Torrents-in-NodeJS",
5 | "success_url": "/",
6 | "env": {
7 | "NODE_ENV": {
8 | "description": "The environment can be: production, staging, development or local (defaults to production)",
9 | "value": "production"
10 | },
11 | "NPM_CONFIG_PRODUCTION": {
12 | "description": "By default Heroku sets this to true, if you are deploying a dev version of the code, set this false.",
13 | "value": "true"
14 | },
15 | "BASE_URL": {
16 | "description": "The URL of the project",
17 | "value": "https://NAME_OF_THE_SERVER.herokuapp.com"
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/bin/pre_www:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | if [ "$NODE_ENV" = "development" ]; then
3 | nodemon ./bin/www
4 | else
5 | node ./bin/www
6 | fi
7 |
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | let app = require('../app');
4 | let http = require('http');
5 | let cluster = require('cluster');
6 |
7 | let server;
8 |
9 | //
10 | // 1. Get port from environment variables, if it is not set, we can use the
11 | // default port.
12 | //
13 | let port = process.env.PORT || 3000;
14 |
15 | //
16 | // 2. Assign the port to the app.
17 | //
18 | app.set('port', port);
19 |
20 | //
21 | // 3. Create, meaning spawn multiple instances of the same app to take
22 | // advantage of multi-core processors.
23 | //
24 | // IF the cluster is the master one, then this is the cluster
25 | // that ins control. This means that the master will spawn
26 | // children processes, while also listening for crashes.
27 | //
28 | // If a process exits, then we span a new process in it place
29 | // so we can make sure that we constantly have X amount of
30 | // processes.
31 | //
32 | // ELSE The spawned processes on the other hand are those that creates
33 | // servers and do the heavy lifting.
34 | //
35 | if(cluster.isMaster) {
36 |
37 | //
38 | // 1. Count the machine's CPUs.
39 | //
40 | let cpuCount = 1 //require('os').cpus().length;
41 |
42 | //
43 | // 3. Create a worker for each CPU core.
44 | //
45 | while(cpuCount--)
46 | {
47 | cluster.fork();
48 | }
49 |
50 | //
51 | // 4. Listen for when the process exits because of a crash, and spawn
52 | // a new process in it place.
53 | //
54 | cluster.on('exit', function (worker) {
55 |
56 | //
57 | // 1. If a worker dies, lets create a new one to replace him
58 | //
59 | cluster.fork();
60 |
61 | //
62 | // -> Log what happened
63 | //
64 | console.log('Worker %d died :(', worker.id);
65 |
66 | });
67 | }
68 | else
69 | {
70 | //
71 | // 1. Create HTTP server.
72 | //
73 | server = http.createServer(app);
74 |
75 | //
76 | // 2. Listen on provided port, on all network interfaces.
77 | //
78 | server.listen(port);
79 |
80 | //
81 | // 3. React to error events
82 | //
83 | server.on('error', onError);
84 |
85 | //
86 | // 4. listen to incoming requests
87 | //
88 | server.on('listening', onListening);
89 | }
90 |
91 | //
92 | // Event listener for HTTP server "error" event.
93 | //
94 | function onError(error)
95 | {
96 | if(error.syscall !== 'listen') {
97 | throw error;
98 | }
99 |
100 | //
101 | // handle specific listen errors with friendly messages
102 | //
103 | switch(error.code)
104 | {
105 | case 'EACCES':
106 | console.error("Port %d requires elevated privileges", port);
107 | process.exit(1);
108 | break;
109 | case 'EADDRINUSE':
110 | console.error("Port %d is already in use", port);
111 | process.exit(1);
112 | break;
113 | default:
114 | throw error;
115 | }
116 | }
117 |
118 | //
119 | // Event listener for HTTP server "listening" event.
120 | //
121 | function onListening() {
122 |
123 | //
124 | // -> display in the console where to look for the server
125 | //
126 | console.log("Worker %d is listening on port %d", cluster.worker.id, port);
127 | }
128 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "how-to-stream-a-movie-using-nodejs",
3 | "version": "1.0.0",
4 | "private": true,
5 | "engines": {
6 | "node": "6.9.1"
7 | },
8 | "scripts": {
9 | "start": "nf start"
10 | },
11 | "dependencies": {
12 | "body-parser": "1.15.1",
13 | "cors": "^2.8.1",
14 | "express": "4.13.4",
15 | "hjs": "0.0.6",
16 | "morgan": "1.9.1",
17 | "webtorrent": "^0.107.16"
18 | },
19 | "devDependencies": {
20 | "nodemon": "1.11.0",
21 | "foreman": "3.0.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | let express = require('express');
2 |
3 | let router = express.Router();
4 |
5 | router.get('/', function(req, res, next) {
6 |
7 | //
8 | // -> Display the index view with the video tag
9 | //
10 | res.render("index", {
11 | base_url: process.env.BASE_URL
12 | });
13 |
14 | });
15 |
16 | module.exports = router;
17 |
--------------------------------------------------------------------------------
/routes/video.js:
--------------------------------------------------------------------------------
1 | let fs = require("fs")
2 | let path = require("path");
3 | let express = require('express');
4 | let WebTorrent = require('webtorrent')
5 |
6 | let router = express.Router();
7 |
8 | //
9 | // 1. When the server starts create a WebTorrent client
10 | //
11 | let client = new WebTorrent();
12 |
13 | //
14 | // 2. The object that holds the client stats to be displayed in the front end
15 | // using an API call every n amount of time using jQuery.
16 | //
17 | let stats = {
18 | progress: 0,
19 | downloadSpeed: 0,
20 | ratio: 0
21 | }
22 |
23 | //
24 | // 3. The variable that holds the error message from the client. Farly crude but
25 | // I don't expect to much happening hear aside the potential to add the same
26 | // Magnet Hash twice.
27 | //
28 | let error_message = "";
29 |
30 | //
31 | // 4. Listen for any potential client error and update the above variable so
32 | // the front end can display it in the browser.
33 | //
34 | client.on('error', function(err) {
35 |
36 | error_message = err.message;
37 |
38 | });
39 |
40 | //
41 | // 5. Emitted by the client whenever data is downloaded. Useful for reporting the
42 | // current torrent status of the client.
43 | //
44 | client.on('download', function(bytes) {
45 |
46 | //
47 | // 1. Update the object with fresh data
48 | //
49 | stats = {
50 | progress: Math.round(client.progress * 100 * 100) / 100,
51 | downloadSpeed: client.downloadSpeed,
52 | ratio: client.ratio
53 | }
54 |
55 | });
56 |
57 | //
58 | // API call that adds a new Magnet Hash to the client so it can start
59 | // downloading it.
60 | //
61 | // magnet -> Magnet Hash
62 | //
63 | // return <- An array with a list of files
64 | //
65 | router.get('/add/:magnet', function(req, res) {
66 |
67 | //
68 | // 1. Extract the magnet Hash and save it in a meaningful variable.
69 | //
70 | let magnet = req.params.magnet;
71 |
72 | //
73 | // 2. Add the magnet Hash to the client
74 | //
75 | client.add(magnet, function (torrent) {
76 |
77 | //
78 | // 1. The array that will hold the content of the Magnet Hash.
79 | //
80 | let files = [];
81 |
82 | //
83 | // 2. Loop over all the file that are inside the Magnet Hash and add
84 | // them to the above variable.
85 | //
86 | torrent.files.forEach(function(data) {
87 |
88 | files.push({
89 | name: data.name,
90 | length: data.length
91 | });
92 |
93 | });
94 |
95 | //
96 | // -> Once we have all the data send it back to the browser to be
97 | // displayed.
98 | //
99 | res.status(200)
100 | res.json(files);
101 |
102 | });
103 |
104 | });
105 |
106 | //
107 | // The API call to start streaming the selected file to the video tag.
108 | //
109 | // magnet -> Magnet Hash
110 | // file_name -> the selected file name that is within the Magnet Hash
111 | //
112 | // return <- A chunk of the video file as buffer (binary data)
113 | //
114 | router.get('/stream/:magnet/:file_name', function(req, res, next) {
115 |
116 | //
117 | // 1. Extract the magnet Hash and save it in a meaningful variable.
118 | //
119 | let magnet = req.params.magnet;
120 |
121 | //
122 | // 2. Returns the torrent with the given torrentId. Convenience method.
123 | // Easier than searching through the client.torrents array. Returns
124 | // null if no matching torrent found.
125 | //
126 | var tor = client.get(magnet);
127 |
128 | //
129 | // 3. Variable that will store the user selected file
130 | //
131 | let file = {};
132 |
133 | //
134 | // 4. Loop over all the files contained inside a Magnet Hash and find the one
135 | // the user selected.
136 | //
137 | for(i = 0; i < tor.files.length; i++)
138 | {
139 | if(tor.files[i].name == req.params.file_name)
140 | {
141 | file = tor.files[i];
142 | }
143 | }
144 |
145 | //
146 | // 5. Save the range the browser is asking for in a clear and
147 | // reusable variable
148 | //
149 | // The range tells us what part of the file the browser wants
150 | // in bytes.
151 | //
152 | // EXAMPLE: bytes=65534-33357823
153 | //
154 | let range = req.headers.range;
155 |
156 | console.log(range);
157 |
158 | //
159 | // 6. Make sure the browser ask for a range to be sent.
160 | //
161 | if(!range)
162 | {
163 | //
164 | // 1. Create the error
165 | //
166 | let err = new Error("Wrong range");
167 | err.status = 416;
168 |
169 | //
170 | // -> Send the error and stop the request.
171 | //
172 | return next(err);
173 | }
174 |
175 | //
176 | // 7. Convert the string range in to an array for easy use.
177 | //
178 | let positions = range.replace(/bytes=/, "").split("-");
179 |
180 | //
181 | // 8. Convert the start value in to an integer
182 | //
183 | let start = parseInt(positions[0], 10);
184 |
185 | //
186 | // 9. Save the total file size in to a clear variable
187 | //
188 | let file_size = file.length;
189 |
190 | //
191 | // 10. IF the end parameter is present we convert it in to an
192 | // integer, the same way we did the start position
193 | //
194 | // ELSE We use the file_size variable as the last part to be
195 | // sent.
196 | //
197 | let end = positions[1] ? parseInt(positions[1], 10) : file_size - 1;
198 |
199 | //
200 | // 11. Calculate the amount of bits will be sent back to the
201 | // browser.
202 | //
203 | let chunksize = (end - start) + 1;
204 |
205 | //
206 | // 12. Create the header for the video tag so it knows what is
207 | // receiving.
208 | //
209 | let head = {
210 | "Content-Range": "bytes " + start + "-" + end + "/" + file_size,
211 | "Accept-Ranges": "bytes",
212 | "Content-Length": chunksize,
213 | "Content-Type": "video/mp4"
214 | }
215 |
216 | //
217 | // 13. Send the custom header
218 | //
219 | res.writeHead(206, head);
220 |
221 | //
222 | // 14. Create the createReadStream option object so createReadStream
223 | // knows how much data it should be read from the file.
224 | //
225 | let stream_position = {
226 | start: start,
227 | end: end
228 | }
229 |
230 | //
231 | // 15. Create a stream chunk based on what the browser asked us for
232 | //
233 | let stream = file.createReadStream(stream_position)
234 |
235 | //
236 | // 16. Pipe the video chunk to the request back to the request
237 | //
238 | stream.pipe(res);
239 |
240 | //
241 | // -> If there was an error while opening a stream we stop the
242 | // request and display it.
243 | //
244 | stream.on("error", function(err) {
245 |
246 | return next(err);
247 |
248 | });
249 |
250 | });
251 |
252 | //
253 | // The API call that gets all the Magnet Hashes that the client is actually
254 | // having.
255 | //
256 | // return <- An array with all the Magnet Hashes
257 | //
258 | router.get('/list', function(req, res, next) {
259 |
260 | //
261 | // 1. Loop over all the Magnet Hashes
262 | //
263 | let torrent = client.torrents.reduce(function(array, data) {
264 |
265 | array.push({
266 | hash: data.infoHash
267 | });
268 |
269 | return array;
270 |
271 | }, []);
272 |
273 | //
274 | // -> Return the Magnet Hashes
275 | //
276 | res.status(200);
277 | res.json(torrent);
278 |
279 | });
280 |
281 | //
282 | // The API call that sends back the stats of the client
283 | //
284 | // return <- A object with the client stats
285 | //
286 | router.get('/stats', function(req, res, next) {
287 |
288 | res.status(200);
289 | res.json(stats);
290 |
291 | });
292 |
293 | //
294 | // The API call that gets errors that occurred with the client
295 | //
296 | // return <- A a string with the error
297 | //
298 | router.get('/errors', function(req, res, next) {
299 |
300 | res.status(200);
301 | res.json(error_message);
302 |
303 | });
304 |
305 | //
306 | // The API call to delete a Magnet Hash from the client.
307 | //
308 | // magnet -> Magnet Hash
309 | //
310 | // return <- Just the status of the request
311 | //
312 | router.get('/delete/:magnet', function(req, res, next) {
313 |
314 | //
315 | // 1. Extract the magnet Hash and save it in a meaningful variable.
316 | //
317 | let magnet = req.params.magnet;
318 |
319 | //
320 | // 2. Remove the Magnet Hash from the client.
321 | //
322 | client.remove(magnet, function() {
323 |
324 | res.status(200);
325 | res.end();
326 |
327 | });
328 |
329 | });
330 |
331 | module.exports = router;
332 |
--------------------------------------------------------------------------------
/views/index.hjs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Samples Torrents:
33 | 34 |Magnet Hash
44 | 45 |Magnet Contents:
46 | 47 |Client Magnets:
50 | 51 |Client Stats:
56 | 57 |Client Errors:
64 | 65 |