1
0
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:
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)
* [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`

View File

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

View File

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

View File

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

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