summaryrefslogtreecommitdiff
path: root/javascript/videojs/test/unit/tracks/text-track-display.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'javascript/videojs/test/unit/tracks/text-track-display.test.js')
-rw-r--r--javascript/videojs/test/unit/tracks/text-track-display.test.js622
1 files changed, 622 insertions, 0 deletions
diff --git a/javascript/videojs/test/unit/tracks/text-track-display.test.js b/javascript/videojs/test/unit/tracks/text-track-display.test.js
new file mode 100644
index 0000000..6dcfb01
--- /dev/null
+++ b/javascript/videojs/test/unit/tracks/text-track-display.test.js
@@ -0,0 +1,622 @@
+/* eslint-env qunit */
+import window from 'global/window';
+import Html5 from '../../../src/js/tech/html5.js';
+import { constructColor } from '../../../src/js/tracks/text-track-display.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';
+import sinon from 'sinon';
+
+QUnit.module('Text Track Display', {
+ beforeEach(assert) {
+ this.clock = sinon.useFakeTimers();
+ },
+ afterEach(assert) {
+ this.clock.restore();
+ }
+});
+
+const getMenuItemByLanguage = function(items, language) {
+ for (let i = items.length - 1; i > 0; i--) {
+ const captionMenuItem = items[i];
+ const trackLanguage = captionMenuItem.track.language;
+
+ if (trackLanguage && trackLanguage === language) {
+ return captionMenuItem;
+ }
+ }
+};
+
+QUnit.test('if native text tracks are not supported, create a texttrackdisplay', function(assert) {
+ const oldTestVid = Html5.TEST_VID;
+ const oldIsFirefox = browser.IS_FIREFOX;
+ const oldTextTrackDisplay = Component.getComponent('TextTrackDisplay');
+ 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);
+
+ Html5.TEST_VID = {
+ textTracks: []
+ };
+
+ browser.stub_IS_FIREFOX(true);
+
+ const fakeTTDSpy = sinon.spy();
+
+ class FakeTTD extends Component {
+ constructor(player, options) {
+ super(player, options);
+ fakeTTDSpy();
+ }
+ }
+
+ Component.registerComponent('TextTrackDisplay', FakeTTD);
+
+ const player = TestHelpers.makePlayer({}, tag);
+
+ assert.strictEqual(fakeTTDSpy.callCount, 1, 'text track display was created');
+
+ Html5.TEST_VID = oldTestVid;
+ browser.stub_IS_FIREFOX(oldIsFirefox);
+ Component.registerComponent('TextTrackDisplay', oldTextTrackDisplay);
+
+ player.dispose();
+});
+
+QUnit.test('shows the default caption track first', function(assert) {
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt',
+ default: true
+ };
+ const track2 = {
+ kind: 'captions',
+ label: 'Spanish',
+ language: 'es',
+ src: 'es.vtt'
+ };
+
+ // Add the text tracks
+ const englishTrack = player.addRemoteTextTrack(track1, true).track;
+ const spanishTrack = player.addRemoteTextTrack(track2, true).track;
+
+ // Make sure the ready handler runs
+ this.clock.tick(1);
+
+ assert.ok(englishTrack.mode === 'showing', 'English track should be showing');
+ assert.ok(spanishTrack.mode === 'disabled', 'Spanish track should not be showing');
+ player.dispose();
+});
+
+if (!Html5.supportsNativeTextTracks()) {
+ QUnit.test('text track display should attach screen orientation change event handler', function(assert) {
+ const oldScreen = window.screen;
+ const removeHandlerSpy = sinon.spy();
+ let changeHandlerSpy;
+ let changeHandlerAttached;
+
+ window.screen = {
+ orientation: {
+ addEventListener: (type, func) => {
+ changeHandlerAttached = true;
+ changeHandlerSpy = sinon.spy();
+ },
+ dispatchEvent: (type) => changeHandlerSpy(),
+ removeEventListener: removeHandlerSpy
+ }
+ };
+
+ const player = TestHelpers.makePlayer();
+
+ this.clock.tick(1);
+
+ assert.true(changeHandlerAttached, 'screen orientation change event handler was not attached');
+ assert.strictEqual(changeHandlerSpy.callCount, 0, 'screen orientation change event handler should not be called');
+
+ window.screen.orientation.dispatchEvent('change');
+
+ assert.strictEqual(changeHandlerSpy.callCount, 1, 'screen orientation change event handler was not called');
+
+ player.dispose();
+
+ assert.strictEqual(
+ removeHandlerSpy.callCount,
+ 1,
+ 'screen orientation change event handler was not removed during player dispose'
+ );
+ window.screen = oldScreen;
+ });
+
+ QUnit.test('selectedlanguagechange is triggered by a track mode change', function(assert) {
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt'
+ };
+ const spy = sinon.spy();
+ const selectedLanguageHandler = function(event) {
+ spy();
+ };
+ const englishTrack = player.addRemoteTextTrack(track1, true).track;
+
+ player.textTracks().addEventListener('selectedlanguagechange', selectedLanguageHandler);
+ englishTrack.mode = 'showing';
+
+ assert.strictEqual(spy.callCount, 1, 'selectedlanguagechange event was fired');
+ player.dispose();
+ player.textTracks().removeEventListener('selectedlanguagechange', selectedLanguageHandler);
+ });
+
+ QUnit.test("if user-selected language is unavailable, don't pick a track to show", function(assert) {
+ // The video has no default language but has ‘English’ captions only
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt'
+ };
+ const captionsButton = player.controlBar.getChild('SubsCapsButton');
+
+ player.src({type: 'video/mp4', src: 'http://google.com'});
+ // manualCleanUp = true by default
+ const englishTrack = player.addRemoteTextTrack(track1, true).track;
+
+ // Force 'es' as user-selected track
+ player.cache_.selectedLanguage = { language: 'es', kind: 'captions' };
+
+ this.clock.tick(1);
+ player.play();
+
+ assert.ok(!captionsButton.hasClass('vjs-hidden'), 'The captions button is shown');
+ assert.ok(englishTrack.mode === 'disabled', 'English track should be disabled');
+ player.dispose();
+ });
+
+ QUnit.test('the user-selected language takes priority over default language', function(assert) {
+ // The video has ‘English’ captions as default, but has ‘Spanish’ captions also
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt',
+ default: true
+ };
+ const track2 = {
+ kind: 'captions',
+ label: 'Spanish',
+ language: 'es',
+ src: 'es.vtt'
+ };
+
+ player.src({type: 'video/mp4', src: 'http://google.com'});
+ // manualCleanUp = true by default
+ const englishTrack = player.addRemoteTextTrack(track1, true).track;
+ const spanishTrack = player.addRemoteTextTrack(track2, true).track;
+
+ // Force 'es' as user-selected track
+ player.cache_.selectedLanguage = { enabled: true, language: 'es', kind: 'captions' };
+ this.clock.tick(1);
+
+ assert.ok(spanishTrack.mode === 'showing', 'Spanish captions should be shown');
+ assert.ok(englishTrack.mode === 'disabled', 'English captions should be hidden');
+ player.dispose();
+ });
+
+ QUnit.test("don't select user language if it is an empty string", function(assert) {
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt'
+ };
+ const track2 = {
+ kind: 'captions',
+ label: 'Spanish',
+ language: 'es',
+ src: 'es.vtt'
+ };
+ const track3 = {
+ kind: 'metadata',
+ label: 'segment-metadata'
+ };
+
+ player.src({type: 'video/mp4', src: 'http://google.com'});
+ // manualCleanUp = true by default
+ const englishTrack = player.addRemoteTextTrack(track1, true).track;
+ const spanishTrack = player.addRemoteTextTrack(track2, true).track;
+ const metadataTrack = player.addRemoteTextTrack(track3, true).track;
+
+ // Force empty string ('') as "user-selected" track
+ player.cache_.selectedLanguage = { enabled: true, language: '', kind: 'captions' };
+ this.clock.tick(1);
+
+ assert.equal(spanishTrack.mode, 'disabled', 'Spanish captions should be disabled');
+ assert.equal(englishTrack.mode, 'disabled', 'English captions should be disabled');
+ assert.notEqual(metadataTrack.mode, 'showing', 'Metadata track should not be showing');
+
+ // Force es as "user-selected" track
+ player.cache_.selectedLanguage = { enabled: true, language: 'es', kind: 'captions' };
+ player.trigger('loadedmetadata');
+
+ assert.equal(spanishTrack.mode, 'showing', 'Spanish captions should be showing');
+ assert.equal(englishTrack.mode, 'disabled', 'English captions should be disabled');
+ assert.notEqual(metadataTrack.mode, 'showing', 'Metadata track should not be showing');
+
+ player.dispose();
+ });
+
+ QUnit.test("matching both the selectedLanguage's language and kind takes priority over just matching the language", function(assert) {
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt'
+ };
+ const track2 = {
+ kind: 'subtitles',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt'
+ };
+
+ player.src({type: 'video/mp4', src: 'http://google.com'});
+ // manualCleanUp = true by default
+ const captionTrack = player.addRemoteTextTrack(track1, true).track;
+ const subsTrack = player.addRemoteTextTrack(track2, true).track;
+
+ // Force English captions as user-selected track
+ player.cache_.selectedLanguage = { enabled: true, language: 'en', kind: 'captions' };
+ this.clock.tick(1);
+
+ assert.ok(captionTrack.mode === 'showing', 'Captions track should be preselected');
+ assert.ok(subsTrack.mode === 'disabled', 'Subtitles track should remain disabled');
+ player.dispose();
+ });
+
+ QUnit.test('the user-selected language is used for subsequent source changes', function(assert) {
+ // Start with two captions tracks: English and Spanish
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt'
+ };
+ const track2 = {
+ kind: 'captions',
+ label: 'Spanish',
+ language: 'es',
+ src: 'es.vtt'
+ };
+ const tracks = player.tech_.remoteTextTracks();
+ const captionsButton = player.controlBar.getChild('SubsCapsButton');
+ let esCaptionMenuItem;
+ let enCaptionMenuItem;
+
+ player.src({type: 'video/mp4', src: 'http://google.com'});
+ // manualCleanUp = true by default
+ player.addRemoteTextTrack(track1, true);
+ player.addRemoteTextTrack(track2, true);
+
+ // Keep track of menu items
+ esCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'es');
+ enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en');
+
+ // The user chooses Spanish
+ player.play();
+ esCaptionMenuItem.trigger('click');
+
+ // Track mode changes on user-selection
+ assert.ok(
+ esCaptionMenuItem.track.mode === 'showing',
+ 'Spanish should be showing after selection'
+ );
+ assert.ok(
+ enCaptionMenuItem.track.mode === 'disabled',
+ 'English should be disabled after selecting Spanish'
+ );
+ assert.deepEqual(
+ player.cache_.selectedLanguage,
+ { enabled: true, language: 'es', kind: 'captions' }
+ );
+
+ // Switch source and remove old tracks
+ player.tech_.src({type: 'video/mp4', src: 'http://example.com'});
+ while (tracks.length > 0) {
+ player.removeRemoteTextTrack(tracks[0]);
+ }
+ // Add tracks for the new source
+ // change the kind of track to subtitles
+ track1.kind = 'subtitles';
+ track2.kind = 'subtitles';
+ const englishTrack = player.addRemoteTextTrack(track1, true).track;
+ const spanishTrack = player.addRemoteTextTrack(track2, true).track;
+
+ // Make sure player ready handler runs
+ this.clock.tick(1);
+
+ // Keep track of menu items
+ esCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'es');
+ enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en');
+
+ // The user-selection should have persisted
+ assert.ok(
+ esCaptionMenuItem.track.mode === 'showing',
+ 'Spanish should remain showing'
+ );
+ assert.ok(
+ enCaptionMenuItem.track.mode === 'disabled',
+ 'English should remain disabled'
+ );
+ assert.deepEqual(
+ player.cache_.selectedLanguage,
+ { enabled: true, language: 'es', kind: 'captions' }
+ );
+
+ assert.ok(spanishTrack.mode === 'showing', 'Spanish track remains showing');
+ assert.ok(englishTrack.mode === 'disabled', 'English track remains disabled');
+ player.dispose();
+ });
+
+ QUnit.test('the user-selected language is cleared on turning off captions', function(assert) {
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt'
+ };
+ const captionsButton = player.controlBar.getChild('SubsCapsButton');
+
+ player.src({type: 'video/mp4', src: 'http://google.com'});
+ // manualCleanUp = true by default
+ const englishTrack = player.addRemoteTextTrack(track1, true).track;
+ // Keep track of menu items
+ const enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en');
+ // we know the position of the OffTextTrackMenuItem
+ const offMenuItem = captionsButton.items[1];
+
+ // Select English initially
+ player.play();
+ enCaptionMenuItem.trigger('click');
+
+ assert.deepEqual(
+ player.cache_.selectedLanguage,
+ { enabled: true, language: 'en', kind: 'captions' }, 'English track is selected'
+ );
+ assert.ok(englishTrack.mode === 'showing', 'English track should be showing');
+
+ // Select the off button
+ offMenuItem.trigger('click');
+
+ assert.deepEqual(
+ player.cache_.selectedLanguage,
+ { enabled: false }, 'selectedLanguage is cleared'
+ );
+ assert.ok(englishTrack.mode === 'disabled', 'English track is disabled');
+ player.dispose();
+ });
+
+ QUnit.test('a color can be constructed from a three digit hex code', function(assert) {
+ const hex = '#f0e';
+
+ // f gets mapped to ff -> 255 in decimal,
+ // 0 gets mapped to 00 -> 0 in decimal,
+ // e gets mapped to ee -> 238 in decimal.
+ assert.equal(constructColor(hex, 1), 'rgba(255,0,238,1)');
+ });
+
+ QUnit.test('a color can be constructed from a six digit hex code', function(assert) {
+ const hex = '#f604e2';
+
+ // f6 -> 246 in decimal,
+ // 04 -> 4 in decimal,
+ // e2 -> 226 in decimal.
+ assert.equal(constructColor(hex, 1), 'rgba(246,4,226,1)');
+ });
+
+ QUnit.test('an invalid hex code will throw an error', function(assert) {
+ const hex = '#f';
+
+ assert.throws(
+ function() {
+ constructColor(hex, 1);
+ },
+ new Error('Invalid color code provided, #f; must be formatted as e.g. #f0e or #f604e2.'),
+ 'colors must be valid hex codes.'
+ );
+ });
+
+ const skipOnOldChrome = window.CSS.supports('inset-inline: 10px') ? 'test' : 'skip';
+
+ QUnit[skipOnOldChrome]('text track display should overlay a video', function(assert) {
+ const tag = document.createElement('video');
+
+ tag.width = 320;
+ tag.height = 180;
+ const player = TestHelpers.makePlayer({}, tag);
+ const textTrackDisplay = player.getChild('TextTrackDisplay');
+ const textTrackDisplayStyle = textTrackDisplay.el().style;
+
+ assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string');
+ assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string');
+
+ // video aspect ratio equal to NaN
+ player.tech_.videoWidth = () => 0;
+ player.tech_.videoHeight = () => 0;
+
+ assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string');
+ assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string');
+
+ // video aspect ratio 2:1
+ player.tech_.videoWidth = () => 100;
+ player.tech_.videoHeight = () => 50;
+
+ textTrackDisplay.updateDisplayOverlay();
+
+ assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string');
+ assert.ok(textTrackDisplayStyle.insetBlock === '10px', 'text track display style insetBlock equal to 10px');
+
+ // video aspect ratio 4:3
+ player.tech_.videoWidth = () => 100;
+ player.tech_.videoHeight = () => 75;
+
+ textTrackDisplay.updateDisplayOverlay();
+
+ assert.ok(textTrackDisplayStyle.insetInline === '40px', 'text track display style insetInline equal to 40px');
+ assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string');
+
+ // video aspect ratio 16:9
+ player.tech_.videoWidth = () => 320;
+ player.tech_.videoHeight = () => 180;
+
+ textTrackDisplay.updateDisplayOverlay();
+
+ assert.ok(textTrackDisplayStyle.insetInline === '', 'text track display style insetInline equal to empty string');
+ assert.ok(textTrackDisplayStyle.insetBlock === '', 'text track display style insetBlock equal to empty string');
+
+ player.dispose();
+ });
+
+ QUnit.test('should use relative position for vjs-text-track-display element if environment does not support window.CSS', function(assert) {
+ const cssDescriptor = Object.getOwnPropertyDescriptor(window, 'CSS');
+
+ try {
+ // Set conditions for the use of the style modifications. Temporarily
+ // remove window.CSS to match the environment created by jsdom.
+ // https://github.com/jsdom/jsdom/issues/3991
+ delete window.CSS;
+
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt',
+ default: true
+ };
+
+ // Add the text track
+ player.addRemoteTextTrack(track1, true);
+
+ player.src({type: 'video/mp4', src: 'http://google.com'});
+ player.play();
+
+ // as if metadata was loaded
+ player.textTrackDisplay.updateDisplayOverlay();
+
+ // Make sure the ready handler runs
+ this.clock.tick(1);
+
+ const textTrack = window.document.querySelector('.vjs-text-track-display');
+
+ assert.ok(textTrack.style.position === 'relative', 'Style of position for vjs-text-track-display element should be relative');
+ assert.ok(textTrack.style.top === 'unset', 'Style of position for vjs-text-track-display element should be unset');
+ assert.ok(textTrack.style.bottom === '0px', 'Style of bottom for vjs-text-track-display element should be 0px');
+ player.dispose();
+ } finally {
+ // Restore window.CSS
+ Object.defineProperty(window, 'CSS', cssDescriptor);
+ }
+ });
+
+ QUnit.test('should use relative position for vjs-text-track-display element if browser does not support inset property', function(assert) {
+ // Set conditions for the use of the style modifications
+ window.CSS.supports = () => false;
+ browser.IS_SMART_TV = () => true;
+
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt',
+ default: true
+ };
+
+ // Add the text track
+ player.addRemoteTextTrack(track1, true);
+
+ player.src({type: 'video/mp4', src: 'http://google.com'});
+ player.play();
+
+ // as if metadata was loaded
+ player.textTrackDisplay.updateDisplayOverlay();
+
+ // Make sure the ready handler runs
+ this.clock.tick(1);
+
+ const textTrack = window.document.querySelector('.vjs-text-track-display');
+
+ assert.ok(textTrack.style.position === 'relative', 'Style of position for vjs-text-track-display element should be relative');
+ assert.ok(textTrack.style.top === 'unset', 'Style of position for vjs-text-track-display element should be unset');
+ assert.ok(textTrack.style.bottom === '0px', 'Style of bottom for vjs-text-track-display element should be 0px');
+ player.dispose();
+ });
+
+ QUnit.test('track cue should use values of top, right, botton, left if browser does not support inset property', function(assert) {
+ // Set conditions for the use of the style modifications
+ window.CSS.supports = () => false;
+ browser.IS_SMART_TV = () => true;
+
+ const player = TestHelpers.makePlayer();
+ const track1 = {
+ kind: 'captions',
+ label: 'English',
+ language: 'en',
+ src: 'en.vtt',
+ default: true
+ };
+
+ // Add the text track
+ player.addRemoteTextTrack(track1, true);
+
+ player.src({type: 'video/mp4', src: 'http://google.com'});
+ player.play();
+
+ // mock caption
+ const textTrackDisplay = window.document.querySelector('.vjs-text-track-display').firstChild;
+ const node = document.createElement('div');
+
+ node.classList.add('vjs-text-track-cue');
+ node.style.inset = '1px 2px 3px';
+ const textnode = document.createTextNode('Sample text');
+
+ node.appendChild(textnode);
+ textTrackDisplay.appendChild(node);
+
+ // avoid captions clear
+ player.textTrackDisplay.clearDisplay = () => '';
+
+ // as if metadata was loaded
+ player.textTrackDisplay.updateDisplay();
+
+ assert.ok(player.textTrackDisplay.el_.querySelector('.vjs-text-track-cue').style.left === 'unset', 'Style of left for vjs-text-track-cue element should be unset');
+ assert.ok(player.textTrackDisplay.el_.querySelector('.vjs-text-track-cue').style.top === '1px', 'Style of top for vjs-text-track-cue element should be 1px');
+ assert.ok(player.textTrackDisplay.el_.querySelector('.vjs-text-track-cue').style.right === '2px', 'Style of right for vjs-text-track-cue element should be 2px');
+ player.dispose();
+ });
+}