├── README.md ├── .gitignore ├── package.json ├── index.js ├── streamers ├── youtube.js ├── base.js ├── http.js └── torrent.js └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | popcorn-streamer 2 | ================ 3 | 4 | The streaming library used by Popcorn Time to stream different media sources 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "popcorn-streamer", 3 | "version": "0.1.0", 4 | "description": "The streaming library used by Popcorn Time to stream different media sources", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/popcorn-official/popcorn-streamer.git" 9 | }, 10 | "author": "XeonCore", 11 | "license": "LGPL-3", 12 | "bugs": { 13 | "url": "https://github.com/popcorn-official/popcorn-streamer/issues" 14 | }, 15 | "homepage": "https://github.com/popcorn-official/popcorn-streamer", 16 | "dependencies": { 17 | "URIjs": "^1.13.2", 18 | "progress-stream": "^0.5.0", 19 | "read-torrent": "^1.0.0", 20 | "request": "^2.35.0", 21 | "streamify": "^0.2.3", 22 | "torrent-stream": "^0.10.2", 23 | "ytdl": "^0.2.8" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* File Streamer from multiple source protocols */ 2 | var URI = require('URIjs'); 3 | 4 | var HTTPStreamer = require('./streamers/http') 5 | , YouTubeStreamer = require('./streamers/youtube') 6 | , TorrentStreamer = require('./streamers/torrent') 7 | 8 | module.exports = { 9 | getStreamer: function(source, type) { 10 | var uri = URI(source); 11 | if(uri.domain() === 'youtu.be' || uri.domain() === 'youtube.com' || type === 'youtube') { 12 | return YouTubeStreamer.bind(null, source); 13 | } else if(uri.protocol() === 'magnet' || uri.suffix() === 'torrent' || type === 'torrent') { 14 | return TorrentStreamer.bind(null, source); 15 | } else if(uri.protocol() === 'http' || uri.protocol() === 'https' || type === 'http') { 16 | return HTTPStreamer.bind(null, source); 17 | } 18 | throw new Error('Unsupported Source Type'); 19 | } 20 | } -------------------------------------------------------------------------------- /streamers/youtube.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits 2 | , ytdl = require('ytdl'); 3 | 4 | var Streamer = require('./base'); 5 | 6 | /* -- YouTube Streamer -- */ 7 | function YoutubeStreamer(source, options) { 8 | if(!(this instanceof YoutubeStreamer)) 9 | return new YoutubeStreamer(source, options); 10 | 11 | Streamer.call(this, options); 12 | var self = this; 13 | options = options || {}; 14 | 15 | this._options = options; 16 | this._source = source; 17 | this._video = ytdl(source, {quality: options.hd ? 22 : 18}); 18 | this._video.on('info', function(info, format) { 19 | self._progress.setLength(format.size); 20 | }) 21 | this._streamify.resolve(this._video); 22 | } 23 | inherits(YoutubeStreamer, Streamer); 24 | 25 | YoutubeStreamer.prototype.seek = function(start, end) { 26 | if(this._destroyed) throw new ReferenceError('Streamer already destroyed'); 27 | 28 | var self = this; 29 | start = start || 0; 30 | 31 | this._video = ytdl(this._source, {quality: this._options.hd ? 22 : 18, range: start + '-' + (end !== undefined ? end : '')}); 32 | this._video.on('info', function(info, format) { 33 | self._progress.setLength(format.size); 34 | }) 35 | 36 | this._streamify.unresolve(); 37 | this._streamify.resolve(this._video); 38 | } 39 | 40 | YoutubeStreamer.prototype.destroy = function() { 41 | if(this._destroyed) throw new ReferenceError('Streamer already destroyed'); 42 | 43 | this._streamify.unresolve(); 44 | this._video = null; 45 | this._destroyed = true; 46 | } 47 | 48 | module.exports = YoutubeStreamer; -------------------------------------------------------------------------------- /streamers/base.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits 2 | , extend = require('extend') 3 | , PassThrough = require('stream').PassThrough 4 | , streamify = require('streamify') 5 | , progressStream = require('progress-stream'); 6 | 7 | /* -- Base Streamer -- */ 8 | function Streamer(options) { 9 | var self = this; 10 | 11 | options = options || {}; 12 | var progressOptions = { 13 | // Hack to allow people to pass the default in for time 14 | time: (options.progressInterval === -1 ? undefined : options.progressInterval) || 1000, 15 | speed: options.speedDelay || 5000 16 | } 17 | 18 | PassThrough.call(this); 19 | 20 | this._destroyed = false; 21 | 22 | this.downloaded = 0; 23 | this.progress = 0; 24 | this.downloadSpeed = 0; 25 | this.eta = Infinity; 26 | 27 | this._streamify = streamify(options.streamify); 28 | this._progress = progressStream(progressOptions); 29 | 30 | this._progress.on('progress', function(progress) { 31 | self.downloaded = progress.transferred; 32 | self.progress = progress.percentage; 33 | self.downloadSpeed = progress.speed; 34 | self.eta = progress.eta || Infinity; 35 | 36 | self.emit('progress', { 37 | downloaded: progress.transferred, 38 | progress: progress.percentage, 39 | downloadSpeed: progress.speed, 40 | eta: progress.eta || Infinity 41 | }) 42 | }) 43 | 44 | this._streamify.pipe(this._progress).pipe(this); 45 | } 46 | inherits(Streamer, PassThrough); 47 | 48 | Streamer.prototype.seek = function(start, end) { 49 | // Virtual function, implemented in child 50 | } 51 | 52 | Streamer.prototype.destroy = function() { 53 | // Virtual function, implemented in child 54 | } 55 | 56 | module.exports = Streamer; -------------------------------------------------------------------------------- /streamers/http.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits 2 | , request = require('request'); 3 | 4 | var Streamer = require('./base'); 5 | 6 | /* -- HTTP Streamer -- */ 7 | function HttpStreamer(source, options) { 8 | if(!(this instanceof HttpStreamer)) 9 | return new HttpStreamer(source, options); 10 | 11 | Streamer.call(this, options); 12 | var self = this; 13 | options = options || {}; 14 | 15 | this.request = request.defaults({ 16 | encoding: null 17 | }); 18 | 19 | this._options = options; 20 | this._source = source; 21 | this._req = this.request(source, options.http); 22 | this._req.on('response', function(res) { 23 | var length = self._req.getHeader('content-length', res.headers); 24 | if(length !== undefined) 25 | self._progress.setLength(parseInt(length)); 26 | }) 27 | this._streamify.resolve(this._req); 28 | } 29 | inherits(HttpStreamer, Streamer); 30 | 31 | HttpStreamer.prototype.seek = function(start, end) { 32 | if(this._destroyed) throw new ReferenceError('Streamer already destroyed'); 33 | 34 | var self = this; 35 | start = start || 0; 36 | 37 | if(this._req) 38 | this._req.destroy(); 39 | 40 | this._req = this.request(this._source, { 41 | headers: { 42 | 'Range': 'bytes=' + start + '-' + (end !== undefined ? end : '') 43 | } 44 | }).on('response', function(res) { 45 | var length = self._req.getHeader('content-length', res.headers); 46 | if(length !== undefined) 47 | self._progress.setLength(parseInt(length)); 48 | }) 49 | 50 | this._streamify.unresolve(); 51 | this._streamify.resolve(this._req); 52 | } 53 | 54 | HttpStreamer.prototype.destroy = function() { 55 | if(this._destroyed) throw new ReferenceError('Streamer already destroyed'); 56 | 57 | if(this._req) 58 | this._req.destroy(); 59 | this._streamify.unresolve(); 60 | this._req = null; 61 | this._destroyed = true; 62 | } 63 | 64 | module.exports = HttpStreamer; -------------------------------------------------------------------------------- /streamers/torrent.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits 2 | , torrentStream = require('torrent-stream') 3 | , readTorrent = require('read-torrent'); 4 | 5 | var Streamer = require('./base'); 6 | 7 | /* -- Torrent Streamer -- */ 8 | function TorrentStreamer(source, options) { 9 | if(!(this instanceof TorrentStreamer)) 10 | return new TorrentStreamer(source, options); 11 | 12 | Streamer.call(this, options); 13 | var self = this; 14 | options = options || {}; 15 | 16 | this._ready = false; 17 | 18 | readTorrent(source, function(err, torrent) { 19 | if(err) throw err; 20 | 21 | self._torrentStream = torrentStream(torrent, options.torrent); 22 | self._torrentStream.on('uninterested', function() { self._torrentStream.swarm.pause() }); 23 | self._torrentStream.on('interested', function() { self._torrentStream.swarm.resume() }); 24 | 25 | self._torrentStream.on('ready', function() { 26 | if (typeof options.fileIndex !== 'number') { 27 | index = self._torrentStream.files.reduce(function(a, b) { 28 | return a.length > b.length ? a : b; 29 | }); 30 | index = self._torrentStream.files.indexOf(index); 31 | } 32 | 33 | self._torrentStream.files[index].select(); 34 | self.file = self._torrentStream.files[index]; 35 | self._progress.setLength(self.file.length); 36 | self._streamify.resolve(self.file.createReadStream()); 37 | self._ready = true; 38 | }) 39 | }) 40 | } 41 | inherits(TorrentStreamer, Streamer); 42 | 43 | TorrentStreamer.prototype.seek = function(start, end) { 44 | if(this._destroyed) throw new ReferenceError('Streamer already destroyed'); 45 | if(!this._ready) return; 46 | 47 | var opts = { 48 | start: start 49 | } 50 | 51 | if(end !== undefined) { 52 | opts.end = end; 53 | } 54 | 55 | this._streamify.unresolve(); 56 | this._streamify.resolve(this.file.createReadStream(opts)); 57 | } 58 | 59 | TorrentStreamer.prototype.destroy = function() { 60 | if(this._destroyed) throw new ReferenceError('Streamer already destroyed'); 61 | 62 | this._torrentStream.destroy(); 63 | this._streamify.unresolve(); 64 | this._ready = false; 65 | this._torrentStream = null; 66 | this.file = null; 67 | this._destroyed = true; 68 | } 69 | 70 | module.exports = TorrentStreamer; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. --------------------------------------------------------------------------------