From 5bf74786c6fb2b713d794b24fb5a165d60f8a117 Mon Sep 17 00:00:00 2001 From: nklhrstv Date: Thu, 30 Apr 2020 11:39:17 +0300 Subject: [PATCH] video directory removed --- src/video/HTMLSubtitles.js | 314 ------------- src/video/HTMLVideo.js | 460 ------------------- src/video/MPVVideo.js | 438 ------------------ src/video/README.MD | 3 - src/video/YouTubeVideo.js | 685 ---------------------------- src/video/binarySearchUpperBound.js | 26 -- src/video/colorConverter.js | 18 - src/video/index.js | 13 - src/video/subtitlesParser.js | 61 --- src/video/subtitlesRenderer.js | 22 - src/video/withStreamingServer.js | 190 -------- webpack.config.js | 3 +- 12 files changed, 1 insertion(+), 2232 deletions(-) delete mode 100644 src/video/HTMLSubtitles.js delete mode 100644 src/video/HTMLVideo.js delete mode 100644 src/video/MPVVideo.js delete mode 100644 src/video/README.MD delete mode 100644 src/video/YouTubeVideo.js delete mode 100644 src/video/binarySearchUpperBound.js delete mode 100644 src/video/colorConverter.js delete mode 100644 src/video/index.js delete mode 100644 src/video/subtitlesParser.js delete mode 100644 src/video/subtitlesRenderer.js delete mode 100644 src/video/withStreamingServer.js diff --git a/src/video/HTMLSubtitles.js b/src/video/HTMLSubtitles.js deleted file mode 100644 index 0132f8eef..000000000 --- a/src/video/HTMLSubtitles.js +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright (C) 2017-2020 Smart code 203358507 - -var EventEmitter = require('events'); -var subtitlesParser = require('./subtitlesParser'); -var subtitlesRenderer = require('./subtitlesRenderer'); -var colorConverter = require('./colorConverter'); - -var COLOR_REGEX = /^#[A-Fa-f0-9]{8}$/; -var ERROR_CODE = Object.freeze({ - FETCH_FAILED: 70, - PARSE_FAILED: 71 -}); -var SIZE_COEF = 25; - -function HTMLSubtitles(options) { - var containerElement = options && options.containerElement; - if (!(containerElement instanceof HTMLElement) || !containerElement.hasAttribute('id')) { - throw new Error('Instance of HTMLElement with id attribute required'); - } - if (!document.body.contains(containerElement)) { - throw new Error('Container element not attached to body'); - } - - var destroyed = false; - var events = new EventEmitter(); - var cuesByTime = null; - var tracks = Object.freeze([]); - var selectedTrackId = null; - var delay = null; - var stylesElement = document.createElement('style'); - var subtitlesElement = document.createElement('div'); - - events.on('error', function() { }); - containerElement.appendChild(stylesElement); - var subtitlesContainerStylesIndex = stylesElement.sheet.insertRule('#' + containerElement.id + ' .subtitles { position: absolute; right: 0; bottom: 0; left: 0; z-index: 0; text-align: center; }', stylesElement.sheet.cssRules.length); - var subtitlesCueStylesIndex = stylesElement.sheet.insertRule('#' + containerElement.id + ' .subtitles .cue { display: inline-block; padding: 0.2em; text-shadow: 0 0 0.03em #222222ff, 0 0 0.03em #222222ff, 0 0 0.03em #222222ff, 0 0 0.03em #222222ff, 0 0 0.03em #222222ff; background-color: #00000000; color: #ffffffff; font-size: 4vmin; }', stylesElement.sheet.cssRules.length); - stylesElement.sheet.insertRule('#' + containerElement.id + ' .subtitles .cue * { font-size: inherit; }', stylesElement.sheet.cssRules.length); - containerElement.appendChild(subtitlesElement); - subtitlesElement.classList.add('subtitles'); - - function on(eventName, listener) { - if (destroyed) { - return; - } - - events.on(eventName, listener); - } - function addTracks(extraTracks) { - if (destroyed || !Array.isArray(extraTracks)) { - return; - } - - tracks = extraTracks - .filter(function(track) { - return track && - typeof track.url === 'string' && - track.url.length > 0 && - typeof track.origin === 'string' && - track.origin.length > 0 && - track.origin !== 'EMBEDDED IN VIDEO'; - }) - .map(function(track) { - return Object.freeze(Object.assign({}, track, { - id: track.url - })); - }) - .concat(tracks) - .filter(function(track, index, tracks) { - for (var i = 0; i < tracks.length; i++) { - if (tracks[i].id === track.id) { - return i === index; - } - } - - return false; - }); - Object.freeze(tracks); - events.emit('propChanged', 'tracks'); - } - function updateText(mediaTime) { - while (subtitlesElement.hasChildNodes()) { - subtitlesElement.removeChild(subtitlesElement.lastChild); - } - - if (cuesByTime === null || isNaN(mediaTime) || mediaTime === null) { - return; - } - - var time = mediaTime + delay; - subtitlesRenderer.render(cuesByTime, time) - .forEach(function(cueNode) { - cueNode.classList.add('cue'); - subtitlesElement.append(cueNode, document.createElement('br')); - }); - } - function clearTracks() { - updateText(NaN); - cuesByTime = null; - tracks = Object.freeze([]); - selectedTrackId = null; - delay = null; - events.emit('propChanged', 'tracks'); - events.emit('propChanged', 'selectedTrackId'); - events.emit('propChanged', 'delay'); - } - function destroy() { - destroyed = true; - clearTracks(); - events.emit('propChanged', 'size'); - events.emit('propChanged', 'textColor'); - events.emit('propChanged', 'backgroundColor'); - events.emit('propChanged', 'outlineColor'); - events.emit('propChanged', 'offset'); - events.removeAllListeners(); - events.on('error', function() { }); - containerElement.removeChild(stylesElement); - containerElement.removeChild(subtitlesElement); - } - - Object.defineProperties(this, { - tracks: { - configurable: false, - enumerable: true, - get: function() { - return Object.freeze(tracks.slice()); - } - }, - selectedTrackId: { - configurable: false, - enumerable: true, - get: function() { - return selectedTrackId; - }, - set: function(value) { - if (destroyed) { - return; - } - - cuesByTime = null; - selectedTrackId = null; - delay = null; - updateText(NaN); - var selecterdTrack = tracks.find(function(track) { - return track.id === value; - }); - if (selecterdTrack) { - selectedTrackId = selecterdTrack.id; - delay = 0; - fetch(selecterdTrack.url) - .then(function(resp) { - return resp.text(); - }) - .catch(function(error) { - events.emit('error', Object.freeze({ - code: ERROR_CODE.FETCH_FAILED, - message: 'Failed to fetch subtitles from ' + selecterdTrack.origin, - track: selecterdTrack, - error: error - })); - }) - .then(function(text) { - if (typeof text === 'string' && selectedTrackId === selecterdTrack.id) { - cuesByTime = subtitlesParser.parse(text); - if (cuesByTime.times.length === 0) { - throw new Error('parse failed'); - } - - events.emit('trackLoaded', selecterdTrack); - } - }) - .catch(function(error) { - events.emit('error', Object.freeze({ - code: ERROR_CODE.PARSE_FAILED, - message: 'Failed to parse subtitles from ' + selecterdTrack.origin, - track: selecterdTrack, - error: error - })); - }); - } - - events.emit('propChanged', 'selectedTrackId'); - events.emit('propChanged', 'delay'); - } - }, - delay: { - configurable: false, - enumerable: true, - get: function() { - return delay; - }, - set: function(value) { - if (destroyed || isNaN(value) || value === null || selectedTrackId === null) { - return; - } - - delay = parseInt(value); - updateText(NaN); - events.emit('propChanged', 'delay'); - } - }, - size: { - configurable: false, - enumerable: true, - get: function() { - if (destroyed) { - return null; - } - - return parseInt(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.fontSize) * SIZE_COEF - }, - set: function(value) { - if (destroyed || isNaN(value) || value === null) { - return; - } - - stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.fontSize = Math.floor(value / SIZE_COEF) + 'vmin'; - events.emit('propChanged', 'size'); - } - }, - offset: { - configurable: false, - enumerable: true, - get: function() { - if (destroyed) { - return null; - } - - return parseInt(stylesElement.sheet.cssRules[subtitlesContainerStylesIndex].style.bottom); - }, - set: function(value) { - if (destroyed || isNaN(value) || value === null) { - return; - } - - stylesElement.sheet.cssRules[subtitlesContainerStylesIndex].style.bottom = Math.max(0, Math.min(100, parseInt(value))) + '%'; - events.emit('propChanged', 'offset'); - } - }, - textColor: { - configurable: false, - enumerable: true, - get: function() { - if (destroyed) { - return null; - } - - return colorConverter.rgbaToHex(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.color); - }, - set: function(value) { - if (destroyed || typeof value !== 'string' || value.length !== 9 || !value.match(COLOR_REGEX)) { - return; - } - - stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.color = value; - events.emit('propChanged', 'textColor'); - } - }, - backgroundColor: { - configurable: false, - enumerable: true, - get: function() { - if (destroyed) { - return null; - } - - return colorConverter.rgbaToHex(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.backgroundColor); - }, - set: function(value) { - if (destroyed || typeof value !== 'string' || value.length !== 9 || !value.match(COLOR_REGEX)) { - return; - } - - stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.backgroundColor = value; - events.emit('propChanged', 'backgroundColor'); - } - }, - outlineColor: { - configurable: false, - enumerable: false, - get: function() { - if (destroyed) { - return null; - } - - return colorConverter.rgbaToHex(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.textShadow); - }, - set: function(value) { - if (destroyed || typeof value !== 'string' || value.length !== 9 || !value.match(COLOR_REGEX)) { - return; - } - - stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.textShadow = - value + ' 0 0 0.03em,' + - value + ' 0 0 0.03em,' + - value + ' 0 0 0.03em,' + - value + ' 0 0 0.03em,' + - value + ' 0 0 0.03em'; - events.emit('propChanged', 'outlineColor'); - } - } - }); - - this.on = on; - this.addTracks = addTracks; - this.updateText = updateText; - this.clearTracks = clearTracks; - this.destroy = destroy; - - Object.freeze(this); -}; - -Object.freeze(HTMLSubtitles); - -module.exports = HTMLSubtitles; diff --git a/src/video/HTMLVideo.js b/src/video/HTMLVideo.js deleted file mode 100644 index da206c02a..000000000 --- a/src/video/HTMLVideo.js +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright (C) 2017-2020 Smart code 203358507 - -var EventEmitter = require('events'); -var HTMLSubtitles = require('./HTMLSubtitles'); - -function HTMLVideo(options) { - var containerElement = options && options.containerElement; - if (!(containerElement instanceof HTMLElement) || !containerElement.hasAttribute('id')) { - throw new Error('Instance of HTMLElement with id attribute required'); - } - if (!document.body.contains(containerElement)) { - throw new Error('Container element not attached to body'); - } - - var destroyed = false; - var loaded = false; - var events = new EventEmitter(); - var observedProps = {}; - var subtitles = new HTMLSubtitles({ containerElement: containerElement }); - var stylesElement = document.createElement('style'); - var videoElement = document.createElement('video'); - - events.on('error', function() { }); - subtitles.on('propChanged', onSubtitlesPropChanged); - subtitles.on('trackLoaded', onSubtitlesTrackLoaded); - subtitles.on('error', onSubtitlesError); - containerElement.appendChild(stylesElement); - stylesElement.sheet.insertRule('#' + containerElement.id + ' .video { position: absolute; width: 100%; height: 100%; z-index: -1; background-color: black; }', stylesElement.sheet.cssRules.length); - containerElement.appendChild(videoElement); - videoElement.classList.add('video'); - videoElement.crossOrigin = 'anonymous'; - videoElement.controls = false; - videoElement.onpause = function() { - onVideoPropChanged('paused'); - }; - videoElement.onplay = function() { - onVideoPropChanged('paused'); - }; - videoElement.ontimeupdate = function() { - onVideoPropChanged('currentTime'); - }; - videoElement.ondurationchange = function() { - onVideoPropChanged('duration'); - }; - videoElement.onwaiting = function() { - onVideoPropChanged('readyState'); - }; - videoElement.onplaying = function() { - onVideoPropChanged('readyState'); - }; - videoElement.onloadeddata = function() { - onVideoPropChanged('readyState'); - }; - videoElement.onvolumechange = function() { - onVideoPropChanged('volume'); - onVideoPropChanged('muted'); - }; - videoElement.onended = function() { - onVideoEnded(); - }; - videoElement.onerror = function() { - onVideoError(); - }; - - function onSubtitlesPropChanged(propName) { - switch (propName) { - case 'tracks': { - if (observedProps['subtitlesTracks']) { - events.emit('propChanged', 'subtitlesTracks', getProp('subtitlesTracks')); - } - - break; - } - case 'selectedTrackId': { - if (observedProps['selectedSubtitlesTrackId']) { - events.emit('propChanged', 'selectedSubtitlesTrackId', getProp('selectedSubtitlesTrackId')); - } - - break; - } - case 'delay': { - subtitles.updateText(getProp('time')); - if (observedProps['subtitlesDelay']) { - events.emit('propChanged', 'subtitlesDelay', getProp('subtitlesDelay')); - } - - break; - } - case 'size': { - if (observedProps['subtitlesSize']) { - events.emit('propChanged', 'subtitlesSize', getProp('subtitlesSize')); - } - - break; - } - case 'offset': { - if (observedProps['subtitlesOffset']) { - events.emit('propChanged', 'subtitlesOffset', getProp('subtitlesOffset')); - } - - break; - } - case 'textColor': { - if (observedProps['subtitlesTextColor']) { - events.emit('propChanged', 'subtitlesTextColor', getProp('subtitlesTextColor')); - } - - break; - } - case 'backgroundColor': { - if (observedProps['subtitlesBackgroundColor']) { - events.emit('propChanged', 'subtitlesBackgroundColor', getProp('subtitlesBackgroundColor')); - } - - break; - } - case 'outlineColor': { - if (observedProps['subtitlesOutlineColor']) { - events.emit('propChanged', 'subtitlesOutlineColor', getProp('subtitlesOutlineColor')); - } - - break; - } - } - } - function onSubtitlesTrackLoaded(track) { - subtitles.updateText(getProp('time')); - events.emit('subtitlesTrackLoaded', track); - } - function onSubtitlesError(error) { - onError(Object.assign({}, error, { - critical: false - })); - } - function onVideoPropChanged(propName) { - switch (propName) { - case 'paused': { - if (observedProps['paused']) { - events.emit('propChanged', 'paused', getProp('paused')); - } - - break; - } - case 'currentTime': { - subtitles.updateText(getProp('time')); - if (observedProps['time']) { - events.emit('propChanged', 'time', getProp('time')); - } - - break; - } - case 'duration': { - if (observedProps['duration']) { - events.emit('propChanged', 'duration', getProp('duration')); - } - - break; - } - case 'readyState': { - if (observedProps['buffering']) { - events.emit('propChanged', 'buffering', getProp('buffering')); - } - - break; - } - case 'volume': { - if (observedProps['volume']) { - events.emit('propChanged', 'volume', getProp('volume')); - } - - break; - } - case 'muted': { - if (observedProps['muted']) { - events.emit('propChanged', 'muted', getProp('muted')); - } - - break; - } - } - } - function onVideoEnded() { - events.emit('ended'); - } - function onVideoError() { - onError({ - code: videoElement.error.code, - message: videoElement.error.message, - critical: true - }); - } - function onError(error) { - if (!error) { - return; - } - - Object.freeze(error); - events.emit('error', error); - if (error.critical) { - command('stop'); - } - } - function getProp(propName) { - switch (propName) { - case 'paused': { - if (!loaded) { - return null; - } - - return !!videoElement.paused; - } - case 'time': { - if (!loaded || isNaN(videoElement.currentTime) || videoElement.currentTime === null) { - return null; - } - - return Math.floor(videoElement.currentTime * 1000); - } - case 'duration': { - if (!loaded || isNaN(videoElement.duration) || videoElement.duration === null) { - return null; - } - - return Math.floor(videoElement.duration * 1000); - } - case 'buffering': { - if (!loaded) { - return null; - } - - return videoElement.readyState < videoElement.HAVE_FUTURE_DATA; - } - case 'volume': { - if (destroyed || isNaN(videoElement.volume) || videoElement.volume === null) { - return null; - } - - return Math.floor(videoElement.volume * 100); - } - case 'muted': { - if (destroyed) { - return null; - } - - return !!videoElement.muted; - } - case 'subtitlesTracks': { - return subtitles.tracks; - } - case 'selectedSubtitlesTrackId': { - return subtitles.selectedTrackId; - } - case 'subtitlesDelay': { - return subtitles.delay; - } - case 'subtitlesSize': { - return subtitles.size; - } - case 'subtitlesOffset': { - return subtitles.offset; - } - case 'subtitlesTextColor': { - return subtitles.textColor; - } - case 'subtitlesBackgroundColor': { - return subtitles.backgroundColor; - } - case 'subtitlesOutlineColor': { - return subtitles.outlineColor; - } - default: { - throw new Error('getProp not supported: ' + propName); - } - } - } - function observeProp(propName) { - if (HTMLVideo.manifest.props.indexOf(propName) === -1) { - throw new Error('observeProp not supported: ' + propName); - } - - events.emit('propValue', propName, getProp(propName)); - observedProps[propName] = true; - } - function setProp(propName, propValue) { - switch (propName) { - case 'paused': { - if (loaded) { - if (!!propValue) { - videoElement.pause(); - } else { - videoElement.play(); - } - } - - break; - } - case 'time': { - if (loaded && !isNaN(propValue) && propValue !== null) { - videoElement.currentTime = parseInt(propValue) / 1000; - } - - break; - } - case 'volume': { - if (!isNaN(propValue) && propValue !== null) { - videoElement.muted = false; - videoElement.volume = Math.max(0, Math.min(100, parseInt(propValue))) / 100; - } - - break; - } - case 'muted': { - videoElement.muted = !!propValue; - break; - } - case 'selectedSubtitlesTrackId': { - if (loaded) { - subtitles.selectedTrackId = propValue; - } - - break; - } - case 'subtitlesDelay': { - if (loaded) { - subtitles.delay = propValue; - } - - break; - } - case 'subtitlesSize': { - subtitles.size = propValue; - break; - } - case 'subtitlesOffset': { - subtitles.offset = propValue; - break; - } - case 'subtitlesTextColor': { - subtitles.textColor = propValue; - break; - } - case 'subtitlesBackgroundColor': { - subtitles.backgroundColor = propValue; - break; - } - case 'subtitlesOutlineColor': { - subtitles.outlineColor = propValue; - break; - } - default: { - throw new Error('setProp not supported: ' + propName); - } - } - } - function command(commandName, commandArgs) { - switch (commandName) { - case 'addSubtitlesTracks': { - if (loaded && commandArgs) { - subtitles.addTracks(commandArgs.tracks); - } - - break; - } - case 'stop': { - loaded = false; - videoElement.removeAttribute('src'); - videoElement.load(); - videoElement.currentTime = 0; - onVideoPropChanged('paused'); - onVideoPropChanged('currentTime'); - onVideoPropChanged('duration'); - onVideoPropChanged('readyState'); - subtitles.clearTracks(); - break; - } - case 'load': { - if (commandArgs && commandArgs.stream && typeof commandArgs.stream.url === 'string') { - command('stop'); - videoElement.autoplay = typeof commandArgs.autoplay === 'boolean' ? commandArgs.autoplay : true; - videoElement.currentTime = !isNaN(commandArgs.time) && commandArgs.time !== null ? parseInt(commandArgs.time) / 1000 : 0; - videoElement.src = commandArgs.stream.url; - loaded = true; - onVideoPropChanged('paused'); - onVideoPropChanged('currentTime'); - onVideoPropChanged('duration'); - onVideoPropChanged('readyState'); - } - - break; - } - case 'destroy': { - command('stop'); - destroyed = true; - onVideoPropChanged('volume'); - onVideoPropChanged('muted'); - subtitles.destroy(); - events.removeAllListeners(); - events.on('error', function() { }); - videoElement.onpause = null; - videoElement.onplay = null; - videoElement.ontimeupdate = null; - videoElement.ondurationchange = null; - videoElement.onwaiting = null; - videoElement.onplaying = null; - videoElement.onloadeddata = null; - videoElement.onvolumechange = null; - videoElement.onended = null; - videoElement.onerror = null; - containerElement.removeChild(videoElement); - containerElement.removeChild(stylesElement); - break; - } - default: { - throw new Error('command not supported: ' + commandName); - } - } - } - function on(eventName, listener) { - if (destroyed) { - throw new Error('Video is destroyed'); - } - - events.on(eventName, listener); - } - function dispatch(args) { - if (destroyed) { - throw new Error('Video is destroyed'); - } - - if (args) { - if (typeof args.commandName === 'string') { - command(args.commandName, args.commandArgs); - return; - } else if (typeof args.propName === 'string') { - setProp(args.propName, args.propValue); - return; - } else if (typeof args.observedPropName === 'string') { - observeProp(args.observedPropName); - return; - } - } - - throw new Error('Invalid dispatch call: ' + JSON.stringify(args)); - } - - this.on = on; - this.dispatch = dispatch; - - Object.freeze(this); -}; - -HTMLVideo.manifest = Object.freeze({ - name: 'HTMLVideo', - embedded: true, - props: Object.freeze(['paused', 'time', 'duration', 'buffering', 'volume', 'muted', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesSize', 'subtitlesDelay', 'subtitlesOffset', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor']) -}); - -Object.freeze(HTMLVideo); - -module.exports = HTMLVideo; diff --git a/src/video/MPVVideo.js b/src/video/MPVVideo.js deleted file mode 100644 index a1ace472b..000000000 --- a/src/video/MPVVideo.js +++ /dev/null @@ -1,438 +0,0 @@ -// Copyright (C) 2017-2020 Smart code 203358507 - -var EventEmitter = require('events'); - -var MPV_CRITICAL_ERROR_CODES = []; - -function MPVVideo(options) { - var ipc = options && options.ipc; - var id = options && options.id; - if (!ipc) { - throw new Error('ipc parameter is required'); - } - - if (typeof id !== 'string') { - throw new Error('id parameter is required'); - } - - var ready = false; - var loaded = false; - var destroyed = false; - var events = new EventEmitter(); - var observedProps = {}; - var dispatchArgsReadyQueue = []; - - events.on('error', function() { }); - ipc.dispatch('mpv', 'createChannel', id) - .then(function() { - if (destroyed) { - return; - } - - ready = true; - Promise.all([getProp('volume'), getProp('mute')]).then(function(values) { - if (destroyed) { - return; - } - - onPropChanged('volume', values[0]); - onPropChanged('mute', values[1]); - }); - ipc.on('mpvEvent', onMpvEvent); - flushDispatchArgsQueue(dispatchArgsReadyQueue); - }) - .catch(function(error) { - onChannelError(error); - }); - - function mapPausedValue(pause) { - if (!loaded || typeof pause !== 'boolean') { - return null; - } - - return pause; - } - function mapTimeValue(timePos) { - if (!loaded || isNaN(timePos) || timePos === null) { - return null; - } - - return Math.round(timePos * 1000); - } - function mapDurationValue(duration) { - if (!loaded || isNaN(duration) || duration === null) { - return null; - } - - return Math.round(duration * 1000); - } - function mapBufferingValue(seeking, pausedForCache) { - if (!loaded || (seeking === null && pausedForCache === null)) { - return null; - } - - return !!seeking || !!pausedForCache; - } - function mapVolumeValue(volume) { - if (!ready || destroyed || isNaN(volume) || volume === null) { - return null; - } - - return volume; - } - function mapMutedValue(mute) { - if (!ready || destroyed || typeof mute !== 'boolean') { - return null; - } - - return mute; - } - function onError(error) { - if (destroyed || !error) { - return; - } - - Object.freeze(error); - events.emit('error', error); - if (error.critical) { - dispatch('command', 'stop'); - } - } - function onEnded() { - if (destroyed) { - return; - } - - events.emit('ended'); - } - function onPropChanged(propName, propValue) { - switch (propName) { - case 'pause': { - if (observedProps['paused']) { - events.emit('propChanged', 'paused', mapPausedValue(propValue)); - } - - break; - } - case 'time-pos': { - if (observedProps['time']) { - events.emit('propChanged', 'time', mapTimeValue(propValue)); - } - - break; - } - case 'duration': { - if (observedProps['duration']) { - events.emit('propChanged', 'duration', mapDurationValue(propValue)); - } - - break; - } - case 'seeking': { - if (observedProps['buffering']) { - events.emit('propChanged', 'buffering', mapBufferingValue(propValue, null)); - } - - break; - } - case 'paused-for-cache': { - if (observedProps['buffering']) { - events.emit('propChanged', 'buffering', mapBufferingValue(null, propValue)); - } - - break; - } - case 'volume': { - if (observedProps['volume']) { - events.emit('propChanged', 'volume', mapVolumeValue(propValue)); - } - - break; - } - case 'mute': { - if (observedProps['muted']) { - events.emit('propChanged', 'muted', mapMutedValue(propValue)); - } - - break; - } - } - } - function onMpvEvent(data) { - if (destroyed) { - return; - } - - if (!data || data.channelId !== id) { - onChannelError(ipc.errors.mpv_channel_id_expired); - return; - } - - switch (data.eventName) { - case 'error': { - onError(Object.assign({}, data, { - critical: MPV_CRITICAL_ERROR_CODES.indexOf(data.code) !== -1 - })); - break; - } - case 'ended': { - onEnded(); - break; - } - case 'propChanged': { - onPropChanged(data.propName, data.propValue); - break; - } - } - } - function onChannelError(error) { - if (destroyed) { - return; - } - - onError(Object.assign({}, error, { - critical: true - })); - dispatch('command', 'destroy'); - } - function observeProp(propName) { - ipc.dispatch('mpv', 'observeProp', id, propName) - .catch(function(error) { - onChannelError(error); - }); - } - function getProp(propName) { - return ipc.dispatch('mpv', 'getProp', id, propName) - .catch(function(error) { - onChannelError(error); - return null; - }); - } - function setProp(propName, propValue) { - ipc.dispatch('mpv', 'setProp', id, propName, propValue) - .catch(function(error) { - onChannelError(error); - }); - } - function command() { - ipc.dispatch.apply(null, ['mpv', 'command', id].concat(Array.from(arguments))) - .catch(function(error) { - onChannelError(error); - }); - } - function flushDispatchArgsQueue(dispatchArgsQueue) { - if (destroyed) { - return; - } - - while (dispatchArgsQueue.length > 0) { - var args = dispatchArgsQueue.shift(); - dispatch.apply(null, args); - } - } - function on(eventName, listener) { - if (destroyed) { - throw new Error('Video is destroyed'); - } - - events.on(eventName, listener); - } - function dispatch() { - if (destroyed) { - throw new Error('Video is destroyed'); - } - - switch (arguments[0]) { - case 'observeProp': { - switch (arguments[1]) { - case 'paused': { - observeProp('pause'); - getProp('pause').then(function(pause) { - if (destroyed) { - return; - } - - observedProps['paused'] = true; - events.emit('propValue', 'paused', mapPausedValue(pause)); - }); - return; - } - case 'time': { - observeProp('time-pos'); - getProp('time-pos').then(function(timePos) { - if (destroyed) { - return; - } - - observedProps['time'] = true; - events.emit('propValue', 'time', mapTimeValue(timePos)); - }); - return; - } - case 'duration': { - observeProp('duration'); - getProp('duration').then(function(duration) { - if (destroyed) { - return; - } - - observedProps['duration'] = true; - events.emit('propValue', 'duration', mapDurationValue(duration)); - }); - return; - } - case 'buffering': { - observeProp('seeking'); - observeProp('paused-for-cache'); - Promise.all([getProp('seeking'), getProp('paused-for-cache')]).then(function(values) { - if (destroyed) { - return; - } - - observedProps['buffering'] = true; - events.emit('propValue', 'buffering', mapBufferingValue(values[0], values[1])); - }); - return; - } - case 'volume': { - observeProp('volume'); - getProp('volume').then(function(volume) { - if (destroyed) { - return; - } - - observedProps['volume'] = true; - events.emit('propValue', 'volume', mapVolumeValue(volume)); - }); - return; - } - case 'muted': { - observeProp('mute'); - getProp('mute').then(function(mute) { - if (destroyed) { - return; - } - - observedProps['muted'] = true; - events.emit('propValue', 'muted', mapMutedValue(mute)); - }); - return; - } - } - } - case 'setProp': { - switch (arguments[1]) { - case 'paused': { - if (loaded) { - setProp('pause', !!arguments[2]); - } - - return; - } - case 'time': { - if (loaded && !isNaN(arguments[2]) && arguments[2] !== null) { - setProp('time-pos', arguments[2] / 1000); - } - - return; - } - case 'volume': { - if (ready) { - if (!isNaN(arguments[2]) && arguments[2] !== null) { - setProp('mute', false); - setProp('volume', Math.max(0, Math.min(100, arguments[2]))); - } - } else { - dispatchArgsReadyQueue.push(Array.from(arguments)); - } - - return; - } - case 'muted': { - if (ready) { - setProp('mute', !!arguments[2]); - } else { - dispatchArgsReadyQueue.push(Array.from(arguments)); - } - - return; - } - } - } - case 'command': { - switch (arguments[1]) { - case 'stop': { - loaded = false; - if (ready) { - command('stop'); - } - onPropChanged('pause', null); - onPropChanged('time-pos', null); - onPropChanged('duration', null); - onPropChanged('seeking', null); - onPropChanged('paused-for-cache', null); - return; - } - case 'load': { - if (ready) { - dispatch('command', 'stop'); - loaded = true; - var startTime = !isNaN(arguments[3].time) && arguments[3].time !== null ? Math.round(arguments[3].time / 1000) : 0; - command('loadfile', arguments[2].url, 'replace', 'time-pos=' + startTime); - setProp('pause', arguments[3].autoplay === false); - Promise.all([ - getProp('pause'), - getProp('time-pos'), - getProp('duration'), - getProp('seeking'), - getProp('paused-for-cache') - ]).then(function(values) { - if (destroyed) { - return; - } - - onPropChanged('pause', values[0]); - onPropChanged('time-pos', values[1]); - onPropChanged('duration', values[2]); - onPropChanged('seeking', values[3]); - onPropChanged('paused-for-cache', values[4]); - }); - } else { - dispatchArgsReadyQueue.push(Array.from(arguments)); - } - - return; - } - case 'destroy': { - dispatch('command', 'stop'); - destroyed = true; - onPropChanged('volume', null); - onPropChanged('mute', null); - events.removeAllListeners(); - events.on('error', function() { }); - ipc.off('mpvEvent', onMpvEvent); - dispatchArgsReadyQueue = []; - return; - } - } - } - } - - throw new Error('Invalid dispatch call: ' + Array.from(arguments).map(String)); - } - - this.on = on; - this.dispatch = dispatch; - - Object.freeze(this); -} - -MPVVideo.manifest = Object.freeze({ - name: 'MPVVideo', - embedded: true, - props: Object.freeze(['paused', 'time', 'duration', 'volume', 'muted', 'buffering']) -}); - -Object.freeze(MPVVideo); - -module.exports = MPVVideo; diff --git a/src/video/README.MD b/src/video/README.MD deleted file mode 100644 index ac775d66d..000000000 --- a/src/video/README.MD +++ /dev/null @@ -1,3 +0,0 @@ -# stremio-video - -### TODO move this folder in a separate repo \ No newline at end of file diff --git a/src/video/YouTubeVideo.js b/src/video/YouTubeVideo.js deleted file mode 100644 index 5b2958803..000000000 --- a/src/video/YouTubeVideo.js +++ /dev/null @@ -1,685 +0,0 @@ -// Copyright (C) 2017-2020 Smart code 203358507 - -var EventEmitter = require('events'); -var HTMLSubtitles = require('./HTMLSubtitles'); - -function YouTubeVideo(options) { - var containerElement = options && options.containerElement; - if (!(containerElement instanceof HTMLElement) || !containerElement.hasAttribute('id')) { - throw new Error('Instance of HTMLElement with id attribute required'); - } - - var self = this; - var ready = false; - var loaded = false; - var destroyed = false; - var events = new EventEmitter(); - var dispatchArgsReadyQueue = []; - var dispatchArgsLoadedQueue = []; - var pausedObserved = false; - var timeObserved = false; - var durationObserved = false; - var bufferingObserved = false; - var volumeObserved = false; - var propChangedIntervalId = window.setInterval(onPropChangedInterval, 100); - var embeddedSubtitlesSelectedTrackId = null; - var subtitles = new HTMLSubtitles(containerElement); - var video = null; - var scriptElement = document.createElement('script'); - var stylesElement = document.createElement('style'); - var videoContainer = document.createElement('div'); - - events.on('error', function() { }); - subtitles.on('error', onSubtitlesError); - subtitles.on('load', updateSubtitleText); - scriptElement.type = 'text/javascript'; - scriptElement.src = 'https://www.youtube.com/iframe_api'; - scriptElement.onload = onYouTubePlayerApiLoaded; - scriptElement.onerror = onYouTubePlayerApiError; - containerElement.appendChild(scriptElement); - containerElement.appendChild(stylesElement); - stylesElement.sheet.insertRule('#' + containerElement.id + ' .video { position: absolute; width: 100%; height: 100%; z-index: -1; }', stylesElement.sheet.cssRules.length); - containerElement.appendChild(videoContainer); - videoContainer.classList.add('video'); - - function getPaused() { - if (!loaded) { - return null; - } - - return video.getPlayerState() !== YT.PlayerState.PLAYING; - } - function getTime() { - if (!loaded || isNaN(video.getCurrentTime()) || video.getCurrentTime() === null) { - return null; - } - - return Math.floor(video.getCurrentTime() * 1000); - } - function getDuration() { - if (!loaded || isNaN(video.getDuration()) || video.getDuration() === null) { - return null; - } - - return Math.floor(video.getDuration() * 1000); - } - function getBuffering() { - if (!loaded) { - return null; - } - - return video.getPlayerState() === YT.PlayerState.BUFFERING; - } - function getVolume() { - if (!ready || destroyed || isNaN(video.getVolume()) || video.getVolume() === null) { - return null; - } - - return video.isMuted() ? 0 : video.getVolume(); - } - function getSubtitlesTracks() { - if (!loaded) { - return Object.freeze([]); - } - - var embeddedTracks = (video.getOption('captions', 'tracklist') || []) - .map(function(track) { - return Object.freeze({ - id: track.languageCode, - origin: 'EMBEDDED IN VIDEO', - label: track.languageName - }); - }); - var extraTracks = subtitles.dispatch('getProp', 'tracks'); - var allTracks = embeddedTracks.concat(extraTracks) - .filter(function(track, index, tracks) { - for (var i = 0; i < tracks.length; i++) { - if (tracks[i].id === track.id) { - return i === index; - } - } - - return false; - }); - return Object.freeze(allTracks); - } - function getSelectedSubtitlesTrackId() { - if (!loaded) { - return null; - } - - return embeddedSubtitlesSelectedTrackId !== null ? - embeddedSubtitlesSelectedTrackId - : - subtitles.dispatch('getProp', 'selectedTrackId'); - } - function getSubtitlesDelay() { - if (!loaded) { - return null; - } - - return embeddedSubtitlesSelectedTrackId !== null ? - null - : - subtitles.dispatch('getProp', 'delay'); - } - function getsubtitlesSize() { - if (!ready || destroyed) { - return null; - } - - return subtitles.dispatch('getProp', 'size'); - } - function getSubtitlesDarkBackground() { - if (!ready || destroyed) { - return null; - } - - return embeddedSubtitlesSelectedTrackId !== null ? - null - : - subtitles.dispatch('getProp', 'darkBackground'); - } - function getSubtitleOffset() { - if (!ready || destroyed) { - return null; - } - - return embeddedSubtitlesSelectedTrackId !== null ? - null - : - subtitles.dispatch('getProp', 'offset'); - } - function onEnded() { - events.emit('ended'); - } - function onError(error) { - Object.freeze(error); - events.emit('error', error); - if (error.critical) { - self.dispatch('command', 'stop'); - } - } - function onPausedChanged() { - events.emit('propChanged', 'paused', getPaused()); - } - function onTimeChanged() { - events.emit('propChanged', 'time', getTime()); - } - function onDurationChanged() { - events.emit('propChanged', 'duration', getDuration()); - } - function onBufferingChanged() { - events.emit('propChanged', 'buffering', getBuffering()); - } - function onVolumeChanged() { - events.emit('propChanged', 'volume', getVolume()); - } - function onSubtitlesTracksChanged() { - events.emit('propChanged', 'subtitlesTracks', getSubtitlesTracks()); - } - function onSelectedSubtitlesTrackIdChanged() { - events.emit('propChanged', 'selectedSubtitlesTrackId', getSelectedSubtitlesTrackId()); - } - function onSubtitlesDelayChanged() { - events.emit('propChanged', 'subtitlesDelay', getSubtitlesDelay()); - } - function onsubtitlesSizeChanged() { - events.emit('propChanged', 'subtitlesSize', getsubtitlesSize()); - } - function onSubtitlesDarkBackgroundChanged() { - events.emit('propChanged', 'subtitlesDarkBackground', getSubtitlesDarkBackground()); - } - function onSubtitleOffsetChanged() { - events.emit('propChanged', 'subtitleOffset', getSubtitleOffset()); - } - function onSubtitlesError(error) { - var code; - var message; - switch (error.code) { - case HTMLSubtitles.ERROR.SUBTITLES_FETCH_FAILED: { - code = HTMLSubtitles.ERROR.SUBTITLES_FETCH_FAILED; - message = 'Failed to fetch subtitles from ' + error.track.origin; - break; - } - case HTMLSubtitles.ERROR.SUBTITLES_PARSE_FAILED: { - code = HTMLSubtitles.ERROR.SUBTITLES_PARSE_FAILED; - message = 'Failed to parse subtitles from ' + error.track.origin; - break; - } - default: { - code = -1; - message = 'Unknown subtitles error'; - } - } - - onError({ - code: code, - message: message, - critical: false - }); - } - function onYouTubePlayerApiError() { - onError({ - code: YouTubeVideo.ERROR.API_LOAD_FAILED, - message: 'YouTube player API failed to load', - critical: true - }); - } - function onYouTubePlayerApiLoaded() { - if (destroyed) { - return; - } - - if (!YT) { - onYouTubePlayerApiError(); - return; - } - - YT.ready(function() { - if (destroyed) { - return; - } - - video = new YT.Player(videoContainer, { - height: '100%', - width: '100%', - playerVars: { - autoplay: 1, - cc_load_policy: 3, - controls: 0, - disablekb: 1, - enablejsapi: 1, - fs: 0, - iv_load_policy: 3, - loop: 0, - modestbranding: 1, - playsinline: 1, - rel: 0 - }, - events: { - onError: onVideoError, - onReady: onVideoReady, - onStateChange: onVideoStateChange, - onApiChange: onVideoApiChange - } - }); - }); - } - function onVideoError(error) { - var code; - var message; - switch (error.data) { - case YouTubeVideo.ERROR.INVALID_REQUEST: { - code = YouTubeVideo.ERROR.INVALID_REQUEST; - message = 'Invalid request'; - break; - } - case YouTubeVideo.ERROR.CONTENT_CANNOT_BE_PLAYED: { - code = YouTubeVideo.ERROR.CONTENT_CANNOT_BE_PLAYED; - message = 'The requested content cannot be played'; - break; - } - case YouTubeVideo.ERROR.REMOVED_VIDEO: { - code = YouTubeVideo.ERROR.REMOVED_VIDEO; - message = 'The video has been removed or marked as private'; - break; - } - case YouTubeVideo.ERROR.CONTENT_CANNOT_BE_EMBEDDED1: - case YouTubeVideo.ERROR.CONTENT_CANNOT_BE_EMBEDDED2: { - code = YouTubeVideo.ERROR.CONTENT_CANNOT_BE_EMBEDDED1; - message = 'The video cannot be played in embedded players'; - break; - } - default: { - code = -1; - message = 'Unknown video error'; - } - } - - onError({ - code: code, - message: message, - critical: true - }); - } - function onVideoReady() { - ready = true; - onVolumeChanged(); - onsubtitlesSizeChanged(); - onSubtitlesDarkBackgroundChanged(); - onSubtitleOffsetChanged(); - flushDispatchArgsQueue(dispatchArgsReadyQueue); - } - function onVideoStateChange(state) { - if (bufferingObserved) { - onBufferingChanged(); - } - - switch (state.data) { - case YT.PlayerState.ENDED: { - onEnded(); - break; - } - case YT.PlayerState.PAUSED: - case YT.PlayerState.PLAYING: { - if (pausedObserved) { - onPausedChanged(); - } - - if (timeObserved) { - onTimeChanged(); - } - - if (durationObserved) { - onDurationChanged(); - } - - break; - } - case YT.PlayerState.UNSTARTED: { - if (pausedObserved) { - onPausedChanged(); - } - - break; - } - } - } - function onVideoApiChange() { - video.loadModule('captions'); - onSubtitlesTracksChanged(); - } - function onPropChangedInterval() { - if (timeObserved) { - onTimeChanged(); - } - - if (durationObserved) { - onDurationChanged(); - } - - if (volumeObserved) { - onVolumeChanged(); - } - - updateSubtitleText(); - } - function updateSubtitleText() { - subtitles.dispatch('command', 'updateText', getTime()); - } - function flushDispatchArgsQueue(dispatchArgsQueue) { - while (dispatchArgsQueue.length > 0) { - var args = dispatchArgsQueue.shift(); - self.dispatch.apply(self, args); - } - } - - this.on = function(eventName, listener) { - if (destroyed) { - throw new Error('Unable to add ' + eventName + ' listener'); - } - - events.on(eventName, listener); - }; - - this.dispatch = function() { - if (destroyed) { - throw new Error('Unable to dispatch ' + arguments[0]); - } - - switch (arguments[0]) { - case 'observeProp': { - switch (arguments[1]) { - case 'paused': { - events.emit('propValue', 'paused', getPaused()); - pausedObserved = true; - return; - } - case 'time': { - events.emit('propValue', 'time', getTime()); - timeObserved = true; - return; - } - case 'duration': { - events.emit('propValue', 'duration', getDuration()); - durationObserved = true; - return; - } - case 'buffering': { - events.emit('propValue', 'buffering', getBuffering()); - bufferingObserved = true; - return; - } - case 'volume': { - events.emit('propValue', 'volume', getVolume()); - volumeObserved = true; - return; - } - case 'subtitlesTracks': { - events.emit('propValue', 'subtitlesTracks', getSubtitlesTracks()); - return; - } - case 'selectedSubtitlesTrackId': { - events.emit('propValue', 'selectedSubtitlesTrackId', getSelectedSubtitlesTrackId()); - return; - } - case 'subtitlesDelay': { - events.emit('propValue', 'subtitlesDelay', getSubtitlesDelay()); - return; - } - case 'subtitlesSize': { - events.emit('propValue', 'subtitlesSize', getsubtitlesSize()); - return; - } - case 'subtitlesDarkBackground': { - events.emit('propValue', 'subtitlesDarkBackground', getSubtitlesDarkBackground()); - return; - } - case 'subtitleOffset': { - events.emit('propValue', 'subtitleOffset', getSubtitleOffset()); - return; - } - default: { - throw new Error('observeProp not supported: ' + arguments[1]); - } - } - } - case 'setProp': { - switch (arguments[1]) { - case 'paused': { - if (loaded) { - arguments[2] ? video.pauseVideo() : video.playVideo(); - } else { - dispatchArgsLoadedQueue.push(Array.from(arguments)); - } - - return; - } - case 'time': { - if (loaded) { - if (!isNaN(arguments[2]) && arguments[2] !== null) { - video.seekTo(arguments[2] / 1000); - } - } else { - dispatchArgsLoadedQueue.push(Array.from(arguments)); - } - - return; - } - case 'volume': { - if (ready) { - if (!isNaN(arguments[2]) && arguments[2] !== null) { - video.unMute(); - video.setVolume(Math.max(0, Math.min(100, arguments[2]))); - } - } else { - dispatchArgsReadyQueue.push(Array.from(arguments)); - } - - return; - } - case 'selectedSubtitlesTrackId': { - if (loaded) { - embeddedSubtitlesSelectedTrackId = null; - var tracks = getSubtitlesTracks(); - for (var i = 0; i < tracks.length; i++) { - if (tracks[i].id === arguments[2] && tracks[i].origin === 'EMBEDDED IN VIDEO') { - embeddedSubtitlesSelectedTrackId = tracks[i].id; - break; - } - } - - video.setOption('captions', 'track', { languageCode: arguments[2] }); - subtitles.dispatch('setProp', 'selectedTrackId', arguments[2]); - onSubtitlesDelayChanged(); - onSubtitlesDarkBackgroundChanged(); - onSelectedSubtitlesTrackIdChanged(); - updateSubtitleText(); - } else { - dispatchArgsLoadedQueue.push(Array.from(arguments)); - } - - return; - } - case 'subtitlesDelay': { - if (loaded) { - subtitles.dispatch('setProp', 'delay', arguments[2]); - onSubtitlesDelayChanged(); - updateSubtitleText(); - } else { - dispatchArgsLoadedQueue.push(Array.from(arguments)); - } - - return; - } - case 'subtitlesSize': { - if (ready) { - subtitles.dispatch('setProp', 'size', arguments[2]); - video.setOption('captions', 'fontSize', Math.max(1, Math.min(5, Math.floor(arguments[2]))) - 2); - onsubtitlesSizeChanged(); - } else { - dispatchArgsReadyQueue.push(Array.from(arguments)); - } - - return; - } - case 'subtitlesDarkBackground': { - if (ready) { - subtitles.dispatch('setProp', 'darkBackground', arguments[2]); - onSubtitlesDarkBackgroundChanged(); - } else { - dispatchArgsReadyQueue.push(Array.from(arguments)); - } - - return; - } - case 'subtitleOffset': { - if (ready) { - subtitles.dispatch('setProp', 'offset', arguments[2]); - onSubtitleOffsetChanged(); - } else { - dispatchArgsReadyQueue.push(Array.from(arguments)); - } - - return; - } - default: { - throw new Error('setProp not supported: ' + arguments[1]); - } - } - } - case 'command': { - switch (arguments[1]) { - case 'addSubtitlesTracks': { - if (loaded) { - subtitles.dispatch('command', 'addTracks', arguments[2]); - onSubtitlesTracksChanged(); - } else { - dispatchArgsLoadedQueue.push(Array.from(arguments)); - } - - return; - } - case 'mute': { - if (ready) { - video.mute(); - } else { - dispatchArgsReadyQueue.push(Array.from(arguments)); - } - - return; - } - case 'unmute': { - if (ready) { - video.unMute(); - if (video.getVolume() === 0) { - video.setVolume(50); - } - } else { - dispatchArgsReadyQueue.push(Array.from(arguments)); - } - - return; - } - case 'stop': { - loaded = false; - dispatchArgsLoadedQueue = []; - subtitles.dispatch('command', 'clearTracks'); - if (ready) { - video.stopVideo(); - } - onPausedChanged(); - onTimeChanged(); - onDurationChanged(); - onBufferingChanged(); - onSubtitlesTracksChanged(); - onSelectedSubtitlesTrackIdChanged(); - onSubtitlesDelayChanged(); - updateSubtitleText(); - return; - } - case 'load': { - if (ready) { - var dispatchArgsLoadedQueueCopy = dispatchArgsLoadedQueue.slice(); - self.dispatch('command', 'stop'); - dispatchArgsLoadedQueue = dispatchArgsLoadedQueueCopy; - var autoplay = typeof arguments[3].autoplay === 'boolean' ? arguments[3].autoplay : true; - var time = !isNaN(arguments[3].time) && arguments[3].time !== null ? arguments[3].time / 1000 : 0; - if (autoplay) { - video.loadVideoById({ - videoId: arguments[2].ytId, - startSeconds: time - }); - } else { - video.cueVideoById({ - videoId: arguments[2].ytId, - startSeconds: time - }); - } - loaded = true; - onPausedChanged(); - onTimeChanged(); - onDurationChanged(); - onBufferingChanged(); - onSubtitlesTracksChanged(); - onSelectedSubtitlesTrackIdChanged(); - onSubtitlesDelayChanged(); - updateSubtitleText(); - flushDispatchArgsQueue(dispatchArgsLoadedQueue); - } else { - dispatchArgsReadyQueue.push(Array.from(arguments)); - } - - return; - } - case 'destroy': { - self.dispatch('command', 'stop'); - destroyed = true; - onVolumeChanged(); - onsubtitlesSizeChanged(); - onSubtitlesDarkBackgroundChanged(); - onSubtitleOffsetChanged(); - events.removeAllListeners(); - clearInterval(propChangedIntervalId); - if (ready) { - video.destroy(); - } - containerElement.removeChild(scriptElement); - containerElement.removeChild(videoContainer); - containerElement.removeChild(stylesElement); - subtitles.dispatch('command', 'destroy'); - return; - } - default: { - throw new Error('command not supported: ' + arguments[1]); - } - } - } - default: { - throw new Error('Invalid dispatch call: ' + Array.from(arguments).map(String)); - } - } - }; - - Object.freeze(this); -}; - -YouTubeVideo.ERROR = Object.freeze({ - API_LOAD_FAILED: 12, - INVALID_REQUEST: 2, - CONTENT_CANNOT_BE_PLAYED: 5, - REMOVED_VIDEO: 100, - CONTENT_CANNOT_BE_EMBEDDED1: 101, - CONTENT_CANNOT_BE_EMBEDDED2: 150 -}); - -YouTubeVideo.manifest = Object.freeze({ - name: 'YouTubeVideo', - embedded: true, - props: Object.freeze(['paused', 'time', 'duration', 'volume', 'buffering', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesSize', 'subtitlesDelay', 'subtitlesDarkBackground', 'subtitleOffset']) -}); - -Object.freeze(YouTubeVideo); - -module.exports = YouTubeVideo; diff --git a/src/video/binarySearchUpperBound.js b/src/video/binarySearchUpperBound.js deleted file mode 100644 index 35f6f1882..000000000 --- a/src/video/binarySearchUpperBound.js +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2017-2020 Smart code 203358507 - -function binarySearchUpperBound(array, value) { - if (value < array[0] || array[array.length - 1] < value) { - return -1; - } - - var left = 0; - var right = array.length - 1; - var index = -1; - while (left <= right) { - var middle = Math.floor((left + right) / 2); - if (array[middle] > value) { - right = middle - 1; - } else if (array[middle] < value) { - left = middle + 1; - } else { - index = middle; - left = middle + 1; - } - } - - return index !== -1 ? index : right; -} - -module.exports = binarySearchUpperBound; diff --git a/src/video/colorConverter.js b/src/video/colorConverter.js deleted file mode 100644 index 6b4f4ad44..000000000 --- a/src/video/colorConverter.js +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2017-2020 Smart code 203358507 - -function padWithZero(str) { - return ('0' + str).slice(-2); -} - -function rgbaToHex(rgbaString) { - var values = rgbaString.split('(')[1].split(')')[0].split(','); - var red = parseInt(values[0]).toString(16); - var green = parseInt(values[1]).toString(16); - var blue = parseInt(values[2]).toString(16); - var alpha = Math.round((values[3] || 1) * 255).toString(16); - return '#' + padWithZero(red) + padWithZero(green) + padWithZero(blue) + padWithZero(alpha); -} - -module.exports = { - rgbaToHex -}; diff --git a/src/video/index.js b/src/video/index.js deleted file mode 100644 index f85cef1d7..000000000 --- a/src/video/index.js +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) 2017-2020 Smart code 203358507 - -var HTMLVideo = require('./HTMLVideo'); -var MPVVideo = require('./MPVVideo'); -var YouTubeVideo = require('./YouTubeVideo'); -var withStreamingServer = require('./withStreamingServer'); - -module.exports = { - HTMLVideo, - MPVVideo, - YouTubeVideo, - withStreamingServer -}; diff --git a/src/video/subtitlesParser.js b/src/video/subtitlesParser.js deleted file mode 100644 index ead1613b4..000000000 --- a/src/video/subtitlesParser.js +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) 2017-2020 Smart code 203358507 - -var VTTJS = require('vtt.js'); -var binarySearchUpperBound = require('./binarySearchUpperBound'); - -function parse(text) { - var nativeVTTCue = window.VTTCue; - window.VTTCue = VTTJS.VTTCue; - var parser = new VTTJS.WebVTT.Parser(window, VTTJS.WebVTT.StringDecoder()); - var cues = []; - var cuesByTime = {}; - parser.oncue = function(c) { - var cue = Object.freeze({ - startTime: (c.startTime * 1000) | 0, - endTime: (c.endTime * 1000) | 0, - text: c.text - }); - cues.push(cue); - cuesByTime[cue.startTime] = cuesByTime[cue.startTime] || []; - cuesByTime[cue.endTime] = cuesByTime[cue.endTime] || []; - }; - parser.parse(text); - parser.flush(); - window.VTTCue = nativeVTTCue; - cuesByTime.times = Object.keys(cuesByTime) - .map(function(time) { - return parseInt(time); - }) - .sort(function(t1, t2) { - return t1 - t2; - }); - Object.freeze(cues); - Object.freeze(cuesByTime); - Object.freeze(cuesByTime.times); - for (var i = 0; i < cues.length; i++) { - cuesByTime[cues[i].startTime].push(cues[i]); - var startTimeIndex = binarySearchUpperBound(cuesByTime.times, cues[i].startTime); - for (var j = startTimeIndex + 1; j < cuesByTime.times.length; j++) { - if (cues[i].endTime <= cuesByTime.times[j]) { - break; - } - - cuesByTime[cuesByTime.times[j]].push(cues[i]); - } - } - - for (var i = 0; i < cuesByTime.times.length; i++) { - cuesByTime[cuesByTime.times[i]].sort(function(c1, c2) { - return c1.startTime - c2.startTime || - c1.endTime - c2.endTime; - }); - - Object.freeze(cuesByTime[cuesByTime.times[i]]); - } - - return cuesByTime; -} - -module.exports = Object.freeze({ - parse: parse -}); diff --git a/src/video/subtitlesRenderer.js b/src/video/subtitlesRenderer.js deleted file mode 100644 index 12d9dc1f8..000000000 --- a/src/video/subtitlesRenderer.js +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2017-2020 Smart code 203358507 - -var VTTJS = require('vtt.js'); -var binarySearchUpperBound = require('./binarySearchUpperBound'); - -function render(cuesByTime, time) { - var nodes = []; - var timeIndex = binarySearchUpperBound(cuesByTime.times, time); - if (timeIndex !== -1) { - var cuesForTime = cuesByTime[cuesByTime.times[timeIndex]]; - for (var i = 0; i < cuesForTime.length; i++) { - var node = VTTJS.WebVTT.convertCueToDOMTree(window, cuesForTime[i].text); - nodes.push(node); - } - } - - return Object.freeze(nodes); -} - -module.exports = Object.freeze({ - render: render -}); diff --git a/src/video/withStreamingServer.js b/src/video/withStreamingServer.js deleted file mode 100644 index 95739517b..000000000 --- a/src/video/withStreamingServer.js +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (C) 2017-2020 Smart code 203358507 - -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) { - var video = new Video(options); - var events = new EventEmitter(); - - var destroyed = false; - var stream = null; - - events.on('error', function() { }); - - function onError(error) { - events.emit('error', error); - if (error.critical) { - stop(); - video.dispatch({ commandName: 'stop' }); - } - } - function stop() { - stream = null; - } - 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); - }; - this.dispatch = function(args) { - if (!destroyed && args) { - if (typeof args.commandName === 'string') { - switch (args.commandName) { - case 'stop': { - stop(); - break; - } - case 'load': { - load(args.commandArgs); - return; - } - case 'destroy': { - destroy(); - break; - } - } - } - } - - video.dispatch(args); - }; - - Object.freeze(this); - } - - StreamingServerVideo.manifest = Object.freeze({ - name: Video.manifest.name + 'WithStreamingServer', - embedded: true, - props: Object.freeze(Video.manifest.props) - }); - - Object.freeze(StreamingServerVideo); - - return StreamingServerVideo; -} - -module.exports = withStreamingServer; diff --git a/webpack.config.js b/webpack.config.js index 73e2db8be..8dfa4b8c7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -106,8 +106,7 @@ module.exports = (env, argv) => ({ extensions: ['.js', '.json', '.less', '.wasm'], alias: { 'stremio': path.resolve(__dirname, 'src'), - 'stremio-router': path.resolve(__dirname, 'src/router'), - 'stremio-video': path.resolve(__dirname, 'src/video') + 'stremio-router': path.resolve(__dirname, 'src/router') } }, devServer: {