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

feat(player): Add playbackRates() method (#7228)

Adds a new playbackRates() method that takes an Array of numbers
representing the rates that are wanted to show up in the playback rates
menu. When new rates are given, a playbackrateschange event will trigger, which
will be used by the PlaybackRatesMenuButton to update itself.

An empty array will hide the menu. No value will return the currently
set playback rates. Other values will be ignored.

Fixes #7198
This commit is contained in:
Gary Katsevman 2021-06-08 11:01:56 -04:00 committed by GitHub
parent 39de502f72
commit 6259ef79e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 149 additions and 15 deletions

View File

@ -2,7 +2,6 @@
* @file playback-rate-menu-button.js
*/
import MenuButton from '../../menu/menu-button.js';
import Menu from '../../menu/menu.js';
import PlaybackRateMenuItem from './playback-rate-menu-item.js';
import Component from '../../component.js';
import * as Dom from '../../utils/dom.js';
@ -33,6 +32,7 @@ class PlaybackRateMenuButton extends MenuButton {
this.on(player, 'loadstart', (e) => this.updateVisibility(e));
this.on(player, 'ratechange', (e) => this.updateLabel(e));
this.on(player, 'playbackrateschange', (e) => this.handlePlaybackRateschange(e));
}
/**
@ -78,22 +78,18 @@ class PlaybackRateMenuButton extends MenuButton {
}
/**
* Create the playback rate menu
* Create the list of menu items. Specific to each subclass.
*
* @return {Menu}
* Menu object populated with {@link PlaybackRateMenuItem}s
*/
createMenu() {
const menu = new Menu(this.player());
createItems() {
const rates = this.playbackRates();
const items = [];
if (rates) {
for (let i = rates.length - 1; i >= 0; i--) {
menu.addChild(new PlaybackRateMenuItem(this.player(), {rate: rates[i] + 'x'}));
}
for (let i = rates.length - 1; i >= 0; i--) {
items.push(new PlaybackRateMenuItem(this.player(), {rate: rates[i] + 'x'}));
}
return menu;
return items;
}
/**
@ -132,6 +128,15 @@ class PlaybackRateMenuButton extends MenuButton {
this.player().playbackRate(newRate);
}
/**
* On playbackrateschange, update the menu to account for the new items.
*
* @listens Player#playbackrateschange
*/
handlePlaybackRateschange(event) {
this.update();
}
/**
* Get possible playback rates
*
@ -139,7 +144,7 @@ class PlaybackRateMenuButton extends MenuButton {
* All possible playback rates
*/
playbackRates() {
return this.options_.playbackRates || (this.options_.playerOptions && this.options_.playerOptions.playbackRates);
return this.player().playbackRates() || [];
}
/**

View File

@ -26,7 +26,7 @@ class PlaybackRateMenuItem extends MenuItem {
// Modify options for parent MenuItem class's init.
options.label = label;
options.selected = rate === 1;
options.selected = rate === player.playbackRate();
options.selectable = true;
options.multiSelectable = false;

View File

@ -508,6 +508,8 @@ class Player extends Component {
this.middleware_ = [];
this.playbackRates(options.playbackRates);
this.initChildren();
// Set isAudio based on whether or not an audio tag was used
@ -2218,6 +2220,7 @@ class Player extends Component {
src: '',
source: {},
sources: [],
playbackRates: [],
volume: 1
};
}
@ -4858,6 +4861,45 @@ class Player extends Component {
this.previousLogLevel_ = undefined;
this.debugEnabled_ = false;
}
}
/**
* Set or get current playback rates.
* Takes an array and updates the playback rates menu with the new items.
* Pass in an empty array to hide the menu.
* Values other than arrays are ignored.
*
* @fires Player#playbackrateschange
* @param {number[]} newRates
* The new rates that the playback rates menu should update to.
* An empty array will hide the menu
* @return {number[]} When used as a getter will return the current playback rates
*/
playbackRates(newRates) {
if (newRates === undefined) {
return this.cache_.playbackRates;
}
// ignore any value that isn't an array
if (!Array.isArray(newRates)) {
return;
}
// ignore any arrays that don't only contain numbers
if (!newRates.every((rate) => typeof rate === 'number')) {
return;
}
this.cache_.playbackRates = newRates;
/**
* fires when the playback rates in a player are changed
*
* @event Player#playbackrateschange
* @type {EventTarget~Event}
*/
this.trigger('playbackrateschange');
}
}

View File

@ -141,13 +141,49 @@ QUnit.test('calculateDistance should use changedTouches, if available', function
slider.dispose();
});
QUnit.test('should hide playback rate control if it\'s not supported', function(assert) {
QUnit.test('playback rate button is hidden by default', function(assert) {
assert.expect(1);
const player = TestHelpers.makePlayer();
const playbackRate = new PlaybackRateMenuButton(player);
assert.ok(playbackRate.el().className.indexOf('vjs-hidden') >= 0, 'playbackRate is not hidden');
assert.ok(playbackRate.el().className.indexOf('vjs-hidden') >= 0, 'playbackRate is hidden');
player.dispose();
playbackRate.dispose();
});
QUnit.test('playback rate button is not hidden if playback rates are set', function(assert) {
assert.expect(1);
const player = TestHelpers.makePlayer({
playbackRates: [1, 2, 3]
});
const playbackRate = new PlaybackRateMenuButton(player);
assert.ok(playbackRate.el().className.indexOf('vjs-hidden') === -1, 'playbackRate is not hidden');
player.dispose();
playbackRate.dispose();
});
QUnit.test('should show or hide playback rate menu button on playback rates change', function(assert) {
const rates = [1, 2, 3];
const norates = [];
let playbackRatesReturnValue = rates;
const player = TestHelpers.makePlayer();
player.playbackRates = () => playbackRatesReturnValue;
const playbackRate = new PlaybackRateMenuButton(player);
assert.ok(playbackRate.el().className.indexOf('vjs-hidden') === -1, 'playbackRate is not hidden');
playbackRatesReturnValue = norates;
player.trigger('playbackrateschange');
assert.ok(playbackRate.el().className.indexOf('vjs-hidden') >= 0, 'playbackRate is hidden');
player.dispose();
playbackRate.dispose();

View File

@ -1809,6 +1809,7 @@ QUnit.test('player#reset clears the player cache', function(assert) {
player.src(sources);
player.duration(10);
player.playbackRate(0.5);
player.playbackRates([1, 2, 3]);
player.volume(0.2);
assert.strictEqual(player.currentSrc(), sources[0].src, 'currentSrc is correct');
@ -1816,6 +1817,7 @@ QUnit.test('player#reset clears the player cache', function(assert) {
assert.deepEqual(player.currentSources(), sources, 'currentSources is correct');
assert.strictEqual(player.duration(), 10, 'duration is correct');
assert.strictEqual(player.playbackRate(), 0.5, 'playbackRate is correct');
assert.deepEqual(player.playbackRates(), [1, 2, 3], 'playbackRates is correct');
assert.strictEqual(player.volume(), 0.2, 'volume is correct');
assert.strictEqual(player.lastVolume_(), 0.2, 'lastVolume_ is correct');
@ -1832,6 +1834,7 @@ QUnit.test('player#reset clears the player cache', function(assert) {
assert.strictEqual(player.getCache().currentTime, 0, 'currentTime is correct');
assert.ok(isNaN(player.duration()), 'duration is correct');
assert.strictEqual(player.playbackRate(), 1, 'playbackRate is correct');
assert.deepEqual(player.playbackRates(), [], 'playbackRates is correct');
assert.strictEqual(player.volume(), 1, 'volume is correct');
assert.strictEqual(player.lastVolume_(), 1, 'lastVolume_ is correct');
});
@ -2599,3 +2602,51 @@ QUnit[testOrSkip]('Should only allow requestPictureInPicture if the tech support
player.requestPictureInPicture();
assert.equal(count, 1, 'requestPictureInPicture not passed through when tech does not support');
});
QUnit.test('playbackRates should trigger a playbackrateschange event', function(assert) {
const player = TestHelpers.makePlayer({});
const rates = [];
let rateschangeCount = 0;
player.on('playbackrateschange', function() {
rates.push(player.playbackRates());
rateschangeCount++;
});
player.playbackRates([1, 2, 3]);
player.playbackRates([]);
player.playbackRates([1, 4]);
assert.equal(rateschangeCount, 3, 'we got 3 playbackrateschange events');
assert.deepEqual(rates[0], [1, 2, 3], 'first rates is 1,2,3');
assert.deepEqual(rates[1], [], 'second rates is empty');
assert.deepEqual(rates[2], [1, 4], 'third rates is 1,4');
player.dispose();
});
QUnit.test('playbackRates only accepts arrays of numbers', function(assert) {
const player = TestHelpers.makePlayer();
let rateschangeCount = 0;
player.on('playbackrateschange', function() {
rateschangeCount++;
});
player.playbackRates([1, 2, 3]);
assert.equal(rateschangeCount, 1, 'we got a playbackrateschange event');
player.playbackRates('hello');
assert.equal(rateschangeCount, 1, 'we did not get a playbackrateschange event');
player.playbackRates([1, 4]);
assert.equal(rateschangeCount, 2, 'we got a playbackrateschange event');
player.playbackRates(5);
assert.equal(rateschangeCount, 2, 'we did not get a playbackrateschange event');
player.playbackRates(['hello', '2', 'why?']);
assert.equal(rateschangeCount, 2, 'we did not get a playbackrateschange event');
player.dispose();
});