mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
HTMLSubtitles uses simpler API
This commit is contained in:
parent
8ff4990c2f
commit
d22e42875b
3 changed files with 180 additions and 158 deletions
|
|
@ -35,10 +35,10 @@ class Video extends Component {
|
||||||
if (this.video === null || this.video.constructor !== Video) {
|
if (this.video === null || this.video.constructor !== Video) {
|
||||||
this.dispatch('command', 'destroy');
|
this.dispatch('command', 'destroy');
|
||||||
this.video = new Video(this.containerRef.current);
|
this.video = new Video(this.containerRef.current);
|
||||||
this.video.on('ended', this.props.onEnded);
|
this.video.addListener('ended', this.props.onEnded);
|
||||||
this.video.on('error', this.props.onError);
|
this.video.addListener('error', this.props.onError);
|
||||||
this.video.on('propValue', this.props.onPropValue);
|
this.video.addListener('propValue', this.props.onPropValue);
|
||||||
this.video.on('propChanged', this.props.onPropChanged);
|
this.video.addListener('propChanged', this.props.onPropChanged);
|
||||||
this.video.constructor.manifest.props.forEach((propName) => {
|
this.video.constructor.manifest.props.forEach((propName) => {
|
||||||
this.dispatch('observeProp', propName);
|
this.dispatch('observeProp', propName);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ var subtitleUtils = require('./utils/subtitles');
|
||||||
|
|
||||||
var HTMLSubtitles = function(containerElement) {
|
var HTMLSubtitles = function(containerElement) {
|
||||||
if (!(containerElement instanceof HTMLElement)) {
|
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 events = new EventEmitter();
|
||||||
|
var destroyed = false;
|
||||||
var tracks = Object.freeze([]);
|
var tracks = Object.freeze([]);
|
||||||
var cues = Object.freeze({});
|
var cues = Object.freeze({});
|
||||||
var selectedTrackId = null;
|
var selectedTrackId = null;
|
||||||
|
|
@ -21,156 +23,169 @@ var HTMLSubtitles = function(containerElement) {
|
||||||
containerElement.appendChild(subtitlesElement);
|
containerElement.appendChild(subtitlesElement);
|
||||||
subtitlesElement.classList.add('subtitles');
|
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) {
|
this.addListener = function(eventName, listener) {
|
||||||
|
if (destroyed) {
|
||||||
|
throw new Error('Unable to add ' + eventName + ' listener');
|
||||||
|
}
|
||||||
|
|
||||||
events.addListener(eventName, listener);
|
events.addListener(eventName, listener);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.removeListener = function(eventName, listener) {
|
this.removeListener = function(eventName, listener) {
|
||||||
|
if (destroyed) {
|
||||||
|
throw new Error('Unable to remove ' + eventName + ' listener');
|
||||||
|
}
|
||||||
|
|
||||||
events.removeListener(eventName, listener);
|
events.removeListener(eventName, listener);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addTracks = function(extraTracks) {
|
this.dispatch = function() {
|
||||||
tracks = (Array.isArray(extraTracks) ? extraTracks : [])
|
if (destroyed) {
|
||||||
.filter(function(track) {
|
throw new Error('Unable to dispatch ' + arguments[0]);
|
||||||
return track &&
|
}
|
||||||
typeof track.url === 'string' &&
|
|
||||||
track.url.length > 0 &&
|
switch (arguments[0]) {
|
||||||
typeof track.origin === 'string' &&
|
case 'getProp':
|
||||||
track.origin.length > 0 &&
|
switch (arguments[1]) {
|
||||||
track.origin !== 'EMBEDDED';
|
case 'tracks':
|
||||||
})
|
return Object.freeze(tracks.slice());
|
||||||
.map(function(track) {
|
case 'selectedTrackId':
|
||||||
return Object.freeze(Object.assign({}, track, {
|
return selectedTrackId;
|
||||||
id: track.url
|
case 'delay':
|
||||||
}));
|
return delay;
|
||||||
})
|
case 'size':
|
||||||
.concat(tracks)
|
return parseFloat(stylesElement.sheet.cssRules[subtitleStylesIndex].style.fontSize);
|
||||||
.filter(function(track, index, tracks) {
|
case 'darkBackground':
|
||||||
for (var i = 0; i < tracks.length; i++) {
|
return subtitlesElement.classList.contains('dark-background');
|
||||||
if (tracks[i].id === track.id) {
|
default:
|
||||||
return i === index;
|
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;
|
return false;
|
||||||
});
|
});
|
||||||
Object.freeze(tracks);
|
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) {
|
if (isNaN(arguments[2]) || !Array.isArray(cues.times)) {
|
||||||
while (subtitlesElement.hasChildNodes()) {
|
return;
|
||||||
subtitlesElement.removeChild(subtitlesElement.lastChild);
|
}
|
||||||
|
|
||||||
|
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(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Object.freeze(HTMLSubtitles);
|
||||||
|
|
||||||
module.exports = HTMLSubtitles;
|
module.exports = HTMLSubtitles;
|
||||||
|
|
|
||||||
|
|
@ -61,38 +61,38 @@ var HTMLVideo = function(containerElement) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.freeze(subtitles.tracks.slice());
|
return subtitles.dispatch('getProp', 'tracks');
|
||||||
}
|
}
|
||||||
function getSelectedSubtitleTrackId() {
|
function getSelectedSubtitleTrackId() {
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return subtitles.selectedTrackId;
|
return subtitles.dispatch('getProp', 'selectedTrackId');
|
||||||
}
|
}
|
||||||
function getSubtitleDelay() {
|
function getSubtitleDelay() {
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return subtitles.delay;
|
return subtitles.dispatch('getProp', 'delay');
|
||||||
}
|
}
|
||||||
function getSubtitleSize() {
|
function getSubtitleSize() {
|
||||||
if (destroyed) {
|
if (destroyed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return subtitles.size;
|
return subtitles.dispatch('getProp', 'size');
|
||||||
}
|
}
|
||||||
function getSubtitleDarkBackground() {
|
function getSubtitleDarkBackground() {
|
||||||
if (destroyed) {
|
if (destroyed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return subtitles.darkBackground;
|
return subtitles.dispatch('getProp', 'darkBackground');
|
||||||
}
|
}
|
||||||
function onError(error) {
|
function onError(error) {
|
||||||
Object.freeze(error)
|
Object.freeze(error);
|
||||||
events.emit('error', error);
|
events.emit('error', error);
|
||||||
if (error.critical) {
|
if (error.critical) {
|
||||||
self.dispatch('command', 'stop');
|
self.dispatch('command', 'stop');
|
||||||
|
|
@ -182,8 +182,7 @@ var HTMLVideo = function(containerElement) {
|
||||||
events.emit('propChanged', 'subtitleDarkBackground', getSubtitleDarkBackground());
|
events.emit('propChanged', 'subtitleDarkBackground', getSubtitleDarkBackground());
|
||||||
}
|
}
|
||||||
function updateSubtitleText() {
|
function updateSubtitleText() {
|
||||||
var time = getTime();
|
subtitles.dispatch('command', 'updateText', getTime());
|
||||||
subtitles.updateTextForTime(time);
|
|
||||||
}
|
}
|
||||||
function flushArgsQueue() {
|
function flushArgsQueue() {
|
||||||
for (var i = 0; i < dispatchArgsQueue.length; i++) {
|
for (var i = 0; i < dispatchArgsQueue.length; i++) {
|
||||||
|
|
@ -193,12 +192,20 @@ var HTMLVideo = function(containerElement) {
|
||||||
dispatchArgsQueue = [];
|
dispatchArgsQueue = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.on = function(eventName, listener) {
|
this.addListener = function(eventName, listener) {
|
||||||
if (destroyed) {
|
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() {
|
this.dispatch = function() {
|
||||||
|
|
@ -274,7 +281,7 @@ var HTMLVideo = function(containerElement) {
|
||||||
break;
|
break;
|
||||||
case 'selectedSubtitleTrackId':
|
case 'selectedSubtitleTrackId':
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
subtitles.selectedTrackId = arguments[2];
|
subtitles.dispatch('setProp', 'selectedTrackId', arguments[2]);
|
||||||
onSubtitleDelayChanged();
|
onSubtitleDelayChanged();
|
||||||
onSelectedSubtitleTrackIdChanged();
|
onSelectedSubtitleTrackIdChanged();
|
||||||
updateSubtitleText();
|
updateSubtitleText();
|
||||||
|
|
@ -283,7 +290,7 @@ var HTMLVideo = function(containerElement) {
|
||||||
case 'subtitleDelay':
|
case 'subtitleDelay':
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
if (!isNaN(arguments[2])) {
|
if (!isNaN(arguments[2])) {
|
||||||
subtitles.delay = arguments[2];
|
subtitles.dispatch('setProp', 'delay', arguments[2]);
|
||||||
onSubtitleDelayChanged();
|
onSubtitleDelayChanged();
|
||||||
updateSubtitleText();
|
updateSubtitleText();
|
||||||
}
|
}
|
||||||
|
|
@ -291,12 +298,12 @@ var HTMLVideo = function(containerElement) {
|
||||||
break;
|
break;
|
||||||
case 'subtitleSize':
|
case 'subtitleSize':
|
||||||
if (!isNaN(arguments[2])) {
|
if (!isNaN(arguments[2])) {
|
||||||
subtitles.size = arguments[2];
|
subtitles.dispatch('setProp', 'size', arguments[2]);
|
||||||
onSubtitleSizeChanged();
|
onSubtitleSizeChanged();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case 'subtitleDarkBackground':
|
case 'subtitleDarkBackground':
|
||||||
subtitles.darkBackground = arguments[2];
|
subtitles.dispatch('setProp', 'darkBackground', arguments[2]);
|
||||||
onSubtitleDarkBackgroundChanged();
|
onSubtitleDarkBackgroundChanged();
|
||||||
return;
|
return;
|
||||||
case 'volume':
|
case 'volume':
|
||||||
|
|
@ -313,7 +320,7 @@ var HTMLVideo = function(containerElement) {
|
||||||
switch (arguments[1]) {
|
switch (arguments[1]) {
|
||||||
case 'addSubtitleTracks':
|
case 'addSubtitleTracks':
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
subtitles.addTracks(arguments[2]);
|
subtitles.dispatch('command', 'addTracks', arguments[2]);
|
||||||
onSubtitleTracksChanged();
|
onSubtitleTracksChanged();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -332,7 +339,7 @@ var HTMLVideo = function(containerElement) {
|
||||||
subtitles.removeListener('load', updateSubtitleText);
|
subtitles.removeListener('load', updateSubtitleText);
|
||||||
loaded = false;
|
loaded = false;
|
||||||
dispatchArgsQueue = [];
|
dispatchArgsQueue = [];
|
||||||
subtitles.clearTracks();
|
subtitles.dispatch('command', 'clearTracks');
|
||||||
videoElement.removeAttribute('src');
|
videoElement.removeAttribute('src');
|
||||||
videoElement.load();
|
videoElement.load();
|
||||||
videoElement.currentTime = 0;
|
videoElement.currentTime = 0;
|
||||||
|
|
@ -383,7 +390,7 @@ var HTMLVideo = function(containerElement) {
|
||||||
videoElement.removeEventListener('loadeddata', onBufferingChanged);
|
videoElement.removeEventListener('loadeddata', onBufferingChanged);
|
||||||
containerElement.removeChild(videoElement);
|
containerElement.removeChild(videoElement);
|
||||||
containerElement.removeChild(stylesElement);
|
containerElement.removeChild(stylesElement);
|
||||||
subtitles.detachElements();
|
subtitles.dispatch('command', 'destroy');
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
throw new Error('command not supported: ' + arguments[1]);
|
throw new Error('command not supported: ' + arguments[1]);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue