diff --git a/src/routes/Player/Video/Video.js b/src/routes/Player/Video/Video.js index af1ee9e26..f1fda990d 100644 --- a/src/routes/Player/Video/Video.js +++ b/src/routes/Player/Video/Video.js @@ -35,10 +35,10 @@ class Video extends Component { if (this.video === null || this.video.constructor !== Video) { this.dispatch('command', 'destroy'); this.video = new Video(this.containerRef.current); - this.video.on('ended', this.props.onEnded); - this.video.on('error', this.props.onError); - this.video.on('propValue', this.props.onPropValue); - this.video.on('propChanged', this.props.onPropChanged); + this.video.addListener('ended', this.props.onEnded); + this.video.addListener('error', this.props.onError); + this.video.addListener('propValue', this.props.onPropValue); + this.video.addListener('propChanged', this.props.onPropChanged); this.video.constructor.manifest.props.forEach((propName) => { this.dispatch('observeProp', propName); }); diff --git a/src/routes/Player/Video/stremio-video/HTMLSubtitles.js b/src/routes/Player/Video/stremio-video/HTMLSubtitles.js index f83edaa4e..75d6f8146 100644 --- a/src/routes/Player/Video/stremio-video/HTMLSubtitles.js +++ b/src/routes/Player/Video/stremio-video/HTMLSubtitles.js @@ -3,10 +3,12 @@ var subtitleUtils = require('./utils/subtitles'); var HTMLSubtitles = function(containerElement) { if (!(containerElement instanceof HTMLElement)) { - throw new Error('Instance of HTMLElement required as a first argument to HTMLSubtitles'); + throw new Error('Instance of HTMLElement required as a first argument'); } + var self = this; var events = new EventEmitter(); + var destroyed = false; var tracks = Object.freeze([]); var cues = Object.freeze({}); var selectedTrackId = null; @@ -21,156 +23,169 @@ var HTMLSubtitles = function(containerElement) { containerElement.appendChild(subtitlesElement); subtitlesElement.classList.add('subtitles'); - Object.defineProperty(this, 'tracks', { - configurable: false, - enumerable: true, - get: function() { return Object.freeze(tracks.slice()); } - }); - - Object.defineProperty(this, 'selectedTrackId', { - configurable: false, - enumerable: true, - get: function() { return selectedTrackId; }, - set: function(nextSelectedTrackId) { - cues = Object.freeze({}); - selectedTrackId = null; - delay = 0; - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - if (track.id === nextSelectedTrackId) { - selectedTrackId = track.id; - fetch(track.url) - .then(function(resp) { - return resp.text(); - }) - .catch(function() { - events.emit('error', Object.freeze({ - code: 70, - track: track - })); - }) - .then(function(text) { - if (typeof text === 'string' && selectedTrackId === track.id) { - cues = subtitleUtils.parse(text); - events.emit('load', Object.freeze({ - track: track - })); - } - }) - .catch(function() { - events.emit('error', Object.freeze({ - code: 71, - track: track - })); - }); - break; - } - } - } - }); - - Object.defineProperty(this, 'delay', { - configurable: false, - enumerable: true, - get: function() { return delay; }, - set: function(nextDelay) { - if (!isNaN(nextDelay)) { - delay = parseFloat(nextDelay); - } - } - }); - - Object.defineProperty(this, 'size', { - configurable: false, - enumerable: true, - get: function() { return parseFloat(stylesElement.sheet.cssRules[subtitleStylesIndex].style.fontSize); }, - set: function(nextSize) { - if (!isNaN(nextSize)) { - stylesElement.sheet.cssRules[subtitleStylesIndex].style.fontSize = parseFloat(nextSize) + 'pt'; - } - } - }); - - Object.defineProperty(this, 'darkBackground', { - configurable: false, - enumerable: true, - get: function() { return subtitlesElement.classList.contains('dark-background'); }, - set: function(nextDarkBackground) { - if (!!nextDarkBackground) { - subtitlesElement.classList.add('dark-background'); - } else { - subtitlesElement.classList.remove('dark-background'); - } - } - }); - this.addListener = function(eventName, listener) { + if (destroyed) { + throw new Error('Unable to add ' + eventName + ' listener'); + } + events.addListener(eventName, listener); }; this.removeListener = function(eventName, listener) { + if (destroyed) { + throw new Error('Unable to remove ' + eventName + ' listener'); + } + events.removeListener(eventName, listener); }; - this.addTracks = function(extraTracks) { - tracks = (Array.isArray(extraTracks) ? 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'; - }) - .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; - } + this.dispatch = function() { + if (destroyed) { + throw new Error('Unable to dispatch ' + arguments[0]); + } + + switch (arguments[0]) { + case 'getProp': + switch (arguments[1]) { + case 'tracks': + return Object.freeze(tracks.slice()); + case 'selectedTrackId': + return selectedTrackId; + case 'delay': + return delay; + case 'size': + return parseFloat(stylesElement.sheet.cssRules[subtitleStylesIndex].style.fontSize); + case 'darkBackground': + return subtitlesElement.classList.contains('dark-background'); + default: + throw new Error('getProp not supported: ' + arguments[1]); } + case 'setProp': + switch (arguments[1]) { + case 'selectedTrackId': + cues = Object.freeze({}); + selectedTrackId = null; + delay = 0; + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + if (track.id === arguments[2]) { + selectedTrackId = track.id; + fetch(track.url) + .then(function(resp) { + return resp.text(); + }) + .catch(function() { + events.emit('error', Object.freeze({ + code: 70, + track: track + })); + }) + .then(function(text) { + if (typeof text === 'string' && selectedTrackId === track.id) { + cues = subtitleUtils.parse(text); + events.emit('load', Object.freeze({ + track: track + })); + } + }) + .catch(function() { + events.emit('error', Object.freeze({ + code: 71, + track: track + })); + }); + break; + } + } + return; + case 'delay': + if (!isNaN(arguments[2])) { + delay = parseFloat(arguments[2]); + } + return; + case 'size': + if (!isNaN(arguments[2])) { + stylesElement.sheet.cssRules[subtitleStylesIndex].style.fontSize = parseFloat(arguments[2]) + 'pt'; + } + return; + case 'darkBackground': + if (arguments[2]) { + subtitlesElement.classList.add('dark-background'); + } else { + subtitlesElement.classList.remove('dark-background'); + } + return; + default: + throw new Error('setProp not supported: ' + arguments[1]); + } + case 'command': + switch (arguments[1]) { + case 'addTracks': + tracks = (Array.isArray(arguments[2]) ? arguments[2] : []) + .filter(function(track) { + return track && + typeof track.url === 'string' && + track.url.length > 0 && + typeof track.origin === 'string' && + track.origin.length > 0 && + track.origin !== 'EMBEDDED'; + }) + .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); - }; + return false; + }); + Object.freeze(tracks); + return; + case 'clearTracks': + tracks = Object.freeze([]); + cues = Object.freeze({}); + selectedTrackId = null; + delay = 0; + return; + case 'updateText': + while (subtitlesElement.hasChildNodes()) { + subtitlesElement.removeChild(subtitlesElement.lastChild); + } - this.updateTextForTime = function(mediaTime) { - while (subtitlesElement.hasChildNodes()) { - subtitlesElement.removeChild(subtitlesElement.lastChild); + if (isNaN(arguments[2]) || !Array.isArray(cues.times)) { + return; + } + + var time = arguments[2] + delay; + var cuesForTime = subtitleUtils.cuesForTime(cues, time); + for (var i = 0; i < cuesForTime.length; i++) { + var cueNode = subtitleUtils.render(cuesForTime[i]); + cueNode.classList.add('cue'); + subtitlesElement.append(cueNode, document.createElement('br')); + } + return; + case 'destroy': + destroyed = true; + events.removeAllListeners(); + self.dispatch('clearTracks'); + containerElement.removeChild(stylesElement); + containerElement.removeChild(subtitlesElement); + return; + } + default: + throw new Error('Invalid dispatch call: ' + Array.from(arguments).map(String)); } - - if (isNaN(mediaTime) || !Array.isArray(cues.times)) { - return; - } - - var time = mediaTime + delay; - var cuesForTime = subtitleUtils.cuesForTime(cues, time); - for (var i = 0; i < cuesForTime.length; i++) { - var cueNode = subtitleUtils.render(cuesForTime[i]); - cueNode.classList.add('cue'); - subtitlesElement.append(cueNode, document.createElement('br')); - } - }; - - this.clearTracks = function() { - tracks = Object.freeze([]); - cues = Object.freeze({}); - selectedTrackId = null; - delay = 0; - }; - - this.detachElements = function() { - containerElement.removeChild(stylesElement); - containerElement.removeChild(subtitlesElement); }; Object.freeze(this); }; +Object.freeze(HTMLSubtitles); + module.exports = HTMLSubtitles; diff --git a/src/routes/Player/Video/stremio-video/HTMLVideo.js b/src/routes/Player/Video/stremio-video/HTMLVideo.js index b2c376ddf..dc39a9ce9 100644 --- a/src/routes/Player/Video/stremio-video/HTMLVideo.js +++ b/src/routes/Player/Video/stremio-video/HTMLVideo.js @@ -61,38 +61,38 @@ var HTMLVideo = function(containerElement) { return []; } - return Object.freeze(subtitles.tracks.slice()); + return subtitles.dispatch('getProp', 'tracks'); } function getSelectedSubtitleTrackId() { if (!loaded) { return null; } - return subtitles.selectedTrackId; + return subtitles.dispatch('getProp', 'selectedTrackId'); } function getSubtitleDelay() { if (!loaded) { return null; } - return subtitles.delay; + return subtitles.dispatch('getProp', 'delay'); } function getSubtitleSize() { if (destroyed) { return null; } - return subtitles.size; + return subtitles.dispatch('getProp', 'size'); } function getSubtitleDarkBackground() { if (destroyed) { return null; } - return subtitles.darkBackground; + return subtitles.dispatch('getProp', 'darkBackground'); } function onError(error) { - Object.freeze(error) + Object.freeze(error); events.emit('error', error); if (error.critical) { self.dispatch('command', 'stop'); @@ -182,8 +182,7 @@ var HTMLVideo = function(containerElement) { events.emit('propChanged', 'subtitleDarkBackground', getSubtitleDarkBackground()); } function updateSubtitleText() { - var time = getTime(); - subtitles.updateTextForTime(time); + subtitles.dispatch('command', 'updateText', getTime()); } function flushArgsQueue() { for (var i = 0; i < dispatchArgsQueue.length; i++) { @@ -193,12 +192,20 @@ var HTMLVideo = function(containerElement) { dispatchArgsQueue = []; } - this.on = function(eventName, listener) { + this.addListener = function(eventName, listener) { if (destroyed) { - throw new Error('Unable to add ' + eventName + ' listener to destroyed video'); + throw new Error('Unable to add ' + eventName + ' listener'); } - events.on(eventName, listener); + events.addListener(eventName, listener); + }; + + this.removeListener = function(eventName, listener) { + if (destroyed) { + throw new Error('Unable to add ' + eventName + ' listener'); + } + + events.removeListener(eventName, listener); }; this.dispatch = function() { @@ -274,7 +281,7 @@ var HTMLVideo = function(containerElement) { break; case 'selectedSubtitleTrackId': if (loaded) { - subtitles.selectedTrackId = arguments[2]; + subtitles.dispatch('setProp', 'selectedTrackId', arguments[2]); onSubtitleDelayChanged(); onSelectedSubtitleTrackIdChanged(); updateSubtitleText(); @@ -283,7 +290,7 @@ var HTMLVideo = function(containerElement) { case 'subtitleDelay': if (loaded) { if (!isNaN(arguments[2])) { - subtitles.delay = arguments[2]; + subtitles.dispatch('setProp', 'delay', arguments[2]); onSubtitleDelayChanged(); updateSubtitleText(); } @@ -291,12 +298,12 @@ var HTMLVideo = function(containerElement) { break; case 'subtitleSize': if (!isNaN(arguments[2])) { - subtitles.size = arguments[2]; + subtitles.dispatch('setProp', 'size', arguments[2]); onSubtitleSizeChanged(); } return; case 'subtitleDarkBackground': - subtitles.darkBackground = arguments[2]; + subtitles.dispatch('setProp', 'darkBackground', arguments[2]); onSubtitleDarkBackgroundChanged(); return; case 'volume': @@ -313,7 +320,7 @@ var HTMLVideo = function(containerElement) { switch (arguments[1]) { case 'addSubtitleTracks': if (loaded) { - subtitles.addTracks(arguments[2]); + subtitles.dispatch('command', 'addTracks', arguments[2]); onSubtitleTracksChanged(); } break; @@ -332,7 +339,7 @@ var HTMLVideo = function(containerElement) { subtitles.removeListener('load', updateSubtitleText); loaded = false; dispatchArgsQueue = []; - subtitles.clearTracks(); + subtitles.dispatch('command', 'clearTracks'); videoElement.removeAttribute('src'); videoElement.load(); videoElement.currentTime = 0; @@ -383,7 +390,7 @@ var HTMLVideo = function(containerElement) { videoElement.removeEventListener('loadeddata', onBufferingChanged); containerElement.removeChild(videoElement); containerElement.removeChild(stylesElement); - subtitles.detachElements(); + subtitles.dispatch('command', 'destroy'); return; default: throw new Error('command not supported: ' + arguments[1]);