videojs-mobile-ui 1.2.0-alpha.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -53
- package/dist/lang/de.js +1 -3
- package/dist/lang/en.js +1 -3
- package/dist/lang/it.js +1 -3
- package/dist/videojs-mobile-ui.cjs.js +195 -43
- package/dist/videojs-mobile-ui.css +104 -2
- package/dist/videojs-mobile-ui.es.js +195 -43
- package/dist/videojs-mobile-ui.js +204 -53
- package/dist/videojs-mobile-ui.min.js +2 -2
- package/docs/api/TouchOverlay.html +964 -0
- package/docs/api/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-Bold-webfont.svg +1830 -0
- package/docs/api/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docs/api/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
- package/docs/api/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docs/api/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-Italic-webfont.svg +1830 -0
- package/docs/api/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docs/api/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-Light-webfont.svg +1831 -0
- package/docs/api/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docs/api/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
- package/docs/api/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docs/api/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docs/api/fonts/OpenSans-Regular-webfont.svg +1831 -0
- package/docs/api/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docs/api/global.html +957 -0
- package/docs/api/index.html +159 -0
- package/docs/api/plugin.js.html +221 -0
- package/docs/api/scripts/linenumber.js +25 -0
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/api/scripts/prettify/lang-css.js +2 -0
- package/docs/api/scripts/prettify/prettify.js +28 -0
- package/docs/api/styles/jsdoc-default.css +358 -0
- package/docs/api/styles/prettify-jsdoc.css +111 -0
- package/docs/api/styles/prettify-tomorrow.css +132 -0
- package/docs/api/swipeFullscreen.js.html +173 -0
- package/docs/api/touchOverlay.js.html +211 -0
- package/index.html +238 -170
- package/package.json +23 -13
- package/scripts/lang.js +24 -0
- package/scripts/netlify.js +16 -0
- package/scripts/postcss.config.js +29 -5
- package/scripts/readme-options.js +370 -0
- package/scripts/rollup.config.js +0 -8
- package/src/plugin.css +6 -0
- package/src/plugin.js +65 -39
- package/src/swipeFullscreen.js +122 -0
- package/src/touchOverlay.js +7 -3
- package/test/plugin.test.js +125 -18
- package/test/swipeFullscreen.test.js +365 -0
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
/*! @name videojs-mobile-ui @version 1.2.
|
|
1
|
+
/*! @name videojs-mobile-ui @version 1.2.1 @license MIT */
|
|
2
2
|
import videojs from 'video.js';
|
|
3
|
-
import window from '
|
|
3
|
+
import window from 'global/window';
|
|
4
4
|
|
|
5
|
-
var version = "1.2.
|
|
5
|
+
var version = "1.2.1";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @file touchOverlay.js
|
|
9
9
|
* Touch UI component
|
|
10
10
|
*/
|
|
11
|
+
|
|
12
|
+
/** @import Player from 'video.js/dist/types/player' */
|
|
13
|
+
|
|
11
14
|
const Component = videojs.getComponent('Component');
|
|
12
15
|
const dom = videojs.dom || videojs;
|
|
13
16
|
|
|
@@ -23,7 +26,7 @@ class TouchOverlay extends Component {
|
|
|
23
26
|
* @param {Player} player
|
|
24
27
|
* The `Player` that this class should be attached to.
|
|
25
28
|
*
|
|
26
|
-
* @param {
|
|
29
|
+
* @param {options} [options]
|
|
27
30
|
* The key/value store of player options.
|
|
28
31
|
*/
|
|
29
32
|
constructor(player, options) {
|
|
@@ -112,7 +115,9 @@ class TouchOverlay extends Component {
|
|
|
112
115
|
if (event.target !== this.el_) {
|
|
113
116
|
return;
|
|
114
117
|
}
|
|
115
|
-
event.
|
|
118
|
+
if (event.cancelable) {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
}
|
|
116
121
|
this.taps += 1;
|
|
117
122
|
if (this.taps === 1) {
|
|
118
123
|
this.removeClass('skip');
|
|
@@ -138,13 +143,184 @@ class TouchOverlay extends Component {
|
|
|
138
143
|
}
|
|
139
144
|
Component.registerComponent('TouchOverlay', TouchOverlay);
|
|
140
145
|
|
|
141
|
-
|
|
146
|
+
/** @import Player from 'video.js/dist/types/player' */
|
|
147
|
+
/** @import Plugin from 'video.js/dist/types/plugin' */
|
|
148
|
+
/** @import {MobileUiOptions} from './plugin' */
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Sets up swiping to enter and exit fullscreen.
|
|
152
|
+
*
|
|
153
|
+
* @this {Plugin}
|
|
154
|
+
* @param {Player} player
|
|
155
|
+
* The player to initialise on.
|
|
156
|
+
* @param {MobileUiOptions} pluginOptions
|
|
157
|
+
* The options used by the mobile ui plugin.
|
|
158
|
+
*/
|
|
159
|
+
const initSwipe = (player, pluginOptions) => {
|
|
160
|
+
const {
|
|
161
|
+
swipeToFullscreen,
|
|
162
|
+
swipeFromFullscreen
|
|
163
|
+
} = pluginOptions.fullscreen;
|
|
164
|
+
if (swipeToFullscreen) {
|
|
165
|
+
player.addClass('using-fs-swipe-up');
|
|
166
|
+
}
|
|
167
|
+
if (swipeFromFullscreen) {
|
|
168
|
+
player.addClass('using-fs-swipe-down');
|
|
169
|
+
}
|
|
170
|
+
let touchStartY = 0;
|
|
171
|
+
let couldBeSwiping = false;
|
|
172
|
+
const swipeThreshold = 30;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Monitor the possible start of a swipe
|
|
176
|
+
*
|
|
177
|
+
* @param {TouchEvent} e Triggering touch event
|
|
178
|
+
*/
|
|
179
|
+
const onStart = e => {
|
|
180
|
+
const isFullscreen = player.isFullscreen();
|
|
181
|
+
if (!isFullscreen && !swipeToFullscreen || isFullscreen && !swipeFromFullscreen) {
|
|
182
|
+
couldBeSwiping = false;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
touchStartY = e.changedTouches[0].clientY;
|
|
186
|
+
couldBeSwiping = true;
|
|
187
|
+
player.tech_.el().style.transition = '';
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Monitor the movement of a swipe
|
|
192
|
+
*
|
|
193
|
+
* @param {TouchEvent} e Triggering touch event
|
|
194
|
+
*/
|
|
195
|
+
const onMove = e => {
|
|
196
|
+
if (!couldBeSwiping) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const currentY = e.touches[0].clientY;
|
|
200
|
+
const deltaY = touchStartY - currentY;
|
|
201
|
+
const isFullscreen = player.isFullscreen();
|
|
202
|
+
let scale = 1;
|
|
203
|
+
if (!isFullscreen && deltaY > 0) {
|
|
204
|
+
// Swiping up to enter fullscreen: Zoom in (Max 1.1)
|
|
205
|
+
scale = 1 + Math.min(0.1, deltaY / 500);
|
|
206
|
+
player.tech_.el().style.transform = `scale(${scale})`;
|
|
207
|
+
} else if (isFullscreen && deltaY < 0) {
|
|
208
|
+
// Swiping down to exit fullscreen: Zoom out (Min 0.9)
|
|
209
|
+
scale = 1 - Math.min(0.1, Math.abs(deltaY) / 500);
|
|
210
|
+
player.tech_.el().style.transform = `scale(${scale})`;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Monitor the touch end to determine a valid swipe
|
|
216
|
+
*
|
|
217
|
+
* @param {TouchEvent} e Triggering touch event
|
|
218
|
+
*/
|
|
219
|
+
const onEnd = e => {
|
|
220
|
+
if (!couldBeSwiping) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
couldBeSwiping = false;
|
|
224
|
+
player.tech_.el().style.transition = 'transform 0.3s ease-out';
|
|
225
|
+
player.tech_.el().style.transform = 'scale(1)';
|
|
226
|
+
if (e.type === 'touchcancel') {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const touchEndY = e.changedTouches[0].clientY;
|
|
230
|
+
const deltaY = touchStartY - touchEndY;
|
|
231
|
+
if (deltaY > swipeThreshold && !player.isFullscreen()) {
|
|
232
|
+
player.requestFullscreen().catch(err => {
|
|
233
|
+
player.log.warn('Browser refused fullscreen', err);
|
|
234
|
+
});
|
|
235
|
+
} else if (deltaY < -swipeThreshold && player.isFullscreen()) {
|
|
236
|
+
player.exitFullscreen();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
player.el().addEventListener('touchstart', onStart, {
|
|
240
|
+
passive: true
|
|
241
|
+
});
|
|
242
|
+
player.el().addEventListener('touchmove', onMove, {
|
|
243
|
+
passive: true
|
|
244
|
+
});
|
|
245
|
+
player.el().addEventListener('touchend', onEnd, {
|
|
246
|
+
passive: true
|
|
247
|
+
});
|
|
248
|
+
player.el().addEventListener('touchcancel', onEnd, {
|
|
249
|
+
passive: true
|
|
250
|
+
});
|
|
251
|
+
player.on('dispose', () => {
|
|
252
|
+
player.el().removeEventListener('touchstart', onStart, {
|
|
253
|
+
passive: true
|
|
254
|
+
});
|
|
255
|
+
player.el().removeEventListener('touchmove', onMove, {
|
|
256
|
+
passive: true
|
|
257
|
+
});
|
|
258
|
+
player.el().removeEventListener('touchend', onEnd, {
|
|
259
|
+
passive: true
|
|
260
|
+
});
|
|
261
|
+
player.el().removeEventListener('touchcancel', onEnd, {
|
|
262
|
+
passive: true
|
|
263
|
+
});
|
|
264
|
+
player.tech_.el().style.transform = '';
|
|
265
|
+
player.tech_.el().style.transition = '';
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @typedef {Object} MobileUiOptions
|
|
271
|
+
* @property {Object} [fullscreen]
|
|
272
|
+
* Options for fullscreen behaviours.
|
|
273
|
+
* @property {boolean} [fullscreen.enterOnRotate]
|
|
274
|
+
* If the device is rotated, enter fullscreen.
|
|
275
|
+
* Default `true`.
|
|
276
|
+
* @property {boolean} [fullscreen.exitOnRotate]
|
|
277
|
+
* If the device is rotated, exit fullscreen, unless `lockOnRotate` is used.
|
|
278
|
+
* Default `true`.
|
|
279
|
+
* @property {boolean} [fullscreen.lockOnRotate]
|
|
280
|
+
* When going fullscreen in response to rotation (`enterOnRotate`), also lock the orientation (not supported by iOS).
|
|
281
|
+
* Default `true`.
|
|
282
|
+
* @property {boolean} [fullscreen.lockToLandscapeOnEnter]
|
|
283
|
+
* When fullscreen is entered by any means, lock the orientation (not supported by iOS).
|
|
284
|
+
* Default `false`.
|
|
285
|
+
* @property {boolean} [fullscreen.swipeToFullscreen]
|
|
286
|
+
* Swipe up to enter fullscreen.
|
|
287
|
+
* Default `false`.
|
|
288
|
+
* @property {boolean} [fullscreen.swipeFromFullscreen]
|
|
289
|
+
* Swipe down to exit fullscreen.
|
|
290
|
+
* Won't do anything on iOS native fullscreen, which has its own swipe down exit gesture.
|
|
291
|
+
* Default `false`.
|
|
292
|
+
* @property {boolean} [fullscreen.disabled]
|
|
293
|
+
* All fullscreen functionality provided by this plugin disabled.
|
|
294
|
+
* Default `false`.
|
|
295
|
+
* @property {Object} [touchControls]
|
|
296
|
+
* Options for tap overlay.
|
|
297
|
+
* @property {number} [touchControls.seekSeconds]
|
|
298
|
+
* Increment to seek in seconds.
|
|
299
|
+
* Default `10`.
|
|
300
|
+
* @property {number} [touchControls.tapTimeout]
|
|
301
|
+
* Timeout to consider multiple taps as double rather than two single.
|
|
302
|
+
* Default `300`.
|
|
303
|
+
* @property {boolean} [touchControls.disableOnEnd]
|
|
304
|
+
* Disable the touch overlay when the video ends.
|
|
305
|
+
* Useful if an end screen overlay is used to avoid conflict.
|
|
306
|
+
* Default `false`.
|
|
307
|
+
* @property {boolean} [touchControls.disabled]
|
|
308
|
+
* All tap overlay functionality provided by this plugin disabled.
|
|
309
|
+
* Default `false`.
|
|
310
|
+
* @internal
|
|
311
|
+
* @property {boolean} [forceForTesting]
|
|
312
|
+
* Used in unit tests
|
|
313
|
+
*/
|
|
314
|
+
|
|
315
|
+
/** @type {MobileUiOptions} */
|
|
142
316
|
const defaults = {
|
|
143
317
|
fullscreen: {
|
|
144
318
|
enterOnRotate: true,
|
|
145
319
|
exitOnRotate: true,
|
|
146
320
|
lockOnRotate: true,
|
|
147
321
|
lockToLandscapeOnEnter: false,
|
|
322
|
+
swipeToFullscreen: false,
|
|
323
|
+
swipeFromFullscreen: false,
|
|
148
324
|
disabled: false
|
|
149
325
|
},
|
|
150
326
|
touchControls: {
|
|
@@ -187,7 +363,7 @@ const getOrientation = () => {
|
|
|
187
363
|
* @param {Player} player
|
|
188
364
|
* A Video.js player object.
|
|
189
365
|
*
|
|
190
|
-
* @param {
|
|
366
|
+
* @param {MobileUiOptions} [options={}]
|
|
191
367
|
* A plain object containing options for the plugin.
|
|
192
368
|
*/
|
|
193
369
|
const onPlayerReady = (player, options) => {
|
|
@@ -204,17 +380,22 @@ const onPlayerReady = (player, options) => {
|
|
|
204
380
|
if (options.fullscreen.disabled) {
|
|
205
381
|
return;
|
|
206
382
|
}
|
|
383
|
+
if (options.fullscreen.swipeToFullscreen || options.fullscreen.swipeFromFullscreen) {
|
|
384
|
+
initSwipe(player, options);
|
|
385
|
+
}
|
|
207
386
|
let locked = false;
|
|
208
387
|
const rotationHandler = () => {
|
|
209
388
|
const currentOrientation = getOrientation();
|
|
210
389
|
if (currentOrientation === 'landscape' && options.fullscreen.enterOnRotate) {
|
|
211
|
-
if (player.paused()
|
|
212
|
-
player.requestFullscreen()
|
|
390
|
+
if (!player.paused() && !player.isFullscreen()) {
|
|
391
|
+
player.requestFullscreen().catch(err => {
|
|
392
|
+
player.log.warn('Browser refused fullscreen request:', err);
|
|
393
|
+
});
|
|
213
394
|
if ((options.fullscreen.lockOnRotate || options.fullscreen.lockToLandscapeOnEnter) && screen.orientation && screen.orientation.lock) {
|
|
214
395
|
screen.orientation.lock('landscape').then(() => {
|
|
215
396
|
locked = true;
|
|
216
|
-
}).catch(
|
|
217
|
-
videojs.log('Browser refused orientation lock:',
|
|
397
|
+
}).catch(err => {
|
|
398
|
+
videojs.log.warn('Browser refused orientation lock:', err);
|
|
218
399
|
});
|
|
219
400
|
}
|
|
220
401
|
}
|
|
@@ -239,6 +420,7 @@ const onPlayerReady = (player, options) => {
|
|
|
239
420
|
}
|
|
240
421
|
}
|
|
241
422
|
player.on('fullscreenchange', _ => {
|
|
423
|
+
player.log('fullscreenchange', player.isFullscreen(), options.fullscreen.lockToLandscapeOnEnter, getOrientation());
|
|
242
424
|
if (player.isFullscreen() && options.fullscreen.lockToLandscapeOnEnter && getOrientation() === 'portrait') {
|
|
243
425
|
screen.orientation.lock('landscape').then(() => {
|
|
244
426
|
locked = true;
|
|
@@ -259,40 +441,10 @@ const onPlayerReady = (player, options) => {
|
|
|
259
441
|
};
|
|
260
442
|
|
|
261
443
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* Adds a monile UI for player control, and fullscreen orientation control
|
|
444
|
+
* Adds a mobile UI for player control, and fullscreen orientation control
|
|
265
445
|
*
|
|
266
446
|
* @function mobileUi
|
|
267
|
-
* @param {
|
|
268
|
-
* Plugin options.
|
|
269
|
-
* @param {boolean} [options.forceForTesting=false]
|
|
270
|
-
* Enables the display regardless of user agent, for testing purposes
|
|
271
|
-
* @param {Object} [options.fullscreen={}]
|
|
272
|
-
* Fullscreen options.
|
|
273
|
-
* @param {boolean} [options.fullscreen.disabled=false]
|
|
274
|
-
* If true no fullscreen handling except the *deprecated* iOS fullwindow hack
|
|
275
|
-
* @param {boolean} [options.fullscreen.enterOnRotate=true]
|
|
276
|
-
* Whether to go fullscreen when rotating to landscape
|
|
277
|
-
* @param {boolean} [options.fullscreen.exitOnRotate=true]
|
|
278
|
-
* Whether to leave fullscreen when rotating to portrait (if not locked)
|
|
279
|
-
* @param {boolean} [options.fullscreen.lockOnRotate=true]
|
|
280
|
-
* Whether to lock orientation when rotating to landscape
|
|
281
|
-
* Unlocked when exiting fullscreen or on 'ended
|
|
282
|
-
* @param {boolean} [options.fullscreen.lockToLandscapeOnEnter=false]
|
|
283
|
-
* Whether to always lock orientation to landscape on fullscreen mode
|
|
284
|
-
* Unlocked when exiting fullscreen or on 'ended'
|
|
285
|
-
* @param {Object} [options.touchControls={}]
|
|
286
|
-
* Touch UI options.
|
|
287
|
-
* @param {boolean} [options.touchControls.disabled=false]
|
|
288
|
-
* If true no touch controls are added.
|
|
289
|
-
* @param {int} [options.touchControls.seekSeconds=10]
|
|
290
|
-
* Number of seconds to seek on double-tap
|
|
291
|
-
* @param {int} [options.touchControls.tapTimeout=300]
|
|
292
|
-
* Interval in ms to be considered a doubletap
|
|
293
|
-
* @param {boolean} [options.touchControls.disableOnEnd=false]
|
|
294
|
-
* Whether to disable when the video ends (e.g., if there is an endscreen)
|
|
295
|
-
* Never shows if the endscreen plugin is present
|
|
447
|
+
* @param {MobileUiOptions} [options={}] Plugin options
|
|
296
448
|
*/
|
|
297
449
|
const mobileUi = function (options = {}) {
|
|
298
450
|
if (options.forceForTesting || videojs.browser.IS_ANDROID || videojs.browser.IS_IOS) {
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
/*! @name videojs-mobile-ui @version 1.2.
|
|
1
|
+
/*! @name videojs-mobile-ui @version 1.2.1 @license MIT */
|
|
2
2
|
(function (global, factory) {
|
|
3
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')
|
|
4
|
-
typeof define === 'function' && define.amd ? define(['video.js'
|
|
5
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsMobileUi = factory(global.videojs
|
|
6
|
-
})(this, (function (videojs
|
|
3
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) :
|
|
4
|
+
typeof define === 'function' && define.amd ? define(['video.js'], factory) :
|
|
5
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsMobileUi = factory(global.videojs));
|
|
6
|
+
})(this, (function (videojs) { 'use strict';
|
|
7
7
|
|
|
8
8
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
9
9
|
|
|
10
10
|
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
|
|
11
|
-
var window__default = /*#__PURE__*/_interopDefaultLegacy(window);
|
|
12
11
|
|
|
13
|
-
var version = "1.2.
|
|
12
|
+
var version = "1.2.1";
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* @file touchOverlay.js
|
|
17
16
|
* Touch UI component
|
|
18
17
|
*/
|
|
18
|
+
|
|
19
|
+
/** @import Player from 'video.js/dist/types/player' */
|
|
20
|
+
|
|
19
21
|
const Component = videojs__default["default"].getComponent('Component');
|
|
20
22
|
const dom = videojs__default["default"].dom || videojs__default["default"];
|
|
21
23
|
|
|
@@ -31,7 +33,7 @@
|
|
|
31
33
|
* @param {Player} player
|
|
32
34
|
* The `Player` that this class should be attached to.
|
|
33
35
|
*
|
|
34
|
-
* @param {
|
|
36
|
+
* @param {options} [options]
|
|
35
37
|
* The key/value store of player options.
|
|
36
38
|
*/
|
|
37
39
|
constructor(player, options) {
|
|
@@ -86,7 +88,7 @@
|
|
|
86
88
|
// Remove and readd class to trigger animation
|
|
87
89
|
this.setAttribute('data-skip-text', `${increment} ${this.localize('seconds')}`);
|
|
88
90
|
this.removeClass('skip');
|
|
89
|
-
|
|
91
|
+
window.requestAnimationFrame(() => {
|
|
90
92
|
this.addClass('skip');
|
|
91
93
|
});
|
|
92
94
|
}, this.tapTimeout);
|
|
@@ -120,7 +122,9 @@
|
|
|
120
122
|
if (event.target !== this.el_) {
|
|
121
123
|
return;
|
|
122
124
|
}
|
|
123
|
-
event.
|
|
125
|
+
if (event.cancelable) {
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
}
|
|
124
128
|
this.taps += 1;
|
|
125
129
|
if (this.taps === 1) {
|
|
126
130
|
this.removeClass('skip');
|
|
@@ -146,13 +150,184 @@
|
|
|
146
150
|
}
|
|
147
151
|
Component.registerComponent('TouchOverlay', TouchOverlay);
|
|
148
152
|
|
|
149
|
-
|
|
153
|
+
/** @import Player from 'video.js/dist/types/player' */
|
|
154
|
+
/** @import Plugin from 'video.js/dist/types/plugin' */
|
|
155
|
+
/** @import {MobileUiOptions} from './plugin' */
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Sets up swiping to enter and exit fullscreen.
|
|
159
|
+
*
|
|
160
|
+
* @this {Plugin}
|
|
161
|
+
* @param {Player} player
|
|
162
|
+
* The player to initialise on.
|
|
163
|
+
* @param {MobileUiOptions} pluginOptions
|
|
164
|
+
* The options used by the mobile ui plugin.
|
|
165
|
+
*/
|
|
166
|
+
const initSwipe = (player, pluginOptions) => {
|
|
167
|
+
const {
|
|
168
|
+
swipeToFullscreen,
|
|
169
|
+
swipeFromFullscreen
|
|
170
|
+
} = pluginOptions.fullscreen;
|
|
171
|
+
if (swipeToFullscreen) {
|
|
172
|
+
player.addClass('using-fs-swipe-up');
|
|
173
|
+
}
|
|
174
|
+
if (swipeFromFullscreen) {
|
|
175
|
+
player.addClass('using-fs-swipe-down');
|
|
176
|
+
}
|
|
177
|
+
let touchStartY = 0;
|
|
178
|
+
let couldBeSwiping = false;
|
|
179
|
+
const swipeThreshold = 30;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Monitor the possible start of a swipe
|
|
183
|
+
*
|
|
184
|
+
* @param {TouchEvent} e Triggering touch event
|
|
185
|
+
*/
|
|
186
|
+
const onStart = e => {
|
|
187
|
+
const isFullscreen = player.isFullscreen();
|
|
188
|
+
if (!isFullscreen && !swipeToFullscreen || isFullscreen && !swipeFromFullscreen) {
|
|
189
|
+
couldBeSwiping = false;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
touchStartY = e.changedTouches[0].clientY;
|
|
193
|
+
couldBeSwiping = true;
|
|
194
|
+
player.tech_.el().style.transition = '';
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Monitor the movement of a swipe
|
|
199
|
+
*
|
|
200
|
+
* @param {TouchEvent} e Triggering touch event
|
|
201
|
+
*/
|
|
202
|
+
const onMove = e => {
|
|
203
|
+
if (!couldBeSwiping) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const currentY = e.touches[0].clientY;
|
|
207
|
+
const deltaY = touchStartY - currentY;
|
|
208
|
+
const isFullscreen = player.isFullscreen();
|
|
209
|
+
let scale = 1;
|
|
210
|
+
if (!isFullscreen && deltaY > 0) {
|
|
211
|
+
// Swiping up to enter fullscreen: Zoom in (Max 1.1)
|
|
212
|
+
scale = 1 + Math.min(0.1, deltaY / 500);
|
|
213
|
+
player.tech_.el().style.transform = `scale(${scale})`;
|
|
214
|
+
} else if (isFullscreen && deltaY < 0) {
|
|
215
|
+
// Swiping down to exit fullscreen: Zoom out (Min 0.9)
|
|
216
|
+
scale = 1 - Math.min(0.1, Math.abs(deltaY) / 500);
|
|
217
|
+
player.tech_.el().style.transform = `scale(${scale})`;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Monitor the touch end to determine a valid swipe
|
|
223
|
+
*
|
|
224
|
+
* @param {TouchEvent} e Triggering touch event
|
|
225
|
+
*/
|
|
226
|
+
const onEnd = e => {
|
|
227
|
+
if (!couldBeSwiping) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
couldBeSwiping = false;
|
|
231
|
+
player.tech_.el().style.transition = 'transform 0.3s ease-out';
|
|
232
|
+
player.tech_.el().style.transform = 'scale(1)';
|
|
233
|
+
if (e.type === 'touchcancel') {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const touchEndY = e.changedTouches[0].clientY;
|
|
237
|
+
const deltaY = touchStartY - touchEndY;
|
|
238
|
+
if (deltaY > swipeThreshold && !player.isFullscreen()) {
|
|
239
|
+
player.requestFullscreen().catch(err => {
|
|
240
|
+
player.log.warn('Browser refused fullscreen', err);
|
|
241
|
+
});
|
|
242
|
+
} else if (deltaY < -swipeThreshold && player.isFullscreen()) {
|
|
243
|
+
player.exitFullscreen();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
player.el().addEventListener('touchstart', onStart, {
|
|
247
|
+
passive: true
|
|
248
|
+
});
|
|
249
|
+
player.el().addEventListener('touchmove', onMove, {
|
|
250
|
+
passive: true
|
|
251
|
+
});
|
|
252
|
+
player.el().addEventListener('touchend', onEnd, {
|
|
253
|
+
passive: true
|
|
254
|
+
});
|
|
255
|
+
player.el().addEventListener('touchcancel', onEnd, {
|
|
256
|
+
passive: true
|
|
257
|
+
});
|
|
258
|
+
player.on('dispose', () => {
|
|
259
|
+
player.el().removeEventListener('touchstart', onStart, {
|
|
260
|
+
passive: true
|
|
261
|
+
});
|
|
262
|
+
player.el().removeEventListener('touchmove', onMove, {
|
|
263
|
+
passive: true
|
|
264
|
+
});
|
|
265
|
+
player.el().removeEventListener('touchend', onEnd, {
|
|
266
|
+
passive: true
|
|
267
|
+
});
|
|
268
|
+
player.el().removeEventListener('touchcancel', onEnd, {
|
|
269
|
+
passive: true
|
|
270
|
+
});
|
|
271
|
+
player.tech_.el().style.transform = '';
|
|
272
|
+
player.tech_.el().style.transition = '';
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @typedef {Object} MobileUiOptions
|
|
278
|
+
* @property {Object} [fullscreen]
|
|
279
|
+
* Options for fullscreen behaviours.
|
|
280
|
+
* @property {boolean} [fullscreen.enterOnRotate]
|
|
281
|
+
* If the device is rotated, enter fullscreen.
|
|
282
|
+
* Default `true`.
|
|
283
|
+
* @property {boolean} [fullscreen.exitOnRotate]
|
|
284
|
+
* If the device is rotated, exit fullscreen, unless `lockOnRotate` is used.
|
|
285
|
+
* Default `true`.
|
|
286
|
+
* @property {boolean} [fullscreen.lockOnRotate]
|
|
287
|
+
* When going fullscreen in response to rotation (`enterOnRotate`), also lock the orientation (not supported by iOS).
|
|
288
|
+
* Default `true`.
|
|
289
|
+
* @property {boolean} [fullscreen.lockToLandscapeOnEnter]
|
|
290
|
+
* When fullscreen is entered by any means, lock the orientation (not supported by iOS).
|
|
291
|
+
* Default `false`.
|
|
292
|
+
* @property {boolean} [fullscreen.swipeToFullscreen]
|
|
293
|
+
* Swipe up to enter fullscreen.
|
|
294
|
+
* Default `false`.
|
|
295
|
+
* @property {boolean} [fullscreen.swipeFromFullscreen]
|
|
296
|
+
* Swipe down to exit fullscreen.
|
|
297
|
+
* Won't do anything on iOS native fullscreen, which has its own swipe down exit gesture.
|
|
298
|
+
* Default `false`.
|
|
299
|
+
* @property {boolean} [fullscreen.disabled]
|
|
300
|
+
* All fullscreen functionality provided by this plugin disabled.
|
|
301
|
+
* Default `false`.
|
|
302
|
+
* @property {Object} [touchControls]
|
|
303
|
+
* Options for tap overlay.
|
|
304
|
+
* @property {number} [touchControls.seekSeconds]
|
|
305
|
+
* Increment to seek in seconds.
|
|
306
|
+
* Default `10`.
|
|
307
|
+
* @property {number} [touchControls.tapTimeout]
|
|
308
|
+
* Timeout to consider multiple taps as double rather than two single.
|
|
309
|
+
* Default `300`.
|
|
310
|
+
* @property {boolean} [touchControls.disableOnEnd]
|
|
311
|
+
* Disable the touch overlay when the video ends.
|
|
312
|
+
* Useful if an end screen overlay is used to avoid conflict.
|
|
313
|
+
* Default `false`.
|
|
314
|
+
* @property {boolean} [touchControls.disabled]
|
|
315
|
+
* All tap overlay functionality provided by this plugin disabled.
|
|
316
|
+
* Default `false`.
|
|
317
|
+
* @internal
|
|
318
|
+
* @property {boolean} [forceForTesting]
|
|
319
|
+
* Used in unit tests
|
|
320
|
+
*/
|
|
321
|
+
|
|
322
|
+
/** @type {MobileUiOptions} */
|
|
150
323
|
const defaults = {
|
|
151
324
|
fullscreen: {
|
|
152
325
|
enterOnRotate: true,
|
|
153
326
|
exitOnRotate: true,
|
|
154
327
|
lockOnRotate: true,
|
|
155
328
|
lockToLandscapeOnEnter: false,
|
|
329
|
+
swipeToFullscreen: false,
|
|
330
|
+
swipeFromFullscreen: false,
|
|
156
331
|
disabled: false
|
|
157
332
|
},
|
|
158
333
|
touchControls: {
|
|
@@ -162,7 +337,7 @@
|
|
|
162
337
|
disabled: false
|
|
163
338
|
}
|
|
164
339
|
};
|
|
165
|
-
const screen =
|
|
340
|
+
const screen = window.screen;
|
|
166
341
|
|
|
167
342
|
/**
|
|
168
343
|
* Gets 'portrait' or 'lanscape' from the two orientation APIs
|
|
@@ -179,8 +354,8 @@
|
|
|
179
354
|
}
|
|
180
355
|
|
|
181
356
|
// iOS only supports window.orientation
|
|
182
|
-
if (typeof
|
|
183
|
-
if (
|
|
357
|
+
if (typeof window.orientation === 'number') {
|
|
358
|
+
if (window.orientation === 0 || window.orientation === 180) {
|
|
184
359
|
return 'portrait';
|
|
185
360
|
}
|
|
186
361
|
return 'landscape';
|
|
@@ -195,7 +370,7 @@
|
|
|
195
370
|
* @param {Player} player
|
|
196
371
|
* A Video.js player object.
|
|
197
372
|
*
|
|
198
|
-
* @param {
|
|
373
|
+
* @param {MobileUiOptions} [options={}]
|
|
199
374
|
* A plain object containing options for the plugin.
|
|
200
375
|
*/
|
|
201
376
|
const onPlayerReady = (player, options) => {
|
|
@@ -212,17 +387,22 @@
|
|
|
212
387
|
if (options.fullscreen.disabled) {
|
|
213
388
|
return;
|
|
214
389
|
}
|
|
390
|
+
if (options.fullscreen.swipeToFullscreen || options.fullscreen.swipeFromFullscreen) {
|
|
391
|
+
initSwipe(player, options);
|
|
392
|
+
}
|
|
215
393
|
let locked = false;
|
|
216
394
|
const rotationHandler = () => {
|
|
217
395
|
const currentOrientation = getOrientation();
|
|
218
396
|
if (currentOrientation === 'landscape' && options.fullscreen.enterOnRotate) {
|
|
219
|
-
if (player.paused()
|
|
220
|
-
player.requestFullscreen()
|
|
397
|
+
if (!player.paused() && !player.isFullscreen()) {
|
|
398
|
+
player.requestFullscreen().catch(err => {
|
|
399
|
+
player.log.warn('Browser refused fullscreen request:', err);
|
|
400
|
+
});
|
|
221
401
|
if ((options.fullscreen.lockOnRotate || options.fullscreen.lockToLandscapeOnEnter) && screen.orientation && screen.orientation.lock) {
|
|
222
402
|
screen.orientation.lock('landscape').then(() => {
|
|
223
403
|
locked = true;
|
|
224
|
-
}).catch(
|
|
225
|
-
videojs__default["default"].log('Browser refused orientation lock:',
|
|
404
|
+
}).catch(err => {
|
|
405
|
+
videojs__default["default"].log.warn('Browser refused orientation lock:', err);
|
|
226
406
|
});
|
|
227
407
|
}
|
|
228
408
|
}
|
|
@@ -234,9 +414,9 @@
|
|
|
234
414
|
};
|
|
235
415
|
if (options.fullscreen.enterOnRotate || options.fullscreen.exitOnRotate) {
|
|
236
416
|
if (videojs__default["default"].browser.IS_IOS) {
|
|
237
|
-
|
|
417
|
+
window.addEventListener('orientationchange', rotationHandler);
|
|
238
418
|
player.on('dispose', () => {
|
|
239
|
-
|
|
419
|
+
window.removeEventListener('orientationchange', rotationHandler);
|
|
240
420
|
});
|
|
241
421
|
} else if (screen.orientation) {
|
|
242
422
|
// addEventListener('orientationchange') is not a user interaction on Android
|
|
@@ -247,6 +427,7 @@
|
|
|
247
427
|
}
|
|
248
428
|
}
|
|
249
429
|
player.on('fullscreenchange', _ => {
|
|
430
|
+
player.log('fullscreenchange', player.isFullscreen(), options.fullscreen.lockToLandscapeOnEnter, getOrientation());
|
|
250
431
|
if (player.isFullscreen() && options.fullscreen.lockToLandscapeOnEnter && getOrientation() === 'portrait') {
|
|
251
432
|
screen.orientation.lock('landscape').then(() => {
|
|
252
433
|
locked = true;
|
|
@@ -267,40 +448,10 @@
|
|
|
267
448
|
};
|
|
268
449
|
|
|
269
450
|
/**
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
* Adds a monile UI for player control, and fullscreen orientation control
|
|
451
|
+
* Adds a mobile UI for player control, and fullscreen orientation control
|
|
273
452
|
*
|
|
274
453
|
* @function mobileUi
|
|
275
|
-
* @param {
|
|
276
|
-
* Plugin options.
|
|
277
|
-
* @param {boolean} [options.forceForTesting=false]
|
|
278
|
-
* Enables the display regardless of user agent, for testing purposes
|
|
279
|
-
* @param {Object} [options.fullscreen={}]
|
|
280
|
-
* Fullscreen options.
|
|
281
|
-
* @param {boolean} [options.fullscreen.disabled=false]
|
|
282
|
-
* If true no fullscreen handling except the *deprecated* iOS fullwindow hack
|
|
283
|
-
* @param {boolean} [options.fullscreen.enterOnRotate=true]
|
|
284
|
-
* Whether to go fullscreen when rotating to landscape
|
|
285
|
-
* @param {boolean} [options.fullscreen.exitOnRotate=true]
|
|
286
|
-
* Whether to leave fullscreen when rotating to portrait (if not locked)
|
|
287
|
-
* @param {boolean} [options.fullscreen.lockOnRotate=true]
|
|
288
|
-
* Whether to lock orientation when rotating to landscape
|
|
289
|
-
* Unlocked when exiting fullscreen or on 'ended
|
|
290
|
-
* @param {boolean} [options.fullscreen.lockToLandscapeOnEnter=false]
|
|
291
|
-
* Whether to always lock orientation to landscape on fullscreen mode
|
|
292
|
-
* Unlocked when exiting fullscreen or on 'ended'
|
|
293
|
-
* @param {Object} [options.touchControls={}]
|
|
294
|
-
* Touch UI options.
|
|
295
|
-
* @param {boolean} [options.touchControls.disabled=false]
|
|
296
|
-
* If true no touch controls are added.
|
|
297
|
-
* @param {int} [options.touchControls.seekSeconds=10]
|
|
298
|
-
* Number of seconds to seek on double-tap
|
|
299
|
-
* @param {int} [options.touchControls.tapTimeout=300]
|
|
300
|
-
* Interval in ms to be considered a doubletap
|
|
301
|
-
* @param {boolean} [options.touchControls.disableOnEnd=false]
|
|
302
|
-
* Whether to disable when the video ends (e.g., if there is an endscreen)
|
|
303
|
-
* Never shows if the endscreen plugin is present
|
|
454
|
+
* @param {MobileUiOptions} [options={}] Plugin options
|
|
304
455
|
*/
|
|
305
456
|
const mobileUi = function (options = {}) {
|
|
306
457
|
if (options.forceForTesting || videojs__default["default"].browser.IS_ANDROID || videojs__default["default"].browser.IS_IOS) {
|