mirror of
https://github.com/videojs/video.js.git
synced 2025-01-02 06:32:07 +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)
|
||||
* [nativeControlsForTouch](#nativecontrolsfortouch)
|
||||
* [notSupportedMessage](#notsupportedmessage)
|
||||
* [fullscreenOptions](#fullscreenoptions)
|
||||
* [playbackRates](#playbackrates)
|
||||
* [plugins](#plugins)
|
||||
* [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.
|
||||
|
||||
### `fullscreenOptions`
|
||||
|
||||
> Type: `Object`
|
||||
> Default: `{navigationUI: 'hide'}`
|
||||
|
||||
See [The Fullscreen API Spec](https://fullscreen.spec.whatwg.org/#dictdef-fullscreenoptions) for more details.
|
||||
|
||||
### `playbackRates`
|
||||
|
||||
> Type: `Array`
|
||||
|
@ -3,7 +3,6 @@
|
||||
*/
|
||||
import Button from '../button.js';
|
||||
import Component from '../component.js';
|
||||
import FullscreenApi from '../fullscreen-api.js';
|
||||
import document from 'global/document';
|
||||
|
||||
/**
|
||||
@ -26,7 +25,7 @@ class FullscreenToggle extends Button {
|
||||
super(player, options);
|
||||
this.on(player, 'fullscreenchange', this.handleFullscreenChange);
|
||||
|
||||
if (document[FullscreenApi.fullscreenEnabled] === false) {
|
||||
if (document[player.fsApi_.fullscreenEnabled] === false) {
|
||||
this.disable();
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,9 @@ import document from 'global/document';
|
||||
* @see [Specification]{@link https://fullscreen.spec.whatwg.org}
|
||||
* @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
|
||||
*/
|
||||
const FullscreenApi = {};
|
||||
const FullscreenApi = {
|
||||
prefixed: true
|
||||
};
|
||||
|
||||
// browser API methods
|
||||
const apiMap = [
|
||||
@ -59,7 +61,6 @@ const apiMap = [
|
||||
|
||||
const specApi = apiMap[0];
|
||||
let browserApi;
|
||||
let prefixedAPI = false;
|
||||
|
||||
// determine the supported set of functions
|
||||
for (let i = 0; i < apiMap.length; i++) {
|
||||
@ -76,8 +77,7 @@ if (browserApi) {
|
||||
FullscreenApi[specApi[i]] = browserApi[i];
|
||||
}
|
||||
|
||||
prefixedAPI = browserApi[0] === specApi[0];
|
||||
FullscreenApi.prefixed = browserApi[0] !== specApi[0];
|
||||
}
|
||||
|
||||
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 { bufferedPercent } from './utils/buffer.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 safeParseTuple from 'safe-json-parse/tuple';
|
||||
import {assign} from './utils/obj';
|
||||
@ -358,6 +358,9 @@ class Player extends Component {
|
||||
// create logger
|
||||
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
|
||||
this.isPosterFromTech_ = false;
|
||||
|
||||
@ -2007,10 +2010,10 @@ class Player extends Component {
|
||||
|
||||
// If cancelling fullscreen, remove event listener.
|
||||
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
|
||||
* @type {EventTarget~Event}
|
||||
@ -2729,14 +2732,17 @@ class Player extends Component {
|
||||
* This includes most mobile devices (iOS, Android) and older versions of
|
||||
* Safari.
|
||||
*
|
||||
* @param {Object} [fullscreenOptions]
|
||||
* Override the player fullscreen options
|
||||
*
|
||||
* @fires Player#fullscreenchange
|
||||
*/
|
||||
requestFullscreen() {
|
||||
const fsApi = FullscreenApi;
|
||||
requestFullscreen(fullscreenOptions) {
|
||||
let fsOptions;
|
||||
|
||||
this.isFullscreen(true);
|
||||
|
||||
if (fsApi.requestFullscreen) {
|
||||
if (this.fsApi_.requestFullscreen) {
|
||||
// the browser supports going fullscreen at the element level so we can
|
||||
// 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
|
||||
// players on a page, they would all be reacting to the same fullscreen
|
||||
// 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()) {
|
||||
// we can't take the video.js controls fullscreen but we can go fullscreen
|
||||
// with native controls
|
||||
@ -2771,13 +2784,11 @@ class Player extends Component {
|
||||
* @fires Player#fullscreenchange
|
||||
*/
|
||||
exitFullscreen() {
|
||||
const fsApi = FullscreenApi;
|
||||
|
||||
this.isFullscreen(false);
|
||||
|
||||
// Check for browser element fullscreen support
|
||||
if (fsApi.requestFullscreen) {
|
||||
silencePromise(document[fsApi.exitFullscreen]());
|
||||
if (this.fsApi_.requestFullscreen) {
|
||||
silencePromise(document[this.fsApi_.exitFullscreen]());
|
||||
} else if (this.tech_.supportsFullScreen()) {
|
||||
this.techCall_('exitFullScreen');
|
||||
} else {
|
||||
@ -2997,7 +3008,7 @@ class Player extends Component {
|
||||
|
||||
const FSToggle = Component.getComponent('FullscreenToggle');
|
||||
|
||||
if (document[FullscreenApi.fullscreenEnabled] !== false) {
|
||||
if (document[this.fsApi_.fullscreenEnabled] !== false) {
|
||||
FSToggle.prototype.handleClick.call(this);
|
||||
}
|
||||
|
||||
@ -4679,6 +4690,10 @@ Player.prototype.options_ = {
|
||||
// Default message to show when a video cannot be played.
|
||||
notSupportedMessage: 'No compatible source was found for this media.',
|
||||
|
||||
fullscreenOptions: {
|
||||
navigationUI: 'hide'
|
||||
},
|
||||
|
||||
breakpoints: {},
|
||||
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