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