videojs-mobile-ui 0.7.0 → 0.9.0-beta.3
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 +114 -46
- package/dist/videojs-mobile-ui.cjs.js +275 -223
- package/dist/videojs-mobile-ui.css +2 -2
- package/dist/videojs-mobile-ui.es.js +270 -217
- package/dist/videojs-mobile-ui.js +278 -265
- 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 +206 -65
- package/package.json +19 -16
- package/src/plugin.css +21 -3
- package/src/plugin.js +32 -59
- package/src/swipeFullscreen.js +117 -0
- package/src/touchOverlay.js +62 -56
- package/test/plugin.test.js +128 -20
- package/test/swipeFullscreen.test.js +365 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets up swiping to enter and exit fullscreen.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} player
|
|
5
|
+
* The player to initialise on.
|
|
6
|
+
* @param {Object} pluginOptions
|
|
7
|
+
* The options used by the mobile ui plugin.
|
|
8
|
+
*/
|
|
9
|
+
const initSwipe = (player, pluginOptions) => {
|
|
10
|
+
const {swipeToFullscreen, swipeFromFullscreen} = pluginOptions.fullscreen;
|
|
11
|
+
|
|
12
|
+
if (swipeToFullscreen) {
|
|
13
|
+
player.addClass('using-fs-swipe-up');
|
|
14
|
+
}
|
|
15
|
+
if (swipeFromFullscreen) {
|
|
16
|
+
player.addClass('using-fs-swipe-down');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let touchStartY = 0;
|
|
20
|
+
let couldBeSwiping = false;
|
|
21
|
+
const swipeThreshold = 30;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Monitor the possible start of a swipe
|
|
25
|
+
*
|
|
26
|
+
* @param {TouchEvent} e Triggering touch event
|
|
27
|
+
*/
|
|
28
|
+
const onStart = (e) => {
|
|
29
|
+
const isFullscreen = player.isFullscreen();
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
(!isFullscreen && !swipeToFullscreen) ||
|
|
33
|
+
(isFullscreen && !swipeFromFullscreen)
|
|
34
|
+
) {
|
|
35
|
+
couldBeSwiping = false;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
touchStartY = e.changedTouches[0].clientY;
|
|
40
|
+
couldBeSwiping = true;
|
|
41
|
+
player.tech_.el().style.transition = '';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Monitor the movement of a swipe
|
|
46
|
+
*
|
|
47
|
+
* @param {TouchEvent} e Triggering touch event
|
|
48
|
+
*/
|
|
49
|
+
const onMove = (e) => {
|
|
50
|
+
if (!couldBeSwiping) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const currentY = e.touches[0].clientY;
|
|
55
|
+
const deltaY = touchStartY - currentY;
|
|
56
|
+
const isFullscreen = player.isFullscreen();
|
|
57
|
+
|
|
58
|
+
let scale = 1;
|
|
59
|
+
|
|
60
|
+
if (!isFullscreen && deltaY > 0) {
|
|
61
|
+
// Swiping up to enter fullscreen: Zoom in (Max 1.1)
|
|
62
|
+
scale = 1 + Math.min(0.1, deltaY / 500);
|
|
63
|
+
player.tech_.el().style.transform = `scale(${scale})`;
|
|
64
|
+
} else if (isFullscreen && deltaY < 0) {
|
|
65
|
+
// Swiping down to exit fullscreen: Zoom out (Min 0.9)
|
|
66
|
+
scale = 1 - Math.min(0.1, Math.abs(deltaY) / 500);
|
|
67
|
+
player.tech_.el().style.transform = `scale(${scale})`;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Monitor the touch end to determine a valid swipe
|
|
73
|
+
*
|
|
74
|
+
* @param {TouchEvent} e Triggering touch event
|
|
75
|
+
*/
|
|
76
|
+
const onEnd = (e) => {
|
|
77
|
+
if (!couldBeSwiping) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
couldBeSwiping = false;
|
|
81
|
+
|
|
82
|
+
player.tech_.el().style.transition = 'transform 0.3s ease-out';
|
|
83
|
+
player.tech_.el().style.transform = 'scale(1)';
|
|
84
|
+
|
|
85
|
+
if (e.type === 'touchcancel') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const touchEndY = e.changedTouches[0].clientY;
|
|
90
|
+
const deltaY = touchStartY - touchEndY;
|
|
91
|
+
|
|
92
|
+
if (deltaY > swipeThreshold && !player.isFullscreen()) {
|
|
93
|
+
player.requestFullscreen().catch((err) => {
|
|
94
|
+
player.log.warn('Browser refused fullscreen', err);
|
|
95
|
+
});
|
|
96
|
+
} else if (deltaY < -swipeThreshold && player.isFullscreen()) {
|
|
97
|
+
player.exitFullscreen();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
player.el().addEventListener('touchstart', onStart, { passive: true });
|
|
102
|
+
player.el().addEventListener('touchmove', onMove, { passive: true });
|
|
103
|
+
player.el().addEventListener('touchend', onEnd, { passive: true });
|
|
104
|
+
player.el().addEventListener('touchcancel', onEnd, { passive: true });
|
|
105
|
+
|
|
106
|
+
player.on('dispose', () => {
|
|
107
|
+
player.el().removeEventListener('touchstart', onStart, { passive: true });
|
|
108
|
+
player.el().removeEventListener('touchmove', onMove, { passive: true });
|
|
109
|
+
player.el().removeEventListener('touchend', onEnd, { passive: true });
|
|
110
|
+
player.el().removeEventListener('touchcancel', onEnd, { passive: true });
|
|
111
|
+
player.tech_.el().style.transform = '';
|
|
112
|
+
player.tech_.el().style.transition = '';
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export default initSwipe;
|
package/src/touchOverlay.js
CHANGED
|
@@ -6,9 +6,23 @@
|
|
|
6
6
|
import videojs from 'video.js';
|
|
7
7
|
import window from 'global/window';
|
|
8
8
|
|
|
9
|
+
/** @import Player from 'video.js/dist/types/player' */
|
|
10
|
+
|
|
9
11
|
const Component = videojs.getComponent('Component');
|
|
10
12
|
const dom = videojs.dom || videojs;
|
|
11
13
|
|
|
14
|
+
const debounce = (callback, wait) => {
|
|
15
|
+
let timeoutId = null;
|
|
16
|
+
|
|
17
|
+
return (...args) => {
|
|
18
|
+
window.clearTimeout(timeoutId);
|
|
19
|
+
|
|
20
|
+
timeoutId = window.setTimeout(() => {
|
|
21
|
+
callback.apply(null, args);
|
|
22
|
+
}, wait);
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
12
26
|
/**
|
|
13
27
|
* The `TouchOverlay` is an overlay to capture tap events.
|
|
14
28
|
*
|
|
@@ -30,6 +44,7 @@ class TouchOverlay extends Component {
|
|
|
30
44
|
|
|
31
45
|
this.seekSeconds = options.seekSeconds;
|
|
32
46
|
this.tapTimeout = options.tapTimeout;
|
|
47
|
+
this.taps = 0;
|
|
33
48
|
|
|
34
49
|
// Add play toggle overlay
|
|
35
50
|
this.addChild('playToggle', {});
|
|
@@ -44,6 +59,46 @@ class TouchOverlay extends Component {
|
|
|
44
59
|
this.player_.options_.inactivityTimeout = 5000;
|
|
45
60
|
}
|
|
46
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Debounced tap handler.
|
|
64
|
+
* Seeks number of (taps - 1) * configured seconds to skip.
|
|
65
|
+
* One tap is a non-op
|
|
66
|
+
*
|
|
67
|
+
* @param {Event} event
|
|
68
|
+
*/
|
|
69
|
+
this.handleTaps_ = debounce(event => {
|
|
70
|
+
const increment = (this.taps - 1) * this.seekSeconds;
|
|
71
|
+
|
|
72
|
+
this.taps = 0;
|
|
73
|
+
if (increment < 1) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const rect = this.el_.getBoundingClientRect();
|
|
78
|
+
const x = event.changedTouches[0].clientX - rect.left;
|
|
79
|
+
|
|
80
|
+
// Check if double tap is in left or right area
|
|
81
|
+
if (x < rect.width * 0.4) {
|
|
82
|
+
this.player_.currentTime(Math.max(0, this.player_.currentTime() - increment));
|
|
83
|
+
this.addClass('reverse');
|
|
84
|
+
} else if (x > rect.width - (rect.width * 0.4)) {
|
|
85
|
+
this.player_.currentTime(Math.min(this.player_.duration(), this.player_.currentTime() + increment));
|
|
86
|
+
this.removeClass('reverse');
|
|
87
|
+
} else {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Remove play toggle if showing
|
|
92
|
+
this.removeClass('show-play-toggle');
|
|
93
|
+
|
|
94
|
+
// Remove and readd class to trigger animation
|
|
95
|
+
this.setAttribute('data-skip-text', `${increment} ${this.localize('seconds')}`);
|
|
96
|
+
this.removeClass('skip');
|
|
97
|
+
window.requestAnimationFrame(() => {
|
|
98
|
+
this.addClass('skip');
|
|
99
|
+
});
|
|
100
|
+
}, this.tapTimeout);
|
|
101
|
+
|
|
47
102
|
this.enable();
|
|
48
103
|
}
|
|
49
104
|
|
|
@@ -76,65 +131,16 @@ class TouchOverlay extends Component {
|
|
|
76
131
|
return;
|
|
77
132
|
}
|
|
78
133
|
|
|
79
|
-
event.
|
|
80
|
-
|
|
81
|
-
if (this.firstTapCaptured) {
|
|
82
|
-
this.firstTapCaptured = false;
|
|
83
|
-
if (this.timeout) {
|
|
84
|
-
window.clearTimeout(this.timeout);
|
|
85
|
-
}
|
|
86
|
-
this.handleDoubleTap(event);
|
|
87
|
-
} else {
|
|
88
|
-
this.firstTapCaptured = true;
|
|
89
|
-
this.timeout = window.setTimeout(() => {
|
|
90
|
-
this.firstTapCaptured = false;
|
|
91
|
-
this.handleSingleTap(event);
|
|
92
|
-
}, this.tapTimeout);
|
|
134
|
+
if (event.cancelable) {
|
|
135
|
+
event.preventDefault();
|
|
93
136
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Toggles display of play toggle
|
|
98
|
-
*
|
|
99
|
-
* @param {Event} event
|
|
100
|
-
* The touch event
|
|
101
|
-
*
|
|
102
|
-
*/
|
|
103
|
-
handleSingleTap(event) {
|
|
104
|
-
this.removeClass('skip');
|
|
105
|
-
this.toggleClass('show-play-toggle');
|
|
106
|
-
}
|
|
107
137
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* The touch event
|
|
113
|
-
*
|
|
114
|
-
*/
|
|
115
|
-
handleDoubleTap(event) {
|
|
116
|
-
const rect = this.el_.getBoundingClientRect();
|
|
117
|
-
const x = event.changedTouches[0].clientX - rect.left;
|
|
118
|
-
|
|
119
|
-
// Check if double tap is in left or right area
|
|
120
|
-
if (x < rect.width * 0.4) {
|
|
121
|
-
this.player_.currentTime(Math.max(0, this.player_.currentTime() - this.seekSeconds));
|
|
122
|
-
this.addClass('reverse');
|
|
123
|
-
} else if (x > rect.width - (rect.width * 0.4)) {
|
|
124
|
-
this.player_.currentTime(Math.min(this.player_.duration(), this.player_.currentTime() + this.seekSeconds));
|
|
125
|
-
this.removeClass('reverse');
|
|
126
|
-
} else {
|
|
127
|
-
return;
|
|
138
|
+
this.taps += 1;
|
|
139
|
+
if (this.taps === 1) {
|
|
140
|
+
this.removeClass('skip');
|
|
141
|
+
this.toggleClass('show-play-toggle');
|
|
128
142
|
}
|
|
129
|
-
|
|
130
|
-
// Remove play toggle if showing
|
|
131
|
-
this.removeClass('show-play-toggle');
|
|
132
|
-
|
|
133
|
-
// Remove and readd class to trigger animation
|
|
134
|
-
this.removeClass('skip');
|
|
135
|
-
window.requestAnimationFrame(() => {
|
|
136
|
-
this.addClass('skip');
|
|
137
|
-
});
|
|
143
|
+
this.handleTaps_(event);
|
|
138
144
|
}
|
|
139
145
|
|
|
140
146
|
/**
|
package/test/plugin.test.js
CHANGED
|
@@ -9,6 +9,8 @@ import plugin from '../src/plugin';
|
|
|
9
9
|
|
|
10
10
|
const Player = videojs.getComponent('Player');
|
|
11
11
|
|
|
12
|
+
const merge = videojs.obj ? videojs.obj.merge : videojs.mergeOptions;
|
|
13
|
+
|
|
12
14
|
QUnit.test('the environment is sane', function(assert) {
|
|
13
15
|
assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
|
|
14
16
|
assert.strictEqual(typeof sinon, 'object', 'sinon exists');
|
|
@@ -16,28 +18,31 @@ QUnit.test('the environment is sane', function(assert) {
|
|
|
16
18
|
assert.strictEqual(typeof plugin, 'function', 'plugin is a function');
|
|
17
19
|
});
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
const beforeEach = function() {
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
// Mock the environment's timers because certain things - particularly
|
|
24
|
+
// player readiness - are asynchronous in video.js 5. This MUST come
|
|
25
|
+
// before any player is created; otherwise, timers could get created
|
|
26
|
+
// with the actual timer methods!
|
|
27
|
+
this.clock = sinon.useFakeTimers();
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// with the actual timer methods!
|
|
27
|
-
this.clock = sinon.useFakeTimers();
|
|
29
|
+
this.fixture = document.getElementById('qunit-fixture');
|
|
30
|
+
this.video = document.createElement('video');
|
|
31
|
+
this.fixture.appendChild(this.video);
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.fixture.appendChild(this.video);
|
|
32
|
-
this.player = videojs(this.video);
|
|
33
|
-
},
|
|
33
|
+
this.player = videojs(this.video);
|
|
34
|
+
};
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
this.clock.restore();
|
|
36
|
+
const afterEach = function() {
|
|
37
|
+
if (!this.player.isDisposed()) {
|
|
38
|
+
this.player.dispose();
|
|
40
39
|
}
|
|
40
|
+
this.clock.restore();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
QUnit.module('videojs-mobile-ui', {
|
|
44
|
+
beforeEach,
|
|
45
|
+
afterEach
|
|
41
46
|
});
|
|
42
47
|
|
|
43
48
|
QUnit.test('registers itself with video.js', function(assert) {
|
|
@@ -49,6 +54,14 @@ QUnit.test('registers itself with video.js', function(assert) {
|
|
|
49
54
|
);
|
|
50
55
|
});
|
|
51
56
|
|
|
57
|
+
QUnit.test('initialises without errors', function(assert) {
|
|
58
|
+
this.player.mobileUi({ forceForTesting: true });
|
|
59
|
+
|
|
60
|
+
this.clock.tick(1);
|
|
61
|
+
|
|
62
|
+
assert.expect(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
52
65
|
QUnit.test('inserts element before control bar', function(assert) {
|
|
53
66
|
|
|
54
67
|
this.player.mobileUi({forceForTesting: true});
|
|
@@ -84,7 +97,7 @@ QUnit.test('iOS event listeners', function(assert) {
|
|
|
84
97
|
|
|
85
98
|
const oldBrowser = videojs.browser;
|
|
86
99
|
|
|
87
|
-
videojs.browser =
|
|
100
|
+
videojs.browser = merge(videojs.browser, {
|
|
88
101
|
IS_IOS: true,
|
|
89
102
|
IS_ANDROID: false
|
|
90
103
|
});
|
|
@@ -124,7 +137,7 @@ QUnit[testOrSkip]('Android event listeners', function(assert) {
|
|
|
124
137
|
|
|
125
138
|
const oldBrowser = videojs.browser;
|
|
126
139
|
|
|
127
|
-
videojs.browser =
|
|
140
|
+
videojs.browser = merge(videojs.browser, {
|
|
128
141
|
IS_IOS: false,
|
|
129
142
|
IS_ANDROID: true
|
|
130
143
|
});
|
|
@@ -156,7 +169,7 @@ QUnit[testOrSkip]('Android event listeners skipped if disabled', function(assert
|
|
|
156
169
|
|
|
157
170
|
const oldBrowser = videojs.browser;
|
|
158
171
|
|
|
159
|
-
videojs.browser =
|
|
172
|
+
videojs.browser = merge(videojs.browser, {
|
|
160
173
|
IS_IOS: false,
|
|
161
174
|
IS_ANDROID: true
|
|
162
175
|
});
|
|
@@ -178,3 +191,98 @@ QUnit[testOrSkip]('Android event listeners skipped if disabled', function(assert
|
|
|
178
191
|
|
|
179
192
|
videojs.browser = oldBrowser;
|
|
180
193
|
});
|
|
194
|
+
|
|
195
|
+
QUnit.test('Adds disable-end class if disableOnEnd option is true', function(assert) {
|
|
196
|
+
this.player.mobileUi({
|
|
197
|
+
forceForTesting: true,
|
|
198
|
+
touchControls: { disableOnEnd: true }
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
this.clock.tick(1);
|
|
202
|
+
|
|
203
|
+
assert.ok(this.player.hasClass('vjs-mobile-ui-disable-end'), 'Class added via option');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
QUnit.test('Adds disable-end class if endscreen plugin is present', function(assert) {
|
|
207
|
+
this.player.endscreen = () => {};
|
|
208
|
+
|
|
209
|
+
this.player.mobileUi({
|
|
210
|
+
forceForTesting: true,
|
|
211
|
+
touchControls: { disableOnEnd: false }
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
this.clock.tick(1);
|
|
215
|
+
|
|
216
|
+
assert.ok(this.player.hasClass('vjs-mobile-ui-disable-end'), 'Class added via endscreen detection');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
QUnit.module('TouchOverlay', {
|
|
220
|
+
beforeEach,
|
|
221
|
+
afterEach
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
QUnit.test('TouchOverlay: double tap right seeks forward', function(assert) {
|
|
225
|
+
// Setup
|
|
226
|
+
this.player.mobileUi({ forceForTesting: true });
|
|
227
|
+
this.clock.tick(1);
|
|
228
|
+
|
|
229
|
+
const touchOverlay = this.player.getChild('TouchOverlay');
|
|
230
|
+
const touchEl = touchOverlay.el_;
|
|
231
|
+
let currentTimeCache = 0;
|
|
232
|
+
|
|
233
|
+
// Mock bounding rect so clicks have a defined "right" side
|
|
234
|
+
// Width is 100, so > 60 is right side
|
|
235
|
+
sinon.stub(touchEl, 'getBoundingClientRect').returns({
|
|
236
|
+
left: 0,
|
|
237
|
+
width: 100
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
this.player.currentTime = (time) => {
|
|
241
|
+
if (time === undefined) {
|
|
242
|
+
return currentTimeCache;
|
|
243
|
+
}
|
|
244
|
+
currentTimeCache = time;
|
|
245
|
+
return currentTimeCache;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
this.player.duration(60);
|
|
249
|
+
this.player.currentTime(10);
|
|
250
|
+
|
|
251
|
+
// Trigger first tap
|
|
252
|
+
touchOverlay.handleTap({
|
|
253
|
+
target: touchEl,
|
|
254
|
+
preventDefault: () => {},
|
|
255
|
+
changedTouches: [{ clientX: 90 }]
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Trigger second tap (double tap)
|
|
259
|
+
touchOverlay.handleTap({
|
|
260
|
+
target: touchEl,
|
|
261
|
+
preventDefault: () => {},
|
|
262
|
+
changedTouches: [{ clientX: 90 }]
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Fast forward debounce timer (default tapTimeout is 300ms)
|
|
266
|
+
this.clock.tick(310);
|
|
267
|
+
assert.equal(this.player.currentTime(), 20, 'Seeked forward 10 seconds (default)');
|
|
268
|
+
|
|
269
|
+
// Advance enough for requestAnimationFrame to trigger
|
|
270
|
+
this.clock.tick(50);
|
|
271
|
+
assert.ok(touchOverlay.hasClass('skip'), 'Skip animation class added');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
QUnit.test('TouchOverlay: single tap toggles play/pause visibility', function(assert) {
|
|
275
|
+
this.player.mobileUi({ forceForTesting: true });
|
|
276
|
+
this.clock.tick(1);
|
|
277
|
+
|
|
278
|
+
const touchOverlay = this.player.getChild('TouchOverlay');
|
|
279
|
+
|
|
280
|
+
// Trigger single tap
|
|
281
|
+
touchOverlay.handleTap({
|
|
282
|
+
target: touchOverlay.el_,
|
|
283
|
+
preventDefault: () => {},
|
|
284
|
+
changedTouches: [{ clientX: 50 }]
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
assert.ok(touchOverlay.hasClass('show-play-toggle'), 'Play toggle is visible after single tap');
|
|
288
|
+
});
|