mirror of
https://github.com/videojs/video.js.git
synced 2024-12-23 02:04:34 +02:00
691 lines
19 KiB
JavaScript
691 lines
19 KiB
JavaScript
/* eslint-env qunit */
|
|
import ChaptersButton from '../../../src/js/control-bar/text-track-controls/chapters-button.js';
|
|
import DescriptionsButton from '../../../src/js/control-bar/text-track-controls/descriptions-button.js';
|
|
import SubtitlesButton from '../../../src/js/control-bar/text-track-controls/subtitles-button.js';
|
|
import CaptionsButton from '../../../src/js/control-bar/text-track-controls/captions-button.js';
|
|
import SubsCapsButton from '../../../src/js/control-bar/text-track-controls/subs-caps-button.js';
|
|
|
|
import TextTrack from '../../../src/js/tracks/text-track.js';
|
|
import TextTrackDisplay from '../../../src/js/tracks/text-track-display.js';
|
|
import Html5 from '../../../src/js/tech/html5.js';
|
|
import Tech from '../../../src/js/tech/tech.js';
|
|
|
|
import * as browser from '../../../src/js/utils/browser.js';
|
|
import TestHelpers from '../test-helpers.js';
|
|
import document from 'global/document';
|
|
import sinon from 'sinon';
|
|
|
|
QUnit.module('Text Tracks', {
|
|
beforeEach(assert) {
|
|
this.clock = sinon.useFakeTimers();
|
|
},
|
|
afterEach(assert) {
|
|
this.clock.restore();
|
|
}
|
|
});
|
|
|
|
QUnit.test('should place title list item into ul', function(assert) {
|
|
const player = TestHelpers.makePlayer();
|
|
const chaptersButton = new ChaptersButton(player);
|
|
|
|
const menuContentElement = chaptersButton.el().getElementsByTagName('UL')[0];
|
|
const titleElement = menuContentElement.children[0];
|
|
|
|
assert.ok(titleElement.innerHTML === 'Chapters', 'title element placed in ul');
|
|
|
|
player.dispose();
|
|
chaptersButton.dispose();
|
|
});
|
|
|
|
QUnit.test('Player track methods call the tech', function(assert) {
|
|
const player = TestHelpers.makePlayer();
|
|
let calls = 0;
|
|
|
|
player.tech_.textTracks = function() {
|
|
calls++;
|
|
};
|
|
player.tech_.addTextTrack = function() {
|
|
calls++;
|
|
};
|
|
|
|
player.addTextTrack();
|
|
player.textTracks();
|
|
|
|
assert.equal(calls, 2, 'both textTrack and addTextTrack defer to the tech');
|
|
|
|
player.dispose();
|
|
});
|
|
|
|
QUnit.test('TextTrackDisplay initializes tracks on player ready', function(assert) {
|
|
let calls = 0;
|
|
const player = TestHelpers.makePlayer();
|
|
|
|
player.addTextTrack = () => calls--;
|
|
player.getChild = () => calls--;
|
|
player.ready = () => calls++;
|
|
|
|
const ttd = new TextTrackDisplay(player, {});
|
|
|
|
assert.equal(calls, 1, 'only a player.ready call was made');
|
|
|
|
ttd.dispose();
|
|
player.dispose();
|
|
});
|
|
|
|
QUnit.test('listen to remove and add track events in native text tracks', function(assert) {
|
|
const oldTestVid = Html5.TEST_VID;
|
|
const oldTextTracks = Html5.prototype.textTracks;
|
|
const events = {};
|
|
|
|
Html5.prototype.textTracks = function() {
|
|
return {
|
|
removeEventListener() {},
|
|
addEventListener(type, handler) {
|
|
events[type] = true;
|
|
}
|
|
};
|
|
};
|
|
|
|
Html5.TEST_VID = {
|
|
textTracks: []
|
|
};
|
|
|
|
const 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_ = {};
|
|
|
|
const html = new Html5({});
|
|
|
|
assert.ok(events.removetrack, 'removetrack listener was added');
|
|
assert.ok(events.addtrack, 'addtrack listener was added');
|
|
|
|
Html5.TEST_VID = oldTestVid;
|
|
Html5.prototype.textTracks = oldTextTracks;
|
|
|
|
html.dispose();
|
|
});
|
|
|
|
QUnit.test('update texttrack buttons on removetrack or addtrack', function(assert) {
|
|
let update = 0;
|
|
const events = {};
|
|
const oldCaptionsUpdate = CaptionsButton.prototype.update;
|
|
const oldSubsUpdate = SubtitlesButton.prototype.update;
|
|
const oldDescriptionsUpdate = DescriptionsButton.prototype.update;
|
|
const oldChaptersUpdate = ChaptersButton.prototype.update;
|
|
const oldSubsCapsUpdate = SubsCapsButton.prototype.update;
|
|
|
|
CaptionsButton.prototype.update = function() {
|
|
update++;
|
|
oldCaptionsUpdate.call(this);
|
|
};
|
|
SubtitlesButton.prototype.update = function() {
|
|
update++;
|
|
oldSubsUpdate.call(this);
|
|
};
|
|
DescriptionsButton.prototype.update = function() {
|
|
update++;
|
|
oldDescriptionsUpdate.call(this);
|
|
};
|
|
ChaptersButton.prototype.update = function() {
|
|
update++;
|
|
oldChaptersUpdate.call(this);
|
|
};
|
|
SubsCapsButton.prototype.update = function() {
|
|
update++;
|
|
oldSubsCapsUpdate.call(this);
|
|
};
|
|
|
|
const oldFeaturesNativeTextTracks = Tech.prototype.featuresNativeTextTracks;
|
|
const oldTextTracks = Tech.prototype.textTracks;
|
|
|
|
Tech.prototype.featuresNativeTextTracks = true;
|
|
Tech.prototype.textTracks = function() {
|
|
return {
|
|
length: 0,
|
|
addEventListener(type, handler) {
|
|
if (!events[type]) {
|
|
events[type] = [];
|
|
}
|
|
events[type].push(handler);
|
|
},
|
|
// Required in player.dispose()
|
|
removeEventListener() {}
|
|
};
|
|
};
|
|
|
|
const tag = document.createElement('video');
|
|
const track1 = document.createElement('track');
|
|
const track2 = document.createElement('track');
|
|
|
|
track1.kind = 'captions';
|
|
track1.label = 'en';
|
|
track1.language = 'English';
|
|
track1.src = '#en.vtt';
|
|
tag.appendChild(track1);
|
|
|
|
track2.kind = 'captions';
|
|
track2.label = 'es';
|
|
track2.language = 'Spanish';
|
|
track2.src = '#es.vtt';
|
|
tag.appendChild(track2);
|
|
|
|
const player = TestHelpers.makePlayer({
|
|
controlBar: {
|
|
captionsButton: true,
|
|
subtitlesButton: true
|
|
}
|
|
}, tag);
|
|
|
|
player.player_ = player;
|
|
|
|
assert.equal(update, 5, 'update was called on the five buttons during init');
|
|
|
|
for (let i = 0; i < events.removetrack.length; i++) {
|
|
events.removetrack[i]();
|
|
}
|
|
|
|
assert.equal(update, 10, 'update was called on the five buttons for remove track');
|
|
|
|
for (let i = 0; i < events.addtrack.length; i++) {
|
|
events.addtrack[i]();
|
|
}
|
|
|
|
assert.equal(update, 15, 'update was called on the five buttons for remove track');
|
|
|
|
Tech.prototype.textTracks = oldTextTracks;
|
|
Tech.prototype.featuresNativeTextTracks = oldFeaturesNativeTextTracks;
|
|
CaptionsButton.prototype.update = oldCaptionsUpdate;
|
|
SubtitlesButton.prototype.update = oldSubsUpdate;
|
|
ChaptersButton.prototype.update = oldChaptersUpdate;
|
|
SubsCapsButton.prototype.update = oldSubsCapsUpdate;
|
|
DescriptionsButton.prototype.update = oldDescriptionsUpdate;
|
|
|
|
player.dispose();
|
|
});
|
|
|
|
QUnit.test('emulated tracks are always used, except in safari', function(assert) {
|
|
const oldTestVid = Html5.TEST_VID;
|
|
const oldIsAnySafari = browser.IS_ANY_SAFARI;
|
|
|
|
Html5.TEST_VID = {
|
|
textTracks: []
|
|
};
|
|
|
|
browser.stub_IS_ANY_SAFARI(false);
|
|
|
|
assert.ok(!Html5.supportsNativeTextTracks(), 'Html5 does not support native text tracks, in non-safari');
|
|
|
|
browser.stub_IS_ANY_SAFARI(true);
|
|
|
|
assert.ok(Html5.supportsNativeTextTracks(), 'Html5 does support native text tracks in safari');
|
|
|
|
Html5.TEST_VID = oldTestVid;
|
|
browser.stub_IS_ANY_SAFARI(oldIsAnySafari);
|
|
});
|
|
|
|
QUnit.test('when switching techs, we should not get a new text track', function(assert) {
|
|
const player = TestHelpers.makePlayer();
|
|
|
|
player.loadTech_('TechFaker');
|
|
const firstTracks = player.textTracks();
|
|
|
|
player.loadTech_('TechFaker');
|
|
const secondTracks = player.textTracks();
|
|
|
|
assert.ok(firstTracks === secondTracks, 'the tracks are equal');
|
|
|
|
player.dispose();
|
|
});
|
|
|
|
if (Html5.supportsNativeTextTracks()) {
|
|
QUnit.test('listen to native remove and add track events in native text tracks', function(assert) {
|
|
const done = assert.async();
|
|
|
|
const el = document.createElement('video');
|
|
const html = new Html5({el});
|
|
const tt = el.textTracks;
|
|
const emulatedTt = html.textTracks();
|
|
const track = document.createElement('track');
|
|
|
|
el.appendChild(track);
|
|
|
|
const addtrack = function() {
|
|
assert.equal(emulatedTt.length, tt.length, 'we have matching tracks length');
|
|
assert.equal(emulatedTt.length, 1, 'we have one text track');
|
|
|
|
el.removeChild(track);
|
|
};
|
|
|
|
emulatedTt.one('addtrack', addtrack);
|
|
emulatedTt.one('removetrack', function() {
|
|
assert.equal(emulatedTt.length, tt.length, 'we have matching tracks length');
|
|
assert.equal(emulatedTt.length, 0, 'we have no more text tracks');
|
|
|
|
html.dispose();
|
|
done();
|
|
});
|
|
});
|
|
|
|
QUnit.test('should have removed tracks on dispose', function(assert) {
|
|
const done = assert.async();
|
|
|
|
const el = document.createElement('video');
|
|
const html = new Html5({el});
|
|
const tt = el.textTracks;
|
|
const emulatedTt = html.textTracks();
|
|
const track = document.createElement('track');
|
|
|
|
el.appendChild(track);
|
|
|
|
const addtrack = function() {
|
|
assert.equal(emulatedTt.length, tt.length, 'we have matching tracks length');
|
|
assert.equal(emulatedTt.length, 1, 'we have one text track');
|
|
|
|
emulatedTt.off('addtrack', addtrack);
|
|
html.dispose();
|
|
|
|
assert.equal(emulatedTt.length, tt.length, 'we have matching tracks length');
|
|
assert.equal(emulatedTt.length, 0, 'we have no more text tracks');
|
|
|
|
done();
|
|
};
|
|
|
|
emulatedTt.on('addtrack', addtrack);
|
|
});
|
|
}
|
|
|
|
QUnit.test('should check for text track changes when emulating text tracks', function(assert) {
|
|
const tech = new Tech();
|
|
let numTextTrackChanges = 0;
|
|
|
|
tech.on('texttrackchange', function() {
|
|
numTextTrackChanges++;
|
|
});
|
|
tech.emulateTextTracks();
|
|
assert.equal(numTextTrackChanges, 1, 'we got a texttrackchange event');
|
|
|
|
tech.dispose();
|
|
});
|
|
|
|
QUnit.test('no lang attribute on cue elements if one is provided', function(assert) {
|
|
const player = TestHelpers.makePlayer();
|
|
const tt = new TextTrack({
|
|
tech: player.tech_,
|
|
mode: 'showing'
|
|
});
|
|
|
|
tt.addCue({
|
|
id: '1',
|
|
startTime: 2,
|
|
endTime: 5
|
|
});
|
|
player.tech_.textTracks().addTrack(tt);
|
|
|
|
player.currentTime(2);
|
|
player.tech_.trigger('playing');
|
|
|
|
assert.notOk(tt.activeCues[0].displayState.hasAttribute('lang'), 'no lang attribute should be set');
|
|
|
|
player.dispose();
|
|
});
|
|
|
|
QUnit.test('set lang attribute on cue elements if one is provided', function(assert) {
|
|
const player = TestHelpers.makePlayer();
|
|
const tt = new TextTrack({
|
|
srclang: 'en',
|
|
tech: player.tech_,
|
|
mode: 'showing'
|
|
});
|
|
|
|
tt.addCue({
|
|
id: '1',
|
|
startTime: 2,
|
|
endTime: 5
|
|
});
|
|
player.tech_.textTracks().addTrack(tt);
|
|
|
|
player.currentTime(2);
|
|
player.tech_.trigger('playing');
|
|
|
|
assert.equal(tt.activeCues[0].displayState.getAttribute('lang'), 'en', 'the lang should be set to en');
|
|
|
|
player.dispose();
|
|
});
|
|
|
|
QUnit.test('removes cuechange event when text track is hidden for emulated tracks', function(assert) {
|
|
const player = TestHelpers.makePlayer();
|
|
const tt = new TextTrack({
|
|
tech: player.tech_,
|
|
mode: 'showing'
|
|
});
|
|
|
|
tt.addCue({
|
|
id: '1',
|
|
startTime: 2,
|
|
endTime: 5
|
|
});
|
|
player.tech_.textTracks().addTrack(tt);
|
|
|
|
let numTextTrackChanges = 0;
|
|
|
|
player.tech_.on('texttrackchange', function() {
|
|
numTextTrackChanges++;
|
|
});
|
|
|
|
tt.mode = 'disabled';
|
|
this.clock.tick(1);
|
|
assert.equal(
|
|
numTextTrackChanges, 1,
|
|
'texttrackchange should be called once for mode change'
|
|
);
|
|
tt.mode = 'showing';
|
|
this.clock.tick(1);
|
|
assert.equal(
|
|
numTextTrackChanges, 2,
|
|
'texttrackchange should be called once for mode change'
|
|
);
|
|
|
|
player.tech_.currentTime = function() {
|
|
return 3;
|
|
};
|
|
player.tech_.trigger('playing');
|
|
assert.equal(
|
|
numTextTrackChanges, 3,
|
|
'texttrackchange should be triggered once for the cuechange'
|
|
);
|
|
|
|
tt.mode = 'hidden';
|
|
this.clock.tick(1);
|
|
assert.equal(
|
|
numTextTrackChanges, 4,
|
|
'texttrackchange should be called once for the mode change'
|
|
);
|
|
|
|
player.tech_.currentTime = function() {
|
|
return 7;
|
|
};
|
|
player.tech_.trigger('timeupdate');
|
|
assert.equal(
|
|
numTextTrackChanges, 4,
|
|
'texttrackchange should be not be called since mode is hidden'
|
|
);
|
|
player.dispose();
|
|
});
|
|
|
|
QUnit.test('should return correct remote text track values', function(assert) {
|
|
const fixture = document.getElementById('qunit-fixture');
|
|
const html = `
|
|
<video id="example_1" class="video-js" autoplay preload="none">
|
|
<source src="http://google.com" type="video/mp4">
|
|
<source src="http://google.com" type="video/webm">
|
|
<track kind="captions" label="label">
|
|
</video>
|
|
`;
|
|
|
|
fixture.innerHTML += html;
|
|
const tag = document.getElementById('example_1');
|
|
const player = TestHelpers.makePlayer({}, tag);
|
|
|
|
this.clock.tick(10);
|
|
|
|
assert.equal(player.remoteTextTracks().length, 1, 'add text track via html');
|
|
assert.equal(player.remoteTextTrackEls().length, 1, 'add html track element via html');
|
|
|
|
const htmlTrackElement = player.addRemoteTextTrack({
|
|
kind: 'captions',
|
|
label: 'label'
|
|
}, true);
|
|
|
|
assert.equal(player.remoteTextTracks().length, 2, 'add text track via method');
|
|
assert.equal(player.remoteTextTrackEls().length, 2, 'add html track element via method');
|
|
|
|
player.removeRemoteTextTrack(htmlTrackElement.track);
|
|
|
|
assert.equal(player.remoteTextTracks().length, 1, 'remove text track via method');
|
|
assert.equal(
|
|
player.remoteTextTrackEls().length,
|
|
1,
|
|
'remove html track element via method'
|
|
);
|
|
|
|
player.dispose();
|
|
});
|
|
|
|
QUnit.test('should uniformly create html track element when adding text track', function(assert) {
|
|
const player = TestHelpers.makePlayer();
|
|
const track = {
|
|
kind: 'kind',
|
|
src: 'src',
|
|
language: 'language',
|
|
label: 'label',
|
|
default: 'default'
|
|
};
|
|
|
|
assert.equal(player.remoteTextTrackEls().length, 0, 'no html text tracks');
|
|
|
|
const htmlTrackElement = player.addRemoteTextTrack(track, true);
|
|
|
|
assert.equal(
|
|
htmlTrackElement.kind,
|
|
htmlTrackElement.track.kind,
|
|
'verify html track element kind'
|
|
);
|
|
assert.equal(
|
|
htmlTrackElement.src,
|
|
htmlTrackElement.track.src,
|
|
'verify html track element src'
|
|
);
|
|
assert.equal(
|
|
htmlTrackElement.srclang,
|
|
htmlTrackElement.track.language,
|
|
'verify html track element language'
|
|
);
|
|
assert.equal(
|
|
htmlTrackElement.label,
|
|
htmlTrackElement.track.label,
|
|
'verify html track element label'
|
|
);
|
|
assert.equal(
|
|
htmlTrackElement.default,
|
|
htmlTrackElement.track.default,
|
|
'verify html track element default'
|
|
);
|
|
|
|
assert.equal(player.remoteTextTrackEls().length, 1, 'html track element exist');
|
|
assert.equal(
|
|
player.remoteTextTrackEls().getTrackElementByTrack_(htmlTrackElement.track),
|
|
htmlTrackElement,
|
|
'verify same html track element'
|
|
);
|
|
|
|
player.dispose();
|
|
});
|
|
|
|
// disable in Firefox because while the code works in practice, during the
|
|
// tests, somehow the text track object isn't ready and thus it won't allow
|
|
// us to change the mode of the track rendering the test non-functional.
|
|
if (!browser.IS_FIREFOX) {
|
|
QUnit.test('remote text tracks change event should fire when using native text tracks', function(assert) {
|
|
const done = assert.async();
|
|
|
|
const player = TestHelpers.makePlayer({
|
|
techOrder: ['html5'],
|
|
html5: { nativeTextTracks: true }
|
|
});
|
|
|
|
player.remoteTextTracks().on('change', function(e) {
|
|
assert.ok(true, 'change event triggered');
|
|
player.dispose();
|
|
done();
|
|
});
|
|
|
|
const track = {
|
|
kind: 'kind',
|
|
src: 'src',
|
|
language: 'language',
|
|
label: 'label',
|
|
default: 'default'
|
|
};
|
|
|
|
player.addRemoteTextTrack(track, true);
|
|
});
|
|
}
|
|
|
|
QUnit.test('default text tracks should show by default', function(assert) {
|
|
const tag = TestHelpers.makeTag();
|
|
const capt = document.createElement('track');
|
|
|
|
capt.setAttribute('kind', 'captions');
|
|
capt.setAttribute('default', 'default');
|
|
|
|
tag.appendChild(capt);
|
|
|
|
const player = TestHelpers.makePlayer({
|
|
html5: {
|
|
nativeTextTracks: false
|
|
}
|
|
}, tag);
|
|
|
|
// native tracks are initialized after the player is ready
|
|
this.clock.tick(1);
|
|
|
|
const tracks = player.textTracks();
|
|
|
|
assert.equal(tracks[0].kind, 'captions', 'the captions track is present');
|
|
assert.equal(tracks[0].mode, 'showing', 'the captions track is showing');
|
|
|
|
player.dispose();
|
|
});
|
|
|
|
QUnit.test('default captions take precedence over default descriptions', function(assert) {
|
|
const tag = TestHelpers.makeTag();
|
|
const desc = document.createElement('track');
|
|
const capt = document.createElement('track');
|
|
|
|
desc.setAttribute('kind', 'descriptions');
|
|
desc.setAttribute('default', 'default');
|
|
capt.setAttribute('kind', 'captions');
|
|
capt.setAttribute('default', 'default');
|
|
|
|
tag.appendChild(desc);
|
|
tag.appendChild(capt);
|
|
|
|
const player = TestHelpers.makePlayer({
|
|
html5: {
|
|
nativeTextTracks: false
|
|
}
|
|
}, tag);
|
|
|
|
// native tracks are initialized after the player is ready
|
|
this.clock.tick(1);
|
|
|
|
const tracks = player.textTracks();
|
|
|
|
assert.equal(tracks[0].kind, 'descriptions', 'the descriptions track is first');
|
|
assert.equal(tracks[0].mode, 'disabled', 'the descriptions track is disabled');
|
|
assert.equal(tracks[1].kind, 'captions', 'the captions track is second');
|
|
assert.equal(tracks[1].mode, 'showing', 'the captions track is showing');
|
|
player.dispose();
|
|
});
|
|
|
|
QUnit.test('removeRemoteTextTrack should be able to take both a track and the response from addRemoteTextTrack', function(assert) {
|
|
const player = TestHelpers.makePlayer();
|
|
const track = {
|
|
kind: 'kind',
|
|
src: 'src',
|
|
language: 'language',
|
|
label: 'label',
|
|
default: 'default'
|
|
};
|
|
let htmlTrackElement = player.addRemoteTextTrack(track, true);
|
|
|
|
assert.equal(player.remoteTextTrackEls().length, 1, 'html track element exist');
|
|
|
|
player.removeRemoteTextTrack(htmlTrackElement);
|
|
|
|
assert.equal(
|
|
player.remoteTextTrackEls().length,
|
|
0,
|
|
'the track element was removed correctly'
|
|
);
|
|
|
|
htmlTrackElement = player.addRemoteTextTrack(track, true);
|
|
assert.equal(player.remoteTextTrackEls().length, 1, 'html track element exist');
|
|
|
|
player.removeRemoteTextTrack(htmlTrackElement.track);
|
|
assert.equal(
|
|
player.remoteTextTrackEls().length,
|
|
0,
|
|
'the track element was removed correctly'
|
|
);
|
|
player.dispose();
|
|
});
|
|
|
|
if (Html5.isSupported()) {
|
|
QUnit.test('auto remove tracks should not clean up tracks added while source is being added', function(assert) {
|
|
const player = TestHelpers.makePlayer({
|
|
techOrder: ['html5'],
|
|
html5: {
|
|
nativeTextTracks: false
|
|
}
|
|
});
|
|
|
|
const track = {
|
|
kind: 'kind',
|
|
src: 'src',
|
|
language: 'language',
|
|
label: 'label',
|
|
default: 'default'
|
|
};
|
|
|
|
player.src({src: 'example.mp4', type: 'video/mp4'});
|
|
player.addRemoteTextTrack(track, false);
|
|
|
|
this.clock.tick(1);
|
|
assert.equal(player.textTracks().length, 1, 'we have one text track');
|
|
|
|
player.dispose();
|
|
});
|
|
|
|
QUnit.test('auto remove tracks added right before a source change will be cleaned up', function(assert) {
|
|
const player = TestHelpers.makePlayer({
|
|
techOrder: ['html5'],
|
|
html5: {
|
|
nativeTextTracks: false
|
|
}
|
|
});
|
|
|
|
const track = {
|
|
kind: 'kind',
|
|
src: 'src',
|
|
language: 'language',
|
|
label: 'label',
|
|
default: 'default'
|
|
};
|
|
|
|
player.addRemoteTextTrack(track, false);
|
|
player.src({src: 'example.mp4', type: 'video/mp4'});
|
|
|
|
this.clock.tick(1);
|
|
assert.equal(player.textTracks().length, 0, 'we do not have any tracks left');
|
|
|
|
player.dispose();
|
|
});
|
|
}
|