mirror of
https://github.com/videojs/video.js.git
synced 2025-01-10 23:30:03 +02:00
@gkatsev always use emulated TextTrackLists so tracks survive tech switches. closes #2425
This commit is contained in:
parent
7d335e0989
commit
2dfd315ac1
@ -85,6 +85,7 @@ CHANGELOG
|
||||
* @sirlancelot change "video" to "media" in error messages ([view](https://github.com/videojs/video.js/pull/2409))
|
||||
* @sirlancelot change "video" to "media" in error messages ([view](https://github.com/videojs/video.js/pull/2409))
|
||||
* @nickygerritsen use the default seekable when a source handler is unset ([view](https://github.com/videojs/video.js/pull/2401))
|
||||
* @gkatsev always use emulated TextTrackLists so tracks survive tech switches ([view](https://github.com/videojs/video.js/pull/2425))
|
||||
|
||||
--------------------
|
||||
|
||||
|
12
package.json
12
package.json
@ -5,11 +5,11 @@
|
||||
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"videojs",
|
||||
"html5",
|
||||
"flash",
|
||||
"html5",
|
||||
"player",
|
||||
"video",
|
||||
"player"
|
||||
"videojs"
|
||||
],
|
||||
"homepage": "http://videojs.com",
|
||||
"author": "Steve Heffernan",
|
||||
@ -79,7 +79,7 @@
|
||||
"karma-sauce-launcher": "^0.2.8",
|
||||
"karma-sinon": "^1.0.3",
|
||||
"load-grunt-tasks": "^3.1.0",
|
||||
"qunitjs": "~1.14.0",
|
||||
"qunitjs": "^1.18.0",
|
||||
"sinon": "~1.9.1",
|
||||
"time-grunt": "^1.1.1",
|
||||
"uglify-js": "~2.3.6",
|
||||
@ -87,13 +87,13 @@
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"**/Gruntfile.js",
|
||||
"**/build/**",
|
||||
"**/dist/**",
|
||||
"**/docs/**",
|
||||
"**/lang/**",
|
||||
"**/sandbox/**",
|
||||
"**/test/**",
|
||||
"**/Gruntfile.js"
|
||||
"**/test/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import globalOptions from './global-options.js';
|
||||
import safeParseTuple from 'safe-json-parse/tuple';
|
||||
import assign from 'object.assign';
|
||||
import mergeOptions from './utils/merge-options.js';
|
||||
import textTrackConverter from './tracks/text-track-list-converter.js';
|
||||
|
||||
// Include required child components (importing also registers them)
|
||||
import MediaLoader from './tech/loader.js';
|
||||
@ -524,6 +525,8 @@ class Player extends Component {
|
||||
let techComponent = Component.getComponent(techName);
|
||||
this.tech = new techComponent(techOptions);
|
||||
|
||||
textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech);
|
||||
|
||||
this.on(this.tech, 'ready', this.handleTechReady);
|
||||
this.on(this.tech, 'usenativecontrols', this.handleTechUseNativeControls);
|
||||
|
||||
@ -583,6 +586,7 @@ class Player extends Component {
|
||||
unloadTech() {
|
||||
// Save the current text tracks so that we can reuse the same text tracks with the next tech
|
||||
this.textTracks_ = this.textTracks();
|
||||
this.textTracksJson_ = textTrackConverter.textTracksToJson(this);
|
||||
|
||||
this.isReady_ = false;
|
||||
|
||||
|
@ -67,6 +67,11 @@ class Html5 extends Tech {
|
||||
|
||||
if (this.featuresNativeTextTracks) {
|
||||
this.on('loadstart', Fn.bind(this, this.hideCaptions));
|
||||
|
||||
this.handleTextTrackChange_ = Fn.bind(this, this.handleTextTrackChange);
|
||||
this.handleTextTrackAdd_ = Fn.bind(this, this.handleTextTrackAdd);
|
||||
this.handleTextTrackRemove_ = Fn.bind(this, this.handleTextTrackRemove);
|
||||
this.proxyNativeTextTracks_();
|
||||
}
|
||||
|
||||
// Determine if native controls should be used
|
||||
@ -86,6 +91,22 @@ class Html5 extends Tech {
|
||||
* @method dispose
|
||||
*/
|
||||
dispose() {
|
||||
let tt = this.el().textTracks;
|
||||
let emulatedTt = this.textTracks();
|
||||
|
||||
// remove native event listeners
|
||||
tt.removeEventListener('change', this.handleTextTrackChange_);
|
||||
tt.removeEventListener('addtrack', this.handleTextTrackAdd_);
|
||||
tt.removeEventListener('removetrack', this.handleTextTrackRemove_);
|
||||
|
||||
// clearout the emulated text track list.
|
||||
let i = emulatedTt.length;
|
||||
|
||||
while (i--) {
|
||||
emulatedTt.removeTrack_(emulatedTt[i]);
|
||||
}
|
||||
|
||||
|
||||
Html5.disposeMediaElement(this.el_);
|
||||
super.dispose();
|
||||
}
|
||||
@ -182,6 +203,32 @@ class Html5 extends Tech {
|
||||
}
|
||||
}
|
||||
|
||||
proxyNativeTextTracks_() {
|
||||
let tt = this.el().textTracks;
|
||||
|
||||
tt.addEventListener('change', this.handleTextTrackChange_);
|
||||
tt.addEventListener('addtrack', this.handleTextTrackAdd_);
|
||||
tt.addEventListener('removetrack', this.handleTextTrackRemove_);
|
||||
}
|
||||
|
||||
handleTextTrackChange(e) {
|
||||
let tt = this.textTracks();
|
||||
this.textTracks().trigger({
|
||||
type: 'change',
|
||||
target: tt,
|
||||
currentTarget: tt,
|
||||
srcElement: tt
|
||||
});
|
||||
}
|
||||
|
||||
handleTextTrackAdd(e) {
|
||||
this.textTracks().addTrack_(e.track);
|
||||
}
|
||||
|
||||
handleTextTrackRemove(e) {
|
||||
this.textTracks().removeTrack_(e.track);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play for html5 tech
|
||||
*
|
||||
@ -593,11 +640,7 @@ class Html5 extends Tech {
|
||||
* @method textTracks
|
||||
*/
|
||||
textTracks() {
|
||||
if (!this['featuresNativeTextTracks']) {
|
||||
return super.textTracks();
|
||||
}
|
||||
|
||||
return this.el_.textTracks;
|
||||
return super.textTracks();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -692,12 +735,12 @@ class Html5 extends Tech {
|
||||
|
||||
this.remoteTextTracks().removeTrack_(track);
|
||||
|
||||
tracks = this.el()['querySelectorAll']('track');
|
||||
tracks = this.el().querySelectorAll('track');
|
||||
|
||||
for (i = 0; i < tracks.length; i++) {
|
||||
if (tracks[i] === track || tracks[i]['track'] === track) {
|
||||
tracks[i]['parentNode']['removeChild'](tracks[i]);
|
||||
break;
|
||||
i = tracks.length;
|
||||
while (i--) {
|
||||
if (track === tracks[i] || track === tracks[i].track) {
|
||||
this.el().removeChild(tracks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -249,6 +249,14 @@ class Tech extends Component {
|
||||
* @method dispose
|
||||
*/
|
||||
dispose() {
|
||||
// clear out text tracks because we can't reuse them between techs
|
||||
let tt = this.textTracks();
|
||||
let i = tt.length;
|
||||
while(i--) {
|
||||
this.removeRemoteTextTrack(tt[i]);
|
||||
}
|
||||
|
||||
|
||||
// Turn off any manual progress or timeupdate tracking
|
||||
if (this.manualProgress) { this.manualProgressOff(); }
|
||||
|
||||
|
77
src/js/tracks/text-track-list-converter.js
Normal file
77
src/js/tracks/text-track-list-converter.js
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Utilities for capturing text track state and re-creating tracks
|
||||
* based on a capture.
|
||||
*
|
||||
* @file text-track-list-converter.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* Examine a single text track and return a JSON-compatible javascript
|
||||
* object that represents the text track's state.
|
||||
* @param track {TextTrackObject} the text track to query
|
||||
* @return {Object} a serializable javascript representation of the
|
||||
* @private
|
||||
*/
|
||||
let trackToJson_ = function(track) {
|
||||
return {
|
||||
kind: track.kind,
|
||||
label: track.label,
|
||||
language: track.language,
|
||||
id: track.id,
|
||||
inBandMetadataTrackDispatchType: track.inBandMetadataTrackDispatchType,
|
||||
mode: track.mode,
|
||||
cues: track.cues && Array.prototype.map.call(track.cues, function(cue) {
|
||||
return {
|
||||
startTime: cue.startTime,
|
||||
endTime: cue.endTime,
|
||||
text: cue.text,
|
||||
id: cue.id
|
||||
};
|
||||
}),
|
||||
src: track.src
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Examine a tech and return a JSON-compatible javascript array that
|
||||
* represents the state of all text tracks currently configured. The
|
||||
* return array is compatible with `jsonToTextTracks`.
|
||||
* @param tech {tech} the tech object to query
|
||||
* @return {Array} a serializable javascript representation of the
|
||||
* @function textTracksToJson
|
||||
*/
|
||||
let textTracksToJson = function(tech) {
|
||||
let trackEls = tech.el().querySelectorAll('track');
|
||||
|
||||
let trackObjs = Array.prototype.map.call(trackEls, (t) => t.track);
|
||||
let tracks = Array.prototype.map.call(trackEls, function(trackEl) {
|
||||
let json = trackToJson_(trackEl.track);
|
||||
json.src = trackEl.src;
|
||||
return json;
|
||||
});
|
||||
|
||||
return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function(track) {
|
||||
return trackObjs.indexOf(track) === -1;
|
||||
}).map(trackToJson_));
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a set of remote text tracks on a tech based on an array of
|
||||
* javascript text track representations.
|
||||
* @param json {Array} an array of text track representation objects,
|
||||
* like those that would be produced by `textTracksToJson`
|
||||
* @param tech {tech} the tech to create text tracks on
|
||||
* @function jsonToTextTracks
|
||||
*/
|
||||
let jsonToTextTracks = function(json, tech) {
|
||||
json.forEach(function(track) {
|
||||
let addedTrack = tech.addRemoteTextTrack(track).track;
|
||||
if (!track.src && track.cues) {
|
||||
track.cues.forEach((cue) => addedTrack.addCue(cue));
|
||||
}
|
||||
});
|
||||
|
||||
return tech.textTracks();
|
||||
};
|
||||
|
||||
export default {textTracksToJson, jsonToTextTracks, trackToJson_};
|
@ -176,6 +176,7 @@ let TextTrack = function(options={}) {
|
||||
});
|
||||
|
||||
if (options.src) {
|
||||
tt.src = options.src;
|
||||
loadTrack(options.src, tt);
|
||||
} else {
|
||||
tt.loaded_ = true;
|
||||
|
@ -93,6 +93,7 @@ test('dispose removes the object element even before ready fires', function() {
|
||||
mockFlash.off = noop;
|
||||
mockFlash.trigger = noop;
|
||||
mockFlash.el_ = {};
|
||||
mockFlash.textTracks = () => ([]);
|
||||
|
||||
dispose.call(mockFlash);
|
||||
strictEqual(mockFlash.el_, null, 'swf el is nulled');
|
||||
|
@ -183,3 +183,48 @@ test('native source handler canHandleSource', function(){
|
||||
// Reset test video canPlayType
|
||||
Html5.TEST_VID.canPlayType = origCPT;
|
||||
});
|
||||
|
||||
if (Html5.supportsNativeTextTracks()) {
|
||||
test('add native textTrack listeners on startup', function() {
|
||||
let adds = [];
|
||||
let rems = [];
|
||||
let tt = {
|
||||
length: 0,
|
||||
addEventListener: (type, fn) => adds.push([type, fn]),
|
||||
removeEventListener: (type, fn) => rems.push([type, fn]),
|
||||
};
|
||||
let el = document.createElement('div');
|
||||
el.textTracks = tt;
|
||||
|
||||
let htmlTech = new Html5({el});
|
||||
|
||||
equal(adds[0][0], 'change', 'change event handler added');
|
||||
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
|
||||
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
|
||||
});
|
||||
|
||||
test('remove all tracks from emulated list on dispose', function() {
|
||||
let adds = [];
|
||||
let rems = [];
|
||||
let tt = {
|
||||
length: 0,
|
||||
addEventListener: (type, fn) => adds.push([type, fn]),
|
||||
removeEventListener: (type, fn) => rems.push([type, fn]),
|
||||
};
|
||||
let el = document.createElement('div');
|
||||
el.textTracks = tt;
|
||||
|
||||
let htmlTech = new Html5({el});
|
||||
htmlTech.dispose();
|
||||
|
||||
equal(adds[0][0], 'change', 'change event handler added');
|
||||
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
|
||||
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
|
||||
equal(rems[0][0], 'change', 'change event handler removed');
|
||||
equal(rems[1][0], 'addtrack', 'addtrack event handler removed');
|
||||
equal(rems[2][0], 'removetrack', 'removetrack event handler removed');
|
||||
equal(adds[0][0], rems[0][0], 'change event handler removed');
|
||||
equal(adds[1][0], rems[1][0], 'addtrack event handler removed');
|
||||
equal(adds[2][0], rems[2][0], 'removetrack event handler removed');
|
||||
});
|
||||
}
|
||||
|
250
test/unit/tracks/text-track-list-converter.test.js
Normal file
250
test/unit/tracks/text-track-list-converter.test.js
Normal file
@ -0,0 +1,250 @@
|
||||
import c from '../../../src/js/tracks/text-track-list-converter.js';
|
||||
import TextTrack from '../../../src/js/tracks/text-track.js';
|
||||
import TextTrackList from '../../../src/js/tracks/text-track-list.js';
|
||||
import Html5 from '../../../src/js/tech/html5.js';
|
||||
import document from 'global/document';
|
||||
|
||||
q.module('Text Track List Converter');
|
||||
|
||||
let clean = (item) => {
|
||||
delete item.id;
|
||||
delete item.inBandMetadataTrackDispatchType;
|
||||
};
|
||||
|
||||
let cleanup = (item) => {
|
||||
if (Array.isArray(item)) {
|
||||
item.forEach(clean);
|
||||
} else {
|
||||
clean(item);
|
||||
}
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
if (Html5.supportsNativeTextTracks()) {
|
||||
q.test('trackToJson_ produces correct representation for native track object', function(a) {
|
||||
let track = document.createElement('track');
|
||||
track.src = 'http://example.com/english.vtt';
|
||||
track.kind = 'captions';
|
||||
track.srclang = 'en';
|
||||
track.label = 'English';
|
||||
|
||||
a.deepEqual(cleanup(c.trackToJson_(track.track)), {
|
||||
src: undefined,
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
mode: 'disabled',
|
||||
cues: null
|
||||
}, 'the json output is same');
|
||||
});
|
||||
|
||||
q.test('textTracksToJson produces good json output', function(a) {
|
||||
let emulatedTrack = new TextTrack({
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'http://example.com/english.vtt',
|
||||
tech: {}
|
||||
});
|
||||
|
||||
let nativeTrack = document.createElement('track');
|
||||
nativeTrack.src = 'http://example.com/spanish.vtt';
|
||||
nativeTrack.kind = 'captions';
|
||||
nativeTrack.srclang = 'es';
|
||||
nativeTrack.label = 'Spanish';
|
||||
|
||||
let tt = new TextTrackList();
|
||||
tt.addTrack_(nativeTrack.track);
|
||||
tt.addTrack_(emulatedTrack);
|
||||
|
||||
let tech = {
|
||||
el() {
|
||||
return {
|
||||
querySelectorAll() {
|
||||
return [nativeTrack];
|
||||
}
|
||||
};
|
||||
},
|
||||
textTracks() {
|
||||
return tt;
|
||||
}
|
||||
};
|
||||
|
||||
a.deepEqual(cleanup(c.textTracksToJson(tech)), [{
|
||||
src: 'http://example.com/spanish.vtt',
|
||||
kind: 'captions',
|
||||
label: 'Spanish',
|
||||
language: 'es',
|
||||
mode: 'disabled',
|
||||
cues: null
|
||||
}, {
|
||||
src: 'http://example.com/english.vtt',
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
mode: 'disabled',
|
||||
cues: null
|
||||
}], 'the output is correct');
|
||||
});
|
||||
|
||||
q.test('jsonToTextTracks calls addRemoteTextTrack on the tech with mixed tracks', function(a) {
|
||||
let emulatedTrack = new TextTrack({
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'http://example.com/english.vtt',
|
||||
tech: {}
|
||||
});
|
||||
|
||||
let nativeTrack = document.createElement('track');
|
||||
nativeTrack.src = 'http://example.com/spanish.vtt';
|
||||
nativeTrack.kind = 'captions';
|
||||
nativeTrack.srclang = 'es';
|
||||
nativeTrack.label = 'Spanish';
|
||||
|
||||
let tt = new TextTrackList();
|
||||
tt.addTrack_(nativeTrack.track);
|
||||
tt.addTrack_(emulatedTrack);
|
||||
|
||||
let addRemotes = 0;
|
||||
let tech = {
|
||||
el() {
|
||||
return {
|
||||
querySelectorAll() {
|
||||
return [nativeTrack];
|
||||
}
|
||||
};
|
||||
},
|
||||
textTracks() {
|
||||
return tt;
|
||||
},
|
||||
addRemoteTextTrack() {
|
||||
addRemotes++;
|
||||
return {
|
||||
track: {}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
c.jsonToTextTracks(cleanup(c.textTracksToJson(tech)), tech);
|
||||
|
||||
a.equal(addRemotes, 2, 'we added two text tracks');
|
||||
});
|
||||
}
|
||||
|
||||
q.test('trackToJson_ produces correct representation for emulated track object', function(a) {
|
||||
let track = new TextTrack({
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'http://example.com/english.vtt',
|
||||
tech: {}
|
||||
});
|
||||
|
||||
a.deepEqual(cleanup(c.trackToJson_(track)), {
|
||||
src: 'http://example.com/english.vtt',
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
mode: 'disabled',
|
||||
cues: null
|
||||
}, 'the json output is same');
|
||||
});
|
||||
|
||||
q.test('textTracksToJson produces good json output for emulated only', function(a) {
|
||||
let emulatedTrack = new TextTrack({
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'http://example.com/english.vtt',
|
||||
tech: {}
|
||||
});
|
||||
|
||||
let anotherTrack = new TextTrack({
|
||||
src: 'http://example.com/spanish.vtt',
|
||||
kind: 'captions',
|
||||
srclang: 'es',
|
||||
label: 'Spanish',
|
||||
tech: {}
|
||||
});
|
||||
|
||||
let tt = new TextTrackList();
|
||||
tt.addTrack_(anotherTrack);
|
||||
tt.addTrack_(emulatedTrack);
|
||||
|
||||
let tech = {
|
||||
el() {
|
||||
return {
|
||||
querySelectorAll() {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
},
|
||||
textTracks() {
|
||||
return tt;
|
||||
}
|
||||
};
|
||||
|
||||
a.deepEqual(cleanup(c.textTracksToJson(tech)), [{
|
||||
src: 'http://example.com/spanish.vtt',
|
||||
kind: 'captions',
|
||||
label: 'Spanish',
|
||||
language: 'es',
|
||||
mode: 'disabled',
|
||||
cues: null
|
||||
}, {
|
||||
src: 'http://example.com/english.vtt',
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
mode: 'disabled',
|
||||
cues: null
|
||||
}], 'the output is correct');
|
||||
});
|
||||
|
||||
q.test('jsonToTextTracks calls addRemoteTextTrack on the tech with emulated tracks only', function(a) {
|
||||
let emulatedTrack = new TextTrack({
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'http://example.com/english.vtt',
|
||||
tech: {}
|
||||
});
|
||||
|
||||
let anotherTrack = new TextTrack({
|
||||
src: 'http://example.com/spanish.vtt',
|
||||
kind: 'captions',
|
||||
srclang: 'es',
|
||||
label: 'Spanish',
|
||||
tech: {}
|
||||
});
|
||||
|
||||
let tt = new TextTrackList();
|
||||
tt.addTrack_(anotherTrack);
|
||||
tt.addTrack_(emulatedTrack);
|
||||
|
||||
let addRemotes = 0;
|
||||
let tech = {
|
||||
el() {
|
||||
return {
|
||||
querySelectorAll() {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
},
|
||||
textTracks() {
|
||||
return tt;
|
||||
},
|
||||
addRemoteTextTrack() {
|
||||
addRemotes++;
|
||||
return {
|
||||
track: {}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
c.jsonToTextTracks(cleanup(c.textTracksToJson(tech)), tech);
|
||||
|
||||
a.equal(addRemotes, 2, 'we added two text tracks');
|
||||
});
|
@ -307,3 +307,56 @@ test('when switching techs, we should not get a new text track', function() {
|
||||
|
||||
ok(htmltracks === flashtracks, 'the tracks are equal');
|
||||
});
|
||||
|
||||
if (Html5.supportsNativeTextTracks()) {
|
||||
test('listen to remove and add track events in native text tracks', function(assert) {
|
||||
let done = assert.async();
|
||||
|
||||
let el = document.createElement('video');
|
||||
let html = new Html5({el});
|
||||
let tt = el.textTracks;
|
||||
let emulatedTt = html.textTracks();
|
||||
let track = document.createElement('track');
|
||||
el.appendChild(track);
|
||||
|
||||
let addtrack = function() {
|
||||
equal(emulatedTt.length, tt.length, 'we have matching tracks length');
|
||||
equal(emulatedTt.length, 1, 'we have one text track');
|
||||
|
||||
emulatedTt.off('addtrack', addtrack);
|
||||
el.removeChild(track);
|
||||
};
|
||||
emulatedTt.on('addtrack', addtrack);
|
||||
emulatedTt.on('removetrack', function() {
|
||||
equal(emulatedTt.length, tt.length, 'we have matching tracks length');
|
||||
equal(emulatedTt.length, 0, 'we have no more text tracks');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should have removed tracks on dispose', function(assert) {
|
||||
let done = assert.async();
|
||||
|
||||
let el = document.createElement('video');
|
||||
let html = new Html5({el});
|
||||
let tt = el.textTracks;
|
||||
let emulatedTt = html.textTracks();
|
||||
let track = document.createElement('track');
|
||||
el.appendChild(track);
|
||||
|
||||
let addtrack = function() {
|
||||
equal(emulatedTt.length, tt.length, 'we have matching tracks length');
|
||||
equal(emulatedTt.length, 1, 'we have one text track');
|
||||
|
||||
emulatedTt.off('addtrack', addtrack);
|
||||
html.dispose();
|
||||
|
||||
equal(emulatedTt.length, tt.length, 'we have matching tracks length');
|
||||
equal(emulatedTt.length, 0, 'we have no more text tracks');
|
||||
|
||||
done();
|
||||
};
|
||||
emulatedTt.on('addtrack', addtrack);
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ import document from 'global/document';
|
||||
import * as Dom from '../../../src/js/utils/dom.js';
|
||||
import TestHelpers from '../test-helpers.js';
|
||||
|
||||
q.module('dom');
|
||||
|
||||
test('should return the element with the ID', function(){
|
||||
var el1 = document.createElement('div');
|
||||
var el2 = document.createElement('div');
|
||||
|
@ -1,5 +1,7 @@
|
||||
import * as Fn from '../../../src/js/utils/fn.js';
|
||||
|
||||
q.module('fn');
|
||||
|
||||
test('should add context to a function', function(){
|
||||
var newContext = { test: 'obj'};
|
||||
var asdf = function(){
|
||||
|
@ -1,5 +1,7 @@
|
||||
import formatTime from '../../../src/js/utils/format-time.js';
|
||||
|
||||
q.module('format-time');
|
||||
|
||||
test('should format time as a string', function(){
|
||||
ok(formatTime(1) === '0:01');
|
||||
ok(formatTime(10) === '0:10');
|
||||
|
@ -1,6 +1,8 @@
|
||||
import log from '../../../src/js/utils/log.js';
|
||||
import window from 'global/window';
|
||||
|
||||
q.module('log');
|
||||
|
||||
test('should confirm logging functions work', function() {
|
||||
let origConsole = window['console'];
|
||||
// replace the native console for testing
|
||||
|
@ -1,5 +1,7 @@
|
||||
import mergeOptions from '../../../src/js/utils/merge-options.js';
|
||||
|
||||
q.module('merge-options');
|
||||
|
||||
test('should merge options objects', function(){
|
||||
var ob1, ob2, ob3;
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
|
||||
|
||||
q.module('time-ranges');
|
||||
|
||||
test('should create a fake timerange', function(){
|
||||
var tr = createTimeRange(0, 10);
|
||||
ok(tr.start() === 0);
|
||||
|
@ -1,5 +1,7 @@
|
||||
import toTitleCase from '../../../src/js/utils/to-title-case.js';
|
||||
|
||||
q.module('to-title-case');
|
||||
|
||||
test('should make a string start with an uppercase letter', function(){
|
||||
var foo = toTitleCase('bar');
|
||||
ok(foo === 'Bar');
|
||||
|
@ -2,6 +2,8 @@ import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
import * as Url from '../../../src/js/utils/url.js';
|
||||
|
||||
q.module('url');
|
||||
|
||||
test('should parse the details of a url correctly', function(){
|
||||
equal(Url.parseUrl('#').protocol, window.location.protocol, 'parsed relative url protocol');
|
||||
equal(Url.parseUrl('#').host, window.location.host, 'parsed relative url host');
|
||||
@ -30,9 +32,11 @@ test('should strip port from hosts using http or https', function() {
|
||||
};
|
||||
|
||||
url = Url.parseUrl('/domain/relative/url');
|
||||
ok(!(/.*:80$/).test(url.host), ':80 is not appended to the host');
|
||||
|
||||
document.createElement = origDocCreate;
|
||||
|
||||
ok(!(/.*:80$/).test(url.host), ':80 is not appended to the host');
|
||||
|
||||
});
|
||||
|
||||
test('should get an absolute URL', function(){
|
||||
|
Loading…
Reference in New Issue
Block a user