@cloudnest/redxplyr 1.0.0
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/.editorconfig +10 -0
- package/.gitpod.yml +6 -0
- package/.node-version +1 -0
- package/.prettierrc +7 -0
- package/.stickler.yml +5 -0
- package/.stylelintrc.json +26 -0
- package/CHANGELOG.md +16 -0
- package/CONTRIBUTING.md +34 -0
- package/CONTROLS.md +49 -0
- package/Dockerfile +32 -0
- package/LICENSE.md +22 -0
- package/README.md +194 -0
- package/cspell.json +48 -0
- package/dist/redxplyr.css +1 -0
- package/dist/redxplyr.js +8801 -0
- package/dist/redxplyr.min.js +2 -0
- package/dist/redxplyr.min.js.map +1 -0
- package/dist/redxplyr.min.mjs +1 -0
- package/dist/redxplyr.min.mjs.map +1 -0
- package/dist/redxplyr.mjs +8793 -0
- package/dist/redxplyr.polyfilled.js +9294 -0
- package/dist/redxplyr.polyfilled.min.js +2 -0
- package/dist/redxplyr.polyfilled.min.js.map +1 -0
- package/dist/redxplyr.polyfilled.min.mjs +1 -0
- package/dist/redxplyr.polyfilled.min.mjs.map +1 -0
- package/dist/redxplyr.polyfilled.mjs +9286 -0
- package/dist/redxplyr.svg +1 -0
- package/eslint.config.mjs +39 -0
- package/gulpfile.js +8 -0
- package/package.json +114 -0
- package/pnpm-workspace.yaml +8 -0
- package/src/js/captions.js +411 -0
- package/src/js/config/defaults.js +459 -0
- package/src/js/config/states.js +10 -0
- package/src/js/config/types.js +34 -0
- package/src/js/console.js +28 -0
- package/src/js/controls.js +1870 -0
- package/src/js/fullscreen.js +305 -0
- package/src/js/html5.js +148 -0
- package/src/js/listeners.js +854 -0
- package/src/js/media.js +61 -0
- package/src/js/plugins/ads.js +647 -0
- package/src/js/plugins/preview-thumbnails.js +706 -0
- package/src/js/plugins/vimeo.js +443 -0
- package/src/js/plugins/youtube.js +451 -0
- package/src/js/plyr.d.ts +729 -0
- package/src/js/plyr.js +1291 -0
- package/src/js/plyr.polyfilled.js +13 -0
- package/src/js/source.js +155 -0
- package/src/js/storage.js +70 -0
- package/src/js/support.js +100 -0
- package/src/js/ui.js +297 -0
- package/src/js/utils/animation.js +33 -0
- package/src/js/utils/arrays.js +23 -0
- package/src/js/utils/browser.js +21 -0
- package/src/js/utils/elements.js +263 -0
- package/src/js/utils/events.js +116 -0
- package/src/js/utils/fetch.js +45 -0
- package/src/js/utils/i18n.js +47 -0
- package/src/js/utils/is.js +81 -0
- package/src/js/utils/load-image.js +19 -0
- package/src/js/utils/load-script.js +14 -0
- package/src/js/utils/load-sprite.js +77 -0
- package/src/js/utils/numbers.js +17 -0
- package/src/js/utils/objects.js +43 -0
- package/src/js/utils/promise.js +14 -0
- package/src/js/utils/strings.js +80 -0
- package/src/js/utils/style.js +148 -0
- package/src/js/utils/time.js +36 -0
- package/src/js/utils/urls.js +40 -0
- package/src/sass/base.scss +69 -0
- package/src/sass/components/badges.scss +12 -0
- package/src/sass/components/captions.scss +58 -0
- package/src/sass/components/control.scss +52 -0
- package/src/sass/components/controls.scss +65 -0
- package/src/sass/components/menus.scss +205 -0
- package/src/sass/components/poster.scss +27 -0
- package/src/sass/components/progress.scss +107 -0
- package/src/sass/components/sliders.scss +99 -0
- package/src/sass/components/times.scss +20 -0
- package/src/sass/components/tooltips.scss +91 -0
- package/src/sass/components/volume.scss +18 -0
- package/src/sass/lib/animation.scss +31 -0
- package/src/sass/lib/css-vars.scss +103 -0
- package/src/sass/lib/functions.scss +3 -0
- package/src/sass/lib/mixins.scss +82 -0
- package/src/sass/plugins/ads.scss +53 -0
- package/src/sass/plugins/preview-thumbnails/index.scss +121 -0
- package/src/sass/plugins/preview-thumbnails/settings.scss +17 -0
- package/src/sass/plyr.scss +46 -0
- package/src/sass/settings/badges.scss +7 -0
- package/src/sass/settings/breakpoints.scss +9 -0
- package/src/sass/settings/captions.scss +10 -0
- package/src/sass/settings/colors.scss +18 -0
- package/src/sass/settings/controls.scss +30 -0
- package/src/sass/settings/cosmetics.scss +5 -0
- package/src/sass/settings/helpers.scss +7 -0
- package/src/sass/settings/menus.scss +13 -0
- package/src/sass/settings/progress.scss +18 -0
- package/src/sass/settings/sliders.scss +39 -0
- package/src/sass/settings/tooltips.scss +11 -0
- package/src/sass/settings/type.scss +16 -0
- package/src/sass/states/fullscreen.scss +15 -0
- package/src/sass/types/audio.scss +61 -0
- package/src/sass/types/video.scss +170 -0
- package/src/sass/utils/animation.scss +7 -0
- package/src/sass/utils/hidden.scss +28 -0
- package/src/sprite/plyr-airplay.svg +8 -0
- package/src/sprite/plyr-captions-off.svg +7 -0
- package/src/sprite/plyr-captions-on.svg +7 -0
- package/src/sprite/plyr-download.svg +8 -0
- package/src/sprite/plyr-enter-fullscreen.svg +4 -0
- package/src/sprite/plyr-exit-fullscreen.svg +4 -0
- package/src/sprite/plyr-fast-forward.svg +3 -0
- package/src/sprite/plyr-logo-vimeo.svg +6 -0
- package/src/sprite/plyr-logo-youtube.svg +6 -0
- package/src/sprite/plyr-muted.svg +8 -0
- package/src/sprite/plyr-pause.svg +8 -0
- package/src/sprite/plyr-pip.svg +6 -0
- package/src/sprite/plyr-play.svg +5 -0
- package/src/sprite/plyr-restart.svg +5 -0
- package/src/sprite/plyr-rewind.svg +3 -0
- package/src/sprite/plyr-settings.svg +5 -0
- package/src/sprite/plyr-volume.svg +11 -0
- package/tasks/build.js +226 -0
- package/tasks/deploy.js +216 -0
- package/tasks/utils/publish.js +34 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
// ==========================================================================
|
|
2
|
+
// YouTube plugin
|
|
3
|
+
// ==========================================================================
|
|
4
|
+
|
|
5
|
+
import ui from '../ui';
|
|
6
|
+
import { createElement, replaceElement, toggleClass } from '../utils/elements';
|
|
7
|
+
import { triggerEvent } from '../utils/events';
|
|
8
|
+
import fetch from '../utils/fetch';
|
|
9
|
+
import is from '../utils/is';
|
|
10
|
+
import loadImage from '../utils/load-image';
|
|
11
|
+
import loadScript from '../utils/load-script';
|
|
12
|
+
import { extend } from '../utils/objects';
|
|
13
|
+
import { format, generateId } from '../utils/strings';
|
|
14
|
+
import { roundAspectRatio, setAspectRatio } from '../utils/style';
|
|
15
|
+
|
|
16
|
+
// Parse YouTube ID from URL
|
|
17
|
+
function parseId(url) {
|
|
18
|
+
if (is.empty(url)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
|
|
23
|
+
const match = url.match(regex);
|
|
24
|
+
return match && match[2] ? match[2] : url;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Set playback state and trigger change (only on actual change)
|
|
28
|
+
function assurePlaybackState(play) {
|
|
29
|
+
if (play && !this.embed.hasPlayed) {
|
|
30
|
+
this.embed.hasPlayed = true;
|
|
31
|
+
}
|
|
32
|
+
if (this.media.paused === play) {
|
|
33
|
+
this.media.paused = !play;
|
|
34
|
+
triggerEvent.call(this, this.media, play ? 'play' : 'pause');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getHost(config) {
|
|
39
|
+
if (config.noCookie) {
|
|
40
|
+
return 'https://www.youtube-nocookie.com';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (window.location.protocol === 'http:') {
|
|
44
|
+
return 'http://www.youtube.com';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Use YouTube's default
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const youtube = {
|
|
52
|
+
setup() {
|
|
53
|
+
// Add embed class for responsive
|
|
54
|
+
toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
|
|
55
|
+
|
|
56
|
+
// Setup API
|
|
57
|
+
if (is.object(window.YT) && is.function(window.YT.Player)) {
|
|
58
|
+
youtube.ready.call(this);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Reference current global callback
|
|
62
|
+
const callback = window.onYouTubeIframeAPIReady;
|
|
63
|
+
|
|
64
|
+
// Set callback to process queue
|
|
65
|
+
window.onYouTubeIframeAPIReady = () => {
|
|
66
|
+
// Call global callback if set
|
|
67
|
+
if (is.function(callback)) {
|
|
68
|
+
callback();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
youtube.ready.call(this);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Load the SDK
|
|
75
|
+
loadScript(this.config.urls.youtube.sdk).catch((error) => {
|
|
76
|
+
this.debug.warn('YouTube API failed to load', error);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// Get the media title
|
|
82
|
+
getTitle(videoId) {
|
|
83
|
+
const url = format(this.config.urls.youtube.api, videoId);
|
|
84
|
+
|
|
85
|
+
fetch(url)
|
|
86
|
+
.then((data) => {
|
|
87
|
+
if (is.object(data)) {
|
|
88
|
+
const { title, height, width } = data;
|
|
89
|
+
|
|
90
|
+
// Set title
|
|
91
|
+
this.config.title = title;
|
|
92
|
+
ui.setTitle.call(this);
|
|
93
|
+
|
|
94
|
+
// Set aspect ratio
|
|
95
|
+
this.embed.ratio = roundAspectRatio(width, height);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
setAspectRatio.call(this);
|
|
99
|
+
})
|
|
100
|
+
.catch(() => {
|
|
101
|
+
// Set aspect ratio
|
|
102
|
+
setAspectRatio.call(this);
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// API ready
|
|
107
|
+
ready() {
|
|
108
|
+
const player = this;
|
|
109
|
+
const config = player.config.youtube;
|
|
110
|
+
// Ignore already setup (race condition)
|
|
111
|
+
const currentId = player.media && player.media.getAttribute('id');
|
|
112
|
+
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Get the source URL or ID
|
|
117
|
+
let source = player.media.getAttribute('src');
|
|
118
|
+
|
|
119
|
+
// Get from <div> if needed
|
|
120
|
+
if (is.empty(source)) {
|
|
121
|
+
source = player.media.getAttribute(this.config.attributes.embed.id);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Replace the <iframe> with a <div> due to YouTube API issues
|
|
125
|
+
const videoId = parseId(source);
|
|
126
|
+
const id = generateId(player.provider);
|
|
127
|
+
// Replace media element
|
|
128
|
+
const container = createElement('div', { id, 'data-poster': config.customControls ? player.poster : undefined });
|
|
129
|
+
player.media = replaceElement(container, player.media);
|
|
130
|
+
|
|
131
|
+
// Only load the poster when using custom controls
|
|
132
|
+
if (config.customControls) {
|
|
133
|
+
const posterSrc = s => `https://i.ytimg.com/vi/${videoId}/${s}default.jpg`;
|
|
134
|
+
|
|
135
|
+
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
|
|
136
|
+
loadImage(posterSrc('maxres'), 121) // Highest quality and un-padded
|
|
137
|
+
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
|
|
138
|
+
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
|
|
139
|
+
.then(image => ui.setPoster.call(player, image.src))
|
|
140
|
+
.then((src) => {
|
|
141
|
+
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
|
|
142
|
+
if (!src.includes('maxres')) {
|
|
143
|
+
player.elements.poster.style.backgroundSize = 'cover';
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
.catch(() => {});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Setup instance
|
|
150
|
+
// https://developers.google.com/youtube/iframe_api_reference
|
|
151
|
+
player.embed = new window.YT.Player(player.media, {
|
|
152
|
+
videoId,
|
|
153
|
+
host: getHost(config),
|
|
154
|
+
playerVars: extend(
|
|
155
|
+
{},
|
|
156
|
+
{
|
|
157
|
+
// Autoplay
|
|
158
|
+
autoplay: player.config.autoplay ? 1 : 0,
|
|
159
|
+
// iframe interface language
|
|
160
|
+
hl: player.config.hl,
|
|
161
|
+
// Only show controls if not fully supported or opted out
|
|
162
|
+
controls: player.supported.ui && config.customControls ? 0 : 1,
|
|
163
|
+
// Disable keyboard as we handle it
|
|
164
|
+
disablekb: 1,
|
|
165
|
+
// Allow iOS inline playback
|
|
166
|
+
playsinline: player.config.playsinline && !player.config.fullscreen.iosNative ? 1 : 0,
|
|
167
|
+
// Captions are flaky on YouTube
|
|
168
|
+
cc_load_policy: player.captions.active ? 1 : 0,
|
|
169
|
+
cc_lang_pref: player.config.captions.language,
|
|
170
|
+
// Tracking for stats
|
|
171
|
+
widget_referrer: window ? window.location.href : null,
|
|
172
|
+
},
|
|
173
|
+
config,
|
|
174
|
+
),
|
|
175
|
+
events: {
|
|
176
|
+
onError(event) {
|
|
177
|
+
// YouTube may fire onError twice, so only handle it once
|
|
178
|
+
if (!player.media.error) {
|
|
179
|
+
const code = event.data;
|
|
180
|
+
// Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
|
|
181
|
+
const message
|
|
182
|
+
= {
|
|
183
|
+
2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
|
|
184
|
+
5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
|
|
185
|
+
100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
|
|
186
|
+
101: 'The owner of the requested video does not allow it to be played in embedded players.',
|
|
187
|
+
150: 'The owner of the requested video does not allow it to be played in embedded players.',
|
|
188
|
+
}[code] || 'An unknown error occurred';
|
|
189
|
+
|
|
190
|
+
player.media.error = { code, message };
|
|
191
|
+
|
|
192
|
+
triggerEvent.call(player, player.media, 'error');
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
onPlaybackRateChange(event) {
|
|
196
|
+
// Get the instance
|
|
197
|
+
const instance = event.target;
|
|
198
|
+
|
|
199
|
+
// Get current speed
|
|
200
|
+
player.media.playbackRate = instance.getPlaybackRate();
|
|
201
|
+
|
|
202
|
+
triggerEvent.call(player, player.media, 'ratechange');
|
|
203
|
+
},
|
|
204
|
+
onReady(event) {
|
|
205
|
+
// Bail if onReady has already been called. See issue #1108
|
|
206
|
+
if (is.function(player.media.play)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Get the instance
|
|
210
|
+
const instance = event.target;
|
|
211
|
+
|
|
212
|
+
// Get the title
|
|
213
|
+
youtube.getTitle.call(player, videoId);
|
|
214
|
+
|
|
215
|
+
// Create a faux HTML5 API using the YouTube API
|
|
216
|
+
player.media.play = () => {
|
|
217
|
+
assurePlaybackState.call(player, true);
|
|
218
|
+
instance.playVideo();
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
player.media.pause = () => {
|
|
222
|
+
assurePlaybackState.call(player, false);
|
|
223
|
+
instance.pauseVideo();
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
player.media.stop = () => {
|
|
227
|
+
instance.stopVideo();
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
player.media.duration = instance.getDuration();
|
|
231
|
+
player.media.paused = true;
|
|
232
|
+
|
|
233
|
+
// Seeking
|
|
234
|
+
player.media.currentTime = 0;
|
|
235
|
+
Object.defineProperty(player.media, 'currentTime', {
|
|
236
|
+
get() {
|
|
237
|
+
return Number(instance.getCurrentTime());
|
|
238
|
+
},
|
|
239
|
+
set(time) {
|
|
240
|
+
// If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
|
|
241
|
+
if (player.paused && !player.embed.hasPlayed) {
|
|
242
|
+
player.embed.mute();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Set seeking state and trigger event
|
|
246
|
+
player.media.seeking = true;
|
|
247
|
+
triggerEvent.call(player, player.media, 'seeking');
|
|
248
|
+
|
|
249
|
+
// Seek after events sent
|
|
250
|
+
instance.seekTo(time);
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Playback speed
|
|
255
|
+
Object.defineProperty(player.media, 'playbackRate', {
|
|
256
|
+
get() {
|
|
257
|
+
return instance.getPlaybackRate();
|
|
258
|
+
},
|
|
259
|
+
set(input) {
|
|
260
|
+
instance.setPlaybackRate(input);
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Volume
|
|
265
|
+
let { volume } = player.config;
|
|
266
|
+
Object.defineProperty(player.media, 'volume', {
|
|
267
|
+
get() {
|
|
268
|
+
return volume;
|
|
269
|
+
},
|
|
270
|
+
set(input) {
|
|
271
|
+
volume = input;
|
|
272
|
+
instance.setVolume(volume * 100);
|
|
273
|
+
triggerEvent.call(player, player.media, 'volumechange');
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Muted
|
|
278
|
+
let { muted } = player.config;
|
|
279
|
+
Object.defineProperty(player.media, 'muted', {
|
|
280
|
+
get() {
|
|
281
|
+
return muted;
|
|
282
|
+
},
|
|
283
|
+
set(input) {
|
|
284
|
+
const toggle = is.boolean(input) ? input : muted;
|
|
285
|
+
muted = toggle;
|
|
286
|
+
instance[toggle ? 'mute' : 'unMute']();
|
|
287
|
+
instance.setVolume(volume * 100);
|
|
288
|
+
triggerEvent.call(player, player.media, 'volumechange');
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Source
|
|
293
|
+
Object.defineProperty(player.media, 'currentSrc', {
|
|
294
|
+
get() {
|
|
295
|
+
return instance.getVideoUrl();
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Ended
|
|
300
|
+
Object.defineProperty(player.media, 'ended', {
|
|
301
|
+
get() {
|
|
302
|
+
return player.currentTime === player.duration;
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Get available speeds
|
|
307
|
+
const speeds = instance.getAvailablePlaybackRates();
|
|
308
|
+
// Filter based on config
|
|
309
|
+
player.options.speed = speeds.filter(s => player.config.speed.options.includes(s));
|
|
310
|
+
|
|
311
|
+
// Set the tabindex to avoid focus entering iframe
|
|
312
|
+
if (player.supported.ui && config.customControls) {
|
|
313
|
+
player.media.setAttribute('tabindex', -1);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
triggerEvent.call(player, player.media, 'timeupdate');
|
|
317
|
+
triggerEvent.call(player, player.media, 'durationchange');
|
|
318
|
+
|
|
319
|
+
// Reset timer
|
|
320
|
+
clearInterval(player.timers.buffering);
|
|
321
|
+
|
|
322
|
+
// Setup buffering
|
|
323
|
+
player.timers.buffering = setInterval(() => {
|
|
324
|
+
// Get loaded % from YouTube
|
|
325
|
+
player.media.buffered = instance.getVideoLoadedFraction();
|
|
326
|
+
|
|
327
|
+
// Trigger progress only when we actually buffer something
|
|
328
|
+
if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
|
|
329
|
+
triggerEvent.call(player, player.media, 'progress');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Set last buffer point
|
|
333
|
+
player.media.lastBuffered = player.media.buffered;
|
|
334
|
+
|
|
335
|
+
// Bail if we're at 100%
|
|
336
|
+
if (player.media.buffered === 1) {
|
|
337
|
+
clearInterval(player.timers.buffering);
|
|
338
|
+
|
|
339
|
+
// Trigger event
|
|
340
|
+
triggerEvent.call(player, player.media, 'canplaythrough');
|
|
341
|
+
}
|
|
342
|
+
}, 200);
|
|
343
|
+
|
|
344
|
+
// Rebuild UI
|
|
345
|
+
if (config.customControls) {
|
|
346
|
+
setTimeout(() => ui.build.call(player), 50);
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
onStateChange(event) {
|
|
350
|
+
// Get the instance
|
|
351
|
+
const instance = event.target;
|
|
352
|
+
|
|
353
|
+
// Reset timer
|
|
354
|
+
clearInterval(player.timers.playing);
|
|
355
|
+
|
|
356
|
+
const seeked = player.media.seeking && [1, 2].includes(event.data);
|
|
357
|
+
|
|
358
|
+
if (seeked) {
|
|
359
|
+
// Unset seeking and fire seeked event
|
|
360
|
+
player.media.seeking = false;
|
|
361
|
+
triggerEvent.call(player, player.media, 'seeked');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Handle events
|
|
365
|
+
// -1 Unstarted
|
|
366
|
+
// 0 Ended
|
|
367
|
+
// 1 Playing
|
|
368
|
+
// 2 Paused
|
|
369
|
+
// 3 Buffering
|
|
370
|
+
// 5 Video cued
|
|
371
|
+
switch (event.data) {
|
|
372
|
+
case -1:
|
|
373
|
+
// Update scrubber
|
|
374
|
+
triggerEvent.call(player, player.media, 'timeupdate');
|
|
375
|
+
|
|
376
|
+
// Get loaded % from YouTube
|
|
377
|
+
player.media.buffered = instance.getVideoLoadedFraction();
|
|
378
|
+
triggerEvent.call(player, player.media, 'progress');
|
|
379
|
+
|
|
380
|
+
break;
|
|
381
|
+
|
|
382
|
+
case 0:
|
|
383
|
+
assurePlaybackState.call(player, false);
|
|
384
|
+
|
|
385
|
+
// YouTube doesn't support loop for a single video, so mimick it.
|
|
386
|
+
if (player.media.loop) {
|
|
387
|
+
// YouTube needs a call to `stopVideo` before playing again
|
|
388
|
+
instance.stopVideo();
|
|
389
|
+
instance.playVideo();
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
triggerEvent.call(player, player.media, 'ended');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
break;
|
|
396
|
+
|
|
397
|
+
case 1:
|
|
398
|
+
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
|
|
399
|
+
if (config.customControls && !player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
|
|
400
|
+
player.media.pause();
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
assurePlaybackState.call(player, true);
|
|
404
|
+
|
|
405
|
+
triggerEvent.call(player, player.media, 'playing');
|
|
406
|
+
|
|
407
|
+
// Poll to get playback progress
|
|
408
|
+
player.timers.playing = setInterval(() => {
|
|
409
|
+
triggerEvent.call(player, player.media, 'timeupdate');
|
|
410
|
+
}, 50);
|
|
411
|
+
|
|
412
|
+
// Check duration again due to YouTube bug
|
|
413
|
+
// https://github.com/xgauravyaduvanshii/redxplyr/issues/374
|
|
414
|
+
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
|
|
415
|
+
if (player.media.duration !== instance.getDuration()) {
|
|
416
|
+
player.media.duration = instance.getDuration();
|
|
417
|
+
triggerEvent.call(player, player.media, 'durationchange');
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
break;
|
|
422
|
+
|
|
423
|
+
case 2:
|
|
424
|
+
// Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
|
|
425
|
+
if (!player.muted) {
|
|
426
|
+
player.embed.unMute();
|
|
427
|
+
}
|
|
428
|
+
assurePlaybackState.call(player, false);
|
|
429
|
+
|
|
430
|
+
break;
|
|
431
|
+
|
|
432
|
+
case 3:
|
|
433
|
+
// Trigger waiting event to add loading classes to container as the video buffers.
|
|
434
|
+
triggerEvent.call(player, player.media, 'waiting');
|
|
435
|
+
|
|
436
|
+
break;
|
|
437
|
+
|
|
438
|
+
default:
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
triggerEvent.call(player, player.elements.container, 'statechange', false, {
|
|
443
|
+
code: event.data,
|
|
444
|
+
});
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
export default youtube;
|