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.
- package/.github/workflows/codeql.yml +100 -0
- package/README.md +36 -58
- package/SECURITY.md +50 -0
- package/css/myetv-player.css +301 -218
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +1713 -1503
- package/dist/myetv-player.min.js +1670 -1471
- package/package.json +6 -1
- package/plugins/README.md +1016 -0
- package/plugins/cloudflare/README.md +1068 -0
- package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +556 -0
- package/plugins/facebook/README.md +1024 -0
- package/plugins/facebook/myetv-player-facebook-plugin.js +437 -0
- package/plugins/gamepad-remote-controller/README.md +816 -0
- package/plugins/gamepad-remote-controller/myetv-player-gamepad-remote-plugin.js +678 -0
- package/plugins/google-adsense-ads/README.md +1 -0
- package/plugins/google-adsense-ads/g-adsense-ads-plugin.js +158 -0
- package/plugins/google-ima-ads/README.md +1 -0
- package/plugins/google-ima-ads/g-ima-ads-plugin.js +355 -0
- package/plugins/twitch/README.md +1185 -0
- package/plugins/twitch/myetv-player-twitch-plugin.js +569 -0
- package/plugins/vast-vpaid-ads/README.md +1 -0
- package/plugins/vast-vpaid-ads/vast-vpaid-ads-plugin.js +346 -0
- package/plugins/vimeo/README.md +1416 -0
- package/plugins/vimeo/myetv-player-vimeo.js +640 -0
- package/plugins/youtube/README.md +851 -0
- package/plugins/youtube/myetv-player-youtube-plugin.js +1714 -210
- package/scss/README.md +160 -0
- package/scss/_menus.scss +840 -672
- package/scss/_responsive.scss +67 -105
- package/scss/_volume.scss +67 -105
- package/src/README.md +559 -0
- package/src/controls.js +16 -4
- package/src/core.js +1192 -1062
- package/src/i18n.js +27 -1
- package/src/quality.js +478 -436
- package/src/subtitles.js +2 -2
package/dist/myetv-player.js
CHANGED
|
@@ -283,14 +283,40 @@ class VideoPlayerI18n {
|
|
|
283
283
|
// Add new translations
|
|
284
284
|
addTranslations(lang, translations) {
|
|
285
285
|
try {
|
|
286
|
+
// SECURITY: Prevent prototype pollution by validating lang parameter
|
|
287
|
+
if (!this.isValidLanguageKey(lang)) {
|
|
288
|
+
console.warn('Invalid language key rejected:', lang);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
286
292
|
if (!this.translations[lang]) {
|
|
287
293
|
this.translations[lang] = {};
|
|
288
294
|
}
|
|
289
|
-
|
|
295
|
+
|
|
296
|
+
// SECURITY: Only copy safe properties from translations object
|
|
297
|
+
for (const key in translations) {
|
|
298
|
+
if (translations.hasOwnProperty(key) && this.isValidLanguageKey(key)) {
|
|
299
|
+
this.translations[lang][key] = translations[key];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
290
302
|
} catch (error) {
|
|
291
303
|
console.warn('Error adding translations:', error);
|
|
292
304
|
}
|
|
293
305
|
}
|
|
306
|
+
|
|
307
|
+
// SECURITY: Validate that a key is safe (not a prototype polluting key)
|
|
308
|
+
isValidLanguageKey(key) {
|
|
309
|
+
if (typeof key !== 'string') return false;
|
|
310
|
+
|
|
311
|
+
// Reject dangerous prototype-polluting keys
|
|
312
|
+
const dangerousKeys = ['__proto__', 'constructor', 'prototype'];
|
|
313
|
+
if (dangerousKeys.includes(key.toLowerCase())) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Accept only alphanumeric keys with underscore/dash (e.g., 'en', 'it', 'play_pause')
|
|
318
|
+
return /^[a-zA-Z0-9_-]+$/.test(key);
|
|
319
|
+
}
|
|
294
320
|
}
|
|
295
321
|
|
|
296
322
|
// Safe initialization with error handling
|
|
@@ -373,432 +399,433 @@ window.registerMYETVPlugin = registerPlugin;
|
|
|
373
399
|
class MYETVvideoplayer {
|
|
374
400
|
|
|
375
401
|
constructor(videoElement, options = {}) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (!this.video) {
|
|
381
|
-
throw new Error('Video element not found: ' + videoElement);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
this.options = {
|
|
385
|
-
showQualitySelector: true,
|
|
386
|
-
showSpeedControl: true,
|
|
387
|
-
showFullscreen: true,
|
|
388
|
-
showPictureInPicture: true,
|
|
389
|
-
showSubtitles: true,
|
|
390
|
-
subtitlesEnabled: false,
|
|
391
|
-
autoHide: true,
|
|
392
|
-
autoHideDelay: 3000,
|
|
393
|
-
poster: null, // URL of poster image
|
|
394
|
-
showPosterOnEnd: false, // Show poster again when video ends
|
|
395
|
-
keyboardControls: true,
|
|
396
|
-
showSeekTooltip: true,
|
|
397
|
-
showTitleOverlay: false,
|
|
398
|
-
videoTitle: '',
|
|
399
|
-
persistentTitle: false,
|
|
400
|
-
debug: false, // Enable/disable debug logging
|
|
401
|
-
autoplay: false, // if video should autoplay at start
|
|
402
|
-
defaultQuality: 'auto', // 'auto', '1080p', '720p', '480p', etc.
|
|
403
|
-
language: null, // language of the player (default english)
|
|
404
|
-
pauseClick: true, // the player should be paused when click over the video area
|
|
405
|
-
doubleTapPause: true, // first tap (or click) show the controlbar, second tap (or click) pause
|
|
406
|
-
brandLogoEnabled: false, // Enable/disable brand logo
|
|
407
|
-
brandLogoUrl: '', // URL for brand logo image
|
|
408
|
-
brandLogoLinkUrl: '', // Optional URL to open when clicking the logo
|
|
409
|
-
playlistEnabled: true, // Enable/disable playlist detection
|
|
410
|
-
playlistAutoPlay: true, // Auto-play next video when current ends
|
|
411
|
-
playlistLoop: false, // Loop playlist when reaching the end
|
|
412
|
-
loop: false, // Loop video when it ends (restart from beginning)
|
|
413
|
-
volumeSlider: 'horizontal', //volume slider type: 'horizontal' or 'vertical'
|
|
414
|
-
// WATERMARK OVERLAY
|
|
415
|
-
watermarkUrl: '', // URL of watermark image
|
|
416
|
-
watermarkLink: '', // Optional URL to open when clicking watermark
|
|
417
|
-
watermarkPosition: 'bottomright', // Position: topleft, topright, bottomleft, bottomright
|
|
418
|
-
watermarkTitle: '', // Optional tooltip title
|
|
419
|
-
hideWatermark: true, // Hide watermark with controls (default: true)
|
|
420
|
-
// ADAPTIVE STREAMING SUPPORT
|
|
421
|
-
adaptiveStreaming: false, // Enable DASH/HLS adaptive streaming
|
|
422
|
-
dashLibUrl: 'https://cdn.dashjs.org/latest/dash.all.min.js', // Dash.js library URL
|
|
423
|
-
hlsLibUrl: 'https://cdn.jsdelivr.net/npm/hls.js@latest', // HLS.js library URL
|
|
424
|
-
adaptiveQualityControl: true, // Show quality control for adaptive streams
|
|
425
|
-
// AUDIO PLAYER
|
|
426
|
-
audiofile: false,
|
|
427
|
-
audiowave: false,
|
|
428
|
-
// RESOLUTION CONTROL
|
|
429
|
-
resolution: "normal", // "normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"
|
|
430
|
-
...options
|
|
431
|
-
};
|
|
402
|
+
this.video = typeof videoElement === 'string'
|
|
403
|
+
? document.getElementById(videoElement)
|
|
404
|
+
: videoElement;
|
|
432
405
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
//
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
406
|
+
if (!this.video) {
|
|
407
|
+
throw new Error('Video element not found: ' + videoElement);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
this.options = {
|
|
411
|
+
showQualitySelector: true,
|
|
412
|
+
showSpeedControl: true,
|
|
413
|
+
showFullscreen: true,
|
|
414
|
+
showPictureInPicture: true,
|
|
415
|
+
showSubtitles: true,
|
|
416
|
+
subtitlesEnabled: false,
|
|
417
|
+
autoHide: true,
|
|
418
|
+
autoHideDelay: 3000,
|
|
419
|
+
poster: null, // URL of poster image
|
|
420
|
+
showPosterOnEnd: false, // Show poster again when video ends
|
|
421
|
+
keyboardControls: true,
|
|
422
|
+
showSeekTooltip: true,
|
|
423
|
+
showTitleOverlay: false,
|
|
424
|
+
videoTitle: '',
|
|
425
|
+
persistentTitle: false,
|
|
426
|
+
debug: false, // Enable/disable debug logging
|
|
427
|
+
autoplay: false, // if video should autoplay at start
|
|
428
|
+
defaultQuality: 'auto', // 'auto', '1080p', '720p', '480p', etc.
|
|
429
|
+
language: null, // language of the player (default english)
|
|
430
|
+
pauseClick: true, // the player should be paused when click over the video area
|
|
431
|
+
doubleTapPause: true, // first tap (or click) show the controlbar, second tap (or click) pause
|
|
432
|
+
brandLogoEnabled: false, // Enable/disable brand logo
|
|
433
|
+
brandLogoUrl: '', // URL for brand logo image
|
|
434
|
+
brandLogoLinkUrl: '', // Optional URL to open when clicking the logo
|
|
435
|
+
playlistEnabled: true, // Enable/disable playlist detection
|
|
436
|
+
playlistAutoPlay: true, // Auto-play next video when current ends
|
|
437
|
+
playlistLoop: false, // Loop playlist when reaching the end
|
|
438
|
+
loop: false, // Loop video when it ends (restart from beginning)
|
|
439
|
+
volumeSlider: 'show', // Mobile volume slider: 'show' (horizontal popup) or 'hide' (no slider on mobile)
|
|
440
|
+
// WATERMARK OVERLAY
|
|
441
|
+
watermarkUrl: '', // URL of watermark image
|
|
442
|
+
watermarkLink: '', // Optional URL to open when clicking watermark
|
|
443
|
+
watermarkPosition: 'bottomright', // Position: topleft, topright, bottomleft, bottomright
|
|
444
|
+
watermarkTitle: '', // Optional tooltip title
|
|
445
|
+
hideWatermark: true, // Hide watermark with controls (default: true)
|
|
446
|
+
// ADAPTIVE STREAMING SUPPORT
|
|
447
|
+
adaptiveStreaming: false, // Enable DASH/HLS adaptive streaming
|
|
448
|
+
dashLibUrl: 'https://cdn.dashjs.org/latest/dash.all.min.js', // Dash.js library URL
|
|
449
|
+
hlsLibUrl: 'https://cdn.jsdelivr.net/npm/hls.js@latest', // HLS.js library URL
|
|
450
|
+
adaptiveQualityControl: true, // Show quality control for adaptive streams
|
|
451
|
+
// AUDIO PLAYER
|
|
452
|
+
audiofile: false,
|
|
453
|
+
audiowave: false,
|
|
454
|
+
// RESOLUTION CONTROL
|
|
455
|
+
resolution: "normal", // "normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"
|
|
456
|
+
...options
|
|
457
|
+
};
|
|
459
458
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
459
|
+
this.isUserSeeking = false;
|
|
460
|
+
this.controlsTimeout = null;
|
|
461
|
+
this.titleTimeout = null;
|
|
462
|
+
this.currentQualityIndex = 0;
|
|
463
|
+
this.qualities = [];
|
|
464
|
+
this.originalSources = [];
|
|
465
|
+
this.setupMenuToggles(); // Initialize menu toggle system
|
|
466
|
+
this.isPiPSupported = this.checkPiPSupport();
|
|
467
|
+
this.seekTooltip = null;
|
|
468
|
+
this.titleOverlay = null;
|
|
469
|
+
this.isPlayerReady = false;
|
|
470
|
+
|
|
471
|
+
// Subtitle management
|
|
472
|
+
this.textTracks = [];
|
|
473
|
+
this.currentSubtitleTrack = null;
|
|
474
|
+
this.subtitlesEnabled = false;
|
|
475
|
+
this.customSubtitleRenderer = null;
|
|
463
476
|
|
|
464
|
-
|
|
465
|
-
|
|
477
|
+
// Chapter management
|
|
478
|
+
this.chapters = [];
|
|
479
|
+
this.chapterMarkersContainer = null;
|
|
480
|
+
this.chapterTooltip = null;
|
|
466
481
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
this.autoHideInitialized = false;
|
|
482
|
+
// Dual quality indicator management
|
|
483
|
+
this.selectedQuality = this.options.defaultQuality || 'auto';
|
|
484
|
+
this.currentPlayingQuality = null;
|
|
485
|
+
this.qualityMonitorInterval = null;
|
|
472
486
|
|
|
473
|
-
|
|
474
|
-
|
|
487
|
+
// Quality change management
|
|
488
|
+
this.qualityChangeTimeout = null;
|
|
489
|
+
this.isChangingQuality = false;
|
|
490
|
+
|
|
491
|
+
// Quality debug
|
|
492
|
+
this.debugQuality = false;
|
|
493
|
+
|
|
494
|
+
// Auto-hide system
|
|
495
|
+
this.autoHideTimer = null;
|
|
496
|
+
this.mouseOverControls = false;
|
|
497
|
+
this.autoHideDebug = false;
|
|
498
|
+
this.autoHideInitialized = false;
|
|
499
|
+
|
|
500
|
+
// Poster management
|
|
501
|
+
this.posterOverlay = null;
|
|
475
502
|
|
|
476
503
|
// Watermark overlay
|
|
477
504
|
this.watermarkElement = null;
|
|
478
505
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
506
|
+
// Custom event system
|
|
507
|
+
this.eventCallbacks = {
|
|
508
|
+
'played': [],
|
|
509
|
+
'paused': [],
|
|
510
|
+
'subtitlechange': [],
|
|
511
|
+
'chapterchange': [],
|
|
512
|
+
'pipchange': [],
|
|
513
|
+
'fullscreenchange': [],
|
|
514
|
+
'speedchange': [],
|
|
515
|
+
'timeupdate': [],
|
|
516
|
+
'volumechange': [],
|
|
517
|
+
'qualitychange': [],
|
|
518
|
+
'playlistchange': [],
|
|
519
|
+
'ended': []
|
|
520
|
+
};
|
|
494
521
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
522
|
+
// Playlist management
|
|
523
|
+
this.playlist = [];
|
|
524
|
+
this.currentPlaylistIndex = -1;
|
|
525
|
+
this.playlistId = null;
|
|
526
|
+
this.isPlaylistActive = false;
|
|
527
|
+
|
|
528
|
+
// Adaptive streaming management
|
|
529
|
+
this.dashPlayer = null;
|
|
530
|
+
this.hlsPlayer = null;
|
|
531
|
+
this.adaptiveStreamingType = null; // 'dash', 'hls', or null
|
|
532
|
+
this.isAdaptiveStream = false;
|
|
533
|
+
this.adaptiveQualities = [];
|
|
534
|
+
this.librariesLoaded = {
|
|
535
|
+
dash: false,
|
|
536
|
+
hls: false
|
|
537
|
+
};
|
|
511
538
|
|
|
512
|
-
|
|
539
|
+
this.lastTimeUpdate = 0; // For throttling timeupdate events
|
|
513
540
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
541
|
+
if (this.options.language && this.isI18nAvailable()) {
|
|
542
|
+
VideoPlayerTranslations.setLanguage(this.options.language);
|
|
543
|
+
}
|
|
544
|
+
// Apply autoplay if enabled
|
|
545
|
+
if (options.autoplay) {
|
|
546
|
+
this.video.autoplay = true;
|
|
547
|
+
}
|
|
521
548
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
if (this.container) {
|
|
534
|
-
this.container.classList.add('audio-player');
|
|
535
|
-
}
|
|
536
|
-
if (this.options.audiowave) {
|
|
537
|
-
this.initAudioWave();
|
|
538
|
-
}
|
|
549
|
+
try {
|
|
550
|
+
this.interceptAutoLoading();
|
|
551
|
+
this.createPlayerStructure();
|
|
552
|
+
this.initializeElements();
|
|
553
|
+
// audio player adaptation
|
|
554
|
+
this.adaptToAudioFile = function () {
|
|
555
|
+
if (this.options.audiofile) {
|
|
556
|
+
// Nascondere video
|
|
557
|
+
if (this.video) {
|
|
558
|
+
this.video.style.display = 'none';
|
|
539
559
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
560
|
+
if (this.container) {
|
|
561
|
+
this.container.classList.add('audio-player');
|
|
562
|
+
}
|
|
563
|
+
if (this.options.audiowave) {
|
|
564
|
+
this.initAudioWave();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
// Audio wave with Web Audio API
|
|
569
|
+
this.initAudioWave = function () {
|
|
570
|
+
if (!this.video) return;
|
|
571
|
+
|
|
572
|
+
this.audioWaveCanvas = document.createElement('canvas');
|
|
573
|
+
this.audioWaveCanvas.className = 'audio-wave-canvas';
|
|
574
|
+
this.container.appendChild(this.audioWaveCanvas);
|
|
575
|
+
|
|
576
|
+
const canvasCtx = this.audioWaveCanvas.getContext('2d');
|
|
577
|
+
const WIDTH = this.audioWaveCanvas.width = this.container.clientWidth;
|
|
578
|
+
const HEIGHT = this.audioWaveCanvas.height = 60; // altezza onda audio
|
|
579
|
+
|
|
580
|
+
// Setup Web Audio API
|
|
581
|
+
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
582
|
+
this.audioCtx = new AudioContext();
|
|
583
|
+
this.analyser = this.audioCtx.createAnalyser();
|
|
584
|
+
this.source = this.audioCtx.createMediaElementSource(this.video);
|
|
585
|
+
this.source.connect(this.analyser);
|
|
586
|
+
this.analyser.connect(this.audioCtx.destination);
|
|
587
|
+
|
|
588
|
+
this.analyser.fftSize = 2048;
|
|
589
|
+
const bufferLength = this.analyser.fftSize;
|
|
590
|
+
const dataArray = new Uint8Array(bufferLength);
|
|
591
|
+
|
|
592
|
+
// canvas
|
|
593
|
+
const draw = () => {
|
|
594
|
+
requestAnimationFrame(draw);
|
|
595
|
+
this.analyser.getByteTimeDomainData(dataArray);
|
|
596
|
+
|
|
597
|
+
canvasCtx.fillStyle = '#222';
|
|
598
|
+
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
|
|
599
|
+
|
|
600
|
+
canvasCtx.lineWidth = 2;
|
|
601
|
+
canvasCtx.strokeStyle = '#33ccff';
|
|
602
|
+
canvasCtx.beginPath();
|
|
603
|
+
|
|
604
|
+
const sliceWidth = WIDTH / bufferLength;
|
|
605
|
+
let x = 0;
|
|
606
|
+
|
|
607
|
+
for (let i = 0; i < bufferLength; i++) {
|
|
608
|
+
const v = dataArray[i] / 128.0;
|
|
609
|
+
const y = v * HEIGHT / 2;
|
|
610
|
+
|
|
611
|
+
if (i === 0) {
|
|
612
|
+
canvasCtx.moveTo(x, y);
|
|
613
|
+
} else {
|
|
614
|
+
canvasCtx.lineTo(x, y);
|
|
591
615
|
}
|
|
592
|
-
canvasCtx.lineTo(WIDTH, HEIGHT / 2);
|
|
593
|
-
canvasCtx.stroke();
|
|
594
|
-
};
|
|
595
616
|
|
|
596
|
-
|
|
617
|
+
x += sliceWidth;
|
|
618
|
+
}
|
|
619
|
+
canvasCtx.lineTo(WIDTH, HEIGHT / 2);
|
|
620
|
+
canvasCtx.stroke();
|
|
597
621
|
};
|
|
598
|
-
this.adaptToAudioFile();
|
|
599
|
-
this.bindEvents();
|
|
600
622
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
623
|
+
draw();
|
|
624
|
+
};
|
|
625
|
+
this.adaptToAudioFile();
|
|
626
|
+
this.bindEvents();
|
|
604
627
|
|
|
605
|
-
|
|
606
|
-
this.
|
|
607
|
-
|
|
608
|
-
this.markPlayerReady();
|
|
609
|
-
this.initializePluginSystem();
|
|
610
|
-
this.restoreSourcesAsync();
|
|
628
|
+
if (this.options.keyboardControls) {
|
|
629
|
+
this.setupKeyboardControls();
|
|
630
|
+
}
|
|
611
631
|
|
|
612
|
-
|
|
613
|
-
|
|
632
|
+
this.updateVolumeSliderVisual();
|
|
633
|
+
this.initVolumeTooltip();
|
|
634
|
+
this.updateTooltips();
|
|
635
|
+
this.markPlayerReady();
|
|
636
|
+
this.initializePluginSystem();
|
|
637
|
+
this.restoreSourcesAsync();
|
|
614
638
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
this.initializePoster();
|
|
618
|
-
this.initializeWatermark();
|
|
639
|
+
this.initializeSubtitles();
|
|
640
|
+
this.initializeQualityMonitoring();
|
|
619
641
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
642
|
+
this.initializeResolution();
|
|
643
|
+
this.initializeChapters();
|
|
644
|
+
this.initializePoster();
|
|
645
|
+
this.initializeWatermark();
|
|
624
646
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
isPlaying: !this.isPaused(),
|
|
628
|
-
isPaused: this.isPaused(),
|
|
629
|
-
currentTime: this.getCurrentTime(),
|
|
630
|
-
duration: this.getDuration(),
|
|
631
|
-
volume: this.getVolume(),
|
|
632
|
-
isMuted: this.isMuted(),
|
|
633
|
-
playbackRate: this.getPlaybackRate(),
|
|
634
|
-
isFullscreen: this.isFullscreenActive(),
|
|
635
|
-
isPictureInPicture: this.isPictureInPictureActive(),
|
|
636
|
-
subtitlesEnabled: this.isSubtitlesEnabled(),
|
|
637
|
-
currentSubtitle: this.getCurrentSubtitleTrack(),
|
|
638
|
-
selectedQuality: this.getSelectedQuality(),
|
|
639
|
-
currentQuality: this.getCurrentPlayingQuality(),
|
|
640
|
-
isAutoQuality: this.isAutoQualityActive()
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
isI18nAvailable() {
|
|
645
|
-
return typeof VideoPlayerTranslations !== 'undefined' &&
|
|
646
|
-
VideoPlayerTranslations !== null &&
|
|
647
|
-
typeof VideoPlayerTranslations.t === 'function';
|
|
647
|
+
} catch (error) {
|
|
648
|
+
if (this.options.debug) console.error('Video player initialization error:', error);
|
|
648
649
|
}
|
|
650
|
+
}
|
|
649
651
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
'subtitles_enable': 'Enable subtitles',
|
|
669
|
-
'subtitles_disable': 'Disable subtitles',
|
|
670
|
-
'subtitles_off': 'Off',
|
|
671
|
-
'auto': 'Auto',
|
|
672
|
-
'brand_logo': 'Brand logo',
|
|
673
|
-
'next_video': 'Next video (N)',
|
|
674
|
-
'prev_video': 'Previous video (P)',
|
|
675
|
-
'playlist_next': 'Next',
|
|
676
|
-
'playlist_prev': 'Previous'
|
|
677
|
-
};
|
|
652
|
+
getPlayerState() {
|
|
653
|
+
return {
|
|
654
|
+
isPlaying: !this.isPaused(),
|
|
655
|
+
isPaused: this.isPaused(),
|
|
656
|
+
currentTime: this.getCurrentTime(),
|
|
657
|
+
duration: this.getDuration(),
|
|
658
|
+
volume: this.getVolume(),
|
|
659
|
+
isMuted: this.isMuted(),
|
|
660
|
+
playbackRate: this.getPlaybackRate(),
|
|
661
|
+
isFullscreen: this.isFullscreenActive(),
|
|
662
|
+
isPictureInPicture: this.isPictureInPictureActive(),
|
|
663
|
+
subtitlesEnabled: this.isSubtitlesEnabled(),
|
|
664
|
+
currentSubtitle: this.getCurrentSubtitleTrack(),
|
|
665
|
+
selectedQuality: this.getSelectedQuality(),
|
|
666
|
+
currentQuality: this.getCurrentPlayingQuality(),
|
|
667
|
+
isAutoQuality: this.isAutoQualityActive()
|
|
668
|
+
};
|
|
669
|
+
}
|
|
678
670
|
|
|
679
|
-
|
|
680
|
-
|
|
671
|
+
isI18nAvailable() {
|
|
672
|
+
return typeof VideoPlayerTranslations !== 'undefined' &&
|
|
673
|
+
VideoPlayerTranslations !== null &&
|
|
674
|
+
typeof VideoPlayerTranslations.t === 'function';
|
|
675
|
+
}
|
|
681
676
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
677
|
+
t(key) {
|
|
678
|
+
if (this.isI18nAvailable()) {
|
|
679
|
+
try {
|
|
680
|
+
return VideoPlayerTranslations.t(key);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
if (this.options.debug) console.warn('Translation error:', error);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const fallback = {
|
|
687
|
+
'play_pause': 'Play/Pause (Space)',
|
|
688
|
+
'mute_unmute': 'Mute/Unmute (M)',
|
|
689
|
+
'volume': 'Volume',
|
|
690
|
+
'playback_speed': 'Playback speed',
|
|
691
|
+
'video_quality': 'Video quality',
|
|
692
|
+
'picture_in_picture': 'Picture-in-Picture (P)',
|
|
693
|
+
'fullscreen': 'Fullscreen (F)',
|
|
694
|
+
'subtitles': 'Subtitles (S)',
|
|
695
|
+
'subtitles_enable': 'Enable subtitles',
|
|
696
|
+
'subtitles_disable': 'Disable subtitles',
|
|
697
|
+
'subtitles_off': 'Off',
|
|
698
|
+
'auto': 'Auto',
|
|
699
|
+
'brand_logo': 'Brand logo',
|
|
700
|
+
'next_video': 'Next video (N)',
|
|
701
|
+
'prev_video': 'Previous video (P)',
|
|
702
|
+
'playlist_next': 'Next',
|
|
703
|
+
'playlist_prev': 'Previous'
|
|
704
|
+
};
|
|
685
705
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
this.video.autoplay = false;
|
|
706
|
+
return fallback[key] || key;
|
|
707
|
+
}
|
|
689
708
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
this.video.src = '';
|
|
694
|
-
}
|
|
709
|
+
interceptAutoLoading() {
|
|
710
|
+
this.saveOriginalSources();
|
|
711
|
+
this.disableSources();
|
|
695
712
|
|
|
696
|
-
|
|
713
|
+
this.video.preload = 'none';
|
|
714
|
+
this.video.controls = false;
|
|
715
|
+
this.video.autoplay = false;
|
|
697
716
|
|
|
698
|
-
|
|
717
|
+
if (this.video.src && this.video.src !== window.location.href) {
|
|
718
|
+
this.originalSrc = this.video.src;
|
|
719
|
+
this.video.removeAttribute('src');
|
|
720
|
+
this.video.src = '';
|
|
699
721
|
}
|
|
700
722
|
|
|
701
|
-
|
|
702
|
-
const sources = this.video.querySelectorAll('source');
|
|
703
|
-
this.originalSources = [];
|
|
723
|
+
this.hideNativePlayer();
|
|
704
724
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
this.originalSources.push({
|
|
708
|
-
element: source,
|
|
709
|
-
src: source.src,
|
|
710
|
-
type: source.type || 'video/mp4',
|
|
711
|
-
quality: source.getAttribute('data-quality') || `quality-${index}`,
|
|
712
|
-
index: index
|
|
713
|
-
});
|
|
714
|
-
}
|
|
715
|
-
});
|
|
725
|
+
if (this.options.debug) console.log('📁 Sources temporarily disabled to prevent blocking');
|
|
726
|
+
}
|
|
716
727
|
|
|
717
|
-
|
|
718
|
-
|
|
728
|
+
saveOriginalSources() {
|
|
729
|
+
const sources = this.video.querySelectorAll('source');
|
|
730
|
+
this.originalSources = [];
|
|
731
|
+
|
|
732
|
+
sources.forEach((source, index) => {
|
|
733
|
+
if (source.src) {
|
|
734
|
+
this.originalSources.push({
|
|
735
|
+
element: source,
|
|
736
|
+
src: source.src,
|
|
737
|
+
type: source.type || 'video/mp4',
|
|
738
|
+
quality: source.getAttribute('data-quality') || `quality-${index}`,
|
|
739
|
+
index: index
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
});
|
|
719
743
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
sources.forEach(source => {
|
|
723
|
-
if (source.src) {
|
|
724
|
-
source.removeAttribute('src');
|
|
725
|
-
}
|
|
726
|
-
});
|
|
727
|
-
}
|
|
744
|
+
if (this.options.debug) console.log(`📁 Saved ${this.originalSources.length} sources originali:`, this.originalSources);
|
|
745
|
+
}
|
|
728
746
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
747
|
+
disableSources() {
|
|
748
|
+
const sources = this.video.querySelectorAll('source');
|
|
749
|
+
sources.forEach(source => {
|
|
750
|
+
if (source.src) {
|
|
751
|
+
source.removeAttribute('src');
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
}
|
|
734
755
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (this.originalSrc) {
|
|
741
|
-
adaptiveSource = this.originalSrc;
|
|
742
|
-
} else if (this.originalSources.length > 0) {
|
|
743
|
-
// Check if any source is adaptive
|
|
744
|
-
const firstSource = this.originalSources[0];
|
|
745
|
-
if (firstSource.src && this.detectStreamType(firstSource.src)) {
|
|
746
|
-
adaptiveSource = firstSource.src;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
756
|
+
restoreSourcesAsync() {
|
|
757
|
+
setTimeout(() => {
|
|
758
|
+
this.restoreSources();
|
|
759
|
+
}, 200);
|
|
760
|
+
}
|
|
749
761
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
762
|
+
async restoreSources() {
|
|
763
|
+
try {
|
|
764
|
+
// Check for adaptive streaming first
|
|
765
|
+
let adaptiveSource = null;
|
|
766
|
+
|
|
767
|
+
if (this.originalSrc) {
|
|
768
|
+
adaptiveSource = this.originalSrc;
|
|
769
|
+
} else if (this.originalSources.length > 0) {
|
|
770
|
+
// Check if any source is adaptive
|
|
771
|
+
const firstSource = this.originalSources[0];
|
|
772
|
+
if (firstSource.src && this.detectStreamType(firstSource.src)) {
|
|
773
|
+
adaptiveSource = firstSource.src;
|
|
757
774
|
}
|
|
775
|
+
}
|
|
758
776
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
777
|
+
// Initialize adaptive streaming if detected
|
|
778
|
+
if (adaptiveSource && this.options.adaptiveStreaming) {
|
|
779
|
+
const adaptiveInitialized = await this.initializeAdaptiveStreaming(adaptiveSource);
|
|
780
|
+
if (adaptiveInitialized) {
|
|
781
|
+
if (this.options.debug) console.log('📡 Adaptive streaming initialized');
|
|
782
|
+
return;
|
|
762
783
|
}
|
|
784
|
+
}
|
|
763
785
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
this.qualities = this.originalSources.map(s => ({
|
|
771
|
-
src: s.src,
|
|
772
|
-
quality: s.quality,
|
|
773
|
-
type: s.type
|
|
774
|
-
}));
|
|
786
|
+
// Fallback to traditional sources
|
|
787
|
+
if (this.originalSrc) {
|
|
788
|
+
this.video.src = this.originalSrc;
|
|
789
|
+
}
|
|
775
790
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
quality: 'default',
|
|
780
|
-
type: 'video/mp4'
|
|
781
|
-
});
|
|
791
|
+
this.originalSources.forEach(sourceData => {
|
|
792
|
+
if (sourceData.element && sourceData.src) {
|
|
793
|
+
sourceData.element.src = sourceData.src;
|
|
782
794
|
}
|
|
795
|
+
});
|
|
783
796
|
|
|
784
|
-
|
|
785
|
-
|
|
797
|
+
this.qualities = this.originalSources.map(s => ({
|
|
798
|
+
src: s.src,
|
|
799
|
+
quality: s.quality,
|
|
800
|
+
type: s.type
|
|
801
|
+
}));
|
|
786
802
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
803
|
+
if (this.originalSrc && this.qualities.length === 0) {
|
|
804
|
+
this.qualities.push({
|
|
805
|
+
src: this.originalSrc,
|
|
806
|
+
quality: 'default',
|
|
807
|
+
type: 'video/mp4'
|
|
808
|
+
});
|
|
809
|
+
}
|
|
795
810
|
|
|
796
|
-
|
|
811
|
+
if (this.qualities.length > 0) {
|
|
812
|
+
this.video.load();
|
|
797
813
|
|
|
798
|
-
|
|
799
|
-
|
|
814
|
+
// CRITICAL: Re-initialize subtitles AFTER video.load() completes
|
|
815
|
+
this.video.addEventListener('loadedmetadata', () => {
|
|
816
|
+
setTimeout(() => {
|
|
817
|
+
this.reinitializeSubtitles();
|
|
818
|
+
if (this.options.debug) console.log('🔄 Subtitles re-initialized after video load');
|
|
819
|
+
}, 300);
|
|
820
|
+
}, { once: true });
|
|
800
821
|
}
|
|
822
|
+
|
|
823
|
+
if (this.options.debug) console.log('✅ Sources ripristinate:', this.qualities);
|
|
824
|
+
|
|
825
|
+
} catch (error) {
|
|
826
|
+
if (this.options.debug) console.error('❌ Errore ripristino sources:', error);
|
|
801
827
|
}
|
|
828
|
+
}
|
|
802
829
|
|
|
803
830
|
reinitializeSubtitles() {
|
|
804
831
|
if (this.options.debug) console.log('🔄 Re-initializing subtitles...');
|
|
@@ -844,630 +871,706 @@ getDefaultSubtitleTrack() {
|
|
|
844
871
|
return -1;
|
|
845
872
|
}
|
|
846
873
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
874
|
+
markPlayerReady() {
|
|
875
|
+
setTimeout(() => {
|
|
876
|
+
this.isPlayerReady = true;
|
|
877
|
+
if (this.container) {
|
|
878
|
+
this.container.classList.add('player-initialized');
|
|
879
|
+
}
|
|
853
880
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
881
|
+
if (this.video) {
|
|
882
|
+
this.video.style.visibility = '';
|
|
883
|
+
this.video.style.opacity = '';
|
|
884
|
+
this.video.style.pointerEvents = '';
|
|
885
|
+
}
|
|
859
886
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
887
|
+
// INITIALIZE AUTO-HIDE AFTER EVERYTHING IS READY
|
|
888
|
+
setTimeout(() => {
|
|
889
|
+
if (this.options.autoHide && !this.autoHideInitialized) {
|
|
890
|
+
this.initAutoHide();
|
|
891
|
+
}
|
|
865
892
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
893
|
+
// Fix: Apply default quality (auto or specific)
|
|
894
|
+
if (this.selectedQuality && this.qualities && this.qualities.length > 0) {
|
|
895
|
+
if (this.options.debug) console.log(`🎯 Applying defaultQuality: "${this.selectedQuality}"`);
|
|
869
896
|
|
|
870
|
-
|
|
871
|
-
|
|
897
|
+
if (this.selectedQuality === 'auto') {
|
|
898
|
+
this.enableAutoQuality();
|
|
899
|
+
} else {
|
|
900
|
+
// Check if requested quality is available
|
|
901
|
+
const requestedQuality = this.qualities.find(q => q.quality === this.selectedQuality);
|
|
902
|
+
if (requestedQuality) {
|
|
903
|
+
if (this.options.debug) console.log(`✅ Quality "${this.selectedQuality}" available`);
|
|
904
|
+
this.setQuality(this.selectedQuality);
|
|
872
905
|
} else {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
if (this.options.debug) console.log(`✅ Quality "${this.selectedQuality}" available`);
|
|
877
|
-
this.setQuality(this.selectedQuality);
|
|
878
|
-
} else {
|
|
879
|
-
if (this.options.debug) console.warn(`⚠️ Quality "${this.selectedQuality}" not available - fallback to auto`);
|
|
880
|
-
if (this.options.debug) console.log('📋 Available qualities:', this.qualities.map(q => q.quality));
|
|
881
|
-
this.enableAutoQuality();
|
|
882
|
-
}
|
|
906
|
+
if (this.options.debug) console.warn(`⚠️ Quality "${this.selectedQuality}" not available - fallback to auto`);
|
|
907
|
+
if (this.options.debug) console.log('📋 Available qualities:', this.qualities.map(q => q.quality));
|
|
908
|
+
this.enableAutoQuality();
|
|
883
909
|
}
|
|
884
910
|
}
|
|
911
|
+
}
|
|
885
912
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
913
|
+
// Autoplay
|
|
914
|
+
if (this.options.autoplay) {
|
|
915
|
+
if (this.options.debug) console.log('🎬 Autoplay enabled');
|
|
916
|
+
setTimeout(() => {
|
|
917
|
+
this.video.play().catch(error => {
|
|
918
|
+
if (this.options.debug) console.warn('⚠️ Autoplay blocked:', error);
|
|
919
|
+
});
|
|
920
|
+
}, 100);
|
|
921
|
+
}
|
|
922
|
+
}, 200);
|
|
896
923
|
|
|
897
|
-
|
|
898
|
-
|
|
924
|
+
}, 100);
|
|
925
|
+
}
|
|
899
926
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
927
|
+
createPlayerStructure() {
|
|
928
|
+
let wrapper = this.video.closest('.video-wrapper');
|
|
929
|
+
if (!wrapper) {
|
|
930
|
+
wrapper = document.createElement('div');
|
|
931
|
+
wrapper.className = 'video-wrapper';
|
|
932
|
+
this.video.parentNode.insertBefore(wrapper, this.video);
|
|
933
|
+
wrapper.appendChild(this.video);
|
|
934
|
+
}
|
|
908
935
|
|
|
909
|
-
|
|
936
|
+
this.container = wrapper;
|
|
910
937
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
938
|
+
this.createInitialLoading();
|
|
939
|
+
this.createLoadingOverlay();
|
|
940
|
+
this.collectVideoQualities();
|
|
941
|
+
this.createControls();
|
|
942
|
+
this.createBrandLogo();
|
|
943
|
+
this.detectPlaylist();
|
|
917
944
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
945
|
+
if (this.options.showTitleOverlay) {
|
|
946
|
+
this.createTitleOverlay();
|
|
921
947
|
}
|
|
948
|
+
}
|
|
922
949
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
950
|
+
createInitialLoading() {
|
|
951
|
+
const initialLoader = document.createElement('div');
|
|
952
|
+
initialLoader.className = 'initial-loading';
|
|
953
|
+
initialLoader.innerHTML = '<div class="loading-spinner"></div>';
|
|
954
|
+
this.container.appendChild(initialLoader);
|
|
955
|
+
this.initialLoading = initialLoader;
|
|
956
|
+
}
|
|
930
957
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
958
|
+
collectVideoQualities() {
|
|
959
|
+
if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
|
|
960
|
+
}
|
|
934
961
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
962
|
+
createLoadingOverlay() {
|
|
963
|
+
const overlay = document.createElement('div');
|
|
964
|
+
overlay.className = 'loading-overlay';
|
|
965
|
+
overlay.id = 'loadingOverlay-' + this.getUniqueId();
|
|
966
|
+
overlay.innerHTML = '<div class="loading-spinner"></div>';
|
|
967
|
+
this.container.appendChild(overlay);
|
|
968
|
+
this.loadingOverlay = overlay;
|
|
969
|
+
}
|
|
943
970
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
971
|
+
createTitleOverlay() {
|
|
972
|
+
const overlay = document.createElement('div');
|
|
973
|
+
overlay.className = 'title-overlay';
|
|
974
|
+
overlay.id = 'titleOverlay-' + this.getUniqueId();
|
|
948
975
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
976
|
+
const titleText = document.createElement('h2');
|
|
977
|
+
titleText.className = 'title-text';
|
|
978
|
+
titleText.textContent = this.options.videoTitle || '';
|
|
952
979
|
|
|
953
|
-
|
|
980
|
+
overlay.appendChild(titleText);
|
|
954
981
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
982
|
+
if (this.controls) {
|
|
983
|
+
this.container.insertBefore(overlay, this.controls);
|
|
984
|
+
} else {
|
|
985
|
+
this.container.appendChild(overlay);
|
|
986
|
+
}
|
|
960
987
|
|
|
961
|
-
|
|
988
|
+
this.titleOverlay = overlay;
|
|
962
989
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
}
|
|
990
|
+
if (this.options.persistentTitle && this.options.videoTitle) {
|
|
991
|
+
this.showTitleOverlay();
|
|
966
992
|
}
|
|
993
|
+
}
|
|
967
994
|
|
|
968
|
-
|
|
969
|
-
|
|
995
|
+
updateTooltips() {
|
|
996
|
+
if (!this.controls) return;
|
|
970
997
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
998
|
+
try {
|
|
999
|
+
this.controls.querySelectorAll('[data-tooltip]').forEach(element => {
|
|
1000
|
+
const key = element.getAttribute('data-tooltip');
|
|
1001
|
+
element.title = this.t(key);
|
|
1002
|
+
});
|
|
976
1003
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
} catch (error) {
|
|
982
|
-
if (this.options.debug) console.warn('Errore aggiornamento tooltip:', error);
|
|
1004
|
+
const autoOption = this.controls.querySelector('.quality-option[data-quality="auto"]');
|
|
1005
|
+
if (autoOption) {
|
|
1006
|
+
autoOption.textContent = this.t('auto');
|
|
983
1007
|
}
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
if (this.options.debug) console.warn('Errore aggiornamento tooltip:', error);
|
|
984
1010
|
}
|
|
1011
|
+
}
|
|
985
1012
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
}
|
|
993
|
-
} catch (error) {
|
|
994
|
-
if (this.options.debug) console.warn('Errore cambio lingua:', error);
|
|
1013
|
+
setLanguage(lang) {
|
|
1014
|
+
if (this.isI18nAvailable()) {
|
|
1015
|
+
try {
|
|
1016
|
+
if (VideoPlayerTranslations.setLanguage(lang)) {
|
|
1017
|
+
this.updateTooltips();
|
|
1018
|
+
return true;
|
|
995
1019
|
}
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
if (this.options.debug) console.warn('Errore cambio lingua:', error);
|
|
996
1022
|
}
|
|
997
|
-
return false;
|
|
998
1023
|
}
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
999
1026
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1027
|
+
setVideoTitle(title) {
|
|
1028
|
+
this.options.videoTitle = title || '';
|
|
1002
1029
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1030
|
+
if (this.titleOverlay) {
|
|
1031
|
+
const titleElement = this.titleOverlay.querySelector('.title-text');
|
|
1032
|
+
if (titleElement) {
|
|
1033
|
+
titleElement.textContent = this.options.videoTitle;
|
|
1034
|
+
}
|
|
1008
1035
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1036
|
+
if (title) {
|
|
1037
|
+
this.showTitleOverlay();
|
|
1011
1038
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1039
|
+
if (!this.options.persistentTitle) {
|
|
1040
|
+
this.clearTitleTimeout();
|
|
1041
|
+
this.titleTimeout = setTimeout(() => {
|
|
1042
|
+
this.hideTitleOverlay();
|
|
1043
|
+
}, 3000);
|
|
1018
1044
|
}
|
|
1019
1045
|
}
|
|
1020
|
-
|
|
1021
|
-
return this;
|
|
1022
1046
|
}
|
|
1023
1047
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1048
|
+
return this;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
getVideoTitle() {
|
|
1052
|
+
return this.options.videoTitle;
|
|
1053
|
+
}
|
|
1027
1054
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1055
|
+
setPersistentTitle(persistent) {
|
|
1056
|
+
this.options.persistentTitle = persistent;
|
|
1030
1057
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1058
|
+
if (this.titleOverlay && this.options.videoTitle) {
|
|
1059
|
+
if (persistent) {
|
|
1060
|
+
this.showTitleOverlay();
|
|
1061
|
+
this.clearTitleTimeout();
|
|
1062
|
+
} else {
|
|
1063
|
+
this.titleOverlay.classList.remove('persistent');
|
|
1064
|
+
if (this.titleOverlay.classList.contains('show')) {
|
|
1034
1065
|
this.clearTitleTimeout();
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
this.clearTitleTimeout();
|
|
1039
|
-
this.titleTimeout = setTimeout(() => {
|
|
1040
|
-
this.hideTitleOverlay();
|
|
1041
|
-
}, 3000);
|
|
1042
|
-
}
|
|
1066
|
+
this.titleTimeout = setTimeout(() => {
|
|
1067
|
+
this.hideTitleOverlay();
|
|
1068
|
+
}, 3000);
|
|
1043
1069
|
}
|
|
1044
1070
|
}
|
|
1045
|
-
|
|
1046
|
-
return this;
|
|
1047
1071
|
}
|
|
1048
1072
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
this.options.showTitleOverlay = true;
|
|
1052
|
-
this.createTitleOverlay();
|
|
1053
|
-
}
|
|
1054
|
-
return this;
|
|
1055
|
-
}
|
|
1073
|
+
return this;
|
|
1074
|
+
}
|
|
1056
1075
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
}
|
|
1062
|
-
this.options.showTitleOverlay = false;
|
|
1063
|
-
return this;
|
|
1076
|
+
enableTitleOverlay() {
|
|
1077
|
+
if (!this.titleOverlay && !this.options.showTitleOverlay) {
|
|
1078
|
+
this.options.showTitleOverlay = true;
|
|
1079
|
+
this.createTitleOverlay();
|
|
1064
1080
|
}
|
|
1081
|
+
return this;
|
|
1082
|
+
}
|
|
1065
1083
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1084
|
+
disableTitleOverlay() {
|
|
1085
|
+
if (this.titleOverlay) {
|
|
1086
|
+
this.titleOverlay.remove();
|
|
1087
|
+
this.titleOverlay = null;
|
|
1068
1088
|
}
|
|
1089
|
+
this.options.showTitleOverlay = false;
|
|
1090
|
+
return this;
|
|
1091
|
+
}
|
|
1069
1092
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
this.progressBuffer = this.controls?.querySelector('.progress-buffer');
|
|
1074
|
-
this.progressHandle = this.controls?.querySelector('.progress-handle');
|
|
1075
|
-
this.seekTooltip = this.controls?.querySelector('.seek-tooltip');
|
|
1093
|
+
getUniqueId() {
|
|
1094
|
+
return Math.random().toString(36).substr(2, 9);
|
|
1095
|
+
}
|
|
1076
1096
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1097
|
+
initializeElements() {
|
|
1098
|
+
this.progressContainer = this.controls?.querySelector('.progress-container');
|
|
1099
|
+
this.progressFilled = this.controls?.querySelector('.progress-filled');
|
|
1100
|
+
this.progressBuffer = this.controls?.querySelector('.progress-buffer');
|
|
1101
|
+
this.progressHandle = this.controls?.querySelector('.progress-handle');
|
|
1102
|
+
this.seekTooltip = this.controls?.querySelector('.seek-tooltip');
|
|
1103
|
+
|
|
1104
|
+
this.playPauseBtn = this.controls?.querySelector('.play-pause-btn');
|
|
1105
|
+
this.muteBtn = this.controls?.querySelector('.mute-btn');
|
|
1106
|
+
this.fullscreenBtn = this.controls?.querySelector('.fullscreen-btn');
|
|
1107
|
+
this.speedBtn = this.controls?.querySelector('.speed-btn');
|
|
1108
|
+
this.qualityBtn = this.controls?.querySelector('.quality-btn');
|
|
1109
|
+
this.pipBtn = this.controls?.querySelector('.pip-btn');
|
|
1110
|
+
this.subtitlesBtn = this.controls?.querySelector('.subtitles-btn');
|
|
1111
|
+
this.playlistPrevBtn = this.controls?.querySelector('.playlist-prev-btn');
|
|
1112
|
+
this.playlistNextBtn = this.controls?.querySelector('.playlist-next-btn');
|
|
1113
|
+
|
|
1114
|
+
this.playIcon = this.controls?.querySelector('.play-icon');
|
|
1115
|
+
this.pauseIcon = this.controls?.querySelector('.pause-icon');
|
|
1116
|
+
this.volumeIcon = this.controls?.querySelector('.volume-icon');
|
|
1117
|
+
this.muteIcon = this.controls?.querySelector('.mute-icon');
|
|
1118
|
+
this.fullscreenIcon = this.controls?.querySelector('.fullscreen-icon');
|
|
1119
|
+
this.exitFullscreenIcon = this.controls?.querySelector('.exit-fullscreen-icon');
|
|
1120
|
+
this.pipIcon = this.controls?.querySelector('.pip-icon');
|
|
1121
|
+
this.pipExitIcon = this.controls?.querySelector('.pip-exit-icon');
|
|
1122
|
+
|
|
1123
|
+
this.volumeSlider = this.controls?.querySelector('.volume-slider');
|
|
1124
|
+
this.currentTimeEl = this.controls?.querySelector('.current-time');
|
|
1125
|
+
this.durationEl = this.controls?.querySelector('.duration');
|
|
1126
|
+
this.speedMenu = this.controls?.querySelector('.speed-menu');
|
|
1127
|
+
this.qualityMenu = this.controls?.querySelector('.quality-menu');
|
|
1128
|
+
this.subtitlesMenu = this.controls?.querySelector('.subtitles-menu');
|
|
1129
|
+
}
|
|
1086
1130
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
this.pipExitIcon = this.controls?.querySelector('.pip-exit-icon');
|
|
1131
|
+
// Generic method to close all active menus (works with plugins too)
|
|
1132
|
+
closeAllMenus() {
|
|
1133
|
+
// Find all elements with class ending in '-menu' that have 'active' class
|
|
1134
|
+
const allMenus = this.controls?.querySelectorAll('[class*="-menu"].active');
|
|
1135
|
+
allMenus?.forEach(menu => {
|
|
1136
|
+
menu.classList.remove('active');
|
|
1137
|
+
});
|
|
1095
1138
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}
|
|
1139
|
+
// Remove active state from all control buttons
|
|
1140
|
+
const allButtons = this.controls?.querySelectorAll('.control-btn.active');
|
|
1141
|
+
allButtons?.forEach(btn => {
|
|
1142
|
+
btn.classList.remove('active');
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1103
1145
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1146
|
+
// Generic menu toggle setup (works with core menus and plugin menus)
|
|
1147
|
+
setupMenuToggles() {
|
|
1148
|
+
// Delegate click events to control bar for any button with associated menu
|
|
1149
|
+
if (this.controls) {
|
|
1150
|
+
this.controls.addEventListener('click', (e) => {
|
|
1151
|
+
// Find if clicked element is a control button or inside one
|
|
1152
|
+
const button = e.target.closest('.control-btn');
|
|
1153
|
+
|
|
1154
|
+
if (!button) return;
|
|
1155
|
+
|
|
1156
|
+
// Get button classes to find associated menu
|
|
1157
|
+
const buttonClasses = button.className.split(' ');
|
|
1158
|
+
let menuClass = null;
|
|
1159
|
+
|
|
1160
|
+
// Find if this button has an associated menu (e.g., speed-btn -> speed-menu)
|
|
1161
|
+
for (const cls of buttonClasses) {
|
|
1162
|
+
if (cls.endsWith('-btn')) {
|
|
1163
|
+
const menuName = cls.replace('-btn', '-menu');
|
|
1164
|
+
const menu = this.controls.querySelector('.' + menuName);
|
|
1165
|
+
if (menu) {
|
|
1166
|
+
menuClass = menuName;
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1106
1171
|
|
|
1107
|
-
|
|
1108
|
-
const percentage = Math.round(volume * 100);
|
|
1172
|
+
if (!menuClass) return;
|
|
1109
1173
|
|
|
1110
|
-
|
|
1174
|
+
e.stopPropagation();
|
|
1111
1175
|
|
|
1112
|
-
|
|
1113
|
-
this.
|
|
1114
|
-
|
|
1176
|
+
// Get the menu element
|
|
1177
|
+
const menu = this.controls.querySelector('.' + menuClass);
|
|
1178
|
+
const isOpen = menu.classList.contains('active');
|
|
1179
|
+
|
|
1180
|
+
// Close all menus first
|
|
1181
|
+
this.closeAllMenus();
|
|
1182
|
+
|
|
1183
|
+
// If menu was closed, open it
|
|
1184
|
+
if (!isOpen) {
|
|
1185
|
+
menu.classList.add('active');
|
|
1186
|
+
button.classList.add('active');
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1115
1189
|
}
|
|
1116
1190
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
if (!
|
|
1120
|
-
|
|
1191
|
+
// Close menus when clicking outside controls
|
|
1192
|
+
document.addEventListener('click', (e) => {
|
|
1193
|
+
if (!this.controls?.contains(e.target)) {
|
|
1194
|
+
this.closeAllMenus();
|
|
1121
1195
|
}
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1122
1198
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
tooltip.textContent = '50%';
|
|
1126
|
-
volumeContainer.appendChild(tooltip);
|
|
1199
|
+
updateVolumeSliderVisual() {
|
|
1200
|
+
if (!this.video || !this.container) return;
|
|
1127
1201
|
|
|
1128
|
-
|
|
1202
|
+
const volume = this.video.muted ? 0 : this.video.volume;
|
|
1203
|
+
const percentage = Math.round(volume * 100);
|
|
1129
1204
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1205
|
+
this.container.style.setProperty('--player-volume-fill', percentage + '%');
|
|
1206
|
+
|
|
1207
|
+
if (this.volumeSlider) {
|
|
1208
|
+
this.volumeSlider.value = percentage;
|
|
1133
1209
|
}
|
|
1210
|
+
}
|
|
1134
1211
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1212
|
+
createVolumeTooltip() {
|
|
1213
|
+
const volumeContainer = this.controls?.querySelector('.volume-container');
|
|
1214
|
+
if (!volumeContainer || volumeContainer.querySelector('.volume-tooltip')) {
|
|
1215
|
+
return; // Tooltip already present
|
|
1216
|
+
}
|
|
1137
1217
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1218
|
+
const tooltip = document.createElement('div');
|
|
1219
|
+
tooltip.className = 'volume-tooltip';
|
|
1220
|
+
tooltip.textContent = '50%';
|
|
1221
|
+
volumeContainer.appendChild(tooltip);
|
|
1140
1222
|
|
|
1141
|
-
|
|
1142
|
-
this.updateVolumeTooltipPosition(this.video.volume);
|
|
1223
|
+
this.volumeTooltip = tooltip;
|
|
1143
1224
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1225
|
+
if (this.options.debug) {
|
|
1226
|
+
console.log('Dynamic volume tooltip created');
|
|
1147
1227
|
}
|
|
1228
|
+
}
|
|
1148
1229
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1230
|
+
updateVolumeTooltip() {
|
|
1231
|
+
if (!this.volumeTooltip || !this.video) return;
|
|
1151
1232
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1233
|
+
const volume = Math.round(this.video.volume * 100);
|
|
1234
|
+
this.volumeTooltip.textContent = volume + '%';
|
|
1154
1235
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
volumeValue = this.video.volume;
|
|
1158
|
-
}
|
|
1236
|
+
// Aggiorna la posizione del tooltip
|
|
1237
|
+
this.updateVolumeTooltipPosition(this.video.volume);
|
|
1159
1238
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1239
|
+
if (this.options.debug) {
|
|
1240
|
+
console.log('Volume tooltip updated:', volume + '%');
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1163
1243
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1244
|
+
updateVolumeTooltipPosition(volumeValue = null) {
|
|
1245
|
+
if (!this.volumeTooltip || !this.video) return;
|
|
1166
1246
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
const availableWidth = sliderWidth - thumbSize;
|
|
1170
|
-
const thumbCenterPosition = (thumbSize / 2) + (availableWidth * volumeValue);
|
|
1247
|
+
const volumeSlider = this.controls?.querySelector('.volume-slider');
|
|
1248
|
+
if (!volumeSlider) return;
|
|
1171
1249
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1250
|
+
// If no volume provided, use current volume
|
|
1251
|
+
if (volumeValue === null) {
|
|
1252
|
+
volumeValue = this.video.volume;
|
|
1253
|
+
}
|
|
1174
1254
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1255
|
+
// Calcola la posizione esatta del thumb
|
|
1256
|
+
const sliderRect = volumeSlider.getBoundingClientRect();
|
|
1257
|
+
const sliderWidth = sliderRect.width;
|
|
1177
1258
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
volumeValue: volumeValue,
|
|
1181
|
-
percentage: percentage + '%',
|
|
1182
|
-
thumbCenter: thumbCenterPosition,
|
|
1183
|
-
sliderWidth: sliderWidth
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1259
|
+
// Thumb size is typically 14px (as defined in CSS)
|
|
1260
|
+
const thumbSize = 14; // var(--player-volume-handle-size)
|
|
1187
1261
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1262
|
+
// Calcola la posizione del centro del thumb
|
|
1263
|
+
// Il thumb si muove da thumbSize/2 a (sliderWidth - thumbSize/2)
|
|
1264
|
+
const availableWidth = sliderWidth - thumbSize;
|
|
1265
|
+
const thumbCenterPosition = (thumbSize / 2) + (availableWidth * volumeValue);
|
|
1191
1266
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1267
|
+
// Converti in percentuale relativa al container dello slider
|
|
1268
|
+
const percentage = (thumbCenterPosition / sliderWidth) * 100;
|
|
1269
|
+
|
|
1270
|
+
// Posiziona il tooltip
|
|
1271
|
+
this.volumeTooltip.style.left = percentage + '%';
|
|
1272
|
+
|
|
1273
|
+
if (this.options.debug) {
|
|
1274
|
+
console.log('Volume tooltip position updated:', {
|
|
1275
|
+
volumeValue: volumeValue,
|
|
1276
|
+
percentage: percentage + '%',
|
|
1277
|
+
thumbCenter: thumbCenterPosition,
|
|
1278
|
+
sliderWidth: sliderWidth
|
|
1279
|
+
});
|
|
1199
1280
|
}
|
|
1281
|
+
}
|
|
1200
1282
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
if (!volumeSlider || !this.video) return;
|
|
1283
|
+
initVolumeTooltip() {
|
|
1284
|
+
this.createVolumeTooltip();
|
|
1204
1285
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1286
|
+
// Set initial position immediately
|
|
1287
|
+
setTimeout(() => {
|
|
1288
|
+
if (this.volumeTooltip && this.video) {
|
|
1289
|
+
this.updateVolumeTooltipPosition(this.video.volume);
|
|
1290
|
+
this.updateVolumeTooltip();
|
|
1291
|
+
}
|
|
1292
|
+
}, 50); // Shorter delay for faster initialization
|
|
1293
|
+
}
|
|
1207
1294
|
|
|
1208
|
-
|
|
1295
|
+
updateVolumeSliderVisualWithTooltip() {
|
|
1296
|
+
const volumeSlider = this.controls?.querySelector('.volume-slider');
|
|
1297
|
+
if (!volumeSlider || !this.video) return;
|
|
1209
1298
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
volumeSlider.style.setProperty('--player-volume-fill', volumeFillPercentage);
|
|
1299
|
+
const volume = this.video.volume || 0;
|
|
1300
|
+
const percentage = Math.round(volume * 100);
|
|
1213
1301
|
|
|
1214
|
-
|
|
1215
|
-
this.updateVolumeTooltip();
|
|
1302
|
+
volumeSlider.value = volume;
|
|
1216
1303
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1304
|
+
// Update CSS custom property per il riempimento visuale
|
|
1305
|
+
const volumeFillPercentage = percentage + '%';
|
|
1306
|
+
volumeSlider.style.setProperty('--player-volume-fill', volumeFillPercentage);
|
|
1307
|
+
|
|
1308
|
+
// Aggiorna anche il tooltip se presente (testo e posizione)
|
|
1309
|
+
this.updateVolumeTooltip();
|
|
1310
|
+
|
|
1311
|
+
if (this.options.debug) {
|
|
1312
|
+
console.log('Volume slider aggiornato:', {
|
|
1313
|
+
volume: volume,
|
|
1314
|
+
percentage: percentage,
|
|
1315
|
+
fillPercentage: volumeFillPercentage
|
|
1316
|
+
});
|
|
1224
1317
|
}
|
|
1318
|
+
}
|
|
1225
1319
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1320
|
+
/**
|
|
1321
|
+
* Set mobile volume slider visibility
|
|
1322
|
+
* @param {String} mode - 'show' (horizontal popup) or 'hide' (no slider on mobile)
|
|
1323
|
+
* @returns {Object} this
|
|
1324
|
+
*/
|
|
1325
|
+
setMobileVolumeSlider(mode) {
|
|
1326
|
+
if (!['show', 'hide'].includes(mode)) {
|
|
1327
|
+
if (this.options.debug) console.warn('Invalid mobile volume slider mode:', mode);
|
|
1230
1328
|
return this;
|
|
1231
1329
|
}
|
|
1232
1330
|
|
|
1233
|
-
this.options.
|
|
1331
|
+
this.options.mobileVolumeSlider = mode;
|
|
1234
1332
|
const volumeContainer = this.controls?.querySelector('.volume-container');
|
|
1235
1333
|
if (volumeContainer) {
|
|
1236
|
-
|
|
1334
|
+
// Set data attribute for CSS to use
|
|
1335
|
+
volumeContainer.setAttribute('data-mobile-slider', mode);
|
|
1336
|
+
if (this.options.debug) console.log('Mobile volume slider set to:', mode);
|
|
1237
1337
|
}
|
|
1238
|
-
|
|
1239
|
-
if (this.options.debug) console.log('Volume slider orientation set to:', orientation);
|
|
1240
1338
|
return this;
|
|
1241
1339
|
}
|
|
1242
1340
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1341
|
+
/**
|
|
1342
|
+
* Get mobile volume slider mode
|
|
1343
|
+
* @returns {String} Current mobile volume slider mode
|
|
1344
|
+
*/
|
|
1345
|
+
getMobileVolumeSlider() {
|
|
1346
|
+
return this.options.mobileVolumeSlider;
|
|
1245
1347
|
}
|
|
1246
1348
|
|
|
1349
|
+
initVolumeTooltip() {
|
|
1247
1350
|
|
|
1248
|
-
|
|
1351
|
+
this.createVolumeTooltip();
|
|
1249
1352
|
|
|
1250
|
-
|
|
1353
|
+
setTimeout(() => {
|
|
1354
|
+
this.updateVolumeTooltip();
|
|
1355
|
+
}, 200);
|
|
1251
1356
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1357
|
+
if (this.options.debug) {
|
|
1358
|
+
console.log('Dynamic volume tooltip inizializzation');
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1254
1361
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
}, 200);
|
|
1362
|
+
setupSeekTooltip() {
|
|
1363
|
+
if (!this.options.showSeekTooltip || !this.progressContainer || !this.seekTooltip) return;
|
|
1258
1364
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1365
|
+
this.progressContainer.addEventListener('mouseenter', () => {
|
|
1366
|
+
if (this.seekTooltip) {
|
|
1367
|
+
this.seekTooltip.classList.add('visible');
|
|
1261
1368
|
}
|
|
1262
|
-
}
|
|
1369
|
+
});
|
|
1263
1370
|
|
|
1264
|
-
|
|
1265
|
-
if (
|
|
1371
|
+
this.progressContainer.addEventListener('mouseleave', () => {
|
|
1372
|
+
if (this.seekTooltip) {
|
|
1373
|
+
this.seekTooltip.classList.remove('visible');
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1266
1376
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
});
|
|
1377
|
+
this.progressContainer.addEventListener('mousemove', (e) => {
|
|
1378
|
+
this.updateSeekTooltip(e);
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1272
1381
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
this.seekTooltip.classList.remove('visible');
|
|
1276
|
-
}
|
|
1277
|
-
});
|
|
1382
|
+
updateSeekTooltip(e) {
|
|
1383
|
+
if (!this.seekTooltip || !this.progressContainer || !this.video || !this.video.duration) return;
|
|
1278
1384
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1385
|
+
const rect = this.progressContainer.getBoundingClientRect();
|
|
1386
|
+
const clickX = e.clientX - rect.left;
|
|
1387
|
+
const percentage = Math.max(0, Math.min(1, clickX / rect.width));
|
|
1388
|
+
const targetTime = percentage * this.video.duration;
|
|
1283
1389
|
|
|
1284
|
-
|
|
1285
|
-
if (!this.seekTooltip || !this.progressContainer || !this.video || !this.video.duration) return;
|
|
1390
|
+
this.seekTooltip.textContent = this.formatTime(targetTime);
|
|
1286
1391
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
const percentage = Math.max(0, Math.min(1, clickX / rect.width));
|
|
1290
|
-
const targetTime = percentage * this.video.duration;
|
|
1392
|
+
const tooltipRect = this.seekTooltip.getBoundingClientRect();
|
|
1393
|
+
let leftPosition = clickX;
|
|
1291
1394
|
|
|
1292
|
-
|
|
1395
|
+
const tooltipWidth = tooltipRect.width || 50;
|
|
1396
|
+
const containerWidth = rect.width;
|
|
1293
1397
|
|
|
1294
|
-
|
|
1295
|
-
let leftPosition = clickX;
|
|
1398
|
+
leftPosition = Math.max(tooltipWidth / 2, Math.min(containerWidth - tooltipWidth / 2, clickX));
|
|
1296
1399
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1400
|
+
this.seekTooltip.style.left = leftPosition + 'px';
|
|
1401
|
+
}
|
|
1299
1402
|
|
|
1300
|
-
|
|
1403
|
+
play() {
|
|
1404
|
+
if (!this.video || this.isChangingQuality) return;
|
|
1301
1405
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1406
|
+
this.video.play().catch(err => {
|
|
1407
|
+
if (this.options.debug) console.log('Play failed:', err);
|
|
1408
|
+
});
|
|
1304
1409
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1410
|
+
if (this.playIcon) this.playIcon.classList.add('hidden');
|
|
1411
|
+
if (this.pauseIcon) this.pauseIcon.classList.remove('hidden');
|
|
1307
1412
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1413
|
+
// Trigger event played
|
|
1414
|
+
this.triggerEvent('played', {
|
|
1415
|
+
currentTime: this.getCurrentTime(),
|
|
1416
|
+
duration: this.getDuration()
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1311
1419
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1420
|
+
pause() {
|
|
1421
|
+
if (!this.video) return;
|
|
1314
1422
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1423
|
+
this.video.pause();
|
|
1424
|
+
if (this.playIcon) this.playIcon.classList.remove('hidden');
|
|
1425
|
+
if (this.pauseIcon) this.pauseIcon.classList.add('hidden');
|
|
1426
|
+
|
|
1427
|
+
// Trigger paused event
|
|
1428
|
+
this.triggerEvent('paused', {
|
|
1429
|
+
currentTime: this.getCurrentTime(),
|
|
1430
|
+
duration: this.getDuration()
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
updateVolume(value) {
|
|
1435
|
+
if (!this.video) return;
|
|
1321
1436
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1437
|
+
const previousVolume = this.video.volume;
|
|
1438
|
+
const previousMuted = this.video.muted;
|
|
1324
1439
|
|
|
1325
|
-
|
|
1326
|
-
if (this.playIcon) this.playIcon.classList.remove('hidden');
|
|
1327
|
-
if (this.pauseIcon) this.pauseIcon.classList.add('hidden');
|
|
1440
|
+
this.video.volume = Math.max(0, Math.min(1, value / 100));
|
|
1328
1441
|
|
|
1329
|
-
|
|
1330
|
-
this.
|
|
1331
|
-
currentTime: this.getCurrentTime(),
|
|
1332
|
-
duration: this.getDuration()
|
|
1333
|
-
});
|
|
1442
|
+
if (this.video.volume > 0 && this.video.muted) {
|
|
1443
|
+
this.video.muted = false;
|
|
1334
1444
|
}
|
|
1335
1445
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1446
|
+
if (this.volumeSlider) this.volumeSlider.value = value;
|
|
1447
|
+
this.updateMuteButton();
|
|
1448
|
+
this.updateVolumeSliderVisual();
|
|
1449
|
+
this.initVolumeTooltip();
|
|
1338
1450
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1451
|
+
// Triggers volumechange event if there is a significant change
|
|
1452
|
+
if (Math.abs(previousVolume - this.video.volume) > 0.01 || previousMuted !== this.video.muted) {
|
|
1453
|
+
this.triggerEvent('volumechange', {
|
|
1454
|
+
volume: this.getVolume(),
|
|
1455
|
+
muted: this.isMuted(),
|
|
1456
|
+
previousVolume: previousVolume,
|
|
1457
|
+
previousMuted: previousMuted
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1341
1461
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
this.updateMuteButton();
|
|
1345
|
-
this.updateVolumeSliderVisual();
|
|
1346
|
-
this.initVolumeTooltip();
|
|
1462
|
+
changeVolume(delta) {
|
|
1463
|
+
if (!this.video) return;
|
|
1347
1464
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
previousVolume: previousVolume,
|
|
1354
|
-
previousMuted: previousMuted
|
|
1355
|
-
});
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1465
|
+
const newVolume = Math.max(0, Math.min(1, this.video.volume + delta));
|
|
1466
|
+
this.updateVolume(newVolume * 100);
|
|
1467
|
+
this.updateVolumeSliderVisual();
|
|
1468
|
+
this.initVolumeTooltip();
|
|
1469
|
+
}
|
|
1358
1470
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1471
|
+
updateProgress() {
|
|
1472
|
+
if (!this.video || !this.progressFilled || !this.progressHandle || this.isUserSeeking) return;
|
|
1361
1473
|
|
|
1362
|
-
|
|
1363
|
-
this.
|
|
1364
|
-
this.
|
|
1365
|
-
this.
|
|
1474
|
+
if (this.video.duration && !isNaN(this.video.duration)) {
|
|
1475
|
+
const progress = (this.video.currentTime / this.video.duration) * 100;
|
|
1476
|
+
this.progressFilled.style.width = progress + '%';
|
|
1477
|
+
this.progressHandle.style.left = progress + '%';
|
|
1366
1478
|
}
|
|
1367
1479
|
|
|
1368
|
-
|
|
1369
|
-
if (!this.video || !this.progressFilled || !this.progressHandle || this.isUserSeeking) return;
|
|
1370
|
-
|
|
1371
|
-
if (this.video.duration && !isNaN(this.video.duration)) {
|
|
1372
|
-
const progress = (this.video.currentTime / this.video.duration) * 100;
|
|
1373
|
-
this.progressFilled.style.width = progress + '%';
|
|
1374
|
-
this.progressHandle.style.left = progress + '%';
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
this.updateTimeDisplay();
|
|
1480
|
+
this.updateTimeDisplay();
|
|
1378
1481
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
}
|
|
1482
|
+
// Trigger timeupdate event (with throttling to avoid too many events)
|
|
1483
|
+
if (!this.lastTimeUpdate || Date.now() - this.lastTimeUpdate > 250) {
|
|
1484
|
+
this.triggerEvent('timeupdate', {
|
|
1485
|
+
currentTime: this.getCurrentTime(),
|
|
1486
|
+
duration: this.getDuration(),
|
|
1487
|
+
progress: (this.getCurrentTime() / this.getDuration()) * 100 || 0
|
|
1488
|
+
});
|
|
1489
|
+
this.lastTimeUpdate = Date.now();
|
|
1388
1490
|
}
|
|
1491
|
+
}
|
|
1389
1492
|
|
|
1390
|
-
|
|
1391
|
-
|
|
1493
|
+
updateBuffer() {
|
|
1494
|
+
if (!this.video || !this.progressBuffer) return;
|
|
1392
1495
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
}
|
|
1398
|
-
} catch (error) {
|
|
1399
|
-
if (this.options.debug) console.log('Buffer update error (non-critical):', error);
|
|
1496
|
+
try {
|
|
1497
|
+
if (this.video.buffered && this.video.buffered.length > 0 && this.video.duration) {
|
|
1498
|
+
const buffered = (this.video.buffered.end(0) / this.video.duration) * 100;
|
|
1499
|
+
this.progressBuffer.style.width = buffered + '%';
|
|
1400
1500
|
}
|
|
1501
|
+
} catch (error) {
|
|
1502
|
+
if (this.options.debug) console.log('Buffer update error (non-critical):', error);
|
|
1401
1503
|
}
|
|
1504
|
+
}
|
|
1402
1505
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1506
|
+
startSeeking(e) {
|
|
1507
|
+
if (this.isChangingQuality) return;
|
|
1405
1508
|
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1509
|
+
this.isUserSeeking = true;
|
|
1510
|
+
this.seek(e);
|
|
1511
|
+
e.preventDefault();
|
|
1409
1512
|
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
}
|
|
1513
|
+
// Show controls during seeking
|
|
1514
|
+
if (this.options.autoHide && this.autoHideInitialized) {
|
|
1515
|
+
this.showControlsNow();
|
|
1516
|
+
this.resetAutoHideTimer();
|
|
1415
1517
|
}
|
|
1518
|
+
}
|
|
1416
1519
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
}
|
|
1520
|
+
continueSeeking(e) {
|
|
1521
|
+
if (this.isUserSeeking && !this.isChangingQuality) {
|
|
1522
|
+
this.seek(e);
|
|
1421
1523
|
}
|
|
1524
|
+
}
|
|
1422
1525
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1526
|
+
endSeeking() {
|
|
1527
|
+
this.isUserSeeking = false;
|
|
1528
|
+
}
|
|
1426
1529
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1530
|
+
seek(e) {
|
|
1531
|
+
if (!this.video || !this.progressContainer || !this.progressFilled || !this.progressHandle || this.isChangingQuality) return;
|
|
1429
1532
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1533
|
+
const rect = this.progressContainer.getBoundingClientRect();
|
|
1534
|
+
const clickX = e.clientX - rect.left;
|
|
1535
|
+
const percentage = Math.max(0, Math.min(1, clickX / rect.width));
|
|
1433
1536
|
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
}
|
|
1537
|
+
if (this.video.duration && !isNaN(this.video.duration)) {
|
|
1538
|
+
this.video.currentTime = percentage * this.video.duration;
|
|
1539
|
+
const progress = percentage * 100;
|
|
1540
|
+
this.progressFilled.style.width = progress + '%';
|
|
1541
|
+
this.progressHandle.style.left = progress + '%';
|
|
1440
1542
|
}
|
|
1543
|
+
}
|
|
1441
1544
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
}
|
|
1545
|
+
updateDuration() {
|
|
1546
|
+
if (this.durationEl && this.video && this.video.duration && !isNaN(this.video.duration)) {
|
|
1547
|
+
this.durationEl.textContent = this.formatTime(this.video.duration);
|
|
1446
1548
|
}
|
|
1549
|
+
}
|
|
1447
1550
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
const speed = parseFloat(e.target.getAttribute('data-speed'));
|
|
1452
|
-
if (speed && speed > 0) {
|
|
1453
|
-
this.video.playbackRate = speed;
|
|
1454
|
-
if (this.speedBtn) this.speedBtn.textContent = speed + 'x';
|
|
1551
|
+
changeSpeed(e) {
|
|
1552
|
+
if (!this.video || !e.target.classList.contains('speed-option') || this.isChangingQuality) return;
|
|
1455
1553
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
e.target.classList.add('active');
|
|
1461
|
-
}
|
|
1554
|
+
const speed = parseFloat(e.target.getAttribute('data-speed'));
|
|
1555
|
+
if (speed && speed > 0) {
|
|
1556
|
+
this.video.playbackRate = speed;
|
|
1557
|
+
if (this.speedBtn) this.speedBtn.textContent = speed + 'x';
|
|
1462
1558
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
speed: speed,
|
|
1467
|
-
previousSpeed: previousSpeed
|
|
1559
|
+
if (this.speedMenu) {
|
|
1560
|
+
this.speedMenu.querySelectorAll('.speed-option').forEach(option => {
|
|
1561
|
+
option.classList.remove('active');
|
|
1468
1562
|
});
|
|
1563
|
+
e.target.classList.add('active');
|
|
1469
1564
|
}
|
|
1565
|
+
|
|
1566
|
+
// Trigger speedchange event
|
|
1567
|
+
const previousSpeed = this.video.playbackRate;
|
|
1568
|
+
this.triggerEvent('speedchange', {
|
|
1569
|
+
speed: speed,
|
|
1570
|
+
previousSpeed: previousSpeed
|
|
1571
|
+
});
|
|
1470
1572
|
}
|
|
1573
|
+
}
|
|
1471
1574
|
|
|
1472
1575
|
onVideoEnded() {
|
|
1473
1576
|
if (this.playIcon) this.playIcon.classList.remove('hidden');
|
|
@@ -1493,217 +1596,270 @@ onVideoEnded() {
|
|
|
1493
1596
|
});
|
|
1494
1597
|
}
|
|
1495
1598
|
|
|
1496
|
-
|
|
1599
|
+
/**
|
|
1600
|
+
* Handle video loading errors (404, 503, network errors, etc.)
|
|
1601
|
+
* Triggers 'ended' event to allow proper cleanup and playlist continuation
|
|
1602
|
+
*/
|
|
1603
|
+
onVideoError(error) {
|
|
1604
|
+
if (this.options.debug) {
|
|
1605
|
+
console.error('Video loading error detected:', {
|
|
1606
|
+
error: error,
|
|
1607
|
+
code: this.video?.error?.code,
|
|
1608
|
+
message: this.video?.error?.message,
|
|
1609
|
+
src: this.video?.currentSrc || this.video?.src
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// Hide loading overlay
|
|
1614
|
+
this.hideLoading();
|
|
1615
|
+
if (this.initialLoading) {
|
|
1616
|
+
this.initialLoading.style.display = 'none';
|
|
1617
|
+
}
|
|
1497
1618
|
|
|
1498
|
-
|
|
1619
|
+
// Remove quality-changing class if present
|
|
1620
|
+
if (this.video?.classList) {
|
|
1621
|
+
this.video.classList.remove('quality-changing');
|
|
1622
|
+
}
|
|
1499
1623
|
|
|
1500
|
-
|
|
1624
|
+
// Reset changing quality flag
|
|
1625
|
+
this.isChangingQuality = false;
|
|
1501
1626
|
|
|
1502
|
-
|
|
1627
|
+
// Show controls to allow user interaction
|
|
1628
|
+
this.showControlsNow();
|
|
1503
1629
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
}
|
|
1630
|
+
// Optional: Show poster if available
|
|
1631
|
+
if (this.options.showPosterOnEnd && this.posterOverlay) {
|
|
1632
|
+
this.showPoster();
|
|
1508
1633
|
}
|
|
1509
1634
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1635
|
+
// Trigger 'ended' event to allow proper cleanup
|
|
1636
|
+
// This allows playlist to continue or other error handling
|
|
1637
|
+
this.triggerEvent('ended', {
|
|
1638
|
+
currentTime: this.getCurrentTime(),
|
|
1639
|
+
duration: this.getDuration(),
|
|
1640
|
+
error: true,
|
|
1641
|
+
errorCode: this.video?.error?.code,
|
|
1642
|
+
errorMessage: this.video?.error?.message,
|
|
1643
|
+
playlistInfo: this.getPlaylistInfo()
|
|
1644
|
+
});
|
|
1513
1645
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
this.video.muted = muted;
|
|
1517
|
-
this.updateMuteButton();
|
|
1518
|
-
this.updateVolumeSliderVisual();
|
|
1519
|
-
this.initVolumeTooltip();
|
|
1520
|
-
}
|
|
1646
|
+
if (this.options.debug) {
|
|
1647
|
+
console.log('Video error handled - triggered ended event');
|
|
1521
1648
|
}
|
|
1649
|
+
}
|
|
1522
1650
|
|
|
1523
|
-
getPlaybackRate() { return this.video ? this.video.playbackRate || 1 : 1; }
|
|
1524
1651
|
|
|
1525
|
-
|
|
1652
|
+
getCurrentTime() { return this.video ? this.video.currentTime || 0 : 0; }
|
|
1526
1653
|
|
|
1527
|
-
|
|
1654
|
+
setCurrentTime(time) { if (this.video && typeof time === 'number' && time >= 0 && !this.isChangingQuality) { this.video.currentTime = time; } }
|
|
1528
1655
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1656
|
+
getDuration() { return this.video && this.video.duration ? this.video.duration : 0; }
|
|
1657
|
+
|
|
1658
|
+
getVolume() { return this.video ? this.video.volume || 0 : 0; }
|
|
1659
|
+
|
|
1660
|
+
setVolume(volume) {
|
|
1661
|
+
if (typeof volume === 'number' && volume >= 0 && volume <= 1) {
|
|
1662
|
+
this.updateVolume(volume * 100);
|
|
1532
1663
|
}
|
|
1664
|
+
}
|
|
1533
1665
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1666
|
+
isPaused() { return this.video ? this.video.paused : true; }
|
|
1667
|
+
|
|
1668
|
+
isMuted() { return this.video ? this.video.muted : false; }
|
|
1669
|
+
|
|
1670
|
+
setMuted(muted) {
|
|
1671
|
+
if (this.video && typeof muted === 'boolean') {
|
|
1672
|
+
this.video.muted = muted;
|
|
1673
|
+
this.updateMuteButton();
|
|
1674
|
+
this.updateVolumeSliderVisual();
|
|
1675
|
+
this.initVolumeTooltip();
|
|
1537
1676
|
}
|
|
1677
|
+
}
|
|
1538
1678
|
|
|
1539
|
-
|
|
1540
|
-
if (!this.options.brandLogoEnabled || !this.options.brandLogoUrl) return;
|
|
1679
|
+
getPlaybackRate() { return this.video ? this.video.playbackRate || 1 : 1; }
|
|
1541
1680
|
|
|
1542
|
-
|
|
1543
|
-
if (!controlsRight) return;
|
|
1681
|
+
setPlaybackRate(rate) { if (this.video && typeof rate === 'number' && rate > 0 && !this.isChangingQuality) { this.video.playbackRate = rate; if (this.speedBtn) this.speedBtn.textContent = rate + 'x'; } }
|
|
1544
1682
|
|
|
1545
|
-
|
|
1546
|
-
const logo = document.createElement('img');
|
|
1547
|
-
logo.className = 'brand-logo';
|
|
1548
|
-
logo.src = this.options.brandLogoUrl;
|
|
1549
|
-
logo.alt = this.t('brand_logo');
|
|
1683
|
+
isPictureInPictureActive() { return document.pictureInPictureElement === this.video; }
|
|
1550
1684
|
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
};
|
|
1685
|
+
getCurrentLanguage() {
|
|
1686
|
+
return this.isI18nAvailable() ?
|
|
1687
|
+
VideoPlayerTranslations.getCurrentLanguage() : 'en';
|
|
1688
|
+
}
|
|
1556
1689
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1690
|
+
getSupportedLanguages() {
|
|
1691
|
+
return this.isI18nAvailable() ?
|
|
1692
|
+
VideoPlayerTranslations.getSupportedLanguages() : ['en'];
|
|
1693
|
+
}
|
|
1560
1694
|
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
logo.style.cursor = 'pointer';
|
|
1564
|
-
logo.addEventListener('click', (e) => {
|
|
1565
|
-
e.stopPropagation(); // Prevent video controls interference
|
|
1566
|
-
window.open(this.options.brandLogoLinkUrl, '_blank', 'noopener,noreferrer');
|
|
1567
|
-
if (this.options.debug) console.log('Brand logo clicked, opening:', this.options.brandLogoLinkUrl);
|
|
1568
|
-
});
|
|
1569
|
-
} else {
|
|
1570
|
-
logo.style.cursor = 'default';
|
|
1571
|
-
}
|
|
1695
|
+
createBrandLogo() {
|
|
1696
|
+
if (!this.options.brandLogoEnabled || !this.options.brandLogoUrl) return;
|
|
1572
1697
|
|
|
1573
|
-
|
|
1574
|
-
|
|
1698
|
+
const controlsRight = this.controls?.querySelector('.controls-right');
|
|
1699
|
+
if (!controlsRight) return;
|
|
1575
1700
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1701
|
+
// Create brand logo image
|
|
1702
|
+
const logo = document.createElement('img');
|
|
1703
|
+
logo.className = 'brand-logo';
|
|
1704
|
+
logo.src = this.options.brandLogoUrl;
|
|
1705
|
+
logo.alt = this.t('brand_logo');
|
|
1706
|
+
|
|
1707
|
+
// Handle loading error
|
|
1708
|
+
logo.onerror = () => {
|
|
1709
|
+
if (this.options.debug) console.warn('Brand logo failed to load:', this.options.brandLogoUrl);
|
|
1710
|
+
logo.style.display = 'none';
|
|
1711
|
+
};
|
|
1712
|
+
|
|
1713
|
+
logo.onload = () => {
|
|
1714
|
+
if (this.options.debug) console.log('Brand logo loaded successfully');
|
|
1715
|
+
};
|
|
1716
|
+
|
|
1717
|
+
// Add click functionality if link URL is provided
|
|
1718
|
+
if (this.options.brandLogoLinkUrl) {
|
|
1719
|
+
logo.style.cursor = 'pointer';
|
|
1720
|
+
logo.addEventListener('click', (e) => {
|
|
1721
|
+
e.stopPropagation(); // Prevent video controls interference
|
|
1722
|
+
window.open(this.options.brandLogoLinkUrl, '_blank', 'noopener,noreferrer');
|
|
1723
|
+
if (this.options.debug) console.log('Brand logo clicked, opening:', this.options.brandLogoLinkUrl);
|
|
1724
|
+
});
|
|
1725
|
+
} else {
|
|
1726
|
+
logo.style.cursor = 'default';
|
|
1583
1727
|
}
|
|
1584
1728
|
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
if (url) {
|
|
1588
|
-
this.options.brandLogoUrl = url;
|
|
1589
|
-
}
|
|
1590
|
-
if (linkUrl !== '') {
|
|
1591
|
-
this.options.brandLogoLinkUrl = linkUrl;
|
|
1592
|
-
}
|
|
1729
|
+
// Position the brand logo at the right of the controlbar (at the left of the buttons)
|
|
1730
|
+
controlsRight.insertBefore(logo, controlsRight.firstChild);
|
|
1593
1731
|
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1732
|
+
if (this.options.debug) {
|
|
1733
|
+
if (this.options.brandLogoLinkUrl) {
|
|
1734
|
+
console.log('Brand logo with click handler created for:', this.options.brandLogoLinkUrl);
|
|
1735
|
+
} else {
|
|
1736
|
+
console.log('Brand logo created (no link)');
|
|
1598
1737
|
}
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1599
1740
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1741
|
+
setBrandLogo(enabled, url = '', linkUrl = '') {
|
|
1742
|
+
this.options.brandLogoEnabled = enabled;
|
|
1743
|
+
if (url) {
|
|
1744
|
+
this.options.brandLogoUrl = url;
|
|
1745
|
+
}
|
|
1746
|
+
if (linkUrl !== '') {
|
|
1747
|
+
this.options.brandLogoLinkUrl = linkUrl;
|
|
1748
|
+
}
|
|
1604
1749
|
|
|
1605
|
-
|
|
1750
|
+
// Remove existing brand logo
|
|
1751
|
+
const existingLogo = this.controls?.querySelector('.brand-logo');
|
|
1752
|
+
if (existingLogo) {
|
|
1753
|
+
existingLogo.remove();
|
|
1606
1754
|
}
|
|
1607
1755
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
url: this.options.brandLogoUrl,
|
|
1612
|
-
linkUrl: this.options.brandLogoLinkUrl
|
|
1613
|
-
};
|
|
1756
|
+
// Recreate the logo if enabled
|
|
1757
|
+
if (enabled && this.options.brandLogoUrl) {
|
|
1758
|
+
this.createBrandLogo();
|
|
1614
1759
|
}
|
|
1615
1760
|
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
if (this.options.debug) console.error('🎵 New video element not found');
|
|
1619
|
-
return false;
|
|
1620
|
-
}
|
|
1761
|
+
return this;
|
|
1762
|
+
}
|
|
1621
1763
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1764
|
+
getBrandLogoSettings() {
|
|
1765
|
+
return {
|
|
1766
|
+
enabled: this.options.brandLogoEnabled,
|
|
1767
|
+
url: this.options.brandLogoUrl,
|
|
1768
|
+
linkUrl: this.options.brandLogoLinkUrl
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1624
1771
|
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
}));
|
|
1772
|
+
switchToVideo(newVideoElement, shouldPlay = false) {
|
|
1773
|
+
if (!newVideoElement) {
|
|
1774
|
+
if (this.options.debug) console.error('🎵 New video element not found');
|
|
1775
|
+
return false;
|
|
1776
|
+
}
|
|
1631
1777
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1778
|
+
// Pause current video
|
|
1779
|
+
this.video.pause();
|
|
1780
|
+
|
|
1781
|
+
// Get new video sources and qualities
|
|
1782
|
+
const newSources = Array.from(newVideoElement.querySelectorAll('source')).map(source => ({
|
|
1783
|
+
src: source.src,
|
|
1784
|
+
quality: source.getAttribute('data-quality') || 'auto',
|
|
1785
|
+
type: source.type || 'video/mp4'
|
|
1786
|
+
}));
|
|
1636
1787
|
|
|
1637
|
-
|
|
1638
|
-
if (this.options.
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1788
|
+
if (newSources.length === 0) {
|
|
1789
|
+
if (this.options.debug) console.error('🎵 New video has no sources');
|
|
1790
|
+
return false;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// Check if new video is adaptive stream
|
|
1794
|
+
if (this.options.adaptiveStreaming && newSources.length > 0) {
|
|
1795
|
+
const firstSource = newSources[0];
|
|
1796
|
+
if (this.detectStreamType(firstSource.src)) {
|
|
1797
|
+
// Initialize adaptive streaming for new video
|
|
1798
|
+
this.initializeAdaptiveStreaming(firstSource.src).then((initialized) => {
|
|
1799
|
+
if (initialized && shouldPlay) {
|
|
1800
|
+
const playPromise = this.video.play();
|
|
1801
|
+
if (playPromise) {
|
|
1802
|
+
playPromise.catch(error => {
|
|
1803
|
+
if (this.options.debug) console.log('Autoplay prevented:', error);
|
|
1804
|
+
});
|
|
1650
1805
|
}
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
1808
|
+
return true;
|
|
1654
1809
|
}
|
|
1810
|
+
}
|
|
1655
1811
|
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1812
|
+
// Update traditional video sources
|
|
1813
|
+
this.video.innerHTML = '';
|
|
1814
|
+
newSources.forEach(source => {
|
|
1815
|
+
const sourceEl = document.createElement('source');
|
|
1816
|
+
sourceEl.src = source.src;
|
|
1817
|
+
sourceEl.type = source.type;
|
|
1818
|
+
sourceEl.setAttribute('data-quality', source.quality);
|
|
1819
|
+
this.video.appendChild(sourceEl);
|
|
1820
|
+
});
|
|
1665
1821
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1822
|
+
// Update subtitles if present
|
|
1823
|
+
const newTracks = Array.from(newVideoElement.querySelectorAll('track'));
|
|
1824
|
+
newTracks.forEach(track => {
|
|
1825
|
+
const trackEl = document.createElement('track');
|
|
1826
|
+
trackEl.kind = track.kind;
|
|
1827
|
+
trackEl.src = track.src;
|
|
1828
|
+
trackEl.srclang = track.srclang;
|
|
1829
|
+
trackEl.label = track.label;
|
|
1830
|
+
if (track.default) trackEl.default = true;
|
|
1831
|
+
this.video.appendChild(trackEl);
|
|
1832
|
+
});
|
|
1677
1833
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
}
|
|
1834
|
+
// Update video title
|
|
1835
|
+
const newTitle = newVideoElement.getAttribute('data-video-title');
|
|
1836
|
+
if (newTitle && this.options.showTitleOverlay) {
|
|
1837
|
+
this.options.videoTitle = newTitle;
|
|
1838
|
+
if (this.titleText) {
|
|
1839
|
+
this.titleText.textContent = newTitle;
|
|
1685
1840
|
}
|
|
1841
|
+
}
|
|
1686
1842
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1843
|
+
// Reload video
|
|
1844
|
+
this.video.load();
|
|
1689
1845
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1846
|
+
// Update qualities and quality selector
|
|
1847
|
+
this.collectVideoQualities();
|
|
1848
|
+
this.updateQualityMenu();
|
|
1693
1849
|
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
}
|
|
1850
|
+
// Play if needed
|
|
1851
|
+
if (shouldPlay) {
|
|
1852
|
+
const playPromise = this.video.play();
|
|
1853
|
+
if (playPromise) {
|
|
1854
|
+
playPromise.catch(error => {
|
|
1855
|
+
if (this.options.debug) console.log('🎵 Autoplay prevented:', error);
|
|
1856
|
+
});
|
|
1702
1857
|
}
|
|
1703
|
-
|
|
1704
|
-
return true;
|
|
1705
1858
|
}
|
|
1706
1859
|
|
|
1860
|
+
return true;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1707
1863
|
/**
|
|
1708
1864
|
* POSTER IMAGE MANAGEMENT
|
|
1709
1865
|
* Initialize and manage video poster image
|
|
@@ -1920,59 +2076,59 @@ isPosterVisible() {
|
|
|
1920
2076
|
}
|
|
1921
2077
|
|
|
1922
2078
|
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2079
|
+
loadScript(src) {
|
|
2080
|
+
return new Promise((resolve, reject) => {
|
|
2081
|
+
if (document.querySelector(`script[src="${src}"]`)) {
|
|
2082
|
+
resolve();
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
1929
2085
|
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
2086
|
+
const script = document.createElement('script');
|
|
2087
|
+
script.src = src;
|
|
2088
|
+
script.onload = resolve;
|
|
2089
|
+
script.onerror = reject;
|
|
2090
|
+
document.head.appendChild(script);
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
1937
2093
|
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
2094
|
+
dispose() {
|
|
2095
|
+
if (this.qualityMonitorInterval) {
|
|
2096
|
+
clearInterval(this.qualityMonitorInterval);
|
|
2097
|
+
this.qualityMonitorInterval = null;
|
|
2098
|
+
}
|
|
1943
2099
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
2100
|
+
if (this.autoHideTimer) {
|
|
2101
|
+
clearTimeout(this.autoHideTimer);
|
|
2102
|
+
this.autoHideTimer = null;
|
|
2103
|
+
}
|
|
1948
2104
|
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
2105
|
+
this.cleanupQualityChange();
|
|
2106
|
+
this.clearControlsTimeout();
|
|
2107
|
+
this.clearTitleTimeout();
|
|
1952
2108
|
|
|
1953
|
-
|
|
1954
|
-
|
|
2109
|
+
// Destroy adaptive streaming players
|
|
2110
|
+
this.destroyAdaptivePlayer();
|
|
1955
2111
|
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2112
|
+
if (this.controls) {
|
|
2113
|
+
this.controls.remove();
|
|
2114
|
+
}
|
|
2115
|
+
if (this.loadingOverlay) {
|
|
2116
|
+
this.loadingOverlay.remove();
|
|
2117
|
+
}
|
|
2118
|
+
if (this.titleOverlay) {
|
|
2119
|
+
this.titleOverlay.remove();
|
|
2120
|
+
}
|
|
2121
|
+
if (this.initialLoading) {
|
|
2122
|
+
this.initialLoading.remove();
|
|
2123
|
+
}
|
|
1968
2124
|
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
2125
|
+
if (this.video) {
|
|
2126
|
+
this.video.classList.remove('video-player');
|
|
2127
|
+
this.video.controls = true;
|
|
2128
|
+
this.video.style.visibility = '';
|
|
2129
|
+
this.video.style.opacity = '';
|
|
2130
|
+
this.video.style.pointerEvents = '';
|
|
2131
|
+
}
|
|
1976
2132
|
if (this.chapterMarkersContainer) {
|
|
1977
2133
|
this.chapterMarkersContainer.remove();
|
|
1978
2134
|
}
|
|
@@ -1984,37 +2140,37 @@ isPosterVisible() {
|
|
|
1984
2140
|
}
|
|
1985
2141
|
this.disposeAllPlugins();
|
|
1986
2142
|
|
|
1987
|
-
|
|
2143
|
+
}
|
|
1988
2144
|
|
|
1989
|
-
|
|
2145
|
+
/**
|
|
1990
2146
|
|
|
1991
|
-
|
|
2147
|
+
* Apply specified resolution mode to video
|
|
1992
2148
|
|
|
1993
|
-
|
|
2149
|
+
* @param {string} resolution - The resolution mode to apply
|
|
1994
2150
|
|
|
1995
|
-
|
|
2151
|
+
*/
|
|
1996
2152
|
|
|
1997
|
-
|
|
2153
|
+
/**
|
|
1998
2154
|
|
|
1999
|
-
|
|
2155
|
+
* Get currently set resolution
|
|
2000
2156
|
|
|
2001
|
-
|
|
2157
|
+
* @returns {string} Current resolution
|
|
2002
2158
|
|
|
2003
|
-
|
|
2159
|
+
*/
|
|
2004
2160
|
|
|
2005
|
-
|
|
2161
|
+
/**
|
|
2006
2162
|
|
|
2007
|
-
|
|
2163
|
+
* Initialize resolution from options value
|
|
2008
2164
|
|
|
2009
|
-
|
|
2165
|
+
*/
|
|
2010
2166
|
|
|
2011
|
-
|
|
2167
|
+
/**
|
|
2012
2168
|
|
|
2013
|
-
|
|
2169
|
+
* Restore resolution after quality change - internal method
|
|
2014
2170
|
|
|
2015
|
-
|
|
2171
|
+
* @private
|
|
2016
2172
|
|
|
2017
|
-
|
|
2173
|
+
*/
|
|
2018
2174
|
|
|
2019
2175
|
addEventListener(eventType, callback) {
|
|
2020
2176
|
if (typeof callback !== 'function') {
|
|
@@ -2400,6 +2556,16 @@ initAutoHide() {
|
|
|
2400
2556
|
this.controls.addEventListener('mouseleave', (e) => {
|
|
2401
2557
|
if (this.autoHideDebug) {
|
|
2402
2558
|
if (this.options.debug) console.log('Mouse EXITS controls - restart timer');
|
|
2559
|
+
|
|
2560
|
+
// Touch events for mobile devices
|
|
2561
|
+
this.container.addEventListener('touchstart', () => {
|
|
2562
|
+
this.showControlsNow();
|
|
2563
|
+
this.resetAutoHideTimer();
|
|
2564
|
+
});
|
|
2565
|
+
|
|
2566
|
+
this.container.addEventListener('touchend', () => {
|
|
2567
|
+
this.resetAutoHideTimer();
|
|
2568
|
+
});
|
|
2403
2569
|
}
|
|
2404
2570
|
this.onMouseLeaveControls(e);
|
|
2405
2571
|
});
|
|
@@ -2443,7 +2609,8 @@ resetAutoHideTimer() {
|
|
|
2443
2609
|
this.autoHideTimer = null;
|
|
2444
2610
|
}
|
|
2445
2611
|
|
|
2446
|
-
|
|
2612
|
+
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
2613
|
+
if (this.mouseOverControls && !isTouchDevice) {
|
|
2447
2614
|
if (this.autoHideDebug) {
|
|
2448
2615
|
if (this.options.debug) console.log('Not starting timer - mouse on controls');
|
|
2449
2616
|
}
|
|
@@ -2488,8 +2655,9 @@ showControlsNow() {
|
|
|
2488
2655
|
}
|
|
2489
2656
|
|
|
2490
2657
|
hideControlsNow() {
|
|
2491
|
-
// Don't hide if mouse is still over controls
|
|
2492
|
-
|
|
2658
|
+
// Don't hide if mouse is still over controls (allow hiding on touch devices)
|
|
2659
|
+
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
2660
|
+
if (this.mouseOverControls && !isTouchDevice) {
|
|
2493
2661
|
if (this.autoHideDebug && this.options.debug) console.log('⏸️ Not hiding - mouse still over controls');
|
|
2494
2662
|
return;
|
|
2495
2663
|
}
|
|
@@ -2700,7 +2868,7 @@ createControls() {
|
|
|
2700
2868
|
<span class="icon mute-icon hidden">🔇</span>
|
|
2701
2869
|
</button>
|
|
2702
2870
|
|
|
2703
|
-
<div class="volume-container" data-
|
|
2871
|
+
<div class="volume-container" data-mobile-slider="${this.options.volumeSlider}">
|
|
2704
2872
|
<input type="range" class="volume-slider" min="0" max="100" value="100" data-tooltip="volume">
|
|
2705
2873
|
</div>
|
|
2706
2874
|
|
|
@@ -3346,140 +3514,160 @@ optimizeButtonsForSmallHeight() {
|
|
|
3346
3514
|
/* Controls methods for main class - All original functionality preserved exactly */
|
|
3347
3515
|
|
|
3348
3516
|
initializeQualityMonitoring() {
|
|
3349
|
-
|
|
3517
|
+
this.qualityMonitorInterval = setInterval(() => {
|
|
3518
|
+
if (!this.isChangingQuality) {
|
|
3519
|
+
this.updateCurrentPlayingQuality();
|
|
3520
|
+
}
|
|
3521
|
+
}, 3000);
|
|
3522
|
+
|
|
3523
|
+
if (this.video) {
|
|
3524
|
+
this.video.addEventListener('loadedmetadata', () => {
|
|
3525
|
+
setTimeout(() => {
|
|
3526
|
+
if (!this.isChangingQuality) {
|
|
3527
|
+
this.updateCurrentPlayingQuality();
|
|
3528
|
+
}
|
|
3529
|
+
}, 100);
|
|
3530
|
+
});
|
|
3531
|
+
|
|
3532
|
+
this.video.addEventListener('resize', () => {
|
|
3350
3533
|
if (!this.isChangingQuality) {
|
|
3351
3534
|
this.updateCurrentPlayingQuality();
|
|
3352
3535
|
}
|
|
3353
|
-
}
|
|
3354
|
-
|
|
3355
|
-
if (this.video) {
|
|
3356
|
-
this.video.addEventListener('loadedmetadata', () => {
|
|
3357
|
-
setTimeout(() => {
|
|
3358
|
-
if (!this.isChangingQuality) {
|
|
3359
|
-
this.updateCurrentPlayingQuality();
|
|
3360
|
-
}
|
|
3361
|
-
}, 100);
|
|
3362
|
-
});
|
|
3536
|
+
});
|
|
3363
3537
|
|
|
3364
|
-
|
|
3538
|
+
this.video.addEventListener('loadeddata', () => {
|
|
3539
|
+
setTimeout(() => {
|
|
3365
3540
|
if (!this.isChangingQuality) {
|
|
3366
3541
|
this.updateCurrentPlayingQuality();
|
|
3367
3542
|
}
|
|
3368
|
-
});
|
|
3369
|
-
|
|
3370
|
-
this.video.addEventListener('loadeddata', () => {
|
|
3371
|
-
setTimeout(() => {
|
|
3372
|
-
if (!this.isChangingQuality) {
|
|
3373
|
-
this.updateCurrentPlayingQuality();
|
|
3374
|
-
}
|
|
3375
|
-
}, 1000);
|
|
3376
|
-
});
|
|
3377
|
-
}
|
|
3543
|
+
}, 1000);
|
|
3544
|
+
});
|
|
3378
3545
|
}
|
|
3546
|
+
}
|
|
3379
3547
|
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
if (this.video.currentSrc && this.qualities && this.qualities.length > 0) {
|
|
3384
|
-
const currentSource = this.qualities.find(q => {
|
|
3385
|
-
const currentUrl = this.video.currentSrc.toLowerCase();
|
|
3386
|
-
const qualityUrl = q.src.toLowerCase();
|
|
3387
|
-
|
|
3388
|
-
if (this.debugQuality) {
|
|
3389
|
-
if (this.options.debug) console.log('Quality comparison:', {
|
|
3390
|
-
current: currentUrl,
|
|
3391
|
-
quality: qualityUrl,
|
|
3392
|
-
qualityName: q.quality,
|
|
3393
|
-
match: currentUrl === qualityUrl || currentUrl.includes(qualityUrl) || qualityUrl.includes(currentUrl)
|
|
3394
|
-
});
|
|
3395
|
-
}
|
|
3548
|
+
getCurrentPlayingQuality() {
|
|
3549
|
+
if (!this.video) return null;
|
|
3396
3550
|
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3551
|
+
if (this.video.currentSrc && this.qualities && this.qualities.length > 0) {
|
|
3552
|
+
const currentSource = this.qualities.find(q => {
|
|
3553
|
+
const currentUrl = this.video.currentSrc.toLowerCase();
|
|
3554
|
+
const qualityUrl = q.src.toLowerCase();
|
|
3401
3555
|
|
|
3402
|
-
if (
|
|
3403
|
-
if (this.
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3556
|
+
if (this.debugQuality) {
|
|
3557
|
+
if (this.options.debug) console.log('Quality comparison:', {
|
|
3558
|
+
current: currentUrl,
|
|
3559
|
+
quality: qualityUrl,
|
|
3560
|
+
qualityName: q.quality,
|
|
3561
|
+
match: currentUrl === qualityUrl || currentUrl.includes(qualityUrl) || qualityUrl.includes(currentUrl)
|
|
3562
|
+
});
|
|
3407
3563
|
}
|
|
3408
|
-
}
|
|
3409
3564
|
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3565
|
+
return currentUrl === qualityUrl ||
|
|
3566
|
+
currentUrl.includes(qualityUrl) ||
|
|
3567
|
+
qualityUrl.includes(currentUrl);
|
|
3568
|
+
});
|
|
3413
3569
|
|
|
3570
|
+
if (currentSource) {
|
|
3414
3571
|
if (this.debugQuality) {
|
|
3415
|
-
if (this.options.debug) console.log('
|
|
3572
|
+
if (this.options.debug) console.log('Quality found from source:', currentSource.quality);
|
|
3416
3573
|
}
|
|
3417
|
-
|
|
3418
|
-
if (height >= 2160) return '4K';
|
|
3419
|
-
if (height >= 1440) return '1440p';
|
|
3420
|
-
if (height >= 1080) return '1080p';
|
|
3421
|
-
if (height >= 720) return '720p';
|
|
3422
|
-
if (height >= 480) return '480p';
|
|
3423
|
-
if (height >= 360) return '360p';
|
|
3424
|
-
if (height >= 240) return '240p';
|
|
3425
|
-
|
|
3426
|
-
return `${height}p`;
|
|
3574
|
+
return currentSource.quality;
|
|
3427
3575
|
}
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
if (this.video.videoHeight && this.video.videoWidth) {
|
|
3579
|
+
const height = this.video.videoHeight;
|
|
3580
|
+
const width = this.video.videoWidth;
|
|
3428
3581
|
|
|
3429
3582
|
if (this.debugQuality) {
|
|
3430
|
-
if (this.options.debug) console.log('
|
|
3431
|
-
currentSrc: this.video.currentSrc,
|
|
3432
|
-
videoHeight: this.video.videoHeight,
|
|
3433
|
-
videoWidth: this.video.videoWidth,
|
|
3434
|
-
qualities: this.qualities
|
|
3435
|
-
});
|
|
3583
|
+
if (this.options.debug) console.log('Risoluzione video:', { height, width });
|
|
3436
3584
|
}
|
|
3437
3585
|
|
|
3438
|
-
return
|
|
3586
|
+
if (height >= 2160) return '4K';
|
|
3587
|
+
if (height >= 1440) return '1440p';
|
|
3588
|
+
if (height >= 1080) return '1080p';
|
|
3589
|
+
if (height >= 720) return '720p';
|
|
3590
|
+
if (height >= 480) return '480p';
|
|
3591
|
+
if (height >= 360) return '360p';
|
|
3592
|
+
if (height >= 240) return '240p';
|
|
3593
|
+
|
|
3594
|
+
return `${height}p`;
|
|
3439
3595
|
}
|
|
3440
3596
|
|
|
3441
|
-
|
|
3442
|
-
|
|
3597
|
+
if (this.debugQuality) {
|
|
3598
|
+
if (this.options.debug) console.log('No quality detected:', {
|
|
3599
|
+
currentSrc: this.video.currentSrc,
|
|
3600
|
+
videoHeight: this.video.videoHeight,
|
|
3601
|
+
videoWidth: this.video.videoWidth,
|
|
3602
|
+
qualities: this.qualities
|
|
3603
|
+
});
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
return null;
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
updateCurrentPlayingQuality() {
|
|
3610
|
+
const newPlayingQuality = this.getCurrentPlayingQuality();
|
|
3443
3611
|
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
}
|
|
3612
|
+
if (newPlayingQuality && newPlayingQuality !== this.currentPlayingQuality) {
|
|
3613
|
+
if (this.options.debug) console.log(`Quality changed: ${this.currentPlayingQuality} → ${newPlayingQuality}`);
|
|
3614
|
+
this.currentPlayingQuality = newPlayingQuality;
|
|
3615
|
+
this.updateQualityDisplay();
|
|
3449
3616
|
}
|
|
3617
|
+
}
|
|
3450
3618
|
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3619
|
+
updateQualityDisplay() {
|
|
3620
|
+
this.updateQualityButton();
|
|
3621
|
+
this.updateQualityMenu();
|
|
3622
|
+
}
|
|
3455
3623
|
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3624
|
+
updateQualityButton() {
|
|
3625
|
+
const qualityBtn = this.controls?.querySelector('.quality-btn');
|
|
3626
|
+
if (!qualityBtn) return;
|
|
3459
3627
|
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3628
|
+
let btnText = qualityBtn.querySelector('.quality-btn-text');
|
|
3629
|
+
if (!btnText) {
|
|
3630
|
+
// SECURITY: Use DOM methods instead of innerHTML to prevent XSS
|
|
3631
|
+
qualityBtn.textContent = ''; // Clear existing content
|
|
3632
|
+
|
|
3633
|
+
// Create icon element
|
|
3634
|
+
const iconSpan = document.createElement('span');
|
|
3635
|
+
iconSpan.className = 'icon';
|
|
3636
|
+
iconSpan.textContent = '⚙';
|
|
3637
|
+
qualityBtn.appendChild(iconSpan);
|
|
3638
|
+
|
|
3639
|
+
// Create text container
|
|
3640
|
+
btnText = document.createElement('div');
|
|
3641
|
+
btnText.className = 'quality-btn-text';
|
|
3642
|
+
|
|
3643
|
+
// Create selected quality element
|
|
3644
|
+
const selectedQualityDiv = document.createElement('div');
|
|
3645
|
+
selectedQualityDiv.className = 'selected-quality';
|
|
3646
|
+
selectedQualityDiv.textContent = this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality;
|
|
3647
|
+
btnText.appendChild(selectedQualityDiv);
|
|
3648
|
+
|
|
3649
|
+
// Create current quality element
|
|
3650
|
+
const currentQualityDiv = document.createElement('div');
|
|
3651
|
+
currentQualityDiv.className = 'current-quality';
|
|
3652
|
+
currentQualityDiv.textContent = this.currentPlayingQuality || '';
|
|
3653
|
+
btnText.appendChild(currentQualityDiv);
|
|
3654
|
+
|
|
3655
|
+
// Append to button
|
|
3656
|
+
qualityBtn.appendChild(btnText);
|
|
3657
|
+
} else {
|
|
3658
|
+
// SECURITY: Update existing elements using textContent (not innerHTML)
|
|
3659
|
+
const selectedEl = btnText.querySelector('.selected-quality');
|
|
3660
|
+
const currentEl = btnText.querySelector('.current-quality');
|
|
3472
3661
|
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3662
|
+
if (selectedEl) {
|
|
3663
|
+
selectedEl.textContent = this.selectedQuality === 'auto' ? this.t('auto') : this.selectedQuality;
|
|
3664
|
+
}
|
|
3476
3665
|
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
currentEl.style.display = this.currentPlayingQuality ? 'block' : 'none';
|
|
3480
|
-
}
|
|
3666
|
+
if (currentEl) {
|
|
3667
|
+
currentEl.textContent = this.currentPlayingQuality || '';
|
|
3481
3668
|
}
|
|
3482
3669
|
}
|
|
3670
|
+
}
|
|
3483
3671
|
|
|
3484
3672
|
updateQualityMenu() {
|
|
3485
3673
|
const qualityMenu = this.controls?.querySelector('.quality-menu');
|
|
@@ -3533,213 +3721,217 @@ updateQualityMenu() {
|
|
|
3533
3721
|
qualityMenu.innerHTML = menuHTML;
|
|
3534
3722
|
}
|
|
3535
3723
|
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3724
|
+
getQualityStatus() {
|
|
3725
|
+
return {
|
|
3726
|
+
selected: this.selectedQuality,
|
|
3727
|
+
playing: this.currentPlayingQuality,
|
|
3728
|
+
isAuto: this.selectedQuality === 'auto',
|
|
3729
|
+
isChanging: this.isChangingQuality
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3544
3732
|
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3733
|
+
getSelectedQuality() {
|
|
3734
|
+
return this.selectedQuality;
|
|
3735
|
+
}
|
|
3548
3736
|
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3737
|
+
isAutoQualityActive() {
|
|
3738
|
+
return this.selectedQuality === 'auto';
|
|
3739
|
+
}
|
|
3552
3740
|
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3741
|
+
enableQualityDebug() {
|
|
3742
|
+
this.debugQuality = true;
|
|
3743
|
+
this.enableAutoHideDebug(); // Abilita anche debug auto-hide
|
|
3744
|
+
if (this.options.debug) console.log('Quality AND auto-hide debug enabled');
|
|
3745
|
+
this.updateCurrentPlayingQuality();
|
|
3746
|
+
}
|
|
3559
3747
|
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3748
|
+
disableQualityDebug() {
|
|
3749
|
+
this.debugQuality = false;
|
|
3750
|
+
this.disableAutoHideDebug();
|
|
3751
|
+
if (this.options.debug) console.log('Quality AND auto-hide debug disabled');
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3754
|
+
changeQuality(e) {
|
|
3755
|
+
if (!e.target.classList.contains('quality-option')) return;
|
|
3756
|
+
if (this.isChangingQuality) return;
|
|
3757
|
+
|
|
3758
|
+
// Handle adaptive streaming quality change
|
|
3759
|
+
const adaptiveQuality = e.target.getAttribute('data-adaptive-quality');
|
|
3760
|
+
if (adaptiveQuality !== null && this.isAdaptiveStream) {
|
|
3761
|
+
const qualityIndex = adaptiveQuality === 'auto' ? -1 : parseInt(adaptiveQuality);
|
|
3762
|
+
this.setAdaptiveQuality(qualityIndex);
|
|
3763
|
+
this.updateAdaptiveQualityMenu();
|
|
3764
|
+
return;
|
|
3564
3765
|
}
|
|
3565
3766
|
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
if (this.isChangingQuality) return;
|
|
3767
|
+
const quality = e.target.getAttribute('data-quality');
|
|
3768
|
+
if (!quality || quality === this.selectedQuality) return;
|
|
3569
3769
|
|
|
3570
|
-
|
|
3571
|
-
const adaptiveQuality = e.target.getAttribute('data-adaptive-quality');
|
|
3572
|
-
if (adaptiveQuality !== null && this.isAdaptiveStream) {
|
|
3573
|
-
const qualityIndex = adaptiveQuality === 'auto' ? -1 : parseInt(adaptiveQuality);
|
|
3574
|
-
this.setAdaptiveQuality(qualityIndex);
|
|
3575
|
-
this.updateAdaptiveQualityMenu();
|
|
3576
|
-
return;
|
|
3577
|
-
}
|
|
3770
|
+
if (this.options.debug) console.log(`Quality change requested: ${this.selectedQuality} → ${quality}`);
|
|
3578
3771
|
|
|
3579
|
-
|
|
3580
|
-
if (!quality || quality === this.selectedQuality) return;
|
|
3772
|
+
this.selectedQuality = quality;
|
|
3581
3773
|
|
|
3582
|
-
|
|
3774
|
+
if (quality === 'auto') {
|
|
3775
|
+
this.enableAutoQuality();
|
|
3776
|
+
} else {
|
|
3777
|
+
this.setQuality(quality);
|
|
3778
|
+
}
|
|
3583
3779
|
|
|
3584
|
-
|
|
3780
|
+
this.updateQualityDisplay();
|
|
3781
|
+
}
|
|
3585
3782
|
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
} else {
|
|
3589
|
-
this.setQuality(quality);
|
|
3590
|
-
}
|
|
3783
|
+
setQuality(targetQuality) {
|
|
3784
|
+
if (this.options.debug) console.log(`setQuality("${targetQuality}") called`);
|
|
3591
3785
|
|
|
3592
|
-
|
|
3786
|
+
if (!targetQuality) {
|
|
3787
|
+
if (this.options.debug) console.error('targetQuality is empty!');
|
|
3788
|
+
return;
|
|
3593
3789
|
}
|
|
3594
3790
|
|
|
3595
|
-
|
|
3596
|
-
|
|
3791
|
+
if (!this.video || !this.qualities || this.qualities.length === 0) return;
|
|
3792
|
+
if (this.isChangingQuality) return;
|
|
3597
3793
|
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3794
|
+
const newSource = this.qualities.find(q => q.quality === targetQuality);
|
|
3795
|
+
if (!newSource || !newSource.src) {
|
|
3796
|
+
if (this.options.debug) console.error(`Quality "${targetQuality}" not found`);
|
|
3797
|
+
return;
|
|
3798
|
+
}
|
|
3602
3799
|
|
|
3603
|
-
|
|
3604
|
-
|
|
3800
|
+
const currentTime = this.video.currentTime || 0;
|
|
3801
|
+
const wasPlaying = !this.video.paused;
|
|
3605
3802
|
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
return;
|
|
3610
|
-
}
|
|
3803
|
+
this.isChangingQuality = true;
|
|
3804
|
+
this.selectedQuality = targetQuality;
|
|
3805
|
+
this.video.pause();
|
|
3611
3806
|
|
|
3612
|
-
|
|
3613
|
-
|
|
3807
|
+
// Show loading state during quality change
|
|
3808
|
+
this.showLoading();
|
|
3809
|
+
if (this.video.classList) {
|
|
3810
|
+
this.video.classList.add('quality-changing');
|
|
3811
|
+
}
|
|
3614
3812
|
|
|
3615
|
-
|
|
3616
|
-
this.
|
|
3617
|
-
this.video.
|
|
3813
|
+
const onLoadedData = () => {
|
|
3814
|
+
if (this.options.debug) console.log(`Quality ${targetQuality} applied!`);
|
|
3815
|
+
this.video.currentTime = currentTime;
|
|
3618
3816
|
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3817
|
+
if (wasPlaying) {
|
|
3818
|
+
this.video.play().catch(e => {
|
|
3819
|
+
if (this.options.debug) console.log('Play error:', e);
|
|
3820
|
+
});
|
|
3623
3821
|
}
|
|
3624
3822
|
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
if (wasPlaying) {
|
|
3630
|
-
this.video.play().catch(e => {
|
|
3631
|
-
if (this.options.debug) console.log('Play error:', e);
|
|
3632
|
-
});
|
|
3633
|
-
}
|
|
3634
|
-
|
|
3635
|
-
this.currentPlayingQuality = targetQuality;
|
|
3636
|
-
this.updateQualityDisplay();
|
|
3637
|
-
this.isChangingQuality = false;
|
|
3823
|
+
this.currentPlayingQuality = targetQuality;
|
|
3824
|
+
this.updateQualityDisplay();
|
|
3825
|
+
this.isChangingQuality = false;
|
|
3638
3826
|
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3827
|
+
// Restore resolution settings after quality change
|
|
3828
|
+
this.restoreResolutionAfterQualityChange();
|
|
3829
|
+
cleanup();
|
|
3830
|
+
};
|
|
3643
3831
|
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
cleanup();
|
|
3648
|
-
};
|
|
3832
|
+
const onError = (error) => {
|
|
3833
|
+
if (this.options.debug) console.error(`Loading error ${targetQuality}:`, error);
|
|
3834
|
+
this.isChangingQuality = false;
|
|
3649
3835
|
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
this.video.removeEventListener('error', onError);
|
|
3653
|
-
};
|
|
3836
|
+
// Trigger ended event for error handling
|
|
3837
|
+
this.onVideoError(error);
|
|
3654
3838
|
|
|
3655
|
-
|
|
3656
|
-
|
|
3839
|
+
cleanup();
|
|
3840
|
+
};
|
|
3657
3841
|
|
|
3658
|
-
|
|
3659
|
-
this.video.
|
|
3660
|
-
|
|
3842
|
+
const cleanup = () => {
|
|
3843
|
+
this.video.removeEventListener('loadeddata', onLoadedData);
|
|
3844
|
+
this.video.removeEventListener('error', onError);
|
|
3845
|
+
};
|
|
3661
3846
|
|
|
3662
|
-
|
|
3663
|
-
|
|
3847
|
+
this.video.addEventListener('loadeddata', onLoadedData, { once: true });
|
|
3848
|
+
this.video.addEventListener('error', onError, { once: true });
|
|
3664
3849
|
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
}
|
|
3850
|
+
this.video.src = newSource.src;
|
|
3851
|
+
this.video.load();
|
|
3852
|
+
}
|
|
3669
3853
|
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
if (success && currentTime > 0 && this.video.duration) {
|
|
3673
|
-
this.video.currentTime = Math.min(currentTime, this.video.duration);
|
|
3674
|
-
}
|
|
3854
|
+
finishQualityChange(success, wasPlaying, currentTime, currentVolume, wasMuted, targetQuality) {
|
|
3855
|
+
if (this.options.debug) console.log(`Quality change completion: success=${success}, target=${targetQuality}`);
|
|
3675
3856
|
|
|
3676
|
-
|
|
3677
|
-
|
|
3857
|
+
if (this.qualityChangeTimeout) {
|
|
3858
|
+
clearTimeout(this.qualityChangeTimeout);
|
|
3859
|
+
this.qualityChangeTimeout = null;
|
|
3860
|
+
}
|
|
3678
3861
|
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
}
|
|
3684
|
-
} catch (error) {
|
|
3685
|
-
if (this.options.debug) console.error('Errore ripristino stato:', error);
|
|
3862
|
+
if (this.video) {
|
|
3863
|
+
try {
|
|
3864
|
+
if (success && currentTime > 0 && this.video.duration) {
|
|
3865
|
+
this.video.currentTime = Math.min(currentTime, this.video.duration);
|
|
3686
3866
|
}
|
|
3687
3867
|
|
|
3688
|
-
|
|
3689
|
-
|
|
3868
|
+
this.video.volume = currentVolume;
|
|
3869
|
+
this.video.muted = wasMuted;
|
|
3870
|
+
|
|
3871
|
+
if (success && wasPlaying) {
|
|
3872
|
+
this.video.play().catch(err => {
|
|
3873
|
+
if (this.options.debug) console.warn('Play after quality change failed:', err);
|
|
3874
|
+
});
|
|
3690
3875
|
}
|
|
3876
|
+
} catch (error) {
|
|
3877
|
+
if (this.options.debug) console.error('Errore ripristino stato:', error);
|
|
3691
3878
|
}
|
|
3692
3879
|
|
|
3693
|
-
this.
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
if (success) {
|
|
3697
|
-
if (this.options.debug) console.log('Quality change completed successfully');
|
|
3698
|
-
setTimeout(() => {
|
|
3699
|
-
this.currentPlayingQuality = targetQuality;
|
|
3700
|
-
this.updateQualityDisplay();
|
|
3701
|
-
if (this.options.debug) console.log(`🎯 Quality confirmed active: ${targetQuality}`);
|
|
3702
|
-
}, 100);
|
|
3703
|
-
} else {
|
|
3704
|
-
if (this.options.debug) console.warn('Quality change failed or timeout');
|
|
3880
|
+
if (this.video.classList) {
|
|
3881
|
+
this.video.classList.remove('quality-changing');
|
|
3705
3882
|
}
|
|
3883
|
+
}
|
|
3706
3884
|
|
|
3885
|
+
this.hideLoading();
|
|
3886
|
+
this.isChangingQuality = false;
|
|
3887
|
+
|
|
3888
|
+
if (success) {
|
|
3889
|
+
if (this.options.debug) console.log('Quality change completed successfully');
|
|
3707
3890
|
setTimeout(() => {
|
|
3708
|
-
this.
|
|
3709
|
-
|
|
3891
|
+
this.currentPlayingQuality = targetQuality;
|
|
3892
|
+
this.updateQualityDisplay();
|
|
3893
|
+
if (this.options.debug) console.log(`🎯 Quality confirmed active: ${targetQuality}`);
|
|
3894
|
+
}, 100);
|
|
3895
|
+
} else {
|
|
3896
|
+
if (this.options.debug) console.warn('Quality change failed or timeout');
|
|
3710
3897
|
}
|
|
3711
3898
|
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
}
|
|
3717
|
-
}
|
|
3899
|
+
setTimeout(() => {
|
|
3900
|
+
this.updateCurrentPlayingQuality();
|
|
3901
|
+
}, 2000);
|
|
3902
|
+
}
|
|
3718
3903
|
|
|
3719
|
-
|
|
3720
|
-
|
|
3904
|
+
cleanupQualityChange() {
|
|
3905
|
+
if (this.qualityChangeTimeout) {
|
|
3906
|
+
clearTimeout(this.qualityChangeTimeout);
|
|
3907
|
+
this.qualityChangeTimeout = null;
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3721
3910
|
|
|
3722
|
-
|
|
3723
|
-
|
|
3911
|
+
enableAutoQuality() {
|
|
3912
|
+
if (this.options.debug) console.log('🔄 enableAutoQuality - keeping selectedQuality as "auto"');
|
|
3724
3913
|
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
this.updateQualityDisplay();
|
|
3728
|
-
return;
|
|
3729
|
-
}
|
|
3914
|
+
// IMPORTANT: Keep selectedQuality as 'auto' for proper UI display
|
|
3915
|
+
this.selectedQuality = 'auto';
|
|
3730
3916
|
|
|
3731
|
-
|
|
3732
|
-
|
|
3917
|
+
if (!this.qualities || this.qualities.length === 0) {
|
|
3918
|
+
if (this.options.debug) console.warn('⚠️ No qualities available for auto selection');
|
|
3919
|
+
this.updateQualityDisplay();
|
|
3920
|
+
return;
|
|
3921
|
+
}
|
|
3733
3922
|
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
console.log('📊 selectedQuality remains: "auto" (for UI)');
|
|
3737
|
-
}
|
|
3923
|
+
// Smart connection-based quality selection
|
|
3924
|
+
let autoSelectedQuality = this.getAutoQualityBasedOnConnection();
|
|
3738
3925
|
|
|
3739
|
-
|
|
3740
|
-
|
|
3926
|
+
if (this.options.debug) {
|
|
3927
|
+
console.log('🎯 Auto quality selected:', autoSelectedQuality);
|
|
3928
|
+
console.log('📊 selectedQuality remains: "auto" (for UI)');
|
|
3741
3929
|
}
|
|
3742
3930
|
|
|
3931
|
+
// Apply the auto-selected quality but keep UI showing "auto"
|
|
3932
|
+
this.applyAutoQuality(autoSelectedQuality);
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3743
3935
|
// ENHANCED CONNECTION DETECTION - Uses RTT + downlink heuristics
|
|
3744
3936
|
// Handles both Ethernet and real mobile 4G intelligently
|
|
3745
3937
|
|
|
@@ -4006,102 +4198,120 @@ getAutoQualityBasedOnConnection() {
|
|
|
4006
4198
|
return maxQuality.quality;
|
|
4007
4199
|
}
|
|
4008
4200
|
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4201
|
+
applyAutoQuality(targetQuality) {
|
|
4202
|
+
if (!targetQuality || !this.video || !this.qualities || this.qualities.length === 0) {
|
|
4203
|
+
return;
|
|
4204
|
+
}
|
|
4013
4205
|
|
|
4014
|
-
|
|
4206
|
+
if (this.isChangingQuality) return;
|
|
4015
4207
|
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4208
|
+
const newSource = this.qualities.find(q => q.quality === targetQuality);
|
|
4209
|
+
if (!newSource || !newSource.src) {
|
|
4210
|
+
if (this.options.debug) console.error('Auto quality', targetQuality, 'not found');
|
|
4211
|
+
return;
|
|
4212
|
+
}
|
|
4021
4213
|
|
|
4022
|
-
|
|
4023
|
-
|
|
4214
|
+
// Store current resolution to restore after quality change
|
|
4215
|
+
const currentResolution = this.getCurrentResolution();
|
|
4024
4216
|
|
|
4025
|
-
|
|
4026
|
-
|
|
4217
|
+
const currentTime = this.video.currentTime || 0;
|
|
4218
|
+
const wasPlaying = !this.video.paused;
|
|
4027
4219
|
|
|
4028
|
-
|
|
4029
|
-
|
|
4220
|
+
this.isChangingQuality = true;
|
|
4221
|
+
this.video.pause();
|
|
4030
4222
|
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
if (this.options.debug) console.log('Autoplay prevented:', e);
|
|
4037
|
-
});
|
|
4038
|
-
}
|
|
4039
|
-
this.currentPlayingQuality = targetQuality;
|
|
4040
|
-
// Keep selectedQuality as 'auto' for UI display
|
|
4041
|
-
this.updateQualityDisplay();
|
|
4042
|
-
this.isChangingQuality = false;
|
|
4043
|
-
cleanup();
|
|
4044
|
-
};
|
|
4223
|
+
// Show loading overlay
|
|
4224
|
+
this.showLoading();
|
|
4225
|
+
if (this.video.classList) {
|
|
4226
|
+
this.video.classList.add('quality-changing');
|
|
4227
|
+
}
|
|
4045
4228
|
|
|
4046
|
-
const onError = (error) => {
|
|
4047
|
-
if (this.options.debug) console.error('Auto quality loading error:', error);
|
|
4048
|
-
this.isChangingQuality = false;
|
|
4049
|
-
cleanup();
|
|
4050
|
-
};
|
|
4051
4229
|
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4230
|
+
const onLoadedData = () => {
|
|
4231
|
+
if (this.options.debug) console.log('Auto quality', targetQuality, 'applied');
|
|
4232
|
+
this.video.currentTime = currentTime;
|
|
4233
|
+
if (wasPlaying) {
|
|
4234
|
+
this.video.play().catch(e => {
|
|
4235
|
+
if (this.options.debug) console.log('Autoplay prevented:', e);
|
|
4236
|
+
});
|
|
4237
|
+
}
|
|
4238
|
+
this.currentPlayingQuality = targetQuality;
|
|
4239
|
+
// Keep selectedQuality as 'auto' for UI display
|
|
4240
|
+
this.updateQualityDisplay();
|
|
4056
4241
|
|
|
4057
|
-
|
|
4058
|
-
this.
|
|
4059
|
-
this.video.
|
|
4060
|
-
|
|
4061
|
-
|
|
4242
|
+
// Hide loading overlay
|
|
4243
|
+
this.hideLoading();
|
|
4244
|
+
if (this.video.classList) {
|
|
4245
|
+
this.video.classList.remove('quality-changing');
|
|
4246
|
+
}
|
|
4062
4247
|
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
this.selectedQuality = quality;
|
|
4248
|
+
this.isChangingQuality = false;
|
|
4249
|
+
cleanup();
|
|
4250
|
+
};
|
|
4067
4251
|
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
this.setQuality(quality);
|
|
4072
|
-
}
|
|
4252
|
+
const onError = (error) => {
|
|
4253
|
+
if (this.options.debug) console.error('Auto quality loading error:', error);
|
|
4254
|
+
this.isChangingQuality = false;
|
|
4073
4255
|
|
|
4074
|
-
|
|
4075
|
-
|
|
4256
|
+
// Trigger ended event for error handling
|
|
4257
|
+
this.onVideoError(error);
|
|
4076
4258
|
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
}
|
|
4259
|
+
cleanup();
|
|
4260
|
+
};
|
|
4080
4261
|
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4262
|
+
const cleanup = () => {
|
|
4263
|
+
this.video.removeEventListener('loadeddata', onLoadedData);
|
|
4264
|
+
this.video.removeEventListener('error', onError);
|
|
4265
|
+
};
|
|
4266
|
+
|
|
4267
|
+
this.video.addEventListener('loadeddata', onLoadedData, { once: true });
|
|
4268
|
+
this.video.addEventListener('error', onError, { once: true });
|
|
4269
|
+
this.video.src = newSource.src;
|
|
4270
|
+
this.video.load();
|
|
4271
|
+
}
|
|
4272
|
+
|
|
4273
|
+
setDefaultQuality(quality) {
|
|
4274
|
+
if (this.options.debug) console.log(`🔧 Setting defaultQuality: "${quality}"`);
|
|
4275
|
+
this.options.defaultQuality = quality;
|
|
4276
|
+
this.selectedQuality = quality;
|
|
4277
|
+
|
|
4278
|
+
if (quality === 'auto') {
|
|
4279
|
+
this.enableAutoQuality();
|
|
4280
|
+
} else {
|
|
4281
|
+
this.setQuality(quality);
|
|
4090
4282
|
}
|
|
4091
4283
|
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
if (!qualityMenu || !this.isAdaptiveStream) return;
|
|
4284
|
+
return this;
|
|
4285
|
+
}
|
|
4095
4286
|
|
|
4096
|
-
|
|
4287
|
+
getDefaultQuality() {
|
|
4288
|
+
return this.options.defaultQuality;
|
|
4289
|
+
}
|
|
4097
4290
|
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4291
|
+
getQualityLabel(height, width) {
|
|
4292
|
+
if (height >= 2160) return '4K';
|
|
4293
|
+
if (height >= 1440) return '1440p';
|
|
4294
|
+
if (height >= 1080) return '1080p';
|
|
4295
|
+
if (height >= 720) return '720p';
|
|
4296
|
+
if (height >= 480) return '480p';
|
|
4297
|
+
if (height >= 360) return '360p';
|
|
4298
|
+
if (height >= 240) return '240p';
|
|
4299
|
+
return `${height}p`;
|
|
4300
|
+
}
|
|
4102
4301
|
|
|
4103
|
-
|
|
4104
|
-
|
|
4302
|
+
updateAdaptiveQualityMenu() {
|
|
4303
|
+
const qualityMenu = this.controls?.querySelector('.quality-menu');
|
|
4304
|
+
if (!qualityMenu || !this.isAdaptiveStream) return;
|
|
4305
|
+
|
|
4306
|
+
let menuHTML = `<div class="quality-option ${this.isAutoQuality() ? 'active' : ''}" data-adaptive-quality="auto">Auto</div>`;
|
|
4307
|
+
|
|
4308
|
+
this.adaptiveQualities.forEach(quality => {
|
|
4309
|
+
const isActive = this.getCurrentAdaptiveQuality() === quality.index;
|
|
4310
|
+
menuHTML += `<div class="quality-option ${isActive ? 'active' : ''}" data-adaptive-quality="${quality.index}">${quality.label}</div>`;
|
|
4311
|
+
});
|
|
4312
|
+
|
|
4313
|
+
qualityMenu.innerHTML = menuHTML;
|
|
4314
|
+
}
|
|
4105
4315
|
|
|
4106
4316
|
updateAdaptiveQualityDisplay() {
|
|
4107
4317
|
if (!this.isAdaptiveStream) return;
|
|
@@ -4127,61 +4337,61 @@ updateAdaptiveQualityDisplay() {
|
|
|
4127
4337
|
}
|
|
4128
4338
|
}
|
|
4129
4339
|
|
|
4130
|
-
|
|
4131
|
-
|
|
4340
|
+
setAdaptiveQuality(qualityIndex) {
|
|
4341
|
+
if (!this.isAdaptiveStream) return;
|
|
4132
4342
|
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
}
|
|
4158
|
-
this.selectedQuality = this.adaptiveQualities[qualityIndex]?.label || 'Unknown';
|
|
4343
|
+
try {
|
|
4344
|
+
if (qualityIndex === 'auto' || qualityIndex === -1) {
|
|
4345
|
+
// Enable auto quality
|
|
4346
|
+
if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
|
|
4347
|
+
this.dashPlayer.updateSettings({
|
|
4348
|
+
streaming: {
|
|
4349
|
+
abr: { autoSwitchBitrate: { video: true } }
|
|
4350
|
+
}
|
|
4351
|
+
});
|
|
4352
|
+
} else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
|
|
4353
|
+
this.hlsPlayer.currentLevel = -1; // Auto level selection
|
|
4354
|
+
}
|
|
4355
|
+
this.selectedQuality = 'auto';
|
|
4356
|
+
} else {
|
|
4357
|
+
// Set specific quality
|
|
4358
|
+
if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
|
|
4359
|
+
this.dashPlayer.updateSettings({
|
|
4360
|
+
streaming: {
|
|
4361
|
+
abr: { autoSwitchBitrate: { video: false } }
|
|
4362
|
+
}
|
|
4363
|
+
});
|
|
4364
|
+
this.dashPlayer.setQualityFor('video', qualityIndex);
|
|
4365
|
+
} else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
|
|
4366
|
+
this.hlsPlayer.currentLevel = qualityIndex;
|
|
4159
4367
|
}
|
|
4368
|
+
this.selectedQuality = this.adaptiveQualities[qualityIndex]?.label || 'Unknown';
|
|
4369
|
+
}
|
|
4160
4370
|
|
|
4161
|
-
|
|
4162
|
-
|
|
4371
|
+
this.updateAdaptiveQualityDisplay();
|
|
4372
|
+
if (this.options.debug) console.log('📡 Adaptive quality set to:', qualityIndex);
|
|
4163
4373
|
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
}
|
|
4374
|
+
} catch (error) {
|
|
4375
|
+
if (this.options.debug) console.error('📡 Error setting adaptive quality:', error);
|
|
4167
4376
|
}
|
|
4377
|
+
}
|
|
4168
4378
|
|
|
4169
|
-
|
|
4170
|
-
|
|
4379
|
+
getCurrentAdaptiveQuality() {
|
|
4380
|
+
if (!this.isAdaptiveStream) return null;
|
|
4171
4381
|
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
}
|
|
4178
|
-
} catch (error) {
|
|
4179
|
-
if (this.options.debug) console.error('📡 Error getting current quality:', error);
|
|
4382
|
+
try {
|
|
4383
|
+
if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
|
|
4384
|
+
return this.dashPlayer.getQualityFor('video');
|
|
4385
|
+
} else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
|
|
4386
|
+
return this.hlsPlayer.currentLevel;
|
|
4180
4387
|
}
|
|
4181
|
-
|
|
4182
|
-
|
|
4388
|
+
} catch (error) {
|
|
4389
|
+
if (this.options.debug) console.error('📡 Error getting current quality:', error);
|
|
4183
4390
|
}
|
|
4184
4391
|
|
|
4392
|
+
return null;
|
|
4393
|
+
}
|
|
4394
|
+
|
|
4185
4395
|
getCurrentAdaptiveQualityLabel() {
|
|
4186
4396
|
const currentIndex = this.getCurrentAdaptiveQuality();
|
|
4187
4397
|
if (currentIndex === null || currentIndex === -1) {
|
|
@@ -4190,75 +4400,75 @@ getCurrentAdaptiveQualityLabel() {
|
|
|
4190
4400
|
return this.adaptiveQualities[currentIndex]?.label || this.tauto;
|
|
4191
4401
|
}
|
|
4192
4402
|
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
}
|
|
4198
|
-
return this.selectedQuality === 'auto';
|
|
4403
|
+
isAutoQuality() {
|
|
4404
|
+
if (this.isAdaptiveStream) {
|
|
4405
|
+
const currentQuality = this.getCurrentAdaptiveQuality();
|
|
4406
|
+
return currentQuality === null || currentQuality === -1 || this.selectedQuality === 'auto';
|
|
4199
4407
|
}
|
|
4408
|
+
return this.selectedQuality === 'auto';
|
|
4409
|
+
}
|
|
4200
4410
|
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4411
|
+
setResolution(resolution) {
|
|
4412
|
+
if (!this.video || !this.container) {
|
|
4413
|
+
if (this.options.debug) console.warn("Video or container not available for setResolution");
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4206
4416
|
|
|
4207
|
-
|
|
4208
|
-
|
|
4417
|
+
// Supported values including new scale-to-fit mode
|
|
4418
|
+
const supportedResolutions = ["normal", "4:3", "16:9", "stretched", "fit-to-screen", "scale-to-fit"];
|
|
4209
4419
|
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4420
|
+
if (!supportedResolutions.includes(resolution)) {
|
|
4421
|
+
if (this.options.debug) console.warn(`Resolution "${resolution}" not supported. Supported values: ${supportedResolutions.join(", ")}`);
|
|
4422
|
+
return;
|
|
4423
|
+
}
|
|
4214
4424
|
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4425
|
+
// Remove all previous resolution classes
|
|
4426
|
+
const allResolutionClasses = [
|
|
4427
|
+
"resolution-normal", "resolution-4-3", "resolution-16-9",
|
|
4428
|
+
"resolution-stretched", "resolution-fit-to-screen", "resolution-scale-to-fit"
|
|
4429
|
+
];
|
|
4220
4430
|
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4431
|
+
this.video.classList.remove(...allResolutionClasses);
|
|
4432
|
+
if (this.container) {
|
|
4433
|
+
this.container.classList.remove(...allResolutionClasses);
|
|
4434
|
+
}
|
|
4225
4435
|
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4436
|
+
// Apply new resolution class
|
|
4437
|
+
const cssClass = `resolution-${resolution.replace(":", "-")}`;
|
|
4438
|
+
this.video.classList.add(cssClass);
|
|
4439
|
+
if (this.container) {
|
|
4440
|
+
this.container.classList.add(cssClass);
|
|
4441
|
+
}
|
|
4232
4442
|
|
|
4233
|
-
|
|
4234
|
-
|
|
4443
|
+
// Update option
|
|
4444
|
+
this.options.resolution = resolution;
|
|
4235
4445
|
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
}
|
|
4446
|
+
if (this.options.debug) {
|
|
4447
|
+
console.log(`Resolution applied: ${resolution} (CSS class: ${cssClass})`);
|
|
4239
4448
|
}
|
|
4449
|
+
}
|
|
4240
4450
|
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4451
|
+
getCurrentResolution() {
|
|
4452
|
+
return this.options.resolution || "normal";
|
|
4453
|
+
}
|
|
4244
4454
|
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
}
|
|
4455
|
+
initializeResolution() {
|
|
4456
|
+
if (this.options.resolution && this.options.resolution !== "normal") {
|
|
4457
|
+
this.setResolution(this.options.resolution);
|
|
4249
4458
|
}
|
|
4459
|
+
}
|
|
4250
4460
|
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
}
|
|
4256
|
-
// Small delay to ensure video element is ready
|
|
4257
|
-
setTimeout(() => {
|
|
4258
|
-
this.setResolution(this.options.resolution);
|
|
4259
|
-
}, 150);
|
|
4461
|
+
restoreResolutionAfterQualityChange() {
|
|
4462
|
+
if (this.options.resolution && this.options.resolution !== "normal") {
|
|
4463
|
+
if (this.options.debug) {
|
|
4464
|
+
console.log(`Restoring resolution "${this.options.resolution}" after quality change`);
|
|
4260
4465
|
}
|
|
4466
|
+
// Small delay to ensure video element is ready
|
|
4467
|
+
setTimeout(() => {
|
|
4468
|
+
this.setResolution(this.options.resolution);
|
|
4469
|
+
}, 150);
|
|
4261
4470
|
}
|
|
4471
|
+
}
|
|
4262
4472
|
|
|
4263
4473
|
/* Subtitles Module for MYETV Video Player
|
|
4264
4474
|
* Conservative modularization - original code preserved exactly
|
|
@@ -4303,7 +4513,7 @@ createCustomSubtitleOverlay() {
|
|
|
4303
4513
|
'bottom: 80px;' +
|
|
4304
4514
|
'left: 50%;' +
|
|
4305
4515
|
'transform: translateX(-50%);' +
|
|
4306
|
-
'z-index: 5;' +
|
|
4516
|
+
'z-index: 5;' +
|
|
4307
4517
|
'color: white;' +
|
|
4308
4518
|
'font-family: Arial, sans-serif;' +
|
|
4309
4519
|
'font-size: clamp(12px, 4vw, 18px);' + // RESPONSIVE font-size
|
|
@@ -4397,7 +4607,7 @@ parseCustomSRT(srtText) {
|
|
|
4397
4607
|
if (timeMatch) {
|
|
4398
4608
|
var startTime = this.customTimeToSeconds(timeMatch[1]);
|
|
4399
4609
|
var endTime = this.customTimeToSeconds(timeMatch[2]);
|
|
4400
|
-
var text = lines.slice(2).join('\n').trim()
|
|
4610
|
+
var text = this.sanitizeSubtitleText(lines.slice(2).join('\n').trim());
|
|
4401
4611
|
|
|
4402
4612
|
if (text.length > 0 && startTime < endTime) {
|
|
4403
4613
|
subtitles.push({
|