HTMLSubtitles uses simpler API

This commit is contained in:
NikolaBorislavovHristov 2019-02-14 12:29:39 +02:00
parent 8ff4990c2f
commit d22e42875b
3 changed files with 180 additions and 158 deletions

View file

@ -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);
}); });

View file

@ -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;

View file

@ -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]);