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.
@@ -32,14 +32,13 @@
32
32
  background: options.background || false,
33
33
  byline: options.byline !== false,
34
34
  color: options.color || null,
35
- controls: options.controls !== false,
36
- dnt: options.dnt || false, // Do Not Track
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', // auto, 360p, 540p, 720p, 1080p, 2k, 4k
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
- this.vimeoContainer.style.cssText = `
120
- position: absolute;
121
- top: 0;
122
- left: 0;
123
- width: 100%;
124
- height: 100%;
125
- z-index: 100;
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
- // Sync with native player controls if requested
143
- if (this.options.syncControls) {
144
- this.syncWithNativeControls();
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
- if (this.options.controls !== undefined) vimeoOptions.controls = this.options.controls;
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) console.log('🎬 Vimeo: play', data);
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) console.log('🎬 Vimeo: pause', data);
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) console.log('🎬 Vimeo: ended', data);
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 start event
394
+ // Buffer events
285
395
  this.vimeoPlayer.on('bufferstart', () => {
286
396
  this.player.triggerEvent('waiting');
287
- if (this.options.debug) console.log('🎬 Vimeo: bufferstart');
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) console.log('🎬 Vimeo: bufferend');
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) console.log('🎬 Vimeo: quality changed to', data.quality);
412
+ if (this.options.debug) {
413
+ console.log('🎬 Vimeo: quality changed to', data.quality);
414
+ }
300
415
  });
301
416
 
302
- // Fullscreen change event
303
- this.vimeoPlayer.on('fullscreenchange', (data) => {
304
- this.player.triggerEvent('fullscreenchange', data);
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
- // Trigger custom event with qualities
347
- this.player.triggerEvent('qualitiesloaded', { qualities });
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: Available qualities', qualities);
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
- // Set initial quality if specified
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
- * Get available qualities
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
- getQualities() {
368
- return this.availableQualities;
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
- * Get current quality
583
+ * Handle quality menu click
373
584
  */
374
- getCurrentQuality() {
375
- return this.vimeoPlayer.getQuality();
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
- * Sync with native player controls
664
+ * Override native video element methods to control Vimeo player
407
665
  */
408
- syncWithNativeControls() {
409
- // This would integrate with your custom player controls
410
- // Example: sync play/pause buttons
411
- const playButton = this.player.container.querySelector('.play-button');
412
- if (playButton) {
413
- playButton.addEventListener('click', () => {
414
- this.vimeoPlayer.getPaused().then(paused => {
415
- if (paused) {
416
- this.vimeoPlayer.play();
417
- } else {
418
- this.vimeoPlayer.pause();
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
- * Load new video
776
+ * Sync with native player controls
427
777
  */
428
- loadVideo(videoIdOrUrl) {
429
- if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
778
+ syncWithNativeControls() {
779
+ if (!this.player.video) return;
430
780
 
431
- const videoId = this.extractVimeoId(videoIdOrUrl);
781
+ const video = this.player.video;
432
782
 
433
- return this.vimeoPlayer.loadVideo(videoId).then(id => {
434
- if (this.options.debug) {
435
- console.log('🎬 Vimeo: Video loaded', id);
436
- }
437
- this.loadQualities();
438
- return id;
439
- }).catch(error => {
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
- * Get video metadata via oEmbed API
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
- try {
453
- const response = await fetch(oembedUrl);
454
- const data = await response.json();
795
+ if (this.player.elements && this.player.elements.loading) {
796
+ this.player.elements.loading.style.display = 'none';
797
+ }
455
798
 
456
- if (this.options.debug) {
457
- console.log('🎬 Vimeo: Video metadata', data);
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
- return {
461
- title: data.title,
462
- description: data.description,
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
- * Play video
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
- * Pause video
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
- * Get current time
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
- * Set current time
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
- * Get duration
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
- * Get volume
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
- * Set volume
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
- * Get muted state
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
- * Set muted state
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
- * Get playback rate
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
- * Set playback rate
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
- * Request fullscreen
568
- */
569
- requestFullscreen() {
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
- * Exit fullscreen
576
- */
577
- exitFullscreen() {
578
- if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
579
- return this.vimeoPlayer.exitFullscreen();
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
- * Get text tracks
876
+ * Play video
584
877
  */
585
- getTextTracks() {
878
+ play() {
586
879
  if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
587
- return this.vimeoPlayer.getTextTracks();
880
+ return this.vimeoPlayer.play();
588
881
  }
589
882
 
590
883
  /**
591
- * Enable text track
884
+ * Pause video
592
885
  */
593
- enableTextTrack(language, kind) {
886
+ pause() {
594
887
  if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
595
- return this.vimeoPlayer.enableTextTrack(language, kind);
888
+ return this.vimeoPlayer.pause();
596
889
  }
597
890
 
598
891
  /**
599
- * Disable text track
892
+ * Load new video
600
893
  */
601
- disableTextTrack() {
894
+ loadVideo(videoIdOrUrl) {
602
895
  if (!this.vimeoPlayer) return Promise.reject('Player not initialized');
603
- return this.vimeoPlayer.disableTextTrack();
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 */