myetv-player 1.7.0 → 1.7.2

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.
@@ -674,6 +674,9 @@ constructor(videoElement, options = {}) {
674
674
  showPictureInPicture: true, // Enable PiP button
675
675
  showSubtitles: true, // Enable subtitles button
676
676
  subtitlesEnabled: false, // Enable subtitles by default if available
677
+ externalSubtitleTracks: [],// external subtitles track from url
678
+ externalSubtitleLabel: 'Default Subtitles', //label for the subtitle track url
679
+ externalSubtitleLang: 'en', // language of the subtitles track url
677
680
  showSettingsMenu: true, // Show settings menu in top bar
678
681
  autoHide: true, // auto-hide controls when idle
679
682
  autoHideDelay: 3000, // hide controls after ... seconds of inactivity (specificed in milliseconds)
@@ -700,6 +703,7 @@ constructor(videoElement, options = {}) {
700
703
  brandLogoUrl: '', // URL for brand logo image
701
704
  brandLogoLinkUrl: '', // Optional URL to open when clicking the logo
702
705
  brandLogoTooltipText: '', // Tooltip text for brand logo
706
+ loadingLogo: null, // URL image to show inside the loading circle
703
707
  playlistEnabled: true, // Enable/disable playlist detection
704
708
  playlistAutoPlay: true, // Auto-play next video when current ends
705
709
  playlistLoop: false, // Loop playlist when reaching the end
@@ -840,9 +844,33 @@ constructor(videoElement, options = {}) {
840
844
  if (this.options.language && this.isI18nAvailable()) {
841
845
  VideoPlayerTranslations.setLanguage(this.options.language);
842
846
  }
843
- // Apply autoplay if enabled
844
- if (options.autoplay) {
845
- this.video.autoplay = true;
847
+
848
+ this.savedAutoplayIntent = options.autoplay || false;
849
+ this.options.autoplay = false;
850
+
851
+ if (this.video) {
852
+ if (this.video.hasAttribute('autoplay')) {
853
+ this.savedAutoplayIntent = true;
854
+ this.video.removeAttribute('autoplay');
855
+ }
856
+
857
+ // Physically stop the browser engine from pre-loading and playing
858
+ this.video.pause();
859
+ this.video.preload = 'none';
860
+
861
+ if (this.video.hasAttribute('poster')) {
862
+ this.savedPoster = this.video.getAttribute('poster');
863
+ this.video.setAttribute('poster', 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
864
+ }
865
+
866
+ this.video.pause();
867
+ this.video.preload = 'none';
868
+
869
+ this.video.removeAttribute('src');
870
+ this.video.load();
871
+
872
+ this.video.style.opacity = '0';
873
+ this.video.style.visibility = 'hidden';
846
874
  }
847
875
 
848
876
  try {
@@ -934,17 +962,8 @@ constructor(videoElement, options = {}) {
934
962
  this.updateVolumeSliderVisual();
935
963
  this.initVolumeTooltip();
936
964
  this.updateTooltips();
937
- this.markPlayerReady();
938
- this.initializePluginSystem();
939
- this.restoreSourcesAsync();
940
965
 
941
- this.initializeSubtitles();
942
- this.initializeQualityMonitoring();
943
-
944
- this.initializeResolution();
945
- this.initializeChapters();
946
- this.initializePoster();
947
- this.initializeWatermark();
966
+ this.initializePluginSystem();
948
967
 
949
968
  } catch (error) {
950
969
  if (this.options.debug) console.error('Video player initialization error:', error);
@@ -1077,6 +1096,8 @@ interceptAutoLoading() {
1077
1096
 
1078
1097
  this.hideNativePlayer();
1079
1098
 
1099
+ this.video.load();
1100
+
1080
1101
  if (this.options.debug) console.log('📁 Sources temporarily disabled to prevent blocking');
1081
1102
  }
1082
1103
 
@@ -1245,6 +1266,7 @@ markPlayerReady() {
1245
1266
  this.video.style.visibility = '';
1246
1267
  this.video.style.opacity = '';
1247
1268
  this.video.style.pointerEvents = '';
1269
+ this.video.style.display = '';
1248
1270
  }
1249
1271
 
1250
1272
  // UPDATE SETTINGS MENU VISIBILITY IF APPLICABLE
@@ -1337,24 +1359,40 @@ createPlayerStructure() {
1337
1359
  createInitialLoading() {
1338
1360
  const initialLoader = document.createElement('div');
1339
1361
  initialLoader.className = 'initial-loading';
1340
- initialLoader.innerHTML = '<div class="loading-spinner"></div><div class="loading-text"></div>';
1362
+ initialLoader.innerHTML = `
1363
+ <div class="loading-spinner-wrap">
1364
+ <div class="loading-spinner"></div>
1365
+ ${this.options.loadingLogo
1366
+ ? `<img class="loading-spinner-logo" src="${this.options.loadingLogo}" alt="" />`
1367
+ : ''}
1368
+ </div>
1369
+ <div class="loading-text"></div>
1370
+ `;
1341
1371
  this.container.appendChild(initialLoader);
1342
1372
  this.initialLoading = initialLoader;
1343
1373
  }
1344
1374
 
1345
- collectVideoQualities() {
1346
- if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
1347
- }
1348
-
1349
1375
  createLoadingOverlay() {
1350
1376
  const overlay = document.createElement('div');
1351
1377
  overlay.className = 'loading-overlay';
1352
- overlay.id = 'loadingOverlay-' + this.getUniqueId();
1353
- overlay.innerHTML = '<div class="loading-spinner"></div><div class="loading-text"></div>';
1378
+ overlay.id = `loadingOverlay-${this.getUniqueId()}`;
1379
+ overlay.innerHTML = `
1380
+ <div class="loading-spinner-wrap">
1381
+ <div class="loading-spinner"></div>
1382
+ ${this.options.loadingLogo
1383
+ ? `<img class="loading-spinner-logo" src="${this.options.loadingLogo}" alt="" />`
1384
+ : ''}
1385
+ </div>
1386
+ <div class="loading-text"></div>
1387
+ `;
1354
1388
  this.container.appendChild(overlay);
1355
1389
  this.loadingOverlay = overlay;
1356
1390
  }
1357
1391
 
1392
+ collectVideoQualities() {
1393
+ if (this.options.debug) console.log('📁 Video qualities will be loaded with restored sources');
1394
+ }
1395
+
1358
1396
  updateTooltips() {
1359
1397
  if (!this.controls) return;
1360
1398
 
@@ -2013,6 +2051,12 @@ updateSeekTooltip(e) {
2013
2051
  }
2014
2052
 
2015
2053
  play() {
2054
+ if (!this.isPlayerReady) {
2055
+ this.savedAutoplayIntent = true;
2056
+ if (this.options.debug) console.log('🛑 Play requested but player is booting. Intent saved for later.');
2057
+ return Promise ? Promise.resolve() : undefined;
2058
+ }
2059
+
2016
2060
  if (!this.video || this.isChangingQuality) return;
2017
2061
 
2018
2062
  this.video.play().catch(err => {
@@ -2022,7 +2066,6 @@ play() {
2022
2066
  if (this.playIcon) this.playIcon.classList.add('hidden');
2023
2067
  if (this.pauseIcon) this.pauseIcon.classList.remove('hidden');
2024
2068
 
2025
- // Trigger event played
2026
2069
  this.triggerEvent('played', {
2027
2070
  currentTime: this.getCurrentTime(),
2028
2071
  duration: this.getDuration()
@@ -2030,6 +2073,11 @@ play() {
2030
2073
  }
2031
2074
 
2032
2075
  pause() {
2076
+ if (!this.isPlayerReady) {
2077
+ this.savedAutoplayIntent = false;
2078
+ return;
2079
+ }
2080
+
2033
2081
  if (!this.video) return;
2034
2082
 
2035
2083
  this.video.pause();
@@ -4399,6 +4447,53 @@ populateSettingsMenu() {
4399
4447
  settingsMenu.innerHTML = menuHTML;
4400
4448
  }
4401
4449
 
4450
+ /**
4451
+ * Updates specific UI elements inside the settings menu
4452
+ * without recreating the entire DOM (prevents accordions from closing)
4453
+ */
4454
+ updateSettingsMenuUI() {
4455
+ const settingsMenu = this.container?.querySelector('.settings-menu');
4456
+ if (!settingsMenu) return;
4457
+
4458
+ if (this.options.showSubtitles) {
4459
+ const subtitlesTrigger = settingsMenu.querySelector('[data-action="subtitles_expand"]');
4460
+
4461
+ if (subtitlesTrigger) {
4462
+ // 1. Update the label text (e.g. "Subtitles: English")
4463
+ const label = subtitlesTrigger.querySelector('.settings-option-label');
4464
+ if (label) {
4465
+ const subtitlesLabel = this.t('subtitles') || 'Subtitles';
4466
+ const currentTrack = this.currentSubtitleTrack;
4467
+
4468
+ let currentLabelText = this.t('subtitlesoff') || 'Off';
4469
+ if (this.subtitlesEnabled && currentTrack) {
4470
+ currentLabelText = currentTrack.label || 'Unknown';
4471
+ }
4472
+
4473
+ label.innerHTML = `${subtitlesLabel} <strong>${currentLabelText}</strong>`;
4474
+ }
4475
+
4476
+ // 2. Update the "active" classes in the dropdown list
4477
+ const submenuContent = subtitlesTrigger.nextElementSibling;
4478
+ if (submenuContent && submenuContent.classList.contains('settings-expandable-content')) {
4479
+ submenuContent.querySelectorAll('.settings-suboption').forEach(opt => {
4480
+ const trackData = opt.getAttribute('data-track');
4481
+ opt.classList.remove('active');
4482
+
4483
+ if (trackData === 'off' && !this.subtitlesEnabled) {
4484
+ opt.classList.add('active');
4485
+ } else if (this.subtitlesEnabled && trackData !== 'off') {
4486
+ const trackIndex = parseInt(trackData);
4487
+ if (this.textTracks[trackIndex] && this.textTracks[trackIndex].track === this.currentSubtitleTrack) {
4488
+ opt.classList.add('active');
4489
+ }
4490
+ }
4491
+ });
4492
+ }
4493
+ }
4494
+ }
4495
+ }
4496
+
4402
4497
  /**
4403
4498
  * Add scrollbar to settings menu on mobile
4404
4499
  */
@@ -6080,6 +6175,33 @@ createCustomSubtitleOverlay() {
6080
6175
  if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
6081
6176
  }
6082
6177
 
6178
+ // Universal time parser for both SRT (00:00:00,000) and VTT (00:00.000)
6179
+ parseTimeToSecondsUniversal(timeString) {
6180
+ if (!timeString) return 0;
6181
+
6182
+ // Normalize comma to dot for consistency
6183
+ let normalized = timeString.replace(',', '.');
6184
+ let parts = normalized.split('.');
6185
+
6186
+ let timeParts = parts[0].split(':');
6187
+ let ms = parts[1] ? parseInt(parts[1], 10) / 1000 : 0;
6188
+
6189
+ let hours = 0, minutes = 0, seconds = 0;
6190
+
6191
+ if (timeParts.length === 3) {
6192
+ // SRT Format: HH:MM:SS
6193
+ hours = parseInt(timeParts[0], 10);
6194
+ minutes = parseInt(timeParts[1], 10);
6195
+ seconds = parseInt(timeParts[2], 10);
6196
+ } else if (timeParts.length === 2) {
6197
+ // VTT Short Format: MM:SS
6198
+ minutes = parseInt(timeParts[0], 10);
6199
+ seconds = parseInt(timeParts[1], 10);
6200
+ }
6201
+
6202
+ return (hours * 3600) + (minutes * 60) + seconds + ms;
6203
+ }
6204
+
6083
6205
  customTimeToSeconds(timeString) {
6084
6206
  if (!timeString) return 0;
6085
6207
 
@@ -6105,6 +6227,33 @@ customTimeToSeconds(timeString) {
6105
6227
  return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
6106
6228
  }
6107
6229
 
6230
+ // Universal time parser for both SRT (00:00:00,000) and VTT (00:00.000)
6231
+ parseTimeToSecondsUniversal(timeString) {
6232
+ if (!timeString) return 0;
6233
+
6234
+ // Normalize comma to dot for consistency
6235
+ const normalized = timeString.replace(',', '.');
6236
+ const parts = normalized.split('.');
6237
+
6238
+ const timeParts = parts[0].split(':');
6239
+ const ms = parts[1] ? parseInt(parts[1], 10) / 1000 : 0;
6240
+
6241
+ let hours = 0, minutes = 0, seconds = 0;
6242
+
6243
+ if (timeParts.length === 3) {
6244
+ // Full format: HH:MM:SS
6245
+ hours = parseInt(timeParts[0], 10);
6246
+ minutes = parseInt(timeParts[1], 10);
6247
+ seconds = parseInt(timeParts[2], 10);
6248
+ } else if (timeParts.length === 2) {
6249
+ // Short format (common in VTT): MM:SS
6250
+ minutes = parseInt(timeParts[0], 10);
6251
+ seconds = parseInt(timeParts[1], 10);
6252
+ }
6253
+
6254
+ return (hours * 3600) + (minutes * 60) + seconds + ms;
6255
+ }
6256
+
6108
6257
  parseCustomSRT(srtText) {
6109
6258
  var subtitles = [];
6110
6259
  var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
@@ -6138,6 +6287,63 @@ parseCustomSRT(srtText) {
6138
6287
  return subtitles;
6139
6288
  }
6140
6289
 
6290
+ // Method to inject multiple external subtitle tracks
6291
+ injectExternalSubtitleTracks(tracksArray) {
6292
+ // Check if video exists and if tracksArray is actually an array
6293
+ if (!this.video || !Array.isArray(tracksArray) || tracksArray.length === 0) return;
6294
+
6295
+ this.video.crossOrigin = "anonymous";
6296
+ let loadedCount = 0;
6297
+
6298
+ // Loop through each track object in the array
6299
+ tracksArray.forEach(trackData => {
6300
+ const trackEl = document.createElement('track');
6301
+ trackEl.kind = 'subtitles';
6302
+ trackEl.label = trackData.label;
6303
+ trackEl.srclang = trackData.lang;
6304
+ trackEl.src = trackData.src;
6305
+
6306
+ // Set default if specified in the JSON
6307
+ if (trackData.default) {
6308
+ trackEl.default = true;
6309
+ trackEl.setAttribute('default', 'default');
6310
+ trackEl.setAttribute('data-default', 'true');
6311
+ }
6312
+
6313
+ // Listen for the load event on EACH track
6314
+ trackEl.addEventListener('load', () => {
6315
+ if (trackEl.track) {
6316
+ // Only show the default one, keep others hidden but ready
6317
+ trackEl.track.mode = trackData.default ? 'showing' : 'hidden';
6318
+ }
6319
+
6320
+ loadedCount++;
6321
+
6322
+ // Re-initialize UI ONLY when ALL tracks have finished loading
6323
+ if (loadedCount === tracksArray.length) {
6324
+ console.log(`MYETV Player: ${loadedCount} external subtitle tracks loaded.`);
6325
+
6326
+ if (typeof this.initializeSubtitles === 'function') {
6327
+ this.initializeSubtitles();
6328
+ } else if (typeof this.initializeCustomSubtitles === 'function') {
6329
+ this.initializeCustomSubtitles();
6330
+ } else if (typeof this.loadCustomSubtitleTracks === 'function') {
6331
+ this.loadCustomSubtitleTracks();
6332
+ }
6333
+ }
6334
+ });
6335
+
6336
+ trackEl.addEventListener('error', () => {
6337
+ console.warn(`MYETV Player: Failed to load track ${trackData.lang}`);
6338
+ // Increment anyway so we don't block the UI initialization
6339
+ loadedCount++;
6340
+ });
6341
+
6342
+ // Append to DOM
6343
+ this.video.appendChild(trackEl);
6344
+ });
6345
+ }
6346
+
6141
6347
  loadCustomSubtitleTracks() {
6142
6348
  var self = this;
6143
6349
  var tracks = this.video.querySelectorAll('track[kind="subtitles"]');
@@ -6148,7 +6354,8 @@ loadCustomSubtitleTracks() {
6148
6354
  var label = track.getAttribute('label') || 'Unknown';
6149
6355
  var srclang = track.getAttribute('srclang') || '';
6150
6356
 
6151
- // CREA L'OGGETTO PRIMA E AGGIUNGILO SUBITO
6357
+ var isDefault = track.default || track.hasAttribute('default');
6358
+
6152
6359
  var trackObj = {
6153
6360
  label: label,
6154
6361
  language: srclang,
@@ -6162,43 +6369,83 @@ loadCustomSubtitleTracks() {
6162
6369
  return response.text();
6163
6370
  })
6164
6371
  .then(function (srtText) {
6372
+ // Normalize newlines
6165
6373
  var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
6374
+
6375
+ // Split into blocks by double newline
6166
6376
  var blocks = normalizedText.trim().split('\n\n');
6167
6377
 
6168
6378
  for (var i = 0; i < blocks.length; i++) {
6169
6379
  var block = blocks[i].trim();
6170
- if (!block) continue;
6380
+ if (!block || block.toUpperCase().startsWith('WEBVTT')) continue;
6381
+
6171
6382
  var lines = block.split('\n');
6383
+ var timeMatch = null;
6384
+ var textLines = [];
6385
+
6386
+ for (var j = 0; j < lines.length; j++) {
6387
+ var line = lines[j].trim();
6388
+
6389
+ // Regex matches both SRT and VTT formats (with or without hours, dot or comma)
6390
+ if (!timeMatch) {
6391
+ var match = line.match(/(\d{2,}:\d{2}:\d{2}[.,]\d{3}|\d{2}:\d{2}[.,]\d{3})\s*-->\s*(\d{2,}:\d{2}:\d{2}[.,]\d{3}|\d{2}:\d{2}[.,]\d{3})/);
6392
+ if (match) {
6393
+ timeMatch = match;
6394
+ continue;
6395
+ }
6396
+ }
6172
6397
 
6173
- if (lines.length >= 3) {
6174
- var timeLine = lines[1].trim();
6175
- var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
6398
+ // Skip numeric IDs common in SRT files
6399
+ if (!timeMatch && /^\d+$/.test(line)) {
6400
+ continue;
6401
+ }
6176
6402
 
6403
+ // Everything after the timestamp is the actual subtitle text
6177
6404
  if (timeMatch) {
6178
- var startParts = timeMatch[1].split(',');
6179
- var startTimeParts = startParts[0].split(':');
6180
- var startTime = parseInt(startTimeParts[0], 10) * 3600 + parseInt(startTimeParts[1], 10) * 60 + parseInt(startTimeParts[2], 10) + parseInt(startParts[1], 10) / 1000;
6181
-
6182
- var endParts = timeMatch[2].split(',');
6183
- var endTimeParts = endParts[0].split(':');
6184
- var endTime = parseInt(endTimeParts[0], 10) * 3600 + parseInt(endTimeParts[1], 10) * 60 + parseInt(endTimeParts[2], 10) + parseInt(endParts[1], 10) / 1000;
6185
-
6186
- var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
6187
-
6188
- if (text && text.length > 0 && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
6189
- trackObj.subtitles.push({
6190
- start: startTime,
6191
- end: endTime,
6192
- text: text
6193
- });
6194
- }
6405
+ textLines.push(line);
6406
+ }
6407
+ }
6408
+
6409
+ // Store the parsed subtitle block
6410
+ if (timeMatch && textLines.length > 0) {
6411
+ var startTime = self.parseTimeToSecondsUniversal(timeMatch[1]);
6412
+ var endTime = self.parseTimeToSecondsUniversal(timeMatch[2]);
6413
+ var text = textLines.join('\n').trim().replace(/<[^>]*>/g, '');
6414
+
6415
+ if (text && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
6416
+ trackObj.subtitles.push({
6417
+ start: startTime,
6418
+ end: endTime,
6419
+ text: text
6420
+ });
6195
6421
  }
6196
6422
  }
6197
6423
  }
6198
6424
 
6199
6425
  if (self.options.debug) {
6200
- console.log('✅ Loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
6426
+ console.log('✅ Custom parser loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
6427
+ }
6428
+
6429
+ // =========================================================
6430
+ // AUTO-ENABLE SUBTITLES
6431
+ // =========================================================
6432
+ var isDefault = track.default || track.hasAttribute('default') || track.getAttribute('data-default') === 'true';
6433
+
6434
+ if (isDefault) {
6435
+ if (self.options.debug) {
6436
+ console.log('⏳ Waiting 500ms for UI alignment... track:', label);
6437
+ }
6438
+
6439
+ // Delay execution to ensure the player has finished
6440
+ // building the menu and populating this.textTracks array!
6441
+ setTimeout(function () {
6442
+ if (self.options.debug) {
6443
+ console.log('🎯 Forced auto-enable triggered now!');
6444
+ }
6445
+ self.enableSubtitleTrack(index);
6446
+ }, 500); // 500 milliseconds of delay
6201
6447
  }
6448
+
6202
6449
  })
6203
6450
  .catch(function (error) {
6204
6451
  console.error('❌ Error loading ' + label + ':', error);
@@ -6317,39 +6564,38 @@ detectTextTracks() {
6317
6564
  enableSubtitleTrack(trackIndex) {
6318
6565
  if (trackIndex < 0 || trackIndex >= this.textTracks.length) return;
6319
6566
 
6320
- // Disable all tracks first
6567
+ // 1. Disable all tracks first to prevent overlapping
6321
6568
  this.disableAllTracks();
6322
6569
 
6323
- // Enable ONLY the custom subtitle system (not native browser)
6570
+ // 2. Enable ONLY the custom WebView-safe subtitle system
6324
6571
  var success = this.enableCustomSubtitleTrack(trackIndex);
6325
6572
 
6326
6573
  if (success) {
6327
6574
  this.currentSubtitleTrack = this.textTracks[trackIndex].track;
6328
6575
  this.subtitlesEnabled = true;
6329
6576
 
6330
- // Make sure native tracks stay DISABLED
6577
+ // 3. Kill the native browser track to avoid double rendering or crashes
6331
6578
  if (this.video.textTracks && this.video.textTracks[trackIndex]) {
6332
- this.video.textTracks[trackIndex].mode = 'disabled'; // Keep native disabled
6579
+ this.video.textTracks[trackIndex].mode = 'disabled';
6333
6580
  }
6334
6581
 
6335
6582
  this.updateSubtitlesButton();
6336
6583
  this.populateSubtitlesMenu();
6337
6584
 
6585
+ if (typeof this.updateSettingsMenuUI === 'function') {
6586
+ this.updateSettingsMenuUI();
6587
+ }
6588
+
6338
6589
  if (this.options.debug) {
6339
- console.log('✅ Custom subtitles enabled:', this.textTracks[trackIndex].label);
6590
+ console.log('✅ Custom UI subtitles strictly enforced for track', trackIndex);
6340
6591
  }
6341
6592
 
6342
- // Trigger subtitle change event
6343
6593
  this.triggerEvent('subtitlechange', {
6344
6594
  enabled: true,
6345
6595
  trackIndex: trackIndex,
6346
6596
  trackLabel: this.textTracks[trackIndex].label,
6347
6597
  trackLanguage: this.textTracks[trackIndex].language
6348
6598
  });
6349
- } else {
6350
- if (this.options.debug) {
6351
- console.error('❌ Failed to enable custom subtitles for track', trackIndex);
6352
- }
6353
6599
  }
6354
6600
  }
6355
6601
 
@@ -6363,6 +6609,11 @@ disableSubtitles() {
6363
6609
  this.updateSubtitlesButton();
6364
6610
  this.populateSubtitlesMenu();
6365
6611
 
6612
+ // 👉 L'AGGIUNTA FONDAMENTALE: Sincronizza il menu Settings!
6613
+ if (typeof this.updateSettingsMenuUI === 'function') {
6614
+ this.updateSettingsMenuUI();
6615
+ }
6616
+
6366
6617
  if (this.options.debug) console.log('📝 Subtitles disabled');
6367
6618
 
6368
6619
  this.triggerEvent('subtitlechange', {
@@ -8617,48 +8868,123 @@ setDashQuality(qualityIndex) {
8617
8868
  // CLASS METHODS - Will be placed INSIDE the class
8618
8869
  // ===================================================================
8619
8870
 
8620
- /**
8621
- * Initialize plugin system for player instance
8622
- * This method should be called in the player constructor
8623
- */
8624
- initializePluginSystem() {
8625
- // Plugin instances storage
8626
- this.plugins = {};
8627
-
8628
- // Plugin hooks for lifecycle events
8629
- this.pluginHooks = {
8630
- 'beforeInit': [],
8631
- 'afterInit': [],
8632
- 'beforePlay': [],
8633
- 'afterPlay': [],
8634
- 'beforePause': [],
8635
- 'afterPause': [],
8636
- 'beforeQualityChange': [],
8637
- 'afterQualityChange': [],
8638
- 'beforeDestroy': [],
8639
- 'afterDestroy': []
8640
- };
8871
+ /**
8872
+ * Initialize plugin system for player instance
8873
+ */
8874
+ initializePluginSystem() {
8875
+ this.plugins = {};
8876
+ this.pluginHooks = {
8877
+ 'beforeInit': [], 'afterInit': [], 'beforePlay': [], 'afterPlay': [],
8878
+ 'beforePause': [], 'afterPause': [], 'beforeQualityChange': [],
8879
+ 'afterQualityChange': [], 'beforeDestroy': [], 'afterDestroy': []
8880
+ };
8641
8881
 
8642
- if (this.options.debug) {
8643
- console.log('🔌 Plugin system initialized');
8882
+ if (this.options.debug) console.log('🔌 Plugin system initialized');
8883
+
8884
+ // Load plugins sequentially if present, otherwise finalize player immediately
8885
+ if (this.options.plugins && typeof this.options.plugins === 'object') {
8886
+ this.loadPluginsSequentially(this.options.plugins);
8887
+ } else {
8888
+ this.finalizePlayerSetup();
8889
+ }
8644
8890
  }
8645
8891
 
8646
- // Load plugins specified in options
8647
- if (this.options.plugins && typeof this.options.plugins === 'object') {
8648
- this.loadPlugins(this.options.plugins);
8892
+ /**
8893
+ * Loads plugins one by one based on their 'order' option.
8894
+ * Prevents any plugin from loading until the previous blocking plugin finishes.
8895
+ * @param {Object} pluginsConfig
8896
+ */
8897
+ async loadPluginsSequentially(pluginsConfig) {
8898
+ // 1. Sort plugins by order (default 99 means load last)
8899
+ const pluginList = Object.keys(pluginsConfig).map(pluginName => {
8900
+ return {
8901
+ name: pluginName,
8902
+ config: pluginsConfig[pluginName],
8903
+ order: pluginsConfig[pluginName] && pluginsConfig[pluginName].order !== undefined
8904
+ ? pluginsConfig[pluginName].order : 99
8905
+ };
8906
+ }).sort((a, b) => a.order - b.order);
8907
+
8908
+ // 2. Process each plugin sequentially
8909
+ for (const pluginData of pluginList) {
8910
+ if (this.options.debug) console.log(`🔌 Loading plugin: ${pluginData.name} (order: ${pluginData.order})`);
8911
+
8912
+ // Instantiate the plugin NOW (plugins with higher order do not exist yet)
8913
+ const pluginInstance = this.usePlugin(pluginData.name, pluginData.config);
8914
+
8915
+ // If the plugin has an async barrier, pause the ENTIRE loop
8916
+ if (pluginInstance && typeof pluginInstance.waitForCompletion === 'function') {
8917
+ if (this.options.debug) console.log(`🔌 Pausing queue. Waiting for ${pluginData.name} to complete...`);
8918
+ await pluginInstance.waitForCompletion();
8919
+ if (this.options.debug) console.log(`🔌 ${pluginData.name} completed. Resuming queue.`);
8920
+ }
8649
8921
  }
8922
+
8923
+ // 3. All plugins processed. Now we can safely boot the main HTML5 player
8924
+ this.finalizePlayerSetup();
8650
8925
  }
8651
8926
 
8652
- /**
8653
- * Load multiple plugins from options
8654
- * @param {Object} pluginsConfig - Object with plugin names as keys and options as values
8655
- */
8656
- loadPlugins(pluginsConfig) {
8657
- for (const pluginName in pluginsConfig) {
8658
- if (pluginsConfig.hasOwnProperty(pluginName)) {
8659
- const pluginOptions = pluginsConfig[pluginName];
8660
- this.usePlugin(pluginName, pluginOptions);
8927
+ /**
8928
+ * Boots up the core HTML5 player features after all blocking plugins have finished.
8929
+ * This function ensures that the main player components (like poster, sources, and watermark)
8930
+ * are initialized only after the plugin queue (including the loader) is fully resolved.
8931
+ */
8932
+ finalizePlayerSetup() {
8933
+ if (this.options.debug) {
8934
+ console.log('🚀 Finalizing main player setup...');
8935
+ }
8936
+
8937
+ // Restore the autoplay intent BEFORE initializing the poster.
8938
+ // This ensures initializePoster() knows we are autoplaying and won't flash the image.
8939
+ if (this.savedAutoplayIntent) {
8940
+ this.options.autoplay = true;
8941
+ }
8942
+
8943
+ // Trigger the global 'playerready' event.
8944
+ // Firing it ONLY NOW guarantees that third-party plugins (like YouTube)
8945
+ // safely instantiate their iframes only after the loader has finished its countdown.
8946
+ this.markPlayerReady();
8947
+
8948
+ this.restoreSourcesAsync();
8949
+ this.initializeSubtitles();
8950
+ this.initializeQualityMonitoring();
8951
+ this.initializeResolution();
8952
+ this.initializeChapters();
8953
+
8954
+ // Prevent the final poster flash if a third-party iframe (YouTube/Vimeo/Twitch) is active.
8955
+ const hasExternalIframe = this.options.plugins && (
8956
+ this.options.plugins.youtube ||
8957
+ this.options.plugins.vimeo ||
8958
+ this.options.plugins.twitch
8959
+ );
8960
+
8961
+ if (!hasExternalIframe) {
8962
+ this.initializePoster();
8963
+ }
8964
+
8965
+ this.initializeWatermark();
8966
+
8967
+ // Check if external subtitle tracks were provided and the array is not empty
8968
+ if (this.options.externalSubtitleTracks && this.options.externalSubtitleTracks.length > 0) {
8969
+ this.injectExternalSubtitleTracks(this.options.externalSubtitleTracks);
8970
+ }
8971
+
8972
+ // 🚀 Restore Autoplay ONLY AFTER everything is fully loaded and the queue is unlocked
8973
+ if (this.savedAutoplayIntent) {
8974
+ if (this.options.debug) console.log('🚀 Triggering delayed autoplay...');
8975
+
8976
+ if (this.video) {
8977
+ this.video.setAttribute('autoplay', '');
8978
+ this.video.style.opacity = '1';
8979
+ this.video.style.visibility = '';
8661
8980
  }
8981
+
8982
+ setTimeout(() => {
8983
+ this.play();
8984
+ }, 300);
8985
+ } else if (this.video) {
8986
+ this.video.style.opacity = '1';
8987
+ this.video.style.visibility = '';
8662
8988
  }
8663
8989
  }
8664
8990