From cb3d70923736feea8dc3ef4087c4dd0420aae2dd Mon Sep 17 00:00:00 2001 From: brandonocasey Date: Mon, 29 Feb 2016 11:08:38 -0500 Subject: [PATCH] @BrandonOCasey converted remaining text-track modules to ES6. closes #3130 --- CHANGELOG.md | 1 + src/js/tracks/text-track-cue-list.js | 132 ++++--- src/js/tracks/text-track-enums.js | 38 +- src/js/tracks/text-track-list.js | 219 ++++++------ src/js/tracks/text-track.js | 509 ++++++++++++++------------- 5 files changed, 475 insertions(+), 424 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eeca912a0..a509aeebc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ CHANGELOG ## HEAD (Unreleased) * @gkatsev updated videojs badges in the README ([view](https://github.com/videojs/video.js/pull/3134)) +* @BrandonOCasey converted remaining text-track modules to ES6 ([view](https://github.com/videojs/video.js/pull/3130)) -------------------- diff --git a/src/js/tracks/text-track-cue-list.js b/src/js/tracks/text-track-cue-list.js index 9f730001b..3abd7ed06 100644 --- a/src/js/tracks/text-track-cue-list.js +++ b/src/js/tracks/text-track-cue-list.js @@ -4,7 +4,8 @@ import * as browser from '../utils/browser.js'; import document from 'global/document'; -/* +/** + * A List of text track cues as defined in: * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist * * interface TextTrackCueList { @@ -12,72 +13,93 @@ import document from 'global/document'; * getter TextTrackCue (unsigned long index); * TextTrackCue? getCueById(DOMString id); * }; + * + * @param {Array} cues A list of cues to be initialized with + * @class TextTrackCueList */ -function TextTrackCueList (cues) { - let list = this; +class TextTrackCueList { + constructor(cues) { + let list = this; - if (browser.IS_IE8) { - list = document.createElement('custom'); + if (browser.IS_IE8) { + list = document.createElement('custom'); - for (let prop in TextTrackCueList.prototype) { - if (prop !== 'constructor') { - list[prop] = TextTrackCueList.prototype[prop]; + for (let prop in TextTrackCueList.prototype) { + if (prop !== 'constructor') { + list[prop] = TextTrackCueList.prototype[prop]; + } + } + } + + TextTrackCueList.prototype.setCues_.call(list, cues); + + Object.defineProperty(list, 'length', { + get() { + return this.length_; + } + }); + + if (browser.IS_IE8) { + return list; + } + } + + /** + * A setter for cues in this list + * + * @param {Array} cues an array of cues + * @method setCues_ + * @private + */ + setCues_(cues) { + let oldLength = this.length || 0; + let i = 0; + let l = cues.length; + + this.cues_ = cues; + this.length_ = cues.length; + + let defineProp = function(index) { + if (!('' + index in this)) { + Object.defineProperty(this, '' + index, { + get() { + return this.cues_[index]; + } + }); + } + }; + + if (oldLength < l) { + i = oldLength; + + for (; i < l; i++) { + defineProp.call(this, i); } } } - TextTrackCueList.prototype.setCues_.call(list, cues); + /** + * Get a cue that is currently in the Cue list by id + * + * @param {String} id + * @method getCueById + * @return {Object} a single cue + */ + getCueById(id) { + let result = null; - Object.defineProperty(list, 'length', { - get: function() { - return this.length_; + for (let i = 0, l = this.length; i < l; i++) { + let cue = this[i]; + + if (cue.id === id) { + result = cue; + break; + } } - }); - if (browser.IS_IE8) { - return list; + return result; } } -TextTrackCueList.prototype.setCues_ = function(cues) { - let oldLength = this.length || 0; - let i = 0; - let l = cues.length; - - this.cues_ = cues; - this.length_ = cues.length; - - let defineProp = function(i) { - if (!(''+i in this)) { - Object.defineProperty(this, '' + i, { - get: function() { - return this.cues_[i]; - } - }); - } - }; - - if (oldLength < l) { - i = oldLength; - - for(; i < l; i++) { - defineProp.call(this, i); - } - } -}; - -TextTrackCueList.prototype.getCueById = function(id) { - let result = null; - for (let i = 0, l = this.length; i < l; i++) { - let cue = this[i]; - if (cue.id === id) { - result = cue; - break; - } - } - - return result; -}; - export default TextTrackCueList; diff --git a/src/js/tracks/text-track-enums.js b/src/js/tracks/text-track-enums.js index 738fbf4cb..40c34f77b 100644 --- a/src/js/tracks/text-track-enums.js +++ b/src/js/tracks/text-track-enums.js @@ -1,27 +1,39 @@ /** * @file text-track-enums.js - * + */ + +/** * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode * * enum TextTrackMode { "disabled", "hidden", "showing" }; */ -var TextTrackMode = { - 'disabled': 'disabled', - 'hidden': 'hidden', - 'showing': 'showing' +const TextTrackMode = { + disabled: 'disabled', + hidden: 'hidden', + showing: 'showing' }; -/* +/** * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackkind * - * enum TextTrackKind { "subtitles", "captions", "descriptions", "chapters", "metadata" }; + * enum TextTrackKind { + * "subtitles", + * "captions", + * "descriptions", + * "chapters", + * "metadata" + * }; */ -var TextTrackKind = { - 'subtitles': 'subtitles', - 'captions': 'captions', - 'descriptions': 'descriptions', - 'chapters': 'chapters', - 'metadata': 'metadata' +const TextTrackKind = { + subtitles: 'subtitles', + captions: 'captions', + descriptions: 'descriptions', + chapters: 'chapters', + metadata: 'metadata' }; +/* jshint ignore:start */ +// we ignore jshint here because it does not see +// TextTrackMode or TextTrackKind as defined here somehow... export { TextTrackMode, TextTrackKind }; +/* jshint ignore:end */ diff --git a/src/js/tracks/text-track-list.js b/src/js/tracks/text-track-list.js index 853d45a29..f4b994331 100644 --- a/src/js/tracks/text-track-list.js +++ b/src/js/tracks/text-track-list.js @@ -6,7 +6,8 @@ import * as Fn from '../utils/fn.js'; import * as browser from '../utils/browser.js'; import document from 'global/document'; -/* +/** + * A text track list as defined in: * https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist * * interface TextTrackList : EventTarget { @@ -18,50 +19,140 @@ import document from 'global/document'; * attribute EventHandler onaddtrack; * attribute EventHandler onremovetrack; * }; + * + * @param {Track[]} tracks A list of tracks to initialize the list with + * @extends EventTarget + * @class TextTrackList */ -function TextTrackList (tracks) { - let list = this; - if (browser.IS_IE8) { - list = document.createElement('custom'); +class TextTrackList extends EventTarget { + constructor(tracks = []) { + super(); + let list = this; - for (let prop in TextTrackList.prototype) { - if (prop !== 'constructor') { - list[prop] = TextTrackList.prototype[prop]; + if (browser.IS_IE8) { + list = document.createElement('custom'); + + for (let prop in TextTrackList.prototype) { + if (prop !== 'constructor') { + list[prop] = TextTrackList.prototype[prop]; + } } } - } - tracks = tracks || []; - list.tracks_ = []; + list.tracks_ = []; - Object.defineProperty(list, 'length', { - get: function() { - return this.tracks_.length; + Object.defineProperty(list, 'length', { + get() { + return this.tracks_.length; + } + }); + + for (let i = 0; i < tracks.length; i++) { + list.addTrack_(tracks[i]); } - }); - for (let i = 0; i < tracks.length; i++) { - list.addTrack_(tracks[i]); + if (browser.IS_IE8) { + return list; + } } - if (browser.IS_IE8) { - return list; + /** + * Add TextTrack from TextTrackList + * + * @param {TextTrack} track + * @method addTrack_ + * @private + */ + addTrack_(track) { + let index = this.tracks_.length; + + if (!('' + index in this)) { + Object.defineProperty(this, index, { + get() { + return this.tracks_[index]; + } + }); + } + + track.addEventListener('modechange', Fn.bind(this, function() { + this.trigger('change'); + })); + this.tracks_.push(track); + + this.trigger({ + track, + type: 'addtrack' + }); + } + + /** + * Remove TextTrack from TextTrackList + * NOTE: Be mindful of what is passed in as it may be a HTMLTrackElement + * + * @param {TextTrack} rtrack + * @method removeTrack_ + * @private + */ + removeTrack_(rtrack) { + let track; + + for (let i = 0, l = this.length; i < l; i++) { + if (this[i] === rtrack) { + track = this[i]; + if (track.off) { + track.off(); + } + + this.tracks_.splice(i, 1); + + break; + } + } + + if (!track) { + return; + } + + this.trigger({ + track, + type: 'removetrack' + }); + } + + /** + * Get a TextTrack from TextTrackList by a tracks id + * + * @param {String} id - the id of the track to get + * @method getTrackById + * @return {TextTrack} + * @private + */ + getTrackById(id) { + let result = null; + + for (let i = 0, l = this.length; i < l; i++) { + let track = this[i]; + + if (track.id === id) { + result = track; + break; + } + } + + return result; } } -TextTrackList.prototype = Object.create(EventTarget.prototype); -TextTrackList.prototype.constructor = TextTrackList; - -/* +/** * change - One or more tracks in the track list have been enabled or disabled. * addtrack - A track has been added to the track list. * removetrack - A track has been removed from the track list. */ TextTrackList.prototype.allowedEvents_ = { - 'change': 'change', - 'addtrack': 'addtrack', - 'removetrack': 'removetrack' + change: 'change', + addtrack: 'addtrack', + removetrack: 'removetrack' }; // emulate attribute EventHandler support to allow for feature detection @@ -69,80 +160,4 @@ for (let event in TextTrackList.prototype.allowedEvents_) { TextTrackList.prototype['on' + event] = null; } -/** - * Add TextTrack from TextTrackList - * - * @param {TextTrack} track - * @method addTrack_ - * @private - */ -TextTrackList.prototype.addTrack_ = function(track) { - let index = this.tracks_.length; - if (!(''+index in this)) { - Object.defineProperty(this, index, { - get: function() { - return this.tracks_[index]; - } - }); - } - - track.addEventListener('modechange', Fn.bind(this, function() { - this.trigger('change'); - })); - this.tracks_.push(track); - - this.trigger({ - type: 'addtrack', - track: track - }); -}; - -/** - * Remove TextTrack from TextTrackList - * NOTE: Be mindful of what is passed in as it may be a HTMLTrackElement - * - * @param {TextTrack} rtrack - * @method removeTrack_ - * @private - */ -TextTrackList.prototype.removeTrack_ = function(rtrack) { - let track; - - for (let i = 0, l = this.length; i < l; i++) { - if (this[i] === rtrack) { - track = this[i]; - if (track.off) { - track.off(); - } - - this.tracks_.splice(i, 1); - - break; - } - } - - if (!track) { - return; - } - - this.trigger({ - type: 'removetrack', - track: track - }); -}; - -TextTrackList.prototype.getTrackById = function(id) { - let result = null; - - for (let i = 0, l = this.length; i < l; i++) { - let track = this[i]; - if (track.id === id) { - result = track; - break; - } - } - - return result; -}; - export default TextTrackList; diff --git a/src/js/tracks/text-track.js b/src/js/tracks/text-track.js index 8678ab343..3f28a02fb 100644 --- a/src/js/tracks/text-track.js +++ b/src/js/tracks/text-track.js @@ -13,229 +13,16 @@ import window from 'global/window'; import { isCrossOrigin } from '../utils/url.js'; import XHR from 'xhr'; -/* - * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack +/** + * takes a webvtt file contents and parses it into cues * - * interface TextTrack : EventTarget { - * readonly attribute TextTrackKind kind; - * readonly attribute DOMString label; - * readonly attribute DOMString language; - * - * readonly attribute DOMString id; - * readonly attribute DOMString inBandMetadataTrackDispatchType; - * - * attribute TextTrackMode mode; - * - * readonly attribute TextTrackCueList? cues; - * readonly attribute TextTrackCueList? activeCues; - * - * void addCue(TextTrackCue cue); - * void removeCue(TextTrackCue cue); - * - * attribute EventHandler oncuechange; - * }; + * @param {String} srcContent webVTT file contents + * @param {Track} track track to addcues to */ -function TextTrack (options={}) { - if (!options.tech) { - throw new Error('A tech was not provided.'); - } - - let tt = this; - if (browser.IS_IE8) { - tt = document.createElement('custom'); - - for (let prop in TextTrack.prototype) { - if (prop !== 'constructor') { - tt[prop] = TextTrack.prototype[prop]; - } - } - } - - tt.tech_ = options.tech; - - let mode = TextTrackEnum.TextTrackMode[options['mode']] || 'disabled'; - let kind = TextTrackEnum.TextTrackKind[options['kind']] || 'subtitles'; - let label = options['label'] || ''; - let language = options['language'] || options['srclang'] || ''; - let id = options['id'] || 'vjs_text_track_' + Guid.newGUID(); - - if (kind === 'metadata' || kind === 'chapters') { - mode = 'hidden'; - } - - tt.cues_ = []; - tt.activeCues_ = []; - - let cues = new TextTrackCueList(tt.cues_); - let activeCues = new TextTrackCueList(tt.activeCues_); - - let changed = false; - let timeupdateHandler = Fn.bind(tt, function() { - this['activeCues']; - if (changed) { - this['trigger']('cuechange'); - changed = false; - } - }); - if (mode !== 'disabled') { - tt.tech_.on('timeupdate', timeupdateHandler); - } - - Object.defineProperty(tt, 'kind', { - get: function() { - return kind; - }, - set: Function.prototype - }); - - Object.defineProperty(tt, 'label', { - get: function() { - return label; - }, - set: Function.prototype - }); - - Object.defineProperty(tt, 'language', { - get: function() { - return language; - }, - set: Function.prototype - }); - - Object.defineProperty(tt, 'id', { - get: function() { - return id; - }, - set: Function.prototype - }); - - Object.defineProperty(tt, 'mode', { - get: function() { - return mode; - }, - set: function(newMode) { - if (!TextTrackEnum.TextTrackMode[newMode]) { - return; - } - mode = newMode; - if (mode === 'showing') { - this.tech_.on('timeupdate', timeupdateHandler); - } - this.trigger('modechange'); - } - }); - - Object.defineProperty(tt, 'cues', { - get: function() { - if (!this.loaded_) { - return null; - } - - return cues; - }, - set: Function.prototype - }); - - Object.defineProperty(tt, 'activeCues', { - get: function() { - if (!this.loaded_) { - return null; - } - - if (this['cues'].length === 0) { - return activeCues; // nothing to do - } - - let ct = this.tech_.currentTime(); - let active = []; - - for (let i = 0, l = this['cues'].length; i < l; i++) { - let cue = this['cues'][i]; - if (cue['startTime'] <= ct && cue['endTime'] >= ct) { - active.push(cue); - } else if (cue['startTime'] === cue['endTime'] && cue['startTime'] <= ct && cue['startTime'] + 0.5 >= ct) { - active.push(cue); - } - } - - changed = false; - - if (active.length !== this.activeCues_.length) { - changed = true; - } else { - for (let i = 0; i < active.length; i++) { - if (indexOf.call(this.activeCues_, active[i]) === -1) { - changed = true; - } - } - } - - this.activeCues_ = active; - activeCues.setCues_(this.activeCues_); - - return activeCues; - }, - set: Function.prototype - }); - - if (options.src) { - tt.src = options.src; - loadTrack(options.src, tt); - } else { - tt.loaded_ = true; - } - - if (browser.IS_IE8) { - return tt; - } -} - -TextTrack.prototype = Object.create(EventTarget.prototype); -TextTrack.prototype.constructor = TextTrack; - -/* - * cuechange - One or more cues in the track have become active or stopped being active. - */ -TextTrack.prototype.allowedEvents_ = { - 'cuechange': 'cuechange' -}; - -TextTrack.prototype.addCue = function(cue) { - let tracks = this.tech_.textTracks(); - - if (tracks) { - for (let i = 0; i < tracks.length; i++) { - if (tracks[i] !== this) { - tracks[i].removeCue(cue); - } - } - } - - this.cues_.push(cue); - this['cues'].setCues_(this.cues_); -}; - -TextTrack.prototype.removeCue = function(removeCue) { - let removed = false; - - for (let i = 0, l = this.cues_.length; i < l; i++) { - let cue = this.cues_[i]; - if (cue === removeCue) { - this.cues_.splice(i, 1); - removed = true; - } - } - - if (removed) { - this.cues.setCues_(this.cues_); - } -}; - -/* -* Downloading stuff happens below this point -*/ -var parseCues = function(srcContent, track) { - let parser = new window.WebVTT.Parser(window, window.vttjs, window.WebVTT.StringDecoder()); +const parseCues = function(srcContent, track) { + let parser = new window.WebVTT.Parser(window, + window.vttjs, + window.WebVTT.StringDecoder()); parser.oncue = function(cue) { track.addCue(cue); @@ -256,17 +43,24 @@ var parseCues = function(srcContent, track) { parser.flush(); }; -var loadTrack = function(src, track) { + +/** + * load a track from a specifed url + * + * @param {String} src url to load track from + * @param {Track} track track to addcues to + */ +const loadTrack = function(src, track) { let opts = { uri: src }; - let crossOrigin = isCrossOrigin(src); + if (crossOrigin) { opts.cors = crossOrigin; } - XHR(opts, Fn.bind(this, function(err, response, responseBody){ + XHR(opts, Fn.bind(this, function(err, response, responseBody) { if (err) { return log.error(err, response); } @@ -284,38 +78,245 @@ var loadTrack = function(src, track) { })); }; -var indexOf = function(searchElement, fromIndex) { - if (this == null) { - throw new TypeError('"this" is null or not defined'); - } - - let O = Object(this); - - let len = O.length >>> 0; - - if (len === 0) { - return -1; - } - - let n = +fromIndex || 0; - - if (Math.abs(n) === Infinity) { - n = 0; - } - - if (n >= len) { - return -1; - } - - let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); - - while (k < len) { - if (k in O && O[k] === searchElement) { - return k; +/** + * A single text track as defined in: + * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack + * + * interface TextTrack : EventTarget { + * readonly attribute TextTrackKind kind; + * readonly attribute DOMString label; + * readonly attribute DOMString language; + * + * readonly attribute DOMString id; + * readonly attribute DOMString inBandMetadataTrackDispatchType; + * + * attribute TextTrackMode mode; + * + * readonly attribute TextTrackCueList? cues; + * readonly attribute TextTrackCueList? activeCues; + * + * void addCue(TextTrackCue cue); + * void removeCue(TextTrackCue cue); + * + * attribute EventHandler oncuechange; + * }; + * + * @param {Object=} options Object of option names and values + * @extends EventTarget + * @class TextTrack + */ +class TextTrack extends EventTarget { + constructor(options = {}) { + super(); + if (!options.tech) { + throw new Error('A tech was not provided.'); + } + + let tt = this; + + if (browser.IS_IE8) { + tt = document.createElement('custom'); + + for (let prop in TextTrack.prototype) { + if (prop !== 'constructor') { + tt[prop] = TextTrack.prototype[prop]; + } + } + } + + tt.tech_ = options.tech; + + let mode = TextTrackEnum.TextTrackMode[options.mode] || 'disabled'; + let kind = TextTrackEnum.TextTrackKind[options.kind] || 'subtitles'; + let label = options.label || ''; + let language = options.language || options.srclang || ''; + let id = options.id || 'vjs_text_track_' + Guid.newGUID(); + + if (kind === 'metadata' || kind === 'chapters') { + mode = 'hidden'; + } + + tt.cues_ = []; + tt.activeCues_ = []; + + let cues = new TextTrackCueList(tt.cues_); + let activeCues = new TextTrackCueList(tt.activeCues_); + let changed = false; + let timeupdateHandler = Fn.bind(tt, function() { + this.activeCues; + if (changed) { + this.trigger('cuechange'); + changed = false; + } + }); + + if (mode !== 'disabled') { + tt.tech_.on('timeupdate', timeupdateHandler); + } + + Object.defineProperty(tt, 'kind', { + get() { + return kind; + }, + set() {} + }); + + Object.defineProperty(tt, 'label', { + get() { + return label; + }, + set() {} + }); + + Object.defineProperty(tt, 'language', { + get() { + return language; + }, + set() {} + }); + + Object.defineProperty(tt, 'id', { + get() { + return id; + }, + set() {} + }); + + Object.defineProperty(tt, 'mode', { + get() { + return mode; + }, + set(newMode) { + if (!TextTrackEnum.TextTrackMode[newMode]) { + return; + } + mode = newMode; + if (mode === 'showing') { + this.tech_.on('timeupdate', timeupdateHandler); + } + this.trigger('modechange'); + } + }); + + Object.defineProperty(tt, 'cues', { + get() { + if (!this.loaded_) { + return null; + } + + return cues; + }, + set() {} + }); + + Object.defineProperty(tt, 'activeCues', { + get() { + if (!this.loaded_) { + return null; + } + + // nothing to do + if (this.cues.length === 0) { + return activeCues; + } + + let ct = this.tech_.currentTime(); + let active = []; + + for (let i = 0, l = this.cues.length; i < l; i++) { + let cue = this.cues[i]; + + if (cue.startTime <= ct && cue.endTime >= ct) { + active.push(cue); + } else if (cue.startTime === cue.endTime && + cue.startTime <= ct && + cue.startTime + 0.5 >= ct) { + active.push(cue); + } + } + + changed = false; + + if (active.length !== this.activeCues_.length) { + changed = true; + } else { + for (let i = 0; i < active.length; i++) { + if (this.activeCues_.indexOf(active[i]) === -1) { + changed = true; + } + } + } + + this.activeCues_ = active; + activeCues.setCues_(this.activeCues_); + + return activeCues; + }, + set() {} + }); + + if (options.src) { + tt.src = options.src; + loadTrack(options.src, tt); + } else { + tt.loaded_ = true; + } + + if (browser.IS_IE8) { + return tt; } - k++; } - return -1; + + /** + * add a cue to the internal list of cues + * + * @param {Object} cue the cue to add to our internal list + * @method addCue + */ + addCue(cue) { + let tracks = this.tech_.textTracks(); + + if (tracks) { + for (let i = 0; i < tracks.length; i++) { + if (tracks[i] !== this) { + tracks[i].removeCue(cue); + } + } + } + + this.cues_.push(cue); + this.cues.setCues_(this.cues_); + } + + /** + * remvoe a cue from our internal list + * + * @param {Object} removeCue the cue to remove from our internal list + * @method removeCue + */ + removeCue(removeCue) { + let removed = false; + + for (let i = 0, l = this.cues_.length; i < l; i++) { + let cue = this.cues_[i]; + + if (cue === removeCue) { + this.cues_.splice(i, 1); + removed = true; + } + } + + if (removed) { + this.cues.setCues_(this.cues_); + } + } +} + +/** + * cuechange - One or more cues in the track have become active or stopped being active. + */ +TextTrack.prototype.allowedEvents_ = { + cuechange: 'cuechange' }; export default TextTrack;