1
0
mirror of https://github.com/videojs/video.js.git synced 2025-02-02 11:34:50 +02:00

feat: trigger languagechange event on a language change (#6891)

This commit is contained in:
Marco Del Toro Barragan 2020-11-10 17:09:37 -06:00 committed by GitHub
parent f74e45b904
commit a0d09c107a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 199 additions and 100 deletions

View File

@ -177,6 +177,15 @@ class ClickableComponent extends Component {
this.off('keydown', this.handleKeyDown);
}
/**
* Handles language change in ClickableComponent for the player in components
*
*
*/
handleLanguagechange() {
this.controlText(this.controlText_);
}
/**
* Event handler that is called when a `ClickableComponent` receives a
* `click` or `tap` event.

View File

@ -95,6 +95,9 @@ class Component {
if (options.evented !== false) {
// Make this an evented object and use `el_`, if available, as its event bus
evented(this, {eventBusKey: this.el_ ? 'el_' : null});
this.handleLanguagechange = this.handleLanguagechange.bind(this);
this.on(this.player_, 'languagechange', this.handleLanguagechange);
}
stateful(this, this.constructor.defaultState);
@ -120,6 +123,7 @@ class Component {
if (options.reportTouchActivity !== false) {
this.enableTouchActivity();
}
}
/**
@ -288,6 +292,7 @@ class Component {
* The localized string or if no localization exists the english string.
*/
localize(string, tokens, defaultValue = string) {
const code = this.player_.language && this.player_.language();
const languages = this.player_.languages && this.player_.languages();
const language = languages && languages[code];
@ -318,6 +323,13 @@ class Component {
return localizedString;
}
/**
* Handles language change for the player in components. Should be overriden by sub-components.
*
* @abstract
*/
handleLanguagechange() {}
/**
* Return the `Component`s DOM element. This is where children get inserted.
* This will usually be the the same as the element returned in {@link Component#el}.

View File

@ -555,6 +555,7 @@ class Player extends Component {
this.one('play', this.listenForUserActivity_);
this.on('stageclick', this.handleStageClick_);
this.on('keydown', this.handleKeyDown);
this.on('languagechange', this.handleLanguagechange);
this.breakpoints(this.options_.breakpoints);
this.responsive(this.options_.responsive);
@ -4279,10 +4280,15 @@ class Player extends Component {
}
/**
* The player's language code
* NOTE: The language should be set in the player options if you want the
* the controls to be built with a specific language. Changing the language
* later will not update controls text.
* The player's language code.
*
* Changing the langauge will trigger
* [languagechange]{@link Player#event:languagechange}
* which Components can use to update control text.
* ClickableComponent will update its control text by default on
* [languagechange]{@link Player#event:languagechange}.
*
* @fires Player#languagechange
*
* @param {string} [code]
* the language code to set the player to
@ -4295,7 +4301,20 @@ class Player extends Component {
return this.language_;
}
if (this.language_ !== String(code).toLowerCase()) {
this.language_ = String(code).toLowerCase();
// during first init, it's possible some things won't be evented
if (isEvented(this)) {
/**
* fires when the player language change
*
* @event Player#languagechange
* @type {EventTarget~Event}
*/
this.trigger('languagechange');
}
}
}
/**

View File

@ -483,6 +483,15 @@ class Tech extends Component {
return createTimeRange();
}
/**
* Start playback
*
* @abstract
*
* @see {Html5#play}
*/
play() {}
/**
* Set whether we are scrubbing or not
*

View File

@ -111,3 +111,32 @@ QUnit.test('handleClick should use handler from options', function(assert) {
testClickableComponent.dispose();
player.dispose();
});
QUnit.test('language change should localize its text', function(assert) {
assert.expect(2);
const player = TestHelpers.makePlayer({
languages: {
es: {
Play: 'Juego'
},
en: {
Play: 'Play'
}
}
});
const testClickableComponent = new ClickableComponent(player);
testClickableComponent.controlText_ = 'Play';
const el = testClickableComponent.createEl();
player.language('en');
assert.equal(el.querySelector('.vjs-control-text').textContent, 'Play', 'text localized');
player.language('es');
assert.equal(el.querySelector('.vjs-control-text').textContent, 'Juego', 'text localized');
testClickableComponent.dispose();
player.dispose();
});

View File

@ -31,8 +31,10 @@ QUnit.module('Component', {
},
beforeEach() {
this.clock = sinon.useFakeTimers();
this.player = TestHelpers.makePlayer();
},
afterEach() {
this.player.dispose();
this.clock.restore();
},
after() {
@ -43,16 +45,6 @@ QUnit.module('Component', {
}
});
const getFakePlayer = function() {
return {
// Fake player requries an ID
id() {
return 'player_1';
},
reportUserActivity() {}
};
};
QUnit.test('registerComponent() throws with bad arguments', function(assert) {
assert.throws(
function() {
@ -92,7 +84,7 @@ QUnit.test('registerComponent() throws with bad arguments', function(assert) {
});
QUnit.test('should create an element', function(assert) {
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
assert.ok(comp.el().nodeName);
@ -100,7 +92,7 @@ QUnit.test('should create an element', function(assert) {
});
QUnit.test('should add a child component', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const child = comp.addChild('component');
@ -114,7 +106,7 @@ QUnit.test('should add a child component', function(assert) {
});
QUnit.test('should add a child component to an index', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const child = comp.addChild('component');
@ -151,7 +143,7 @@ QUnit.test('should insert element relative to the element of the component to in
// for legibility of the test itself:
/* eslint-disable no-unused-vars */
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const child0 = comp.addChild('component', {el: Dom.createEl('div', {}, {class: 'c0'})});
const child1 = comp.addChild('component', {createEl: false});
@ -169,7 +161,7 @@ QUnit.test('should allow for children that are elements', function(assert) {
// for legibility of the test itself:
/* eslint-disable no-unused-vars */
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const testEl = Dom.createEl('div');
// Add element as video el gets added to player
@ -185,7 +177,7 @@ QUnit.test('should allow for children that are elements', function(assert) {
});
QUnit.test('addChild should throw if the child does not exist', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
assert.throws(function() {
comp.addChild('non-existent-child');
@ -195,8 +187,8 @@ QUnit.test('addChild should throw if the child does not exist', function(assert)
});
QUnit.test('addChild with instance should allow getting child correctly', function(assert) {
const comp = new Component(getFakePlayer());
const comp2 = new Component(getFakePlayer());
const comp = new Component(this.player);
const comp2 = new Component(this.player);
comp2.name = function() {
return 'foo';
@ -210,7 +202,7 @@ QUnit.test('addChild with instance should allow getting child correctly', functi
});
QUnit.test('should add a child component with title case name', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const child = comp.addChild('Component');
@ -224,7 +216,7 @@ QUnit.test('should add a child component with title case name', function(assert)
});
QUnit.test('should init child components from options', function(assert) {
const comp = new Component(getFakePlayer(), {
const comp = new Component(this.player, {
children: {
component: {}
}
@ -237,7 +229,7 @@ QUnit.test('should init child components from options', function(assert) {
});
QUnit.test('should init child components from simple children array', function(assert) {
const comp = new Component(getFakePlayer(), {
const comp = new Component(this.player, {
children: [
'component',
'component',
@ -252,7 +244,7 @@ QUnit.test('should init child components from simple children array', function(a
});
QUnit.test('should init child components from children array of objects', function(assert) {
const comp = new Component(getFakePlayer(), {
const comp = new Component(this.player, {
children: [
{ name: 'component' },
{ name: 'component' },
@ -278,7 +270,7 @@ QUnit.test('should do a deep merge of child options', function(assert) {
}
};
const comp = new Component(getFakePlayer(), {
const comp = new Component(this.player, {
example: {
childOne: { foo: 'baz', abc: '123' },
childThree: false,
@ -338,7 +330,7 @@ QUnit.test('should allows setting child options at the parent options level', fu
};
try {
parent = new Component(getFakePlayer(), options);
parent = new Component(this.player, options);
} catch (err) {
assert.ok(false, 'Child with `false` option was initialized');
}
@ -362,7 +354,7 @@ QUnit.test('should allows setting child options at the parent options level', fu
};
try {
parent = new Component(getFakePlayer(), options);
parent = new Component(this.player, options);
} catch (err) {
assert.ok(false, 'Child with `false` option was initialized');
}
@ -372,7 +364,7 @@ QUnit.test('should allows setting child options at the parent options level', fu
});
QUnit.test('should dispose of component and children', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
// Add a child
const child = comp.addChild('Component');
@ -413,7 +405,7 @@ QUnit.test('should dispose of component and children', function(assert) {
});
QUnit.test('should add and remove event listeners to element', function(assert) {
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
// No need to make this async because we're triggering events inline.
// We're going to trigger the event after removing the listener,
@ -434,7 +426,7 @@ QUnit.test('should add and remove event listeners to element', function(assert)
});
QUnit.test('should trigger a listener once using one()', function(assert) {
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
assert.expect(1);
@ -450,7 +442,7 @@ QUnit.test('should trigger a listener once using one()', function(assert) {
});
QUnit.test('should be possible to pass data when you trigger an event', function(assert) {
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
const data1 = 'Data1';
const data2 = {txt: 'Data2'};
@ -470,7 +462,7 @@ QUnit.test('should be possible to pass data when you trigger an event', function
});
QUnit.test('should add listeners to other components and remove them', function(assert) {
const player = getFakePlayer();
const player = this.player;
const comp1 = new Component(player);
const comp2 = new Component(player);
let listenerFired = 0;
@ -503,7 +495,7 @@ QUnit.test('should add listeners to other components and remove them', function(
});
QUnit.test('should add listeners to other components and remove when them other component is disposed', function(assert) {
const player = getFakePlayer();
const player = this.player;
const comp1 = new Component(player);
const comp2 = new Component(player);
@ -521,7 +513,7 @@ QUnit.test('should add listeners to other components and remove when them other
});
QUnit.test('should add listeners to other components that are fired once', function(assert) {
const player = getFakePlayer();
const player = this.player;
const comp1 = new Component(player);
const comp2 = new Component(player);
let listenerFired = 0;
@ -542,7 +534,7 @@ QUnit.test('should add listeners to other components that are fired once', funct
});
QUnit.test('should add listeners to other element and remove them', function(assert) {
const player = getFakePlayer();
const player = this.player;
const comp1 = new Component(player);
const el = document.createElement('div');
let listenerFired = 0;
@ -583,7 +575,7 @@ QUnit.test('should add listeners to other element and remove them', function(ass
});
QUnit.test('should add listeners to other components that are fired once', function(assert) {
const player = getFakePlayer();
const player = this.player;
const comp1 = new Component(player);
const el = document.createElement('div');
let listenerFired = 0;
@ -607,7 +599,7 @@ QUnit.test('should trigger a listener when ready', function(assert) {
let methodListenerFired;
let syncListenerFired;
const comp = new Component(getFakePlayer(), {}, function() {
const comp = new Component(this.player, {}, function() {
initListenerFired = true;
});
@ -647,7 +639,7 @@ QUnit.test('should trigger a listener when ready', function(assert) {
QUnit.test('should not retrigger a listener when the listener calls triggerReady', function(assert) {
let timesCalled = 0;
let selfTriggered = false;
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
const readyListener = function() {
timesCalled++;
@ -671,7 +663,7 @@ QUnit.test('should not retrigger a listener when the listener calls triggerReady
});
QUnit.test('should add and remove a CSS class', function(assert) {
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
comp.addClass('test-class');
assert.ok(comp.el().className.indexOf('test-class') !== -1);
@ -686,7 +678,7 @@ QUnit.test('should add and remove a CSS class', function(assert) {
});
QUnit.test('should show and hide an element', function(assert) {
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
comp.hide();
assert.ok(comp.hasClass('vjs-hidden') === true);
@ -702,7 +694,7 @@ QUnit.test('dimension() should treat NaN and null as zero', function(assert) {
const width = 300;
const height = 150;
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
// set component dimension
comp.dimensions(width, height);
@ -729,7 +721,7 @@ QUnit.test('dimension() should treat NaN and null as zero', function(assert) {
QUnit.test('should change the width and height of a component', function(assert) {
const container = document.createElement('div');
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
const el = comp.el();
const fixture = document.getElementById('qunit-fixture');
@ -761,7 +753,7 @@ QUnit.test('should change the width and height of a component', function(assert)
QUnit.test('should get the computed dimensions', function(assert) {
const container = document.createElement('div');
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
const el = comp.el();
const fixture = document.getElementById('qunit-fixture');
@ -802,7 +794,7 @@ QUnit.test('should use a defined content el for appending children', function(as
}
}
const comp = new CompWithContent(getFakePlayer());
const comp = new CompWithContent(this.player);
const child = comp.addChild('component');
assert.ok(comp.children().length === 1);
@ -823,7 +815,7 @@ QUnit.test('should use a defined content el for appending children', function(as
});
QUnit.test('should emit a tap event', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
let singleTouch = {};
const origTouch = browser.TOUCH_ENABLED;
@ -882,7 +874,7 @@ QUnit.test('should emit a tap event', function(assert) {
});
QUnit.test('should provide timeout methods that automatically get cleared on component disposal', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
let timeoutsFired = 0;
const timeoutToClear = comp.setTimeout(function() {
timeoutsFired++;
@ -918,7 +910,7 @@ QUnit.test('should provide timeout methods that automatically get cleared on com
});
QUnit.test('should provide interval methods that automatically get cleared on component disposal', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
let intervalsFired = 0;
@ -953,7 +945,7 @@ QUnit.test('should provide interval methods that automatically get cleared on co
});
QUnit.test('should provide a requestAnimationFrame method that is cleared on disposal', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const oldRAF = window.requestAnimationFrame;
const oldCAF = window.cancelAnimationFrame;
@ -989,7 +981,7 @@ QUnit.test('should provide a requestAnimationFrame method that is cleared on dis
});
QUnit.test('should provide a requestNamedAnimationFrame method that is cleared on disposal', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const oldRAF = window.requestAnimationFrame;
const oldCAF = window.cancelAnimationFrame;
@ -1025,7 +1017,7 @@ QUnit.test('should provide a requestNamedAnimationFrame method that is cleared o
});
QUnit.test('requestAnimationFrame falls back to timers if rAF not supported', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const oldRAF = window.requestAnimationFrame;
const oldCAF = window.cancelAnimationFrame;
@ -1053,7 +1045,7 @@ QUnit.test('requestAnimationFrame falls back to timers if rAF not supported', fu
});
QUnit.test('setTimeout should remove dispose handler on trigger', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
comp.setTimeout(() => {}, 1);
@ -1067,7 +1059,7 @@ QUnit.test('setTimeout should remove dispose handler on trigger', function(asser
});
QUnit.test('requestNamedAnimationFrame should remove dispose handler on trigger', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const oldRAF = window.requestAnimationFrame;
const oldCAF = window.cancelAnimationFrame;
@ -1098,7 +1090,7 @@ QUnit.test('requestNamedAnimationFrame should remove dispose handler on trigger'
});
QUnit.test('requestAnimationFrame should remove dispose handler on trigger', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const oldRAF = window.requestAnimationFrame;
const oldCAF = window.cancelAnimationFrame;
@ -1127,7 +1119,7 @@ QUnit.test('requestAnimationFrame should remove dispose handler on trigger', fun
});
QUnit.test('setTimeout should be canceled on dispose', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
let called = false;
let clearId;
const setId = comp.setTimeout(() => {
@ -1154,7 +1146,7 @@ QUnit.test('setTimeout should be canceled on dispose', function(assert) {
});
QUnit.test('requestAnimationFrame should be canceled on dispose', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
let called = false;
let clearId;
const setId = comp.requestAnimationFrame(() => {
@ -1181,7 +1173,7 @@ QUnit.test('requestAnimationFrame should be canceled on dispose', function(asser
});
QUnit.test('setInterval should be canceled on dispose', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
let called = false;
let clearId;
const setId = comp.setInterval(() => {
@ -1208,7 +1200,7 @@ QUnit.test('setInterval should be canceled on dispose', function(assert) {
});
QUnit.test('requestNamedAnimationFrame should be canceled on dispose', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
let called = false;
let clearName;
const setName = comp.requestNamedAnimationFrame('testing', () => {
@ -1237,7 +1229,7 @@ QUnit.test('requestNamedAnimationFrame should be canceled on dispose', function(
});
QUnit.test('requestNamedAnimationFrame should only allow one raf of a specific name at a time', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const calls = {
one: 0,
two: 0,
@ -1324,7 +1316,7 @@ QUnit.test('requestNamedAnimationFrame should only allow one raf of a specific n
});
QUnit.test('$ and $$ functions', function(assert) {
const comp = new Component(getFakePlayer());
const comp = new Component(this.player);
const contentEl = document.createElement('div');
const children = [
document.createElement('div'),
@ -1341,7 +1333,7 @@ QUnit.test('$ and $$ functions', function(assert) {
});
QUnit.test('should use the stateful mixin', function(assert) {
const comp = new Component(getFakePlayer(), {});
const comp = new Component(this.player, {});
assert.ok(Obj.isPlain(comp.state), '`state` is a plain object');
assert.strictEqual(Object.prototype.toString.call(comp.setState), '[object Function]', '`setState` is a function');
@ -1353,9 +1345,9 @@ QUnit.test('should use the stateful mixin', function(assert) {
});
QUnit.test('should remove child when the child moves to the other parent', function(assert) {
const parentComponent1 = new Component(getFakePlayer(), {});
const parentComponent2 = new Component(getFakePlayer(), {});
const childComponent = new Component(getFakePlayer(), {});
const parentComponent1 = new Component(this.player, {});
const parentComponent2 = new Component(this.player, {});
const childComponent = new Component(this.player, {});
parentComponent1.addChild(childComponent);
@ -1379,10 +1371,10 @@ QUnit.test('should remove child when the child moves to the other parent', funct
});
QUnit.test('getDescendant should work as expected', function(assert) {
const comp = new Component(getFakePlayer(), {name: 'component'});
const descendant1 = new Component(getFakePlayer(), {name: 'descendant1'});
const descendant2 = new Component(getFakePlayer(), {name: 'descendant2'});
const descendant3 = new Component(getFakePlayer(), {name: 'descendant3'});
const comp = new Component(this.player, {name: 'component'});
const descendant1 = new Component(this.player, {name: 'descendant1'});
const descendant2 = new Component(this.player, {name: 'descendant2'});
const descendant3 = new Component(this.player, {name: 'descendant3'});
comp.addChild(descendant1);
descendant1.addChild(descendant2);

View File

@ -1385,6 +1385,50 @@ QUnit.test('sets lang attribute on player el', function(assert) {
}
});
QUnit.test('language changed should trigger languagechange event', function(assert) {
const player = TestHelpers.makePlayer({});
assert.expect(1);
player.on('languagechange', function() {
assert.ok(true, 'languagechange event triggered');
});
player.language('es-MX');
player.dispose();
});
QUnit.test('language changed should not trigger languagechange event if language is the same', function(assert) {
const player = TestHelpers.makePlayer({});
assert.expect(1);
let triggered = false;
player.language('es-MX');
player.on('languagechange', function() {
triggered = true;
});
player.language('es-MX');
assert.equal(triggered, false, 'languagechange event was not triggered');
player.dispose();
});
QUnit.test('change language multiple times should trigger languagechange event', function(assert) {
const player = TestHelpers.makePlayer({});
assert.expect(3);
player.on('languagechange', function() {
assert.ok(true, 'languagechange event triggered');
});
player.language('es-MX');
player.language('en-EU');
// set same language should not trigger the event so we expect 3 asserts not 4.
player.language('en-EU');
player.language('es-ES');
player.dispose();
});
QUnit.test('should return correct values for canPlayType', function(assert) {
const player = TestHelpers.makePlayer();

View File

@ -9,21 +9,9 @@ QUnit.module('PosterImage', {
this.poster1 = '#poster1';
this.poster2 = '#poster2';
// Create a mock player object that responds as a player would
this.mockPlayer = {
poster_: this.poster1,
poster() {
return this.poster_;
},
handler_: null,
off() {},
on(type, handler) {
this.handler_ = handler;
},
trigger(type) {
this.handler_.call();
}
};
this.mockPlayer = TestHelpers.makePlayer({
poster: this.poster1
});
},
afterEach() {}
});

View File

@ -13,6 +13,7 @@ import VideoTrackList from '../../../src/js/tracks/video-track-list';
import TextTrackList from '../../../src/js/tracks/text-track-list';
import sinon from 'sinon';
import log from '../../../src/js/utils/log.js';
import TestHelpers from '../test-helpers.js';
function stubbedSourceHandler(handler) {
return {
@ -663,7 +664,7 @@ QUnit.test('delegates only deferred deferrables to the source handler', function
QUnit.test('Tech.isTech returns correct answers for techs and components', function(assert) {
const isTech = Tech.isTech;
const tech = new Html5({}, {});
const button = new Button({}, {});
const button = new Button(TestHelpers.makePlayer(), {});
assert.ok(isTech(Tech), 'Tech is a Tech');
assert.ok(isTech(Html5), 'Html5 is a Tech');

View File

@ -57,8 +57,8 @@ QUnit.test('if native text tracks are not supported, create a texttrackdisplay',
const fakeTTDSpy = sinon.spy();
class FakeTTD extends Component {
constructor() {
super();
constructor(player, options) {
super(player, options);
fakeTTDSpy();
}
}

View File

@ -58,22 +58,18 @@ QUnit.test('Player track methods call the tech', function(assert) {
QUnit.test('TextTrackDisplay initializes tracks on player ready', function(assert) {
let calls = 0;
const ttd = new TextTrackDisplay({
on() {},
addTextTracks() {
calls--;
},
getChild() {
calls--;
},
ready() {
calls++;
}
}, {});
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) {