1
0
mirror of https://github.com/videojs/video.js.git synced 2025-01-02 06:32:07 +02:00

@BrandonOCasey converted remaining text-track modules to ES6. closes #3130

This commit is contained in:
brandonocasey 2016-02-29 11:08:38 -05:00 committed by Gary Katsevman
parent d9a0a4503c
commit cb3d709237
5 changed files with 475 additions and 424 deletions

View File

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

View File

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

View File

@ -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 */

View File

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

View File

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