myetv-player 1.0.0 → 1.0.8

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.
Files changed (38) hide show
  1. package/.github/workflows/codeql.yml +100 -0
  2. package/README.md +49 -58
  3. package/SECURITY.md +50 -0
  4. package/css/myetv-player.css +424 -219
  5. package/css/myetv-player.min.css +1 -1
  6. package/dist/myetv-player.js +1759 -1502
  7. package/dist/myetv-player.min.js +1705 -1469
  8. package/package.json +7 -1
  9. package/plugins/README.md +1016 -0
  10. package/plugins/cloudflare/README.md +1068 -0
  11. package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +556 -0
  12. package/plugins/facebook/README.md +1024 -0
  13. package/plugins/facebook/myetv-player-facebook-plugin.js +437 -0
  14. package/plugins/gamepad-remote-controller/README.md +816 -0
  15. package/plugins/gamepad-remote-controller/myetv-player-gamepad-remote-plugin.js +678 -0
  16. package/plugins/google-adsense-ads/README.md +1 -0
  17. package/plugins/google-adsense-ads/g-adsense-ads-plugin.js +158 -0
  18. package/plugins/google-ima-ads/README.md +1 -0
  19. package/plugins/google-ima-ads/g-ima-ads-plugin.js +355 -0
  20. package/plugins/twitch/README.md +1185 -0
  21. package/plugins/twitch/myetv-player-twitch-plugin.js +569 -0
  22. package/plugins/vast-vpaid-ads/README.md +1 -0
  23. package/plugins/vast-vpaid-ads/vast-vpaid-ads-plugin.js +346 -0
  24. package/plugins/vimeo/README.md +1416 -0
  25. package/plugins/vimeo/myetv-player-vimeo.js +640 -0
  26. package/plugins/youtube/README.md +851 -0
  27. package/plugins/youtube/myetv-player-youtube-plugin.js +1714 -210
  28. package/scss/README.md +160 -0
  29. package/scss/_controls.scss +184 -30
  30. package/scss/_menus.scss +840 -672
  31. package/scss/_responsive.scss +67 -105
  32. package/scss/_volume.scss +67 -105
  33. package/src/README.md +559 -0
  34. package/src/controls.js +17 -5
  35. package/src/core.js +1237 -1060
  36. package/src/i18n.js +27 -1
  37. package/src/quality.js +478 -436
  38. package/src/subtitles.js +2 -2
@@ -279,14 +279,40 @@ class VideoPlayerI18n {
279
279
 
280
280
  addTranslations(lang, translations) {
281
281
  try {
282
+
283
+ if (!this.isValidLanguageKey(lang)) {
284
+ console.warn('Invalid language key rejected:', lang);
285
+ return;
286
+ }
287
+
282
288
  if (!this.translations[lang]) {
283
289
  this.translations[lang] = {};
284
290
  }
285
- Object.assign(this.translations[lang], translations);
291
+
292
+
293
+ for (const key in translations) {
294
+ if (translations.hasOwnProperty(key) && this.isValidLanguageKey(key)) {
295
+ this.translations[lang][key] = translations[key];
296
+ }
297
+ }
286
298
  } catch (error) {
287
299
  console.warn('Error adding translations:', error);
288
300
  }
289
301
  }
302
+
303
+
304
+ isValidLanguageKey(key) {
305
+ if (typeof key !== 'string') return false;
306
+
307
+
308
+ const dangerousKeys = ['__proto__', 'constructor', 'prototype'];
309
+ if (dangerousKeys.includes(key.toLowerCase())) {
310
+ return false;
311
+ }
312
+
313
+
314
+ return /^[a-zA-Z0-9_-]+$/.test(key);
315
+ }
290
316
  }
291
317
 
292
318
  let VideoPlayerTranslations;
@@ -359,432 +385,435 @@ window.registerMYETVPlugin = registerPlugin;
359
385
  class MYETVvideoplayer {
360
386
 
361
387
  constructor(videoElement, options = {}) {
362
- this.video = typeof videoElement === 'string'
363
- ? document.getElementById(videoElement)
364
- : videoElement;
365
-
366
- if (!this.video) {
367
- throw new Error('Video element not found: ' + videoElement);
368
- }
369
-
370
- this.options = {
371
- showQualitySelector: true,
372
- showSpeedControl: true,
373
- showFullscreen: true,
374
- showPictureInPicture: true,
375
- showSubtitles: true,
376
- subtitlesEnabled: false,
377
- autoHide: true,
378
- autoHideDelay: 3000,
379
- poster: null,
380
- showPosterOnEnd: false,
381
- keyboardControls: true,
382
- showSeekTooltip: true,
383
- showTitleOverlay: false,
384
- videoTitle: '',
385
- persistentTitle: false,
386
- debug: false,
387
- autoplay: false,
388
- defaultQuality: 'auto',
389
- language: null,
390
- pauseClick: true,
391
- doubleTapPause: true,
392
- brandLogoEnabled: false,
393
- brandLogoUrl: '',
394
- brandLogoLinkUrl: '',
395
- playlistEnabled: true,
396
- playlistAutoPlay: true,
397
- playlistLoop: false,
398
- loop: false,
399
- volumeSlider: 'horizontal',
400
-
401
- watermarkUrl: '',
402
- watermarkLink: '',
403
- watermarkPosition: 'bottomright',
404
- watermarkTitle: '',
405
- hideWatermark: true,
406
-
407
- adaptiveStreaming: false,
408
- dashLibUrl: 'https://cdn.dashjs.org/latest/dash.all.min.js',
409
- hlsLibUrl: 'https://cdn.jsdelivr.net/npm/hls.js@latest',
410
- adaptiveQualityControl: true,
411
-
412
- audiofile: false,
413
- audiowave: false,
414
-
415
- resolution: "normal",
416
- ...options
417
- };
418
-
419
- this.isUserSeeking = false;
420
- this.controlsTimeout = null;
421
- this.titleTimeout = null;
422
- this.currentQualityIndex = 0;
423
- this.qualities = [];
424
- this.originalSources = [];
425
- this.isPiPSupported = this.checkPiPSupport();
426
- this.seekTooltip = null;
427
- this.titleOverlay = null;
428
- this.isPlayerReady = false;
388
+ this.video = typeof videoElement === 'string'
389
+ ? document.getElementById(videoElement)
390
+ : videoElement;
429
391
 
392
+ if (!this.video) {
393
+ throw new Error('Video element not found: ' + videoElement);
394
+ }
395
+
396
+ this.options = {
397
+ showQualitySelector: true,
398
+ showSpeedControl: true,
399
+ showFullscreen: true,
400
+ showPictureInPicture: true,
401
+ showSubtitles: true,
402
+ subtitlesEnabled: false,
403
+ autoHide: true,
404
+ autoHideDelay: 3000,
405
+ poster: null,
406
+ showPosterOnEnd: false,
407
+ keyboardControls: true,
408
+ showSeekTooltip: true,
409
+ showTitleOverlay: false,
410
+ videoTitle: '',
411
+ persistentTitle: false,
412
+ debug: false,
413
+ autoplay: false,
414
+ defaultQuality: 'auto',
415
+ language: null,
416
+ pauseClick: true,
417
+ doubleTapPause: true,
418
+ brandLogoEnabled: false,
419
+ brandLogoUrl: '',
420
+ brandLogoLinkUrl: '',
421
+ playlistEnabled: true,
422
+ playlistAutoPlay: true,
423
+ playlistLoop: false,
424
+ loop: false,
425
+ volumeSlider: 'show',
430
426
 
431
- this.textTracks = [];
432
- this.currentSubtitleTrack = null;
433
- this.subtitlesEnabled = false;
434
- this.customSubtitleRenderer = null;
435
-
427
+ watermarkUrl: '',
428
+ watermarkLink: '',
429
+ watermarkPosition: 'bottomright',
430
+ watermarkTitle: '',
431
+ hideWatermark: true,
436
432
 
437
- this.chapters = [];
438
- this.chapterMarkersContainer = null;
439
- this.chapterTooltip = null;
440
-
433
+ adaptiveStreaming: false,
434
+ dashLibUrl: 'https://cdn.dashjs.org/latest/dash.all.min.js',
435
+ hlsLibUrl: 'https://cdn.jsdelivr.net/npm/hls.js@latest',
436
+ adaptiveQualityControl: true,
441
437
 
442
- this.selectedQuality = this.options.defaultQuality || 'auto';
443
- this.currentPlayingQuality = null;
444
- this.qualityMonitorInterval = null;
445
-
438
+ seekHandleShape: 'circle',
446
439
 
447
- this.qualityChangeTimeout = null;
448
- this.isChangingQuality = false;
449
-
440
+ audiofile: false,
441
+ audiowave: false,
450
442
 
451
- this.debugQuality = false;
443
+ resolution: "normal",
444
+ ...options
445
+ };
452
446
 
453
-
454
- this.autoHideTimer = null;
455
- this.mouseOverControls = false;
456
- this.autoHideDebug = false;
457
- this.autoHideInitialized = false;
447
+ this.isUserSeeking = false;
448
+ this.controlsTimeout = null;
449
+ this.titleTimeout = null;
450
+ this.currentQualityIndex = 0;
451
+ this.qualities = [];
452
+ this.originalSources = [];
453
+ this.setupMenuToggles();
454
+ this.isPiPSupported = this.checkPiPSupport();
455
+ this.seekTooltip = null;
456
+ this.titleOverlay = null;
457
+ this.isPlayerReady = false;
458
458
 
459
-
460
- this.posterOverlay = null;
459
+
460
+ this.textTracks = [];
461
+ this.currentSubtitleTrack = null;
462
+ this.subtitlesEnabled = false;
463
+ this.customSubtitleRenderer = null;
461
464
 
462
465
 
463
- this.watermarkElement = null;
466
+ this.chapters = [];
467
+ this.chapterMarkersContainer = null;
468
+ this.chapterTooltip = null;
464
469
 
465
-
466
- this.eventCallbacks = {
467
- 'played': [],
468
- 'paused': [],
469
- 'subtitlechange': [],
470
- 'chapterchange': [],
471
- 'pipchange': [],
472
- 'fullscreenchange': [],
473
- 'speedchange': [],
474
- 'timeupdate': [],
475
- 'volumechange': [],
476
- 'qualitychange': [],
477
- 'playlistchange': [],
478
- 'ended': []
479
- };
470
+
471
+ this.selectedQuality = this.options.defaultQuality || 'auto';
472
+ this.currentPlayingQuality = null;
473
+ this.qualityMonitorInterval = null;
480
474
 
481
-
482
- this.playlist = [];
483
- this.currentPlaylistIndex = -1;
484
- this.playlistId = null;
485
- this.isPlaylistActive = false;
475
+
476
+ this.qualityChangeTimeout = null;
477
+ this.isChangingQuality = false;
486
478
 
487
-
488
- this.dashPlayer = null;
489
- this.hlsPlayer = null;
490
- this.adaptiveStreamingType = null;
491
- this.isAdaptiveStream = false;
492
- this.adaptiveQualities = [];
493
- this.librariesLoaded = {
494
- dash: false,
495
- hls: false
496
- };
479
+
480
+ this.debugQuality = false;
497
481
 
498
- this.lastTimeUpdate = 0;
482
+
483
+ this.autoHideTimer = null;
484
+ this.mouseOverControls = false;
485
+ this.autoHideDebug = false;
486
+ this.autoHideInitialized = false;
499
487
 
500
- if (this.options.language && this.isI18nAvailable()) {
501
- VideoPlayerTranslations.setLanguage(this.options.language);
502
- }
488
+
489
+ this.posterOverlay = null;
503
490
 
504
- if (options.autoplay) {
505
- this.video.autoplay = true;
506
- }
491
+
492
+ this.watermarkElement = null;
507
493
 
508
- try {
509
- this.interceptAutoLoading();
510
- this.createPlayerStructure();
511
- this.initializeElements();
512
-
513
- this.adaptToAudioFile = function () {
514
- if (this.options.audiofile) {
515
-
516
- if (this.video) {
517
- this.video.style.display = 'none';
518
- }
519
- if (this.container) {
520
- this.container.classList.add('audio-player');
521
- }
522
- if (this.options.audiowave) {
523
- this.initAudioWave();
524
- }
525
- }
526
- };
527
-
528
- this.initAudioWave = function () {
529
- if (!this.video) return;
494
+
495
+ this.eventCallbacks = {
496
+ 'played': [],
497
+ 'paused': [],
498
+ 'subtitlechange': [],
499
+ 'chapterchange': [],
500
+ 'pipchange': [],
501
+ 'fullscreenchange': [],
502
+ 'speedchange': [],
503
+ 'timeupdate': [],
504
+ 'volumechange': [],
505
+ 'qualitychange': [],
506
+ 'playlistchange': [],
507
+ 'ended': []
508
+ };
530
509
 
531
- this.audioWaveCanvas = document.createElement('canvas');
532
- this.audioWaveCanvas.className = 'audio-wave-canvas';
533
- this.container.appendChild(this.audioWaveCanvas);
510
+
511
+ this.playlist = [];
512
+ this.currentPlaylistIndex = -1;
513
+ this.playlistId = null;
514
+ this.isPlaylistActive = false;
534
515
 
535
- const canvasCtx = this.audioWaveCanvas.getContext('2d');
536
- const WIDTH = this.audioWaveCanvas.width = this.container.clientWidth;
537
- const HEIGHT = this.audioWaveCanvas.height = 60;
516
+
517
+ this.dashPlayer = null;
518
+ this.hlsPlayer = null;
519
+ this.adaptiveStreamingType = null;
520
+ this.isAdaptiveStream = false;
521
+ this.adaptiveQualities = [];
522
+ this.librariesLoaded = {
523
+ dash: false,
524
+ hls: false
525
+ };
538
526
 
539
-
540
- const AudioContext = window.AudioContext || window.webkitAudioContext;
541
- this.audioCtx = new AudioContext();
542
- this.analyser = this.audioCtx.createAnalyser();
543
- this.source = this.audioCtx.createMediaElementSource(this.video);
544
- this.source.connect(this.analyser);
545
- this.analyser.connect(this.audioCtx.destination);
527
+ this.lastTimeUpdate = 0;
546
528
 
547
- this.analyser.fftSize = 2048;
548
- const bufferLength = this.analyser.fftSize;
549
- const dataArray = new Uint8Array(bufferLength);
529
+ if (this.options.language && this.isI18nAvailable()) {
530
+ VideoPlayerTranslations.setLanguage(this.options.language);
531
+ }
532
+
533
+ if (options.autoplay) {
534
+ this.video.autoplay = true;
535
+ }
550
536
 
537
+ try {
538
+ this.interceptAutoLoading();
539
+ this.createPlayerStructure();
540
+ this.initializeElements();
541
+
542
+ this.adaptToAudioFile = function () {
543
+ if (this.options.audiofile) {
551
544
 
552
- const draw = () => {
553
- requestAnimationFrame(draw);
554
- this.analyser.getByteTimeDomainData(dataArray);
555
-
556
- canvasCtx.fillStyle = '#222';
557
- canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
545
+ if (this.video) {
546
+ this.video.style.display = 'none';
547
+ }
548
+ if (this.container) {
549
+ this.container.classList.add('audio-player');
550
+ }
551
+ if (this.options.audiowave) {
552
+ this.initAudioWave();
553
+ }
554
+ }
555
+ };
556
+
557
+ this.initAudioWave = function () {
558
+ if (!this.video) return;
558
559
 
559
- canvasCtx.lineWidth = 2;
560
- canvasCtx.strokeStyle = '#33ccff';
561
- canvasCtx.beginPath();
560
+ this.audioWaveCanvas = document.createElement('canvas');
561
+ this.audioWaveCanvas.className = 'audio-wave-canvas';
562
+ this.container.appendChild(this.audioWaveCanvas);
562
563
 
563
- const sliceWidth = WIDTH / bufferLength;
564
- let x = 0;
564
+ const canvasCtx = this.audioWaveCanvas.getContext('2d');
565
+ const WIDTH = this.audioWaveCanvas.width = this.container.clientWidth;
566
+ const HEIGHT = this.audioWaveCanvas.height = 60;
565
567
 
566
- for (let i = 0; i < bufferLength; i++) {
567
- const v = dataArray[i] / 128.0;
568
- const y = v * HEIGHT / 2;
568
+
569
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
570
+ this.audioCtx = new AudioContext();
571
+ this.analyser = this.audioCtx.createAnalyser();
572
+ this.source = this.audioCtx.createMediaElementSource(this.video);
573
+ this.source.connect(this.analyser);
574
+ this.analyser.connect(this.audioCtx.destination);
569
575
 
570
- if (i === 0) {
571
- canvasCtx.moveTo(x, y);
572
- } else {
573
- canvasCtx.lineTo(x, y);
574
- }
576
+ this.analyser.fftSize = 2048;
577
+ const bufferLength = this.analyser.fftSize;
578
+ const dataArray = new Uint8Array(bufferLength);
575
579
 
576
- x += sliceWidth;
577
- }
578
- canvasCtx.lineTo(WIDTH, HEIGHT / 2);
579
- canvasCtx.stroke();
580
- };
580
+
581
+ const draw = () => {
582
+ requestAnimationFrame(draw);
583
+ this.analyser.getByteTimeDomainData(dataArray);
581
584
 
582
- draw();
583
- };
584
- this.adaptToAudioFile();
585
- this.bindEvents();
585
+ canvasCtx.fillStyle = '#222';
586
+ canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
586
587
 
587
- if (this.options.keyboardControls) {
588
- this.setupKeyboardControls();
589
- }
588
+ canvasCtx.lineWidth = 2;
589
+ canvasCtx.strokeStyle = '#33ccff';
590
+ canvasCtx.beginPath();
590
591
 
591
- this.updateVolumeSliderVisual();
592
- this.initVolumeTooltip();
593
- this.updateTooltips();
594
- this.markPlayerReady();
595
- this.initializePluginSystem();
596
- this.restoreSourcesAsync();
592
+ const sliceWidth = WIDTH / bufferLength;
593
+ let x = 0;
597
594
 
598
- this.initializeSubtitles();
599
- this.initializeQualityMonitoring();
595
+ for (let i = 0; i < bufferLength; i++) {
596
+ const v = dataArray[i] / 128.0;
597
+ const y = v * HEIGHT / 2;
600
598
 
601
- this.initializeResolution();
602
- this.initializeChapters();
603
- this.initializePoster();
604
- this.initializeWatermark();
599
+ if (i === 0) {
600
+ canvasCtx.moveTo(x, y);
601
+ } else {
602
+ canvasCtx.lineTo(x, y);
603
+ }
605
604
 
606
- } catch (error) {
607
- if (this.options.debug) console.error('Video player initialization error:', error);
608
- }
609
- }
605
+ x += sliceWidth;
606
+ }
607
+ canvasCtx.lineTo(WIDTH, HEIGHT / 2);
608
+ canvasCtx.stroke();
609
+ };
610
610
 
611
- getPlayerState() {
612
- return {
613
- isPlaying: !this.isPaused(),
614
- isPaused: this.isPaused(),
615
- currentTime: this.getCurrentTime(),
616
- duration: this.getDuration(),
617
- volume: this.getVolume(),
618
- isMuted: this.isMuted(),
619
- playbackRate: this.getPlaybackRate(),
620
- isFullscreen: this.isFullscreenActive(),
621
- isPictureInPicture: this.isPictureInPictureActive(),
622
- subtitlesEnabled: this.isSubtitlesEnabled(),
623
- currentSubtitle: this.getCurrentSubtitleTrack(),
624
- selectedQuality: this.getSelectedQuality(),
625
- currentQuality: this.getCurrentPlayingQuality(),
626
- isAutoQuality: this.isAutoQualityActive()
611
+ draw();
627
612
  };
628
- }
629
-
630
- isI18nAvailable() {
631
- return typeof VideoPlayerTranslations !== 'undefined' &&
632
- VideoPlayerTranslations !== null &&
633
- typeof VideoPlayerTranslations.t === 'function';
634
- }
613
+ this.adaptToAudioFile();
614
+ this.bindEvents();
635
615
 
636
- t(key) {
637
- if (this.isI18nAvailable()) {
638
- try {
639
- return VideoPlayerTranslations.t(key);
640
- } catch (error) {
641
- if (this.options.debug) console.warn('Translation error:', error);
642
- }
616
+ if (this.options.keyboardControls) {
617
+ this.setupKeyboardControls();
643
618
  }
644
619
 
645
- const fallback = {
646
- 'play_pause': 'Play/Pause (Space)',
647
- 'mute_unmute': 'Mute/Unmute (M)',
648
- 'volume': 'Volume',
649
- 'playback_speed': 'Playback speed',
650
- 'video_quality': 'Video quality',
651
- 'picture_in_picture': 'Picture-in-Picture (P)',
652
- 'fullscreen': 'Fullscreen (F)',
653
- 'subtitles': 'Subtitles (S)',
654
- 'subtitles_enable': 'Enable subtitles',
655
- 'subtitles_disable': 'Disable subtitles',
656
- 'subtitles_off': 'Off',
657
- 'auto': 'Auto',
658
- 'brand_logo': 'Brand logo',
659
- 'next_video': 'Next video (N)',
660
- 'prev_video': 'Previous video (P)',
661
- 'playlist_next': 'Next',
662
- 'playlist_prev': 'Previous'
663
- };
620
+ this.updateVolumeSliderVisual();
621
+ this.initVolumeTooltip();
622
+ this.updateTooltips();
623
+ this.markPlayerReady();
624
+ this.initializePluginSystem();
625
+ this.restoreSourcesAsync();
664
626
 
665
- return fallback[key] || key;
627
+ this.initializeSubtitles();
628
+ this.initializeQualityMonitoring();
629
+
630
+ this.initializeResolution();
631
+ this.initializeChapters();
632
+ this.initializePoster();
633
+ this.initializeWatermark();
634
+
635
+ } catch (error) {
636
+ if (this.options.debug) console.error('Video player initialization error:', error);
666
637
  }
638
+ }
639
+
640
+ getPlayerState() {
641
+ return {
642
+ isPlaying: !this.isPaused(),
643
+ isPaused: this.isPaused(),
644
+ currentTime: this.getCurrentTime(),
645
+ duration: this.getDuration(),
646
+ volume: this.getVolume(),
647
+ isMuted: this.isMuted(),
648
+ playbackRate: this.getPlaybackRate(),
649
+ isFullscreen: this.isFullscreenActive(),
650
+ isPictureInPicture: this.isPictureInPictureActive(),
651
+ subtitlesEnabled: this.isSubtitlesEnabled(),
652
+ currentSubtitle: this.getCurrentSubtitleTrack(),
653
+ selectedQuality: this.getSelectedQuality(),
654
+ currentQuality: this.getCurrentPlayingQuality(),
655
+ isAutoQuality: this.isAutoQualityActive()
656
+ };
657
+ }
667
658
 
668
- interceptAutoLoading() {
669
- this.saveOriginalSources();
670
- this.disableSources();
659
+ isI18nAvailable() {
660
+ return typeof VideoPlayerTranslations !== 'undefined' &&
661
+ VideoPlayerTranslations !== null &&
662
+ typeof VideoPlayerTranslations.t === 'function';
663
+ }
671
664
 
672
- this.video.preload = 'none';
673
- this.video.controls = false;
674
- this.video.autoplay = false;
665
+ t(key) {
666
+ if (this.isI18nAvailable()) {
667
+ try {
668
+ return VideoPlayerTranslations.t(key);
669
+ } catch (error) {
670
+ if (this.options.debug) console.warn('Translation error:', error);
671
+ }
672
+ }
673
+
674
+ const fallback = {
675
+ 'play_pause': 'Play/Pause (Space)',
676
+ 'mute_unmute': 'Mute/Unmute (M)',
677
+ 'volume': 'Volume',
678
+ 'playback_speed': 'Playback speed',
679
+ 'video_quality': 'Video quality',
680
+ 'picture_in_picture': 'Picture-in-Picture (P)',
681
+ 'fullscreen': 'Fullscreen (F)',
682
+ 'subtitles': 'Subtitles (S)',
683
+ 'subtitles_enable': 'Enable subtitles',
684
+ 'subtitles_disable': 'Disable subtitles',
685
+ 'subtitles_off': 'Off',
686
+ 'auto': 'Auto',
687
+ 'brand_logo': 'Brand logo',
688
+ 'next_video': 'Next video (N)',
689
+ 'prev_video': 'Previous video (P)',
690
+ 'playlist_next': 'Next',
691
+ 'playlist_prev': 'Previous'
692
+ };
675
693
 
676
- if (this.video.src && this.video.src !== window.location.href) {
677
- this.originalSrc = this.video.src;
678
- this.video.removeAttribute('src');
679
- this.video.src = '';
680
- }
694
+ return fallback[key] || key;
695
+ }
681
696
 
682
- this.hideNativePlayer();
697
+ interceptAutoLoading() {
698
+ this.saveOriginalSources();
699
+ this.disableSources();
683
700
 
684
- if (this.options.debug) console.log('📁 Sources temporarily disabled to prevent blocking');
701
+ this.video.preload = 'none';
702
+ this.video.controls = false;
703
+ this.video.autoplay = false;
704
+
705
+ if (this.video.src && this.video.src !== window.location.href) {
706
+ this.originalSrc = this.video.src;
707
+ this.video.removeAttribute('src');
708
+ this.video.src = '';
685
709
  }
686
710
 
687
- saveOriginalSources() {
688
- const sources = this.video.querySelectorAll('source');
689
- this.originalSources = [];
711
+ this.hideNativePlayer();
690
712
 
691
- sources.forEach((source, index) => {
692
- if (source.src) {
693
- this.originalSources.push({
694
- element: source,
695
- src: source.src,
696
- type: source.type || 'video/mp4',
697
- quality: source.getAttribute('data-quality') || `quality-${index}`,
698
- index: index
699
- });
700
- }
701
- });
713
+ if (this.options.debug) console.log('📁 Sources temporarily disabled to prevent blocking');
714
+ }
702
715
 
703
- if (this.options.debug) console.log(`📁 Saved ${this.originalSources.length} sources originali:`, this.originalSources);
704
- }
716
+ saveOriginalSources() {
717
+ const sources = this.video.querySelectorAll('source');
718
+ this.originalSources = [];
719
+
720
+ sources.forEach((source, index) => {
721
+ if (source.src) {
722
+ this.originalSources.push({
723
+ element: source,
724
+ src: source.src,
725
+ type: source.type || 'video/mp4',
726
+ quality: source.getAttribute('data-quality') || `quality-${index}`,
727
+ index: index
728
+ });
729
+ }
730
+ });
705
731
 
706
- disableSources() {
707
- const sources = this.video.querySelectorAll('source');
708
- sources.forEach(source => {
709
- if (source.src) {
710
- source.removeAttribute('src');
711
- }
712
- });
713
- }
732
+ if (this.options.debug) console.log(`📁 Saved ${this.originalSources.length} sources originali:`, this.originalSources);
733
+ }
714
734
 
715
- restoreSourcesAsync() {
716
- setTimeout(() => {
717
- this.restoreSources();
718
- }, 200);
719
- }
735
+ disableSources() {
736
+ const sources = this.video.querySelectorAll('source');
737
+ sources.forEach(source => {
738
+ if (source.src) {
739
+ source.removeAttribute('src');
740
+ }
741
+ });
742
+ }
720
743
 
721
- async restoreSources() {
722
- try {
723
-
724
- let adaptiveSource = null;
744
+ restoreSourcesAsync() {
745
+ setTimeout(() => {
746
+ this.restoreSources();
747
+ }, 200);
748
+ }
725
749
 
726
- if (this.originalSrc) {
727
- adaptiveSource = this.originalSrc;
728
- } else if (this.originalSources.length > 0) {
729
-
730
- const firstSource = this.originalSources[0];
731
- if (firstSource.src && this.detectStreamType(firstSource.src)) {
732
- adaptiveSource = firstSource.src;
733
- }
734
- }
750
+ async restoreSources() {
751
+ try {
752
+
753
+ let adaptiveSource = null;
735
754
 
755
+ if (this.originalSrc) {
756
+ adaptiveSource = this.originalSrc;
757
+ } else if (this.originalSources.length > 0) {
736
758
 
737
- if (adaptiveSource && this.options.adaptiveStreaming) {
738
- const adaptiveInitialized = await this.initializeAdaptiveStreaming(adaptiveSource);
739
- if (adaptiveInitialized) {
740
- if (this.options.debug) console.log('📡 Adaptive streaming initialized');
741
- return;
742
- }
759
+ const firstSource = this.originalSources[0];
760
+ if (firstSource.src && this.detectStreamType(firstSource.src)) {
761
+ adaptiveSource = firstSource.src;
743
762
  }
763
+ }
744
764
 
745
-
746
- if (this.originalSrc) {
747
- this.video.src = this.originalSrc;
765
+
766
+ if (adaptiveSource && this.options.adaptiveStreaming) {
767
+ const adaptiveInitialized = await this.initializeAdaptiveStreaming(adaptiveSource);
768
+ if (adaptiveInitialized) {
769
+ if (this.options.debug) console.log('📡 Adaptive streaming initialized');
770
+ return;
748
771
  }
772
+ }
749
773
 
750
- this.originalSources.forEach(sourceData => {
751
- if (sourceData.element && sourceData.src) {
752
- sourceData.element.src = sourceData.src;
753
- }
754
- });
755
-
756
- this.qualities = this.originalSources.map(s => ({
757
- src: s.src,
758
- quality: s.quality,
759
- type: s.type
760
- }));
774
+
775
+ if (this.originalSrc) {
776
+ this.video.src = this.originalSrc;
777
+ }
761
778
 
762
- if (this.originalSrc && this.qualities.length === 0) {
763
- this.qualities.push({
764
- src: this.originalSrc,
765
- quality: 'default',
766
- type: 'video/mp4'
767
- });
779
+ this.originalSources.forEach(sourceData => {
780
+ if (sourceData.element && sourceData.src) {
781
+ sourceData.element.src = sourceData.src;
768
782
  }
783
+ });
769
784
 
770
- if (this.qualities.length > 0) {
771
- this.video.load();
785
+ this.qualities = this.originalSources.map(s => ({
786
+ src: s.src,
787
+ quality: s.quality,
788
+ type: s.type
789
+ }));
772
790
 
773
-
774
- this.video.addEventListener('loadedmetadata', () => {
775
- setTimeout(() => {
776
- this.reinitializeSubtitles();
777
- if (this.options.debug) console.log('🔄 Subtitles re-initialized after video load');
778
- }, 300);
779
- }, { once: true });
780
- }
791
+ if (this.originalSrc && this.qualities.length === 0) {
792
+ this.qualities.push({
793
+ src: this.originalSrc,
794
+ quality: 'default',
795
+ type: 'video/mp4'
796
+ });
797
+ }
781
798
 
782
- if (this.options.debug) console.log('✅ Sources ripristinate:', this.qualities);
799
+ if (this.qualities.length > 0) {
800
+ this.video.load();
783
801
 
784
- } catch (error) {
785
- if (this.options.debug) console.error('❌ Errore ripristino sources:', error);
802
+
803
+ this.video.addEventListener('loadedmetadata', () => {
804
+ setTimeout(() => {
805
+ this.reinitializeSubtitles();
806
+ if (this.options.debug) console.log('🔄 Subtitles re-initialized after video load');
807
+ }, 300);
808
+ }, { once: true });
786
809
  }
810
+
811
+ if (this.options.debug) console.log('✅ Sources ripristinate:', this.qualities);
812
+
813
+ } catch (error) {
814
+ if (this.options.debug) console.error('❌ Errore ripristino sources:', error);
787
815
  }
816
+ }
788
817
 
789
818
  reinitializeSubtitles() {
790
819
  if (this.options.debug) console.log('🔄 Re-initializing subtitles...');
@@ -830,629 +859,697 @@ getDefaultSubtitleTrack() {
830
859
  return -1;
831
860
  }
832
861
 
833
- markPlayerReady() {
834
- setTimeout(() => {
835
- this.isPlayerReady = true;
836
- if (this.container) {
837
- this.container.classList.add('player-initialized');
838
- }
862
+ markPlayerReady() {
863
+ setTimeout(() => {
864
+ this.isPlayerReady = true;
865
+ if (this.container) {
866
+ this.container.classList.add('player-initialized');
867
+ }
868
+
869
+ if (this.video) {
870
+ this.video.style.visibility = '';
871
+ this.video.style.opacity = '';
872
+ this.video.style.pointerEvents = '';
873
+ }
839
874
 
840
- if (this.video) {
841
- this.video.style.visibility = '';
842
- this.video.style.opacity = '';
843
- this.video.style.pointerEvents = '';
875
+
876
+ setTimeout(() => {
877
+ if (this.options.autoHide && !this.autoHideInitialized) {
878
+ this.initAutoHide();
844
879
  }
845
880
 
846
881
 
847
- setTimeout(() => {
848
- if (this.options.autoHide && !this.autoHideInitialized) {
849
- this.initAutoHide();
850
- }
851
-
852
-
853
- if (this.selectedQuality && this.qualities && this.qualities.length > 0) {
854
- if (this.options.debug) console.log(`🎯 Applying defaultQuality: "${this.selectedQuality}"`);
882
+ if (this.selectedQuality && this.qualities && this.qualities.length > 0) {
883
+ if (this.options.debug) console.log(`🎯 Applying defaultQuality: "${this.selectedQuality}"`);
855
884
 
856
- if (this.selectedQuality === 'auto') {
857
- this.enableAutoQuality();
885
+ if (this.selectedQuality === 'auto') {
886
+ this.enableAutoQuality();
887
+ } else {
888
+
889
+ const requestedQuality = this.qualities.find(q => q.quality === this.selectedQuality);
890
+ if (requestedQuality) {
891
+ if (this.options.debug) console.log(`✅ Quality "${this.selectedQuality}" available`);
892
+ this.setQuality(this.selectedQuality);
858
893
  } else {
859
-
860
- const requestedQuality = this.qualities.find(q => q.quality === this.selectedQuality);
861
- if (requestedQuality) {
862
- if (this.options.debug) console.log(`✅ Quality "${this.selectedQuality}" available`);
863
- this.setQuality(this.selectedQuality);
864
- } else {
865
- if (this.options.debug) console.warn(`⚠️ Quality "${this.selectedQuality}" not available - fallback to auto`);
866
- if (this.options.debug) console.log('📋 Available qualities:', this.qualities.map(q => q.quality));
867
- this.enableAutoQuality();
868
- }
894
+ if (this.options.debug) console.warn(`⚠️ Quality "${this.selectedQuality}" not available - fallback to auto`);
895
+ if (this.options.debug) console.log('📋 Available qualities:', this.qualities.map(q => q.quality));
896
+ this.enableAutoQuality();
869
897
  }
870
898
  }
899
+ }
871
900
 
872
-
873
- if (this.options.autoplay) {
874
- if (this.options.debug) console.log('🎬 Autoplay enabled');
875
- setTimeout(() => {
876
- this.video.play().catch(error => {
877
- if (this.options.debug) console.warn('⚠️ Autoplay blocked:', error);
878
- });
879
- }, 100);
880
- }
881
- }, 200);
901
+
902
+ if (this.options.autoplay) {
903
+ if (this.options.debug) console.log('🎬 Autoplay enabled');
904
+ setTimeout(() => {
905
+ this.video.play().catch(error => {
906
+ if (this.options.debug) console.warn('⚠️ Autoplay blocked:', error);
907
+ });
908
+ }, 100);
909
+ }
910
+ }, 200);
882
911
 
883
- }, 100);
884
- }
912
+ }, 100);
913
+ }
885
914
 
886
- createPlayerStructure() {
887
- let wrapper = this.video.closest('.video-wrapper');
888
- if (!wrapper) {
889
- wrapper = document.createElement('div');
890
- wrapper.className = 'video-wrapper';
891
- this.video.parentNode.insertBefore(wrapper, this.video);
892
- wrapper.appendChild(this.video);
893
- }
915
+ createPlayerStructure() {
916
+ let wrapper = this.video.closest('.video-wrapper');
917
+ if (!wrapper) {
918
+ wrapper = document.createElement('div');
919
+ wrapper.className = 'video-wrapper';
920
+ this.video.parentNode.insertBefore(wrapper, this.video);
921
+ wrapper.appendChild(this.video);
922
+ }
894
923
 
895
- this.container = wrapper;
924
+ this.container = wrapper;
896
925
 
897
- this.createInitialLoading();
898
- this.createLoadingOverlay();
899
- this.collectVideoQualities();
900
- this.createControls();
901
- this.createBrandLogo();
902
- this.detectPlaylist();
926
+ this.createInitialLoading();
927
+ this.createLoadingOverlay();
928
+ this.collectVideoQualities();
929
+ this.createControls();
930
+ this.createBrandLogo();
931
+ this.detectPlaylist();
903
932
 
904
- if (this.options.showTitleOverlay) {
905
- this.createTitleOverlay();
906
- }
933
+ if (this.options.showTitleOverlay) {
934
+ this.createTitleOverlay();
907
935
  }
936
+ }
908
937
 
909
- createInitialLoading() {
910
- const initialLoader = document.createElement('div');
911
- initialLoader.className = 'initial-loading';
912
- initialLoader.innerHTML = '<div class="loading-spinner"></div>';
913
- this.container.appendChild(initialLoader);
914
- this.initialLoading = initialLoader;
915
- }
938
+ createInitialLoading() {
939
+ const initialLoader = document.createElement('div');
940
+ initialLoader.className = 'initial-loading';
941
+ initialLoader.innerHTML = '<div class="loading-spinner"></div>';
942
+ this.container.appendChild(initialLoader);
943
+ this.initialLoading = initialLoader;
944
+ }
916
945
 
917
- collectVideoQualities() {
918
- if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
919
- }
946
+ collectVideoQualities() {
947
+ if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
948
+ }
949
+
950
+ createLoadingOverlay() {
951
+ const overlay = document.createElement('div');
952
+ overlay.className = 'loading-overlay';
953
+ overlay.id = 'loadingOverlay-' + this.getUniqueId();
954
+ overlay.innerHTML = '<div class="loading-spinner"></div>';
955
+ this.container.appendChild(overlay);
956
+ this.loadingOverlay = overlay;
957
+ }
958
+
959
+ createTitleOverlay() {
960
+ const overlay = document.createElement('div');
961
+ overlay.className = 'title-overlay';
962
+ overlay.id = 'titleOverlay-' + this.getUniqueId();
963
+
964
+ const titleText = document.createElement('h2');
965
+ titleText.className = 'title-text';
966
+ titleText.textContent = this.options.videoTitle || '';
967
+
968
+ overlay.appendChild(titleText);
920
969
 
921
- createLoadingOverlay() {
922
- const overlay = document.createElement('div');
923
- overlay.className = 'loading-overlay';
924
- overlay.id = 'loadingOverlay-' + this.getUniqueId();
925
- overlay.innerHTML = '<div class="loading-spinner"></div>';
970
+ if (this.controls) {
971
+ this.container.insertBefore(overlay, this.controls);
972
+ } else {
926
973
  this.container.appendChild(overlay);
927
- this.loadingOverlay = overlay;
928
974
  }
929
975
 
930
- createTitleOverlay() {
931
- const overlay = document.createElement('div');
932
- overlay.className = 'title-overlay';
933
- overlay.id = 'titleOverlay-' + this.getUniqueId();
976
+ this.titleOverlay = overlay;
934
977
 
935
- const titleText = document.createElement('h2');
936
- titleText.className = 'title-text';
937
- titleText.textContent = this.options.videoTitle || '';
938
-
939
- overlay.appendChild(titleText);
978
+ if (this.options.persistentTitle && this.options.videoTitle) {
979
+ this.showTitleOverlay();
980
+ }
981
+ }
940
982
 
941
- if (this.controls) {
942
- this.container.insertBefore(overlay, this.controls);
943
- } else {
944
- this.container.appendChild(overlay);
945
- }
983
+ updateTooltips() {
984
+ if (!this.controls) return;
946
985
 
947
- this.titleOverlay = overlay;
986
+ try {
987
+ this.controls.querySelectorAll('[data-tooltip]').forEach(element => {
988
+ const key = element.getAttribute('data-tooltip');
989
+ element.title = this.t(key);
990
+ });
948
991
 
949
- if (this.options.persistentTitle && this.options.videoTitle) {
950
- this.showTitleOverlay();
992
+ const autoOption = this.controls.querySelector('.quality-option[data-quality="auto"]');
993
+ if (autoOption) {
994
+ autoOption.textContent = this.t('auto');
951
995
  }
996
+ } catch (error) {
997
+ if (this.options.debug) console.warn('Errore aggiornamento tooltip:', error);
952
998
  }
999
+ }
953
1000
 
954
- updateTooltips() {
955
- if (!this.controls) return;
956
-
1001
+ setLanguage(lang) {
1002
+ if (this.isI18nAvailable()) {
957
1003
  try {
958
- this.controls.querySelectorAll('[data-tooltip]').forEach(element => {
959
- const key = element.getAttribute('data-tooltip');
960
- element.title = this.t(key);
961
- });
962
-
963
- const autoOption = this.controls.querySelector('.quality-option[data-quality="auto"]');
964
- if (autoOption) {
965
- autoOption.textContent = this.t('auto');
1004
+ if (VideoPlayerTranslations.setLanguage(lang)) {
1005
+ this.updateTooltips();
1006
+ return true;
966
1007
  }
967
1008
  } catch (error) {
968
- if (this.options.debug) console.warn('Errore aggiornamento tooltip:', error);
1009
+ if (this.options.debug) console.warn('Errore cambio lingua:', error);
969
1010
  }
970
1011
  }
1012
+ return false;
1013
+ }
971
1014
 
972
- setLanguage(lang) {
973
- if (this.isI18nAvailable()) {
974
- try {
975
- if (VideoPlayerTranslations.setLanguage(lang)) {
976
- this.updateTooltips();
977
- return true;
978
- }
979
- } catch (error) {
980
- if (this.options.debug) console.warn('Errore cambio lingua:', error);
1015
+ setVideoTitle(title) {
1016
+ this.options.videoTitle = title || '';
1017
+
1018
+ if (this.titleOverlay) {
1019
+ const titleElement = this.titleOverlay.querySelector('.title-text');
1020
+ if (titleElement) {
1021
+ titleElement.textContent = this.options.videoTitle;
1022
+ }
1023
+
1024
+ if (title) {
1025
+ this.showTitleOverlay();
1026
+
1027
+ if (!this.options.persistentTitle) {
1028
+ this.clearTitleTimeout();
1029
+ this.titleTimeout = setTimeout(() => {
1030
+ this.hideTitleOverlay();
1031
+ }, 3000);
981
1032
  }
982
1033
  }
983
- return false;
984
1034
  }
985
1035
 
986
- setVideoTitle(title) {
987
- this.options.videoTitle = title || '';
1036
+ return this;
1037
+ }
988
1038
 
989
- if (this.titleOverlay) {
990
- const titleElement = this.titleOverlay.querySelector('.title-text');
991
- if (titleElement) {
992
- titleElement.textContent = this.options.videoTitle;
993
- }
1039
+ getVideoTitle() {
1040
+ return this.options.videoTitle;
1041
+ }
994
1042
 
995
- if (title) {
996
- this.showTitleOverlay();
1043
+ setPersistentTitle(persistent) {
1044
+ this.options.persistentTitle = persistent;
997
1045
 
998
- if (!this.options.persistentTitle) {
999
- this.clearTitleTimeout();
1000
- this.titleTimeout = setTimeout(() => {
1001
- this.hideTitleOverlay();
1002
- }, 3000);
1003
- }
1046
+ if (this.titleOverlay && this.options.videoTitle) {
1047
+ if (persistent) {
1048
+ this.showTitleOverlay();
1049
+ this.clearTitleTimeout();
1050
+ } else {
1051
+ this.titleOverlay.classList.remove('persistent');
1052
+ if (this.titleOverlay.classList.contains('show')) {
1053
+ this.clearTitleTimeout();
1054
+ this.titleTimeout = setTimeout(() => {
1055
+ this.hideTitleOverlay();
1056
+ }, 3000);
1004
1057
  }
1005
1058
  }
1059
+ }
1006
1060
 
1007
- return this;
1061
+ return this;
1062
+ }
1063
+
1064
+ enableTitleOverlay() {
1065
+ if (!this.titleOverlay && !this.options.showTitleOverlay) {
1066
+ this.options.showTitleOverlay = true;
1067
+ this.createTitleOverlay();
1008
1068
  }
1069
+ return this;
1070
+ }
1009
1071
 
1010
- getVideoTitle() {
1011
- return this.options.videoTitle;
1072
+ disableTitleOverlay() {
1073
+ if (this.titleOverlay) {
1074
+ this.titleOverlay.remove();
1075
+ this.titleOverlay = null;
1012
1076
  }
1077
+ this.options.showTitleOverlay = false;
1078
+ return this;
1079
+ }
1013
1080
 
1014
- setPersistentTitle(persistent) {
1015
- this.options.persistentTitle = persistent;
1081
+ getUniqueId() {
1082
+ return Math.random().toString(36).substr(2, 9);
1083
+ }
1016
1084
 
1017
- if (this.titleOverlay && this.options.videoTitle) {
1018
- if (persistent) {
1019
- this.showTitleOverlay();
1020
- this.clearTitleTimeout();
1021
- } else {
1022
- this.titleOverlay.classList.remove('persistent');
1023
- if (this.titleOverlay.classList.contains('show')) {
1024
- this.clearTitleTimeout();
1025
- this.titleTimeout = setTimeout(() => {
1026
- this.hideTitleOverlay();
1027
- }, 3000);
1028
- }
1029
- }
1030
- }
1085
+ initializeElements() {
1086
+ this.progressContainer = this.controls?.querySelector('.progress-container');
1087
+ this.progressFilled = this.controls?.querySelector('.progress-filled');
1088
+ this.progressBuffer = this.controls?.querySelector('.progress-buffer');
1089
+ this.progressHandle = this.controls?.querySelector('.progress-handle');
1090
+ this.seekTooltip = this.controls?.querySelector('.seek-tooltip');
1091
+
1092
+ this.playPauseBtn = this.controls?.querySelector('.play-pause-btn');
1093
+ this.muteBtn = this.controls?.querySelector('.mute-btn');
1094
+ this.fullscreenBtn = this.controls?.querySelector('.fullscreen-btn');
1095
+ this.speedBtn = this.controls?.querySelector('.speed-btn');
1096
+ this.qualityBtn = this.controls?.querySelector('.quality-btn');
1097
+ this.pipBtn = this.controls?.querySelector('.pip-btn');
1098
+ this.subtitlesBtn = this.controls?.querySelector('.subtitles-btn');
1099
+ this.playlistPrevBtn = this.controls?.querySelector('.playlist-prev-btn');
1100
+ this.playlistNextBtn = this.controls?.querySelector('.playlist-next-btn');
1101
+
1102
+ this.playIcon = this.controls?.querySelector('.play-icon');
1103
+ this.pauseIcon = this.controls?.querySelector('.pause-icon');
1104
+ this.volumeIcon = this.controls?.querySelector('.volume-icon');
1105
+ this.muteIcon = this.controls?.querySelector('.mute-icon');
1106
+ this.fullscreenIcon = this.controls?.querySelector('.fullscreen-icon');
1107
+ this.exitFullscreenIcon = this.controls?.querySelector('.exit-fullscreen-icon');
1108
+ this.pipIcon = this.controls?.querySelector('.pip-icon');
1109
+ this.pipExitIcon = this.controls?.querySelector('.pip-exit-icon');
1110
+
1111
+ this.volumeSlider = this.controls?.querySelector('.volume-slider');
1112
+ this.currentTimeEl = this.controls?.querySelector('.current-time');
1113
+ this.durationEl = this.controls?.querySelector('.duration');
1114
+ this.speedMenu = this.controls?.querySelector('.speed-menu');
1115
+ this.qualityMenu = this.controls?.querySelector('.quality-menu');
1116
+ this.subtitlesMenu = this.controls?.querySelector('.subtitles-menu');
1117
+ }
1031
1118
 
1032
- return this;
1033
- }
1119
+ closeAllMenus() {
1120
+
1121
+ const allMenus = this.controls?.querySelectorAll('[class*="-menu"].active');
1122
+ allMenus?.forEach(menu => {
1123
+ menu.classList.remove('active');
1124
+ });
1034
1125
 
1035
- enableTitleOverlay() {
1036
- if (!this.titleOverlay && !this.options.showTitleOverlay) {
1037
- this.options.showTitleOverlay = true;
1038
- this.createTitleOverlay();
1039
- }
1040
- return this;
1041
- }
1126
+
1127
+ const allButtons = this.controls?.querySelectorAll('.control-btn.active');
1128
+ allButtons?.forEach(btn => {
1129
+ btn.classList.remove('active');
1130
+ });
1131
+ }
1042
1132
 
1043
- disableTitleOverlay() {
1044
- if (this.titleOverlay) {
1045
- this.titleOverlay.remove();
1046
- this.titleOverlay = null;
1047
- }
1048
- this.options.showTitleOverlay = false;
1049
- return this;
1050
- }
1133
+ setupMenuToggles() {
1134
+
1135
+ if (this.controls) {
1136
+ this.controls.addEventListener('click', (e) => {
1137
+
1138
+ const button = e.target.closest('.control-btn');
1051
1139
 
1052
- getUniqueId() {
1053
- return Math.random().toString(36).substr(2, 9);
1054
- }
1140
+ if (!button) return;
1141
+
1142
+
1143
+ const buttonClasses = button.className.split(' ');
1144
+ let menuClass = null;
1145
+
1146
+
1147
+ for (const cls of buttonClasses) {
1148
+ if (cls.endsWith('-btn')) {
1149
+ const menuName = cls.replace('-btn', '-menu');
1150
+ const menu = this.controls.querySelector('.' + menuName);
1151
+ if (menu) {
1152
+ menuClass = menuName;
1153
+ break;
1154
+ }
1155
+ }
1156
+ }
1055
1157
 
1056
- initializeElements() {
1057
- this.progressContainer = this.controls?.querySelector('.progress-container');
1058
- this.progressFilled = this.controls?.querySelector('.progress-filled');
1059
- this.progressBuffer = this.controls?.querySelector('.progress-buffer');
1060
- this.progressHandle = this.controls?.querySelector('.progress-handle');
1061
- this.seekTooltip = this.controls?.querySelector('.seek-tooltip');
1158
+ if (!menuClass) return;
1062
1159
 
1063
- this.playPauseBtn = this.controls?.querySelector('.play-pause-btn');
1064
- this.muteBtn = this.controls?.querySelector('.mute-btn');
1065
- this.fullscreenBtn = this.controls?.querySelector('.fullscreen-btn');
1066
- this.speedBtn = this.controls?.querySelector('.speed-btn');
1067
- this.qualityBtn = this.controls?.querySelector('.quality-btn');
1068
- this.pipBtn = this.controls?.querySelector('.pip-btn');
1069
- this.subtitlesBtn = this.controls?.querySelector('.subtitles-btn');
1070
- this.playlistPrevBtn = this.controls?.querySelector('.playlist-prev-btn');
1071
- this.playlistNextBtn = this.controls?.querySelector('.playlist-next-btn');
1160
+ e.stopPropagation();
1072
1161
 
1073
- this.playIcon = this.controls?.querySelector('.play-icon');
1074
- this.pauseIcon = this.controls?.querySelector('.pause-icon');
1075
- this.volumeIcon = this.controls?.querySelector('.volume-icon');
1076
- this.muteIcon = this.controls?.querySelector('.mute-icon');
1077
- this.fullscreenIcon = this.controls?.querySelector('.fullscreen-icon');
1078
- this.exitFullscreenIcon = this.controls?.querySelector('.exit-fullscreen-icon');
1079
- this.pipIcon = this.controls?.querySelector('.pip-icon');
1080
- this.pipExitIcon = this.controls?.querySelector('.pip-exit-icon');
1162
+
1163
+ const menu = this.controls.querySelector('.' + menuClass);
1164
+ const isOpen = menu.classList.contains('active');
1165
+
1166
+
1167
+ this.closeAllMenus();
1081
1168
 
1082
- this.volumeSlider = this.controls?.querySelector('.volume-slider');
1083
- this.currentTimeEl = this.controls?.querySelector('.current-time');
1084
- this.durationEl = this.controls?.querySelector('.duration');
1085
- this.speedMenu = this.controls?.querySelector('.speed-menu');
1086
- this.qualityMenu = this.controls?.querySelector('.quality-menu');
1087
- this.subtitlesMenu = this.controls?.querySelector('.subtitles-menu');
1169
+
1170
+ if (!isOpen) {
1171
+ menu.classList.add('active');
1172
+ button.classList.add('active');
1173
+ }
1174
+ });
1088
1175
  }
1089
1176
 
1090
- updateVolumeSliderVisual() {
1091
- if (!this.video || !this.container) return;
1177
+
1178
+ document.addEventListener('click', (e) => {
1179
+ if (!this.controls?.contains(e.target)) {
1180
+ this.closeAllMenus();
1181
+ }
1182
+ });
1183
+ }
1184
+
1185
+ updateVolumeSliderVisual() {
1186
+ if (!this.video || !this.container) return;
1092
1187
 
1093
- const volume = this.video.muted ? 0 : this.video.volume;
1094
- const percentage = Math.round(volume * 100);
1188
+ const volume = this.video.muted ? 0 : this.video.volume;
1189
+ const percentage = Math.round(volume * 100);
1095
1190
 
1096
- this.container.style.setProperty('--player-volume-fill', percentage + '%');
1191
+ this.container.style.setProperty('--player-volume-fill', percentage + '%');
1097
1192
 
1098
- if (this.volumeSlider) {
1099
- this.volumeSlider.value = percentage;
1100
- }
1193
+ if (this.volumeSlider) {
1194
+ this.volumeSlider.value = percentage;
1101
1195
  }
1196
+ }
1102
1197
 
1103
- createVolumeTooltip() {
1104
- const volumeContainer = this.controls?.querySelector('.volume-container');
1105
- if (!volumeContainer || volumeContainer.querySelector('.volume-tooltip')) {
1106
- return;
1107
- }
1198
+ createVolumeTooltip() {
1199
+ const volumeContainer = this.controls?.querySelector('.volume-container');
1200
+ if (!volumeContainer || volumeContainer.querySelector('.volume-tooltip')) {
1201
+ return;
1202
+ }
1108
1203
 
1109
- const tooltip = document.createElement('div');
1110
- tooltip.className = 'volume-tooltip';
1111
- tooltip.textContent = '50%';
1112
- volumeContainer.appendChild(tooltip);
1204
+ const tooltip = document.createElement('div');
1205
+ tooltip.className = 'volume-tooltip';
1206
+ tooltip.textContent = '50%';
1207
+ volumeContainer.appendChild(tooltip);
1113
1208
 
1114
- this.volumeTooltip = tooltip;
1209
+ this.volumeTooltip = tooltip;
1115
1210
 
1116
- if (this.options.debug) {
1117
- console.log('Dynamic volume tooltip created');
1118
- }
1211
+ if (this.options.debug) {
1212
+ console.log('Dynamic volume tooltip created');
1119
1213
  }
1214
+ }
1120
1215
 
1121
- updateVolumeTooltip() {
1122
- if (!this.volumeTooltip || !this.video) return;
1216
+ updateVolumeTooltip() {
1217
+ if (!this.volumeTooltip || !this.video) return;
1123
1218
 
1124
- const volume = Math.round(this.video.volume * 100);
1125
- this.volumeTooltip.textContent = volume + '%';
1219
+ const volume = Math.round(this.video.volume * 100);
1220
+ this.volumeTooltip.textContent = volume + '%';
1126
1221
 
1127
-
1128
- this.updateVolumeTooltipPosition(this.video.volume);
1222
+
1223
+ this.updateVolumeTooltipPosition(this.video.volume);
1129
1224
 
1130
- if (this.options.debug) {
1131
- console.log('Volume tooltip updated:', volume + '%');
1132
- }
1225
+ if (this.options.debug) {
1226
+ console.log('Volume tooltip updated:', volume + '%');
1133
1227
  }
1228
+ }
1134
1229
 
1135
- updateVolumeTooltipPosition(volumeValue = null) {
1136
- if (!this.volumeTooltip || !this.video) return;
1230
+ updateVolumeTooltipPosition(volumeValue = null) {
1231
+ if (!this.volumeTooltip || !this.video) return;
1137
1232
 
1138
- const volumeSlider = this.controls?.querySelector('.volume-slider');
1139
- if (!volumeSlider) return;
1233
+ const volumeSlider = this.controls?.querySelector('.volume-slider');
1234
+ if (!volumeSlider) return;
1140
1235
 
1141
-
1142
- if (volumeValue === null) {
1143
- volumeValue = this.video.volume;
1144
- }
1236
+
1237
+ if (volumeValue === null) {
1238
+ volumeValue = this.video.volume;
1239
+ }
1145
1240
 
1146
-
1147
- const sliderRect = volumeSlider.getBoundingClientRect();
1148
- const sliderWidth = sliderRect.width;
1241
+
1242
+ const sliderRect = volumeSlider.getBoundingClientRect();
1243
+ const sliderWidth = sliderRect.width;
1149
1244
 
1150
-
1151
- const thumbSize = 14;
1245
+
1246
+ const thumbSize = 14;
1152
1247
 
1153
-
1154
-
1155
- const availableWidth = sliderWidth - thumbSize;
1156
- const thumbCenterPosition = (thumbSize / 2) + (availableWidth * volumeValue);
1248
+
1249
+
1250
+ const availableWidth = sliderWidth - thumbSize;
1251
+ const thumbCenterPosition = (thumbSize / 2) + (availableWidth * volumeValue);
1157
1252
 
1158
-
1159
- const percentage = (thumbCenterPosition / sliderWidth) * 100;
1253
+
1254
+ const percentage = (thumbCenterPosition / sliderWidth) * 100;
1160
1255
 
1161
-
1162
- this.volumeTooltip.style.left = percentage + '%';
1256
+
1257
+ this.volumeTooltip.style.left = percentage + '%';
1163
1258
 
1164
- if (this.options.debug) {
1165
- console.log('Volume tooltip position updated:', {
1166
- volumeValue: volumeValue,
1167
- percentage: percentage + '%',
1168
- thumbCenter: thumbCenterPosition,
1169
- sliderWidth: sliderWidth
1170
- });
1171
- }
1259
+ if (this.options.debug) {
1260
+ console.log('Volume tooltip position updated:', {
1261
+ volumeValue: volumeValue,
1262
+ percentage: percentage + '%',
1263
+ thumbCenter: thumbCenterPosition,
1264
+ sliderWidth: sliderWidth
1265
+ });
1172
1266
  }
1267
+ }
1173
1268
 
1174
- initVolumeTooltip() {
1175
- this.createVolumeTooltip();
1176
- this.setupVolumeTooltipEvents();
1269
+ initVolumeTooltip() {
1270
+ this.createVolumeTooltip();
1177
1271
 
1178
-
1179
- setTimeout(() => {
1180
- if (this.volumeTooltip && this.video) {
1181
- this.updateVolumeTooltipPosition(this.video.volume);
1182
- this.updateVolumeTooltip();
1183
- }
1184
- }, 50);
1185
- }
1272
+
1273
+ setTimeout(() => {
1274
+ if (this.volumeTooltip && this.video) {
1275
+ this.updateVolumeTooltipPosition(this.video.volume);
1276
+ this.updateVolumeTooltip();
1277
+ }
1278
+ }, 50);
1279
+ }
1186
1280
 
1187
- updateVolumeSliderVisualWithTooltip() {
1188
- const volumeSlider = this.controls?.querySelector('.volume-slider');
1189
- if (!volumeSlider || !this.video) return;
1281
+ updateVolumeSliderVisualWithTooltip() {
1282
+ const volumeSlider = this.controls?.querySelector('.volume-slider');
1283
+ if (!volumeSlider || !this.video) return;
1190
1284
 
1191
- const volume = this.video.volume || 0;
1192
- const percentage = Math.round(volume * 100);
1285
+ const volume = this.video.volume || 0;
1286
+ const percentage = Math.round(volume * 100);
1193
1287
 
1194
- volumeSlider.value = volume;
1288
+ volumeSlider.value = volume;
1195
1289
 
1196
-
1197
- const volumeFillPercentage = percentage + '%';
1198
- volumeSlider.style.setProperty('--player-volume-fill', volumeFillPercentage);
1290
+
1291
+ const volumeFillPercentage = percentage + '%';
1292
+ volumeSlider.style.setProperty('--player-volume-fill', volumeFillPercentage);
1199
1293
 
1200
-
1201
- this.updateVolumeTooltip();
1294
+
1295
+ this.updateVolumeTooltip();
1202
1296
 
1203
- if (this.options.debug) {
1204
- console.log('Volume slider aggiornato:', {
1205
- volume: volume,
1206
- percentage: percentage,
1207
- fillPercentage: volumeFillPercentage
1208
- });
1209
- }
1297
+ if (this.options.debug) {
1298
+ console.log('Volume slider aggiornato:', {
1299
+ volume: volume,
1300
+ percentage: percentage,
1301
+ fillPercentage: volumeFillPercentage
1302
+ });
1210
1303
  }
1304
+ }
1211
1305
 
1212
- setVolumeSliderOrientation(orientation) {
1213
- if (!['horizontal', 'vertical'].includes(orientation)) {
1214
- if (this.options.debug) console.warn('Invalid volume slider orientation:', orientation);
1306
+
1307
+ setMobileVolumeSlider(mode) {
1308
+ if (!['show', 'hide'].includes(mode)) {
1309
+ if (this.options.debug) console.warn('Invalid mobile volume slider mode:', mode);
1215
1310
  return this;
1216
1311
  }
1217
1312
 
1218
- this.options.volumeSlider = orientation;
1313
+ this.options.mobileVolumeSlider = mode;
1219
1314
  const volumeContainer = this.controls?.querySelector('.volume-container');
1220
1315
  if (volumeContainer) {
1221
- volumeContainer.setAttribute('data-orientation', orientation);
1316
+
1317
+ volumeContainer.setAttribute('data-mobile-slider', mode);
1318
+ if (this.options.debug) console.log('Mobile volume slider set to:', mode);
1222
1319
  }
1223
-
1224
- if (this.options.debug) console.log('Volume slider orientation set to:', orientation);
1225
1320
  return this;
1226
1321
  }
1227
1322
 
1228
- getVolumeSliderOrientation() {
1229
- return this.options.volumeSlider;
1323
+
1324
+ getMobileVolumeSlider() {
1325
+ return this.options.mobileVolumeSlider;
1230
1326
  }
1231
1327
 
1328
+ initVolumeTooltip() {
1232
1329
 
1233
- initVolumeTooltip() {
1330
+ this.createVolumeTooltip();
1234
1331
 
1235
- this.createVolumeTooltip();
1332
+ setTimeout(() => {
1333
+ this.updateVolumeTooltip();
1334
+ }, 200);
1236
1335
 
1237
-
1238
- this.setupVolumeTooltipEvents();
1336
+ if (this.options.debug) {
1337
+ console.log('Dynamic volume tooltip inizializzation');
1338
+ }
1339
+ }
1239
1340
 
1240
- setTimeout(() => {
1241
- this.updateVolumeTooltip();
1242
- }, 200);
1341
+ setupSeekTooltip() {
1342
+ if (!this.options.showSeekTooltip || !this.progressContainer || !this.seekTooltip) return;
1243
1343
 
1244
- if (this.options.debug) {
1245
- console.log('Dynamic volume tooltip inizializzato');
1344
+ this.progressContainer.addEventListener('mouseenter', () => {
1345
+ if (this.seekTooltip) {
1346
+ this.seekTooltip.classList.add('visible');
1246
1347
  }
1247
- }
1348
+ });
1248
1349
 
1249
- setupSeekTooltip() {
1250
- if (!this.options.showSeekTooltip || !this.progressContainer || !this.seekTooltip) return;
1350
+ this.progressContainer.addEventListener('mouseleave', () => {
1351
+ if (this.seekTooltip) {
1352
+ this.seekTooltip.classList.remove('visible');
1353
+ }
1354
+ });
1251
1355
 
1252
- this.progressContainer.addEventListener('mouseenter', () => {
1253
- if (this.seekTooltip) {
1254
- this.seekTooltip.classList.add('visible');
1255
- }
1256
- });
1356
+ this.progressContainer.addEventListener('mousemove', (e) => {
1357
+ this.updateSeekTooltip(e);
1358
+ });
1359
+ }
1257
1360
 
1258
- this.progressContainer.addEventListener('mouseleave', () => {
1259
- if (this.seekTooltip) {
1260
- this.seekTooltip.classList.remove('visible');
1261
- }
1262
- });
1361
+ updateSeekTooltip(e) {
1362
+ if (!this.seekTooltip || !this.progressContainer || !this.video || !this.video.duration) return;
1263
1363
 
1264
- this.progressContainer.addEventListener('mousemove', (e) => {
1265
- this.updateSeekTooltip(e);
1266
- });
1267
- }
1364
+ const rect = this.progressContainer.getBoundingClientRect();
1365
+ const clickX = e.clientX - rect.left;
1366
+ const percentage = Math.max(0, Math.min(1, clickX / rect.width));
1367
+ const targetTime = percentage * this.video.duration;
1268
1368
 
1269
- updateSeekTooltip(e) {
1270
- if (!this.seekTooltip || !this.progressContainer || !this.video || !this.video.duration) return;
1369
+ this.seekTooltip.textContent = this.formatTime(targetTime);
1271
1370
 
1272
- const rect = this.progressContainer.getBoundingClientRect();
1273
- const clickX = e.clientX - rect.left;
1274
- const percentage = Math.max(0, Math.min(1, clickX / rect.width));
1275
- const targetTime = percentage * this.video.duration;
1371
+ const tooltipRect = this.seekTooltip.getBoundingClientRect();
1372
+ let leftPosition = clickX;
1276
1373
 
1277
- this.seekTooltip.textContent = this.formatTime(targetTime);
1374
+ const tooltipWidth = tooltipRect.width || 50;
1375
+ const containerWidth = rect.width;
1278
1376
 
1279
- const tooltipRect = this.seekTooltip.getBoundingClientRect();
1280
- let leftPosition = clickX;
1377
+ leftPosition = Math.max(tooltipWidth / 2, Math.min(containerWidth - tooltipWidth / 2, clickX));
1281
1378
 
1282
- const tooltipWidth = tooltipRect.width || 50;
1283
- const containerWidth = rect.width;
1379
+ this.seekTooltip.style.left = leftPosition + 'px';
1380
+ }
1284
1381
 
1285
- leftPosition = Math.max(tooltipWidth / 2, Math.min(containerWidth - tooltipWidth / 2, clickX));
1382
+ play() {
1383
+ if (!this.video || this.isChangingQuality) return;
1286
1384
 
1287
- this.seekTooltip.style.left = leftPosition + 'px';
1288
- }
1385
+ this.video.play().catch(err => {
1386
+ if (this.options.debug) console.log('Play failed:', err);
1387
+ });
1289
1388
 
1290
- play() {
1291
- if (!this.video || this.isChangingQuality) return;
1389
+ if (this.playIcon) this.playIcon.classList.add('hidden');
1390
+ if (this.pauseIcon) this.pauseIcon.classList.remove('hidden');
1292
1391
 
1293
- this.video.play().catch(err => {
1294
- if (this.options.debug) console.log('Play failed:', err);
1295
- });
1392
+
1393
+ this.triggerEvent('played', {
1394
+ currentTime: this.getCurrentTime(),
1395
+ duration: this.getDuration()
1396
+ });
1397
+ }
1296
1398
 
1297
- if (this.playIcon) this.playIcon.classList.add('hidden');
1298
- if (this.pauseIcon) this.pauseIcon.classList.remove('hidden');
1399
+ pause() {
1400
+ if (!this.video) return;
1299
1401
 
1300
-
1301
- this.triggerEvent('played', {
1302
- currentTime: this.getCurrentTime(),
1303
- duration: this.getDuration()
1304
- });
1305
- }
1402
+ this.video.pause();
1403
+ if (this.playIcon) this.playIcon.classList.remove('hidden');
1404
+ if (this.pauseIcon) this.pauseIcon.classList.add('hidden');
1306
1405
 
1307
- pause() {
1308
- if (!this.video) return;
1406
+
1407
+ this.triggerEvent('paused', {
1408
+ currentTime: this.getCurrentTime(),
1409
+ duration: this.getDuration()
1410
+ });
1411
+ }
1309
1412
 
1310
- this.video.pause();
1311
- if (this.playIcon) this.playIcon.classList.remove('hidden');
1312
- if (this.pauseIcon) this.pauseIcon.classList.add('hidden');
1413
+ updateVolume(value) {
1414
+ if (!this.video) return;
1313
1415
 
1314
-
1315
- this.triggerEvent('paused', {
1316
- currentTime: this.getCurrentTime(),
1317
- duration: this.getDuration()
1318
- });
1319
- }
1416
+ const previousVolume = this.video.volume;
1417
+ const previousMuted = this.video.muted;
1320
1418
 
1321
- updateVolume(value) {
1322
- if (!this.video) return;
1419
+ this.video.volume = Math.max(0, Math.min(1, value / 100));
1323
1420
 
1324
- const previousVolume = this.video.volume;
1325
- const previousMuted = this.video.muted;
1421
+ if (this.video.volume > 0 && this.video.muted) {
1422
+ this.video.muted = false;
1423
+ }
1326
1424
 
1327
- this.video.volume = Math.max(0, Math.min(1, value / 100));
1328
- if (this.volumeSlider) this.volumeSlider.value = value;
1329
- this.updateMuteButton();
1330
- this.updateVolumeSliderVisual();
1331
- this.initVolumeTooltip();
1425
+ if (this.volumeSlider) this.volumeSlider.value = value;
1426
+ this.updateMuteButton();
1427
+ this.updateVolumeSliderVisual();
1428
+ this.initVolumeTooltip();
1332
1429
 
1333
-
1334
- if (Math.abs(previousVolume - this.video.volume) > 0.01 || previousMuted !== this.video.muted) {
1335
- this.triggerEvent('volumechange', {
1336
- volume: this.getVolume(),
1337
- muted: this.isMuted(),
1338
- previousVolume: previousVolume,
1339
- previousMuted: previousMuted
1340
- });
1341
- }
1430
+
1431
+ if (Math.abs(previousVolume - this.video.volume) > 0.01 || previousMuted !== this.video.muted) {
1432
+ this.triggerEvent('volumechange', {
1433
+ volume: this.getVolume(),
1434
+ muted: this.isMuted(),
1435
+ previousVolume: previousVolume,
1436
+ previousMuted: previousMuted
1437
+ });
1342
1438
  }
1439
+ }
1343
1440
 
1344
- changeVolume(delta) {
1345
- if (!this.video) return;
1441
+ changeVolume(delta) {
1442
+ if (!this.video) return;
1346
1443
 
1347
- const newVolume = Math.max(0, Math.min(1, this.video.volume + delta));
1348
- this.updateVolume(newVolume * 100);
1349
- this.updateVolumeSliderVisual();
1350
- this.initVolumeTooltip();
1351
- }
1444
+ const newVolume = Math.max(0, Math.min(1, this.video.volume + delta));
1445
+ this.updateVolume(newVolume * 100);
1446
+ this.updateVolumeSliderVisual();
1447
+ this.initVolumeTooltip();
1448
+ }
1352
1449
 
1353
- updateProgress() {
1354
- if (!this.video || !this.progressFilled || !this.progressHandle || this.isUserSeeking) return;
1450
+ updateProgress() {
1451
+ if (!this.video || !this.progressFilled || !this.progressHandle || this.isUserSeeking) return;
1355
1452
 
1356
- if (this.video.duration && !isNaN(this.video.duration)) {
1357
- const progress = (this.video.currentTime / this.video.duration) * 100;
1358
- this.progressFilled.style.width = progress + '%';
1359
- this.progressHandle.style.left = progress + '%';
1360
- }
1453
+ if (this.video.duration && !isNaN(this.video.duration)) {
1454
+ const progress = (this.video.currentTime / this.video.duration) * 100;
1455
+ this.progressFilled.style.width = progress + '%';
1456
+ this.progressHandle.style.left = progress + '%';
1457
+ }
1361
1458
 
1362
- this.updateTimeDisplay();
1459
+ this.updateTimeDisplay();
1363
1460
 
1364
-
1365
- if (!this.lastTimeUpdate || Date.now() - this.lastTimeUpdate > 250) {
1366
- this.triggerEvent('timeupdate', {
1367
- currentTime: this.getCurrentTime(),
1368
- duration: this.getDuration(),
1369
- progress: (this.getCurrentTime() / this.getDuration()) * 100 || 0
1370
- });
1371
- this.lastTimeUpdate = Date.now();
1372
- }
1461
+
1462
+ if (!this.lastTimeUpdate || Date.now() - this.lastTimeUpdate > 250) {
1463
+ this.triggerEvent('timeupdate', {
1464
+ currentTime: this.getCurrentTime(),
1465
+ duration: this.getDuration(),
1466
+ progress: (this.getCurrentTime() / this.getDuration()) * 100 || 0
1467
+ });
1468
+ this.lastTimeUpdate = Date.now();
1373
1469
  }
1470
+ }
1374
1471
 
1375
- updateBuffer() {
1376
- if (!this.video || !this.progressBuffer) return;
1472
+ updateBuffer() {
1473
+ if (!this.video || !this.progressBuffer) return;
1377
1474
 
1378
- try {
1379
- if (this.video.buffered && this.video.buffered.length > 0 && this.video.duration) {
1380
- const buffered = (this.video.buffered.end(0) / this.video.duration) * 100;
1381
- this.progressBuffer.style.width = buffered + '%';
1382
- }
1383
- } catch (error) {
1384
- if (this.options.debug) console.log('Buffer update error (non-critical):', error);
1475
+ try {
1476
+ if (this.video.buffered && this.video.buffered.length > 0 && this.video.duration) {
1477
+ const buffered = (this.video.buffered.end(0) / this.video.duration) * 100;
1478
+ this.progressBuffer.style.width = buffered + '%';
1385
1479
  }
1480
+ } catch (error) {
1481
+ if (this.options.debug) console.log('Buffer update error (non-critical):', error);
1386
1482
  }
1483
+ }
1387
1484
 
1388
- startSeeking(e) {
1389
- if (this.isChangingQuality) return;
1485
+ startSeeking(e) {
1486
+ if (this.isChangingQuality) return;
1390
1487
 
1391
- this.isUserSeeking = true;
1392
- this.seek(e);
1393
- e.preventDefault();
1488
+ this.isUserSeeking = true;
1489
+ this.seek(e);
1490
+ e.preventDefault();
1394
1491
 
1395
-
1396
- if (this.options.autoHide && this.autoHideInitialized) {
1397
- this.showControlsNow();
1398
- this.resetAutoHideTimer();
1399
- }
1492
+
1493
+ if (this.options.autoHide && this.autoHideInitialized) {
1494
+ this.showControlsNow();
1495
+ this.resetAutoHideTimer();
1400
1496
  }
1497
+ }
1401
1498
 
1402
- continueSeeking(e) {
1403
- if (this.isUserSeeking && !this.isChangingQuality) {
1404
- this.seek(e);
1405
- }
1499
+ continueSeeking(e) {
1500
+ if (this.isUserSeeking && !this.isChangingQuality) {
1501
+ this.seek(e);
1406
1502
  }
1503
+ }
1407
1504
 
1408
- endSeeking() {
1409
- this.isUserSeeking = false;
1410
- }
1505
+ endSeeking() {
1506
+ this.isUserSeeking = false;
1507
+ }
1411
1508
 
1412
- seek(e) {
1413
- if (!this.video || !this.progressContainer || !this.progressFilled || !this.progressHandle || this.isChangingQuality) return;
1509
+ seek(e) {
1510
+ if (!this.video || !this.progressContainer || !this.progressFilled || !this.progressHandle || this.isChangingQuality) return;
1414
1511
 
1415
- const rect = this.progressContainer.getBoundingClientRect();
1416
- const clickX = e.clientX - rect.left;
1417
- const percentage = Math.max(0, Math.min(1, clickX / rect.width));
1512
+ const rect = this.progressContainer.getBoundingClientRect();
1513
+ const clickX = e.clientX - rect.left;
1514
+ const percentage = Math.max(0, Math.min(1, clickX / rect.width));
1418
1515
 
1419
- if (this.video.duration && !isNaN(this.video.duration)) {
1420
- this.video.currentTime = percentage * this.video.duration;
1421
- const progress = percentage * 100;
1422
- this.progressFilled.style.width = progress + '%';
1423
- this.progressHandle.style.left = progress + '%';
1424
- }
1516
+ if (this.video.duration && !isNaN(this.video.duration)) {
1517
+ this.video.currentTime = percentage * this.video.duration;
1518
+ const progress = percentage * 100;
1519
+ this.progressFilled.style.width = progress + '%';
1520
+ this.progressHandle.style.left = progress + '%';
1425
1521
  }
1522
+ }
1426
1523
 
1427
- updateDuration() {
1428
- if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
1429
- this.durationEl.textContent = this.formatTime(this.video.duration);
1430
- }
1524
+ updateDuration() {
1525
+ if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
1526
+ this.durationEl.textContent = this.formatTime(this.video.duration);
1431
1527
  }
1528
+ }
1432
1529
 
1433
- changeSpeed(e) {
1434
- if (!this.video || !e.target.classList.contains('speed-option') || this.isChangingQuality) return;
1435
-
1436
- const speed = parseFloat(e.target.getAttribute('data-speed'));
1437
- if (speed && speed > 0) {
1438
- this.video.playbackRate = speed;
1439
- if (this.speedBtn) this.speedBtn.textContent = speed + 'x';
1530
+ changeSpeed(e) {
1531
+ if (!this.video || !e.target.classList.contains('speed-option') || this.isChangingQuality) return;
1440
1532
 
1441
- if (this.speedMenu) {
1442
- this.speedMenu.querySelectorAll('.speed-option').forEach(option => {
1443
- option.classList.remove('active');
1444
- });
1445
- e.target.classList.add('active');
1446
- }
1533
+ const speed = parseFloat(e.target.getAttribute('data-speed'));
1534
+ if (speed && speed > 0) {
1535
+ this.video.playbackRate = speed;
1536
+ if (this.speedBtn) this.speedBtn.textContent = speed + 'x';
1447
1537
 
1448
-
1449
- const previousSpeed = this.video.playbackRate;
1450
- this.triggerEvent('speedchange', {
1451
- speed: speed,
1452
- previousSpeed: previousSpeed
1538
+ if (this.speedMenu) {
1539
+ this.speedMenu.querySelectorAll('.speed-option').forEach(option => {
1540
+ option.classList.remove('active');
1453
1541
  });
1542
+ e.target.classList.add('active');
1454
1543
  }
1544
+
1545
+
1546
+ const previousSpeed = this.video.playbackRate;
1547
+ this.triggerEvent('speedchange', {
1548
+ speed: speed,
1549
+ previousSpeed: previousSpeed
1550
+ });
1455
1551
  }
1552
+ }
1456
1553
 
1457
1554
  onVideoEnded() {
1458
1555
  if (this.playIcon) this.playIcon.classList.remove('hidden');
@@ -1478,217 +1575,267 @@ onVideoEnded() {
1478
1575
  });
1479
1576
  }
1480
1577
 
1481
- getCurrentTime() { return this.video ? this.video.currentTime || 0 : 0; }
1482
1578
 
1483
- setCurrentTime(time) { if (this.video && typeof time === 'number' && time >= 0 && !this.isChangingQuality) { this.video.currentTime = time; } }
1484
-
1485
- getDuration() { return this.video && this.video.duration ? this.video.duration : 0; }
1579
+ onVideoError(error) {
1580
+ if (this.options.debug) {
1581
+ console.error('Video loading error detected:', {
1582
+ error: error,
1583
+ code: this.video?.error?.code,
1584
+ message: this.video?.error?.message,
1585
+ src: this.video?.currentSrc || this.video?.src
1586
+ });
1587
+ }
1486
1588
 
1487
- getVolume() { return this.video ? this.video.volume || 0 : 0; }
1589
+
1590
+ this.hideLoading();
1591
+ if (this.initialLoading) {
1592
+ this.initialLoading.style.display = 'none';
1593
+ }
1488
1594
 
1489
- setVolume(volume) {
1490
- if (typeof volume === 'number' && volume >= 0 && volume <= 1) {
1491
- this.updateVolume(volume * 100);
1492
- }
1595
+
1596
+ if (this.video?.classList) {
1597
+ this.video.classList.remove('quality-changing');
1493
1598
  }
1494
1599
 
1495
- isPaused() { return this.video ? this.video.paused : true; }
1600
+
1601
+ this.isChangingQuality = false;
1496
1602
 
1497
- isMuted() { return this.video ? this.video.muted : false; }
1603
+
1604
+ this.showControlsNow();
1498
1605
 
1499
- setMuted(muted) {
1500
- if (this.video && typeof muted === 'boolean') {
1501
- this.video.muted = muted;
1502
- this.updateMuteButton();
1503
- this.updateVolumeSliderVisual();
1504
- this.initVolumeTooltip();
1505
- }
1606
+
1607
+ if (this.options.showPosterOnEnd && this.posterOverlay) {
1608
+ this.showPoster();
1506
1609
  }
1507
1610
 
1508
- getPlaybackRate() { return this.video ? this.video.playbackRate || 1 : 1; }
1611
+
1612
+
1613
+ this.triggerEvent('ended', {
1614
+ currentTime: this.getCurrentTime(),
1615
+ duration: this.getDuration(),
1616
+ error: true,
1617
+ errorCode: this.video?.error?.code,
1618
+ errorMessage: this.video?.error?.message,
1619
+ playlistInfo: this.getPlaylistInfo()
1620
+ });
1509
1621
 
1510
- setPlaybackRate(rate) { if (this.video && typeof rate === 'number' && rate > 0 && !this.isChangingQuality) { this.video.playbackRate = rate; if (this.speedBtn) this.speedBtn.textContent = rate + 'x'; } }
1622
+ if (this.options.debug) {
1623
+ console.log('Video error handled - triggered ended event');
1624
+ }
1625
+ }
1511
1626
 
1512
- isPictureInPictureActive() { return document.pictureInPictureElement === this.video; }
1513
1627
 
1514
- getCurrentLanguage() {
1515
- return this.isI18nAvailable() ?
1516
- VideoPlayerTranslations.getCurrentLanguage() : 'en';
1628
+ getCurrentTime() { return this.video ? this.video.currentTime || 0 : 0; }
1629
+
1630
+ setCurrentTime(time) { if (this.video && typeof time === 'number' && time >= 0 && !this.isChangingQuality) { this.video.currentTime = time; } }
1631
+
1632
+ getDuration() { return this.video && this.video.duration ? this.video.duration : 0; }
1633
+
1634
+ getVolume() { return this.video ? this.video.volume || 0 : 0; }
1635
+
1636
+ setVolume(volume) {
1637
+ if (typeof volume === 'number' && volume >= 0 && volume <= 1) {
1638
+ this.updateVolume(volume * 100);
1517
1639
  }
1640
+ }
1518
1641
 
1519
- getSupportedLanguages() {
1520
- return this.isI18nAvailable() ?
1521
- VideoPlayerTranslations.getSupportedLanguages() : ['en'];
1642
+ isPaused() { return this.video ? this.video.paused : true; }
1643
+
1644
+ isMuted() { return this.video ? this.video.muted : false; }
1645
+
1646
+ setMuted(muted) {
1647
+ if (this.video && typeof muted === 'boolean') {
1648
+ this.video.muted = muted;
1649
+ this.updateMuteButton();
1650
+ this.updateVolumeSliderVisual();
1651
+ this.initVolumeTooltip();
1522
1652
  }
1653
+ }
1523
1654
 
1524
- createBrandLogo() {
1525
- if (!this.options.brandLogoEnabled || !this.options.brandLogoUrl) return;
1655
+ getPlaybackRate() { return this.video ? this.video.playbackRate || 1 : 1; }
1526
1656
 
1527
- const controlsRight = this.controls?.querySelector('.controls-right');
1528
- if (!controlsRight) return;
1657
+ setPlaybackRate(rate) { if (this.video && typeof rate === 'number' && rate > 0 && !this.isChangingQuality) { this.video.playbackRate = rate; if (this.speedBtn) this.speedBtn.textContent = rate + 'x'; } }
1529
1658
 
1530
-
1531
- const logo = document.createElement('img');
1532
- logo.className = 'brand-logo';
1533
- logo.src = this.options.brandLogoUrl;
1534
- logo.alt = this.t('brand_logo');
1659
+ isPictureInPictureActive() { return document.pictureInPictureElement === this.video; }
1535
1660
 
1536
-
1537
- logo.onerror = () => {
1538
- if (this.options.debug) console.warn('Brand logo failed to load:', this.options.brandLogoUrl);
1539
- logo.style.display = 'none';
1540
- };
1661
+ getCurrentLanguage() {
1662
+ return this.isI18nAvailable() ?
1663
+ VideoPlayerTranslations.getCurrentLanguage() : 'en';
1664
+ }
1541
1665
 
1542
- logo.onload = () => {
1543
- if (this.options.debug) console.log('Brand logo loaded successfully');
1544
- };
1666
+ getSupportedLanguages() {
1667
+ return this.isI18nAvailable() ?
1668
+ VideoPlayerTranslations.getSupportedLanguages() : ['en'];
1669
+ }
1545
1670
 
1546
-
1547
- if (this.options.brandLogoLinkUrl) {
1548
- logo.style.cursor = 'pointer';
1549
- logo.addEventListener('click', (e) => {
1550
- e.stopPropagation();
1551
- window.open(this.options.brandLogoLinkUrl, '_blank', 'noopener,noreferrer');
1552
- if (this.options.debug) console.log('Brand logo clicked, opening:', this.options.brandLogoLinkUrl);
1553
- });
1554
- } else {
1555
- logo.style.cursor = 'default';
1556
- }
1671
+ createBrandLogo() {
1672
+ if (!this.options.brandLogoEnabled || !this.options.brandLogoUrl) return;
1673
+
1674
+ const controlsRight = this.controls?.querySelector('.controls-right');
1675
+ if (!controlsRight) return;
1676
+
1677
+
1678
+ const logo = document.createElement('img');
1679
+ logo.className = 'brand-logo';
1680
+ logo.src = this.options.brandLogoUrl;
1681
+ logo.alt = this.t('brand_logo');
1682
+
1683
+
1684
+ logo.onerror = () => {
1685
+ if (this.options.debug) console.warn('Brand logo failed to load:', this.options.brandLogoUrl);
1686
+ logo.style.display = 'none';
1687
+ };
1557
1688
 
1558
-
1559
- controlsRight.insertBefore(logo, controlsRight.firstChild);
1689
+ logo.onload = () => {
1690
+ if (this.options.debug) console.log('Brand logo loaded successfully');
1691
+ };
1560
1692
 
1561
- if (this.options.debug) {
1562
- if (this.options.brandLogoLinkUrl) {
1563
- console.log('Brand logo with click handler created for:', this.options.brandLogoLinkUrl);
1564
- } else {
1565
- console.log('Brand logo created (no link)');
1566
- }
1567
- }
1693
+
1694
+ if (this.options.brandLogoLinkUrl) {
1695
+ logo.style.cursor = 'pointer';
1696
+ logo.addEventListener('click', (e) => {
1697
+ e.stopPropagation();
1698
+ window.open(this.options.brandLogoLinkUrl, '_blank', 'noopener,noreferrer');
1699
+ if (this.options.debug) console.log('Brand logo clicked, opening:', this.options.brandLogoLinkUrl);
1700
+ });
1701
+ } else {
1702
+ logo.style.cursor = 'default';
1568
1703
  }
1569
1704
 
1570
- setBrandLogo(enabled, url = '', linkUrl = '') {
1571
- this.options.brandLogoEnabled = enabled;
1572
- if (url) {
1573
- this.options.brandLogoUrl = url;
1574
- }
1575
- if (linkUrl !== '') {
1576
- this.options.brandLogoLinkUrl = linkUrl;
1577
- }
1705
+
1706
+ controlsRight.insertBefore(logo, controlsRight.firstChild);
1578
1707
 
1579
-
1580
- const existingLogo = this.controls?.querySelector('.brand-logo');
1581
- if (existingLogo) {
1582
- existingLogo.remove();
1708
+ if (this.options.debug) {
1709
+ if (this.options.brandLogoLinkUrl) {
1710
+ console.log('Brand logo with click handler created for:', this.options.brandLogoLinkUrl);
1711
+ } else {
1712
+ console.log('Brand logo created (no link)');
1583
1713
  }
1714
+ }
1715
+ }
1584
1716
 
1585
-
1586
- if (enabled && this.options.brandLogoUrl) {
1587
- this.createBrandLogo();
1588
- }
1717
+ setBrandLogo(enabled, url = '', linkUrl = '') {
1718
+ this.options.brandLogoEnabled = enabled;
1719
+ if (url) {
1720
+ this.options.brandLogoUrl = url;
1721
+ }
1722
+ if (linkUrl !== '') {
1723
+ this.options.brandLogoLinkUrl = linkUrl;
1724
+ }
1589
1725
 
1590
- return this;
1726
+
1727
+ const existingLogo = this.controls?.querySelector('.brand-logo');
1728
+ if (existingLogo) {
1729
+ existingLogo.remove();
1591
1730
  }
1592
1731
 
1593
- getBrandLogoSettings() {
1594
- return {
1595
- enabled: this.options.brandLogoEnabled,
1596
- url: this.options.brandLogoUrl,
1597
- linkUrl: this.options.brandLogoLinkUrl
1598
- };
1732
+
1733
+ if (enabled && this.options.brandLogoUrl) {
1734
+ this.createBrandLogo();
1599
1735
  }
1600
1736
 
1601
- switchToVideo(newVideoElement, shouldPlay = false) {
1602
- if (!newVideoElement) {
1603
- if (this.options.debug) console.error('🎵 New video element not found');
1604
- return false;
1605
- }
1737
+ return this;
1738
+ }
1606
1739
 
1607
-
1608
- this.video.pause();
1740
+ getBrandLogoSettings() {
1741
+ return {
1742
+ enabled: this.options.brandLogoEnabled,
1743
+ url: this.options.brandLogoUrl,
1744
+ linkUrl: this.options.brandLogoLinkUrl
1745
+ };
1746
+ }
1609
1747
 
1610
-
1611
- const newSources = Array.from(newVideoElement.querySelectorAll('source')).map(source => ({
1612
- src: source.src,
1613
- quality: source.getAttribute('data-quality') || 'auto',
1614
- type: source.type || 'video/mp4'
1615
- }));
1748
+ switchToVideo(newVideoElement, shouldPlay = false) {
1749
+ if (!newVideoElement) {
1750
+ if (this.options.debug) console.error('🎵 New video element not found');
1751
+ return false;
1752
+ }
1616
1753
 
1617
- if (newSources.length === 0) {
1618
- if (this.options.debug) console.error('🎵 New video has no sources');
1619
- return false;
1620
- }
1754
+
1755
+ this.video.pause();
1621
1756
 
1622
-
1623
- if (this.options.adaptiveStreaming && newSources.length > 0) {
1624
- const firstSource = newSources[0];
1625
- if (this.detectStreamType(firstSource.src)) {
1626
-
1627
- this.initializeAdaptiveStreaming(firstSource.src).then((initialized) => {
1628
- if (initialized && shouldPlay) {
1629
- const playPromise = this.video.play();
1630
- if (playPromise) {
1631
- playPromise.catch(error => {
1632
- if (this.options.debug) console.log('Autoplay prevented:', error);
1633
- });
1634
- }
1757
+
1758
+ const newSources = Array.from(newVideoElement.querySelectorAll('source')).map(source => ({
1759
+ src: source.src,
1760
+ quality: source.getAttribute('data-quality') || 'auto',
1761
+ type: source.type || 'video/mp4'
1762
+ }));
1763
+
1764
+ if (newSources.length === 0) {
1765
+ if (this.options.debug) console.error('🎵 New video has no sources');
1766
+ return false;
1767
+ }
1768
+
1769
+
1770
+ if (this.options.adaptiveStreaming && newSources.length > 0) {
1771
+ const firstSource = newSources[0];
1772
+ if (this.detectStreamType(firstSource.src)) {
1773
+
1774
+ this.initializeAdaptiveStreaming(firstSource.src).then((initialized) => {
1775
+ if (initialized && shouldPlay) {
1776
+ const playPromise = this.video.play();
1777
+ if (playPromise) {
1778
+ playPromise.catch(error => {
1779
+ if (this.options.debug) console.log('Autoplay prevented:', error);
1780
+ });
1635
1781
  }
1636
- });
1637
- return true;
1638
- }
1782
+ }
1783
+ });
1784
+ return true;
1639
1785
  }
1786
+ }
1640
1787
 
1641
-
1642
- this.video.innerHTML = '';
1643
- newSources.forEach(source => {
1644
- const sourceEl = document.createElement('source');
1645
- sourceEl.src = source.src;
1646
- sourceEl.type = source.type;
1647
- sourceEl.setAttribute('data-quality', source.quality);
1648
- this.video.appendChild(sourceEl);
1649
- });
1788
+
1789
+ this.video.innerHTML = '';
1790
+ newSources.forEach(source => {
1791
+ const sourceEl = document.createElement('source');
1792
+ sourceEl.src = source.src;
1793
+ sourceEl.type = source.type;
1794
+ sourceEl.setAttribute('data-quality', source.quality);
1795
+ this.video.appendChild(sourceEl);
1796
+ });
1650
1797
 
1651
-
1652
- const newTracks = Array.from(newVideoElement.querySelectorAll('track'));
1653
- newTracks.forEach(track => {
1654
- const trackEl = document.createElement('track');
1655
- trackEl.kind = track.kind;
1656
- trackEl.src = track.src;
1657
- trackEl.srclang = track.srclang;
1658
- trackEl.label = track.label;
1659
- if (track.default) trackEl.default = true;
1660
- this.video.appendChild(trackEl);
1661
- });
1798
+
1799
+ const newTracks = Array.from(newVideoElement.querySelectorAll('track'));
1800
+ newTracks.forEach(track => {
1801
+ const trackEl = document.createElement('track');
1802
+ trackEl.kind = track.kind;
1803
+ trackEl.src = track.src;
1804
+ trackEl.srclang = track.srclang;
1805
+ trackEl.label = track.label;
1806
+ if (track.default) trackEl.default = true;
1807
+ this.video.appendChild(trackEl);
1808
+ });
1662
1809
 
1663
-
1664
- const newTitle = newVideoElement.getAttribute('data-video-title');
1665
- if (newTitle && this.options.showTitleOverlay) {
1666
- this.options.videoTitle = newTitle;
1667
- if (this.titleText) {
1668
- this.titleText.textContent = newTitle;
1669
- }
1810
+
1811
+ const newTitle = newVideoElement.getAttribute('data-video-title');
1812
+ if (newTitle && this.options.showTitleOverlay) {
1813
+ this.options.videoTitle = newTitle;
1814
+ if (this.titleText) {
1815
+ this.titleText.textContent = newTitle;
1670
1816
  }
1817
+ }
1671
1818
 
1672
-
1673
- this.video.load();
1819
+
1820
+ this.video.load();
1674
1821
 
1675
-
1676
- this.collectVideoQualities();
1677
- this.updateQualityMenu();
1822
+
1823
+ this.collectVideoQualities();
1824
+ this.updateQualityMenu();
1678
1825
 
1679
-
1680
- if (shouldPlay) {
1681
- const playPromise = this.video.play();
1682
- if (playPromise) {
1683
- playPromise.catch(error => {
1684
- if (this.options.debug) console.log('🎵 Autoplay prevented:', error);
1685
- });
1686
- }
1826
+
1827
+ if (shouldPlay) {
1828
+ const playPromise = this.video.play();
1829
+ if (playPromise) {
1830
+ playPromise.catch(error => {
1831
+ if (this.options.debug) console.log('🎵 Autoplay prevented:', error);
1832
+ });
1687
1833
  }
1688
-
1689
- return true;
1690
1834
  }
1691
1835
 
1836
+ return true;
1837
+ }
1838
+
1692
1839
 
1693
1840
  initializePoster() {
1694
1841
  if (!this.video) {
@@ -1878,59 +2025,94 @@ isPosterVisible() {
1878
2025
  }
1879
2026
 
1880
2027
 
1881
- loadScript(src) {
1882
- return new Promise((resolve, reject) => {
1883
- if (document.querySelector(`script[src="${src}"]`)) {
1884
- resolve();
1885
- return;
1886
- }
2028
+ loadScript(src) {
2029
+ return new Promise((resolve, reject) => {
2030
+ if (document.querySelector(`script[src="${src}"]`)) {
2031
+ resolve();
2032
+ return;
2033
+ }
2034
+
2035
+ const script = document.createElement('script');
2036
+ script.src = src;
2037
+ script.onload = resolve;
2038
+ script.onerror = reject;
2039
+ document.head.appendChild(script);
2040
+ });
2041
+ }
2042
+
2043
+
2044
+ setSeekHandleShape(shape) {
2045
+ const validShapes = ['none', 'circle', 'square', 'diamond', 'arrow', 'triangle', 'heart', 'star'];
2046
+
2047
+ if (!validShapes.includes(shape)) {
2048
+ if (this.options.debug) console.warn('Invalid seek handle shape:', shape);
2049
+ return this;
2050
+ }
2051
+
2052
+ this.options.seekHandleShape = shape;
1887
2053
 
1888
- const script = document.createElement('script');
1889
- script.src = src;
1890
- script.onload = resolve;
1891
- script.onerror = reject;
1892
- document.head.appendChild(script);
2054
+
2055
+ if (this.progressHandle) {
2056
+
2057
+ validShapes.forEach(s => {
2058
+ this.progressHandle.classList.remove(`progress-handle-${s}`);
1893
2059
  });
2060
+
2061
+ this.progressHandle.classList.add(`progress-handle-${shape}`);
1894
2062
  }
1895
2063
 
1896
- dispose() {
1897
- if (this.qualityMonitorInterval) {
1898
- clearInterval(this.qualityMonitorInterval);
1899
- this.qualityMonitorInterval = null;
1900
- }
2064
+ if (this.options.debug) console.log('Seek handle shape changed to:', shape);
2065
+ return this;
2066
+ }
1901
2067
 
1902
- if (this.autoHideTimer) {
1903
- clearTimeout(this.autoHideTimer);
1904
- this.autoHideTimer = null;
1905
- }
1906
2068
 
1907
- this.cleanupQualityChange();
1908
- this.clearControlsTimeout();
1909
- this.clearTitleTimeout();
2069
+ getSeekHandleShape() {
2070
+ return this.options.seekHandleShape;
2071
+ }
1910
2072
 
1911
-
1912
- this.destroyAdaptivePlayer();
1913
2073
 
1914
- if (this.controls) {
1915
- this.controls.remove();
1916
- }
1917
- if (this.loadingOverlay) {
1918
- this.loadingOverlay.remove();
1919
- }
1920
- if (this.titleOverlay) {
1921
- this.titleOverlay.remove();
1922
- }
1923
- if (this.initialLoading) {
1924
- this.initialLoading.remove();
1925
- }
2074
+ getAvailableSeekHandleShapes() {
2075
+ return ['none', 'circle', 'square', 'diamond', 'arrow', 'triangle', 'heart', 'star'];
2076
+ }
1926
2077
 
1927
- if (this.video) {
1928
- this.video.classList.remove('video-player');
1929
- this.video.controls = true;
1930
- this.video.style.visibility = '';
1931
- this.video.style.opacity = '';
1932
- this.video.style.pointerEvents = '';
1933
- }
2078
+ dispose() {
2079
+ if (this.qualityMonitorInterval) {
2080
+ clearInterval(this.qualityMonitorInterval);
2081
+ this.qualityMonitorInterval = null;
2082
+ }
2083
+
2084
+ if (this.autoHideTimer) {
2085
+ clearTimeout(this.autoHideTimer);
2086
+ this.autoHideTimer = null;
2087
+ }
2088
+
2089
+ this.cleanupQualityChange();
2090
+ this.clearControlsTimeout();
2091
+ this.clearTitleTimeout();
2092
+
2093
+
2094
+ this.destroyAdaptivePlayer();
2095
+
2096
+ if (this.controls) {
2097
+ this.controls.remove();
2098
+ }
2099
+ if (this.loadingOverlay) {
2100
+ this.loadingOverlay.remove();
2101
+ }
2102
+ if (this.titleOverlay) {
2103
+ this.titleOverlay.remove();
2104
+ }
2105
+ if (this.initialLoading) {
2106
+ this.initialLoading.remove();
2107
+ }
2108
+
2109
+ if (this.video) {
2110
+ this.video.classList.remove('video-player');
2111
+ this.video.controls = true;
2112
+ this.video.style.visibility = '';
2113
+ this.video.style.opacity = '';
2114
+ this.video.style.pointerEvents = '';
2115
+ }
1934
2116
  if (this.chapterMarkersContainer) {
1935
2117
  this.chapterMarkersContainer.remove();
1936
2118
  }
@@ -1942,15 +2124,15 @@ isPosterVisible() {
1942
2124
  }
1943
2125
  this.disposeAllPlugins();
1944
2126
 
1945
- }
2127
+ }
2128
+
2129
+
2130
+
2131
+
1946
2132
 
1947
-
1948
2133
 
1949
-
1950
2134
 
1951
-
1952
2135
 
1953
-
1954
2136
 
1955
2137
  addEventListener(eventType, callback) {
1956
2138
  if (typeof callback !== 'function') {
@@ -2333,6 +2515,16 @@ initAutoHide() {
2333
2515
  this.controls.addEventListener('mouseleave', (e) => {
2334
2516
  if (this.autoHideDebug) {
2335
2517
  if (this.options.debug) console.log('Mouse EXITS controls - restart timer');
2518
+
2519
+
2520
+ this.container.addEventListener('touchstart', () => {
2521
+ this.showControlsNow();
2522
+ this.resetAutoHideTimer();
2523
+ });
2524
+
2525
+ this.container.addEventListener('touchend', () => {
2526
+ this.resetAutoHideTimer();
2527
+ });
2336
2528
  }
2337
2529
  this.onMouseLeaveControls(e);
2338
2530
  });
@@ -2376,7 +2568,8 @@ resetAutoHideTimer() {
2376
2568
  this.autoHideTimer = null;
2377
2569
  }
2378
2570
 
2379
- if (this.mouseOverControls) {
2571
+ const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
2572
+ if (this.mouseOverControls && !isTouchDevice) {
2380
2573
  if (this.autoHideDebug) {
2381
2574
  if (this.options.debug) console.log('Not starting timer - mouse on controls');
2382
2575
  }
@@ -2422,7 +2615,8 @@ showControlsNow() {
2422
2615
 
2423
2616
  hideControlsNow() {
2424
2617
 
2425
- if (this.mouseOverControls) {
2618
+ const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
2619
+ if (this.mouseOverControls && !isTouchDevice) {
2426
2620
  if (this.autoHideDebug && this.options.debug) console.log('⏸️ Not hiding - mouse still over controls');
2427
2621
  return;
2428
2622
  }
@@ -2615,7 +2809,7 @@ createControls() {
2615
2809
  <div class="progress-bar">
2616
2810
  <div class="progress-buffer"></div>
2617
2811
  <div class="progress-filled"></div>
2618
- <div class="progress-handle"></div>
2812
+ <div class="progress-handle progress-handle-${this.options.seekHandleShape}"></div>
2619
2813
  </div>
2620
2814
  ${this.options.showSeekTooltip ? '<div class="seek-tooltip">0:00</div>' : ''}
2621
2815
  </div>
@@ -2632,7 +2826,7 @@ createControls() {
2632
2826
  <span class="icon mute-icon hidden">🔇</span>
2633
2827
  </button>
2634
2828
 
2635
- <div class="volume-container" data-orientation="${this.options.volumeSlider}">
2829
+ <div class="volume-container" data-mobile-slider="${this.options.volumeSlider}">
2636
2830
  <input type="range" class="volume-slider" min="0" max="100" value="100" data-tooltip="volume">
2637
2831
  </div>
2638
2832
 
@@ -3278,140 +3472,160 @@ optimizeButtonsForSmallHeight() {
3278
3472
 
3279
3473
 
3280
3474
  initializeQualityMonitoring() {
3281
- this.qualityMonitorInterval = setInterval(() => {
3475
+ this.qualityMonitorInterval = setInterval(() => {
3476
+ if (!this.isChangingQuality) {
3477
+ this.updateCurrentPlayingQuality();
3478
+ }
3479
+ }, 3000);
3480
+
3481
+ if (this.video) {
3482
+ this.video.addEventListener('loadedmetadata', () => {
3483
+ setTimeout(() => {
3484
+ if (!this.isChangingQuality) {
3485
+ this.updateCurrentPlayingQuality();
3486
+ }
3487
+ }, 100);
3488
+ });
3489
+
3490
+ this.video.addEventListener('resize', () => {
3282
3491
  if (!this.isChangingQuality) {
3283
3492
  this.updateCurrentPlayingQuality();
3284
3493
  }
3285
- }, 3000);
3286
-
3287
- if (this.video) {
3288
- this.video.addEventListener('loadedmetadata', () => {
3289
- setTimeout(() => {
3290
- if (!this.isChangingQuality) {
3291
- this.updateCurrentPlayingQuality();
3292
- }
3293
- }, 100);
3294
- });
3494
+ });
3295
3495
 
3296
- this.video.addEventListener('resize', () => {
3496
+ this.video.addEventListener('loadeddata', () => {
3497
+ setTimeout(() => {
3297
3498
  if (!this.isChangingQuality) {
3298
3499
  this.updateCurrentPlayingQuality();
3299
3500
  }
3300
- });
3301
-
3302
- this.video.addEventListener('loadeddata', () => {
3303
- setTimeout(() => {
3304
- if (!this.isChangingQuality) {
3305
- this.updateCurrentPlayingQuality();
3306
- }
3307
- }, 1000);
3308
- });
3309
- }
3501
+ }, 1000);
3502
+ });
3310
3503
  }
3504
+ }
3311
3505
 
3312
- getCurrentPlayingQuality() {
3313
- if (!this.video) return null;
3314
-
3315
- if (this.video.currentSrc && this.qualities && this.qualities.length > 0) {
3316
- const currentSource = this.qualities.find(q => {
3317
- const currentUrl = this.video.currentSrc.toLowerCase();
3318
- const qualityUrl = q.src.toLowerCase();
3319
-
3320
- if (this.debugQuality) {
3321
- if (this.options.debug) console.log('Quality comparison:', {
3322
- current: currentUrl,
3323
- quality: qualityUrl,
3324
- qualityName: q.quality,
3325
- match: currentUrl === qualityUrl || currentUrl.includes(qualityUrl) || qualityUrl.includes(currentUrl)
3326
- });
3327
- }
3506
+ getCurrentPlayingQuality() {
3507
+ if (!this.video) return null;
3328
3508
 
3329
- return currentUrl === qualityUrl ||
3330
- currentUrl.includes(qualityUrl) ||
3331
- qualityUrl.includes(currentUrl);
3332
- });
3509
+ if (this.video.currentSrc && this.qualities && this.qualities.length > 0) {
3510
+ const currentSource = this.qualities.find(q => {
3511
+ const currentUrl = this.video.currentSrc.toLowerCase();
3512
+ const qualityUrl = q.src.toLowerCase();
3333
3513
 
3334
- if (currentSource) {
3335
- if (this.debugQuality) {
3336
- if (this.options.debug) console.log('Quality found from source:', currentSource.quality);
3337
- }
3338
- return currentSource.quality;
3514
+ if (this.debugQuality) {
3515
+ if (this.options.debug) console.log('Quality comparison:', {
3516
+ current: currentUrl,
3517
+ quality: qualityUrl,
3518
+ qualityName: q.quality,
3519
+ match: currentUrl === qualityUrl || currentUrl.includes(qualityUrl) || qualityUrl.includes(currentUrl)
3520
+ });
3339
3521
  }
3340
- }
3341
3522
 
3342
- if (this.video.videoHeight && this.video.videoWidth) {
3343
- const height = this.video.videoHeight;
3344
- const width = this.video.videoWidth;
3523
+ return currentUrl === qualityUrl ||
3524
+ currentUrl.includes(qualityUrl) ||
3525
+ qualityUrl.includes(currentUrl);
3526
+ });
3345
3527
 
3528
+ if (currentSource) {
3346
3529
  if (this.debugQuality) {
3347
- if (this.options.debug) console.log('Risoluzione video:', { height, width });
3530
+ if (this.options.debug) console.log('Quality found from source:', currentSource.quality);
3348
3531
  }
3349
-
3350
- if (height >= 2160) return '4K';
3351
- if (height >= 1440) return '1440p';
3352
- if (height >= 1080) return '1080p';
3353
- if (height >= 720) return '720p';
3354
- if (height >= 480) return '480p';
3355
- if (height >= 360) return '360p';
3356
- if (height >= 240) return '240p';
3357
-
3358
- return `${height}p`;
3532
+ return currentSource.quality;
3359
3533
  }
3534
+ }
3535
+
3536
+ if (this.video.videoHeight && this.video.videoWidth) {
3537
+ const height = this.video.videoHeight;
3538
+ const width = this.video.videoWidth;
3360
3539
 
3361
3540
  if (this.debugQuality) {
3362
- if (this.options.debug) console.log('No quality detected:', {
3363
- currentSrc: this.video.currentSrc,
3364
- videoHeight: this.video.videoHeight,
3365
- videoWidth: this.video.videoWidth,
3366
- qualities: this.qualities
3367
- });
3541
+ if (this.options.debug) console.log('Risoluzione video:', { height, width });
3368
3542
  }
3369
3543
 
3370
- return null;
3371
- }
3544
+ if (height >= 2160) return '4K';
3545
+ if (height >= 1440) return '1440p';
3546
+ if (height >= 1080) return '1080p';
3547
+ if (height >= 720) return '720p';
3548
+ if (height >= 480) return '480p';
3549
+ if (height >= 360) return '360p';
3550
+ if (height >= 240) return '240p';
3372
3551
 
3373
- updateCurrentPlayingQuality() {
3374
- const newPlayingQuality = this.getCurrentPlayingQuality();
3552
+ return `${height}p`;
3553
+ }
3375
3554
 
3376
- if (newPlayingQuality && newPlayingQuality !== this.currentPlayingQuality) {
3377
- if (this.options.debug) console.log(`Quality changed: ${this.currentPlayingQuality} → ${newPlayingQuality}`);
3378
- this.currentPlayingQuality = newPlayingQuality;
3379
- this.updateQualityDisplay();
3380
- }
3555
+ if (this.debugQuality) {
3556
+ if (this.options.debug) console.log('No quality detected:', {
3557
+ currentSrc: this.video.currentSrc,
3558
+ videoHeight: this.video.videoHeight,
3559
+ videoWidth: this.video.videoWidth,
3560
+ qualities: this.qualities
3561
+ });
3381
3562
  }
3382
3563
 
3383
- updateQualityDisplay() {
3384
- this.updateQualityButton();
3385
- this.updateQualityMenu();
3564
+ return null;
3565
+ }
3566
+
3567
+ updateCurrentPlayingQuality() {
3568
+ const newPlayingQuality = this.getCurrentPlayingQuality();
3569
+
3570
+ if (newPlayingQuality && newPlayingQuality !== this.currentPlayingQuality) {
3571
+ if (this.options.debug) console.log(`Quality changed: ${this.currentPlayingQuality} → ${newPlayingQuality}`);
3572
+ this.currentPlayingQuality = newPlayingQuality;
3573
+ this.updateQualityDisplay();
3386
3574
  }
3575
+ }
3387
3576
 
3388
- updateQualityButton() {
3389
- const qualityBtn = this.controls?.querySelector('.quality-btn');
3390
- if (!qualityBtn) return;
3577
+ updateQualityDisplay() {
3578
+ this.updateQualityButton();
3579
+ this.updateQualityMenu();
3580
+ }
3391
3581
 
3392
- let btnText = qualityBtn.querySelector('.quality-btn-text');
3393
- if (!btnText) {
3394
- qualityBtn.innerHTML = `
3395
- <span class="icon">⚙</span>
3396
- <div class="quality-btn-text">
3397
- <div class="selected-quality">${this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality}</div>
3398
- <div class="current-quality">${this.currentPlayingQuality || ''}</div>
3399
- </div>
3400
- `;
3401
- } else {
3402
- const selectedEl = btnText.querySelector('.selected-quality');
3403
- const currentEl = btnText.querySelector('.current-quality');
3582
+ updateQualityButton() {
3583
+ const qualityBtn = this.controls?.querySelector('.quality-btn');
3584
+ if (!qualityBtn) return;
3404
3585
 
3405
- if (selectedEl) {
3406
- selectedEl.textContent = this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality;
3407
- }
3586
+ let btnText = qualityBtn.querySelector('.quality-btn-text');
3587
+ if (!btnText) {
3588
+
3589
+ qualityBtn.textContent = '';
3408
3590
 
3409
- if (currentEl) {
3410
- currentEl.textContent = this.currentPlayingQuality || '';
3411
- currentEl.style.display = this.currentPlayingQuality ? 'block' : 'none';
3412
- }
3591
+
3592
+ const iconSpan = document.createElement('span');
3593
+ iconSpan.className = 'icon';
3594
+ iconSpan.textContent = '⚙';
3595
+ qualityBtn.appendChild(iconSpan);
3596
+
3597
+
3598
+ btnText = document.createElement('div');
3599
+ btnText.className = 'quality-btn-text';
3600
+
3601
+
3602
+ const selectedQualityDiv = document.createElement('div');
3603
+ selectedQualityDiv.className = 'selected-quality';
3604
+ selectedQualityDiv.textContent = this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality;
3605
+ btnText.appendChild(selectedQualityDiv);
3606
+
3607
+
3608
+ const currentQualityDiv = document.createElement('div');
3609
+ currentQualityDiv.className = 'current-quality';
3610
+ currentQualityDiv.textContent = this.currentPlayingQuality || '';
3611
+ btnText.appendChild(currentQualityDiv);
3612
+
3613
+
3614
+ qualityBtn.appendChild(btnText);
3615
+ } else {
3616
+
3617
+ const selectedEl = btnText.querySelector('.selected-quality');
3618
+ const currentEl = btnText.querySelector('.current-quality');
3619
+
3620
+ if (selectedEl) {
3621
+ selectedEl.textContent = this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality;
3622
+ }
3623
+
3624
+ if (currentEl) {
3625
+ currentEl.textContent = this.currentPlayingQuality || '';
3413
3626
  }
3414
3627
  }
3628
+ }
3415
3629
 
3416
3630
  updateQualityMenu() {
3417
3631
  const qualityMenu = this.controls?.querySelector('.quality-menu');
@@ -3465,213 +3679,217 @@ updateQualityMenu() {
3465
3679
  qualityMenu.innerHTML = menuHTML;
3466
3680
  }
3467
3681
 
3468
- getQualityStatus() {
3469
- return {
3470
- selected: this.selectedQuality,
3471
- playing: this.currentPlayingQuality,
3472
- isAuto: this.selectedQuality === 'auto',
3473
- isChanging: this.isChangingQuality
3474
- };
3475
- }
3682
+ getQualityStatus() {
3683
+ return {
3684
+ selected: this.selectedQuality,
3685
+ playing: this.currentPlayingQuality,
3686
+ isAuto: this.selectedQuality === 'auto',
3687
+ isChanging: this.isChangingQuality
3688
+ };
3689
+ }
3476
3690
 
3477
- getSelectedQuality() {
3478
- return this.selectedQuality;
3479
- }
3691
+ getSelectedQuality() {
3692
+ return this.selectedQuality;
3693
+ }
3480
3694
 
3481
- isAutoQualityActive() {
3482
- return this.selectedQuality === 'auto';
3483
- }
3695
+ isAutoQualityActive() {
3696
+ return this.selectedQuality === 'auto';
3697
+ }
3698
+
3699
+ enableQualityDebug() {
3700
+ this.debugQuality = true;
3701
+ this.enableAutoHideDebug();
3702
+ if (this.options.debug) console.log('Quality AND auto-hide debug enabled');
3703
+ this.updateCurrentPlayingQuality();
3704
+ }
3705
+
3706
+ disableQualityDebug() {
3707
+ this.debugQuality = false;
3708
+ this.disableAutoHideDebug();
3709
+ if (this.options.debug) console.log('Quality AND auto-hide debug disabled');
3710
+ }
3484
3711
 
3485
- enableQualityDebug() {
3486
- this.debugQuality = true;
3487
- this.enableAutoHideDebug();
3488
- if (this.options.debug) console.log('Quality AND auto-hide debug enabled');
3489
- this.updateCurrentPlayingQuality();
3490
- }
3712
+ changeQuality(e) {
3713
+ if (!e.target.classList.contains('quality-option')) return;
3714
+ if (this.isChangingQuality) return;
3491
3715
 
3492
- disableQualityDebug() {
3493
- this.debugQuality = false;
3494
- this.disableAutoHideDebug();
3495
- if (this.options.debug) console.log('Quality AND auto-hide debug disabled');
3716
+
3717
+ const adaptiveQuality = e.target.getAttribute('data-adaptive-quality');
3718
+ if (adaptiveQuality !== null && this.isAdaptiveStream) {
3719
+ const qualityIndex = adaptiveQuality === 'auto' ? -1 : parseInt(adaptiveQuality);
3720
+ this.setAdaptiveQuality(qualityIndex);
3721
+ this.updateAdaptiveQualityMenu();
3722
+ return;
3496
3723
  }
3497
3724
 
3498
- changeQuality(e) {
3499
- if (!e.target.classList.contains('quality-option')) return;
3500
- if (this.isChangingQuality) return;
3725
+ const quality = e.target.getAttribute('data-quality');
3726
+ if (!quality || quality === this.selectedQuality) return;
3501
3727
 
3502
-
3503
- const adaptiveQuality = e.target.getAttribute('data-adaptive-quality');
3504
- if (adaptiveQuality !== null && this.isAdaptiveStream) {
3505
- const qualityIndex = adaptiveQuality === 'auto' ? -1 : parseInt(adaptiveQuality);
3506
- this.setAdaptiveQuality(qualityIndex);
3507
- this.updateAdaptiveQualityMenu();
3508
- return;
3509
- }
3728
+ if (this.options.debug) console.log(`Quality change requested: ${this.selectedQuality} → ${quality}`);
3510
3729
 
3511
- const quality = e.target.getAttribute('data-quality');
3512
- if (!quality || quality === this.selectedQuality) return;
3730
+ this.selectedQuality = quality;
3513
3731
 
3514
- if (this.options.debug) console.log(`Quality change requested: ${this.selectedQuality} → ${quality}`);
3732
+ if (quality === 'auto') {
3733
+ this.enableAutoQuality();
3734
+ } else {
3735
+ this.setQuality(quality);
3736
+ }
3515
3737
 
3516
- this.selectedQuality = quality;
3738
+ this.updateQualityDisplay();
3739
+ }
3517
3740
 
3518
- if (quality === 'auto') {
3519
- this.enableAutoQuality();
3520
- } else {
3521
- this.setQuality(quality);
3522
- }
3741
+ setQuality(targetQuality) {
3742
+ if (this.options.debug) console.log(`setQuality("${targetQuality}") called`);
3523
3743
 
3524
- this.updateQualityDisplay();
3744
+ if (!targetQuality) {
3745
+ if (this.options.debug) console.error('targetQuality is empty!');
3746
+ return;
3525
3747
  }
3526
3748
 
3527
- setQuality(targetQuality) {
3528
- if (this.options.debug) console.log(`setQuality("${targetQuality}") called`);
3749
+ if (!this.video || !this.qualities || this.qualities.length === 0) return;
3750
+ if (this.isChangingQuality) return;
3529
3751
 
3530
- if (!targetQuality) {
3531
- if (this.options.debug) console.error('targetQuality is empty!');
3532
- return;
3533
- }
3752
+ const newSource = this.qualities.find(q => q.quality === targetQuality);
3753
+ if (!newSource || !newSource.src) {
3754
+ if (this.options.debug) console.error(`Quality "${targetQuality}" not found`);
3755
+ return;
3756
+ }
3534
3757
 
3535
- if (!this.video || !this.qualities || this.qualities.length === 0) return;
3536
- if (this.isChangingQuality) return;
3758
+ const currentTime = this.video.currentTime || 0;
3759
+ const wasPlaying = !this.video.paused;
3537
3760
 
3538
- const newSource = this.qualities.find(q => q.quality === targetQuality);
3539
- if (!newSource || !newSource.src) {
3540
- if (this.options.debug) console.error(`Quality "${targetQuality}" not found`);
3541
- return;
3542
- }
3761
+ this.isChangingQuality = true;
3762
+ this.selectedQuality = targetQuality;
3763
+ this.video.pause();
3543
3764
 
3544
- const currentTime = this.video.currentTime || 0;
3545
- const wasPlaying = !this.video.paused;
3765
+
3766
+ this.showLoading();
3767
+ if (this.video.classList) {
3768
+ this.video.classList.add('quality-changing');
3769
+ }
3546
3770
 
3547
- this.isChangingQuality = true;
3548
- this.selectedQuality = targetQuality;
3549
- this.video.pause();
3771
+ const onLoadedData = () => {
3772
+ if (this.options.debug) console.log(`Quality ${targetQuality} applied!`);
3773
+ this.video.currentTime = currentTime;
3550
3774
 
3551
-
3552
- this.showLoading();
3553
- if (this.video.classList) {
3554
- this.video.classList.add('quality-changing');
3775
+ if (wasPlaying) {
3776
+ this.video.play().catch(e => {
3777
+ if (this.options.debug) console.log('Play error:', e);
3778
+ });
3555
3779
  }
3556
3780
 
3557
- const onLoadedData = () => {
3558
- if (this.options.debug) console.log(`Quality ${targetQuality} applied!`);
3559
- this.video.currentTime = currentTime;
3560
-
3561
- if (wasPlaying) {
3562
- this.video.play().catch(e => {
3563
- if (this.options.debug) console.log('Play error:', e);
3564
- });
3565
- }
3566
-
3567
- this.currentPlayingQuality = targetQuality;
3568
- this.updateQualityDisplay();
3569
- this.isChangingQuality = false;
3781
+ this.currentPlayingQuality = targetQuality;
3782
+ this.updateQualityDisplay();
3783
+ this.isChangingQuality = false;
3570
3784
 
3571
-
3572
- this.restoreResolutionAfterQualityChange();
3573
- cleanup();
3574
- };
3785
+
3786
+ this.restoreResolutionAfterQualityChange();
3787
+ cleanup();
3788
+ };
3575
3789
 
3576
- const onError = (error) => {
3577
- if (this.options.debug) console.error(`Loading error ${targetQuality}:`, error);
3578
- this.isChangingQuality = false;
3579
- cleanup();
3580
- };
3790
+ const onError = (error) => {
3791
+ if (this.options.debug) console.error(`Loading error ${targetQuality}:`, error);
3792
+ this.isChangingQuality = false;
3581
3793
 
3582
- const cleanup = () => {
3583
- this.video.removeEventListener('loadeddata', onLoadedData);
3584
- this.video.removeEventListener('error', onError);
3585
- };
3794
+
3795
+ this.onVideoError(error);
3586
3796
 
3587
- this.video.addEventListener('loadeddata', onLoadedData, { once: true });
3588
- this.video.addEventListener('error', onError, { once: true });
3797
+ cleanup();
3798
+ };
3589
3799
 
3590
- this.video.src = newSource.src;
3591
- this.video.load();
3592
- }
3800
+ const cleanup = () => {
3801
+ this.video.removeEventListener('loadeddata', onLoadedData);
3802
+ this.video.removeEventListener('error', onError);
3803
+ };
3593
3804
 
3594
- finishQualityChange(success, wasPlaying, currentTime, currentVolume, wasMuted, targetQuality) {
3595
- if (this.options.debug) console.log(`Quality change completion: success=${success}, target=${targetQuality}`);
3805
+ this.video.addEventListener('loadeddata', onLoadedData, { once: true });
3806
+ this.video.addEventListener('error', onError, { once: true });
3596
3807
 
3597
- if (this.qualityChangeTimeout) {
3598
- clearTimeout(this.qualityChangeTimeout);
3599
- this.qualityChangeTimeout = null;
3600
- }
3808
+ this.video.src = newSource.src;
3809
+ this.video.load();
3810
+ }
3601
3811
 
3602
- if (this.video) {
3603
- try {
3604
- if (success && currentTime > 0 && this.video.duration) {
3605
- this.video.currentTime = Math.min(currentTime, this.video.duration);
3606
- }
3812
+ finishQualityChange(success, wasPlaying, currentTime, currentVolume, wasMuted, targetQuality) {
3813
+ if (this.options.debug) console.log(`Quality change completion: success=${success}, target=${targetQuality}`);
3607
3814
 
3608
- this.video.volume = currentVolume;
3609
- this.video.muted = wasMuted;
3815
+ if (this.qualityChangeTimeout) {
3816
+ clearTimeout(this.qualityChangeTimeout);
3817
+ this.qualityChangeTimeout = null;
3818
+ }
3610
3819
 
3611
- if (success && wasPlaying) {
3612
- this.video.play().catch(err => {
3613
- if (this.options.debug) console.warn('Play after quality change failed:', err);
3614
- });
3615
- }
3616
- } catch (error) {
3617
- if (this.options.debug) console.error('Errore ripristino stato:', error);
3820
+ if (this.video) {
3821
+ try {
3822
+ if (success && currentTime > 0 && this.video.duration) {
3823
+ this.video.currentTime = Math.min(currentTime, this.video.duration);
3618
3824
  }
3619
3825
 
3620
- if (this.video.classList) {
3621
- this.video.classList.remove('quality-changing');
3826
+ this.video.volume = currentVolume;
3827
+ this.video.muted = wasMuted;
3828
+
3829
+ if (success && wasPlaying) {
3830
+ this.video.play().catch(err => {
3831
+ if (this.options.debug) console.warn('Play after quality change failed:', err);
3832
+ });
3622
3833
  }
3834
+ } catch (error) {
3835
+ if (this.options.debug) console.error('Errore ripristino stato:', error);
3623
3836
  }
3624
3837
 
3625
- this.hideLoading();
3626
- this.isChangingQuality = false;
3627
-
3628
- if (success) {
3629
- if (this.options.debug) console.log('Quality change completed successfully');
3630
- setTimeout(() => {
3631
- this.currentPlayingQuality = targetQuality;
3632
- this.updateQualityDisplay();
3633
- if (this.options.debug) console.log(`🎯 Quality confirmed active: ${targetQuality}`);
3634
- }, 100);
3635
- } else {
3636
- if (this.options.debug) console.warn('Quality change failed or timeout');
3838
+ if (this.video.classList) {
3839
+ this.video.classList.remove('quality-changing');
3637
3840
  }
3841
+ }
3842
+
3843
+ this.hideLoading();
3844
+ this.isChangingQuality = false;
3638
3845
 
3846
+ if (success) {
3847
+ if (this.options.debug) console.log('Quality change completed successfully');
3639
3848
  setTimeout(() => {
3640
- this.updateCurrentPlayingQuality();
3641
- }, 2000);
3849
+ this.currentPlayingQuality = targetQuality;
3850
+ this.updateQualityDisplay();
3851
+ if (this.options.debug) console.log(`🎯 Quality confirmed active: ${targetQuality}`);
3852
+ }, 100);
3853
+ } else {
3854
+ if (this.options.debug) console.warn('Quality change failed or timeout');
3642
3855
  }
3643
3856
 
3644
- cleanupQualityChange() {
3645
- if (this.qualityChangeTimeout) {
3646
- clearTimeout(this.qualityChangeTimeout);
3647
- this.qualityChangeTimeout = null;
3648
- }
3649
- }
3857
+ setTimeout(() => {
3858
+ this.updateCurrentPlayingQuality();
3859
+ }, 2000);
3860
+ }
3650
3861
 
3651
- enableAutoQuality() {
3652
- if (this.options.debug) console.log('🔄 enableAutoQuality - keeping selectedQuality as "auto"');
3862
+ cleanupQualityChange() {
3863
+ if (this.qualityChangeTimeout) {
3864
+ clearTimeout(this.qualityChangeTimeout);
3865
+ this.qualityChangeTimeout = null;
3866
+ }
3867
+ }
3653
3868
 
3654
-
3655
- this.selectedQuality = 'auto';
3869
+ enableAutoQuality() {
3870
+ if (this.options.debug) console.log('🔄 enableAutoQuality - keeping selectedQuality as "auto"');
3656
3871
 
3657
- if (!this.qualities || this.qualities.length === 0) {
3658
- if (this.options.debug) console.warn('⚠️ No qualities available for auto selection');
3659
- this.updateQualityDisplay();
3660
- return;
3661
- }
3872
+
3873
+ this.selectedQuality = 'auto';
3662
3874
 
3663
-
3664
- let autoSelectedQuality = this.getAutoQualityBasedOnConnection();
3875
+ if (!this.qualities || this.qualities.length === 0) {
3876
+ if (this.options.debug) console.warn('⚠️ No qualities available for auto selection');
3877
+ this.updateQualityDisplay();
3878
+ return;
3879
+ }
3665
3880
 
3666
- if (this.options.debug) {
3667
- console.log('🎯 Auto quality selected:', autoSelectedQuality);
3668
- console.log('📊 selectedQuality remains: "auto" (for UI)');
3669
- }
3881
+
3882
+ let autoSelectedQuality = this.getAutoQualityBasedOnConnection();
3670
3883
 
3671
-
3672
- this.applyAutoQuality(autoSelectedQuality);
3884
+ if (this.options.debug) {
3885
+ console.log('🎯 Auto quality selected:', autoSelectedQuality);
3886
+ console.log('📊 selectedQuality remains: "auto" (for UI)');
3673
3887
  }
3674
3888
 
3889
+
3890
+ this.applyAutoQuality(autoSelectedQuality);
3891
+ }
3892
+
3675
3893
 
3676
3894
  getAutoQualityBasedOnConnection() {
3677
3895
 
@@ -3936,102 +4154,120 @@ getAutoQualityBasedOnConnection() {
3936
4154
  return maxQuality.quality;
3937
4155
  }
3938
4156
 
3939
- applyAutoQuality(targetQuality) {
3940
- if (!targetQuality || !this.video || !this.qualities || this.qualities.length === 0) {
3941
- return;
3942
- }
4157
+ applyAutoQuality(targetQuality) {
4158
+ if (!targetQuality || !this.video || !this.qualities || this.qualities.length === 0) {
4159
+ return;
4160
+ }
3943
4161
 
3944
- if (this.isChangingQuality) return;
4162
+ if (this.isChangingQuality) return;
3945
4163
 
3946
- const newSource = this.qualities.find(q => q.quality === targetQuality);
3947
- if (!newSource || !newSource.src) {
3948
- if (this.options.debug) console.error('Auto quality', targetQuality, 'not found');
3949
- return;
3950
- }
4164
+ const newSource = this.qualities.find(q => q.quality === targetQuality);
4165
+ if (!newSource || !newSource.src) {
4166
+ if (this.options.debug) console.error('Auto quality', targetQuality, 'not found');
4167
+ return;
4168
+ }
4169
+
4170
+
4171
+ const currentResolution = this.getCurrentResolution();
4172
+
4173
+ const currentTime = this.video.currentTime || 0;
4174
+ const wasPlaying = !this.video.paused;
4175
+
4176
+ this.isChangingQuality = true;
4177
+ this.video.pause();
4178
+
4179
+
4180
+ this.showLoading();
4181
+ if (this.video.classList) {
4182
+ this.video.classList.add('quality-changing');
4183
+ }
3951
4184
 
4185
+
4186
+ const onLoadedData = () => {
4187
+ if (this.options.debug) console.log('Auto quality', targetQuality, 'applied');
4188
+ this.video.currentTime = currentTime;
4189
+ if (wasPlaying) {
4190
+ this.video.play().catch(e => {
4191
+ if (this.options.debug) console.log('Autoplay prevented:', e);
4192
+ });
4193
+ }
4194
+ this.currentPlayingQuality = targetQuality;
3952
4195
 
3953
- const currentResolution = this.getCurrentResolution();
4196
+ this.updateQualityDisplay();
3954
4197
 
3955
- const currentTime = this.video.currentTime || 0;
3956
- const wasPlaying = !this.video.paused;
4198
+
4199
+ this.hideLoading();
4200
+ if (this.video.classList) {
4201
+ this.video.classList.remove('quality-changing');
4202
+ }
3957
4203
 
3958
- this.isChangingQuality = true;
3959
- this.video.pause();
4204
+ this.isChangingQuality = false;
4205
+ cleanup();
4206
+ };
3960
4207
 
3961
- const onLoadedData = () => {
3962
- if (this.options.debug) console.log('Auto quality', targetQuality, 'applied');
3963
- this.video.currentTime = currentTime;
3964
- if (wasPlaying) {
3965
- this.video.play().catch(e => {
3966
- if (this.options.debug) console.log('Autoplay prevented:', e);
3967
- });
3968
- }
3969
- this.currentPlayingQuality = targetQuality;
3970
-
3971
- this.updateQualityDisplay();
3972
- this.isChangingQuality = false;
3973
- cleanup();
3974
- };
4208
+ const onError = (error) => {
4209
+ if (this.options.debug) console.error('Auto quality loading error:', error);
4210
+ this.isChangingQuality = false;
3975
4211
 
3976
- const onError = (error) => {
3977
- if (this.options.debug) console.error('Auto quality loading error:', error);
3978
- this.isChangingQuality = false;
3979
- cleanup();
3980
- };
4212
+
4213
+ this.onVideoError(error);
3981
4214
 
3982
- const cleanup = () => {
3983
- this.video.removeEventListener('loadeddata', onLoadedData);
3984
- this.video.removeEventListener('error', onError);
3985
- };
4215
+ cleanup();
4216
+ };
3986
4217
 
3987
- this.video.addEventListener('loadeddata', onLoadedData, { once: true });
3988
- this.video.addEventListener('error', onError, { once: true });
3989
- this.video.src = newSource.src;
3990
- this.video.load();
3991
- }
4218
+ const cleanup = () => {
4219
+ this.video.removeEventListener('loadeddata', onLoadedData);
4220
+ this.video.removeEventListener('error', onError);
4221
+ };
3992
4222
 
3993
- setDefaultQuality(quality) {
3994
- if (this.options.debug) console.log(`🔧 Setting defaultQuality: "${quality}"`);
3995
- this.options.defaultQuality = quality;
3996
- this.selectedQuality = quality;
4223
+ this.video.addEventListener('loadeddata', onLoadedData, { once: true });
4224
+ this.video.addEventListener('error', onError, { once: true });
4225
+ this.video.src = newSource.src;
4226
+ this.video.load();
4227
+ }
3997
4228
 
3998
- if (quality === 'auto') {
3999
- this.enableAutoQuality();
4000
- } else {
4001
- this.setQuality(quality);
4002
- }
4229
+ setDefaultQuality(quality) {
4230
+ if (this.options.debug) console.log(`🔧 Setting defaultQuality: "${quality}"`);
4231
+ this.options.defaultQuality = quality;
4232
+ this.selectedQuality = quality;
4003
4233
 
4004
- return this;
4234
+ if (quality === 'auto') {
4235
+ this.enableAutoQuality();
4236
+ } else {
4237
+ this.setQuality(quality);
4005
4238
  }
4006
4239
 
4007
- getDefaultQuality() {
4008
- return this.options.defaultQuality;
4009
- }
4240
+ return this;
4241
+ }
4010
4242
 
4011
- getQualityLabel(height, width) {
4012
- if (height >= 2160) return '4K';
4013
- if (height >= 1440) return '1440p';
4014
- if (height >= 1080) return '1080p';
4015
- if (height >= 720) return '720p';
4016
- if (height >= 480) return '480p';
4017
- if (height >= 360) return '360p';
4018
- if (height >= 240) return '240p';
4019
- return `${height}p`;
4020
- }
4243
+ getDefaultQuality() {
4244
+ return this.options.defaultQuality;
4245
+ }
4021
4246
 
4022
- updateAdaptiveQualityMenu() {
4023
- const qualityMenu = this.controls?.querySelector('.quality-menu');
4024
- if (!qualityMenu || !this.isAdaptiveStream) return;
4247
+ getQualityLabel(height, width) {
4248
+ if (height >= 2160) return '4K';
4249
+ if (height >= 1440) return '1440p';
4250
+ if (height >= 1080) return '1080p';
4251
+ if (height >= 720) return '720p';
4252
+ if (height >= 480) return '480p';
4253
+ if (height >= 360) return '360p';
4254
+ if (height >= 240) return '240p';
4255
+ return `${height}p`;
4256
+ }
4025
4257
 
4026
- let menuHTML = `<div class="quality-option ${this.isAutoQuality() ? 'active' : ''}" data-adaptive-quality="auto">Auto</div>`;
4258
+ updateAdaptiveQualityMenu() {
4259
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
4260
+ if (!qualityMenu || !this.isAdaptiveStream) return;
4027
4261
 
4028
- this.adaptiveQualities.forEach(quality => {
4029
- const isActive = this.getCurrentAdaptiveQuality() === quality.index;
4030
- menuHTML += `<div class="quality-option ${isActive ? 'active' : ''}" data-adaptive-quality="${quality.index}">${quality.label}</div>`;
4031
- });
4262
+ let menuHTML = `<div class="quality-option ${this.isAutoQuality() ? 'active' : ''}" data-adaptive-quality="auto">Auto</div>`;
4032
4263
 
4033
- qualityMenu.innerHTML = menuHTML;
4034
- }
4264
+ this.adaptiveQualities.forEach(quality => {
4265
+ const isActive = this.getCurrentAdaptiveQuality() === quality.index;
4266
+ menuHTML += `<div class="quality-option ${isActive ? 'active' : ''}" data-adaptive-quality="${quality.index}">${quality.label}</div>`;
4267
+ });
4268
+
4269
+ qualityMenu.innerHTML = menuHTML;
4270
+ }
4035
4271
 
4036
4272
  updateAdaptiveQualityDisplay() {
4037
4273
  if (!this.isAdaptiveStream) return;
@@ -4057,61 +4293,61 @@ updateAdaptiveQualityDisplay() {
4057
4293
  }
4058
4294
  }
4059
4295
 
4060
- setAdaptiveQuality(qualityIndex) {
4061
- if (!this.isAdaptiveStream) return;
4296
+ setAdaptiveQuality(qualityIndex) {
4297
+ if (!this.isAdaptiveStream) return;
4062
4298
 
4063
- try {
4064
- if (qualityIndex === 'auto' || qualityIndex === -1) {
4065
-
4066
- if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
4067
- this.dashPlayer.updateSettings({
4068
- streaming: {
4069
- abr: { autoSwitchBitrate: { video: true } }
4070
- }
4071
- });
4072
- } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
4073
- this.hlsPlayer.currentLevel = -1;
4074
- }
4075
- this.selectedQuality = 'auto';
4076
- } else {
4077
-
4078
- if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
4079
- this.dashPlayer.updateSettings({
4080
- streaming: {
4081
- abr: { autoSwitchBitrate: { video: false } }
4082
- }
4083
- });
4084
- this.dashPlayer.setQualityFor('video', qualityIndex);
4085
- } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
4086
- this.hlsPlayer.currentLevel = qualityIndex;
4087
- }
4088
- this.selectedQuality = this.adaptiveQualities[qualityIndex]?.label || 'Unknown';
4299
+ try {
4300
+ if (qualityIndex === 'auto' || qualityIndex === -1) {
4301
+
4302
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
4303
+ this.dashPlayer.updateSettings({
4304
+ streaming: {
4305
+ abr: { autoSwitchBitrate: { video: true } }
4306
+ }
4307
+ });
4308
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
4309
+ this.hlsPlayer.currentLevel = -1;
4310
+ }
4311
+ this.selectedQuality = 'auto';
4312
+ } else {
4313
+
4314
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
4315
+ this.dashPlayer.updateSettings({
4316
+ streaming: {
4317
+ abr: { autoSwitchBitrate: { video: false } }
4318
+ }
4319
+ });
4320
+ this.dashPlayer.setQualityFor('video', qualityIndex);
4321
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
4322
+ this.hlsPlayer.currentLevel = qualityIndex;
4089
4323
  }
4324
+ this.selectedQuality = this.adaptiveQualities[qualityIndex]?.label || 'Unknown';
4325
+ }
4090
4326
 
4091
- this.updateAdaptiveQualityDisplay();
4092
- if (this.options.debug) console.log('📡 Adaptive quality set to:', qualityIndex);
4327
+ this.updateAdaptiveQualityDisplay();
4328
+ if (this.options.debug) console.log('📡 Adaptive quality set to:', qualityIndex);
4093
4329
 
4094
- } catch (error) {
4095
- if (this.options.debug) console.error('📡 Error setting adaptive quality:', error);
4096
- }
4330
+ } catch (error) {
4331
+ if (this.options.debug) console.error('📡 Error setting adaptive quality:', error);
4097
4332
  }
4333
+ }
4098
4334
 
4099
- getCurrentAdaptiveQuality() {
4100
- if (!this.isAdaptiveStream) return null;
4335
+ getCurrentAdaptiveQuality() {
4336
+ if (!this.isAdaptiveStream) return null;
4101
4337
 
4102
- try {
4103
- if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
4104
- return this.dashPlayer.getQualityFor('video');
4105
- } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
4106
- return this.hlsPlayer.currentLevel;
4107
- }
4108
- } catch (error) {
4109
- if (this.options.debug) console.error('📡 Error getting current quality:', error);
4338
+ try {
4339
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
4340
+ return this.dashPlayer.getQualityFor('video');
4341
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
4342
+ return this.hlsPlayer.currentLevel;
4110
4343
  }
4111
-
4112
- return null;
4344
+ } catch (error) {
4345
+ if (this.options.debug) console.error('📡 Error getting current quality:', error);
4113
4346
  }
4114
4347
 
4348
+ return null;
4349
+ }
4350
+
4115
4351
  getCurrentAdaptiveQualityLabel() {
4116
4352
  const currentIndex = this.getCurrentAdaptiveQuality();
4117
4353
  if (currentIndex === null || currentIndex === -1) {
@@ -4120,75 +4356,75 @@ getCurrentAdaptiveQualityLabel() {
4120
4356
  return this.adaptiveQualities[currentIndex]?.label || this.tauto;
4121
4357
  }
4122
4358
 
4123
- isAutoQuality() {
4124
- if (this.isAdaptiveStream) {
4125
- const currentQuality = this.getCurrentAdaptiveQuality();
4126
- return currentQuality === null || currentQuality === -1 || this.selectedQuality === 'auto';
4127
- }
4128
- return this.selectedQuality === 'auto';
4359
+ isAutoQuality() {
4360
+ if (this.isAdaptiveStream) {
4361
+ const currentQuality = this.getCurrentAdaptiveQuality();
4362
+ return currentQuality === null || currentQuality === -1 || this.selectedQuality === 'auto';
4129
4363
  }
4364
+ return this.selectedQuality === 'auto';
4365
+ }
4130
4366
 
4131
- setResolution(resolution) {
4132
- if (!this.video || !this.container) {
4133
- if (this.options.debug) console.warn("Video or container not available for setResolution");
4134
- return;
4135
- }
4367
+ setResolution(resolution) {
4368
+ if (!this.video || !this.container) {
4369
+ if (this.options.debug) console.warn("Video or container not available for setResolution");
4370
+ return;
4371
+ }
4136
4372
 
4137
-
4138
- const supportedResolutions = ["normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"];
4373
+
4374
+ const supportedResolutions = ["normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"];
4139
4375
 
4140
- if (!supportedResolutions.includes(resolution)) {
4141
- if (this.options.debug) console.warn(`Resolution "${resolution}" not supported. Supported values: ${supportedResolutions.join(", ")}`);
4142
- return;
4143
- }
4376
+ if (!supportedResolutions.includes(resolution)) {
4377
+ if (this.options.debug) console.warn(`Resolution "${resolution}" not supported. Supported values: ${supportedResolutions.join(", ")}`);
4378
+ return;
4379
+ }
4144
4380
 
4145
-
4146
- const allResolutionClasses = [
4147
- "resolution-normal", "resolution-4-3", "resolution-16-9",
4148
- "resolution-stretched", "resolution-fit-to-screen", "resolution-scale-to-fit"
4149
- ];
4381
+
4382
+ const allResolutionClasses = [
4383
+ "resolution-normal", "resolution-4-3", "resolution-16-9",
4384
+ "resolution-stretched", "resolution-fit-to-screen", "resolution-scale-to-fit"
4385
+ ];
4150
4386
 
4151
- this.video.classList.remove(...allResolutionClasses);
4152
- if (this.container) {
4153
- this.container.classList.remove(...allResolutionClasses);
4154
- }
4387
+ this.video.classList.remove(...allResolutionClasses);
4388
+ if (this.container) {
4389
+ this.container.classList.remove(...allResolutionClasses);
4390
+ }
4155
4391
 
4156
-
4157
- const cssClass = `resolution-${resolution.replace(":", "-")}`;
4158
- this.video.classList.add(cssClass);
4159
- if (this.container) {
4160
- this.container.classList.add(cssClass);
4161
- }
4392
+
4393
+ const cssClass = `resolution-${resolution.replace(":", "-")}`;
4394
+ this.video.classList.add(cssClass);
4395
+ if (this.container) {
4396
+ this.container.classList.add(cssClass);
4397
+ }
4162
4398
 
4163
-
4164
- this.options.resolution = resolution;
4399
+
4400
+ this.options.resolution = resolution;
4165
4401
 
4166
- if (this.options.debug) {
4167
- console.log(`Resolution applied: ${resolution} (CSS class: ${cssClass})`);
4168
- }
4402
+ if (this.options.debug) {
4403
+ console.log(`Resolution applied: ${resolution} (CSS class: ${cssClass})`);
4169
4404
  }
4405
+ }
4170
4406
 
4171
- getCurrentResolution() {
4172
- return this.options.resolution || "normal";
4173
- }
4407
+ getCurrentResolution() {
4408
+ return this.options.resolution || "normal";
4409
+ }
4174
4410
 
4175
- initializeResolution() {
4176
- if (this.options.resolution && this.options.resolution !== "normal") {
4177
- this.setResolution(this.options.resolution);
4178
- }
4411
+ initializeResolution() {
4412
+ if (this.options.resolution && this.options.resolution !== "normal") {
4413
+ this.setResolution(this.options.resolution);
4179
4414
  }
4415
+ }
4180
4416
 
4181
- restoreResolutionAfterQualityChange() {
4182
- if (this.options.resolution && this.options.resolution !== "normal") {
4183
- if (this.options.debug) {
4184
- console.log(`Restoring resolution "${this.options.resolution}" after quality change`);
4185
- }
4186
-
4187
- setTimeout(() => {
4188
- this.setResolution(this.options.resolution);
4189
- }, 150);
4417
+ restoreResolutionAfterQualityChange() {
4418
+ if (this.options.resolution && this.options.resolution !== "normal") {
4419
+ if (this.options.debug) {
4420
+ console.log(`Restoring resolution "${this.options.resolution}" after quality change`);
4190
4421
  }
4422
+
4423
+ setTimeout(() => {
4424
+ this.setResolution(this.options.resolution);
4425
+ }, 150);
4191
4426
  }
4427
+ }
4192
4428
 
4193
4429
 
4194
4430
 
@@ -4230,7 +4466,7 @@ createCustomSubtitleOverlay() {
4230
4466
  'bottom: 80px;' +
4231
4467
  'left: 50%;' +
4232
4468
  'transform: translateX(-50%);' +
4233
- 'z-index: 5;' +
4469
+ 'z-index: 5;' +
4234
4470
  'color: white;' +
4235
4471
  'font-family: Arial, sans-serif;' +
4236
4472
  'font-size: clamp(12px, 4vw, 18px);' +
@@ -4324,7 +4560,7 @@ parseCustomSRT(srtText) {
4324
4560
  if (timeMatch) {
4325
4561
  var startTime = this.customTimeToSeconds(timeMatch[1]);
4326
4562
  var endTime = this.customTimeToSeconds(timeMatch[2]);
4327
- var text = lines.slice(2).join(' ').trim().replace(/<[^>]*>/g, '');
4563
+ var text = this.sanitizeSubtitleText(lines.slice(2).join(' ').trim());
4328
4564
 
4329
4565
  if (text.length > 0 && startTime < endTime) {
4330
4566
  subtitles.push({