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.
Files changed (53) hide show
  1. package/README.md +104 -53
  2. package/dist/lang/de.js +1 -3
  3. package/dist/lang/en.js +1 -3
  4. package/dist/lang/it.js +1 -3
  5. package/dist/videojs-mobile-ui.cjs.js +195 -43
  6. package/dist/videojs-mobile-ui.css +104 -2
  7. package/dist/videojs-mobile-ui.es.js +195 -43
  8. package/dist/videojs-mobile-ui.js +204 -53
  9. package/dist/videojs-mobile-ui.min.js +2 -2
  10. package/docs/api/TouchOverlay.html +964 -0
  11. package/docs/api/fonts/OpenSans-Bold-webfont.eot +0 -0
  12. package/docs/api/fonts/OpenSans-Bold-webfont.svg +1830 -0
  13. package/docs/api/fonts/OpenSans-Bold-webfont.woff +0 -0
  14. package/docs/api/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  15. package/docs/api/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  16. package/docs/api/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  17. package/docs/api/fonts/OpenSans-Italic-webfont.eot +0 -0
  18. package/docs/api/fonts/OpenSans-Italic-webfont.svg +1830 -0
  19. package/docs/api/fonts/OpenSans-Italic-webfont.woff +0 -0
  20. package/docs/api/fonts/OpenSans-Light-webfont.eot +0 -0
  21. package/docs/api/fonts/OpenSans-Light-webfont.svg +1831 -0
  22. package/docs/api/fonts/OpenSans-Light-webfont.woff +0 -0
  23. package/docs/api/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  24. package/docs/api/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  25. package/docs/api/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  26. package/docs/api/fonts/OpenSans-Regular-webfont.eot +0 -0
  27. package/docs/api/fonts/OpenSans-Regular-webfont.svg +1831 -0
  28. package/docs/api/fonts/OpenSans-Regular-webfont.woff +0 -0
  29. package/docs/api/global.html +957 -0
  30. package/docs/api/index.html +159 -0
  31. package/docs/api/plugin.js.html +221 -0
  32. package/docs/api/scripts/linenumber.js +25 -0
  33. package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -0
  34. package/docs/api/scripts/prettify/lang-css.js +2 -0
  35. package/docs/api/scripts/prettify/prettify.js +28 -0
  36. package/docs/api/styles/jsdoc-default.css +358 -0
  37. package/docs/api/styles/prettify-jsdoc.css +111 -0
  38. package/docs/api/styles/prettify-tomorrow.css +132 -0
  39. package/docs/api/swipeFullscreen.js.html +173 -0
  40. package/docs/api/touchOverlay.js.html +211 -0
  41. package/index.html +238 -170
  42. package/package.json +23 -13
  43. package/scripts/lang.js +24 -0
  44. package/scripts/netlify.js +16 -0
  45. package/scripts/postcss.config.js +29 -5
  46. package/scripts/readme-options.js +370 -0
  47. package/scripts/rollup.config.js +0 -8
  48. package/src/plugin.css +6 -0
  49. package/src/plugin.js +65 -39
  50. package/src/swipeFullscreen.js +122 -0
  51. package/src/touchOverlay.js +7 -3
  52. package/test/plugin.test.js +125 -18
  53. package/test/swipeFullscreen.test.js +365 -0
@@ -1,13 +1,16 @@
1
- /*! @name videojs-mobile-ui @version 1.2.0-alpha.0 @license MIT */
1
+ /*! @name videojs-mobile-ui @version 1.2.1 @license MIT */
2
2
  import videojs from 'video.js';
3
- import window from '@ungap/global-this';
3
+ import window from 'global/window';
4
4
 
5
- var version = "1.2.0-alpha.0";
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 {Object} [options]
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.preventDefault();
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
- // Default options for the plugin.
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 {Object} [options={}]
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() === false) {
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(e => {
217
- videojs.log('Browser refused orientation lock:', e);
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
- * A video.js plugin.
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 {Object} [options={}]
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.0-alpha.0 @license MIT */
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'), require('@ungap/global-this')) :
4
- typeof define === 'function' && define.amd ? define(['video.js', '@ungap/global-this'], factory) :
5
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsMobileUi = factory(global.videojs, global.window));
6
- })(this, (function (videojs, window) { 'use strict';
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.0-alpha.0";
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 {Object} [options]
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
- window__default["default"].requestAnimationFrame(() => {
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.preventDefault();
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
- // Default options for the plugin.
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 = window__default["default"].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 window__default["default"].orientation === 'number') {
183
- if (window__default["default"].orientation === 0 || window__default["default"].orientation === 180) {
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 {Object} [options={}]
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() === false) {
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(e => {
225
- videojs__default["default"].log('Browser refused orientation lock:', e);
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
- window__default["default"].addEventListener('orientationchange', rotationHandler);
417
+ window.addEventListener('orientationchange', rotationHandler);
238
418
  player.on('dispose', () => {
239
- window__default["default"].removeEventListener('orientationchange', rotationHandler);
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
- * A video.js plugin.
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 {Object} [options={}]
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) {