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,4 +1,5 @@
1
- import window from '@ungap/global-this';
1
+ import document from 'global/document';
2
+ import window from 'global/window';
2
3
 
3
4
  import QUnit from 'qunit';
4
5
  import sinon from 'sinon';
@@ -15,28 +16,31 @@ QUnit.test('the environment is sane', function(assert) {
15
16
  assert.strictEqual(typeof plugin, 'function', 'plugin is a function');
16
17
  });
17
18
 
18
- QUnit.module('videojs-mobile-ui', {
19
+ const beforeEach = function() {
19
20
 
20
- beforeEach() {
21
+ // Mock the environment's timers because certain things - particularly
22
+ // player readiness - are asynchronous in video.js 5. This MUST come
23
+ // before any player is created; otherwise, timers could get created
24
+ // with the actual timer methods!
25
+ this.clock = sinon.useFakeTimers();
21
26
 
22
- // Mock the environment's timers because certain things - particularly
23
- // player readiness - are asynchronous in video.js 5. This MUST come
24
- // before any player is created; otherwise, timers could get created
25
- // with the actual timer methods!
26
- this.clock = sinon.useFakeTimers();
27
+ this.fixture = document.getElementById('qunit-fixture');
28
+ this.video = document.createElement('video');
29
+ this.fixture.appendChild(this.video);
27
30
 
28
- this.fixture = window.document.getElementById('qunit-fixture');
29
- this.video = window.document.createElement('video');
30
- this.fixture.appendChild(this.video);
31
- this.player = videojs(this.video);
32
- },
31
+ this.player = videojs(this.video);
32
+ };
33
33
 
34
- afterEach() {
35
- if (!this.player.isDisposed()) {
36
- this.player.dispose();
37
- }
38
- this.clock.restore();
34
+ const afterEach = function() {
35
+ if (!this.player.isDisposed()) {
36
+ this.player.dispose();
39
37
  }
38
+ this.clock.restore();
39
+ };
40
+
41
+ QUnit.module('videojs-mobile-ui', {
42
+ beforeEach,
43
+ afterEach
40
44
  });
41
45
 
42
46
  QUnit.test('registers itself with video.js', function(assert) {
@@ -48,6 +52,14 @@ QUnit.test('registers itself with video.js', function(assert) {
48
52
  );
49
53
  });
50
54
 
55
+ QUnit.test('initialises without errors', function(assert) {
56
+ this.player.mobileUi({ forceForTesting: true });
57
+
58
+ this.clock.tick(1);
59
+
60
+ assert.expect(0);
61
+ });
62
+
51
63
  QUnit.test('inserts element before control bar', function(assert) {
52
64
 
53
65
  this.player.mobileUi({forceForTesting: true});
@@ -177,3 +189,98 @@ QUnit[testOrSkip]('Android event listeners skipped if disabled', function(assert
177
189
 
178
190
  videojs.browser = oldBrowser;
179
191
  });
192
+
193
+ QUnit.test('Adds disable-end class if disableOnEnd option is true', function(assert) {
194
+ this.player.mobileUi({
195
+ forceForTesting: true,
196
+ touchControls: { disableOnEnd: true }
197
+ });
198
+
199
+ this.clock.tick(1);
200
+
201
+ assert.ok(this.player.hasClass('vjs-mobile-ui-disable-end'), 'Class added via option');
202
+ });
203
+
204
+ QUnit.test('Adds disable-end class if endscreen plugin is present', function(assert) {
205
+ this.player.endscreen = () => {};
206
+
207
+ this.player.mobileUi({
208
+ forceForTesting: true,
209
+ touchControls: { disableOnEnd: false }
210
+ });
211
+
212
+ this.clock.tick(1);
213
+
214
+ assert.ok(this.player.hasClass('vjs-mobile-ui-disable-end'), 'Class added via endscreen detection');
215
+ });
216
+
217
+ QUnit.module('TouchOverlay', {
218
+ beforeEach,
219
+ afterEach
220
+ });
221
+
222
+ QUnit.test('TouchOverlay: double tap right seeks forward', function(assert) {
223
+ // Setup
224
+ this.player.mobileUi({ forceForTesting: true });
225
+ this.clock.tick(1);
226
+
227
+ const touchOverlay = this.player.getChild('TouchOverlay');
228
+ const touchEl = touchOverlay.el_;
229
+ let currentTimeCache = 0;
230
+
231
+ // Mock bounding rect so clicks have a defined "right" side
232
+ // Width is 100, so > 60 is right side
233
+ sinon.stub(touchEl, 'getBoundingClientRect').returns({
234
+ left: 0,
235
+ width: 100
236
+ });
237
+
238
+ this.player.currentTime = (time) => {
239
+ if (time === undefined) {
240
+ return currentTimeCache;
241
+ }
242
+ currentTimeCache = time;
243
+ return currentTimeCache;
244
+ };
245
+
246
+ this.player.duration(60);
247
+ this.player.currentTime(10);
248
+
249
+ // Trigger first tap
250
+ touchOverlay.handleTap({
251
+ target: touchEl,
252
+ preventDefault: () => {},
253
+ changedTouches: [{ clientX: 90 }]
254
+ });
255
+
256
+ // Trigger second tap (double tap)
257
+ touchOverlay.handleTap({
258
+ target: touchEl,
259
+ preventDefault: () => {},
260
+ changedTouches: [{ clientX: 90 }]
261
+ });
262
+
263
+ // Fast forward debounce timer (default tapTimeout is 300ms)
264
+ this.clock.tick(310);
265
+ assert.equal(this.player.currentTime(), 20, 'Seeked forward 10 seconds (default)');
266
+
267
+ // Advance enough for requestAnimationFrame to trigger
268
+ this.clock.tick(50);
269
+ assert.ok(touchOverlay.hasClass('skip'), 'Skip animation class added');
270
+ });
271
+
272
+ QUnit.test('TouchOverlay: single tap toggles play/pause visibility', function(assert) {
273
+ this.player.mobileUi({ forceForTesting: true });
274
+ this.clock.tick(1);
275
+
276
+ const touchOverlay = this.player.getChild('TouchOverlay');
277
+
278
+ // Trigger single tap
279
+ touchOverlay.handleTap({
280
+ target: touchOverlay.el_,
281
+ preventDefault: () => {},
282
+ changedTouches: [{ clientX: 50 }]
283
+ });
284
+
285
+ assert.ok(touchOverlay.hasClass('show-play-toggle'), 'Play toggle is visible after single tap');
286
+ });
@@ -0,0 +1,365 @@
1
+ import document from 'global/document';
2
+ import window from 'global/window';
3
+
4
+ import QUnit from 'qunit';
5
+ import sinon from 'sinon';
6
+ import videojs from 'video.js';
7
+
8
+ import plugin from '../src/plugin';
9
+
10
+ const createTouch = (el, opts) => {
11
+ if ('Touch' in window) {
12
+ opts.identifier = Date.now();
13
+ opts.target = el;
14
+
15
+ return new window.Touch(opts);
16
+ }
17
+
18
+ return opts;
19
+ };
20
+
21
+ const createTouchEvent = (el, evt, opts) => {
22
+ if ('TouchEvent' in window) {
23
+ opts.identifier = Date.now();
24
+ opts.target = el;
25
+
26
+ opts.touches = opts.touches.map((t) => {
27
+ return createTouch(el, t);
28
+ });
29
+ opts.changedTouches = opts.changedTouches.map((t) => {
30
+ return createTouch(el, t);
31
+ });
32
+
33
+ return new window.TouchEvent(evt, opts);
34
+ }
35
+
36
+ opts.type = evt;
37
+
38
+ return opts;
39
+ };
40
+
41
+ const skipWithoutTouch = 'TouchEvent' in window ? 'test' : 'skip';
42
+
43
+ QUnit.test('the environment is sane', function(assert) {
44
+ assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
45
+ assert.strictEqual(typeof sinon, 'object', 'sinon exists');
46
+ assert.strictEqual(typeof videojs, 'function', 'videojs exists');
47
+ assert.strictEqual(typeof plugin, 'function', 'plugin is a function');
48
+ });
49
+
50
+ QUnit.module('swipeFullscreen', {
51
+
52
+ beforeEach() {
53
+ this.clock = sinon.useFakeTimers();
54
+ this.fixture = document.getElementById('qunit-fixture');
55
+ this.video = document.createElement('video');
56
+ this.fixture.appendChild(this.video);
57
+ this.player = videojs(this.video);
58
+ },
59
+
60
+ afterEach() {
61
+ if (!this.player.isDisposed()) {
62
+ this.player.dispose();
63
+ }
64
+ this.clock.restore();
65
+ }
66
+ });
67
+
68
+ QUnit.test('swipeToFullscreen: adds CSS class when enabled', function(assert) {
69
+ this.player.mobileUi({
70
+ fullscreen: {
71
+ swipeToFullscreen: true,
72
+ swipeFromFullscreen: false,
73
+ swipeThreshold: 50
74
+ },
75
+ forceForTesting: true
76
+ });
77
+
78
+ this.clock.tick(1);
79
+
80
+ assert.true(this.player.hasClass('using-fs-swipe-up'), 'adds using-fs-swipe-up class');
81
+ assert.false(this.player.hasClass('using-fs-swipe-down'), 'does not add using-fs-swipe-down class');
82
+ });
83
+
84
+ QUnit.test('swipeFromFullscreen: adds CSS class when enabled', function(assert) {
85
+ this.player.mobileUi({
86
+ fullscreen: {
87
+ swipeToFullscreen: false,
88
+ swipeFromFullscreen: true,
89
+ swipeThreshold: 50
90
+ },
91
+ forceForTesting: true
92
+ });
93
+
94
+ this.clock.tick(1);
95
+
96
+ assert.true(this.player.hasClass('using-fs-swipe-down'), 'adds using-fs-swipe-down class');
97
+ assert.false(this.player.hasClass('using-fs-swipe-up'), 'does not add using-fs-swipe-up class');
98
+ });
99
+
100
+ QUnit[skipWithoutTouch]('touch start outside fullscreen', function(assert) {
101
+ this.player.mobileUi({
102
+ fullscreen: {
103
+ swipeToFullscreen: true,
104
+ swipeFromFullscreen: true,
105
+ swipeThreshold: 50
106
+ },
107
+ forceForTesting: true
108
+ });
109
+
110
+ this.clock.tick(1);
111
+
112
+ const touchEvent = createTouchEvent(this.player.el(), 'touchstart', {
113
+ bubbles: true,
114
+ cancelable: true,
115
+ touches: [],
116
+ changedTouches: [{ clientY: 100 }]
117
+ });
118
+
119
+ this.player.el().dispatchEvent(touchEvent);
120
+
121
+ // Test that the event listener is properly set up
122
+ assert.expect(0);
123
+ });
124
+
125
+ QUnit[skipWithoutTouch]('prevents swiping when feature disabled', function(assert) {
126
+ this.player.mobileUi({
127
+ fullscreen: {
128
+ swipeToFullscreen: false,
129
+ swipeFromFullscreen: false,
130
+ swipeThreshold: 50
131
+ },
132
+ forceForTesting: true
133
+ });
134
+
135
+ this.clock.tick(1);
136
+
137
+ sinon.stub(this.player, 'requestFullscreen');
138
+
139
+ const touchStart = createTouchEvent(this.player.el(), 'touchstart', {
140
+ bubbles: true,
141
+ cancelable: true,
142
+ touches: [],
143
+ changedTouches: [{ clientY: 100 }]
144
+ });
145
+
146
+ const touchMove = createTouchEvent(this.player.el(), 'touchmove', {
147
+ bubbles: true,
148
+ cancelable: true,
149
+ touches: [{ clientY: 50 }],
150
+ changedTouches: []
151
+ });
152
+
153
+ const touchEnd = createTouchEvent(this.player.el(), 'touchend', {
154
+ bubbles: true,
155
+ cancelable: true,
156
+ touches: [],
157
+ changedTouches: [{ clientY: 50 }]
158
+ });
159
+
160
+ this.player.el().dispatchEvent(touchStart);
161
+ this.player.el().dispatchEvent(touchMove);
162
+ this.player.el().dispatchEvent(touchEnd);
163
+
164
+ assert.notOk(this.player.requestFullscreen.called, 'requestFullscreen not called when swipe disabled');
165
+ });
166
+
167
+ QUnit[skipWithoutTouch]('scales element on touch move', function(assert) {
168
+ this.player.mobileUi({
169
+ fullscreen: {
170
+ swipeToFullscreen: true,
171
+ swipeFromFullscreen: true,
172
+ swipeThreshold: 50
173
+ },
174
+ forceForTesting: true
175
+ });
176
+
177
+ this.clock.tick(1);
178
+
179
+ const techEl = this.player.tech_.el();
180
+
181
+ const touchStart = createTouchEvent(this.player.el(), 'touchstart', {
182
+ bubbles: true,
183
+ cancelable: true,
184
+ touches: [],
185
+ changedTouches: [{ clientY: 100 }]
186
+ });
187
+
188
+ const touchMove = createTouchEvent(this.player.el(), 'touchmove', {
189
+ bubbles: true,
190
+ cancelable: true,
191
+ touches: [{ clientY: 50 }],
192
+ changedTouches: []
193
+ });
194
+
195
+ this.player.el().dispatchEvent(touchStart);
196
+ this.player.el().dispatchEvent(touchMove);
197
+
198
+ assert.ok(techEl.style.transform.includes('scale('), 'applies scale transform on touch move');
199
+ });
200
+
201
+ QUnit[skipWithoutTouch]('enters fullscreen on swipe up', function(assert) {
202
+ this.player.mobileUi({
203
+ fullscreen: {
204
+ swipeToFullscreen: true,
205
+ swipeFromFullscreen: true,
206
+ swipeThreshold: 50
207
+ },
208
+ forceForTesting: true
209
+ });
210
+
211
+ this.clock.tick(1);
212
+
213
+ sinon.stub(this.player, 'isFullscreen').returns(false);
214
+ sinon.stub(this.player, 'requestFullscreen').returns(Promise.resolve());
215
+
216
+ const touchStart = createTouchEvent(this.player.el(), 'touchstart', {
217
+ bubbles: true,
218
+ cancelable: true,
219
+ touches: [],
220
+ changedTouches: [{ clientY: 200 }]
221
+ });
222
+
223
+ const touchEnd = createTouchEvent(this.player.el(), 'touchend', {
224
+ bubbles: true,
225
+ cancelable: true,
226
+ touches: [],
227
+ changedTouches: [{ clientY: 100 }]
228
+ });
229
+
230
+ this.player.el().dispatchEvent(touchStart);
231
+ this.player.el().dispatchEvent(touchEnd);
232
+
233
+ assert.true(this.player.requestFullscreen.called, 'requestFullscreen called on swipe up above threshold');
234
+ });
235
+
236
+ QUnit[skipWithoutTouch]('exits fullscreen on swipe down', function(assert) {
237
+ this.player.mobileUi({
238
+ fullscreen: {
239
+ swipeToFullscreen: true,
240
+ swipeFromFullscreen: true,
241
+ swipeThreshold: 50
242
+ },
243
+ forceForTesting: true
244
+ });
245
+
246
+ this.clock.tick(1);
247
+
248
+ sinon.stub(this.player, 'isFullscreen').returns(true);
249
+ sinon.stub(this.player, 'exitFullscreen');
250
+
251
+ const touchStart = createTouchEvent(this.player.el(), 'touchstart', {
252
+ bubbles: true,
253
+ cancelable: true,
254
+ touches: [],
255
+ changedTouches: [{ clientY: 100 }]
256
+ });
257
+
258
+ const touchEnd = createTouchEvent(this.player.el(), 'touchend', {
259
+ bubbles: true,
260
+ cancelable: true,
261
+ touches: [],
262
+ changedTouches: [{ clientY: 200 }]
263
+ });
264
+
265
+ this.player.el().dispatchEvent(touchStart);
266
+ this.player.el().dispatchEvent(touchEnd);
267
+
268
+ assert.true(this.player.exitFullscreen.called, 'exitFullscreen called on swipe down below threshold');
269
+ });
270
+
271
+ QUnit[skipWithoutTouch]('respects swipe threshold', function(assert) {
272
+ this.player.mobileUi({
273
+ fullscreen: {
274
+ swipeToFullscreen: true,
275
+ swipeFromFullscreen: true,
276
+ swipeThreshold: 100
277
+ },
278
+ forceForTesting: true
279
+ });
280
+
281
+ this.clock.tick(1);
282
+
283
+ sinon.stub(this.player, 'isFullscreen').returns(false);
284
+ sinon.stub(this.player, 'requestFullscreen').returns(Promise.resolve());
285
+
286
+ const touchStart = createTouchEvent(this.player.el(), 'touchstart', {
287
+ bubbles: true,
288
+ cancelable: true,
289
+ touches: [],
290
+ changedTouches: [{ clientY: 200 }]
291
+ });
292
+
293
+ // Small swipe (30px) - below 100px threshold
294
+ const touchEnd = createTouchEvent(this.player.el(), 'touchend', {
295
+ bubbles: true,
296
+ cancelable: true,
297
+ touches: [],
298
+ changedTouches: [{ clientY: 170 }]
299
+ });
300
+
301
+ this.player.el().dispatchEvent(touchStart);
302
+ this.player.el().dispatchEvent(touchEnd);
303
+
304
+ assert.notOk(this.player.requestFullscreen.called, 'requestFullscreen not called when swipe below threshold');
305
+ });
306
+
307
+ QUnit[skipWithoutTouch]('handles touchcancel event', function(assert) {
308
+ this.player.mobileUi({
309
+ fullscreen: {
310
+ swipeToFullscreen: true,
311
+ swipeFromFullscreen: true,
312
+ swipeThreshold: 50
313
+ },
314
+ forceForTesting: true
315
+ });
316
+
317
+ this.clock.tick(1);
318
+
319
+ sinon.stub(this.player, 'isFullscreen').returns(false);
320
+ sinon.stub(this.player, 'requestFullscreen').returns(Promise.resolve());
321
+
322
+ const touchStart = createTouchEvent(this.player.el(), 'touchstart', {
323
+ bubbles: true,
324
+ cancelable: true,
325
+ touches: [],
326
+ changedTouches: [{ clientY: 200 }]
327
+ });
328
+
329
+ const touchCancel = createTouchEvent(this.player.el(), 'touchcancel', {
330
+ bubbles: true,
331
+ cancelable: true,
332
+ touches: [],
333
+ changedTouches: [{ clientY: 100 }],
334
+ type: 'touchcancel'
335
+ });
336
+
337
+ this.player.el().dispatchEvent(touchStart);
338
+ this.player.el().dispatchEvent(touchCancel);
339
+
340
+ assert.notOk(this.player.requestFullscreen.called, 'requestFullscreen not called on touchcancel');
341
+ });
342
+
343
+ QUnit.test('cleans up event listeners on dispose', function(assert) {
344
+ this.player.mobileUi({
345
+ fullscreen: {
346
+ swipeToFullscreen: true,
347
+ swipeFromFullscreen: true,
348
+ swipeThreshold: 50
349
+ },
350
+ forceForTesting: true
351
+ });
352
+
353
+ this.clock.tick(1);
354
+
355
+ const rELSpy = sinon.spy(this.player.el(), 'removeEventListener');
356
+
357
+ this.player.dispose();
358
+
359
+ this.clock.tick(10);
360
+
361
+ assert.ok(rELSpy.calledWith('touchstart'), 'removeEventListener called during dispose');
362
+
363
+ rELSpy.restore();
364
+ });
365
+