myetv-player 1.0.0 → 1.0.6

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