mirror of
https://github.com/videojs/video.js.git
synced 2024-12-25 02:42:10 +02:00
@BrandonOCasey added audio and video track support. closes #3173
This commit is contained in:
parent
18cdf08c0e
commit
2e2dbde4b4
@ -6,6 +6,7 @@ CHANGELOG
|
|||||||
* @gkatsev Use fonts 2.0 that do not require wrapping codepoints ([view](https://github.com/videojs/video.js/pull/3252))
|
* @gkatsev Use fonts 2.0 that do not require wrapping codepoints ([view](https://github.com/videojs/video.js/pull/3252))
|
||||||
* @chrisauclair Make controls visible for accessibility reasons ([view](https://github.com/videojs/video.js/pull/3237))
|
* @chrisauclair Make controls visible for accessibility reasons ([view](https://github.com/videojs/video.js/pull/3237))
|
||||||
* @gkatsev updated text track documentation and crossorigin warning. Fixes #1888, #1958, #2628, #3202 ([view](https://github.com/videojs/video.js/pull/3256))
|
* @gkatsev updated text track documentation and crossorigin warning. Fixes #1888, #1958, #2628, #3202 ([view](https://github.com/videojs/video.js/pull/3256))
|
||||||
|
* @BrandonOCasey added audio and video track support ([view](https://github.com/videojs/video.js/pull/3173))
|
||||||
|
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ import safeParseTuple from 'safe-json-parse/tuple';
|
|||||||
import assign from 'object.assign';
|
import assign from 'object.assign';
|
||||||
import mergeOptions from './utils/merge-options.js';
|
import mergeOptions from './utils/merge-options.js';
|
||||||
import textTrackConverter from './tracks/text-track-list-converter.js';
|
import textTrackConverter from './tracks/text-track-list-converter.js';
|
||||||
|
import AudioTrackList from './tracks/audio-track-list.js';
|
||||||
|
import VideoTrackList from './tracks/video-track-list.js';
|
||||||
|
|
||||||
// Include required child components (importing also registers them)
|
// Include required child components (importing also registers them)
|
||||||
import MediaLoader from './tech/loader.js';
|
import MediaLoader from './tech/loader.js';
|
||||||
@ -555,7 +557,9 @@ class Player extends Component {
|
|||||||
'source': source,
|
'source': source,
|
||||||
'playerId': this.id(),
|
'playerId': this.id(),
|
||||||
'techId': `${this.id()}_${techName}_api`,
|
'techId': `${this.id()}_${techName}_api`,
|
||||||
|
'videoTracks': this.videoTracks_,
|
||||||
'textTracks': this.textTracks_,
|
'textTracks': this.textTracks_,
|
||||||
|
'audioTracks': this.audioTracks_,
|
||||||
'autoplay': this.options_.autoplay,
|
'autoplay': this.options_.autoplay,
|
||||||
'preload': this.options_.preload,
|
'preload': this.options_.preload,
|
||||||
'loop': this.options_.loop,
|
'loop': this.options_.loop,
|
||||||
@ -648,7 +652,9 @@ class Player extends Component {
|
|||||||
*/
|
*/
|
||||||
unloadTech_() {
|
unloadTech_() {
|
||||||
// Save the current text tracks so that we can reuse the same text tracks with the next tech
|
// Save the current text tracks so that we can reuse the same text tracks with the next tech
|
||||||
|
this.videoTracks_ = this.videoTracks();
|
||||||
this.textTracks_ = this.textTracks();
|
this.textTracks_ = this.textTracks();
|
||||||
|
this.audioTracks_ = this.audioTracks();
|
||||||
this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
|
this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
|
||||||
|
|
||||||
this.isReady_ = false;
|
this.isReady_ = false;
|
||||||
@ -2509,12 +2515,48 @@ class Player extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Text tracks are tracks of timed text events.
|
* Get a video track list
|
||||||
* Captions - text displayed over the video for the hearing impaired
|
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
|
||||||
* Subtitles - text displayed over the video for those who don't understand language in the video
|
*
|
||||||
* Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
|
* @return {VideoTrackList} thes current video track list
|
||||||
* Descriptions - audio descriptions that are read back to the user by a screen reading device
|
* @method videoTracks
|
||||||
*/
|
*/
|
||||||
|
videoTracks() {
|
||||||
|
// if we have not yet loadTech_, we create videoTracks_
|
||||||
|
// these will be passed to the tech during loading
|
||||||
|
if (!this.tech_) {
|
||||||
|
this.videoTracks_ = this.videoTracks_ || new VideoTrackList();
|
||||||
|
return this.videoTracks_;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tech_.videoTracks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an audio track list
|
||||||
|
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
|
||||||
|
*
|
||||||
|
* @return {AudioTrackList} thes current audio track list
|
||||||
|
* @method audioTracks
|
||||||
|
*/
|
||||||
|
audioTracks() {
|
||||||
|
// if we have not yet loadTech_, we create videoTracks_
|
||||||
|
// these will be passed to the tech during loading
|
||||||
|
if (!this.tech_) {
|
||||||
|
this.audioTracks_ = this.audioTracks_ || new AudioTrackList();
|
||||||
|
return this.audioTracks_;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tech_.audioTracks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Text tracks are tracks of timed text events.
|
||||||
|
* Captions - text displayed over the video for the hearing impaired
|
||||||
|
* Subtitles - text displayed over the video for those who don't understand language in the video
|
||||||
|
* Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
|
||||||
|
* Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an array of associated text tracks. captions, subtitles, chapters, descriptions
|
* Get an array of associated text tracks. captions, subtitles, chapters, descriptions
|
||||||
@ -2609,8 +2651,6 @@ class Player extends Component {
|
|||||||
// initialTime: function(){ return this.techCall_('initialTime'); },
|
// initialTime: function(){ return this.techCall_('initialTime'); },
|
||||||
// startOffsetTime: function(){ return this.techCall_('startOffsetTime'); },
|
// startOffsetTime: function(){ return this.techCall_('startOffsetTime'); },
|
||||||
// played: function(){ return this.techCall_('played'); },
|
// played: function(){ return this.techCall_('played'); },
|
||||||
// videoTracks: function(){ return this.techCall_('videoTracks'); },
|
|
||||||
// audioTracks: function(){ return this.techCall_('audioTracks'); },
|
|
||||||
// defaultPlaybackRate: function(){ return this.techCall_('defaultPlaybackRate'); },
|
// defaultPlaybackRate: function(){ return this.techCall_('defaultPlaybackRate'); },
|
||||||
// defaultMuted: function(){ return this.techCall_('defaultMuted'); }
|
// defaultMuted: function(){ return this.techCall_('defaultMuted'); }
|
||||||
|
|
||||||
|
@ -312,7 +312,7 @@ class Flash extends Tech {
|
|||||||
// Create setters and getters for attributes
|
// Create setters and getters for attributes
|
||||||
const _api = Flash.prototype;
|
const _api = Flash.prototype;
|
||||||
const _readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(',');
|
const _readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(',');
|
||||||
const _readOnly = 'networkState,readyState,initialTime,duration,startOffsetTime,paused,ended,videoTracks,audioTracks,videoWidth,videoHeight'.split(',');
|
const _readOnly = 'networkState,readyState,initialTime,duration,startOffsetTime,paused,ended,videoWidth,videoHeight'.split(',');
|
||||||
|
|
||||||
function _createSetter(attr){
|
function _createSetter(attr){
|
||||||
var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
|
var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
|
||||||
|
@ -16,6 +16,7 @@ import document from 'global/document';
|
|||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
import assign from 'object.assign';
|
import assign from 'object.assign';
|
||||||
import mergeOptions from '../utils/merge-options.js';
|
import mergeOptions from '../utils/merge-options.js';
|
||||||
|
import toTitleCase from '../utils/to-title-case.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTML5 Media Controller - Wrapper for HTML5 Media API
|
* HTML5 Media Controller - Wrapper for HTML5 Media API
|
||||||
@ -78,6 +79,24 @@ class Html5 extends Tech {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let trackTypes = ['audio', 'video'];
|
||||||
|
|
||||||
|
// ProxyNativeTextTracks
|
||||||
|
trackTypes.forEach((type) => {
|
||||||
|
let capitalType = toTitleCase(type);
|
||||||
|
|
||||||
|
if (!this[`featuresNative${capitalType}Tracks`]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let tl = this.el()[`${type}Tracks`];
|
||||||
|
|
||||||
|
if (tl && tl.addEventListener) {
|
||||||
|
tl.addEventListener('change', Fn.bind(this, this[`handle${capitalType}TrackChange_`]));
|
||||||
|
tl.addEventListener('addtrack', Fn.bind(this, this[`handle${capitalType}TrackAdd_`]));
|
||||||
|
tl.addEventListener('removetrack', Fn.bind(this, this[`handle${capitalType}TrackRemove_`]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (this.featuresNativeTextTracks) {
|
if (this.featuresNativeTextTracks) {
|
||||||
if (crossoriginTracks) {
|
if (crossoriginTracks) {
|
||||||
log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.
|
log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.
|
||||||
@ -109,25 +128,20 @@ class Html5 extends Tech {
|
|||||||
* @method dispose
|
* @method dispose
|
||||||
*/
|
*/
|
||||||
dispose() {
|
dispose() {
|
||||||
let tt = this.el().textTracks;
|
// Un-ProxyNativeTracks
|
||||||
let emulatedTt = this.textTracks();
|
['audio', 'video', 'text'].forEach((type) => {
|
||||||
|
let capitalType = toTitleCase(type);
|
||||||
// remove native event listeners
|
let tl = this.el_[`${type}Tracks`];
|
||||||
if (tt && tt.removeEventListener) {
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (tl && tl.removeEventListener) {
|
||||||
|
tl.removeEventListener('change', this[`handle${capitalType}TrackChange_`]);
|
||||||
|
tl.removeEventListener('addtrack', this[`handle${capitalType}TrackAdd_`]);
|
||||||
|
tl.removeEventListener('removetrack', this[`handle${capitalType}TrackRemove_`]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Html5.disposeMediaElement(this.el_);
|
Html5.disposeMediaElement(this.el_);
|
||||||
|
// tech will handle clearing of the emulated track list
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,6 +317,43 @@ class Html5 extends Tech {
|
|||||||
this.textTracks().removeTrack_(e.track);
|
this.textTracks().removeTrack_(e.track);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleVideoTrackChange_(e) {
|
||||||
|
let vt = this.videoTracks();
|
||||||
|
this.videoTracks().trigger({
|
||||||
|
type: 'change',
|
||||||
|
target: vt,
|
||||||
|
currentTarget: vt,
|
||||||
|
srcElement: vt
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleVideoTrackAdd_(e) {
|
||||||
|
this.videoTracks().addTrack_(e.track);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleVideoTrackRemove_(e) {
|
||||||
|
this.videoTracks().removeTrack_(e.track);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAudioTrackChange_(e) {
|
||||||
|
let audioTrackList = this.audioTracks();
|
||||||
|
this.audioTracks().trigger({
|
||||||
|
type: 'change',
|
||||||
|
target: audioTrackList,
|
||||||
|
currentTarget: audioTrackList,
|
||||||
|
srcElement: audioTrackList
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAudioTrackAdd_(e) {
|
||||||
|
this.audioTracks().addTrack_(e.track);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAudioTrackRemove_(e) {
|
||||||
|
this.audioTracks().removeTrack_(e.track);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play for html5 tech
|
* Play for html5 tech
|
||||||
*
|
*
|
||||||
@ -989,6 +1040,27 @@ Html5.supportsNativeTextTracks = function() {
|
|||||||
return supportsTextTracks;
|
return supportsTextTracks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check to see if native video tracks are supported by this browser/device
|
||||||
|
*
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
Html5.supportsNativeVideoTracks = function() {
|
||||||
|
let supportsVideoTracks = !!Html5.TEST_VID.videoTracks;
|
||||||
|
return supportsVideoTracks;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check to see if native audio tracks are supported by this browser/device
|
||||||
|
*
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
Html5.supportsNativeAudioTracks = function() {
|
||||||
|
let supportsAudioTracks = !!Html5.TEST_VID.audioTracks;
|
||||||
|
return supportsAudioTracks;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of events available on the Html5 tech.
|
* An array of events available on the Html5 tech.
|
||||||
*
|
*
|
||||||
@ -1062,6 +1134,21 @@ Html5.prototype['featuresProgressEvents'] = true;
|
|||||||
*/
|
*/
|
||||||
Html5.prototype['featuresNativeTextTracks'] = Html5.supportsNativeTextTracks();
|
Html5.prototype['featuresNativeTextTracks'] = Html5.supportsNativeTextTracks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the tech's status on native text track support
|
||||||
|
*
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
Html5.prototype['featuresNativeVideoTracks'] = Html5.supportsNativeVideoTracks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the tech's status on native audio track support
|
||||||
|
*
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
Html5.prototype['featuresNativeAudioTracks'] = Html5.supportsNativeAudioTracks();
|
||||||
|
|
||||||
|
|
||||||
// HTML5 Feature detection and Device Fixes --------------------------------- //
|
// HTML5 Feature detection and Device Fixes --------------------------------- //
|
||||||
let canPlayType;
|
let canPlayType;
|
||||||
const mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
|
const mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
|
||||||
|
@ -10,6 +10,10 @@ import HTMLTrackElementList from '../tracks/html-track-element-list';
|
|||||||
import mergeOptions from '../utils/merge-options.js';
|
import mergeOptions from '../utils/merge-options.js';
|
||||||
import TextTrack from '../tracks/text-track';
|
import TextTrack from '../tracks/text-track';
|
||||||
import TextTrackList from '../tracks/text-track-list';
|
import TextTrackList from '../tracks/text-track-list';
|
||||||
|
import VideoTrack from '../tracks/video-track';
|
||||||
|
import VideoTrackList from '../tracks/video-track-list';
|
||||||
|
import AudioTrackList from '../tracks/audio-track-list';
|
||||||
|
import AudioTrack from '../tracks/audio-track';
|
||||||
import * as Fn from '../utils/fn.js';
|
import * as Fn from '../utils/fn.js';
|
||||||
import log from '../utils/log.js';
|
import log from '../utils/log.js';
|
||||||
import { createTimeRange } from '../utils/time-ranges.js';
|
import { createTimeRange } from '../utils/time-ranges.js';
|
||||||
@ -45,6 +49,8 @@ class Tech extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.textTracks_ = options.textTracks;
|
this.textTracks_ = options.textTracks;
|
||||||
|
this.videoTracks_ = options.videoTracks;
|
||||||
|
this.audioTracks_ = options.audioTracks;
|
||||||
|
|
||||||
// Manually track progress in cases where the browser/flash player doesn't report it.
|
// Manually track progress in cases where the browser/flash player doesn't report it.
|
||||||
if (!this.featuresProgressEvents) {
|
if (!this.featuresProgressEvents) {
|
||||||
@ -65,6 +71,7 @@ class Tech extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.initTextTrackListeners();
|
this.initTextTrackListeners();
|
||||||
|
this.initTrackListeners();
|
||||||
|
|
||||||
// Turn on component tap events
|
// Turn on component tap events
|
||||||
this.emitTapEvents();
|
this.emitTapEvents();
|
||||||
@ -218,15 +225,9 @@ class Tech extends Component {
|
|||||||
* @method dispose
|
* @method dispose
|
||||||
*/
|
*/
|
||||||
dispose() {
|
dispose() {
|
||||||
// clear out text tracks because we can't reuse them between techs
|
|
||||||
let textTracks = this.textTracks();
|
|
||||||
|
|
||||||
if (textTracks) {
|
// clear out all tracks because we can't reuse them between techs
|
||||||
let i = textTracks.length;
|
this.clearTracks(['audio', 'video', 'text']);
|
||||||
while(i--) {
|
|
||||||
this.removeRemoteTextTrack(textTracks[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn off any manual progress or timeupdate tracking
|
// Turn off any manual progress or timeupdate tracking
|
||||||
if (this.manualProgress) { this.manualProgressOff(); }
|
if (this.manualProgress) { this.manualProgressOff(); }
|
||||||
@ -236,6 +237,33 @@ class Tech extends Component {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear out a track list, or multiple track lists
|
||||||
|
*
|
||||||
|
* Note: Techs without source handlers should call this between
|
||||||
|
* sources for video & audio tracks, as usually you don't want
|
||||||
|
* to use them between tracks and we have no automatic way to do
|
||||||
|
* it for you
|
||||||
|
*
|
||||||
|
* @method clearTracks
|
||||||
|
* @param {Array|String} types type(s) of track lists to empty
|
||||||
|
*/
|
||||||
|
clearTracks(types) {
|
||||||
|
types = [].concat(types);
|
||||||
|
// clear out all tracks because we can't reuse them between techs
|
||||||
|
types.forEach((type) => {
|
||||||
|
let list = this[`${type}Tracks`]() || [];
|
||||||
|
let i = list.length;
|
||||||
|
while (i--) {
|
||||||
|
let track = list[i];
|
||||||
|
if (type === 'text') {
|
||||||
|
this.removeRemoteTextTrack(track);
|
||||||
|
}
|
||||||
|
list.removeTrack_(track);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the tech. Removes all sources and resets readyState.
|
* Reset the tech. Removes all sources and resets readyState.
|
||||||
*
|
*
|
||||||
@ -313,6 +341,32 @@ class Tech extends Component {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize audio and video track listeners
|
||||||
|
*
|
||||||
|
* @method initTrackListeners
|
||||||
|
*/
|
||||||
|
initTrackListeners() {
|
||||||
|
const trackTypes = ['video', 'audio'];
|
||||||
|
|
||||||
|
trackTypes.forEach((type) => {
|
||||||
|
let trackListChanges = () => {
|
||||||
|
this.trigger(`${type}trackchange`);
|
||||||
|
};
|
||||||
|
|
||||||
|
let tracks = this[`${type}Tracks`]();
|
||||||
|
|
||||||
|
tracks.addEventListener('removetrack', trackListChanges);
|
||||||
|
tracks.addEventListener('addtrack', trackListChanges);
|
||||||
|
|
||||||
|
this.on('dispose', () => {
|
||||||
|
tracks.removeEventListener('removetrack', trackListChanges);
|
||||||
|
tracks.removeEventListener('addtrack', trackListChanges);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emulate texttracks
|
* Emulate texttracks
|
||||||
*
|
*
|
||||||
@ -337,8 +391,10 @@ class Tech extends Component {
|
|||||||
script.onload = null;
|
script.onload = null;
|
||||||
script.onerror = null;
|
script.onerror = null;
|
||||||
});
|
});
|
||||||
this.el().parentNode.appendChild(script);
|
// but have not loaded yet and we set it to true before the inject so that
|
||||||
|
// we don't overwrite the injected window.WebVTT if it loads right away
|
||||||
window['WebVTT'] = true;
|
window['WebVTT'] = true;
|
||||||
|
this.el().parentNode.appendChild(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
let updateDisplay = () => this.trigger('texttrackchange');
|
let updateDisplay = () => this.trigger('texttrackchange');
|
||||||
@ -362,6 +418,28 @@ class Tech extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get videotracks
|
||||||
|
*
|
||||||
|
* @returns {VideoTrackList}
|
||||||
|
* @method videoTracks
|
||||||
|
*/
|
||||||
|
videoTracks() {
|
||||||
|
this.videoTracks_ = this.videoTracks_ || new VideoTrackList();
|
||||||
|
return this.videoTracks_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get audiotracklist
|
||||||
|
*
|
||||||
|
* @returns {AudioTrackList}
|
||||||
|
* @method audioTracks
|
||||||
|
*/
|
||||||
|
audioTracks() {
|
||||||
|
this.audioTracks_ = this.audioTracks_ || new AudioTrackList();
|
||||||
|
return this.audioTracks_;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Provide default methods for text tracks.
|
* Provide default methods for text tracks.
|
||||||
*
|
*
|
||||||
@ -536,14 +614,31 @@ class Tech extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* List of associated text tracks
|
* List of associated text tracks
|
||||||
*
|
*
|
||||||
* @type {Array}
|
* @type {TextTrackList}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Tech.prototype.textTracks_;
|
Tech.prototype.textTracks_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of associated audio tracks
|
||||||
|
*
|
||||||
|
* @type {AudioTrackList}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Tech.prototype.audioTracks_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of associated video tracks
|
||||||
|
*
|
||||||
|
* @type {VideoTrackList}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Tech.prototype.videoTracks_;
|
||||||
|
|
||||||
|
|
||||||
var createTrackHelper = function(self, kind, label, language, options={}) {
|
var createTrackHelper = function(self, kind, label, language, options={}) {
|
||||||
let tracks = self.textTracks();
|
let tracks = self.textTracks();
|
||||||
|
|
||||||
@ -713,6 +808,12 @@ Tech.withSourceHandlers = function(_Tech){
|
|||||||
this.disposeSourceHandler();
|
this.disposeSourceHandler();
|
||||||
this.off('dispose', this.disposeSourceHandler);
|
this.off('dispose', this.disposeSourceHandler);
|
||||||
|
|
||||||
|
// if we have a source and get another one
|
||||||
|
// then we are loading something new
|
||||||
|
// than clear all of our current tracks
|
||||||
|
if (this.currentSource_) {
|
||||||
|
this.clearTracks(['audio', 'video']);
|
||||||
|
}
|
||||||
this.currentSource_ = source;
|
this.currentSource_ = source;
|
||||||
this.sourceHandler_ = sh.handleSource(source, this, this.options_);
|
this.sourceHandler_ = sh.handleSource(source, this, this.options_);
|
||||||
this.on('dispose', this.disposeSourceHandler);
|
this.on('dispose', this.disposeSourceHandler);
|
||||||
|
113
src/js/tracks/audio-track-list.js
Normal file
113
src/js/tracks/audio-track-list.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* @file audio-track-list.js
|
||||||
|
*/
|
||||||
|
import TrackList from './track-list';
|
||||||
|
import * as browser from '../utils/browser.js';
|
||||||
|
import document from 'global/document';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* anywhere we call this function we diverge from the spec
|
||||||
|
* as we only support one enabled audiotrack at a time
|
||||||
|
*
|
||||||
|
* @param {Array|AudioTrackList} list list to work on
|
||||||
|
* @param {AudioTrack} track the track to skip
|
||||||
|
*/
|
||||||
|
const disableOthers = function(list, track) {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
if (track.id === list[i].id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// another audio track is enabled, disable it
|
||||||
|
list[i].enabled = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* A list of possible audio tracks. All functionality is in the
|
||||||
|
* base class Tracklist and the spec for AudioTrackList is located at:
|
||||||
|
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
|
||||||
|
*
|
||||||
|
* interface AudioTrackList : EventTarget {
|
||||||
|
* readonly attribute unsigned long length;
|
||||||
|
* getter AudioTrack (unsigned long index);
|
||||||
|
* AudioTrack? getTrackById(DOMString id);
|
||||||
|
*
|
||||||
|
* attribute EventHandler onchange;
|
||||||
|
* attribute EventHandler onaddtrack;
|
||||||
|
* attribute EventHandler onremovetrack;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* @param {AudioTrack[]} tracks a list of audio tracks to instantiate the list with
|
||||||
|
* @extends TrackList
|
||||||
|
* @class AudioTrackList
|
||||||
|
*/
|
||||||
|
class AudioTrackList extends TrackList {
|
||||||
|
constructor(tracks = []) {
|
||||||
|
let list;
|
||||||
|
|
||||||
|
// make sure only 1 track is enabled
|
||||||
|
// sorted from last index to first index
|
||||||
|
for (let i = tracks.length - 1; i >= 0; i--) {
|
||||||
|
if (tracks[i].enabled) {
|
||||||
|
disableOthers(tracks, tracks[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IE8 forces us to implement inheritance ourselves
|
||||||
|
// as it does not support Object.defineProperty properly
|
||||||
|
if (browser.IS_IE8) {
|
||||||
|
list = document.createElement('custom');
|
||||||
|
for (let prop in TrackList.prototype) {
|
||||||
|
if (prop !== 'constructor') {
|
||||||
|
list[prop] = TrackList.prototype[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let prop in AudioTrackList.prototype) {
|
||||||
|
if (prop !== 'constructor') {
|
||||||
|
list[prop] = AudioTrackList.prototype[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list = super(tracks, list);
|
||||||
|
list.changing_ = false;
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrack_(track) {
|
||||||
|
if (track.enabled) {
|
||||||
|
disableOthers(this, track);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.addTrack_(track);
|
||||||
|
// native tracks don't have this
|
||||||
|
if (!track.addEventListener) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track.addEventListener('enabledchange', () => {
|
||||||
|
// when we are disabling other tracks (since we don't support
|
||||||
|
// more than one track at a time) we will set changing_
|
||||||
|
// to true so that we don't trigger additional change events
|
||||||
|
if (this.changing_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.changing_ = true;
|
||||||
|
disableOthers(this, track);
|
||||||
|
this.changing_ = false;
|
||||||
|
this.trigger('change');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrack(track) {
|
||||||
|
this.addTrack_(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTrack(track) {
|
||||||
|
super.removeTrack_(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AudioTrackList;
|
63
src/js/tracks/audio-track.js
Normal file
63
src/js/tracks/audio-track.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import {AudioTrackKind} from './track-enums';
|
||||||
|
import Track from './track';
|
||||||
|
import merge from '../utils/merge-options';
|
||||||
|
import * as browser from '../utils/browser.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single audio text track as defined in:
|
||||||
|
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack
|
||||||
|
*
|
||||||
|
* interface AudioTrack {
|
||||||
|
* readonly attribute DOMString id;
|
||||||
|
* readonly attribute DOMString kind;
|
||||||
|
* readonly attribute DOMString label;
|
||||||
|
* readonly attribute DOMString language;
|
||||||
|
* attribute boolean enabled;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* @param {Object=} options Object of option names and values
|
||||||
|
* @class AudioTrack
|
||||||
|
*/
|
||||||
|
class AudioTrack extends Track {
|
||||||
|
constructor(options = {}) {
|
||||||
|
let settings = merge(options, {
|
||||||
|
kind: AudioTrackKind[options.kind] || ''
|
||||||
|
});
|
||||||
|
// on IE8 this will be a document element
|
||||||
|
// for every other browser this will be a normal object
|
||||||
|
let track = super(settings);
|
||||||
|
let enabled = false;
|
||||||
|
|
||||||
|
if (browser.IS_IE8) {
|
||||||
|
for (let prop in AudioTrack.prototype) {
|
||||||
|
if (prop !== 'constructor') {
|
||||||
|
track[prop] = AudioTrack.prototype[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(track, 'enabled', {
|
||||||
|
get() { return enabled; },
|
||||||
|
set(newEnabled) {
|
||||||
|
// an invalid or unchanged value
|
||||||
|
if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
enabled = newEnabled;
|
||||||
|
this.trigger('enabledchange');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// if the user sets this track to selected then
|
||||||
|
// set selected to that true value otherwise
|
||||||
|
// we keep it false
|
||||||
|
if (settings.enabled) {
|
||||||
|
track.enabled = settings.enabled;
|
||||||
|
}
|
||||||
|
track.loaded_ = true;
|
||||||
|
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AudioTrack;
|
@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file text-track-enums.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
|
|
||||||
*
|
|
||||||
* enum TextTrackMode { "disabled", "hidden", "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"
|
|
||||||
* };
|
|
||||||
*/
|
|
||||||
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 */
|
|
@ -1,14 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* @file text-track-list.js
|
* @file text-track-list.js
|
||||||
*/
|
*/
|
||||||
import EventTarget from '../event-target';
|
import TrackList from './track-list';
|
||||||
import * as Fn from '../utils/fn.js';
|
import * as Fn from '../utils/fn.js';
|
||||||
import * as browser from '../utils/browser.js';
|
import * as browser from '../utils/browser.js';
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A text track list as defined in:
|
* A list of possible text tracks. All functionality is in the
|
||||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist
|
* base class TrackList. The spec for TextTrackList is located at:
|
||||||
|
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist
|
||||||
*
|
*
|
||||||
* interface TextTrackList : EventTarget {
|
* interface TextTrackList : EventTarget {
|
||||||
* readonly attribute unsigned long length;
|
* readonly attribute unsigned long length;
|
||||||
@ -20,19 +21,23 @@ import document from 'global/document';
|
|||||||
* attribute EventHandler onremovetrack;
|
* attribute EventHandler onremovetrack;
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* @param {Track[]} tracks A list of tracks to initialize the list with
|
* @param {TextTrack[]} tracks A list of tracks to initialize the list with
|
||||||
* @extends EventTarget
|
* @extends TrackList
|
||||||
* @class TextTrackList
|
* @class TextTrackList
|
||||||
*/
|
*/
|
||||||
|
class TextTrackList extends TrackList {
|
||||||
class TextTrackList extends EventTarget {
|
|
||||||
constructor(tracks = []) {
|
constructor(tracks = []) {
|
||||||
super();
|
let list;
|
||||||
let list = this;
|
|
||||||
|
|
||||||
|
// IE8 forces us to implement inheritance ourselves
|
||||||
|
// as it does not support Object.defineProperty properly
|
||||||
if (browser.IS_IE8) {
|
if (browser.IS_IE8) {
|
||||||
list = document.createElement('custom');
|
list = document.createElement('custom');
|
||||||
|
for (let prop in TrackList.prototype) {
|
||||||
|
if (prop !== 'constructor') {
|
||||||
|
list[prop] = TrackList.prototype[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
for (let prop in TextTrackList.prototype) {
|
for (let prop in TextTrackList.prototype) {
|
||||||
if (prop !== 'constructor') {
|
if (prop !== 'constructor') {
|
||||||
list[prop] = TextTrackList.prototype[prop];
|
list[prop] = TextTrackList.prototype[prop];
|
||||||
@ -40,54 +45,15 @@ class TextTrackList extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
list.tracks_ = [];
|
list = super(tracks, list);
|
||||||
|
return list;
|
||||||
Object.defineProperty(list, 'length', {
|
|
||||||
get() {
|
|
||||||
return this.tracks_.length;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < tracks.length; i++) {
|
|
||||||
list.addTrack_(tracks[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browser.IS_IE8) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add TextTrack from TextTrackList
|
|
||||||
*
|
|
||||||
* @param {TextTrack} track
|
|
||||||
* @method addTrack_
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
addTrack_(track) {
|
addTrack_(track) {
|
||||||
let index = this.tracks_.length;
|
super.addTrack_(track);
|
||||||
|
|
||||||
if (!('' + index in this)) {
|
|
||||||
Object.defineProperty(this, index, {
|
|
||||||
get() {
|
|
||||||
return this.tracks_[index];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
track.addEventListener('modechange', Fn.bind(this, function() {
|
track.addEventListener('modechange', Fn.bind(this, function() {
|
||||||
this.trigger('change');
|
this.trigger('change');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Do not add duplicate tracks
|
|
||||||
if (this.tracks_.indexOf(track) === -1) {
|
|
||||||
this.tracks_.push(track);
|
|
||||||
this.trigger({
|
|
||||||
track,
|
|
||||||
type: 'addtrack'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,21 +113,4 @@ class TextTrackList extends EventTarget {
|
|||||||
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;
|
export default TextTrackList;
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
*/
|
*/
|
||||||
import TextTrackCueList from './text-track-cue-list';
|
import TextTrackCueList from './text-track-cue-list';
|
||||||
import * as Fn from '../utils/fn.js';
|
import * as Fn from '../utils/fn.js';
|
||||||
import * as Guid from '../utils/guid.js';
|
import {TextTrackKind, TextTrackMode} from './track-enums';
|
||||||
import * as browser from '../utils/browser.js';
|
|
||||||
import * as TextTrackEnum from './text-track-enums';
|
|
||||||
import log from '../utils/log.js';
|
import log from '../utils/log.js';
|
||||||
import EventTarget from '../event-target';
|
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
|
import Track from './track.js';
|
||||||
import { isCrossOrigin } from '../utils/url.js';
|
import { isCrossOrigin } from '../utils/url.js';
|
||||||
import XHR from 'xhr';
|
import XHR from 'xhr';
|
||||||
|
import merge from '../utils/merge-options';
|
||||||
|
import * as browser from '../utils/browser.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* takes a webvtt file contents and parses it into cues
|
* takes a webvtt file contents and parses it into cues
|
||||||
@ -54,7 +54,6 @@ const parseCues = function(srcContent, track) {
|
|||||||
parser.flush();
|
parser.flush();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* load a track from a specifed url
|
* load a track from a specifed url
|
||||||
*
|
*
|
||||||
@ -99,7 +98,7 @@ const loadTrack = function(src, track) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A single text track as defined in:
|
* A single text track as defined in:
|
||||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack
|
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack
|
||||||
*
|
*
|
||||||
* interface TextTrack : EventTarget {
|
* interface TextTrack : EventTarget {
|
||||||
* readonly attribute TextTrackKind kind;
|
* readonly attribute TextTrackKind kind;
|
||||||
@ -121,21 +120,31 @@ const loadTrack = function(src, track) {
|
|||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* @param {Object=} options Object of option names and values
|
* @param {Object=} options Object of option names and values
|
||||||
* @extends EventTarget
|
* @extends Track
|
||||||
* @class TextTrack
|
* @class TextTrack
|
||||||
*/
|
*/
|
||||||
class TextTrack extends EventTarget {
|
class TextTrack extends Track {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
super();
|
|
||||||
if (!options.tech) {
|
if (!options.tech) {
|
||||||
throw new Error('A tech was not provided.');
|
throw new Error('A tech was not provided.');
|
||||||
}
|
}
|
||||||
|
|
||||||
let tt = this;
|
let settings = merge(options, {
|
||||||
|
kind: TextTrackKind[options.kind] || 'subtitles',
|
||||||
|
language: options.language || options.srclang || ''
|
||||||
|
});
|
||||||
|
let mode = TextTrackMode[settings.mode] || 'disabled';
|
||||||
|
let default_ = settings.default;
|
||||||
|
|
||||||
|
if (settings.kind === 'metadata' || settings.kind === 'chapters') {
|
||||||
|
mode = 'hidden';
|
||||||
|
}
|
||||||
|
// on IE8 this will be a document element
|
||||||
|
// for every other browser this will be a normal object
|
||||||
|
let tt = super(settings);
|
||||||
|
tt.tech_ = settings.tech;
|
||||||
|
|
||||||
if (browser.IS_IE8) {
|
if (browser.IS_IE8) {
|
||||||
tt = document.createElement('custom');
|
|
||||||
|
|
||||||
for (let prop in TextTrack.prototype) {
|
for (let prop in TextTrack.prototype) {
|
||||||
if (prop !== 'constructor') {
|
if (prop !== 'constructor') {
|
||||||
tt[prop] = TextTrack.prototype[prop];
|
tt[prop] = TextTrack.prototype[prop];
|
||||||
@ -143,19 +152,6 @@ class TextTrack extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tt.tech_ = options.tech;
|
|
||||||
|
|
||||||
let mode = TextTrackEnum.TextTrackMode[options.mode] || 'disabled';
|
|
||||||
let kind = TextTrackEnum.TextTrackKind[options.kind] || 'subtitles';
|
|
||||||
let default_ = options.default;
|
|
||||||
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.cues_ = [];
|
||||||
tt.activeCues_ = [];
|
tt.activeCues_ = [];
|
||||||
|
|
||||||
@ -174,34 +170,6 @@ class TextTrack extends EventTarget {
|
|||||||
tt.tech_.on('timeupdate', timeupdateHandler);
|
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, 'default', {
|
Object.defineProperty(tt, 'default', {
|
||||||
get() {
|
get() {
|
||||||
return default_;
|
return default_;
|
||||||
@ -214,7 +182,7 @@ class TextTrack extends EventTarget {
|
|||||||
return mode;
|
return mode;
|
||||||
},
|
},
|
||||||
set(newMode) {
|
set(newMode) {
|
||||||
if (!TextTrackEnum.TextTrackMode[newMode]) {
|
if (!TextTrackMode[newMode]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mode = newMode;
|
mode = newMode;
|
||||||
@ -282,16 +250,14 @@ class TextTrack extends EventTarget {
|
|||||||
set() {}
|
set() {}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (options.src) {
|
if (settings.src) {
|
||||||
tt.src = options.src;
|
tt.src = settings.src;
|
||||||
loadTrack(options.src, tt);
|
loadTrack(settings.src, tt);
|
||||||
} else {
|
} else {
|
||||||
tt.loaded_ = true;
|
tt.loaded_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (browser.IS_IE8) {
|
return tt;
|
||||||
return tt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
86
src/js/tracks/track-enums.js
Normal file
86
src/js/tracks/track-enums.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* @file track-kinds.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
|
||||||
|
*
|
||||||
|
* enum VideoTrackKind {
|
||||||
|
* "alternative",
|
||||||
|
* "captions",
|
||||||
|
* "main",
|
||||||
|
* "sign",
|
||||||
|
* "subtitles",
|
||||||
|
* "commentary",
|
||||||
|
* "",
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
const VideoTrackKind = {
|
||||||
|
alternative: 'alternative',
|
||||||
|
captions: 'captions',
|
||||||
|
main: 'main',
|
||||||
|
sign: 'sign',
|
||||||
|
subtitles: 'subtitles',
|
||||||
|
commentary: 'commentary',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
|
||||||
|
*
|
||||||
|
* enum AudioTrackKind {
|
||||||
|
* "alternative",
|
||||||
|
* "descriptions",
|
||||||
|
* "main",
|
||||||
|
* "main-desc",
|
||||||
|
* "translation",
|
||||||
|
* "commentary",
|
||||||
|
* "",
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
const AudioTrackKind = {
|
||||||
|
alternative: 'alternative',
|
||||||
|
descriptions: 'descriptions',
|
||||||
|
main: 'main',
|
||||||
|
'main-desc': 'main-desc',
|
||||||
|
translation: 'translation',
|
||||||
|
commentary: 'commentary',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackkind
|
||||||
|
*
|
||||||
|
* enum TextTrackKind {
|
||||||
|
* "subtitles",
|
||||||
|
* "captions",
|
||||||
|
* "descriptions",
|
||||||
|
* "chapters",
|
||||||
|
* "metadata"
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
const TextTrackKind = {
|
||||||
|
subtitles: 'subtitles',
|
||||||
|
captions: 'captions',
|
||||||
|
descriptions: 'descriptions',
|
||||||
|
chapters: 'chapters',
|
||||||
|
metadata: 'metadata'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
|
||||||
|
*
|
||||||
|
* enum TextTrackMode { "disabled", "hidden", "showing" };
|
||||||
|
*/
|
||||||
|
const TextTrackMode = {
|
||||||
|
disabled: 'disabled',
|
||||||
|
hidden: 'hidden',
|
||||||
|
showing: 'showing'
|
||||||
|
};
|
||||||
|
|
||||||
|
/* jshint ignore:start */
|
||||||
|
// we ignore jshint here because it does not see
|
||||||
|
// AudioTrackKind as defined here
|
||||||
|
export default { VideoTrackKind, AudioTrackKind, TextTrackKind, TextTrackMode };
|
||||||
|
/* jshint ignore:end */
|
||||||
|
|
148
src/js/tracks/track-list.js
Normal file
148
src/js/tracks/track-list.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* @file track-list.js
|
||||||
|
*/
|
||||||
|
import EventTarget from '../event-target';
|
||||||
|
import * as Fn from '../utils/fn.js';
|
||||||
|
import * as browser from '../utils/browser.js';
|
||||||
|
import document from 'global/document';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common functionaliy between Text, Audio, and Video TrackLists
|
||||||
|
* Interfaces defined in the following spec:
|
||||||
|
* @link https://html.spec.whatwg.org/multipage/embedded-content.html
|
||||||
|
*
|
||||||
|
* @param {Track[]} tracks A list of tracks to initialize the list with
|
||||||
|
* @param {Object} list the child object with inheritance done manually for ie8
|
||||||
|
* @extends EventTarget
|
||||||
|
* @class TrackList
|
||||||
|
*/
|
||||||
|
class TrackList extends EventTarget {
|
||||||
|
constructor(tracks = [], list = null) {
|
||||||
|
super();
|
||||||
|
if (!list) {
|
||||||
|
list = this;
|
||||||
|
if (browser.IS_IE8) {
|
||||||
|
list = document.createElement('custom');
|
||||||
|
for (let prop in TrackList.prototype) {
|
||||||
|
if (prop !== 'constructor') {
|
||||||
|
list[prop] = TrackList.prototype[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.tracks_ = [];
|
||||||
|
Object.defineProperty(list, 'length', {
|
||||||
|
get() {
|
||||||
|
return this.tracks_.length;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < tracks.length; i++) {
|
||||||
|
list.addTrack_(tracks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Track from TrackList
|
||||||
|
*
|
||||||
|
* @param {Mixed} track
|
||||||
|
* @method addTrack_
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
addTrack_(track) {
|
||||||
|
let index = this.tracks_.length;
|
||||||
|
|
||||||
|
if (!('' + index in this)) {
|
||||||
|
Object.defineProperty(this, index, {
|
||||||
|
get() {
|
||||||
|
return this.tracks_[index];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not add duplicate tracks
|
||||||
|
if (this.tracks_.indexOf(track) === -1) {
|
||||||
|
this.tracks_.push(track);
|
||||||
|
this.trigger({
|
||||||
|
track,
|
||||||
|
type: 'addtrack'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a Track from TrackList
|
||||||
|
*
|
||||||
|
* @param {Track} rtrack track to be removed
|
||||||
|
* @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 Track from the TrackList by a tracks id
|
||||||
|
*
|
||||||
|
* @param {String} id - the id of the track to get
|
||||||
|
* @method getTrackById
|
||||||
|
* @return {Track}
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
TrackList.prototype.allowedEvents_ = {
|
||||||
|
change: 'change',
|
||||||
|
addtrack: 'addtrack',
|
||||||
|
removetrack: 'removetrack'
|
||||||
|
};
|
||||||
|
|
||||||
|
// emulate attribute EventHandler support to allow for feature detection
|
||||||
|
for (let event in TrackList.prototype.allowedEvents_) {
|
||||||
|
TrackList.prototype['on' + event] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TrackList;
|
50
src/js/tracks/track.js
Normal file
50
src/js/tracks/track.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* @file track.js
|
||||||
|
*/
|
||||||
|
import * as browser from '../utils/browser.js';
|
||||||
|
import document from 'global/document';
|
||||||
|
import * as Guid from '../utils/guid.js';
|
||||||
|
import EventTarget from '../event-target';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setup the common parts of an audio, video, or text track
|
||||||
|
* @link https://html.spec.whatwg.org/multipage/embedded-content.html
|
||||||
|
*
|
||||||
|
* @param {String} type The type of track we are dealing with audio|video|text
|
||||||
|
* @param {Object=} options Object of option names and values
|
||||||
|
* @extends EventTarget
|
||||||
|
* @class Track
|
||||||
|
*/
|
||||||
|
class Track extends EventTarget {
|
||||||
|
constructor(options = {}) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
let track = this;
|
||||||
|
if (browser.IS_IE8) {
|
||||||
|
track = document.createElement('custom');
|
||||||
|
for (let prop in Track.prototype) {
|
||||||
|
if (prop !== 'constructor') {
|
||||||
|
track[prop] = Track.prototype[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let trackProps = {
|
||||||
|
id: options.id || 'vjs_track_' + Guid.newGUID(),
|
||||||
|
kind: options.kind || '',
|
||||||
|
label: options.label || '',
|
||||||
|
language: options.language || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let key in trackProps) {
|
||||||
|
Object.defineProperty(track, key, {
|
||||||
|
get() { return trackProps[key]; },
|
||||||
|
set() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Track;
|
123
src/js/tracks/video-track-list.js
Normal file
123
src/js/tracks/video-track-list.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* @file video-track-list.js
|
||||||
|
*/
|
||||||
|
import TrackList from './track-list';
|
||||||
|
import * as browser from '../utils/browser.js';
|
||||||
|
import document from 'global/document';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* disable other video tracks before selecting the new one
|
||||||
|
*
|
||||||
|
* @param {Array|VideoTrackList} list list to work on
|
||||||
|
* @param {VideoTrack} track the track to skip
|
||||||
|
*/
|
||||||
|
const disableOthers = function(list, track) {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
if (track.id === list[i].id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// another audio track is enabled, disable it
|
||||||
|
list[i].selected = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of possiblee video tracks. Most functionality is in the
|
||||||
|
* base class Tracklist and the spec for VideoTrackList is located at:
|
||||||
|
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
|
||||||
|
*
|
||||||
|
* interface VideoTrackList : EventTarget {
|
||||||
|
* readonly attribute unsigned long length;
|
||||||
|
* getter VideoTrack (unsigned long index);
|
||||||
|
* VideoTrack? getTrackById(DOMString id);
|
||||||
|
* readonly attribute long selectedIndex;
|
||||||
|
*
|
||||||
|
* attribute EventHandler onchange;
|
||||||
|
* attribute EventHandler onaddtrack;
|
||||||
|
* attribute EventHandler onremovetrack;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* @param {VideoTrack[]} tracks a list of video tracks to instantiate the list with
|
||||||
|
# @extends TrackList
|
||||||
|
* @class VideoTrackList
|
||||||
|
*/
|
||||||
|
class VideoTrackList extends TrackList {
|
||||||
|
|
||||||
|
constructor(tracks = []) {
|
||||||
|
let list;
|
||||||
|
|
||||||
|
// make sure only 1 track is enabled
|
||||||
|
// sorted from last index to first index
|
||||||
|
for (let i = tracks.length - 1; i >= 0; i--) {
|
||||||
|
if (tracks[i].selected) {
|
||||||
|
disableOthers(tracks, tracks[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IE8 forces us to implement inheritance ourselves
|
||||||
|
// as it does not support Object.defineProperty properly
|
||||||
|
if (browser.IS_IE8) {
|
||||||
|
list = document.createElement('custom');
|
||||||
|
for (let prop in TrackList.prototype) {
|
||||||
|
if (prop !== 'constructor') {
|
||||||
|
list[prop] = TrackList.prototype[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let prop in VideoTrackList.prototype) {
|
||||||
|
if (prop !== 'constructor') {
|
||||||
|
list[prop] = VideoTrackList.prototype[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list = super(tracks, list);
|
||||||
|
list.changing_ = false;
|
||||||
|
|
||||||
|
Object.defineProperty(list, 'selectedIndex', {
|
||||||
|
get() {
|
||||||
|
for (let i = 0; i < this.length; i++) {
|
||||||
|
if (this[i].selected) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
set() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrack_(track) {
|
||||||
|
if (track.selected) {
|
||||||
|
disableOthers(this, track);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.addTrack_(track);
|
||||||
|
// native tracks don't have this
|
||||||
|
if (!track.addEventListener) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
track.addEventListener('selectedchange', () => {
|
||||||
|
if (this.changing_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.changing_ = true;
|
||||||
|
disableOthers(this, track);
|
||||||
|
this.changing_ = false;
|
||||||
|
this.trigger('change');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrack(track) {
|
||||||
|
this.addTrack_(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTrack(track) {
|
||||||
|
super.removeTrack_(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoTrackList;
|
63
src/js/tracks/video-track.js
Normal file
63
src/js/tracks/video-track.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import {VideoTrackKind} from './track-enums';
|
||||||
|
import Track from './track';
|
||||||
|
import merge from '../utils/merge-options';
|
||||||
|
import * as browser from '../utils/browser.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single video text track as defined in:
|
||||||
|
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack
|
||||||
|
*
|
||||||
|
* interface VideoTrack {
|
||||||
|
* readonly attribute DOMString id;
|
||||||
|
* readonly attribute DOMString kind;
|
||||||
|
* readonly attribute DOMString label;
|
||||||
|
* readonly attribute DOMString language;
|
||||||
|
* attribute boolean selected;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* @param {Object=} options Object of option names and values
|
||||||
|
* @class VideoTrack
|
||||||
|
*/
|
||||||
|
class VideoTrack extends Track {
|
||||||
|
constructor(options = {}) {
|
||||||
|
let settings = merge(options, {
|
||||||
|
kind: VideoTrackKind[options.kind] || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// on IE8 this will be a document element
|
||||||
|
// for every other browser this will be a normal object
|
||||||
|
let track = super(settings);
|
||||||
|
let selected = false;
|
||||||
|
|
||||||
|
if (browser.IS_IE8) {
|
||||||
|
for (let prop in VideoTrack.prototype) {
|
||||||
|
if (prop !== 'constructor') {
|
||||||
|
track[prop] = VideoTrack.prototype[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(track, 'selected', {
|
||||||
|
get() { return selected; },
|
||||||
|
set(newSelected) {
|
||||||
|
// an invalid or unchanged value
|
||||||
|
if (typeof newSelected !== 'boolean' || newSelected === selected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selected = newSelected;
|
||||||
|
this.trigger('selectedchange');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// if the user sets this track to selected then
|
||||||
|
// set selected to that true value otherwise
|
||||||
|
// we keep it false
|
||||||
|
if (settings.selected) {
|
||||||
|
track.selected = settings.selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoTrack;
|
@ -13,6 +13,8 @@ import plugin from './plugins.js';
|
|||||||
import mergeOptions from '../../src/js/utils/merge-options.js';
|
import mergeOptions from '../../src/js/utils/merge-options.js';
|
||||||
import * as Fn from './utils/fn.js';
|
import * as Fn from './utils/fn.js';
|
||||||
import TextTrack from './tracks/text-track.js';
|
import TextTrack from './tracks/text-track.js';
|
||||||
|
import AudioTrack from './tracks/audio-track.js';
|
||||||
|
import VideoTrack from './tracks/video-track.js';
|
||||||
|
|
||||||
import assign from 'object.assign';
|
import assign from 'object.assign';
|
||||||
import { createTimeRanges } from './utils/time-ranges.js';
|
import { createTimeRanges } from './utils/time-ranges.js';
|
||||||
@ -549,6 +551,22 @@ videojs.xhr = xhr;
|
|||||||
*/
|
*/
|
||||||
videojs.TextTrack = TextTrack;
|
videojs.TextTrack = TextTrack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* export the AudioTrack class so that source handlers can create
|
||||||
|
* AudioTracks and then add them to the players AudioTrackList
|
||||||
|
*
|
||||||
|
* @type {Function}
|
||||||
|
*/
|
||||||
|
videojs.AudioTrack = AudioTrack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* export the VideoTrack class so that source handlers can create
|
||||||
|
* VideoTracks and then add them to the players VideoTrackList
|
||||||
|
*
|
||||||
|
* @type {Function}
|
||||||
|
*/
|
||||||
|
videojs.VideoTrack = VideoTrack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines, via duck typing, whether or not a value is a DOM element.
|
* Determines, via duck typing, whether or not a value is a DOM element.
|
||||||
*
|
*
|
||||||
|
@ -59,7 +59,9 @@ test('should be able to access expected player API methods', function() {
|
|||||||
ok(player.usingNativeControls, 'usingNativeControls exists');
|
ok(player.usingNativeControls, 'usingNativeControls exists');
|
||||||
ok(player.isFullscreen, 'isFullscreen exists');
|
ok(player.isFullscreen, 'isFullscreen exists');
|
||||||
|
|
||||||
// TextTrack methods
|
// Track methods
|
||||||
|
ok(player.audioTracks, 'audioTracks exists');
|
||||||
|
ok(player.videoTracks, 'videoTracks exists');
|
||||||
ok(player.textTracks, 'textTracks exists');
|
ok(player.textTracks, 'textTracks exists');
|
||||||
ok(player.remoteTextTrackEls, 'remoteTextTrackEls exists');
|
ok(player.remoteTextTrackEls, 'remoteTextTrackEls exists');
|
||||||
ok(player.remoteTextTracks, 'remoteTextTracks exists');
|
ok(player.remoteTextTracks, 'remoteTextTracks exists');
|
||||||
|
@ -86,14 +86,13 @@ test('dispose removes the object element even before ready fires', function() {
|
|||||||
// This test appears to test bad functionaly that was fixed
|
// This test appears to test bad functionaly that was fixed
|
||||||
// so it's debateable whether or not it's useful
|
// so it's debateable whether or not it's useful
|
||||||
let dispose = Flash.prototype.dispose;
|
let dispose = Flash.prototype.dispose;
|
||||||
let mockFlash = {};
|
let mockFlash = new MockFlash();
|
||||||
let noop = function(){};
|
let noop = function(){};
|
||||||
|
|
||||||
// Mock required functions for dispose
|
// Mock required functions for dispose
|
||||||
mockFlash.off = noop;
|
mockFlash.off = noop;
|
||||||
mockFlash.trigger = noop;
|
mockFlash.trigger = noop;
|
||||||
mockFlash.el_ = {};
|
mockFlash.el_ = {};
|
||||||
mockFlash.textTracks = () => ([]);
|
|
||||||
|
|
||||||
dispose.call(mockFlash);
|
dispose.call(mockFlash);
|
||||||
strictEqual(mockFlash.el_, null, 'swf el is nulled');
|
strictEqual(mockFlash.el_, null, 'swf el is nulled');
|
||||||
|
@ -249,6 +249,97 @@ if (Html5.supportsNativeTextTracks()) {
|
|||||||
equal(adds[2][0], rems[2][0], 'removetrack event handler removed');
|
equal(adds[2][0], rems[2][0], 'removetrack event handler removed');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Html5.supportsNativeAudioTracks()) {
|
||||||
|
test('add native audioTrack listeners on startup', function() {
|
||||||
|
let adds = [];
|
||||||
|
let rems = [];
|
||||||
|
let at = {
|
||||||
|
length: 0,
|
||||||
|
addEventListener: (type, fn) => adds.push([type, fn]),
|
||||||
|
removeEventListener: (type, fn) => rems.push([type, fn]),
|
||||||
|
};
|
||||||
|
let el = document.createElement('div');
|
||||||
|
el.audioTracks = at;
|
||||||
|
|
||||||
|
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 at = {
|
||||||
|
length: 0,
|
||||||
|
addEventListener: (type, fn) => adds.push([type, fn]),
|
||||||
|
removeEventListener: (type, fn) => rems.push([type, fn]),
|
||||||
|
};
|
||||||
|
let el = document.createElement('div');
|
||||||
|
el.audioTracks = at;
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Html5.supportsNativeVideoTracks()) {
|
||||||
|
test('add native videoTrack listeners on startup', function() {
|
||||||
|
let adds = [];
|
||||||
|
let rems = [];
|
||||||
|
let vt = {
|
||||||
|
length: 0,
|
||||||
|
addEventListener: (type, fn) => adds.push([type, fn]),
|
||||||
|
removeEventListener: (type, fn) => rems.push([type, fn]),
|
||||||
|
};
|
||||||
|
let el = document.createElement('div');
|
||||||
|
el.videoTracks = vt;
|
||||||
|
|
||||||
|
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 vt = {
|
||||||
|
length: 0,
|
||||||
|
addEventListener: (type, fn) => adds.push([type, fn]),
|
||||||
|
removeEventListener: (type, fn) => rems.push([type, fn]),
|
||||||
|
};
|
||||||
|
let el = document.createElement('div');
|
||||||
|
el.videoTracks = vt;
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
test('should always return currentSource_ if set', function(){
|
test('should always return currentSource_ if set', function(){
|
||||||
let currentSrc = Html5.prototype.currentSrc;
|
let currentSrc = Html5.prototype.currentSrc;
|
||||||
equal(currentSrc.call({el_: {currentSrc:'test1'}}), 'test1', 'sould return source from element if nothing else set');
|
equal(currentSrc.call({el_: {currentSrc:'test1'}}), 'test1', 'sould return source from element if nothing else set');
|
||||||
|
@ -7,6 +7,13 @@ import Button from '../../../src/js/button.js';
|
|||||||
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
|
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
|
||||||
import extendFn from '../../../src/js/extend.js';
|
import extendFn from '../../../src/js/extend.js';
|
||||||
import MediaError from '../../../src/js/media-error.js';
|
import MediaError from '../../../src/js/media-error.js';
|
||||||
|
import AudioTrack from '../../../src/js/tracks/audio-track';
|
||||||
|
import VideoTrack from '../../../src/js/tracks/video-track';
|
||||||
|
import TextTrack from '../../../src/js/tracks/text-track';
|
||||||
|
import AudioTrackList from '../../../src/js/tracks/audio-track-list';
|
||||||
|
import VideoTrackList from '../../../src/js/tracks/video-track-list';
|
||||||
|
import TextTrackList from '../../../src/js/tracks/text-track-list';
|
||||||
|
|
||||||
|
|
||||||
q.module('Media Tech', {
|
q.module('Media Tech', {
|
||||||
'setup': function() {
|
'setup': function() {
|
||||||
@ -14,20 +21,10 @@ q.module('Media Tech', {
|
|||||||
this.clock = sinon.useFakeTimers();
|
this.clock = sinon.useFakeTimers();
|
||||||
this.featuresProgessEvents = Tech.prototype['featuresProgessEvents'];
|
this.featuresProgessEvents = Tech.prototype['featuresProgessEvents'];
|
||||||
Tech.prototype['featuresProgressEvents'] = false;
|
Tech.prototype['featuresProgressEvents'] = false;
|
||||||
Tech.prototype['featuresNativeTextTracks'] = true;
|
|
||||||
oldTextTracks = Tech.prototype.textTracks;
|
|
||||||
Tech.prototype.textTracks = function() {
|
|
||||||
return {
|
|
||||||
addEventListener: Function.prototype,
|
|
||||||
removeEventListener: Function.prototype
|
|
||||||
};
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
'teardown': function() {
|
'teardown': function() {
|
||||||
this.clock.restore();
|
this.clock.restore();
|
||||||
Tech.prototype['featuresProgessEvents'] = this.featuresProgessEvents;
|
Tech.prototype['featuresProgessEvents'] = this.featuresProgessEvents;
|
||||||
Tech.prototype['featuresNativeTextTracks'] = false;
|
|
||||||
Tech.prototype.textTracks = oldTextTracks;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -103,6 +100,54 @@ test('dispose() should stop time tracking', function() {
|
|||||||
ok(true, 'no exception was thrown');
|
ok(true, 'no exception was thrown');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('dispose() should clear all tracks that are passed when its created', function() {
|
||||||
|
var audioTracks = new AudioTrackList([new AudioTrack(), new AudioTrack()]);
|
||||||
|
var videoTracks = new VideoTrackList([new VideoTrack(), new VideoTrack()]);
|
||||||
|
var textTracks = new TextTrackList([new TextTrack({tech: {}}), new TextTrack({tech: {}})]);
|
||||||
|
|
||||||
|
equal(audioTracks.length, 2, 'should have two audio tracks at the start');
|
||||||
|
equal(videoTracks.length, 2, 'should have two video tracks at the start');
|
||||||
|
equal(textTracks.length, 2, 'should have two text tracks at the start');
|
||||||
|
|
||||||
|
var tech = new Tech({audioTracks, videoTracks, textTracks});
|
||||||
|
equal(tech.videoTracks().length, videoTracks.length, 'should hold video tracks that we passed');
|
||||||
|
equal(tech.audioTracks().length, audioTracks.length, 'should hold audio tracks that we passed');
|
||||||
|
equal(tech.textTracks().length, textTracks.length, 'should hold text tracks that we passed');
|
||||||
|
|
||||||
|
tech.dispose();
|
||||||
|
|
||||||
|
equal(audioTracks.length, 0, 'should have zero audio tracks after dispose');
|
||||||
|
equal(videoTracks.length, 0, 'should have zero video tracks after dispose');
|
||||||
|
equal(textTracks.length, 0, 'should have zero text tracks after dispose');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dispose() should clear all tracks that are added after creation', function() {
|
||||||
|
var tech = new Tech();
|
||||||
|
|
||||||
|
tech.addRemoteTextTrack({});
|
||||||
|
tech.addRemoteTextTrack({});
|
||||||
|
|
||||||
|
tech.audioTracks().addTrack_(new AudioTrack());
|
||||||
|
tech.audioTracks().addTrack_(new AudioTrack());
|
||||||
|
|
||||||
|
tech.videoTracks().addTrack_(new VideoTrack());
|
||||||
|
tech.videoTracks().addTrack_(new VideoTrack());
|
||||||
|
|
||||||
|
equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
|
||||||
|
equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
|
||||||
|
equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
|
||||||
|
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
|
||||||
|
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
||||||
|
|
||||||
|
tech.dispose();
|
||||||
|
|
||||||
|
equal(tech.audioTracks().length, 0, 'should have zero audio tracks after dispose');
|
||||||
|
equal(tech.videoTracks().length, 0, 'should have zero video tracks after dispose');
|
||||||
|
equal(tech.remoteTextTrackEls().length, 0, 'should have zero remote text tracks els');
|
||||||
|
equal(tech.remoteTextTracks().length, 0, 'should have zero remote text tracks');
|
||||||
|
equal(tech.textTracks().length, 0, 'should have zero video tracks after dispose');
|
||||||
|
});
|
||||||
|
|
||||||
test('should add the source handler interface to a tech', function(){
|
test('should add the source handler interface to a tech', function(){
|
||||||
var sourceA = { src: 'foo.mp4', type: 'video/mp4' };
|
var sourceA = { src: 'foo.mp4', type: 'video/mp4' };
|
||||||
var sourceB = { src: 'no-support', type: 'no-support' };
|
var sourceB = { src: 'no-support', type: 'no-support' };
|
||||||
@ -185,13 +230,49 @@ test('should add the source handler interface to a tech', function(){
|
|||||||
strictEqual(MyTech.canPlaySource(sourceA), 'probably', 'the Tech returned probably for the valid source');
|
strictEqual(MyTech.canPlaySource(sourceA), 'probably', 'the Tech returned probably for the valid source');
|
||||||
strictEqual(MyTech.canPlaySource(sourceB), '', 'the Tech returned an empty string for the invalid source');
|
strictEqual(MyTech.canPlaySource(sourceB), '', 'the Tech returned an empty string for the invalid source');
|
||||||
|
|
||||||
|
tech.addRemoteTextTrack({});
|
||||||
|
tech.addRemoteTextTrack({});
|
||||||
|
|
||||||
|
tech.audioTracks().addTrack_(new AudioTrack());
|
||||||
|
tech.audioTracks().addTrack_(new AudioTrack());
|
||||||
|
|
||||||
|
tech.videoTracks().addTrack_(new VideoTrack());
|
||||||
|
tech.videoTracks().addTrack_(new VideoTrack());
|
||||||
|
|
||||||
|
equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
|
||||||
|
equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
|
||||||
|
equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
|
||||||
|
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
|
||||||
|
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
||||||
|
|
||||||
// Pass a source through the source handler process of a tech instance
|
// Pass a source through the source handler process of a tech instance
|
||||||
tech.setSource(sourceA);
|
tech.setSource(sourceA);
|
||||||
|
|
||||||
|
// verify that the Tracks are still there
|
||||||
|
equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
|
||||||
|
equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
|
||||||
|
equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
|
||||||
|
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
|
||||||
|
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
||||||
|
|
||||||
|
strictEqual(tech.currentSource_, sourceA, 'sourceA was handled and stored');
|
||||||
|
ok(tech.sourceHandler_.dispose, 'the handlerOne state instance was stored');
|
||||||
|
|
||||||
|
// Pass a second source
|
||||||
|
tech.setSource(sourceA);
|
||||||
strictEqual(tech.currentSource_, sourceA, 'sourceA was handled and stored');
|
strictEqual(tech.currentSource_, sourceA, 'sourceA was handled and stored');
|
||||||
ok(tech.sourceHandler_.dispose, 'the handlerOne state instance was stored');
|
ok(tech.sourceHandler_.dispose, 'the handlerOne state instance was stored');
|
||||||
|
|
||||||
|
// verify that all the tracks were removed as we got a new source
|
||||||
|
equal(tech.audioTracks().length, 0, 'should have zero audio tracks');
|
||||||
|
equal(tech.videoTracks().length, 0, 'should have zero video tracks');
|
||||||
|
equal(tech.textTracks().length, 2, 'should have two video tracks');
|
||||||
|
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
|
||||||
|
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
||||||
|
|
||||||
// Check that the handler dipose method works
|
// Check that the handler dipose method works
|
||||||
ok(!disposeCalled, 'dispose has not been called for the handler yet');
|
ok(disposeCalled, 'dispose has been called for the handler yet');
|
||||||
|
disposeCalled = false;
|
||||||
tech.dispose();
|
tech.dispose();
|
||||||
ok(disposeCalled, 'the handler dispose method was called when the tech was disposed');
|
ok(disposeCalled, 'the handler dispose method was called when the tech was disposed');
|
||||||
});
|
});
|
||||||
|
97
test/unit/tracks/audio-track-list.test.js
Normal file
97
test/unit/tracks/audio-track-list.test.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import AudioTrackList from '../../../src/js/tracks/audio-track-list.js';
|
||||||
|
import AudioTrack from '../../../src/js/tracks/audio-track.js';
|
||||||
|
import EventTarget from '../../../src/js/event-target.js';
|
||||||
|
|
||||||
|
q.module('Audio Track List');
|
||||||
|
|
||||||
|
test('trigger "change" when "enabledchange" is fired on a track', function() {
|
||||||
|
let track = new EventTarget();
|
||||||
|
track.loaded_ = true;
|
||||||
|
let audioTrackList = new AudioTrackList([track]);
|
||||||
|
let changes = 0;
|
||||||
|
let changeHandler = function() {
|
||||||
|
changes++;
|
||||||
|
};
|
||||||
|
audioTrackList.on('change', changeHandler);
|
||||||
|
track.trigger('enabledchange');
|
||||||
|
equal(changes, 1, 'one change events for trigger');
|
||||||
|
|
||||||
|
audioTrackList.off('change', changeHandler);
|
||||||
|
audioTrackList.onchange = changeHandler;
|
||||||
|
|
||||||
|
track.trigger('enabledchange');
|
||||||
|
equal(changes, 2, 'one change events for another trigger');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('only one track is ever enabled', function() {
|
||||||
|
let track = new AudioTrack({enabled: true});
|
||||||
|
let track2 = new AudioTrack({enabled: true});
|
||||||
|
let track3 = new AudioTrack({enabled: true});
|
||||||
|
let track4 = new AudioTrack();
|
||||||
|
let list = new AudioTrackList([track, track2]);
|
||||||
|
equal(track.enabled, false, 'track is disabled');
|
||||||
|
equal(track2.enabled, true, 'track2 is enabled');
|
||||||
|
|
||||||
|
track.enabled = true;
|
||||||
|
equal(track.enabled, true, 'track is enabled');
|
||||||
|
equal(track2.enabled, false, 'track2 is disabled');
|
||||||
|
|
||||||
|
list.addTrack_(track3);
|
||||||
|
equal(track.enabled, false, 'track is disabled');
|
||||||
|
equal(track2.enabled, false, 'track2 is disabled');
|
||||||
|
equal(track3.enabled, true, 'track3 is enabled');
|
||||||
|
|
||||||
|
track.enabled = true;
|
||||||
|
equal(track.enabled, true, 'track is disabled');
|
||||||
|
equal(track2.enabled, false, 'track2 is disabled');
|
||||||
|
equal(track3.enabled, false, 'track3 is disabled');
|
||||||
|
|
||||||
|
list.addTrack_(track4);
|
||||||
|
equal(track.enabled, true, 'track is enabled');
|
||||||
|
equal(track2.enabled, false, 'track2 is disabled');
|
||||||
|
equal(track3.enabled, false, 'track3 is disabled');
|
||||||
|
equal(track4.enabled, false, 'track4 is disabled');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('all tracks can be disabled', function() {
|
||||||
|
let track = new AudioTrack();
|
||||||
|
let track2 = new AudioTrack();
|
||||||
|
let list = new AudioTrackList([track, track2]);
|
||||||
|
equal(track.enabled, false, 'track is disabled');
|
||||||
|
equal(track2.enabled, false, 'track2 is disabled');
|
||||||
|
|
||||||
|
track.enabled = true;
|
||||||
|
equal(track.enabled, true, 'track is enabled');
|
||||||
|
equal(track2.enabled, false, 'track2 is disabled');
|
||||||
|
|
||||||
|
track.enabled = false;
|
||||||
|
equal(track.enabled, false, 'track is disabled');
|
||||||
|
equal(track2.enabled, false, 'track2 is disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('trigger a change event per enabled change', function() {
|
||||||
|
let track = new AudioTrack({enabled: true});
|
||||||
|
let track2 = new AudioTrack({enabled: true});
|
||||||
|
let track3 = new AudioTrack({enabled: true});
|
||||||
|
let track4 = new AudioTrack();
|
||||||
|
let list = new AudioTrackList([track, track2]);
|
||||||
|
|
||||||
|
let change = 0;
|
||||||
|
list.on('change', () => change++);
|
||||||
|
track.enabled = true;
|
||||||
|
equal(change, 1, 'one change triggered');
|
||||||
|
|
||||||
|
list.addTrack_(track3);
|
||||||
|
equal(change, 2, 'another change triggered by adding an enabled track');
|
||||||
|
|
||||||
|
track.enabled = true;
|
||||||
|
equal(change, 3, 'another change trigger by changing enabled');
|
||||||
|
|
||||||
|
track.enabled = false;
|
||||||
|
equal(change, 4, 'another change trigger by changing enabled');
|
||||||
|
|
||||||
|
list.addTrack_(track4);
|
||||||
|
equal(change, 4, 'no change triggered by adding a disabled track');
|
||||||
|
|
||||||
|
});
|
118
test/unit/tracks/audio-track.test.js
Normal file
118
test/unit/tracks/audio-track.test.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import AudioTrack from '../../../src/js/tracks/audio-track.js';
|
||||||
|
import {AudioTrackKind} from '../../../src/js/tracks/track-enums.js';
|
||||||
|
import TrackBaseline from './track-baseline';
|
||||||
|
|
||||||
|
q.module('Audio Track');
|
||||||
|
|
||||||
|
// do baseline track testing
|
||||||
|
TrackBaseline(AudioTrack, {
|
||||||
|
id: '1',
|
||||||
|
language: 'en',
|
||||||
|
label: 'English',
|
||||||
|
kind: 'main',
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can create an enabled propert on an AudioTrack', function() {
|
||||||
|
let enabled = true;
|
||||||
|
let track = new AudioTrack({
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
|
equal(track.enabled, enabled, 'enabled value matches what we passed in');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('defaults when items not provided', function() {
|
||||||
|
let track = new AudioTrack();
|
||||||
|
|
||||||
|
equal(track.kind, '', 'kind defaulted to empty string');
|
||||||
|
equal(track.enabled, false, 'enabled defaulted to true since there is one track');
|
||||||
|
equal(track.label, '', 'label defaults to empty string');
|
||||||
|
equal(track.language, '', 'language defaults to empty string');
|
||||||
|
ok(track.id.match(/vjs_track_\d{5}/), 'id defaults to vjs_track_GUID');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('kind can only be one of several options, defaults to empty string', function() {
|
||||||
|
let track = new AudioTrack({
|
||||||
|
kind: 'foo'
|
||||||
|
});
|
||||||
|
|
||||||
|
equal(track.kind, '', 'the kind is set to empty string, not foo');
|
||||||
|
notEqual(track.kind, 'foo', 'the kind is set to empty string, not foo');
|
||||||
|
|
||||||
|
// loop through all possible kinds to verify
|
||||||
|
for (let key in AudioTrackKind) {
|
||||||
|
let currentKind = AudioTrackKind[key];
|
||||||
|
let track = new AudioTrack({
|
||||||
|
kind: currentKind,
|
||||||
|
});
|
||||||
|
equal(track.kind, currentKind, 'the kind is set to ' + currentKind);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enabled can only be instantiated to true or false, defaults to false', function() {
|
||||||
|
let track = new AudioTrack({
|
||||||
|
enabled: 'foo'
|
||||||
|
});
|
||||||
|
|
||||||
|
equal(track.enabled, false, 'the enabled value is set to false, not foo');
|
||||||
|
notEqual(track.enabled, 'foo', 'the enabled value is not set to foo');
|
||||||
|
|
||||||
|
track = new AudioTrack({
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
|
||||||
|
equal(track.enabled, true, 'the enabled value is set to true');
|
||||||
|
|
||||||
|
track = new AudioTrack({
|
||||||
|
enabled: false
|
||||||
|
});
|
||||||
|
|
||||||
|
equal(track.enabled, false, 'the enabled value is set to false');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enabled can only be changed to true or false', function() {
|
||||||
|
let track = new AudioTrack();
|
||||||
|
|
||||||
|
track.enabled = 'foo';
|
||||||
|
notEqual(track.enabled, 'foo', 'enabled not set to invalid value, foo');
|
||||||
|
equal(track.enabled, false, 'enabled remains on the old value, false');
|
||||||
|
|
||||||
|
track.enabled = true;
|
||||||
|
equal(track.enabled, true, 'enabled was set to true');
|
||||||
|
|
||||||
|
track.enabled = 'baz';
|
||||||
|
notEqual(track.enabled, 'baz', 'enabled not set to invalid value, baz');
|
||||||
|
equal(track.enabled, true, 'enabled remains on the old value, true');
|
||||||
|
|
||||||
|
track.enabled = false;
|
||||||
|
equal(track.enabled, false, 'enabled was set to false');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when enabled is changed enabledchange event is fired', function() {
|
||||||
|
let track = new AudioTrack({
|
||||||
|
tech: this.tech,
|
||||||
|
enabled: false
|
||||||
|
});
|
||||||
|
let eventsTriggered = 0;
|
||||||
|
track.addEventListener('enabledchange', () => {
|
||||||
|
eventsTriggered++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// two events
|
||||||
|
track.enabled = true;
|
||||||
|
track.enabled = false;
|
||||||
|
equal(eventsTriggered, 2, 'two enabled changes');
|
||||||
|
|
||||||
|
// no event here
|
||||||
|
track.enabled = false;
|
||||||
|
track.enabled = false;
|
||||||
|
equal(eventsTriggered, 2, 'still two enabled changes');
|
||||||
|
|
||||||
|
// one event
|
||||||
|
track.enabled = true;
|
||||||
|
equal(eventsTriggered, 3, 'three enabled changes');
|
||||||
|
|
||||||
|
// no events
|
||||||
|
track.enabled = true;
|
||||||
|
track.enabled = true;
|
||||||
|
equal(eventsTriggered, 3, 'still three enabled changes');
|
||||||
|
});
|
114
test/unit/tracks/audio-tracks.test.js
Normal file
114
test/unit/tracks/audio-tracks.test.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import AudioTrack from '../../../src/js/tracks/text-track.js';
|
||||||
|
import Html5 from '../../../src/js/tech/html5.js';
|
||||||
|
import Tech from '../../../src/js/tech/tech.js';
|
||||||
|
import Component from '../../../src/js/component.js';
|
||||||
|
|
||||||
|
import * as browser from '../../../src/js/utils/browser.js';
|
||||||
|
import TestHelpers from '../test-helpers.js';
|
||||||
|
import document from 'global/document';
|
||||||
|
|
||||||
|
q.module('Tracks', {
|
||||||
|
setup() {
|
||||||
|
this.clock = sinon.useFakeTimers();
|
||||||
|
},
|
||||||
|
teardown() {
|
||||||
|
this.clock.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Player track methods call the tech', function() {
|
||||||
|
let player;
|
||||||
|
let calls = 0;
|
||||||
|
|
||||||
|
player = TestHelpers.makePlayer();
|
||||||
|
|
||||||
|
player.tech_.audioTracks = function() {
|
||||||
|
calls++;
|
||||||
|
};
|
||||||
|
|
||||||
|
player.audioTracks();
|
||||||
|
|
||||||
|
equal(calls, 1, 'audioTrack defers to the tech');
|
||||||
|
player.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('listen to remove and add track events in native audio tracks', function() {
|
||||||
|
let oldTestVid = Html5.TEST_VID;
|
||||||
|
let player;
|
||||||
|
let options;
|
||||||
|
let oldAudioTracks = Html5.prototype.audioTracks;
|
||||||
|
let events = {};
|
||||||
|
let html;
|
||||||
|
|
||||||
|
Html5.prototype.audioTracks = function() {
|
||||||
|
return {
|
||||||
|
addEventListener(type, handler) {
|
||||||
|
events[type] = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Html5.TEST_VID = {
|
||||||
|
audioTracks: []
|
||||||
|
};
|
||||||
|
|
||||||
|
player = {
|
||||||
|
// Function.prototype is a built-in no-op function.
|
||||||
|
controls() {},
|
||||||
|
ready() {},
|
||||||
|
options() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
addChild() {},
|
||||||
|
id() {},
|
||||||
|
el() {
|
||||||
|
return {
|
||||||
|
insertBefore() {},
|
||||||
|
appendChild() {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
player.player_ = player;
|
||||||
|
player.options_ = options = {};
|
||||||
|
|
||||||
|
html = new Html5(options);
|
||||||
|
|
||||||
|
ok(events.removetrack, 'removetrack listener was added');
|
||||||
|
ok(events.addtrack, 'addtrack listener was added');
|
||||||
|
|
||||||
|
Html5.TEST_VID = oldTestVid;
|
||||||
|
Html5.prototype.audioTracks = oldAudioTracks;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('html5 tech supports native audio tracks if the video supports it', function() {
|
||||||
|
let oldTestVid = Html5.TEST_VID;
|
||||||
|
|
||||||
|
Html5.TEST_VID = {
|
||||||
|
audioTracks: []
|
||||||
|
};
|
||||||
|
|
||||||
|
ok(Html5.supportsNativeAudioTracks(), 'native audio tracks are supported');
|
||||||
|
|
||||||
|
Html5.TEST_VID = oldTestVid;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('html5 tech does not support native audio tracks if the video does not supports it', function() {
|
||||||
|
let oldTestVid = Html5.TEST_VID;
|
||||||
|
Html5.TEST_VID = {};
|
||||||
|
|
||||||
|
ok(!Html5.supportsNativeAudioTracks(), 'native audio tracks are not supported');
|
||||||
|
|
||||||
|
Html5.TEST_VID = oldTestVid;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when switching techs, we should not get a new audio track', function() {
|
||||||
|
let player = TestHelpers.makePlayer();
|
||||||
|
|
||||||
|
player.loadTech_('TechFaker');
|
||||||
|
let firstTracks = player.audioTracks();
|
||||||
|
|
||||||
|
player.loadTech_('TechFaker');
|
||||||
|
let secondTracks = player.audioTracks();
|
||||||
|
|
||||||
|
ok(firstTracks === secondTracks, 'the tracks are equal');
|
||||||
|
});
|
@ -2,155 +2,7 @@ import TextTrackList from '../../../src/js/tracks/text-track-list.js';
|
|||||||
import TextTrack from '../../../src/js/tracks/text-track.js';
|
import TextTrack from '../../../src/js/tracks/text-track.js';
|
||||||
import EventTarget from '../../../src/js/event-target.js';
|
import EventTarget from '../../../src/js/event-target.js';
|
||||||
|
|
||||||
const genericTracks = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
addEventListener() {},
|
|
||||||
off() {}
|
|
||||||
}, {
|
|
||||||
id: '2',
|
|
||||||
addEventListener() {},
|
|
||||||
off() {}
|
|
||||||
}, {
|
|
||||||
id: '3',
|
|
||||||
addEventListener() {},
|
|
||||||
off() {}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
q.module('Text Track List');
|
q.module('Text Track List');
|
||||||
|
|
||||||
test('TextTrackList\'s length is set correctly', function() {
|
|
||||||
let ttl = new TextTrackList(genericTracks);
|
|
||||||
|
|
||||||
equal(ttl.length, genericTracks.length, 'the length is ' + genericTracks.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('can get text tracks by id', function() {
|
|
||||||
let ttl = new TextTrackList(genericTracks);
|
|
||||||
|
|
||||||
equal(ttl.getTrackById('1').id, 1, 'id "1" has id of "1"');
|
|
||||||
equal(ttl.getTrackById('2').id, 2, 'id "2" has id of "2"');
|
|
||||||
equal(ttl.getTrackById('3').id, 3, 'id "3" has id of "3"');
|
|
||||||
ok(!ttl.getTrackById(1), 'there isn\'t an item with "numeric" id of `1`');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('length is updated when new tracks are added or removed', function() {
|
|
||||||
let ttl = new TextTrackList(genericTracks);
|
|
||||||
|
|
||||||
ttl.addTrack_({id: '100', addEventListener() {}, off() {}});
|
|
||||||
equal(ttl.length, genericTracks.length + 1, 'the length is ' + (genericTracks.length + 1));
|
|
||||||
ttl.addTrack_({id: '101', addEventListener() {}, off() {}});
|
|
||||||
equal(ttl.length, genericTracks.length + 2, 'the length is ' + (genericTracks.length + 2));
|
|
||||||
|
|
||||||
ttl.removeTrack_(ttl.getTrackById('101'));
|
|
||||||
equal(ttl.length, genericTracks.length + 1, 'the length is ' + (genericTracks.length + 1));
|
|
||||||
ttl.removeTrack_(ttl.getTrackById('100'));
|
|
||||||
equal(ttl.length, genericTracks.length, 'the length is ' + genericTracks.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('can access items by index', function() {
|
|
||||||
let ttl = new TextTrackList(genericTracks);
|
|
||||||
let i = 0;
|
|
||||||
let length = ttl.length;
|
|
||||||
|
|
||||||
expect(length);
|
|
||||||
|
|
||||||
for (; i < length; i++) {
|
|
||||||
equal(ttl[i].id, String(i + 1), 'the id of a track matches the index + 1');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('can access new items by index', function() {
|
|
||||||
let ttl = new TextTrackList(genericTracks);
|
|
||||||
|
|
||||||
ttl.addTrack_({id: '100', addEventListener() {}});
|
|
||||||
equal(ttl[3].id, '100', 'id of item at index 3 is 100');
|
|
||||||
ttl.addTrack_({id: '101', addEventListener() {}});
|
|
||||||
equal(ttl[4].id, '101', 'id of item at index 4 is 101');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cannot access removed items by index', function() {
|
|
||||||
let ttl = new TextTrackList(genericTracks);
|
|
||||||
|
|
||||||
ttl.addTrack_({id: '100', addEventListener() {}, off() {}});
|
|
||||||
ttl.addTrack_({id: '101', addEventListener() {}, off() {}});
|
|
||||||
equal(ttl[3].id, '100', 'id of item at index 3 is 100');
|
|
||||||
equal(ttl[4].id, '101', 'id of item at index 4 is 101');
|
|
||||||
|
|
||||||
ttl.removeTrack_(ttl.getTrackById('101'));
|
|
||||||
ttl.removeTrack_(ttl.getTrackById('100'));
|
|
||||||
|
|
||||||
ok(!ttl[3], 'nothing at index 3');
|
|
||||||
ok(!ttl[4], 'nothing at index 4');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('new item available at old index', function() {
|
|
||||||
let ttl = new TextTrackList(genericTracks);
|
|
||||||
|
|
||||||
ttl.addTrack_({id: '100', addEventListener() {}, off() {}});
|
|
||||||
equal(ttl[3].id, '100', 'id of item at index 3 is 100');
|
|
||||||
|
|
||||||
ttl.removeTrack_(ttl.getTrackById('100'));
|
|
||||||
ok(!ttl[3], 'nothing at index 3');
|
|
||||||
|
|
||||||
ttl.addTrack_({id: '101', addEventListener() {}});
|
|
||||||
equal(ttl[3].id, '101', 'id of new item at index 3 is now 101');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a "addtrack" event is triggered when new tracks are added', function() {
|
|
||||||
let ttl = new TextTrackList(genericTracks);
|
|
||||||
let tracks = 0;
|
|
||||||
let adds = 0;
|
|
||||||
let addHandler = function(e) {
|
|
||||||
if (e.track) {
|
|
||||||
tracks++;
|
|
||||||
}
|
|
||||||
adds++;
|
|
||||||
};
|
|
||||||
|
|
||||||
ttl.on('addtrack', addHandler);
|
|
||||||
|
|
||||||
ttl.addTrack_({id: '100', addEventListener() {}});
|
|
||||||
ttl.addTrack_({id: '101', addEventListener() {}});
|
|
||||||
|
|
||||||
ttl.off('addtrack', addHandler);
|
|
||||||
|
|
||||||
ttl.onaddtrack = addHandler;
|
|
||||||
|
|
||||||
ttl.addTrack_({id: '102', addEventListener() {}});
|
|
||||||
ttl.addTrack_({id: '103', addEventListener() {}});
|
|
||||||
|
|
||||||
equal(adds, 4, 'we got ' + adds + ' "addtrack" events');
|
|
||||||
equal(tracks, 4, 'we got a track with every event');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a "removetrack" event is triggered when tracks are removed', function() {
|
|
||||||
let ttl = new TextTrackList(genericTracks);
|
|
||||||
let tracks = 0;
|
|
||||||
let rms = 0;
|
|
||||||
let rmHandler = function(e) {
|
|
||||||
if (e.track) {
|
|
||||||
tracks++;
|
|
||||||
}
|
|
||||||
rms++;
|
|
||||||
};
|
|
||||||
|
|
||||||
ttl.on('removetrack', rmHandler);
|
|
||||||
|
|
||||||
ttl.removeTrack_(ttl.getTrackById('1'));
|
|
||||||
ttl.removeTrack_(ttl.getTrackById('2'));
|
|
||||||
|
|
||||||
ttl.off('removetrack', rmHandler);
|
|
||||||
|
|
||||||
ttl.onremovetrack = rmHandler;
|
|
||||||
|
|
||||||
ttl.removeTrack_(ttl.getTrackById('3'));
|
|
||||||
|
|
||||||
equal(rms, 3, 'we got ' + rms + ' "removetrack" events');
|
|
||||||
equal(tracks, 3, 'we got a track with every event');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('trigger "change" event when "modechange" is fired on a track', function() {
|
test('trigger "change" event when "modechange" is fired on a track', function() {
|
||||||
let tt = new EventTarget();
|
let tt = new EventTarget();
|
||||||
let ttl = new TextTrackList([tt]);
|
let ttl = new TextTrackList([tt]);
|
||||||
@ -160,15 +12,12 @@ test('trigger "change" event when "modechange" is fired on a track', function()
|
|||||||
};
|
};
|
||||||
|
|
||||||
ttl.on('change', changeHandler);
|
ttl.on('change', changeHandler);
|
||||||
|
|
||||||
tt.trigger('modechange');
|
tt.trigger('modechange');
|
||||||
|
|
||||||
ttl.off('change', changeHandler);
|
ttl.off('change', changeHandler);
|
||||||
|
|
||||||
ttl.onchange = changeHandler;
|
ttl.onchange = changeHandler;
|
||||||
|
|
||||||
tt.trigger('modechange');
|
tt.trigger('modechange');
|
||||||
|
|
||||||
equal(changes, 2, 'two change events should have fired');
|
equal(changes, 2, 'two change events should have fired');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -185,11 +34,9 @@ test('trigger "change" event when mode changes on a TextTrack', function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ttl.on('change', changeHandler);
|
ttl.on('change', changeHandler);
|
||||||
|
|
||||||
tt.mode = 'showing';
|
tt.mode = 'showing';
|
||||||
|
|
||||||
ttl.off('change', changeHandler);
|
ttl.off('change', changeHandler);
|
||||||
|
|
||||||
ttl.onchange = changeHandler;
|
ttl.onchange = changeHandler;
|
||||||
|
|
||||||
tt.mode = 'hidden';
|
tt.mode = 'hidden';
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import window from 'global/window';
|
import window from 'global/window';
|
||||||
import EventTarget from '../../../src/js/event-target.js';
|
import EventTarget from '../../../src/js/event-target.js';
|
||||||
|
import TrackBaseline from './track-baseline';
|
||||||
|
import TechFaker from '../tech/tech-faker';
|
||||||
import TextTrack from '../../../src/js/tracks/text-track.js';
|
import TextTrack from '../../../src/js/tracks/text-track.js';
|
||||||
import TestHelpers from '../test-helpers.js';
|
import TestHelpers from '../test-helpers.js';
|
||||||
import log from '../../../src/js/utils/log.js';
|
import log from '../../../src/js/utils/log.js';
|
||||||
@ -13,40 +15,38 @@ const defaultTech = {
|
|||||||
off() {},
|
off() {},
|
||||||
currentTime() {}
|
currentTime() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
q.module('Text Track');
|
q.module('Text Track');
|
||||||
|
|
||||||
test('text-track requires a tech', function() {
|
// do baseline track testing
|
||||||
let error = new Error('A tech was not provided.');
|
TrackBaseline(TextTrack, {
|
||||||
|
id: '1',
|
||||||
q.throws(() => new TextTrack(), error, 'a tech is required for text track');
|
kind: 'subtitles',
|
||||||
|
mode: 'disabled',
|
||||||
|
label: 'English',
|
||||||
|
language: 'en',
|
||||||
|
tech: defaultTech
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can create a TextTrack with various properties', function() {
|
test('requires a tech', function() {
|
||||||
let kind = 'captions';
|
let error = new Error('A tech was not provided.');
|
||||||
let label = 'English';
|
|
||||||
let language = 'en';
|
q.throws(() => new TextTrack({}), error, 'a tech is required');
|
||||||
let id = '1';
|
q.throws(() => new TextTrack({tech: null}), error, 'a tech is required');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can create a TextTrack with a mode property', function() {
|
||||||
let mode = 'disabled';
|
let mode = 'disabled';
|
||||||
let tt = new TextTrack({
|
let tt = new TextTrack({
|
||||||
kind,
|
|
||||||
label,
|
|
||||||
language,
|
|
||||||
id,
|
|
||||||
mode,
|
mode,
|
||||||
tech: defaultTech
|
tech: defaultTech
|
||||||
});
|
});
|
||||||
|
|
||||||
equal(tt.kind, kind, 'we have a kind');
|
|
||||||
equal(tt.label, label, 'we have a label');
|
|
||||||
equal(tt.language, language, 'we have a language');
|
|
||||||
equal(tt.id, id, 'we have a id');
|
|
||||||
equal(tt.mode, mode, 'we have a mode');
|
equal(tt.mode, mode, 'we have a mode');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('defaults when items not provided', function() {
|
test('defaults when items not provided', function() {
|
||||||
let tt = new TextTrack({
|
let tt = new TextTrack({
|
||||||
tech: defaultTech
|
tech: TechFaker
|
||||||
});
|
});
|
||||||
|
|
||||||
equal(tt.kind, 'subtitles', 'kind defaulted to subtitles');
|
equal(tt.kind, 'subtitles', 'kind defaulted to subtitles');
|
||||||
@ -131,32 +131,16 @@ test('mode can only be one of several options, defaults to disabled', function()
|
|||||||
equal(tt.mode, 'showing', 'the mode is set to showing');
|
equal(tt.mode, 'showing', 'the mode is set to showing');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('kind, label, language, id, cue, and activeCues are read only', function() {
|
test('cue and activeCues are read only', function() {
|
||||||
let kind = 'captions';
|
|
||||||
let label = 'English';
|
|
||||||
let language = 'en';
|
|
||||||
let id = '1';
|
|
||||||
let mode = 'disabled';
|
let mode = 'disabled';
|
||||||
let tt = new TextTrack({
|
let tt = new TextTrack({
|
||||||
kind,
|
|
||||||
label,
|
|
||||||
language,
|
|
||||||
id,
|
|
||||||
mode,
|
mode,
|
||||||
tech: defaultTech
|
tech: defaultTech,
|
||||||
});
|
});
|
||||||
|
|
||||||
tt.kind = 'subtitles';
|
|
||||||
tt.label = 'Spanish';
|
|
||||||
tt.language = 'es';
|
|
||||||
tt.id = '2';
|
|
||||||
tt.cues = 'foo';
|
tt.cues = 'foo';
|
||||||
tt.activeCues = 'bar';
|
tt.activeCues = 'bar';
|
||||||
|
|
||||||
equal(tt.kind, kind, 'kind is still set to captions');
|
|
||||||
equal(tt.label, label, 'label is still set to English');
|
|
||||||
equal(tt.language, language, 'language is still set to en');
|
|
||||||
equal(tt.id, id, 'id is still set to \'1\'');
|
|
||||||
notEqual(tt.cues, 'foo', 'cues is still original value');
|
notEqual(tt.cues, 'foo', 'cues is still original value');
|
||||||
notEqual(tt.activeCues, 'bar', 'activeCues is still original value');
|
notEqual(tt.activeCues, 'bar', 'activeCues is still original value');
|
||||||
});
|
});
|
||||||
|
43
test/unit/tracks/track-baseline.js
Normal file
43
test/unit/tracks/track-baseline.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import * as browser from '../../../src/js/utils/browser.js';
|
||||||
|
import document from 'global/document';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests baseline functionality for all tracks
|
||||||
|
*
|
||||||
|
# @param {Track} TrackClass the track class object to use for testing
|
||||||
|
# @param {Object} options the options to setup a track with
|
||||||
|
*/
|
||||||
|
const TrackBaseline = function(TrackClass, options) {
|
||||||
|
|
||||||
|
test('is setup with id, kind, label, and language', function() {
|
||||||
|
let track = new TrackClass(options);
|
||||||
|
equal(track.kind, options.kind, 'we have a kind');
|
||||||
|
equal(track.label, options.label, 'we have a label');
|
||||||
|
equal(track.language, options.language, 'we have a language');
|
||||||
|
equal(track.id, options.id, 'we have a id');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('kind, label, language, id, are read only', function() {
|
||||||
|
let track = new TrackClass(options);
|
||||||
|
track.kind = 'subtitles';
|
||||||
|
track.label = 'Spanish';
|
||||||
|
track.language = 'es';
|
||||||
|
track.id = '2';
|
||||||
|
|
||||||
|
equal(track.kind, options.kind, 'we have a kind');
|
||||||
|
equal(track.label, options.label, 'we have a label');
|
||||||
|
equal(track.language, options.language, 'we have a language');
|
||||||
|
equal(track.id, options.id, 'we have an id');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns an instance of itself on non ie8 browsers', function() {
|
||||||
|
let track = new TrackClass(options);
|
||||||
|
if (browser.IS_IE8) {
|
||||||
|
ok(track, 'returns an object on ie8');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ok(track instanceof TrackClass, 'returns an instance');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TrackBaseline;
|
143
test/unit/tracks/track-list.test.js
Normal file
143
test/unit/tracks/track-list.test.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import TrackList from '../../../src/js/tracks/track-list.js';
|
||||||
|
import EventTarget from '../../../src/js/event-target.js';
|
||||||
|
|
||||||
|
const newTrack = function(id) {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
addEventListener() {},
|
||||||
|
off() {},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
q.module('Track List', {
|
||||||
|
beforeEach() {
|
||||||
|
this.tracks = [newTrack('1'), newTrack('2'), newTrack('3')];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TrackList\'s length is set correctly', function() {
|
||||||
|
let trackList = new TrackList(this.tracks);
|
||||||
|
|
||||||
|
equal(trackList.length, this.tracks.length, 'length is ' + this.tracks.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can get tracks by int and string id', function() {
|
||||||
|
let trackList = new TrackList(this.tracks);
|
||||||
|
|
||||||
|
equal(trackList.getTrackById('1').id, '1', 'id "1" has id of "1"');
|
||||||
|
equal(trackList.getTrackById('2').id, '2', 'id "2" has id of "2"');
|
||||||
|
equal(trackList.getTrackById('3').id, '3', 'id "3" has id of "3"');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('length is updated when new tracks are added or removed', function() {
|
||||||
|
let trackList = new TrackList(this.tracks);
|
||||||
|
|
||||||
|
trackList.addTrack_(newTrack('100'));
|
||||||
|
equal(trackList.length, this.tracks.length + 1, 'the length is ' + (this.tracks.length + 1));
|
||||||
|
trackList.addTrack_(newTrack('101'));
|
||||||
|
equal(trackList.length, this.tracks.length + 2, 'the length is ' + (this.tracks.length + 2));
|
||||||
|
|
||||||
|
trackList.removeTrack_(trackList.getTrackById('101'));
|
||||||
|
equal(trackList.length, this.tracks.length + 1, 'the length is ' + (this.tracks.length + 1));
|
||||||
|
trackList.removeTrack_(trackList.getTrackById('100'));
|
||||||
|
equal(trackList.length, this.tracks.length, 'the length is ' + this.tracks.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can access items by index', function() {
|
||||||
|
let trackList = new TrackList(this.tracks);
|
||||||
|
let length = trackList.length;
|
||||||
|
|
||||||
|
expect(length);
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
equal(trackList[i].id, String(i + 1), 'the id of a track matches the index + 1');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can access new items by index', function() {
|
||||||
|
let trackList = new TrackList(this.tracks);
|
||||||
|
|
||||||
|
trackList.addTrack_(newTrack('100'));
|
||||||
|
equal(trackList[3].id, '100', 'id of item at index 3 is 100');
|
||||||
|
|
||||||
|
trackList.addTrack_(newTrack('101'));
|
||||||
|
equal(trackList[4].id, '101', 'id of item at index 4 is 101');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cannot access removed items by index', function() {
|
||||||
|
let trackList = new TrackList(this.tracks);
|
||||||
|
|
||||||
|
trackList.addTrack_(newTrack('100'));
|
||||||
|
trackList.addTrack_(newTrack('101'));
|
||||||
|
equal(trackList[3].id, '100', 'id of item at index 3 is 100');
|
||||||
|
equal(trackList[4].id, '101', 'id of item at index 4 is 101');
|
||||||
|
|
||||||
|
trackList.removeTrack_(trackList.getTrackById('101'));
|
||||||
|
trackList.removeTrack_(trackList.getTrackById('100'));
|
||||||
|
|
||||||
|
ok(!trackList[3], 'nothing at index 3');
|
||||||
|
ok(!trackList[4], 'nothing at index 4');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('new item available at old index', function() {
|
||||||
|
let trackList = new TrackList(this.tracks);
|
||||||
|
|
||||||
|
trackList.addTrack_(newTrack('100'));
|
||||||
|
equal(trackList[3].id, '100', 'id of item at index 3 is 100');
|
||||||
|
|
||||||
|
trackList.removeTrack_(trackList.getTrackById('100'));
|
||||||
|
ok(!trackList[3], 'nothing at index 3');
|
||||||
|
|
||||||
|
trackList.addTrack_(newTrack('101'));
|
||||||
|
equal(trackList[3].id, '101', 'id of new item at index 3 is now 101');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a "addtrack" event is triggered when new tracks are added', function() {
|
||||||
|
let trackList = new TrackList(this.tracks);
|
||||||
|
let tracks = 0;
|
||||||
|
let adds = 0;
|
||||||
|
let addHandler = (e) => {
|
||||||
|
if (e.track) {
|
||||||
|
tracks++;
|
||||||
|
}
|
||||||
|
adds++;
|
||||||
|
};
|
||||||
|
|
||||||
|
trackList.on('addtrack', addHandler);
|
||||||
|
|
||||||
|
trackList.addTrack_(newTrack('100'));
|
||||||
|
trackList.addTrack_(newTrack('101'));
|
||||||
|
|
||||||
|
trackList.off('addtrack', addHandler);
|
||||||
|
trackList.onaddtrack = addHandler;
|
||||||
|
|
||||||
|
trackList.addTrack_(newTrack('102'));
|
||||||
|
trackList.addTrack_(newTrack('103'));
|
||||||
|
|
||||||
|
equal(adds, 4, 'we got ' + adds + ' "addtrack" events');
|
||||||
|
equal(tracks, 4, 'we got a track with every event');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a "removetrack" event is triggered when tracks are removed', function() {
|
||||||
|
let trackList = new TrackList(this.tracks);
|
||||||
|
let tracks = 0;
|
||||||
|
let rms = 0;
|
||||||
|
let rmHandler = (e) => {
|
||||||
|
if (e.track) {
|
||||||
|
tracks++;
|
||||||
|
}
|
||||||
|
rms++;
|
||||||
|
};
|
||||||
|
|
||||||
|
trackList.on('removetrack', rmHandler);
|
||||||
|
trackList.removeTrack_(trackList.getTrackById('1'));
|
||||||
|
trackList.removeTrack_(trackList.getTrackById('2'));
|
||||||
|
|
||||||
|
trackList.off('removetrack', rmHandler);
|
||||||
|
trackList.onremovetrack = rmHandler;
|
||||||
|
trackList.removeTrack_(trackList.getTrackById('3'));
|
||||||
|
|
||||||
|
equal(rms, 3, 'we got ' + rms + ' "removetrack" events');
|
||||||
|
equal(tracks, 3, 'we got a track with every event');
|
||||||
|
});
|
34
test/unit/tracks/track.test.js
Normal file
34
test/unit/tracks/track.test.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import TechFaker from '../tech/tech-faker';
|
||||||
|
import TrackBaseline from './track-baseline';
|
||||||
|
import Track from '../../../src/js/tracks/track.js';
|
||||||
|
import * as browser from '../../../src/js/utils/browser.js';
|
||||||
|
|
||||||
|
const defaultTech = {
|
||||||
|
textTracks() {},
|
||||||
|
on() {},
|
||||||
|
off() {},
|
||||||
|
currentTime() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// do baseline track testing
|
||||||
|
q.module('Track');
|
||||||
|
|
||||||
|
TrackBaseline(Track, {
|
||||||
|
id: '1',
|
||||||
|
kind: 'subtitles',
|
||||||
|
mode: 'disabled',
|
||||||
|
label: 'English',
|
||||||
|
language: 'en',
|
||||||
|
tech: new TechFaker()
|
||||||
|
});
|
||||||
|
|
||||||
|
test('defaults when items not provided', function() {
|
||||||
|
let track = new Track({
|
||||||
|
tech: defaultTech
|
||||||
|
});
|
||||||
|
|
||||||
|
equal(track.kind, '', 'kind defaulted to empty string');
|
||||||
|
equal(track.label, '', 'label defaults to empty string');
|
||||||
|
equal(track.language, '', 'language defaults to empty string');
|
||||||
|
ok(track.id.match(/vjs_track_\d{5}/), 'id defaults to vjs_track_GUID');
|
||||||
|
});
|
96
test/unit/tracks/video-track-list.test.js
Normal file
96
test/unit/tracks/video-track-list.test.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import VideoTrackList from '../../../src/js/tracks/video-track-list.js';
|
||||||
|
import VideoTrack from '../../../src/js/tracks/video-track.js';
|
||||||
|
import EventTarget from '../../../src/js/event-target.js';
|
||||||
|
|
||||||
|
q.module('Video Track List');
|
||||||
|
|
||||||
|
test('trigger "change" when "selectedchange" is fired on a track', function() {
|
||||||
|
let track = new EventTarget();
|
||||||
|
track.loaded_ = true;
|
||||||
|
let audioTrackList = new VideoTrackList([track]);
|
||||||
|
let changes = 0;
|
||||||
|
let changeHandler = function() {
|
||||||
|
changes++;
|
||||||
|
};
|
||||||
|
audioTrackList.on('change', changeHandler);
|
||||||
|
track.trigger('selectedchange');
|
||||||
|
equal(changes, 1, 'one change events for trigger');
|
||||||
|
|
||||||
|
audioTrackList.off('change', changeHandler);
|
||||||
|
audioTrackList.onchange = changeHandler;
|
||||||
|
|
||||||
|
track.trigger('selectedchange');
|
||||||
|
equal(changes, 2, 'one change events for another trigger');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('only one track is ever selected', function() {
|
||||||
|
let track = new VideoTrack({selected: true});
|
||||||
|
let track2 = new VideoTrack({selected: true});
|
||||||
|
let track3 = new VideoTrack({selected: true});
|
||||||
|
let track4 = new VideoTrack();
|
||||||
|
let list = new VideoTrackList([track, track2]);
|
||||||
|
equal(track.selected, false, 'track is unselected');
|
||||||
|
equal(track2.selected, true, 'track2 is selected');
|
||||||
|
|
||||||
|
track.selected = true;
|
||||||
|
equal(track.selected, true, 'track is selected');
|
||||||
|
equal(track2.selected, false, 'track2 is unselected');
|
||||||
|
|
||||||
|
list.addTrack_(track3);
|
||||||
|
equal(track.selected, false, 'track is unselected');
|
||||||
|
equal(track2.selected, false, 'track2 is unselected');
|
||||||
|
equal(track3.selected, true, 'track3 is selected');
|
||||||
|
|
||||||
|
track.selected = true;
|
||||||
|
equal(track.selected, true, 'track is unselected');
|
||||||
|
equal(track2.selected, false, 'track2 is unselected');
|
||||||
|
equal(track3.selected, false, 'track3 is unselected');
|
||||||
|
|
||||||
|
list.addTrack_(track4);
|
||||||
|
equal(track.selected, true, 'track is selected');
|
||||||
|
equal(track2.selected, false, 'track2 is unselected');
|
||||||
|
equal(track3.selected, false, 'track3 is unselected');
|
||||||
|
equal(track4.selected, false, 'track4 is unselected');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('all tracks can be unselected', function() {
|
||||||
|
let track = new VideoTrack();
|
||||||
|
let track2 = new VideoTrack();
|
||||||
|
let list = new VideoTrackList([track, track2]);
|
||||||
|
equal(track.selected, false, 'track is unselected');
|
||||||
|
equal(track2.selected, false, 'track2 is unselected');
|
||||||
|
|
||||||
|
track.selected = true;
|
||||||
|
equal(track.selected, true, 'track is selected');
|
||||||
|
equal(track2.selected, false, 'track2 is unselected');
|
||||||
|
|
||||||
|
track.selected = false;
|
||||||
|
equal(track.selected, false, 'track is unselected');
|
||||||
|
equal(track2.selected, false, 'track2 is unselected');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('trigger a change event per selected change', function() {
|
||||||
|
let track = new VideoTrack({selected: true});
|
||||||
|
let track2 = new VideoTrack({selected: true});
|
||||||
|
let track3 = new VideoTrack({selected: true});
|
||||||
|
let track4 = new VideoTrack();
|
||||||
|
let list = new VideoTrackList([track, track2]);
|
||||||
|
|
||||||
|
let change = 0;
|
||||||
|
list.on('change', () => change++);
|
||||||
|
track.selected = true;
|
||||||
|
equal(change, 1, 'one change triggered');
|
||||||
|
|
||||||
|
list.addTrack_(track3);
|
||||||
|
equal(change, 2, 'another change triggered by adding an selected track');
|
||||||
|
|
||||||
|
track.selected = true;
|
||||||
|
equal(change, 3, 'another change trigger by changing selected');
|
||||||
|
|
||||||
|
track.selected = false;
|
||||||
|
equal(change, 4, 'another change trigger by changing selected');
|
||||||
|
|
||||||
|
list.addTrack_(track4);
|
||||||
|
equal(change, 4, 'no change triggered by adding a unselected track');
|
||||||
|
});
|
111
test/unit/tracks/video-track.test.js
Normal file
111
test/unit/tracks/video-track.test.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import VideoTrack from '../../../src/js/tracks/video-track';
|
||||||
|
import VideoTrackList from '../../../src/js/tracks/video-track-list';
|
||||||
|
import {VideoTrackKind} from '../../../src/js/tracks/track-enums';
|
||||||
|
import TrackBaseline from './track-baseline';
|
||||||
|
|
||||||
|
q.module('Video Track');
|
||||||
|
|
||||||
|
// do baseline track testing
|
||||||
|
TrackBaseline(VideoTrack, {
|
||||||
|
id: '1',
|
||||||
|
language: 'en',
|
||||||
|
label: 'English',
|
||||||
|
kind: 'main',
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can create an VideoTrack a selected property', function() {
|
||||||
|
let selected = true;
|
||||||
|
let track = new VideoTrack({
|
||||||
|
selected,
|
||||||
|
});
|
||||||
|
equal(track.selected, selected, 'selected value matches what we passed in');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('defaults when items not provided', function() {
|
||||||
|
let track = new VideoTrack();
|
||||||
|
|
||||||
|
equal(track.kind, '', 'kind defaulted to empty string');
|
||||||
|
equal(track.selected, false, 'selected defaulted to true since there is one track');
|
||||||
|
equal(track.label, '', 'label defaults to empty string');
|
||||||
|
equal(track.language, '', 'language defaults to empty string');
|
||||||
|
ok(track.id.match(/vjs_track_\d{5}/), 'id defaults to vjs_track_GUID');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('kind can only be one of several options, defaults to empty string', function() {
|
||||||
|
let track = new VideoTrack({
|
||||||
|
kind: 'foo'
|
||||||
|
});
|
||||||
|
|
||||||
|
equal(track.kind, '', 'the kind is set to empty string, not foo');
|
||||||
|
notEqual(track.kind, 'foo', 'the kind is set to empty string, not foo');
|
||||||
|
|
||||||
|
// loop through all possible kinds to verify
|
||||||
|
for (let key in VideoTrackKind) {
|
||||||
|
let currentKind = VideoTrackKind[key];
|
||||||
|
let track = new VideoTrack({kind: currentKind});
|
||||||
|
equal(track.kind, currentKind, 'the kind is set to ' + currentKind);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selected can only be instantiated to true or false, defaults to false', function() {
|
||||||
|
let track = new VideoTrack({
|
||||||
|
selected: 'foo'
|
||||||
|
});
|
||||||
|
|
||||||
|
equal(track.selected, false, 'the selected value is set to false, not foo');
|
||||||
|
notEqual(track.selected, 'foo', 'the selected value is not set to foo');
|
||||||
|
|
||||||
|
track = new VideoTrack({
|
||||||
|
selected: true
|
||||||
|
});
|
||||||
|
|
||||||
|
equal(track.selected, true, 'the selected value is set to true');
|
||||||
|
|
||||||
|
track = new VideoTrack({
|
||||||
|
selected: false
|
||||||
|
});
|
||||||
|
|
||||||
|
equal(track.selected, false, 'the selected value is set to false');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selected can only be changed to true or false', function() {
|
||||||
|
let track = new VideoTrack();
|
||||||
|
|
||||||
|
track.selected = 'foo';
|
||||||
|
notEqual(track.selected, 'foo', 'selected not set to invalid value, foo');
|
||||||
|
equal(track.selected, false, 'selected remains on the old value, false');
|
||||||
|
|
||||||
|
track.selected = true;
|
||||||
|
equal(track.selected, true, 'selected was set to true');
|
||||||
|
|
||||||
|
track.selected = 'baz';
|
||||||
|
notEqual(track.selected, 'baz', 'selected not set to invalid value, baz');
|
||||||
|
equal(track.selected, true, 'selected remains on the old value, true');
|
||||||
|
|
||||||
|
track.selected = false;
|
||||||
|
equal(track.selected, false, 'selected was set to false');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when selected is changed selectedchange event is fired', function() {
|
||||||
|
let track = new VideoTrack({
|
||||||
|
selected: false
|
||||||
|
});
|
||||||
|
let eventsTriggered = 0;
|
||||||
|
track.addEventListener('selectedchange', () => {
|
||||||
|
eventsTriggered++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// two events
|
||||||
|
track.selected = true;
|
||||||
|
track.selected = false;
|
||||||
|
equal(eventsTriggered, 2, 'two selected changes');
|
||||||
|
|
||||||
|
// no event here
|
||||||
|
track.selected = false;
|
||||||
|
track.selected = false;
|
||||||
|
equal(eventsTriggered, 2, 'still two selected changes');
|
||||||
|
|
||||||
|
// one event
|
||||||
|
track.selected = true;
|
||||||
|
equal(eventsTriggered, 3, 'three selected changes');
|
||||||
|
});
|
114
test/unit/tracks/video-tracks.test.js
Normal file
114
test/unit/tracks/video-tracks.test.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import VideoTrack from '../../../src/js/tracks/video-track.js';
|
||||||
|
import Html5 from '../../../src/js/tech/html5.js';
|
||||||
|
import Tech from '../../../src/js/tech/tech.js';
|
||||||
|
import Component from '../../../src/js/component.js';
|
||||||
|
|
||||||
|
import * as browser from '../../../src/js/utils/browser.js';
|
||||||
|
import TestHelpers from '../test-helpers.js';
|
||||||
|
import document from 'global/document';
|
||||||
|
|
||||||
|
q.module('Video Tracks', {
|
||||||
|
setup() {
|
||||||
|
this.clock = sinon.useFakeTimers();
|
||||||
|
},
|
||||||
|
teardown() {
|
||||||
|
this.clock.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Player track methods call the tech', function() {
|
||||||
|
let player;
|
||||||
|
let calls = 0;
|
||||||
|
|
||||||
|
player = TestHelpers.makePlayer();
|
||||||
|
|
||||||
|
player.tech_.videoTracks = function() {
|
||||||
|
calls++;
|
||||||
|
};
|
||||||
|
|
||||||
|
player.videoTracks();
|
||||||
|
|
||||||
|
equal(calls, 1, 'videoTrack defers to the tech');
|
||||||
|
player.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('listen to remove and add track events in native video tracks', function() {
|
||||||
|
let oldTestVid = Html5.TEST_VID;
|
||||||
|
let player;
|
||||||
|
let options;
|
||||||
|
let oldVideoTracks = Html5.prototype.videoTracks;
|
||||||
|
let events = {};
|
||||||
|
let html;
|
||||||
|
|
||||||
|
Html5.prototype.videoTracks = function() {
|
||||||
|
return {
|
||||||
|
addEventListener(type, handler) {
|
||||||
|
events[type] = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Html5.TEST_VID = {
|
||||||
|
videoTracks: []
|
||||||
|
};
|
||||||
|
|
||||||
|
player = {
|
||||||
|
// Function.prototype is a built-in no-op function.
|
||||||
|
controls() {},
|
||||||
|
ready() {},
|
||||||
|
options() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
addChild() {},
|
||||||
|
id() {},
|
||||||
|
el() {
|
||||||
|
return {
|
||||||
|
insertBefore() {},
|
||||||
|
appendChild() {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
player.player_ = player;
|
||||||
|
player.options_ = options = {};
|
||||||
|
|
||||||
|
html = new Html5(options);
|
||||||
|
|
||||||
|
ok(events.removetrack, 'removetrack listener was added');
|
||||||
|
ok(events.addtrack, 'addtrack listener was added');
|
||||||
|
|
||||||
|
Html5.TEST_VID = oldTestVid;
|
||||||
|
Html5.prototype.videoTracks = oldVideoTracks;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('html5 tech supports native video tracks if the video supports it', function() {
|
||||||
|
let oldTestVid = Html5.TEST_VID;
|
||||||
|
|
||||||
|
Html5.TEST_VID = {
|
||||||
|
videoTracks: []
|
||||||
|
};
|
||||||
|
|
||||||
|
ok(Html5.supportsNativeVideoTracks(), 'native video tracks are supported');
|
||||||
|
|
||||||
|
Html5.TEST_VID = oldTestVid;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('html5 tech does not support native video tracks if the video does not supports it', function() {
|
||||||
|
let oldTestVid = Html5.TEST_VID;
|
||||||
|
Html5.TEST_VID = {};
|
||||||
|
|
||||||
|
ok(!Html5.supportsNativeVideoTracks(), 'native video tracks are not supported');
|
||||||
|
|
||||||
|
Html5.TEST_VID = oldTestVid;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when switching techs, we should not get a new video track', function() {
|
||||||
|
let player = TestHelpers.makePlayer();
|
||||||
|
|
||||||
|
player.loadTech_('TechFaker');
|
||||||
|
let firstTracks = player.videoTracks();
|
||||||
|
|
||||||
|
player.loadTech_('TechFaker');
|
||||||
|
let secondTracks = player.videoTracks();
|
||||||
|
|
||||||
|
ok(firstTracks === secondTracks, 'the tracks are equal');
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user