mirror of
https://github.com/videojs/video.js.git
synced 2025-01-04 06:48:49 +02:00
feat(fs): support FullscreenOptions (#5856)
On browsers that implement the Unprefixed Fullscreen API, pass a FullscreenOptions dictionary to requestFullscreen. Add `fullscreenOptions` option with default value of `{navigationUI: 'hide'}` to player. See https://fullscreen.spec.whatwg.org/#dictdef-fullscreenoptions
This commit is contained in:
parent
2878c1d0d4
commit
631ac3b68d
@ -29,6 +29,7 @@
|
|||||||
* [languages](#languages)
|
* [languages](#languages)
|
||||||
* [nativeControlsForTouch](#nativecontrolsfortouch)
|
* [nativeControlsForTouch](#nativecontrolsfortouch)
|
||||||
* [notSupportedMessage](#notsupportedmessage)
|
* [notSupportedMessage](#notsupportedmessage)
|
||||||
|
* [fullscreenOptions](#fullscreenoptions)
|
||||||
* [playbackRates](#playbackrates)
|
* [playbackRates](#playbackrates)
|
||||||
* [plugins](#plugins)
|
* [plugins](#plugins)
|
||||||
* [responsive](#responsive)
|
* [responsive](#responsive)
|
||||||
@ -277,6 +278,13 @@ Explicitly set a default value for [the associated tech option](#nativecontrolsf
|
|||||||
|
|
||||||
Allows overriding the default message that is displayed when Video.js cannot play back a media source.
|
Allows overriding the default message that is displayed when Video.js cannot play back a media source.
|
||||||
|
|
||||||
|
### `fullscreenOptions`
|
||||||
|
|
||||||
|
> Type: `Object`
|
||||||
|
> Default: `{navigationUI: 'hide'}`
|
||||||
|
|
||||||
|
See [The Fullscreen API Spec](https://fullscreen.spec.whatwg.org/#dictdef-fullscreenoptions) for more details.
|
||||||
|
|
||||||
### `playbackRates`
|
### `playbackRates`
|
||||||
|
|
||||||
> Type: `Array`
|
> Type: `Array`
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
import Button from '../button.js';
|
import Button from '../button.js';
|
||||||
import Component from '../component.js';
|
import Component from '../component.js';
|
||||||
import FullscreenApi from '../fullscreen-api.js';
|
|
||||||
import document from 'global/document';
|
import document from 'global/document';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,7 +25,7 @@ class FullscreenToggle extends Button {
|
|||||||
super(player, options);
|
super(player, options);
|
||||||
this.on(player, 'fullscreenchange', this.handleFullscreenChange);
|
this.on(player, 'fullscreenchange', this.handleFullscreenChange);
|
||||||
|
|
||||||
if (document[FullscreenApi.fullscreenEnabled] === false) {
|
if (document[player.fsApi_.fullscreenEnabled] === false) {
|
||||||
this.disable();
|
this.disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,9 @@ import document from 'global/document';
|
|||||||
* @see [Specification]{@link https://fullscreen.spec.whatwg.org}
|
* @see [Specification]{@link https://fullscreen.spec.whatwg.org}
|
||||||
* @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
|
* @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
|
||||||
*/
|
*/
|
||||||
const FullscreenApi = {};
|
const FullscreenApi = {
|
||||||
|
prefixed: true
|
||||||
|
};
|
||||||
|
|
||||||
// browser API methods
|
// browser API methods
|
||||||
const apiMap = [
|
const apiMap = [
|
||||||
@ -59,7 +61,6 @@ const apiMap = [
|
|||||||
|
|
||||||
const specApi = apiMap[0];
|
const specApi = apiMap[0];
|
||||||
let browserApi;
|
let browserApi;
|
||||||
let prefixedAPI = false;
|
|
||||||
|
|
||||||
// determine the supported set of functions
|
// determine the supported set of functions
|
||||||
for (let i = 0; i < apiMap.length; i++) {
|
for (let i = 0; i < apiMap.length; i++) {
|
||||||
@ -76,8 +77,7 @@ if (browserApi) {
|
|||||||
FullscreenApi[specApi[i]] = browserApi[i];
|
FullscreenApi[specApi[i]] = browserApi[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixedAPI = browserApi[0] === specApi[0];
|
FullscreenApi.prefixed = browserApi[0] !== specApi[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FullscreenApi;
|
export default FullscreenApi;
|
||||||
export { prefixedAPI };
|
|
||||||
|
@ -21,7 +21,7 @@ import toTitleCase, { titleCaseEquals } from './utils/to-title-case.js';
|
|||||||
import { createTimeRange } from './utils/time-ranges.js';
|
import { createTimeRange } from './utils/time-ranges.js';
|
||||||
import { bufferedPercent } from './utils/buffer.js';
|
import { bufferedPercent } from './utils/buffer.js';
|
||||||
import * as stylesheet from './utils/stylesheet.js';
|
import * as stylesheet from './utils/stylesheet.js';
|
||||||
import FullscreenApi, {prefixedAPI as prefixedFS} from './fullscreen-api.js';
|
import FullscreenApi from './fullscreen-api.js';
|
||||||
import MediaError from './media-error.js';
|
import MediaError from './media-error.js';
|
||||||
import safeParseTuple from 'safe-json-parse/tuple';
|
import safeParseTuple from 'safe-json-parse/tuple';
|
||||||
import {assign} from './utils/obj';
|
import {assign} from './utils/obj';
|
||||||
@ -358,6 +358,9 @@ class Player extends Component {
|
|||||||
// create logger
|
// create logger
|
||||||
this.log = createLogger(this.id_);
|
this.log = createLogger(this.id_);
|
||||||
|
|
||||||
|
// Hold our own reference to fullscreen api so it can be mocked in tests
|
||||||
|
this.fsApi_ = FullscreenApi;
|
||||||
|
|
||||||
// Tracks when a tech changes the poster
|
// Tracks when a tech changes the poster
|
||||||
this.isPosterFromTech_ = false;
|
this.isPosterFromTech_ = false;
|
||||||
|
|
||||||
@ -2007,10 +2010,10 @@ class Player extends Component {
|
|||||||
|
|
||||||
// If cancelling fullscreen, remove event listener.
|
// If cancelling fullscreen, remove event listener.
|
||||||
if (this.isFullscreen() === false) {
|
if (this.isFullscreen() === false) {
|
||||||
Events.off(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
|
Events.off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prefixedFS) {
|
if (this.fsApi_.prefixed) {
|
||||||
/**
|
/**
|
||||||
* @event Player#fullscreenchange
|
* @event Player#fullscreenchange
|
||||||
* @type {EventTarget~Event}
|
* @type {EventTarget~Event}
|
||||||
@ -2729,14 +2732,17 @@ class Player extends Component {
|
|||||||
* This includes most mobile devices (iOS, Android) and older versions of
|
* This includes most mobile devices (iOS, Android) and older versions of
|
||||||
* Safari.
|
* Safari.
|
||||||
*
|
*
|
||||||
|
* @param {Object} [fullscreenOptions]
|
||||||
|
* Override the player fullscreen options
|
||||||
|
*
|
||||||
* @fires Player#fullscreenchange
|
* @fires Player#fullscreenchange
|
||||||
*/
|
*/
|
||||||
requestFullscreen() {
|
requestFullscreen(fullscreenOptions) {
|
||||||
const fsApi = FullscreenApi;
|
let fsOptions;
|
||||||
|
|
||||||
this.isFullscreen(true);
|
this.isFullscreen(true);
|
||||||
|
|
||||||
if (fsApi.requestFullscreen) {
|
if (this.fsApi_.requestFullscreen) {
|
||||||
// the browser supports going fullscreen at the element level so we can
|
// the browser supports going fullscreen at the element level so we can
|
||||||
// take the controls fullscreen as well as the video
|
// take the controls fullscreen as well as the video
|
||||||
|
|
||||||
@ -2745,10 +2751,17 @@ class Player extends Component {
|
|||||||
// when canceling fullscreen. Otherwise if there's multiple
|
// when canceling fullscreen. Otherwise if there's multiple
|
||||||
// players on a page, they would all be reacting to the same fullscreen
|
// players on a page, they would all be reacting to the same fullscreen
|
||||||
// events
|
// events
|
||||||
Events.on(document, fsApi.fullscreenchange, this.boundDocumentFullscreenChange_);
|
Events.on(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
|
||||||
|
|
||||||
silencePromise(this.el_[fsApi.requestFullscreen]());
|
// only pass FullscreenOptions to requestFullscreen if it isn't prefixed
|
||||||
|
if (!this.fsApi_.prefixed) {
|
||||||
|
fsOptions = this.options_.fullscreenOptions;
|
||||||
|
if (fullscreenOptions !== undefined) {
|
||||||
|
fsOptions = fullscreenOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
silencePromise(this.el_[this.fsApi_.requestFullscreen](fsOptions));
|
||||||
} else if (this.tech_.supportsFullScreen()) {
|
} else if (this.tech_.supportsFullScreen()) {
|
||||||
// we can't take the video.js controls fullscreen but we can go fullscreen
|
// we can't take the video.js controls fullscreen but we can go fullscreen
|
||||||
// with native controls
|
// with native controls
|
||||||
@ -2771,13 +2784,11 @@ class Player extends Component {
|
|||||||
* @fires Player#fullscreenchange
|
* @fires Player#fullscreenchange
|
||||||
*/
|
*/
|
||||||
exitFullscreen() {
|
exitFullscreen() {
|
||||||
const fsApi = FullscreenApi;
|
|
||||||
|
|
||||||
this.isFullscreen(false);
|
this.isFullscreen(false);
|
||||||
|
|
||||||
// Check for browser element fullscreen support
|
// Check for browser element fullscreen support
|
||||||
if (fsApi.requestFullscreen) {
|
if (this.fsApi_.requestFullscreen) {
|
||||||
silencePromise(document[fsApi.exitFullscreen]());
|
silencePromise(document[this.fsApi_.exitFullscreen]());
|
||||||
} else if (this.tech_.supportsFullScreen()) {
|
} else if (this.tech_.supportsFullScreen()) {
|
||||||
this.techCall_('exitFullScreen');
|
this.techCall_('exitFullScreen');
|
||||||
} else {
|
} else {
|
||||||
@ -2997,7 +3008,7 @@ class Player extends Component {
|
|||||||
|
|
||||||
const FSToggle = Component.getComponent('FullscreenToggle');
|
const FSToggle = Component.getComponent('FullscreenToggle');
|
||||||
|
|
||||||
if (document[FullscreenApi.fullscreenEnabled] !== false) {
|
if (document[this.fsApi_.fullscreenEnabled] !== false) {
|
||||||
FSToggle.prototype.handleClick.call(this);
|
FSToggle.prototype.handleClick.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4679,6 +4690,10 @@ Player.prototype.options_ = {
|
|||||||
// Default message to show when a video cannot be played.
|
// Default message to show when a video cannot be played.
|
||||||
notSupportedMessage: 'No compatible source was found for this media.',
|
notSupportedMessage: 'No compatible source was found for this media.',
|
||||||
|
|
||||||
|
fullscreenOptions: {
|
||||||
|
navigationUI: 'hide'
|
||||||
|
},
|
||||||
|
|
||||||
breakpoints: {},
|
breakpoints: {},
|
||||||
responsive: false
|
responsive: false
|
||||||
};
|
};
|
||||||
|
202
test/unit/player-fullscreen.test.js
Normal file
202
test/unit/player-fullscreen.test.js
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/* eslint-env qunit */
|
||||||
|
import Player from '../../src/js/player.js';
|
||||||
|
import TestHelpers from './test-helpers.js';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
import window from 'global/window';
|
||||||
|
|
||||||
|
const FullscreenTestHelpers = {
|
||||||
|
makePlayer(prefixed, playerOptions, videoTag) {
|
||||||
|
const player = TestHelpers.makePlayer(playerOptions, videoTag);
|
||||||
|
|
||||||
|
player.fsApi_ = {
|
||||||
|
prefixed,
|
||||||
|
requestFullscreen: 'vjsRequestFullscreen',
|
||||||
|
exitFullscreen: 'vjsExitFullscreen',
|
||||||
|
fullscreenElement: 'vjsFullscreenElement',
|
||||||
|
fullscreenEnabled: 'vjsFullscreenEnabled',
|
||||||
|
fullscreenchange: 'vjsfullscreenchange',
|
||||||
|
fullscreenerror: 'vjsfullscreenerror'
|
||||||
|
};
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QUnit.module('Player Fullscreen', {
|
||||||
|
beforeEach(assert) {
|
||||||
|
this.clock = sinon.useFakeTimers();
|
||||||
|
// reset players storage
|
||||||
|
for (const playerId in Player.players) {
|
||||||
|
if (Player.players[playerId] !== null) {
|
||||||
|
Player.players[playerId].dispose();
|
||||||
|
}
|
||||||
|
delete Player.players[playerId];
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Element.prototype.vjsRequestFullscreen = function() {
|
||||||
|
assert.ok(false, 'vjsRequestFullscreen should not be called');
|
||||||
|
};
|
||||||
|
window.Element.prototype.vjsExitFullscreen = function() {
|
||||||
|
assert.ok(false, 'vjsExitFullscreen should not be called');
|
||||||
|
};
|
||||||
|
window.Element.prototype.vjsFullscreenElement = function() {
|
||||||
|
assert.ok(false, 'vjsFullscreenElement should not be called');
|
||||||
|
};
|
||||||
|
window.Element.prototype.vjsFullscreenEnabled = function() {
|
||||||
|
assert.ok(false, 'vjsFullscreenEnabled should not be called');
|
||||||
|
};
|
||||||
|
window.Element.prototype.vjsfullscreenchange = function() {
|
||||||
|
assert.ok(false, 'vjsfullscreenchange should not be called');
|
||||||
|
};
|
||||||
|
window.Element.prototype.vjsfullscreenerror = function() {
|
||||||
|
assert.ok(false, 'vjsfullscreenerror should not be called');
|
||||||
|
};
|
||||||
|
},
|
||||||
|
afterEach() {
|
||||||
|
this.clock.restore();
|
||||||
|
|
||||||
|
delete window.Element.prototype.vjsRequestFullscreen;
|
||||||
|
delete window.Element.prototype.vjsExitFullscreen;
|
||||||
|
delete window.Element.prototype.vjsFullscreenElement;
|
||||||
|
delete window.Element.prototype.vjsFullscreenEnabled;
|
||||||
|
delete window.Element.prototype.vjsfullscreenchange;
|
||||||
|
delete window.Element.prototype.vjsfullscreenerror;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fullscreenOptions should not be passed from player options on prefixed api', function(assert) {
|
||||||
|
|
||||||
|
const fullscreenOptions = {
|
||||||
|
navigationUI: 'test',
|
||||||
|
foo: 'bar'
|
||||||
|
};
|
||||||
|
|
||||||
|
const player = FullscreenTestHelpers.makePlayer(true, {
|
||||||
|
fullscreenOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
let requestFullscreenCalled = false;
|
||||||
|
let fsOpts;
|
||||||
|
|
||||||
|
window.Element.prototype.vjsRequestFullscreen = function(opts) {
|
||||||
|
requestFullscreenCalled = true;
|
||||||
|
fsOpts = opts;
|
||||||
|
};
|
||||||
|
|
||||||
|
player.requestFullscreen();
|
||||||
|
|
||||||
|
assert.ok(requestFullscreenCalled, 'vjsRequestFullscreen should be called');
|
||||||
|
assert.strictEqual(fsOpts, undefined, 'fullscreenOptions should not be passed');
|
||||||
|
|
||||||
|
player.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fullscreenOptions should be passed from player options on unprefixed api', function(assert) {
|
||||||
|
|
||||||
|
const fullscreenOptions = {
|
||||||
|
navigationUI: 'test',
|
||||||
|
foo: 'bar'
|
||||||
|
};
|
||||||
|
|
||||||
|
const player = FullscreenTestHelpers.makePlayer(false, {
|
||||||
|
fullscreenOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
let requestFullscreenCalled = false;
|
||||||
|
let fsOpts;
|
||||||
|
|
||||||
|
window.Element.prototype.vjsRequestFullscreen = function(opts) {
|
||||||
|
requestFullscreenCalled = true;
|
||||||
|
fsOpts = opts;
|
||||||
|
};
|
||||||
|
|
||||||
|
player.requestFullscreen();
|
||||||
|
|
||||||
|
assert.ok(requestFullscreenCalled, 'vjsRequestFullscreen should be called');
|
||||||
|
assert.notStrictEqual(fsOpts, undefined, 'fullscreenOptions should be passed');
|
||||||
|
assert.deepEqual(fsOpts, fullscreenOptions, 'fullscreenOptions should match player options');
|
||||||
|
|
||||||
|
player.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fullscreenOptions should not be passed from function arguments on prefixed api', function(assert) {
|
||||||
|
|
||||||
|
const fullscreenOptions = {
|
||||||
|
navigationUI: 'test',
|
||||||
|
foo: 'bar'
|
||||||
|
};
|
||||||
|
|
||||||
|
const player = FullscreenTestHelpers.makePlayer(true);
|
||||||
|
|
||||||
|
let requestFullscreenCalled = false;
|
||||||
|
let fsOpts;
|
||||||
|
|
||||||
|
window.Element.prototype.vjsRequestFullscreen = function(opts) {
|
||||||
|
requestFullscreenCalled = true;
|
||||||
|
fsOpts = opts;
|
||||||
|
};
|
||||||
|
|
||||||
|
player.requestFullscreen(fullscreenOptions);
|
||||||
|
|
||||||
|
assert.ok(requestFullscreenCalled, 'vjsRequestFullscreen should be called');
|
||||||
|
assert.strictEqual(fsOpts, undefined, 'fullscreenOptions should not be passed');
|
||||||
|
|
||||||
|
player.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fullscreenOptions should be passed from function arguments on unprefixed api', function(assert) {
|
||||||
|
|
||||||
|
const fullscreenOptions = {
|
||||||
|
navigationUI: 'test',
|
||||||
|
foo: 'bar'
|
||||||
|
};
|
||||||
|
|
||||||
|
const player = FullscreenTestHelpers.makePlayer(false);
|
||||||
|
|
||||||
|
let requestFullscreenCalled = false;
|
||||||
|
let fsOpts;
|
||||||
|
|
||||||
|
window.Element.prototype.vjsRequestFullscreen = function(opts) {
|
||||||
|
requestFullscreenCalled = true;
|
||||||
|
fsOpts = opts;
|
||||||
|
};
|
||||||
|
|
||||||
|
player.requestFullscreen(fullscreenOptions);
|
||||||
|
|
||||||
|
assert.ok(requestFullscreenCalled, 'vjsRequestFullscreen should be called');
|
||||||
|
assert.notStrictEqual(fsOpts, undefined, 'fullscreenOptions should be passed');
|
||||||
|
assert.deepEqual(fsOpts, fullscreenOptions, 'fullscreenOptions should match function args');
|
||||||
|
|
||||||
|
player.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('fullscreenOptions from function args should override player options', function(assert) {
|
||||||
|
|
||||||
|
const fullscreenOptions = {
|
||||||
|
navigationUI: 'args',
|
||||||
|
baz: 'bar'
|
||||||
|
};
|
||||||
|
|
||||||
|
const player = FullscreenTestHelpers.makePlayer(false, {
|
||||||
|
fullscreenOptions: {
|
||||||
|
navigationUI: 'playeroptions',
|
||||||
|
foo: 'bar'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let requestFullscreenCalled = false;
|
||||||
|
let fsOpts;
|
||||||
|
|
||||||
|
window.Element.prototype.vjsRequestFullscreen = function(opts) {
|
||||||
|
requestFullscreenCalled = true;
|
||||||
|
fsOpts = opts;
|
||||||
|
};
|
||||||
|
|
||||||
|
player.requestFullscreen(fullscreenOptions);
|
||||||
|
|
||||||
|
assert.ok(requestFullscreenCalled, 'vjsRequestFullscreen should be called');
|
||||||
|
assert.notStrictEqual(fsOpts, undefined, 'fullscreenOptions should be passed');
|
||||||
|
assert.deepEqual(fsOpts, fullscreenOptions, 'fullscreenOptions should match function args');
|
||||||
|
|
||||||
|
player.dispose();
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user