myetv-player 1.1.3 → 1.1.5

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.
@@ -271,42 +271,46 @@
271
271
  * Set auto caption language on player initialization
272
272
  */
273
273
  setAutoCaptionLanguage() {
274
- if (!this.options.autoCaptionLanguage || !this.ytPlayer) {
275
- return;
276
- }
274
+ if (!this.options.autoCaptionLanguage || !this.ytPlayer) return;
277
275
 
278
276
  try {
279
277
  if (this.api.player.options.debug) {
280
- console.log('[YT Plugin] Setting auto caption language to', this.options.autoCaptionLanguage);
278
+ console.log('YT Plugin: Setting auto caption language to', this.options.autoCaptionLanguage);
281
279
  }
282
280
 
283
281
  this.ytPlayer.setOption('captions', 'reload', true);
284
282
  this.ytPlayer.loadModule('captions');
285
-
286
283
  this.ytPlayer.setOption('captions', 'track', {
287
- 'translationLanguage': this.options.autoCaptionLanguage
284
+ translationLanguage: { languageCode: this.options.autoCaptionLanguage }
288
285
  });
289
286
 
290
287
  this.captionsEnabled = true;
288
+ this.currentTranslation = this.options.autoCaptionLanguage;
291
289
 
292
290
  const subtitlesBtn = this.api.container.querySelector('.subtitles-btn');
293
291
  if (subtitlesBtn) {
294
292
  subtitlesBtn.classList.add('active');
295
293
  }
296
294
 
295
+ // update menu selection after a short delay
296
+ setTimeout(() => {
297
+ this.updateMenuSelection(`translate-${this.options.autoCaptionLanguage}`);
298
+ }, 500);
299
+
297
300
  if (this.api.player.options.debug) {
298
- console.log('[YT Plugin] Auto caption language set successfully');
301
+ console.log('YT Plugin: Auto caption language set successfully');
299
302
  }
300
-
301
303
  } catch (error) {
302
304
  if (this.api.player.options.debug) {
303
- console.error('[YT Plugin] Error setting auto caption language', error);
305
+ console.error('YT Plugin: Error setting auto caption language', error);
304
306
  }
305
307
  }
306
308
  }
307
309
 
310
+ /**
311
+ * Handle responsive layout for mobile settings
312
+ */
308
313
  handleResponsiveLayout() {
309
-
310
314
  const containerWidth = this.api.container.offsetWidth;
311
315
  const pipBtn = this.api.container.querySelector('.pip-btn');
312
316
  const subtitlesBtn = this.api.container.querySelector('.subtitles-btn');
@@ -319,11 +323,66 @@
319
323
 
320
324
  // Breakpoint at 600px
321
325
  if (containerWidth < 600) {
326
+ // Add max-height and scroll to settings menu on mobile
327
+ if (settingsMenu) {
328
+ const playerHeight = this.api.container.offsetHeight;
329
+ const maxMenuHeight = playerHeight - 100; // Leave 100px margin from top/bottom
330
+
331
+ settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
332
+ settingsMenu.style.overflowY = 'auto';
333
+ settingsMenu.style.overflowX = 'hidden';
334
+
335
+ // Add scrollbar styling
336
+ if (!document.getElementById('yt-settings-scrollbar-style')) {
337
+ const scrollbarStyle = document.createElement('style');
338
+ scrollbarStyle.id = 'yt-settings-scrollbar-style';
339
+ scrollbarStyle.textContent = `
340
+ .settings-menu::-webkit-scrollbar {
341
+ width: 6px;
342
+ }
343
+ .settings-menu::-webkit-scrollbar-track {
344
+ background: rgba(255,255,255,0.05);
345
+ border-radius: 3px;
346
+ }
347
+ .settings-menu::-webkit-scrollbar-thumb {
348
+ background: rgba(255,255,255,0.3);
349
+ border-radius: 3px;
350
+ }
351
+ .settings-menu::-webkit-scrollbar-thumb:hover {
352
+ background: rgba(255,255,255,0.5);
353
+ }
354
+ `;
355
+ document.head.appendChild(scrollbarStyle);
356
+ }
357
+
358
+ // Firefox scrollbar
359
+ settingsMenu.style.scrollbarWidth = 'thin';
360
+ settingsMenu.style.scrollbarColor = 'rgba(255,255,255,0.3) transparent';
361
+ }
362
+
322
363
  // Hide subtitles button
323
364
  if (subtitlesBtn) {
324
365
  subtitlesBtn.style.display = 'none';
325
366
  }
326
367
 
368
+ // Hide original speed menu option from settings (if exists)
369
+ if (settingsMenu) {
370
+ // Hide old non-expandable speed option
371
+ const originalSpeedOption = settingsMenu.querySelector('[data-action="speed"]');
372
+ if (originalSpeedOption) {
373
+ originalSpeedOption.style.display = 'none';
374
+ }
375
+
376
+ // Hide new expandable speed option
377
+ const expandableSpeedWrapper = settingsMenu.querySelector('[data-action="speed-expand"]');
378
+ if (expandableSpeedWrapper) {
379
+ const wrapper = expandableSpeedWrapper.closest('.settings-expandable-wrapper');
380
+ if (wrapper) {
381
+ wrapper.style.display = 'none';
382
+ }
383
+ }
384
+ }
385
+
327
386
  // Add subtitles option to settings menu
328
387
  if (settingsMenu) {
329
388
  let subtitlesWrapper = settingsMenu.querySelector('.yt-subtitles-wrapper');
@@ -345,121 +404,236 @@
345
404
  // Create trigger
346
405
  const trigger = document.createElement('div');
347
406
  trigger.className = 'quality-option';
407
+ trigger.style.fontSize = '10px';
348
408
  trigger.textContent = subtitlesText;
349
409
 
350
- // Create submenu
351
- const submenu = document.createElement('div');
352
- submenu.className = 'yt-subtitles-submenu';
353
- submenu.style.cssText = `
410
+ // Add arrow indicator
411
+ const arrow = document.createElement('span');
412
+ arrow.textContent = '';
413
+ arrow.style.cssText = 'font-size: 8px; transition: transform 0.2s;';
414
+ trigger.appendChild(arrow);
415
+
416
+ // Container for expanded options
417
+ const optionsContainer = document.createElement('div');
418
+ optionsContainer.className = 'yt-subtitles-options';
419
+ optionsContainer.style.cssText = `
354
420
  display: none;
355
- position: absolute;
356
- right: 100%;
357
- top: 0;
358
- margin-right: 5px;
359
- background: #1c1c1c;
360
- border: 1px solid rgba(255,255,255,0.1);
361
- border-radius: 4px;
362
- padding: 8px 0;
363
- z-index: 999999;
364
- color: white;
365
- width: fit-content;
366
- max-width: 180px;
367
- max-height: 250px;
368
- overflow-y: auto;
369
- overflow-x: hidden;`;
370
-
371
- // Hover state tracking
372
- let isHoveringTrigger = false;
373
- let isHoveringSubmenu = false;
374
- let hideTimeout = null;
375
-
376
- const checkAndHide = () => {
377
- if (hideTimeout) clearTimeout(hideTimeout);
378
- hideTimeout = setTimeout(() => {
379
- if (!isHoveringTrigger && !isHoveringSubmenu) {
380
- submenu.style.display = 'none';
421
+ padding-left: 15px;
422
+ margin-top: 4px;
423
+ `;
424
+
425
+ // Function to rebuild options from main menu
426
+ const rebuildOptions = () => {
427
+ const mainSubtitlesMenu = this.api.container.querySelector('.subtitles-menu');
428
+ if (!mainSubtitlesMenu) return;
429
+
430
+ // Clone all options from main menu
431
+ optionsContainer.innerHTML = '';
432
+ const options = mainSubtitlesMenu.querySelectorAll('.subtitles-option');
433
+
434
+ options.forEach(option => {
435
+ const clonedOption = option.cloneNode(true);
436
+ clonedOption.style.cssText = `
437
+ padding: 6px 12px;
438
+ cursor: pointer;
439
+ color: white;
440
+ font-size: 10px;
441
+ white-space: normal;
442
+ word-wrap: break-word;
443
+ opacity: 0.8;
444
+ transition: opacity 0.2s;
445
+ `;
446
+
447
+ // Highlight selected option
448
+ if (option.classList.contains('selected')) {
449
+ clonedOption.style.opacity = '1';
450
+ clonedOption.style.fontWeight = 'bold';
381
451
  }
382
- }, 200);
452
+
453
+ // Hover effect
454
+ clonedOption.addEventListener('mouseenter', () => {
455
+ clonedOption.style.opacity = '1';
456
+ });
457
+ clonedOption.addEventListener('mouseleave', () => {
458
+ if (!option.classList.contains('selected')) {
459
+ clonedOption.style.opacity = '0.8';
460
+ }
461
+ });
462
+
463
+ // Add click handler
464
+ clonedOption.addEventListener('click', (e) => {
465
+ e.stopPropagation();
466
+
467
+ // Trigger click on original option
468
+ option.click();
469
+
470
+ // Rebuild to update selection
471
+ setTimeout(() => {
472
+ rebuildOptions();
473
+ }, 50);
474
+ });
475
+
476
+ optionsContainer.appendChild(clonedOption);
477
+ });
383
478
  };
384
479
 
385
- // Add OFF option
386
- const offOption = document.createElement('div');
387
- offOption.className = 'quality-option';
388
- offOption.textContent = 'Off';
389
- offOption.style.cssText = 'padding: 8px 16px; cursor: pointer; color: white;';
390
- offOption.addEventListener('click', () => {
391
- if (this.disableCaptions) this.disableCaptions();
392
- submenu.style.display = 'none';
393
- settingsMenu.classList.remove('show');
480
+ // Toggle expanded/collapsed state
481
+ let isExpanded = false;
482
+ trigger.addEventListener('click', (e) => {
483
+ e.stopPropagation();
484
+
485
+ isExpanded = !isExpanded;
486
+
487
+ if (isExpanded) {
488
+ rebuildOptions();
489
+ optionsContainer.style.display = 'block';
490
+ arrow.style.transform = 'rotate(180deg)';
491
+ } else {
492
+ optionsContainer.style.display = 'none';
493
+ arrow.style.transform = 'rotate(0deg)';
494
+ }
394
495
  });
395
- submenu.appendChild(offOption);
396
496
 
397
- // Add caption options
398
- if (this.availableCaptions && this.availableCaptions.length > 0) {
399
- this.availableCaptions.forEach((caption, index) => {
497
+ // Assemble
498
+ subtitlesWrapper.appendChild(trigger);
499
+ subtitlesWrapper.appendChild(optionsContainer);
500
+ settingsMenu.insertBefore(subtitlesWrapper, settingsMenu.firstChild);
501
+ }
502
+
503
+ // Add speed option to settings menu
504
+ let speedWrapper = settingsMenu.querySelector('.yt-speed-wrapper');
505
+
506
+ if (!speedWrapper) {
507
+ // Get i18n text - use 'playback_speed' key
508
+ let speedText = 'Playback speed';
509
+ if (this.api.player && this.api.player.t) {
510
+ speedText = this.api.player.t('playback_speed');
511
+ } else if (this.player && this.player.t) {
512
+ speedText = this.player.t('playback_speed');
513
+ }
514
+
515
+ // Create wrapper
516
+ speedWrapper = document.createElement('div');
517
+ speedWrapper.className = 'yt-speed-wrapper';
518
+ speedWrapper.style.cssText = 'position: relative; display: block;';
519
+
520
+ // Create trigger
521
+ const trigger = document.createElement('div');
522
+ trigger.className = 'quality-option';
523
+ trigger.style.fontSize = '10px';
524
+
525
+ // Get current speed
526
+ const getCurrentSpeed = () => {
527
+ if (this.ytPlayer && this.ytPlayer.getPlaybackRate) {
528
+ return this.ytPlayer.getPlaybackRate();
529
+ }
530
+ return 1;
531
+ };
532
+
533
+ trigger.textContent = `${speedText}: ${getCurrentSpeed()}x`;
534
+
535
+ // Add arrow indicator
536
+ const arrow = document.createElement('span');
537
+ arrow.textContent = ' ▼';
538
+ arrow.style.cssText = 'font-size: 8px; transition: transform 0.2s;';
539
+ trigger.appendChild(arrow);
540
+
541
+ // Container for expanded options
542
+ const optionsContainer = document.createElement('div');
543
+ optionsContainer.className = 'yt-speed-options';
544
+ optionsContainer.style.cssText = `
545
+ display: none;
546
+ padding-left: 15px;
547
+ margin-top: 4px;
548
+ `;
549
+
550
+ // Available speeds
551
+ const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
552
+
553
+ // Function to rebuild options
554
+ const rebuildOptions = () => {
555
+ optionsContainer.innerHTML = '';
556
+ const currentSpeed = getCurrentSpeed();
557
+
558
+ speeds.forEach(speed => {
400
559
  const option = document.createElement('div');
401
- option.className = 'quality-option';
402
- option.textContent = caption.label || caption.languageName;
403
- option.style.cssText = 'padding: 8px 16px; cursor: pointer; color: white;';
404
- option.addEventListener('click', () => {
405
- if (this.setCaptions) this.setCaptions(index);
406
- submenu.style.display = 'none';
407
- settingsMenu.classList.remove('show');
560
+ option.style.cssText = `
561
+ padding: 6px 12px;
562
+ cursor: pointer;
563
+ color: white;
564
+ font-size: 10px;
565
+ white-space: normal;
566
+ word-wrap: break-word;
567
+ opacity: 0.8;
568
+ transition: opacity 0.2s;
569
+ `;
570
+ option.textContent = `${speed}x`;
571
+
572
+ // Highlight selected option
573
+ if (Math.abs(speed - currentSpeed) < 0.01) {
574
+ option.style.opacity = '1';
575
+ option.style.fontWeight = 'bold';
576
+ }
577
+
578
+ // Hover effect
579
+ option.addEventListener('mouseenter', () => {
580
+ option.style.opacity = '1';
581
+ });
582
+ option.addEventListener('mouseleave', () => {
583
+ if (Math.abs(speed - getCurrentSpeed()) >= 0.01) {
584
+ option.style.opacity = '0.8';
585
+ }
408
586
  });
409
- submenu.appendChild(option);
410
- });
411
- } else {
412
- // Add Auto option
413
- const autoOption = document.createElement('div');
414
- autoOption.className = 'quality-option';
415
- autoOption.textContent = 'On (Auto)';
416
- autoOption.style.cssText = 'padding: 8px 16px; cursor: pointer; color: white;';
417
- autoOption.addEventListener('click', () => {
418
- if (this.enableAutoCaptions) this.enableAutoCaptions();
419
- submenu.style.display = 'none';
420
- settingsMenu.classList.remove('show');
421
- });
422
- submenu.appendChild(autoOption);
423
- }
424
587
 
425
- // Trigger events
426
- trigger.addEventListener('mouseenter', () => {
427
- isHoveringTrigger = true;
428
- if (hideTimeout) clearTimeout(hideTimeout);
429
- submenu.style.display = 'block';
430
- });
588
+ // Add click handler
589
+ option.addEventListener('click', (e) => {
590
+ e.stopPropagation();
431
591
 
432
- trigger.addEventListener('mouseleave', () => {
433
- isHoveringTrigger = false;
434
- checkAndHide();
435
- });
592
+ if (this.ytPlayer && this.ytPlayer.setPlaybackRate) {
593
+ this.ytPlayer.setPlaybackRate(speed);
594
+ }
436
595
 
437
- // Submenu events
438
- submenu.addEventListener('mouseenter', () => {
439
- isHoveringSubmenu = true;
440
- if (hideTimeout) clearTimeout(hideTimeout);
441
- submenu.style.display = 'block';
442
- });
596
+ // Update trigger text
597
+ trigger.childNodes[0].textContent = `${speedText}: ${speed}x`;
443
598
 
444
- submenu.addEventListener('mouseleave', () => {
445
- isHoveringSubmenu = false;
446
- checkAndHide();
447
- });
599
+ // Rebuild to update selection
600
+ setTimeout(() => {
601
+ rebuildOptions();
602
+ }, 50);
603
+ });
604
+
605
+ optionsContainer.appendChild(option);
606
+ });
607
+ };
448
608
 
449
- // Click alternative
609
+ // Toggle expanded/collapsed state
610
+ let isExpanded = false;
450
611
  trigger.addEventListener('click', (e) => {
451
612
  e.stopPropagation();
452
- if (submenu.style.display === 'none' || !submenu.style.display) {
453
- submenu.style.display = 'block';
613
+
614
+ isExpanded = !isExpanded;
615
+
616
+ if (isExpanded) {
617
+ rebuildOptions();
618
+ optionsContainer.style.display = 'block';
619
+ arrow.style.transform = 'rotate(180deg)';
454
620
  } else {
455
- submenu.style.display = 'none';
621
+ optionsContainer.style.display = 'none';
622
+ arrow.style.transform = 'rotate(0deg)';
456
623
  }
457
624
  });
458
625
 
459
626
  // Assemble
460
- subtitlesWrapper.appendChild(trigger);
461
- subtitlesWrapper.appendChild(submenu);
462
- settingsMenu.insertBefore(subtitlesWrapper, settingsMenu.firstChild);
627
+ speedWrapper.appendChild(trigger);
628
+ speedWrapper.appendChild(optionsContainer);
629
+
630
+ // Insert after subtitles wrapper
631
+ const subtitlesWrapper = settingsMenu.querySelector('.yt-subtitles-wrapper');
632
+ if (subtitlesWrapper) {
633
+ subtitlesWrapper.insertAdjacentElement('afterend', speedWrapper);
634
+ } else {
635
+ settingsMenu.insertBefore(speedWrapper, settingsMenu.firstChild);
636
+ }
463
637
  }
464
638
  }
465
639
  } else {
@@ -468,12 +642,43 @@ width: fit-content;
468
642
  subtitlesBtn.style.display = '';
469
643
  }
470
644
 
645
+ // Reset settings menu styles
646
+ if (settingsMenu) {
647
+ settingsMenu.style.maxHeight = '';
648
+ settingsMenu.style.overflowY = '';
649
+ settingsMenu.style.overflowX = '';
650
+ settingsMenu.style.scrollbarWidth = '';
651
+ settingsMenu.style.scrollbarColor = '';
652
+ }
653
+
654
+ // Show original speed option again
655
+ if (settingsMenu) {
656
+ const originalSpeedOption = settingsMenu.querySelector('[data-action="speed"]');
657
+ if (originalSpeedOption) {
658
+ originalSpeedOption.style.display = '';
659
+ }
660
+
661
+ // Show expandable speed option again
662
+ const expandableSpeedWrapper = settingsMenu.querySelector('[data-action="speed-expand"]');
663
+ if (expandableSpeedWrapper) {
664
+ const wrapper = expandableSpeedWrapper.closest('.settings-expandable-wrapper');
665
+ if (wrapper) {
666
+ wrapper.style.display = '';
667
+ }
668
+ }
669
+ }
670
+
471
671
  // Remove from settings
472
672
  if (settingsMenu) {
473
673
  const subtitlesWrapper = settingsMenu.querySelector('.yt-subtitles-wrapper');
474
674
  if (subtitlesWrapper) {
475
675
  subtitlesWrapper.remove();
476
676
  }
677
+
678
+ const speedWrapper = settingsMenu.querySelector('.yt-speed-wrapper');
679
+ if (speedWrapper) {
680
+ speedWrapper.remove();
681
+ }
477
682
  }
478
683
  }
479
684
  }
@@ -1210,16 +1415,21 @@ startBufferMonitoring() {
1210
1415
  setTimeout(() => this.setQuality(this.options.quality), 1000);
1211
1416
  }
1212
1417
 
1213
- // NEW: Update player watermark with channel data
1418
+ // Update player watermark with channel data
1214
1419
  if (this.options.enableChannelWatermark) {
1215
1420
  this.updatePlayerWatermark();
1216
1421
  }
1217
1422
 
1218
- // NEW: Set auto caption language
1423
+ // Set auto caption language
1219
1424
  if (this.options.autoCaptionLanguage) {
1220
1425
  setTimeout(() => this.setAutoCaptionLanguage(), 1500);
1221
1426
  }
1222
1427
 
1428
+ // Check initial caption state AFTER captions are loaded and menu is built
1429
+ setTimeout(() => {
1430
+ this.checkInitialCaptionState();
1431
+ }, 2500); // Dopo che tutto è stato inizializzato
1432
+
1223
1433
  this.api.triggerEvent('youtubeplugin:playerready', {});
1224
1434
 
1225
1435
  }
@@ -2177,13 +2387,13 @@ startBufferMonitoring() {
2177
2387
  if (this.api.player.options.debug) console.log('[YT Plugin] Subtitles control created');
2178
2388
  this.buildSubtitlesMenu();
2179
2389
  this.bindSubtitlesButton();
2180
- this.checkInitialCaptionState();
2390
+ //this.checkInitialCaptionState();
2181
2391
  this.startCaptionStateMonitoring();
2182
2392
  }
2183
2393
 
2184
2394
  /**
2185
- * Build the subtitles menu
2186
- */
2395
+ * Build the subtitles menu
2396
+ */
2187
2397
  buildSubtitlesMenu() {
2188
2398
  const subtitlesMenu = this.api.container.querySelector('.subtitles-menu');
2189
2399
  if (!subtitlesMenu) return;
@@ -2219,22 +2429,22 @@ startBufferMonitoring() {
2219
2429
  });
2220
2430
  subtitlesMenu.appendChild(option);
2221
2431
  });
2222
- } else {
2223
- // Auto-caption only (without tracklist)
2224
- const autoOption = document.createElement('div');
2225
- autoOption.className = 'subtitles-option';
2226
- autoOption.textContent = 'Auto-generated';
2227
- autoOption.dataset.id = 'auto';
2228
- autoOption.addEventListener('click', (e) => {
2229
- e.stopPropagation();
2230
- this.enableAutoCaptions();
2231
- this.updateMenuSelection('auto');
2232
- subtitlesMenu.classList.remove('show');
2233
- });
2234
- subtitlesMenu.appendChild(autoOption);
2235
2432
  }
2236
2433
 
2237
- // Always add "Auto-translate" (both with and without tracklist)
2434
+ // SEMPRE aggiungi l'opzione Auto-generated (sia con che senza tracklist)
2435
+ const autoOption = document.createElement('div');
2436
+ autoOption.className = 'subtitles-option';
2437
+ autoOption.textContent = 'Auto-generated';
2438
+ autoOption.dataset.id = 'auto';
2439
+ autoOption.addEventListener('click', (e) => {
2440
+ e.stopPropagation();
2441
+ this.enableAutoCaptions();
2442
+ this.updateMenuSelection('auto');
2443
+ subtitlesMenu.classList.remove('show');
2444
+ });
2445
+ subtitlesMenu.appendChild(autoOption);
2446
+
2447
+ // Always add Auto-translate (both with and without tracklist)
2238
2448
  const translateOption = document.createElement('div');
2239
2449
  translateOption.className = 'subtitles-option translate-option';
2240
2450
  translateOption.textContent = 'Auto-translate';
@@ -2524,27 +2734,69 @@ startBufferMonitoring() {
2524
2734
  }
2525
2735
 
2526
2736
  /**
2527
- * Check initial caption state
2528
- */
2737
+ * Check initial caption state
2738
+ */
2529
2739
  checkInitialCaptionState() {
2530
2740
  setTimeout(() => {
2531
2741
  try {
2532
2742
  const currentTrack = this.ytPlayer.getOption('captions', 'track');
2743
+
2533
2744
  if (currentTrack && currentTrack.languageCode) {
2534
2745
  this.captionsEnabled = true;
2535
- this.updateMenuSelection('caption-0');
2746
+
2747
+ // erify if translation is active
2748
+ if (currentTrack.translationLanguage) {
2749
+ this.currentTranslation = currentTrack.translationLanguage.languageCode;
2750
+
2751
+ // when translation is active, set currentCaption to null
2752
+ this.updateMenuSelection('auto');
2753
+
2754
+ // specify translation submenu selection after a short delay
2755
+ setTimeout(() => {
2756
+ const translationMenu = this.api.container.querySelector('.translation-menu');
2757
+ if (translationMenu) {
2758
+ translationMenu.querySelectorAll('.subtitles-option').forEach(option => {
2759
+ option.classList.remove('selected');
2760
+ });
2761
+
2762
+ const selectedTranslation = translationMenu.querySelector(`[data-id="translate-${this.currentTranslation}"]`);
2763
+ if (selectedTranslation) {
2764
+ selectedTranslation.classList.add('selected');
2765
+ }
2766
+ }
2767
+ }, 100);
2768
+ } else {
2769
+ // find current caption track index
2770
+ const captionIndex = this.availableCaptions.findIndex(
2771
+ cap => cap.languageCode === currentTrack.languageCode
2772
+ );
2773
+
2774
+ if (captionIndex >= 0) {
2775
+ this.currentCaption = currentTrack.languageCode;
2776
+ this.updateMenuSelection(`caption-${captionIndex}`);
2777
+ } else {
2778
+ // Se non trovato nella lista, è auto-generated
2779
+ this.updateMenuSelection('auto');
2780
+ }
2781
+ }
2782
+
2783
+ // activate button
2784
+ const subtitlesBtn = this.api.container.querySelector('.subtitles-btn');
2785
+ if (subtitlesBtn) {
2786
+ subtitlesBtn.classList.add('active');
2787
+ }
2536
2788
  } else {
2537
2789
  this.updateMenuSelection('off');
2538
2790
  }
2539
2791
  } catch (e) {
2540
2792
  this.updateMenuSelection('off');
2541
2793
  }
2542
- }, 1500);
2794
+ }, 2000);
2543
2795
  }
2544
2796
 
2545
- /**
2546
- * Monitor caption state
2547
- */
2797
+ /**
2798
+ * Monitor caption state
2799
+ */
2548
2800
  startCaptionStateMonitoring() {
2549
2801
  if (this.captionStateCheckInterval) {
2550
2802
  clearInterval(this.captionStateCheckInterval);
@@ -2554,7 +2806,6 @@ startBufferMonitoring() {
2554
2806
  try {
2555
2807
  const currentTrack = this.ytPlayer.getOption('captions', 'track');
2556
2808
  const wasEnabled = this.captionsEnabled;
2557
-
2558
2809
  this.captionsEnabled = !!(currentTrack && currentTrack.languageCode);
2559
2810
 
2560
2811
  if (wasEnabled !== this.captionsEnabled) {
@@ -2562,8 +2813,24 @@ startBufferMonitoring() {
2562
2813
  if (subtitlesBtn) {
2563
2814
  if (this.captionsEnabled) {
2564
2815
  subtitlesBtn.classList.add('active');
2816
+
2817
+ // update current caption/translation
2818
+ if (currentTrack.translationLanguage) {
2819
+ this.currentTranslation = currentTrack.translationLanguage.languageCode;
2820
+ this.updateMenuSelection(`translate-${this.currentTranslation}`);
2821
+ } else {
2822
+ const captionIndex = this.availableCaptions.findIndex(
2823
+ cap => cap.languageCode === currentTrack.languageCode
2824
+ );
2825
+ if (captionIndex >= 0) {
2826
+ this.updateMenuSelection(`caption-${captionIndex}`);
2827
+ } else {
2828
+ this.updateMenuSelection('auto');
2829
+ }
2830
+ }
2565
2831
  } else {
2566
2832
  subtitlesBtn.classList.remove('active');
2833
+ this.updateMenuSelection('off');
2567
2834
  }
2568
2835
  }
2569
2836
  }
@@ -2883,7 +3150,7 @@ startBufferMonitoring() {
2883
3150
  }
2884
3151
  };
2885
3152
 
2886
- // Override pause method
3153
+ // Override pause method
2887
3154
  const originalPause = this.player.pause;
2888
3155
  this.player.pause = () => {
2889
3156
  if (this.ytPlayer && this.ytPlayer.pauseVideo) {
@@ -2955,15 +3222,13 @@ startBufferMonitoring() {
2955
3222
  // Override mute toggle
2956
3223
  const originalToggleMute = this.player.toggleMute;
2957
3224
  this.player.toggleMute = () => {
2958
- if (this.ytPlayer && this.ytPlayer.isMuted && this.ytPlayer.mute && this.ytPlayer.unMute) {
3225
+ if (this.ytPlayer && this.ytPlayer.isMuted) {
2959
3226
  const isMuted = this.ytPlayer.isMuted();
2960
-
2961
3227
  if (isMuted) {
2962
3228
  this.ytPlayer.unMute();
2963
3229
  } else {
2964
3230
  this.ytPlayer.mute();
2965
3231
  }
2966
-
2967
3232
  this.updateMuteButtonState(!isMuted);
2968
3233
 
2969
3234
  if (!isMuted) {
@@ -2977,97 +3242,6 @@ startBufferMonitoring() {
2977
3242
  }
2978
3243
  };
2979
3244
 
2980
- // Volume tooltip events for YouTube
2981
- if (this.api.player.volumeSlider) {
2982
- const volumeSlider = this.api.player.volumeSlider;
2983
- const volumeContainer = this.api.container.querySelector('.volume-container');
2984
-
2985
- // Remove existing listeners to avoid duplicates
2986
- const newVolumeSlider = volumeSlider.cloneNode(true);
2987
- volumeSlider.parentNode.replaceChild(newVolumeSlider, volumeSlider);
2988
- this.api.player.volumeSlider = newVolumeSlider;
2989
-
2990
- // Update tooltip on input (slider drag)
2991
- newVolumeSlider.addEventListener('input', (e) => {
2992
- const value = parseFloat(e.target.value);
2993
- this.player.updateVolume(value);
2994
-
2995
- // Update tooltip position and text during drag
2996
- if (this.api.player.updateVolumeTooltipPosition) {
2997
- this.api.player.updateVolumeTooltipPosition(value / 100);
2998
- }
2999
- if (this.api.player.updateVolumeTooltip) {
3000
- this.api.player.updateVolumeTooltip();
3001
- }
3002
- });
3003
-
3004
- // Update tooltip position on mousemove over slider
3005
- newVolumeSlider.addEventListener('mousemove', (e) => {
3006
- const rect = newVolumeSlider.getBoundingClientRect();
3007
- const mouseX = e.clientX - rect.left;
3008
- const percentage = Math.max(0, Math.min(1, mouseX / rect.width));
3009
-
3010
- // Update tooltip position as mouse moves
3011
- if (this.api.player.updateVolumeTooltipPosition) {
3012
- this.api.player.updateVolumeTooltipPosition(percentage);
3013
- }
3014
-
3015
- // Update tooltip text to show value under mouse
3016
- const volumeTooltip = this.api.container.querySelector('.volume-tooltip');
3017
- if (volumeTooltip) {
3018
- volumeTooltip.textContent = Math.round(percentage * 100) + '%';
3019
- }
3020
- });
3021
-
3022
- // Show/hide tooltip on hover
3023
- if (volumeContainer) {
3024
- volumeContainer.addEventListener('mouseenter', () => {
3025
- const volumeTooltip = this.api.container.querySelector('.volume-tooltip');
3026
- if (volumeTooltip) {
3027
- volumeTooltip.classList.add('visible');
3028
- }
3029
- });
3030
-
3031
- volumeContainer.addEventListener('mouseleave', () => {
3032
- const volumeTooltip = this.api.container.querySelector('.volume-tooltip');
3033
- if (volumeTooltip) {
3034
- volumeTooltip.classList.remove('visible');
3035
- }
3036
- });
3037
- }
3038
-
3039
- if (this.api.player.options.debug) {
3040
- console.log('[YT Plugin] Volume tooltip events bound');
3041
- }
3042
- }
3043
-
3044
- // Override playback speed
3045
- const originalChangeSpeed = this.player.changeSpeed;
3046
- if (originalChangeSpeed) {
3047
- this.player.changeSpeed = (e) => {
3048
- if (!e.target.classList.contains('speed-option')) return;
3049
-
3050
- const speed = parseFloat(e.target.getAttribute('data-speed'));
3051
-
3052
- if (this.ytPlayer && this.ytPlayer.setPlaybackRate && speed > 0) {
3053
- this.ytPlayer.setPlaybackRate(speed);
3054
-
3055
- const speedBtn = this.api.container.querySelector('.speed-btn');
3056
- if (speedBtn) speedBtn.textContent = `${speed}x`;
3057
-
3058
- const speedMenu = this.api.container.querySelector('.speed-menu');
3059
- if (speedMenu) {
3060
- speedMenu.querySelectorAll('.speed-option').forEach(option => {
3061
- option.classList.remove('active');
3062
- });
3063
- e.target.classList.add('active');
3064
- }
3065
- } else {
3066
- originalChangeSpeed.call(this.player, e);
3067
- }
3068
- };
3069
- }
3070
-
3071
3245
  // Override progress bar seeking
3072
3246
  if (this.api.player.progressContainer) {
3073
3247
  const progressContainer = this.api.player.progressContainer;
@@ -3082,7 +3256,22 @@ startBufferMonitoring() {
3082
3256
  // Create tooltip for seek preview
3083
3257
  const seekTooltip = document.createElement('div');
3084
3258
  seekTooltip.className = 'yt-seek-tooltip';
3085
- seekTooltip.style.cssText = 'position:absolute;bottom:calc(100% + 10px);left:0;background:rgba(28,28,28,0.95);color:#fff;padding:6px 10px;border-radius:3px;font-size:13px;font-weight:500;white-space:nowrap;pointer-events:none;visibility:hidden;z-index:99999;box-shadow:0 2px 8px rgba(0,0,0,0.3);';
3259
+ seekTooltip.style.cssText = `
3260
+ position: absolute;
3261
+ bottom: calc(100% + 10px);
3262
+ left: 0;
3263
+ background: rgba(28,28,28,0.95);
3264
+ color: #fff;
3265
+ padding: 6px 10px;
3266
+ border-radius: 3px;
3267
+ font-size: 13px;
3268
+ font-weight: 500;
3269
+ white-space: nowrap;
3270
+ pointer-events: none;
3271
+ visibility: hidden;
3272
+ z-index: 99999;
3273
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
3274
+ `;
3086
3275
  newProgressContainer.appendChild(seekTooltip);
3087
3276
 
3088
3277
  // Format time function for tooltip
@@ -3100,25 +3289,32 @@ startBufferMonitoring() {
3100
3289
  let isSeeking = false;
3101
3290
 
3102
3291
  const handleSeek = (e) => {
3103
- if (!this.ytPlayer || !this.ytPlayer.getDuration) return;
3292
+ if (!this.ytPlayer || !this.ytPlayer.getDuration()) return;
3104
3293
 
3105
3294
  const rect = newProgressContainer.getBoundingClientRect();
3106
- const clickX = e.clientX - rect.left;
3295
+
3296
+ // Support both mouse and touch events
3297
+ const clientX = e.clientX !== undefined ? e.clientX :
3298
+ (e.touches && e.touches[0] ? e.touches[0].clientX :
3299
+ (e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].clientX : 0));
3300
+
3301
+ const clickX = clientX - rect.left;
3107
3302
  const percentage = Math.max(0, Math.min(1, clickX / rect.width));
3108
3303
  const duration = this.ytPlayer.getDuration();
3109
3304
  const targetTime = percentage * duration;
3110
3305
 
3111
3306
  this.ytPlayer.seekTo(targetTime, true);
3112
3307
 
3113
- const progress = percentage * 100 + '%';
3308
+ const progress = percentage * 100;
3114
3309
  if (this.api.player.progressFilled) {
3115
- this.api.player.progressFilled.style.width = progress;
3310
+ this.api.player.progressFilled.style.width = `${progress}%`;
3116
3311
  }
3117
3312
  if (this.api.player.progressHandle) {
3118
- this.api.player.progressHandle.style.left = progress;
3313
+ this.api.player.progressHandle.style.left = `${progress}%`;
3119
3314
  }
3120
3315
  };
3121
3316
 
3317
+ // MOUSE EVENTS
3122
3318
  newProgressContainer.addEventListener('mousedown', (e) => {
3123
3319
  isSeeking = true;
3124
3320
  handleSeek(e);
@@ -3130,7 +3326,7 @@ startBufferMonitoring() {
3130
3326
  }
3131
3327
 
3132
3328
  // Show tooltip with timestamp
3133
- if (!isSeeking && this.ytPlayer && this.ytPlayer.getDuration) {
3329
+ if (!isSeeking && this.ytPlayer && this.ytPlayer.getDuration()) {
3134
3330
  const rect = newProgressContainer.getBoundingClientRect();
3135
3331
  const mouseX = e.clientX - rect.left;
3136
3332
  const percentage = Math.max(0, Math.min(1, mouseX / rect.width));
@@ -3138,7 +3334,7 @@ startBufferMonitoring() {
3138
3334
  const time = percentage * duration;
3139
3335
 
3140
3336
  seekTooltip.textContent = formatTimeForTooltip(time);
3141
- seekTooltip.style.left = mouseX + 'px';
3337
+ seekTooltip.style.left = `${mouseX}px`;
3142
3338
  seekTooltip.style.visibility = 'visible';
3143
3339
  }
3144
3340
  });
@@ -3151,10 +3347,50 @@ startBufferMonitoring() {
3151
3347
  isSeeking = false;
3152
3348
  });
3153
3349
 
3350
+ // TOUCH EVENTS - AGGIUNTI QUI!
3351
+ newProgressContainer.addEventListener('touchstart', (e) => {
3352
+ e.preventDefault(); // scroll prevention during drag
3353
+ isSeeking = true;
3354
+ handleSeek(e);
3355
+ }, { passive: false });
3356
+
3357
+ newProgressContainer.addEventListener('touchmove', (e) => {
3358
+ e.preventDefault(); // scroll prevention during drag
3359
+
3360
+ if (isSeeking) {
3361
+ handleSeek(e);
3362
+ }
3363
+
3364
+ // Show tooltip with timestamp during touch
3365
+ if (!isSeeking && this.ytPlayer && this.ytPlayer.getDuration()) {
3366
+ const rect = newProgressContainer.getBoundingClientRect();
3367
+ const touch = e.touches[0];
3368
+ const touchX = touch.clientX - rect.left;
3369
+ const percentage = Math.max(0, Math.min(1, touchX / rect.width));
3370
+ const duration = this.ytPlayer.getDuration();
3371
+ const time = percentage * duration;
3372
+
3373
+ seekTooltip.textContent = formatTimeForTooltip(time);
3374
+ seekTooltip.style.left = `${touchX}px`;
3375
+ seekTooltip.style.visibility = 'visible';
3376
+ }
3377
+ }, { passive: false });
3378
+
3379
+ newProgressContainer.addEventListener('touchend', () => {
3380
+ isSeeking = false;
3381
+ seekTooltip.style.visibility = 'hidden';
3382
+ });
3383
+
3384
+ newProgressContainer.addEventListener('touchcancel', () => {
3385
+ isSeeking = false;
3386
+ seekTooltip.style.visibility = 'hidden';
3387
+ });
3388
+
3389
+ // CLICK EVENT
3154
3390
  newProgressContainer.addEventListener('click', handleSeek);
3155
- }
3156
3391
 
3157
- this.bindVolumeSlider();
3392
+ this.bindVolumeSlider();
3393
+ }
3158
3394
 
3159
3395
  // Time update interval
3160
3396
  if (this.timeUpdateInterval) {
@@ -3175,14 +3411,13 @@ startBufferMonitoring() {
3175
3411
  } else {
3176
3412
  // For regular videos, calculate normally
3177
3413
  progress = (currentTime / duration) * 100;
3178
- }
3179
3414
 
3180
- // Check if live badge exists = it's a live stream
3181
- const liveBadge = this.api.container.querySelector('.live-badge');
3182
-
3183
- if (liveBadge) {
3184
- // Force 100% for live streams
3185
- progress = 100;
3415
+ // Check if live badge exists = it's a live stream
3416
+ const liveBadge = this.api.container.querySelector('.live-badge');
3417
+ if (liveBadge) {
3418
+ // Force 100% for live streams
3419
+ progress = 100;
3420
+ }
3186
3421
  }
3187
3422
 
3188
3423
  this.api.player.progressFilled.style.width = `${progress}%`;
@@ -3194,8 +3429,13 @@ startBufferMonitoring() {
3194
3429
  const currentTimeEl = this.api.container.querySelector('.current-time');
3195
3430
  const durationEl = this.api.container.querySelector('.duration');
3196
3431
 
3197
- if (currentTimeEl) currentTimeEl.textContent = this.formatTime(currentTime);
3198
- if (durationEl && duration) durationEl.textContent = this.formatTime(duration);
3432
+ if (currentTimeEl) {
3433
+ currentTimeEl.textContent = this.formatTime(currentTime);
3434
+ }
3435
+
3436
+ if (durationEl && duration) {
3437
+ durationEl.textContent = this.formatTime(duration);
3438
+ }
3199
3439
  }
3200
3440
  }, 250);
3201
3441
  }