myetv-player 1.4.0 → 1.6.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 +65 -1
- package/css/myetv-player.css +7 -2
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +669 -79
- package/dist/myetv-player.min.js +579 -42
- package/package.json +4 -2
- package/plugins/google-adsense-ads/README.md +230 -0
- package/plugins/google-adsense-ads/g-adsense-ads-plugin.js +75 -8
- package/plugins/google-ima-ads/README.md +258 -0
- package/plugins/google-ima-ads/g-ima-ads-plugin.js +2 -2
- package/plugins/soundcloud/myetv-player-soundcloud-plugin.js +927 -0
- package/plugins/soundcloud/readme.md +89 -0
- package/plugins/youtube/myetv-player-youtube-plugin.js +155 -164
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# MYETV SoundCloud Plugin
|
|
2
|
+
|
|
3
|
+
**SoundCloud Plugin for MyETV Player** - Seamlessly integrates SoundCloud into MyETV Player with native controls.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Native Controls**: Play/Pause, Volume, Mute/Unmute
|
|
8
|
+
- ✅ **Full Progress Bar**: Precise seek + time tooltip on mouseover
|
|
9
|
+
- ✅ **Auto Restore**: Hides SoundCloud after 10 seconds
|
|
10
|
+
- ✅ **Volume Slider**: Drag & drop with precise decimals (0-1)
|
|
11
|
+
- ✅ **YouTube Compatible**: Same style and behavior
|
|
12
|
+
- ✅ **Zero Conflicts**: Works only in plugin, doesn't touch main player
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
1. **Add plugin to MyETV Player**:
|
|
17
|
+
```
|
|
18
|
+
const player = new MyETVPlayer(container, {
|
|
19
|
+
plugins: [{
|
|
20
|
+
name: 'soundcloud',
|
|
21
|
+
src: 'myetv-player-soundcloud-plugin.js'
|
|
22
|
+
}]
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Detailed Features
|
|
27
|
+
|
|
28
|
+
| Function | Implementation | Status |
|
|
29
|
+
|----------|----------------|--------|
|
|
30
|
+
| **Play/Pause** | `widget.bind(PLAY/PAUSE)` | ✅ |
|
|
31
|
+
| **Volume** | `widget.setVolume(0-100)` | ✅ |
|
|
32
|
+
| **Seek** | `widget.seekTo(ms)` | ✅ |
|
|
33
|
+
| **Tooltip** | Mouseover progress bar | ✅ |
|
|
34
|
+
| **Auto-hide** | 10s timeout | ✅ |
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Configurable Options
|
|
38
|
+
```
|
|
39
|
+
plugins: [{
|
|
40
|
+
name: 'soundcloud',
|
|
41
|
+
src: 'myetv-player-soundcloud-plugin.js',
|
|
42
|
+
options: {
|
|
43
|
+
soundcloudUrl: 'https://soundcloud.com/artist/track', // REQUIRED
|
|
44
|
+
debug: true, // Detailed logs
|
|
45
|
+
controlsDisplayTime: 10000, // Auto-hide timeout (ms)
|
|
46
|
+
color: 'ff5500', // Player color (hex)
|
|
47
|
+
autoPlay: false, // Auto play
|
|
48
|
+
hideRelated: true, // Hide related tracks
|
|
49
|
+
showComments: false, // Show comments
|
|
50
|
+
showUser: true, // Show user info
|
|
51
|
+
showReposts: false, // Show reposts
|
|
52
|
+
showTeaser: false, // Show teaser
|
|
53
|
+
visual: false, // Waveform mode
|
|
54
|
+
showArtwork: true, // Show artwork
|
|
55
|
+
buying: false, // Show buy button
|
|
56
|
+
sharing: false, // Show share button
|
|
57
|
+
download: false, // Show download button
|
|
58
|
+
showPlaycount: false // Show play count
|
|
59
|
+
}
|
|
60
|
+
}]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Compatibility
|
|
64
|
+
|
|
65
|
+
- ✅ **Desktop**: Chrome, Firefox, Safari, Edge
|
|
66
|
+
- ✅ **Mobile**: iOS Safari, Android Chrome
|
|
67
|
+
- ✅ **Smart TV**: Fire TV, Android TV
|
|
68
|
+
- ✅ **Picture-in-Picture**: Full support
|
|
69
|
+
|
|
70
|
+
## Contributing
|
|
71
|
+
|
|
72
|
+
1. Fork the project
|
|
73
|
+
2. Create feature branch (`git checkout -b feature/AmazingFeature`)
|
|
74
|
+
3. Commit (`git commit -m 'Add some AmazingFeature'`)
|
|
75
|
+
4. Push (`git push origin feature/AmazingFeature`)
|
|
76
|
+
5. Open Pull Request
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
Distributed under the [MIT License](LICENSE).
|
|
81
|
+
|
|
82
|
+
## Authors & Acknowledgments
|
|
83
|
+
|
|
84
|
+
- **MYETV Team** - Main player
|
|
85
|
+
- **SoundCloud Widget API** - [Documentation](https://developers.soundcloud.com/docs/api/widget)
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
⭐ **Star this repo if useful!** ⭐
|
|
@@ -320,6 +320,25 @@
|
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
// Helper: update menu selection highlight
|
|
324
|
+
updateMenuHeight() {
|
|
325
|
+
const menu = this.api.container.querySelector('.settings-menu');
|
|
326
|
+
if (!menu) return;
|
|
327
|
+
|
|
328
|
+
const settingsBtn = this.api.container.querySelector('.settings-btn');
|
|
329
|
+
const containerRect = this.api.container.getBoundingClientRect();
|
|
330
|
+
const btnRect = settingsBtn.getBoundingClientRect();
|
|
331
|
+
|
|
332
|
+
// space from top of container to settings button
|
|
333
|
+
const distanceFromTop = btnRect.top - containerRect.top;
|
|
334
|
+
|
|
335
|
+
// Max height = distance from top - 30px padding
|
|
336
|
+
const maxHeight = Math.max(150, distanceFromTop - 30);
|
|
337
|
+
|
|
338
|
+
menu.style.maxHeight = `${maxHeight}px !important`;
|
|
339
|
+
menu.style.overflowY = 'scroll !important';
|
|
340
|
+
}
|
|
341
|
+
|
|
323
342
|
/**
|
|
324
343
|
* Handle responsive layout for mobile settings
|
|
325
344
|
*/
|
|
@@ -334,43 +353,18 @@
|
|
|
334
353
|
pipBtn.style.display = 'none';
|
|
335
354
|
}
|
|
336
355
|
|
|
337
|
-
// Breakpoint at 600px
|
|
338
|
-
if (containerWidth < 600) {
|
|
339
356
|
// Add max-height and scroll to settings menu on mobile
|
|
340
357
|
if (settingsMenu) {
|
|
341
358
|
const playerHeight = this.api.container.offsetHeight;
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const scrollbarStyle = document.createElement('style');
|
|
351
|
-
scrollbarStyle.id = 'yt-settings-scrollbar-style';
|
|
352
|
-
scrollbarStyle.textContent = `
|
|
353
|
-
.settings-menu::-webkit-scrollbar {
|
|
354
|
-
width: 6px;
|
|
355
|
-
}
|
|
356
|
-
.settings-menu::-webkit-scrollbar-track {
|
|
357
|
-
background: rgba(255,255,255,0.05);
|
|
358
|
-
border-radius: 3px;
|
|
359
|
-
}
|
|
360
|
-
.settings-menu::-webkit-scrollbar-thumb {
|
|
361
|
-
background: rgba(255,255,255,0.3);
|
|
362
|
-
border-radius: 3px;
|
|
363
|
-
}
|
|
364
|
-
.settings-menu::-webkit-scrollbar-thumb:hover {
|
|
365
|
-
background: rgba(255,255,255,0.5);
|
|
366
|
-
}
|
|
367
|
-
`;
|
|
368
|
-
document.head.appendChild(scrollbarStyle);
|
|
369
|
-
}
|
|
359
|
+
const settingsBtn = this.api.container.querySelector('.settings-btn');
|
|
360
|
+
const settingsBtnRect = settingsBtn.getBoundingClientRect();
|
|
361
|
+
const containerRect = this.api.container.getBoundingClientRect();
|
|
362
|
+
|
|
363
|
+
// Calculate distance from top of container to settings button
|
|
364
|
+
const distanceFromTop = settingsBtnRect.top - containerRect.top;
|
|
365
|
+
|
|
366
|
+
const maxMenuHeight = Math.min(300, playerHeight * 0.5);
|
|
370
367
|
|
|
371
|
-
// Firefox scrollbar
|
|
372
|
-
settingsMenu.style.scrollbarWidth = 'thin';
|
|
373
|
-
settingsMenu.style.scrollbarColor = 'rgba(255,255,255,0.3) transparent';
|
|
374
368
|
}
|
|
375
369
|
|
|
376
370
|
// Hide subtitles button
|
|
@@ -417,7 +411,6 @@
|
|
|
417
411
|
// Create trigger
|
|
418
412
|
const trigger = document.createElement('div');
|
|
419
413
|
trigger.className = 'quality-option';
|
|
420
|
-
trigger.style.fontSize = '10px';
|
|
421
414
|
trigger.textContent = subtitlesText;
|
|
422
415
|
|
|
423
416
|
// Add arrow indicator
|
|
@@ -450,7 +443,6 @@
|
|
|
450
443
|
padding: 6px 12px;
|
|
451
444
|
cursor: pointer;
|
|
452
445
|
color: white;
|
|
453
|
-
font-size: 10px;
|
|
454
446
|
white-space: normal;
|
|
455
447
|
word-wrap: break-word;
|
|
456
448
|
opacity: 0.8;
|
|
@@ -494,16 +486,19 @@
|
|
|
494
486
|
let isExpanded = false;
|
|
495
487
|
trigger.addEventListener('click', (e) => {
|
|
496
488
|
e.stopPropagation();
|
|
497
|
-
|
|
498
489
|
isExpanded = !isExpanded;
|
|
499
490
|
|
|
500
491
|
if (isExpanded) {
|
|
501
492
|
rebuildOptions();
|
|
502
493
|
optionsContainer.style.display = 'block';
|
|
503
494
|
arrow.style.transform = 'rotate(180deg)';
|
|
495
|
+
|
|
496
|
+
setTimeout(() => this.updateMenuHeight(), 10);
|
|
504
497
|
} else {
|
|
505
498
|
optionsContainer.style.display = 'none';
|
|
506
499
|
arrow.style.transform = 'rotate(0deg)';
|
|
500
|
+
|
|
501
|
+
setTimeout(() => this.updateMenuHeight(), 10);
|
|
507
502
|
}
|
|
508
503
|
});
|
|
509
504
|
|
|
@@ -533,7 +528,6 @@
|
|
|
533
528
|
// Create trigger
|
|
534
529
|
const trigger = document.createElement('div');
|
|
535
530
|
trigger.className = 'quality-option';
|
|
536
|
-
trigger.style.fontSize = '10px';
|
|
537
531
|
|
|
538
532
|
// Get current speed
|
|
539
533
|
const getCurrentSpeed = () => {
|
|
@@ -574,7 +568,6 @@
|
|
|
574
568
|
padding: 6px 12px;
|
|
575
569
|
cursor: pointer;
|
|
576
570
|
color: white;
|
|
577
|
-
font-size: 10px;
|
|
578
571
|
white-space: normal;
|
|
579
572
|
word-wrap: break-word;
|
|
580
573
|
opacity: 0.8;
|
|
@@ -630,9 +623,11 @@
|
|
|
630
623
|
rebuildOptions();
|
|
631
624
|
optionsContainer.style.display = 'block';
|
|
632
625
|
arrow.style.transform = 'rotate(180deg)';
|
|
626
|
+
setTimeout(() => this.updateMenuHeight(), 10);
|
|
633
627
|
} else {
|
|
634
628
|
optionsContainer.style.display = 'none';
|
|
635
629
|
arrow.style.transform = 'rotate(0deg)';
|
|
630
|
+
setTimeout(() => this.updateMenuHeight(), 10);
|
|
636
631
|
}
|
|
637
632
|
});
|
|
638
633
|
|
|
@@ -649,51 +644,7 @@
|
|
|
649
644
|
}
|
|
650
645
|
}
|
|
651
646
|
}
|
|
652
|
-
} else {
|
|
653
|
-
// Wide screen
|
|
654
|
-
if (subtitlesBtn) {
|
|
655
|
-
subtitlesBtn.style.display = '';
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Reset settings menu styles
|
|
659
|
-
if (settingsMenu) {
|
|
660
|
-
settingsMenu.style.maxHeight = '';
|
|
661
|
-
settingsMenu.style.overflowY = '';
|
|
662
|
-
settingsMenu.style.overflowX = '';
|
|
663
|
-
settingsMenu.style.scrollbarWidth = '';
|
|
664
|
-
settingsMenu.style.scrollbarColor = '';
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// Show original speed option again
|
|
668
|
-
if (settingsMenu) {
|
|
669
|
-
const originalSpeedOption = settingsMenu.querySelector('[data-action="speed"]');
|
|
670
|
-
if (originalSpeedOption) {
|
|
671
|
-
originalSpeedOption.style.display = '';
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Show expandable speed option again
|
|
675
|
-
const expandableSpeedWrapper = settingsMenu.querySelector('[data-action="speed-expand"]');
|
|
676
|
-
if (expandableSpeedWrapper) {
|
|
677
|
-
const wrapper = expandableSpeedWrapper.closest('.settings-expandable-wrapper');
|
|
678
|
-
if (wrapper) {
|
|
679
|
-
wrapper.style.display = '';
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
647
|
|
|
684
|
-
// Remove from settings
|
|
685
|
-
if (settingsMenu) {
|
|
686
|
-
const subtitlesWrapper = settingsMenu.querySelector('.yt-subtitles-wrapper');
|
|
687
|
-
if (subtitlesWrapper) {
|
|
688
|
-
subtitlesWrapper.remove();
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
const speedWrapper = settingsMenu.querySelector('.yt-speed-wrapper');
|
|
692
|
-
if (speedWrapper) {
|
|
693
|
-
speedWrapper.remove();
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
648
|
}
|
|
698
649
|
|
|
699
650
|
hidePipFromSettingsMenuOnly() {
|
|
@@ -1047,7 +998,7 @@
|
|
|
1047
998
|
z-index: 2;
|
|
1048
999
|
background: transparent;
|
|
1049
1000
|
pointer-events: ${pointerEvents};
|
|
1050
|
-
cursor:
|
|
1001
|
+
cursor: auto;
|
|
1051
1002
|
`;
|
|
1052
1003
|
|
|
1053
1004
|
this.api.container.insertBefore(this.mouseMoveOverlay, this.api.controls);
|
|
@@ -1416,14 +1367,27 @@
|
|
|
1416
1367
|
this.checkInitialCaptionState();
|
|
1417
1368
|
}, 2500); // after 2.5s
|
|
1418
1369
|
|
|
1419
|
-
// Initialize cursor
|
|
1370
|
+
// Initialize cursor synchronization with player's auto-hide system
|
|
1420
1371
|
if (!this.options.showYouTubeUI && this.api.player.options.hideCursor) {
|
|
1421
|
-
//
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
|
|
1372
|
+
// Hook into player's hideControlsNow method
|
|
1373
|
+
const originalHideControlsNow = this.api.player.hideControlsNow;
|
|
1374
|
+
this.api.player.hideControlsNow = function () {
|
|
1375
|
+
originalHideControlsNow.call(this);
|
|
1376
|
+
// Cursor is already hidden by the player's hideCursor() call inside hideControlsNow
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
// Hook into player's showControlsNow method
|
|
1380
|
+
const originalShowControlsNow = this.api.player.showControlsNow;
|
|
1381
|
+
this.api.player.showControlsNow = function () {
|
|
1382
|
+
originalShowControlsNow.call(this);
|
|
1383
|
+
// Cursor is already shown by the player's showCursor() call inside showControlsNow
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
if (this.api.player.options.debug) {
|
|
1387
|
+
console.log('[YT Plugin] Cursor sync hooks installed');
|
|
1425
1388
|
}
|
|
1426
1389
|
}
|
|
1390
|
+
|
|
1427
1391
|
if (this.api.player.options.debug) console.log('YT Plugin: Setup completed');
|
|
1428
1392
|
this.api.triggerEvent('youtubeplugin:playerready', {});
|
|
1429
1393
|
|
|
@@ -3470,11 +3434,67 @@
|
|
|
3470
3434
|
'3': 'BUFFERING',
|
|
3471
3435
|
'5': 'CUED'
|
|
3472
3436
|
};
|
|
3473
|
-
if (this.api.player.options.debug) console.log('[YT Plugin] State:', states[event.data], event.data);
|
|
3474
3437
|
|
|
3475
|
-
|
|
3438
|
+
if (this.api.player.options.debug)
|
|
3439
|
+
console.log('YT Plugin State:', states[event.data], event.data);
|
|
3440
|
+
|
|
3441
|
+
// Handle auto-hide based on YouTube state
|
|
3442
|
+
if (event.data === YT.PlayerState.PAUSED) {
|
|
3443
|
+
// Video paused: show controls and CANCEL timer
|
|
3444
|
+
if (this.api.player.showControlsNow) {
|
|
3445
|
+
this.api.player.showControlsNow();
|
|
3446
|
+
}
|
|
3447
|
+
// Clear timer to prevent controls from disappearing
|
|
3448
|
+
if (this.api.player.autoHideTimer) {
|
|
3449
|
+
clearTimeout(this.api.player.autoHideTimer);
|
|
3450
|
+
this.api.player.autoHideTimer = null;
|
|
3451
|
+
}
|
|
3452
|
+
// Add CSS class
|
|
3453
|
+
if (this.api.container) {
|
|
3454
|
+
this.api.container.classList.add('video-paused');
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
if (this.api.player.options.debug)
|
|
3458
|
+
console.log('YT Plugin: Video paused - controls locked visible');
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
if (event.data === YT.PlayerState.PLAYING) {
|
|
3462
|
+
// Video playing: remove class and restart auto-hide
|
|
3463
|
+
if (this.api.container) {
|
|
3464
|
+
this.api.container.classList.remove('video-paused');
|
|
3465
|
+
}
|
|
3466
|
+
// Restart auto-hide only if enabled
|
|
3467
|
+
if (this.api.player.options.autoHide && this.api.player.autoHideInitialized) {
|
|
3468
|
+
if (this.api.player.showControlsNow) {
|
|
3469
|
+
this.api.player.showControlsNow();
|
|
3470
|
+
}
|
|
3471
|
+
if (this.api.player.resetAutoHideTimer) {
|
|
3472
|
+
this.api.player.resetAutoHideTimer();
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
if (this.api.player.options.debug)
|
|
3477
|
+
console.log('YT Plugin: Video playing - auto-hide restarted');
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
// Handle when video fails to autoplay (stays in UNSTARTED)
|
|
3481
|
+
if (event.data === YT.PlayerState.UNSTARTED || event.data === -1) {
|
|
3482
|
+
// Show controls and block auto-hide
|
|
3483
|
+
if (this.api.player.showControlsNow) {
|
|
3484
|
+
this.api.player.showControlsNow();
|
|
3485
|
+
}
|
|
3486
|
+
// Clear any active timer
|
|
3487
|
+
if (this.api.player.autoHideTimer) {
|
|
3488
|
+
clearTimeout(this.api.player.autoHideTimer);
|
|
3489
|
+
this.api.player.autoHideTimer = null;
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
if (this.api.player.options.debug)
|
|
3493
|
+
console.log('YT Plugin: Video UNSTARTED (autoplay blocked?) - controls locked visible');
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
// Start timeout when video is unstarted
|
|
3476
3497
|
if (event.data === YT.PlayerState.UNSTARTED || event.data === -1) {
|
|
3477
|
-
// start timeout when video is unstarted
|
|
3478
3498
|
if (this.playAttemptTimeout) {
|
|
3479
3499
|
clearTimeout(this.playAttemptTimeout);
|
|
3480
3500
|
}
|
|
@@ -3484,15 +3504,14 @@
|
|
|
3484
3504
|
|
|
3485
3505
|
const currentState = this.ytPlayer.getPlayerState();
|
|
3486
3506
|
|
|
3487
|
-
// If video is
|
|
3507
|
+
// If video is unstarted after timeout, consider it restricted
|
|
3488
3508
|
if (currentState === YT.PlayerState.UNSTARTED || currentState === -1) {
|
|
3489
|
-
if (this.api.player.options.debug)
|
|
3509
|
+
if (this.api.player.options.debug)
|
|
3490
3510
|
console.log('YT Plugin: Video stuck in UNSTARTED - possibly members-only or restricted');
|
|
3491
|
-
}
|
|
3492
3511
|
|
|
3493
3512
|
// Trigger ended event
|
|
3494
3513
|
this.api.triggerEvent('ended', {
|
|
3495
|
-
reason: '
|
|
3514
|
+
reason: 'videorestrictedormembership',
|
|
3496
3515
|
state: currentState
|
|
3497
3516
|
});
|
|
3498
3517
|
|
|
@@ -3500,14 +3519,12 @@
|
|
|
3500
3519
|
this.showPosterOverlay();
|
|
3501
3520
|
|
|
3502
3521
|
// Trigger custom event
|
|
3503
|
-
this.api.triggerEvent('
|
|
3522
|
+
this.api.triggerEvent('youtubepluginmembershiprestricted', {
|
|
3504
3523
|
videoId: this.videoId
|
|
3505
3524
|
});
|
|
3506
3525
|
}
|
|
3507
|
-
}, 15000); // 15 seconds
|
|
3508
|
-
|
|
3509
|
-
} else if (event.data === YT.PlayerState.PLAYING ||
|
|
3510
|
-
event.data === YT.PlayerState.BUFFERING) {
|
|
3526
|
+
}, 15000); // 15 seconds timeout
|
|
3527
|
+
} else if (event.data === YT.PlayerState.PLAYING || event.data === YT.PlayerState.BUFFERING) {
|
|
3511
3528
|
// Clear the timeout if video starts correctly
|
|
3512
3529
|
if (this.playAttemptTimeout) {
|
|
3513
3530
|
clearTimeout(this.playAttemptTimeout);
|
|
@@ -3524,9 +3541,8 @@
|
|
|
3524
3541
|
|
|
3525
3542
|
// Handle live stream ended
|
|
3526
3543
|
if (this.isLiveStream && event.data === YT.PlayerState.ENDED) {
|
|
3527
|
-
if (this.api.player.options.debug)
|
|
3528
|
-
console.log('
|
|
3529
|
-
}
|
|
3544
|
+
if (this.api.player.options.debug)
|
|
3545
|
+
console.log('YT Plugin: Live stream ended (player state ENDED)');
|
|
3530
3546
|
this.handleLiveStreamEnded();
|
|
3531
3547
|
return;
|
|
3532
3548
|
}
|
|
@@ -3536,24 +3552,19 @@
|
|
|
3536
3552
|
if (event.data === YT.PlayerState.PAUSED) {
|
|
3537
3553
|
// Orange when paused during live
|
|
3538
3554
|
badge.style.background = '#ff8800';
|
|
3539
|
-
badge.textContent = '
|
|
3555
|
+
badge.textContent = 'LIVE';
|
|
3540
3556
|
badge.title = 'Livestreaming in Pause';
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
console.log('[YT Plugin] 🟠 Live paused');
|
|
3544
|
-
}
|
|
3557
|
+
if (this.api.player.options.debug)
|
|
3558
|
+
console.log('YT Plugin: Live paused');
|
|
3545
3559
|
} else if (event.data === YT.PlayerState.PLAYING) {
|
|
3546
3560
|
// Red when playing (will be checked for de-sync below)
|
|
3547
3561
|
badge.style.background = '#ff0000';
|
|
3548
3562
|
badge.textContent = 'LIVE';
|
|
3549
3563
|
badge.title = 'Livestreaming';
|
|
3550
|
-
|
|
3551
|
-
if (this.api.player.options.debug) {
|
|
3552
|
-
console.log('[YT Plugin] 🔴 Live playing');
|
|
3553
|
-
}
|
|
3554
3564
|
}
|
|
3555
3565
|
}
|
|
3556
3566
|
|
|
3567
|
+
// Handle state changes
|
|
3557
3568
|
switch (event.data) {
|
|
3558
3569
|
case YT.PlayerState.PLAYING:
|
|
3559
3570
|
this.api.triggerEvent('played', {});
|
|
@@ -3585,27 +3596,13 @@
|
|
|
3585
3596
|
case YT.PlayerState.ENDED:
|
|
3586
3597
|
this.api.triggerEvent('ended', {});
|
|
3587
3598
|
|
|
3588
|
-
// Show play icon
|
|
3599
|
+
// Show play icon for replay
|
|
3589
3600
|
if (playIcon && pauseIcon) {
|
|
3590
3601
|
playIcon.classList.remove('hidden');
|
|
3591
3602
|
pauseIcon.classList.add('hidden');
|
|
3592
3603
|
}
|
|
3593
3604
|
break;
|
|
3594
3605
|
}
|
|
3595
|
-
|
|
3596
|
-
if (event.data === YT.PlayerState.PAUSED) {
|
|
3597
|
-
// add pause class
|
|
3598
|
-
if (this.api.container) {
|
|
3599
|
-
this.api.container.classList.add('video-paused');
|
|
3600
|
-
}
|
|
3601
|
-
}
|
|
3602
|
-
|
|
3603
|
-
if (event.data === YT.PlayerState.PLAYING) {
|
|
3604
|
-
// remove pause class
|
|
3605
|
-
if (this.api.container) {
|
|
3606
|
-
this.api.container.classList.remove('video-paused');
|
|
3607
|
-
}
|
|
3608
|
-
}
|
|
3609
3606
|
}
|
|
3610
3607
|
|
|
3611
3608
|
onPlaybackQualityChange(event) {
|
|
@@ -3996,19 +3993,20 @@
|
|
|
3996
3993
|
* Only works when showYouTubeUI is false (custom controls)
|
|
3997
3994
|
*/
|
|
3998
3995
|
hideCursor() {
|
|
3999
|
-
|
|
4000
|
-
if (this.options.
|
|
4001
|
-
return;
|
|
4002
|
-
}
|
|
3996
|
+
if (this.options.showYouTubeUI) return;
|
|
3997
|
+
if (!this.api.player.options.hideCursor) return;
|
|
4003
3998
|
|
|
4004
|
-
//
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
this.api.container.classList.add('hide-cursor');
|
|
3999
|
+
// Update main container
|
|
4000
|
+
if (this.api.player && this.api.player.container) {
|
|
4001
|
+
this.api.player.container.classList.add('hide-cursor');
|
|
4008
4002
|
}
|
|
4009
4003
|
|
|
4010
|
-
|
|
4011
|
-
|
|
4004
|
+
// Update overlay cursor as well!
|
|
4005
|
+
if (this.mouseMoveOverlay) {
|
|
4006
|
+
this.mouseMoveOverlay.style.cursor = 'none';
|
|
4007
|
+
if (this.api.player.options.debug) {
|
|
4008
|
+
console.log('[YT Plugin] Overlay cursor hidden');
|
|
4009
|
+
}
|
|
4012
4010
|
}
|
|
4013
4011
|
}
|
|
4014
4012
|
|
|
@@ -4016,13 +4014,17 @@
|
|
|
4016
4014
|
* Show mouse cursor in YouTube player
|
|
4017
4015
|
*/
|
|
4018
4016
|
showCursor() {
|
|
4019
|
-
//
|
|
4020
|
-
if (this.api.container) {
|
|
4021
|
-
this.api.container.classList.remove('hide-cursor');
|
|
4017
|
+
// Update main container
|
|
4018
|
+
if (this.api.player && this.api.player.container) {
|
|
4019
|
+
this.api.player.container.classList.remove('hide-cursor');
|
|
4022
4020
|
}
|
|
4023
4021
|
|
|
4024
|
-
|
|
4025
|
-
|
|
4022
|
+
// Update overlay cursor back to default!
|
|
4023
|
+
if (this.mouseMoveOverlay) {
|
|
4024
|
+
this.mouseMoveOverlay.style.cursor = 'default';
|
|
4025
|
+
if (this.api.player.options.debug) {
|
|
4026
|
+
console.log('[YT Plugin] Overlay cursor shown');
|
|
4027
|
+
}
|
|
4026
4028
|
}
|
|
4027
4029
|
}
|
|
4028
4030
|
|
|
@@ -4381,18 +4383,19 @@
|
|
|
4381
4383
|
}
|
|
4382
4384
|
|
|
4383
4385
|
/**
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4386
|
+
* Parse chapters from video description
|
|
4387
|
+
* Validates YouTube chapter requirements: ≥3 chapters, starts at 0:00
|
|
4388
|
+
*/
|
|
4387
4389
|
parseChaptersFromDescription(description) {
|
|
4388
4390
|
if (!description) return null;
|
|
4389
4391
|
|
|
4390
4392
|
const chapters = [];
|
|
4393
|
+
|
|
4391
4394
|
// Regex for timestamps: 0:00, 00:00, 0:00:00
|
|
4392
4395
|
// Matches both "0:00 Title" and "0:00 - Title"
|
|
4393
|
-
const timestampRegex =
|
|
4394
|
-
let match;
|
|
4396
|
+
const timestampRegex = /^(\d{1,2}:\d{2}(?::\d{2})?)\s*[-–—]*\s*(.+)$/gm;
|
|
4395
4397
|
|
|
4398
|
+
let match;
|
|
4396
4399
|
while ((match = timestampRegex.exec(description)) !== null) {
|
|
4397
4400
|
const timeString = match[1].trim();
|
|
4398
4401
|
const title = match[2].trim();
|
|
@@ -4401,7 +4404,6 @@
|
|
|
4401
4404
|
if (!title || title.length < 2) continue;
|
|
4402
4405
|
|
|
4403
4406
|
const seconds = this.parseTimeToSeconds(timeString);
|
|
4404
|
-
|
|
4405
4407
|
if (seconds !== null) {
|
|
4406
4408
|
chapters.push({
|
|
4407
4409
|
time: seconds,
|
|
@@ -4416,27 +4418,16 @@
|
|
|
4416
4418
|
|
|
4417
4419
|
// Validate: at least 3 chapters and first starts at 0:00
|
|
4418
4420
|
if (chapters.length >= 3 && chapters[0].time === 0) {
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
for (let i = 0; i < chapters.length - 1; i++) {
|
|
4422
|
-
if (chapters[i + 1].time - chapters[i].time < 10) {
|
|
4423
|
-
valid = false;
|
|
4424
|
-
break;
|
|
4425
|
-
}
|
|
4426
|
-
}
|
|
4427
|
-
|
|
4428
|
-
if (valid) {
|
|
4429
|
-
if (this.api.player.options.debug) {
|
|
4430
|
-
console.log(`YT Plugin: Found ${chapters.length} valid chapters`);
|
|
4431
|
-
}
|
|
4432
|
-
return chapters;
|
|
4421
|
+
if (this.api.player.options.debug) {
|
|
4422
|
+
console.log('[YT Plugin] ✅ Found', chapters.length, 'valid chapters');
|
|
4433
4423
|
}
|
|
4424
|
+
return chapters;
|
|
4434
4425
|
}
|
|
4435
4426
|
|
|
4436
4427
|
if (this.api.player.options.debug) {
|
|
4437
|
-
console.log('YT Plugin
|
|
4428
|
+
console.log('[YT Plugin] ❌ No valid chapters found (requires ≥3 chapters starting at 0:00)');
|
|
4429
|
+
console.log('[YT Plugin] Debug: Found', chapters.length, 'chapters, first time:', chapters[0]?.time);
|
|
4438
4430
|
}
|
|
4439
|
-
|
|
4440
4431
|
return null;
|
|
4441
4432
|
}
|
|
4442
4433
|
|