diff --git a/CHANGELOG.md b/CHANGELOG.md index eeca912a0e..a509aeebc7 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 9f730001b7..3abd7ed069 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); + TextTrackCueList.prototype.setCues_.call(list, cues); - Object.defineProperty(list, 'length', { - get: function() { - return this.length_; - } - }); + Object.defineProperty(list, 'length', { + get() { + return this.length_; + } + }); - if (browser.IS_IE8) { - return list; + if (browser.IS_IE8) { + return list; + } } -} -TextTrackCueList.prototype.setCues_ = function(cues) { - let oldLength = this.length || 0; - let i = 0; - let l = cues.length; + /** + * 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; + 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]; - } - }); - } - }; + let defineProp = function(index) { + if (!('' + index in this)) { + Object.defineProperty(this, '' + index, { + get() { + return this.cues_[index]; + } + }); + } + }; - if (oldLength < l) { - i = oldLength; + if (oldLength < l) { + i = oldLength; - for(; i < l; i++) { - defineProp.call(this, i); + 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; + + /** + * 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; + + for (let i = 0, l = this.length; i < l; i++) { + let cue = this[i]; + + if (cue.id === id) { + result = cue; + break; + } } - } - return result; -}; + return result; + } +} export default TextTrackCueList; diff --git a/src/js/tracks/text-track-enums.js b/src/js/tracks/text-track-enums.js index 738fbf4cbd..40c34f77b3 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 853d45a293..f4b9943312 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,131 +19,145 @@ 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; + } } -} -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' -}; + /** + * 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]; + } + }); + } -// emulate attribute EventHandler support to allow for feature detection -for (let event in TextTrackList.prototype.allowedEvents_) { - TextTrackList.prototype['on' + event] = null; -} + track.addEventListener('modechange', Fn.bind(this, function() { + this.trigger('change'); + })); + this.tracks_.push(track); -/** - * 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]; - } + this.trigger({ + track, + type: 'addtrack' }); } - 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(); + /** + * 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; } + } - this.tracks_.splice(i, 1); - - break; + if (!track) { + return; } - } - if (!track) { - return; + this.trigger({ + track, + type: 'removetrack' + }); } - 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; + /** + * 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; } +} - return result; +/** + * 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' }; +// emulate attribute EventHandler support to allow for feature detection +for (let event in TextTrackList.prototype.allowedEvents_) { + TextTrackList.prototype['on' + event] = null; +} + export default TextTrackList; diff --git a/src/js/tracks/text-track.js b/src/js/tracks/text-track.js index 8678ab3431..3f28a02fbf 100644 --- a/src/js/tracks/text-track.js +++ b/src/js/tracks/text-track.js @@ -13,7 +13,73 @@ import window from 'global/window'; import { isCrossOrigin } from '../utils/url.js'; import XHR from 'xhr'; -/* +/** + * takes a webvtt file contents and parses it into cues + * + * @param {String} srcContent webVTT file contents + * @param {Track} track track to addcues to + */ +const parseCues = function(srcContent, track) { + let parser = new window.WebVTT.Parser(window, + window.vttjs, + window.WebVTT.StringDecoder()); + + parser.oncue = function(cue) { + track.addCue(cue); + }; + + parser.onparsingerror = function(error) { + log.error(error); + }; + + parser.onflush = function() { + track.trigger({ + type: 'loadeddata', + target: track + }); + }; + + parser.parse(srcContent); + parser.flush(); +}; + + +/** + * 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) { + if (err) { + return log.error(err, response); + } + + track.loaded_ = true; + + // NOTE: this is only used for the alt/video.novtt.js build + if (typeof window.WebVTT !== 'function') { + window.setTimeout(function() { + parseCues(responseBody, track); + }, 100); + } else { + parseCues(responseBody, track); + } + })); +}; + +/** + * A single text track as defined in: * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack * * interface TextTrack : EventTarget { @@ -34,288 +100,223 @@ import XHR from 'xhr'; * * attribute EventHandler oncuechange; * }; + * + * @param {Object=} options Object of option names and values + * @extends EventTarget + * @class TextTrack */ -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]; - } +class TextTrack extends EventTarget { + constructor(options = {}) { + super(); + if (!options.tech) { + throw new Error('A tech was not provided.'); } - } - tt.tech_ = options.tech; + let tt = this; - 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 (browser.IS_IE8) { + tt = document.createElement('custom'); - if (kind === 'metadata' || kind === 'chapters') { - mode = 'hidden'; - } + for (let prop in TextTrack.prototype) { + if (prop !== 'constructor') { + tt[prop] = TextTrack.prototype[prop]; + } + } + } - tt.cues_ = []; - tt.activeCues_ = []; + tt.tech_ = options.tech; - let cues = new TextTrackCueList(tt.cues_); - let activeCues = new TextTrackCueList(tt.activeCues_); + 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(); - let changed = false; - let timeupdateHandler = Fn.bind(tt, function() { - this['activeCues']; - if (changed) { - this['trigger']('cuechange'); - changed = false; + if (kind === 'metadata' || kind === 'chapters') { + mode = 'hidden'; } - }); - 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); + 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; } - this.trigger('modechange'); - } - }); + }); - Object.defineProperty(tt, 'cues', { - get: function() { - if (!this.loaded_) { - return null; - } + if (mode !== 'disabled') { + tt.tech_.on('timeupdate', timeupdateHandler); + } - return cues; - }, - set: Function.prototype - }); + Object.defineProperty(tt, 'kind', { + get() { + return kind; + }, + set() {} + }); - Object.defineProperty(tt, 'activeCues', { - get: function() { - if (!this.loaded_) { - return null; - } + Object.defineProperty(tt, 'label', { + get() { + return label; + }, + set() {} + }); - if (this['cues'].length === 0) { - return activeCues; // nothing to do - } + Object.defineProperty(tt, 'language', { + get() { + return language; + }, + set() {} + }); - let ct = this.tech_.currentTime(); - let active = []; + Object.defineProperty(tt, 'id', { + get() { + return id; + }, + set() {} + }); - 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); + Object.defineProperty(tt, 'mode', { + get() { + return mode; + }, + set(newMode) { + if (!TextTrackEnum.TextTrackMode[newMode]) { + return; } - } - - 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; - } + mode = newMode; + if (mode === 'showing') { + this.tech_.on('timeupdate', timeupdateHandler); } + this.trigger('modechange'); } + }); - 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' -}; + Object.defineProperty(tt, 'cues', { + get() { + if (!this.loaded_) { + return null; + } -TextTrack.prototype.addCue = function(cue) { - let tracks = this.tech_.textTracks(); + return cues; + }, + set() {} + }); - if (tracks) { - for (let i = 0; i < tracks.length; i++) { - if (tracks[i] !== this) { - tracks[i].removeCue(cue); - } - } - } + Object.defineProperty(tt, 'activeCues', { + get() { + if (!this.loaded_) { + return null; + } - this.cues_.push(cue); - this['cues'].setCues_(this.cues_); -}; + // nothing to do + if (this.cues.length === 0) { + return activeCues; + } -TextTrack.prototype.removeCue = function(removeCue) { - let removed = false; + 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 === removeCue) { - this.cues_.splice(i, 1); - removed = true; - } - } + for (let i = 0, l = this.cues.length; i < l; i++) { + let cue = this.cues[i]; - if (removed) { - this.cues.setCues_(this.cues_); - } -}; + 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); + } + } -/* -* Downloading stuff happens below this point -*/ -var parseCues = function(srcContent, track) { - let parser = new window.WebVTT.Parser(window, window.vttjs, window.WebVTT.StringDecoder()); + changed = false; - parser.oncue = function(cue) { - track.addCue(cue); - }; + 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; + } + } + } - parser.onparsingerror = function(error) { - log.error(error); - }; + this.activeCues_ = active; + activeCues.setCues_(this.activeCues_); - parser.onflush = function() { - track.trigger({ - type: 'loadeddata', - target: track + return activeCues; + }, + set() {} }); - }; - parser.parse(srcContent); - parser.flush(); -}; - -var 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){ - if (err) { - return log.error(err, response); - } - - track.loaded_ = true; - - // NOTE: this is only used for the alt/video.novtt.js build - if (typeof window.WebVTT !== 'function') { - window.setTimeout(function() { - parseCues(responseBody, track); - }, 100); + if (options.src) { + tt.src = options.src; + loadTrack(options.src, tt); } else { - parseCues(responseBody, track); + tt.loaded_ = true; } - })); -}; - -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; + if (browser.IS_IE8) { + return tt; + } } - let n = +fromIndex || 0; - - if (Math.abs(n) === Infinity) { - n = 0; - } + /** + * 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); + } + } + } - if (n >= len) { - return -1; + this.cues_.push(cue); + this.cues.setCues_(this.cues_); } - let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); + /** + * 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; + } + } - while (k < len) { - if (k in O && O[k] === searchElement) { - return k; + if (removed) { + this.cues.setCues_(this.cues_); } - k++; } - return -1; +} + +/** + * cuechange - One or more cues in the track have become active or stopped being active. + */ +TextTrack.prototype.allowedEvents_ = { + cuechange: 'cuechange' }; export default TextTrack;