myetv-player 1.0.8 → 1.1.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/README.md +76 -2
- package/css/myetv-player.css +321 -208
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +219 -37
- package/dist/myetv-player.min.js +204 -26
- package/package.json +3 -1
- package/plugins/cloudflare/README.md +26 -4
- package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +1273 -217
- package/plugins/facebook/myetv-player-facebook-plugin.js +1340 -164
- package/plugins/twitch/myetv-player-twitch-plugin.js +428 -167
- package/plugins/vimeo/README.md +1 -1
- package/plugins/vimeo/myetv-player-vimeo.js +560 -247
- package/plugins/youtube/README.md +18 -7
- package/plugins/youtube/myetv-player-youtube-plugin.js +1485 -190
- package/scss/_base.scss +0 -15
- package/scss/_controls.scss +182 -2
- package/scss/_menus.scss +51 -0
- package/scss/_responsive.scss +187 -321
- package/scss/_title-overlay.scss +27 -0
- package/scss/_video.scss +0 -75
- package/scss/_watermark.scss +120 -0
- package/scss/myetv-player.scss +7 -7
- package/src/controls.js +72 -21
- package/src/core.js +43 -5
- package/src/events.js +33 -5
- package/src/utils.js +20 -6
- package/src/watermark.js +51 -0
|
@@ -32,14 +32,13 @@
|
|
|
32
32
|
background: options.background || false,
|
|
33
33
|
byline: options.byline !== false,
|
|
34
34
|
color: options.color || null,
|
|
35
|
-
controls:
|
|
36
|
-
dnt: options.dnt || false,
|
|
35
|
+
controls: false, // Force controls off to use MYETV controls
|
|
36
|
+
dnt: options.dnt || false,
|
|
37
37
|
loop: options.loop || false,
|
|
38
38
|
muted: options.muted || false,
|
|
39
|
-
pip: options.pip !== false, // Picture-in-Picture
|
|
40
39
|
playsinline: options.playsinline !== false,
|
|
41
40
|
portrait: options.portrait !== false,
|
|
42
|
-
quality: options.quality || 'auto',
|
|
41
|
+
quality: options.quality || 'auto',
|
|
43
42
|
responsive: options.responsive !== false,
|
|
44
43
|
speed: options.speed || false,
|
|
45
44
|
texttrack: options.texttrack || null,
|
|
@@ -69,8 +68,12 @@
|
|
|
69
68
|
* Setup plugin
|
|
70
69
|
*/
|
|
71
70
|
setup() {
|
|
71
|
+
// Disable PIP for Vimeo (not supported)
|
|
72
|
+
this.disablePipButton();
|
|
73
|
+
|
|
72
74
|
this.loadVimeoSDK().then(() => {
|
|
73
75
|
this.createVimeoPlayer();
|
|
76
|
+
|
|
74
77
|
if (this.options.replaceNativePlayer) {
|
|
75
78
|
this.hideNativePlayer();
|
|
76
79
|
}
|
|
@@ -83,6 +86,97 @@
|
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Disable PIP button permanently (Vimeo doesn't support it)
|
|
91
|
+
*/
|
|
92
|
+
disablePipButton() {
|
|
93
|
+
// Set option to false
|
|
94
|
+
if (this.player.options) {
|
|
95
|
+
this.player.options.showPictureInPicture = false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Hide PIP button immediately
|
|
99
|
+
this.hidePipFromSettingsMenuOnly();
|
|
100
|
+
|
|
101
|
+
// Setup responsive layout handler
|
|
102
|
+
this.handleResponsiveLayout();
|
|
103
|
+
|
|
104
|
+
if (this.options.debug) {
|
|
105
|
+
console.log('🎬 Vimeo: PIP disabled (not supported)');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Hide PIP from settings menu using MutationObserver
|
|
111
|
+
* Copied from YouTube plugin
|
|
112
|
+
*/
|
|
113
|
+
hidePipFromSettingsMenuOnly() {
|
|
114
|
+
const hidePipOption = () => {
|
|
115
|
+
const settingsMenu = this.player.container.querySelector('.settings-menu');
|
|
116
|
+
if (settingsMenu) {
|
|
117
|
+
const pipOption = settingsMenu.querySelector('[data-setting="pip"]');
|
|
118
|
+
if (pipOption) {
|
|
119
|
+
pipOption.style.display = 'none';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Hide immediately
|
|
125
|
+
hidePipOption();
|
|
126
|
+
|
|
127
|
+
// Setup observer for dynamic changes
|
|
128
|
+
const observer = new MutationObserver(() => {
|
|
129
|
+
hidePipOption();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (this.player.container) {
|
|
133
|
+
observer.observe(this.player.container, {
|
|
134
|
+
childList: true,
|
|
135
|
+
subtree: true
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Store observer for cleanup
|
|
140
|
+
this.pipObserver = observer;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Handle responsive layout to hide PIP button
|
|
145
|
+
* Copied from YouTube plugin
|
|
146
|
+
*/
|
|
147
|
+
handleResponsiveLayout() {
|
|
148
|
+
const hidePipButton = () => {
|
|
149
|
+
const pipBtn = this.player.container.querySelector('.pip-btn, [data-pip-btn]');
|
|
150
|
+
if (pipBtn) {
|
|
151
|
+
pipBtn.style.display = 'none';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Also hide from settings menu
|
|
155
|
+
const settingsMenu = this.player.container.querySelector('.settings-menu');
|
|
156
|
+
if (settingsMenu) {
|
|
157
|
+
const pipOption = settingsMenu.querySelector('[data-setting="pip"]');
|
|
158
|
+
if (pipOption) {
|
|
159
|
+
pipOption.style.display = 'none';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Hide immediately
|
|
165
|
+
hidePipButton();
|
|
166
|
+
|
|
167
|
+
// Hide on window resize
|
|
168
|
+
window.addEventListener('resize', hidePipButton);
|
|
169
|
+
|
|
170
|
+
// Hide on orientation change
|
|
171
|
+
window.addEventListener('orientationchange', hidePipButton);
|
|
172
|
+
|
|
173
|
+
// Store cleanup function
|
|
174
|
+
this.pipCleanup = () => {
|
|
175
|
+
window.removeEventListener('resize', hidePipButton);
|
|
176
|
+
window.removeEventListener('orientationchange', hidePipButton);
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
86
180
|
/**
|
|
87
181
|
* Load Vimeo Player SDK
|
|
88
182
|
*/
|
|
@@ -116,17 +210,51 @@
|
|
|
116
210
|
// Create container for Vimeo player
|
|
117
211
|
this.vimeoContainer = document.createElement('div');
|
|
118
212
|
this.vimeoContainer.className = 'vimeo-player-container';
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
213
|
+
|
|
214
|
+
// Add CSS to ensure iframe fills container properly
|
|
215
|
+
const style = document.createElement('style');
|
|
216
|
+
style.textContent = `
|
|
217
|
+
.vimeo-player-container {
|
|
218
|
+
position: absolute !important;
|
|
219
|
+
top: 0 !important;
|
|
220
|
+
left: 0 !important;
|
|
221
|
+
width: 100% !important;
|
|
222
|
+
height: 100% !important;
|
|
223
|
+
z-index: 1 !important;
|
|
224
|
+
}
|
|
225
|
+
.vimeo-player-container iframe {
|
|
226
|
+
position: absolute !important;
|
|
227
|
+
top: 0 !important;
|
|
228
|
+
left: 0 !important;
|
|
229
|
+
width: 100% !important;
|
|
230
|
+
height: 100% !important;
|
|
231
|
+
border: none !important;
|
|
232
|
+
}
|
|
126
233
|
`;
|
|
234
|
+
if (!document.querySelector('#vimeo-plugin-styles')) {
|
|
235
|
+
style.id = 'vimeo-plugin-styles';
|
|
236
|
+
document.head.appendChild(style);
|
|
237
|
+
}
|
|
127
238
|
|
|
128
239
|
this.player.container.appendChild(this.vimeoContainer);
|
|
129
240
|
|
|
241
|
+
// Prevent default Vimeo click behavior and sync with MYETV
|
|
242
|
+
this.vimeoContainer.addEventListener('click', (e) => {
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
e.stopPropagation();
|
|
245
|
+
|
|
246
|
+
// Toggle play/pause through MYETV player
|
|
247
|
+
if (this.player.video) {
|
|
248
|
+
this.vimeoPlayer.getPaused().then(paused => {
|
|
249
|
+
if (paused) {
|
|
250
|
+
this.player.video.play();
|
|
251
|
+
} else {
|
|
252
|
+
this.player.video.pause();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
130
258
|
// Prepare Vimeo options
|
|
131
259
|
const vimeoOptions = this.prepareVimeoOptions();
|
|
132
260
|
|
|
@@ -139,10 +267,11 @@
|
|
|
139
267
|
// Load available qualities
|
|
140
268
|
this.loadQualities();
|
|
141
269
|
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
270
|
+
// Override native methods
|
|
271
|
+
this.overrideNativeMethods();
|
|
272
|
+
|
|
273
|
+
// Sync with native controls
|
|
274
|
+
this.syncWithNativeControls();
|
|
146
275
|
|
|
147
276
|
this.isVimeoLoaded = true;
|
|
148
277
|
|
|
@@ -168,17 +297,18 @@
|
|
|
168
297
|
if (this.options.width) vimeoOptions.width = this.options.width;
|
|
169
298
|
if (this.options.height) vimeoOptions.height = this.options.height;
|
|
170
299
|
|
|
171
|
-
// Set embed options
|
|
300
|
+
// Set embed options - FORCE controls to false
|
|
172
301
|
if (this.options.autopause !== undefined) vimeoOptions.autopause = this.options.autopause;
|
|
173
302
|
if (this.options.autoplay) vimeoOptions.autoplay = this.options.autoplay;
|
|
174
303
|
if (this.options.background) vimeoOptions.background = this.options.background;
|
|
175
304
|
if (this.options.byline !== undefined) vimeoOptions.byline = this.options.byline;
|
|
176
305
|
if (this.options.color) vimeoOptions.color = this.options.color;
|
|
177
|
-
|
|
306
|
+
|
|
307
|
+
vimeoOptions.controls = false; // ALWAYS FALSE - use MYETV controls
|
|
308
|
+
|
|
178
309
|
if (this.options.dnt) vimeoOptions.dnt = this.options.dnt;
|
|
179
310
|
if (this.options.loop) vimeoOptions.loop = this.options.loop;
|
|
180
311
|
if (this.options.muted) vimeoOptions.muted = this.options.muted;
|
|
181
|
-
if (this.options.pip !== undefined) vimeoOptions.pip = this.options.pip;
|
|
182
312
|
if (this.options.playsinline !== undefined) vimeoOptions.playsinline = this.options.playsinline;
|
|
183
313
|
if (this.options.portrait !== undefined) vimeoOptions.portrait = this.options.portrait;
|
|
184
314
|
if (this.options.quality && this.options.quality !== 'auto') vimeoOptions.quality = this.options.quality;
|
|
@@ -195,14 +325,11 @@
|
|
|
195
325
|
* Extract Vimeo ID from various formats
|
|
196
326
|
*/
|
|
197
327
|
extractVimeoId(input) {
|
|
198
|
-
// Already a number
|
|
199
328
|
if (typeof input === 'number') return input;
|
|
200
329
|
|
|
201
|
-
// Extract from URL
|
|
202
330
|
const match = input.match(/vimeo\.com\/(\d+)/);
|
|
203
331
|
if (match) return parseInt(match[1]);
|
|
204
332
|
|
|
205
|
-
// Try parsing as number
|
|
206
333
|
const parsed = parseInt(input);
|
|
207
334
|
if (!isNaN(parsed)) return parsed;
|
|
208
335
|
|
|
@@ -213,10 +340,7 @@
|
|
|
213
340
|
* Extract full Vimeo URL
|
|
214
341
|
*/
|
|
215
342
|
extractVimeoUrl(input) {
|
|
216
|
-
// Already a full URL
|
|
217
343
|
if (input.startsWith('http')) return input;
|
|
218
|
-
|
|
219
|
-
// Build URL from ID
|
|
220
344
|
return `https://vimeo.com/${input}`;
|
|
221
345
|
}
|
|
222
346
|
|
|
@@ -227,24 +351,25 @@
|
|
|
227
351
|
// Play event
|
|
228
352
|
this.vimeoPlayer.on('play', (data) => {
|
|
229
353
|
this.player.triggerEvent('play', data);
|
|
230
|
-
if (this.options.debug)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// Playing event
|
|
234
|
-
this.vimeoPlayer.on('playing', (data) => {
|
|
235
|
-
this.player.triggerEvent('playing', data);
|
|
354
|
+
if (this.options.debug) {
|
|
355
|
+
console.log('🎬 Vimeo: play', data);
|
|
356
|
+
}
|
|
236
357
|
});
|
|
237
358
|
|
|
238
359
|
// Pause event
|
|
239
360
|
this.vimeoPlayer.on('pause', (data) => {
|
|
240
361
|
this.player.triggerEvent('pause', data);
|
|
241
|
-
if (this.options.debug)
|
|
362
|
+
if (this.options.debug) {
|
|
363
|
+
console.log('🎬 Vimeo: pause', data);
|
|
364
|
+
}
|
|
242
365
|
});
|
|
243
366
|
|
|
244
367
|
// Ended event
|
|
245
368
|
this.vimeoPlayer.on('ended', (data) => {
|
|
246
369
|
this.player.triggerEvent('ended', data);
|
|
247
|
-
if (this.options.debug)
|
|
370
|
+
if (this.options.debug) {
|
|
371
|
+
console.log('🎬 Vimeo: ended', data);
|
|
372
|
+
}
|
|
248
373
|
});
|
|
249
374
|
|
|
250
375
|
// Time update event
|
|
@@ -256,21 +381,6 @@
|
|
|
256
381
|
});
|
|
257
382
|
});
|
|
258
383
|
|
|
259
|
-
// Progress event (buffering)
|
|
260
|
-
this.vimeoPlayer.on('progress', (data) => {
|
|
261
|
-
this.player.triggerEvent('progress', data);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// Seeking event
|
|
265
|
-
this.vimeoPlayer.on('seeking', (data) => {
|
|
266
|
-
this.player.triggerEvent('seeking', data);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// Seeked event
|
|
270
|
-
this.vimeoPlayer.on('seeked', (data) => {
|
|
271
|
-
this.player.triggerEvent('seeked', data);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
384
|
// Volume change event
|
|
275
385
|
this.vimeoPlayer.on('volumechange', (data) => {
|
|
276
386
|
this.player.triggerEvent('volumechange', data);
|
|
@@ -281,27 +391,35 @@
|
|
|
281
391
|
this.player.triggerEvent('playbackratechange', data);
|
|
282
392
|
});
|
|
283
393
|
|
|
284
|
-
// Buffer
|
|
394
|
+
// Buffer events
|
|
285
395
|
this.vimeoPlayer.on('bufferstart', () => {
|
|
286
396
|
this.player.triggerEvent('waiting');
|
|
287
|
-
if (this.options.debug)
|
|
397
|
+
if (this.options.debug) {
|
|
398
|
+
console.log('🎬 Vimeo: bufferstart');
|
|
399
|
+
}
|
|
288
400
|
});
|
|
289
401
|
|
|
290
|
-
// Buffer end event
|
|
291
402
|
this.vimeoPlayer.on('bufferend', () => {
|
|
292
403
|
this.player.triggerEvent('canplay');
|
|
293
|
-
if (this.options.debug)
|
|
404
|
+
if (this.options.debug) {
|
|
405
|
+
console.log('🎬 Vimeo: bufferend');
|
|
406
|
+
}
|
|
294
407
|
});
|
|
295
408
|
|
|
296
409
|
// Quality change event
|
|
297
410
|
this.vimeoPlayer.on('qualitychange', (data) => {
|
|
298
411
|
this.player.triggerEvent('qualitychange', data);
|
|
299
|
-
if (this.options.debug)
|
|
412
|
+
if (this.options.debug) {
|
|
413
|
+
console.log('🎬 Vimeo: quality changed to', data.quality);
|
|
414
|
+
}
|
|
300
415
|
});
|
|
301
416
|
|
|
302
|
-
//
|
|
303
|
-
this.vimeoPlayer.on('
|
|
304
|
-
this.player.triggerEvent('
|
|
417
|
+
// Loaded event
|
|
418
|
+
this.vimeoPlayer.on('loaded', (data) => {
|
|
419
|
+
this.player.triggerEvent('loadedmetadata', data);
|
|
420
|
+
if (this.options.debug) {
|
|
421
|
+
console.log('🎬 Vimeo: loaded', data);
|
|
422
|
+
}
|
|
305
423
|
});
|
|
306
424
|
|
|
307
425
|
// Error event
|
|
@@ -309,51 +427,65 @@
|
|
|
309
427
|
console.error('🎬 Vimeo error:', data);
|
|
310
428
|
this.player.triggerEvent('error', data);
|
|
311
429
|
});
|
|
312
|
-
|
|
313
|
-
// Loaded event
|
|
314
|
-
this.vimeoPlayer.on('loaded', (data) => {
|
|
315
|
-
this.player.triggerEvent('loadedmetadata', data);
|
|
316
|
-
if (this.options.debug) console.log('🎬 Vimeo: loaded', data);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// Text track change
|
|
320
|
-
this.vimeoPlayer.on('texttrackchange', (data) => {
|
|
321
|
-
this.player.triggerEvent('texttrackchange', data);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
// Chapter change
|
|
325
|
-
this.vimeoPlayer.on('chapterchange', (data) => {
|
|
326
|
-
this.player.triggerEvent('chapterchange', data);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// Picture-in-Picture events
|
|
330
|
-
this.vimeoPlayer.on('enterpictureinpicture', () => {
|
|
331
|
-
this.player.triggerEvent('enterpictureinpicture');
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
this.vimeoPlayer.on('leavepictureinpicture', () => {
|
|
335
|
-
this.player.triggerEvent('leavepictureinpicture');
|
|
336
|
-
});
|
|
337
430
|
}
|
|
338
431
|
|
|
339
432
|
/**
|
|
340
|
-
* Load available qualities
|
|
433
|
+
* Load available qualities and sync with MYETV player
|
|
341
434
|
*/
|
|
342
435
|
loadQualities() {
|
|
343
436
|
this.vimeoPlayer.getQualities().then(qualities => {
|
|
344
437
|
this.availableQualities = qualities;
|
|
345
438
|
|
|
346
|
-
|
|
347
|
-
|
|
439
|
+
if (this.options.debug) {
|
|
440
|
+
console.log('🎬 Vimeo: Raw qualities from API:', qualities);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Inject fake qualities into player.qualities array
|
|
444
|
+
this.player.qualities = qualities.map(q => ({
|
|
445
|
+
src: '',
|
|
446
|
+
quality: q,
|
|
447
|
+
height: this.parseQualityHeight(q),
|
|
448
|
+
type: 'video/vimeo'
|
|
449
|
+
}));
|
|
450
|
+
|
|
451
|
+
// Add AUTO at the beginning
|
|
452
|
+
this.player.qualities.unshift({
|
|
453
|
+
src: '',
|
|
454
|
+
quality: 'auto',
|
|
455
|
+
height: 9999,
|
|
456
|
+
type: 'video/vimeo'
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Set selected quality
|
|
460
|
+
this.player.selectedQuality = 'auto';
|
|
348
461
|
|
|
349
462
|
if (this.options.debug) {
|
|
350
|
-
console.log('🎬 Vimeo:
|
|
463
|
+
console.log('🎬 Vimeo: player.qualities set:', this.player.qualities);
|
|
464
|
+
console.log('🎬 Vimeo: qualities length:', this.player.qualities.length);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Force update quality button and menu using native methods
|
|
468
|
+
if (typeof this.player.updateQualityButton === 'function') {
|
|
469
|
+
this.player.updateQualityButton();
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (typeof this.player.updateQualityMenu === 'function') {
|
|
473
|
+
this.player.updateQualityMenu();
|
|
351
474
|
}
|
|
352
475
|
|
|
353
|
-
//
|
|
476
|
+
// Wait a bit then populate with Vimeo-specific options
|
|
477
|
+
setTimeout(() => {
|
|
478
|
+
this.updatePlayerQualityMenu();
|
|
479
|
+
}, 300);
|
|
480
|
+
|
|
481
|
+
// Trigger event
|
|
482
|
+
this.player.triggerEvent('qualitiesloaded', { qualities });
|
|
483
|
+
|
|
484
|
+
// Set initial quality
|
|
354
485
|
if (this.options.quality && this.options.quality !== 'auto') {
|
|
355
486
|
this.setQuality(this.options.quality);
|
|
356
487
|
}
|
|
488
|
+
|
|
357
489
|
}).catch(error => {
|
|
358
490
|
if (this.options.debug) {
|
|
359
491
|
console.warn('🎬 Vimeo: Could not load qualities', error);
|
|
@@ -362,17 +494,129 @@
|
|
|
362
494
|
}
|
|
363
495
|
|
|
364
496
|
/**
|
|
365
|
-
|
|
497
|
+
* Update native player quality menu with Vimeo-specific handlers
|
|
498
|
+
*/
|
|
499
|
+
updatePlayerQualityMenu() {
|
|
500
|
+
const qualityMenu = this.player.container.querySelector('.quality-menu');
|
|
501
|
+
if (!qualityMenu) {
|
|
502
|
+
if (this.options.debug) {
|
|
503
|
+
console.warn('🎬 Vimeo: Quality menu not found');
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Clear and repopulate with Vimeo click handlers
|
|
509
|
+
qualityMenu.innerHTML = '';
|
|
510
|
+
|
|
511
|
+
// Add Auto option
|
|
512
|
+
const autoOption = document.createElement('div');
|
|
513
|
+
autoOption.className = 'quality-option active';
|
|
514
|
+
autoOption.setAttribute('data-quality', 'auto');
|
|
515
|
+
autoOption.textContent = 'Auto';
|
|
516
|
+
autoOption.addEventListener('click', () => {
|
|
517
|
+
this.handleQualityClick('auto');
|
|
518
|
+
});
|
|
519
|
+
qualityMenu.appendChild(autoOption);
|
|
520
|
+
|
|
521
|
+
// Add Vimeo qualities
|
|
522
|
+
this.availableQualities.forEach(quality => {
|
|
523
|
+
const option = document.createElement('div');
|
|
524
|
+
option.className = 'quality-option';
|
|
525
|
+
option.setAttribute('data-quality', quality);
|
|
526
|
+
option.textContent = quality;
|
|
527
|
+
option.addEventListener('click', () => {
|
|
528
|
+
this.handleQualityClick(quality);
|
|
529
|
+
});
|
|
530
|
+
qualityMenu.appendChild(option);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
if (this.options.debug) {
|
|
534
|
+
console.log('🎬 Vimeo: Quality menu updated');
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Update native player quality menu with Vimeo qualities
|
|
366
540
|
*/
|
|
367
|
-
|
|
368
|
-
|
|
541
|
+
updatePlayerQualityMenu() {
|
|
542
|
+
setTimeout(() => {
|
|
543
|
+
const qualityMenu = this.player.container.querySelector('.quality-menu');
|
|
544
|
+
if (!qualityMenu) {
|
|
545
|
+
if (this.options.debug) {
|
|
546
|
+
console.warn('🎬 Vimeo: Quality menu not found in DOM');
|
|
547
|
+
}
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Clear existing
|
|
552
|
+
qualityMenu.innerHTML = '';
|
|
553
|
+
|
|
554
|
+
// Add Auto option
|
|
555
|
+
const autoOption = document.createElement('div');
|
|
556
|
+
autoOption.className = 'quality-option active';
|
|
557
|
+
autoOption.setAttribute('data-quality', 'auto');
|
|
558
|
+
autoOption.textContent = 'Auto';
|
|
559
|
+
autoOption.addEventListener('click', () => {
|
|
560
|
+
this.handleQualityClick('auto');
|
|
561
|
+
});
|
|
562
|
+
qualityMenu.appendChild(autoOption);
|
|
563
|
+
|
|
564
|
+
// Add Vimeo qualities
|
|
565
|
+
this.availableQualities.forEach(quality => {
|
|
566
|
+
const option = document.createElement('div');
|
|
567
|
+
option.className = 'quality-option';
|
|
568
|
+
option.setAttribute('data-quality', quality);
|
|
569
|
+
option.textContent = quality;
|
|
570
|
+
option.addEventListener('click', () => {
|
|
571
|
+
this.handleQualityClick(quality);
|
|
572
|
+
});
|
|
573
|
+
qualityMenu.appendChild(option);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
if (this.options.debug) {
|
|
577
|
+
console.log('🎬 Vimeo: Quality menu populated');
|
|
578
|
+
}
|
|
579
|
+
}, 300);
|
|
369
580
|
}
|
|
370
581
|
|
|
371
582
|
/**
|
|
372
|
-
*
|
|
583
|
+
* Handle quality menu click
|
|
373
584
|
*/
|
|
374
|
-
|
|
375
|
-
|
|
585
|
+
handleQualityClick(quality) {
|
|
586
|
+
// Update active class
|
|
587
|
+
const qualityMenu = this.player.container.querySelector('.quality-menu');
|
|
588
|
+
if (qualityMenu) {
|
|
589
|
+
qualityMenu.querySelectorAll('.quality-option').forEach(opt => {
|
|
590
|
+
opt.classList.remove('active');
|
|
591
|
+
});
|
|
592
|
+
const selected = qualityMenu.querySelector(`[data-quality="${quality}"]`);
|
|
593
|
+
if (selected) {
|
|
594
|
+
selected.classList.add('active');
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Update button label
|
|
599
|
+
const qualityBtn = this.player.container.querySelector('.quality-btn span');
|
|
600
|
+
if (qualityBtn) {
|
|
601
|
+
qualityBtn.textContent = quality === 'auto' ? 'Auto' : quality;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Set quality
|
|
605
|
+
this.setQuality(quality).then(() => {
|
|
606
|
+
// Close menu
|
|
607
|
+
if (qualityMenu) {
|
|
608
|
+
qualityMenu.classList.remove('show');
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Parse quality string to height number
|
|
615
|
+
*/
|
|
616
|
+
parseQualityHeight(quality) {
|
|
617
|
+
if (!quality) return 0;
|
|
618
|
+
const match = quality.match(/(\d+)p/);
|
|
619
|
+
return match ? parseInt(match[1]) : 0;
|
|
376
620
|
}
|
|
377
621
|
|
|
378
622
|
/**
|
|
@@ -392,6 +636,20 @@
|
|
|
392
636
|
});
|
|
393
637
|
}
|
|
394
638
|
|
|
639
|
+
/**
|
|
640
|
+
* Get available qualities
|
|
641
|
+
*/
|
|
642
|
+
getQualities() {
|
|
643
|
+
return this.availableQualities;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Get current quality
|
|
648
|
+
*/
|
|
649
|
+
getCurrentQuality() {
|
|
650
|
+
return this.vimeoPlayer.getQuality();
|
|
651
|
+
}
|
|
652
|
+
|
|
395
653
|
/**
|
|
396
654
|
* Hide native player
|
|
397
655
|
*/
|
|
@@ -403,210 +661,266 @@
|
|
|
403
661
|
}
|
|
404
662
|
|
|
405
663
|
/**
|
|
406
|
-
*
|
|
664
|
+
* Override native video element methods to control Vimeo player
|
|
407
665
|
*/
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
666
|
+
overrideNativeMethods() {
|
|
667
|
+
if (!this.player.video) return;
|
|
668
|
+
|
|
669
|
+
const video = this.player.video;
|
|
670
|
+
const vimeoPlayer = this.vimeoPlayer;
|
|
671
|
+
const self = this;
|
|
672
|
+
|
|
673
|
+
// Override play method
|
|
674
|
+
video._originalPlay = video.play;
|
|
675
|
+
video.play = function () {
|
|
676
|
+
vimeoPlayer.play().catch(err => {
|
|
677
|
+
if (self.options.debug) {
|
|
678
|
+
console.error('Play error:', err);
|
|
679
|
+
}
|
|
421
680
|
});
|
|
681
|
+
return Promise.resolve();
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
// Override pause method
|
|
685
|
+
video._originalPause = video.pause;
|
|
686
|
+
video.pause = function () {
|
|
687
|
+
vimeoPlayer.pause().catch(err => {
|
|
688
|
+
if (self.options.debug) {
|
|
689
|
+
console.error('Pause error:', err);
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
// Initialize cached values
|
|
695
|
+
video._cachedCurrentTime = 0;
|
|
696
|
+
video._cachedVolume = 1;
|
|
697
|
+
video._cachedMuted = false;
|
|
698
|
+
video._cachedPlaybackRate = 1;
|
|
699
|
+
video._cachedDuration = 0;
|
|
700
|
+
video._cachedPaused = true;
|
|
701
|
+
|
|
702
|
+
// Override properties
|
|
703
|
+
Object.defineProperties(video, {
|
|
704
|
+
currentTime: {
|
|
705
|
+
get: function () {
|
|
706
|
+
return this._cachedCurrentTime || 0;
|
|
707
|
+
},
|
|
708
|
+
set: function (time) {
|
|
709
|
+
vimeoPlayer.setCurrentTime(time).catch(err => {
|
|
710
|
+
if (self.options.debug) {
|
|
711
|
+
console.error('Seek error:', err);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
},
|
|
715
|
+
configurable: true
|
|
716
|
+
},
|
|
717
|
+
volume: {
|
|
718
|
+
get: function () {
|
|
719
|
+
return this._cachedVolume || 1;
|
|
720
|
+
},
|
|
721
|
+
set: function (volume) {
|
|
722
|
+
vimeoPlayer.setVolume(volume).catch(err => {
|
|
723
|
+
if (self.options.debug) {
|
|
724
|
+
console.error('Volume error:', err);
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
},
|
|
728
|
+
configurable: true
|
|
729
|
+
},
|
|
730
|
+
muted: {
|
|
731
|
+
get: function () {
|
|
732
|
+
return this._cachedMuted || false;
|
|
733
|
+
},
|
|
734
|
+
set: function (muted) {
|
|
735
|
+
vimeoPlayer.setMuted(muted).catch(err => {
|
|
736
|
+
if (self.options.debug) {
|
|
737
|
+
console.error('Mute error:', err);
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
},
|
|
741
|
+
configurable: true
|
|
742
|
+
},
|
|
743
|
+
playbackRate: {
|
|
744
|
+
get: function () {
|
|
745
|
+
return this._cachedPlaybackRate || 1;
|
|
746
|
+
},
|
|
747
|
+
set: function (rate) {
|
|
748
|
+
vimeoPlayer.setPlaybackRate(rate).catch(err => {
|
|
749
|
+
if (self.options.debug) {
|
|
750
|
+
console.error('Speed error:', err);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
},
|
|
754
|
+
configurable: true
|
|
755
|
+
},
|
|
756
|
+
duration: {
|
|
757
|
+
get: function () {
|
|
758
|
+
return this._cachedDuration || 0;
|
|
759
|
+
},
|
|
760
|
+
configurable: true
|
|
761
|
+
},
|
|
762
|
+
paused: {
|
|
763
|
+
get: function () {
|
|
764
|
+
return this._cachedPaused !== false;
|
|
765
|
+
},
|
|
766
|
+
configurable: true
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
if (this.options.debug) {
|
|
771
|
+
console.log('🎬 Vimeo: Native methods overridden');
|
|
422
772
|
}
|
|
423
773
|
}
|
|
424
774
|
|
|
425
775
|
/**
|
|
426
|
-
*
|
|
776
|
+
* Sync with native player controls
|
|
427
777
|
*/
|
|
428
|
-
|
|
429
|
-
if (!this.
|
|
778
|
+
syncWithNativeControls() {
|
|
779
|
+
if (!this.player.video) return;
|
|
430
780
|
|
|
431
|
-
const
|
|
781
|
+
const video = this.player.video;
|
|
432
782
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
console.error('🎬 Vimeo: Failed to load video', error);
|
|
441
|
-
throw error;
|
|
783
|
+
// Update cached properties from Vimeo events
|
|
784
|
+
this.vimeoPlayer.on('timeupdate', (data) => {
|
|
785
|
+
video._cachedCurrentTime = data.seconds;
|
|
786
|
+
video._cachedDuration = data.duration;
|
|
787
|
+
|
|
788
|
+
const event = new Event('timeupdate');
|
|
789
|
+
video.dispatchEvent(event);
|
|
442
790
|
});
|
|
443
|
-
}
|
|
444
791
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
*/
|
|
448
|
-
async getVideoMetadata(videoIdOrUrl) {
|
|
449
|
-
const videoUrl = this.extractVimeoUrl(videoIdOrUrl);
|
|
450
|
-
const oembedUrl = `https://vimeo.com/api/oembed.json?url=${encodeURIComponent(videoUrl)}`;
|
|
792
|
+
this.vimeoPlayer.on('play', () => {
|
|
793
|
+
video._cachedPaused = false;
|
|
451
794
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
795
|
+
if (this.player.elements && this.player.elements.loading) {
|
|
796
|
+
this.player.elements.loading.style.display = 'none';
|
|
797
|
+
}
|
|
455
798
|
|
|
456
|
-
|
|
457
|
-
|
|
799
|
+
const event = new Event('play');
|
|
800
|
+
video.dispatchEvent(event);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
this.vimeoPlayer.on('playing', () => {
|
|
804
|
+
video._cachedPaused = false;
|
|
805
|
+
|
|
806
|
+
if (this.player.elements && this.player.elements.loading) {
|
|
807
|
+
this.player.elements.loading.style.display = 'none';
|
|
458
808
|
}
|
|
459
809
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
thumbnail: data.thumbnail_url,
|
|
464
|
-
thumbnailLarge: data.thumbnail_url.replace(/_\d+x\d+/, '_1280x720'),
|
|
465
|
-
duration: data.duration,
|
|
466
|
-
author: data.author_name,
|
|
467
|
-
authorUrl: data.author_url,
|
|
468
|
-
width: data.width,
|
|
469
|
-
height: data.height,
|
|
470
|
-
html: data.html
|
|
471
|
-
};
|
|
472
|
-
} catch (error) {
|
|
473
|
-
console.error('🎬 Vimeo: Failed to fetch metadata', error);
|
|
474
|
-
throw error;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
810
|
+
const event = new Event('playing');
|
|
811
|
+
video.dispatchEvent(event);
|
|
812
|
+
});
|
|
477
813
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
*/
|
|
481
|
-
play() {
|
|
482
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
483
|
-
return this.vimeoPlayer.play();
|
|
484
|
-
}
|
|
814
|
+
this.vimeoPlayer.on('pause', () => {
|
|
815
|
+
video._cachedPaused = true;
|
|
485
816
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
pause() {
|
|
490
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
491
|
-
return this.vimeoPlayer.pause();
|
|
492
|
-
}
|
|
817
|
+
const event = new Event('pause');
|
|
818
|
+
video.dispatchEvent(event);
|
|
819
|
+
});
|
|
493
820
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
*/
|
|
497
|
-
getCurrentTime() {
|
|
498
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
499
|
-
return this.vimeoPlayer.getCurrentTime();
|
|
500
|
-
}
|
|
821
|
+
this.vimeoPlayer.on('volumechange', (data) => {
|
|
822
|
+
video._cachedVolume = data.volume;
|
|
501
823
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
setCurrentTime(seconds) {
|
|
506
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
507
|
-
return this.vimeoPlayer.setCurrentTime(seconds);
|
|
508
|
-
}
|
|
824
|
+
const event = new Event('volumechange');
|
|
825
|
+
video.dispatchEvent(event);
|
|
826
|
+
});
|
|
509
827
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
*/
|
|
513
|
-
getDuration() {
|
|
514
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
515
|
-
return this.vimeoPlayer.getDuration();
|
|
516
|
-
}
|
|
828
|
+
this.vimeoPlayer.on('playbackratechange', (data) => {
|
|
829
|
+
video._cachedPlaybackRate = data.playbackRate;
|
|
517
830
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
getVolume() {
|
|
522
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
523
|
-
return this.vimeoPlayer.getVolume();
|
|
524
|
-
}
|
|
831
|
+
const event = new Event('ratechange');
|
|
832
|
+
video.dispatchEvent(event);
|
|
833
|
+
});
|
|
525
834
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
*/
|
|
529
|
-
setVolume(volume) {
|
|
530
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
531
|
-
return this.vimeoPlayer.setVolume(volume);
|
|
532
|
-
}
|
|
835
|
+
this.vimeoPlayer.on('loaded', (data) => {
|
|
836
|
+
video._cachedDuration = data.duration;
|
|
533
837
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
getMuted() {
|
|
538
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
539
|
-
return this.vimeoPlayer.getMuted();
|
|
540
|
-
}
|
|
838
|
+
if (this.player.elements && this.player.elements.loading) {
|
|
839
|
+
this.player.elements.loading.style.display = 'none';
|
|
840
|
+
}
|
|
541
841
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
setMuted(muted) {
|
|
546
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
547
|
-
return this.vimeoPlayer.setMuted(muted);
|
|
548
|
-
}
|
|
842
|
+
const event = new Event('loadedmetadata');
|
|
843
|
+
video.dispatchEvent(event);
|
|
844
|
+
});
|
|
549
845
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
*/
|
|
553
|
-
getPlaybackRate() {
|
|
554
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
555
|
-
return this.vimeoPlayer.getPlaybackRate();
|
|
556
|
-
}
|
|
846
|
+
this.vimeoPlayer.on('ended', () => {
|
|
847
|
+
video._cachedPaused = true;
|
|
557
848
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
setPlaybackRate(rate) {
|
|
562
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
563
|
-
return this.vimeoPlayer.setPlaybackRate(rate);
|
|
564
|
-
}
|
|
849
|
+
const event = new Event('ended');
|
|
850
|
+
video.dispatchEvent(event);
|
|
851
|
+
});
|
|
565
852
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
571
|
-
return this.vimeoPlayer.requestFullscreen();
|
|
572
|
-
}
|
|
853
|
+
// Get initial values
|
|
854
|
+
this.vimeoPlayer.getDuration().then(duration => {
|
|
855
|
+
video._cachedDuration = duration;
|
|
856
|
+
});
|
|
573
857
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
858
|
+
this.vimeoPlayer.getVolume().then(volume => {
|
|
859
|
+
video._cachedVolume = volume;
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
this.vimeoPlayer.getPlaybackRate().then(rate => {
|
|
863
|
+
video._cachedPlaybackRate = rate;
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
this.vimeoPlayer.getPaused().then(paused => {
|
|
867
|
+
video._cachedPaused = paused;
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
if (this.options.debug) {
|
|
871
|
+
console.log('🎬 Vimeo: Synced with native controls');
|
|
872
|
+
}
|
|
580
873
|
}
|
|
581
874
|
|
|
582
875
|
/**
|
|
583
|
-
*
|
|
876
|
+
* Play video
|
|
584
877
|
*/
|
|
585
|
-
|
|
878
|
+
play() {
|
|
586
879
|
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
587
|
-
return this.vimeoPlayer.
|
|
880
|
+
return this.vimeoPlayer.play();
|
|
588
881
|
}
|
|
589
882
|
|
|
590
883
|
/**
|
|
591
|
-
*
|
|
884
|
+
* Pause video
|
|
592
885
|
*/
|
|
593
|
-
|
|
886
|
+
pause() {
|
|
594
887
|
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
595
|
-
return this.vimeoPlayer.
|
|
888
|
+
return this.vimeoPlayer.pause();
|
|
596
889
|
}
|
|
597
890
|
|
|
598
891
|
/**
|
|
599
|
-
*
|
|
892
|
+
* Load new video
|
|
600
893
|
*/
|
|
601
|
-
|
|
894
|
+
loadVideo(videoIdOrUrl) {
|
|
602
895
|
if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
|
|
603
|
-
|
|
896
|
+
|
|
897
|
+
const videoId = this.extractVimeoId(videoIdOrUrl);
|
|
898
|
+
|
|
899
|
+
return this.vimeoPlayer.loadVideo(videoId).then(id => {
|
|
900
|
+
if (this.options.debug) {
|
|
901
|
+
console.log('🎬 Vimeo: Video loaded', id);
|
|
902
|
+
}
|
|
903
|
+
this.loadQualities();
|
|
904
|
+
return id;
|
|
905
|
+
}).catch(error => {
|
|
906
|
+
console.error('🎬 Vimeo: Failed to load video', error);
|
|
907
|
+
throw error;
|
|
908
|
+
});
|
|
604
909
|
}
|
|
605
910
|
|
|
606
911
|
/**
|
|
607
912
|
* Dispose plugin
|
|
608
913
|
*/
|
|
609
914
|
dispose() {
|
|
915
|
+
// Cleanup PIP observers and listeners
|
|
916
|
+
if (this.pipObserver) {
|
|
917
|
+
this.pipObserver.disconnect();
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (this.pipCleanup) {
|
|
921
|
+
this.pipCleanup();
|
|
922
|
+
}
|
|
923
|
+
|
|
610
924
|
if (this.vimeoPlayer) {
|
|
611
925
|
this.vimeoPlayer.destroy().then(() => {
|
|
612
926
|
if (this.options.debug) {
|
|
@@ -635,6 +949,5 @@
|
|
|
635
949
|
if (typeof window.registerMYETVPlugin === 'function') {
|
|
636
950
|
window.registerMYETVPlugin('vimeo', VimeoPlugin);
|
|
637
951
|
}
|
|
638
|
-
|
|
639
952
|
})();
|
|
640
953
|
/* GLOBAL_END */
|