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.
@@ -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 maxMenuHeight = playerHeight - 100; // Leave 100px margin from top/bottom
343
-
344
- settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
345
- settingsMenu.style.overflowY = 'auto';
346
- settingsMenu.style.overflowX = 'hidden';
347
-
348
- // Add scrollbar styling
349
- if (!document.getElementById('yt-settings-scrollbar-style')) {
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: default;
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 state based on controls visibility
1370
+ // Initialize cursor synchronization with player's auto-hide system
1420
1371
  if (!this.options.showYouTubeUI && this.api.player.options.hideCursor) {
1421
- // Check if controls are visible
1422
- const controlsVisible = this.api.controls && this.api.controls.classList.contains('show');
1423
- if (!controlsVisible) {
1424
- this.hideCursor();
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
- // Know if video have some problems to start
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 unstrated after timeout, consider it restricted
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: 'video_restricted_or_membership',
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('youtubeplugin:membershiprestricted', {
3522
+ this.api.triggerEvent('youtubepluginmembershiprestricted', {
3504
3523
  videoId: this.videoId
3505
3524
  });
3506
3525
  }
3507
- }, 15000); // 15 seconds of timeout
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('[YT Plugin] 🔴➡️📹 Live stream ended (player state: ENDED)');
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 = 'LIVE';
3555
+ badge.textContent = 'LIVE';
3540
3556
  badge.title = 'Livestreaming in Pause';
3541
-
3542
- if (this.api.player.options.debug) {
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 (for replay)
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
- // Don't hide cursor if YouTube native UI is active
4000
- if (this.options.showYouTubeUI) {
4001
- return;
4002
- }
3996
+ if (this.options.showYouTubeUI) return;
3997
+ if (!this.api.player.options.hideCursor) return;
4003
3998
 
4004
- // Add hide-cursor class to MAIN PLAYER CONTAINER
4005
- // This ensures cursor is hidden everywhere in the player
4006
- if (this.api.container) {
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
- if (this.api.player.options.debug) {
4011
- console.log('[YT Plugin] Cursor hidden on main container');
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
- // Remove hide-cursor class from MAIN PLAYER CONTAINER
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
- if (this.api.player.options.debug) {
4025
- console.log('[YT Plugin] Cursor shown on main container');
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
- * Parse chapters from video description
4385
- * Validates YouTube chapter requirements: 3+ chapters, starts at 0:00, 10+ seconds each
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 = /(?:^|\n)(\d{1,2}:?\d{0,2}:?\d{2})\s*[-–—]?\s*(.+?)(?=\n|$)/gm;
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
- // Validate minimum duration (10 seconds)
4420
- let valid = true;
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: No valid chapters found (requires 3+ chapters, starting at 0:00, each 10+ seconds)');
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