1
0
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:
Austin Morton 2019-06-17 14:05:22 -04:00 committed by Gary Katsevman
parent 2878c1d0d4
commit 631ac3b68d
5 changed files with 243 additions and 19 deletions

View File

@ -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`

View File

@ -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();
} }
} }

View File

@ -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 };

View File

@ -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
}; };

View 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();
});