diff --git a/src/video/withStreamingServer.js b/src/video/withStreamingServer.js index ae9a85403..95739517b 100644 --- a/src/video/withStreamingServer.js +++ b/src/video/withStreamingServer.js @@ -2,6 +2,66 @@ var UrlUtils = require('url'); var EventEmitter = require('events'); +var parseVideoName = require('video-name-parser'); + +var VIDEO_FILE_EXTENTIONS = /.mkv$|.avi$|.mp4$|.wmv$|.vp8$|.mov$|.mpg$|.ts$|.webm$/i; + +function createTorrent(streamingServerUrl, infoHash, sources) { + return fetch(UrlUtils.resolve(streamingServerUrl, `/${encodeURIComponent(infoHash)}/create`), { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + torrent: { + infoHash, + peerSearch: { + sources: [`dht:${infoHash}`].concat(Array.isArray(sources) ? sources : []), + min: 40, + max: 150 + } + } + }) + }).then(function(resp) { + return resp.json(); + }).catch(function(error) { + throw { + message: 'Unable to get files from torrent', + critical: true, + error: error + }; + }).then(function(resp) { + if (!resp || !Array.isArray(resp.files) || resp.files.length === 0) { + throw { + message: 'Unable to get files from torrent', + critical: true + }; + } + + return resp; + }); +} + +function guessFileIdx(files, seriesInfo) { + var videoFilesForEpisode = files.filter(function(file) { + if (seriesInfo && file.path.match(VIDEO_FILE_EXTENTIONS)) { + try { + var info = parseVideoName(file.path); + return !isNaN(info.season) && Array.isArray(info.episode) && + info.season === seriesInfo.season && info.episode.indexOf(seriesInfo.episode) !== -1; + } catch (e) { + return false; + } + } + }); + var largestFile = (videoFilesForEpisode.length > 0 ? videoFilesForEpisode : files) + .reduce((result, file) => { + if (!result || file.length > result.length) { + return file; + } + + return result; + }, null); + return files.indexOf(largestFile); +} function withStreamingServer(Video) { function StreamingServerVideo(options) { @@ -9,156 +69,109 @@ function withStreamingServer(Video) { var events = new EventEmitter(); var destroyed = false; - var loaded = false; var stream = null; - var dispatchArgsLoadingQueue = []; events.on('error', function() { }); function onError(error) { - if (!error) { - return; - } - - Object.freeze(error); events.emit('error', error); if (error.critical) { - dispatch({ commandName: 'stop' }); + stop(); + video.dispatch({ commandName: 'stop' }); } } - function flushDispatchArgsQueue(dispatchArgsQueue) { - while (dispatchArgsQueue.length > 0) { - var args = dispatchArgsQueue.shift(); - dispatch(args); - } + function stop() { + stream = null; } - function on(eventName, listener) { + function load(args) { + video.dispatch({ commandName: 'stop' }); + stream = args.stream; + new Promise(function(resolve, reject) { + if (typeof args.stream.ytId === 'string') { + resolve(UrlUtils.resolve(args.streamingServerUrl, `/yt/${encodeURIComponent(args.stream.ytId)}?${new URLSearchParams([['request', Date.now()]])}`)); + return; + } + + if (typeof args.stream.infoHash === 'string') { + if (args.stream.fileIdx !== null && !isNaN(args.stream.fileIdx)) { + resolve(UrlUtils.resolve(args.streamingServerUrl, `/${args.stream.infoHash}/${args.stream.fileIdx}`)); + } else { + createTorrent(args.streamingServerUrl, args.stream.infoHash, args.stream.sources) + .then(function(resp) { + var fileIdx = guessFileIdx(resp.files, args.stream.seriesInfo); + resolve(UrlUtils.resolve(args.streamingServerUrl, `/${args.stream.infoHash}/${fileIdx}`)); + }) + .catch(function(error) { + reject(error); + }); + } + return; + } + + reject({ + message: 'Unable to play stream', + critical: true, + stream: args.stream + }); + }).then(function(url) { + if (destroyed || args.stream !== stream) { + return; + } + + video.dispatch({ + commandName: 'load', + commandArgs: { + autoplay: args.autoplay, + time: args.time, + stream: { + url: url + } + } + }); + }).catch(function(error) { + if (destroyed || args.stream !== stream) { + return; + } + + onError(error); + }); + } + function destroy() { + stop(); + destroyed = true; + events.removeAllListeners(); + events.on('error', function() { }); + } + + this.on = function(eventName, listener) { if (!destroyed) { events.on(eventName, listener); } video.on(eventName, listener); - } - function dispatch(args) { + }; + this.dispatch = function(args) { if (!destroyed && args) { if (typeof args.commandName === 'string') { switch (args.commandName) { - case 'addSubtitlesTracks': { - if (!loaded && stream !== null) { - dispatchArgsLoadingQueue.push(args); - return; - } - - break; - } case 'stop': { - loaded = false; - stream = null; - dispatchArgsLoadingQueue = []; + stop(); break; } case 'load': { - dispatch({ commandName: 'stop' }); - if (args.commandArgs && typeof args.commandArgs.streamingServerUrl === 'string' && args.commandArgs.stream) { - if (typeof args.commandArgs.stream.infoHash === 'string') { - stream = args.commandArgs.stream; - if (stream.fileIdx !== null && !isNaN(stream.fileIdx)) { - video.dispatch({ - commandName: 'load', - commandArgs: { - autoplay: args.commandArgs.autoplay, - time: args.commandArgs.time, - stream: { - url: UrlUtils.resolve(args.commandArgs.streamingServerUrl, stream.infoHash + '/' + String(stream.fileIdx)) - } - } - }); - loaded = true; - } else { - fetch(UrlUtils.resolve(args.commandArgs.streamingServerUrl, stream.infoHash + '/create'), { - method: 'POST', - headers: { - 'content-type': 'application/json' - }, - body: JSON.stringify({ - torrent: { - infoHash: stream.infoHash - } - }) - }).then(function(resp) { - return resp.json(); - }).then(function(resp) { - if (stream !== args.commandArgs.stream) { - return; - } - - if (!Array.isArray(resp.files) || resp.files.length === 0) { - onError({ - message: 'Unable to get files from torrent', - critical: true - }); - return; - } - - var fileIdx = resp.files.reduce((fileIdx, _, index, files) => { - if (files[index].length > files[fileIdx].length) { - return index; - } - - return fileIdx; - }, 0); - video.dispatch({ - commandName: 'load', - commandArgs: { - autoplay: args.commandArgs.autoplay, - time: args.commandArgs.time, - stream: { - url: UrlUtils.resolve(args.commandArgs.streamingServerUrl, stream.infoHash + '/' + String(fileIdx)) - } - } - }); - loaded = true; - flushDispatchArgsQueue(dispatchArgsLoadingQueue); - }).catch(function(error) { - if (stream !== args.commandArgs.stream) { - return; - } - - onError({ - message: 'Unable to get files from torrent', - critical: true, - error: error - }); - }); - } - - return; - } - } - - break; + load(args.commandArgs); + return; } case 'destroy': { - dispatch({ commandName: 'stop' }); - destroyed = true; - events.removeAllListeners(); - events.on('error', function() { }); + destroy(); break; } } - } else if (typeof args.propName === 'string') { - if (!loaded && stream !== null && ['paused', 'time', 'selectedSubtitlesTrackId', 'subtitlesDelay'].indexOf(args.propName) !== -1) { - dispatchArgsLoadingQueue.push(args); - return; - } } } video.dispatch(args); - } - - this.on = on; - this.dispatch = dispatch; + }; Object.freeze(this); }