myetv-player 1.1.2 → 1.1.4

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.
@@ -3360,7 +3360,9 @@ updateSettingsMenuVisibility() {
3360
3360
  }
3361
3361
  }
3362
3362
 
3363
- /* Populate settings menu with controls */
3363
+ /**
3364
+ * Populate settings menu with controls
3365
+ */
3364
3366
  populateSettingsMenu() {
3365
3367
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3366
3368
  if (!settingsMenu) return;
@@ -3375,54 +3377,104 @@ populateSettingsMenu() {
3375
3377
  </div>`;
3376
3378
  }
3377
3379
 
3378
- // Speed Control submenu
3380
+ // Speed Control - expandable
3379
3381
  if (this.options.showSpeedControl) {
3380
3382
  const speedLabel = this.t('playback_speed') || 'Playback Speed';
3381
3383
  const currentSpeed = this.video ? this.video.playbackRate : 1;
3382
- menuHTML += `<div class="settings-option" data-action="speed">
3383
- <span class="settings-option-label">${speedLabel}</span>
3384
- <span class="settings-option-value">${currentSpeed}x</span>
3385
- <div class="settings-submenu speed-submenu">
3386
- <div class="settings-suboption ${currentSpeed === 0.5 ? 'active' : ''}" data-speed="0.5">0.5x</div>
3387
- <div class="settings-suboption ${currentSpeed === 0.75 ? 'active' : ''}" data-speed="0.75">0.75x</div>
3388
- <div class="settings-suboption ${currentSpeed === 1 ? 'active' : ''}" data-speed="1">1x</div>
3389
- <div class="settings-suboption ${currentSpeed === 1.25 ? 'active' : ''}" data-speed="1.25">1.25x</div>
3390
- <div class="settings-suboption ${currentSpeed === 1.5 ? 'active' : ''}" data-speed="1.5">1.5x</div>
3391
- <div class="settings-suboption ${currentSpeed === 2 ? 'active' : ''}" data-speed="2">2x</div>
3392
- </div>
3393
- </div>`;
3384
+
3385
+ menuHTML += `
3386
+ <div class="settings-expandable-wrapper">
3387
+ <div class="settings-option expandable-trigger" data-action="speed-expand">
3388
+ <span class="settings-option-label">${speedLabel}: ${currentSpeed}x</span>
3389
+ <span class="expand-arrow">▼</span>
3390
+ </div>
3391
+ <div class="settings-expandable-content" style="display: none;">`;
3392
+
3393
+ const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
3394
+ speeds.forEach(speed => {
3395
+ const isActive = Math.abs(speed - currentSpeed) < 0.01;
3396
+ menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-speed="${speed}">${speed}x</div>`;
3397
+ });
3398
+
3399
+ menuHTML += `</div></div>`;
3394
3400
  }
3395
3401
 
3396
- // Subtitles submenu
3402
+ // Subtitles - expandable
3397
3403
  if (this.options.showSubtitles && this.textTracks && this.textTracks.length > 0) {
3398
3404
  const subtitlesLabel = this.t('subtitles') || 'Subtitles';
3399
3405
  const currentTrack = this.currentSubtitleTrack;
3400
- const currentLabel = this.subtitlesEnabled && currentTrack
3401
- ? (currentTrack.label || 'Track')
3402
- : (this.t('subtitlesoff') || 'Off');
3403
-
3404
- menuHTML += `<div class="settings-option" data-action="subtitles">
3405
- <span class="settings-option-label">${subtitlesLabel}</span>
3406
- <span class="settings-option-value">${currentLabel}</span>
3407
- <div class="settings-submenu subtitles-submenu">
3408
- <div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">
3409
- ${this.t('subtitlesoff') || 'Off'}
3410
- </div>`;
3406
+ const currentLabel = this.subtitlesEnabled && currentTrack ? currentTrack.label : (this.t('subtitlesoff') || 'Off');
3407
+
3408
+ menuHTML += `
3409
+ <div class="settings-expandable-wrapper">
3410
+ <div class="settings-option expandable-trigger" data-action="subtitles-expand">
3411
+ <span class="settings-option-label">${subtitlesLabel}: ${currentLabel}</span>
3412
+ <span class="expand-arrow">▼</span>
3413
+ </div>
3414
+ <div class="settings-expandable-content" style="display: none;">`;
3415
+
3416
+ // Off option
3417
+ menuHTML += `<div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">${this.t('subtitlesoff') || 'Off'}</div>`;
3411
3418
 
3419
+ // Subtitle tracks
3412
3420
  this.textTracks.forEach((trackData, index) => {
3413
3421
  const isActive = this.currentSubtitleTrack === trackData.track;
3414
- menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">
3415
- ${trackData.label}
3416
- </div>`;
3422
+ menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">${trackData.label}</div>`;
3417
3423
  });
3418
3424
 
3419
- menuHTML += '</div></div>';
3425
+ menuHTML += `</div></div>`;
3420
3426
  }
3421
3427
 
3422
3428
  settingsMenu.innerHTML = menuHTML;
3429
+
3430
+ // Add scrollbar if needed
3431
+ this.addSettingsMenuScrollbar();
3423
3432
  }
3424
3433
 
3425
- /* Bind settings menu events */
3434
+ /**
3435
+ * Add scrollbar to settings menu on mobile
3436
+ */
3437
+ addSettingsMenuScrollbar() {
3438
+ const settingsMenu = this.controls?.querySelector('.settings-menu');
3439
+ if (!settingsMenu) return;
3440
+
3441
+ const playerHeight = this.container.offsetHeight;
3442
+ const maxMenuHeight = playerHeight - 100;
3443
+
3444
+ settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
3445
+ settingsMenu.style.overflowY = 'auto';
3446
+ settingsMenu.style.overflowX = 'hidden';
3447
+
3448
+ // Add scrollbar styling
3449
+ if (!document.getElementById('player-settings-scrollbar-style')) {
3450
+ const scrollbarStyle = document.createElement('style');
3451
+ scrollbarStyle.id = 'player-settings-scrollbar-style';
3452
+ scrollbarStyle.textContent = `
3453
+ .settings-menu::-webkit-scrollbar {
3454
+ width: 6px;
3455
+ }
3456
+ .settings-menu::-webkit-scrollbar-track {
3457
+ background: rgba(255,255,255,0.05);
3458
+ border-radius: 3px;
3459
+ }
3460
+ .settings-menu::-webkit-scrollbar-thumb {
3461
+ background: rgba(255,255,255,0.3);
3462
+ border-radius: 3px;
3463
+ }
3464
+ .settings-menu::-webkit-scrollbar-thumb:hover {
3465
+ background: rgba(255,255,255,0.5);
3466
+ }
3467
+ `;
3468
+ document.head.appendChild(scrollbarStyle);
3469
+ }
3470
+
3471
+ settingsMenu.style.scrollbarWidth = 'thin';
3472
+ settingsMenu.style.scrollbarColor = 'rgba(255,255,255,0.3) transparent';
3473
+ }
3474
+
3475
+ /**
3476
+ * Bind settings menu events
3477
+ */
3426
3478
  bindSettingsMenuEvents() {
3427
3479
  const settingsMenu = this.controls?.querySelector('.settings-menu');
3428
3480
  if (!settingsMenu) return;
@@ -3430,7 +3482,26 @@ bindSettingsMenuEvents() {
3430
3482
  settingsMenu.addEventListener('click', (e) => {
3431
3483
  e.stopPropagation();
3432
3484
 
3433
- // Handle direct actions
3485
+ // Handle expandable triggers
3486
+ if (e.target.classList.contains('expandable-trigger') || e.target.closest('.expandable-trigger')) {
3487
+ const trigger = e.target.classList.contains('expandable-trigger') ? e.target : e.target.closest('.expandable-trigger');
3488
+ const wrapper = trigger.closest('.settings-expandable-wrapper');
3489
+ const content = wrapper.querySelector('.settings-expandable-content');
3490
+ const arrow = trigger.querySelector('.expand-arrow');
3491
+
3492
+ const isExpanded = content.style.display !== 'none';
3493
+
3494
+ if (isExpanded) {
3495
+ content.style.display = 'none';
3496
+ arrow.style.transform = 'rotate(0deg)';
3497
+ } else {
3498
+ content.style.display = 'block';
3499
+ arrow.style.transform = 'rotate(180deg)';
3500
+ }
3501
+ return;
3502
+ }
3503
+
3504
+ // Handle direct actions (like PiP)
3434
3505
  if (e.target.classList.contains('settings-option') || e.target.closest('.settings-option')) {
3435
3506
  const option = e.target.classList.contains('settings-option') ? e.target : e.target.closest('.settings-option');
3436
3507
  const action = option.getAttribute('data-action');
@@ -3443,32 +3514,31 @@ bindSettingsMenuEvents() {
3443
3514
 
3444
3515
  // Handle submenu actions
3445
3516
  if (e.target.classList.contains('settings-suboption')) {
3446
- const parent = e.target.closest('.settings-option');
3447
- const action = parent?.getAttribute('data-action');
3517
+ const wrapper = e.target.closest('.settings-expandable-wrapper');
3518
+ const trigger = wrapper.querySelector('.expandable-trigger');
3519
+ const action = trigger.getAttribute('data-action');
3448
3520
 
3449
- if (action === 'speed') {
3521
+ if (action === 'speed-expand') {
3450
3522
  const speed = parseFloat(e.target.getAttribute('data-speed'));
3451
3523
  if (speed && speed > 0 && this.video && !this.isChangingQuality) {
3452
3524
  this.video.playbackRate = speed;
3453
3525
 
3454
3526
  // Update active states
3455
- parent.querySelectorAll('.settings-suboption').forEach(opt => {
3456
- opt.classList.remove('active');
3457
- });
3527
+ wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
3458
3528
  e.target.classList.add('active');
3459
3529
 
3460
- // Update value display
3461
- const valueEl = parent.querySelector('.settings-option-value');
3462
- if (valueEl) valueEl.textContent = speed + 'x';
3530
+ // Update trigger text
3531
+ const label = trigger.querySelector('.settings-option-label');
3532
+ if (label) {
3533
+ const speedLabel = this.t('playback_speed') || 'Playback Speed';
3534
+ label.textContent = `${speedLabel}: ${speed}x`;
3535
+ }
3463
3536
 
3464
3537
  // Trigger event
3465
3538
  this.triggerEvent('speedchange', { speed, previousSpeed: this.video.playbackRate });
3466
3539
  }
3467
- }
3468
-
3469
- else if (action === 'subtitles') {
3540
+ } else if (action === 'subtitles-expand') {
3470
3541
  const trackData = e.target.getAttribute('data-track');
3471
-
3472
3542
  if (trackData === 'off') {
3473
3543
  this.disableSubtitles();
3474
3544
  } else {
@@ -3477,14 +3547,15 @@ bindSettingsMenuEvents() {
3477
3547
  }
3478
3548
 
3479
3549
  // Update active states
3480
- parent.querySelectorAll('.settings-suboption').forEach(opt => {
3481
- opt.classList.remove('active');
3482
- });
3550
+ wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
3483
3551
  e.target.classList.add('active');
3484
3552
 
3485
- // Update value display
3486
- const valueEl = parent.querySelector('.settings-option-value');
3487
- if (valueEl) valueEl.textContent = e.target.textContent;
3553
+ // Update trigger text
3554
+ const label = trigger.querySelector('.settings-option-label');
3555
+ if (label) {
3556
+ const subtitlesLabel = this.t('subtitles') || 'Subtitles';
3557
+ label.textContent = `${subtitlesLabel}: ${e.target.textContent}`;
3558
+ }
3488
3559
  }
3489
3560
  }
3490
3561
  });
@@ -4827,10 +4898,10 @@ createCustomSubtitleOverlay() {
4827
4898
  'bottom: 80px;' +
4828
4899
  'left: 50%;' +
4829
4900
  'transform: translateX(-50%);' +
4830
- 'z-index: 5;' +
4901
+ 'z-index: 999;' +
4831
4902
  'color: white;' +
4832
4903
  'font-family: Arial, sans-serif;' +
4833
- 'font-size: clamp(12px, 4vw, 18px);' + // RESPONSIVE font-size
4904
+ 'font-size: clamp(12px, 4vw, 18px);' +
4834
4905
  'font-weight: bold;' +
4835
4906
  'text-align: center;' +
4836
4907
  'text-shadow: 2px 2px 4px rgba(0, 0, 0, 1);' +
@@ -4857,52 +4928,29 @@ createCustomSubtitleOverlay() {
4857
4928
  if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
4858
4929
  }
4859
4930
 
4860
- loadCustomSubtitleTracks() {
4861
- var self = this;
4862
- var trackElements = document.querySelectorAll('track[kind="subtitles"], track[kind="captions"]');
4863
- var loadPromises = [];
4864
-
4865
- if (this.options.debug) console.log('📥 Loading ' + trackElements.length + ' subtitle files...');
4866
-
4867
- for (var i = 0; i < trackElements.length; i++) {
4868
- var track = trackElements[i];
4869
-
4870
- (function (trackElement, index) {
4871
- var promise = fetch(trackElement.src)
4872
- .then(function (response) {
4873
- return response.text();
4874
- })
4875
- .then(function (srtText) {
4876
- var subtitles = self.parseCustomSRT(srtText);
4877
- self.customSubtitles.push({
4878
- label: trackElement.label || 'Track ' + (index + 1),
4879
- language: trackElement.srclang || 'unknown',
4880
- subtitles: subtitles
4881
- });
4931
+ customTimeToSeconds(timeString) {
4932
+ if (!timeString) return 0;
4882
4933
 
4883
- if (self.options.debug) {
4884
- console.log('✅ Loaded: ' + trackElement.label + ' (' + subtitles.length + ' subtitles)');
4885
- }
4886
- })
4887
- .catch(function (error) {
4888
- if (self.options.debug) {
4889
- console.error('❌ Error loading ' + trackElement.src + ':', error);
4890
- }
4891
- });
4934
+ var parts = timeString.split(',');
4935
+ if (parts.length !== 2) return 0;
4892
4936
 
4893
- loadPromises.push(promise);
4894
- })(track, i);
4895
- }
4937
+ var time = parts[0];
4938
+ var millis = parts[1];
4896
4939
 
4897
- Promise.all(loadPromises).then(function () {
4898
- if (self.options.debug && self.customSubtitles.length > 0) {
4899
- console.log('✅ All custom subtitle tracks loaded');
4900
- }
4940
+ var timeParts = time.split(':');
4941
+ if (timeParts.length !== 3) return 0;
4901
4942
 
4902
- if (self.options.debug) {
4903
- console.log('📝 Subtitles loaded but NOT auto-enabled - user must activate manually');
4904
- }
4905
- });
4943
+ var hours = parseInt(timeParts[0], 10);
4944
+ var minutes = parseInt(timeParts[1], 10);
4945
+ var seconds = parseInt(timeParts[2], 10);
4946
+ var milliseconds = parseInt(millis, 10);
4947
+
4948
+ if (isNaN(hours) || isNaN(minutes) || isNaN(seconds) || isNaN(milliseconds)) {
4949
+ console.error('❌ customTimeToSeconds failed for:', timeString);
4950
+ return 0;
4951
+ }
4952
+
4953
+ return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
4906
4954
  }
4907
4955
 
4908
4956
  parseCustomSRT(srtText) {
@@ -4921,9 +4969,9 @@ parseCustomSRT(srtText) {
4921
4969
  if (timeMatch) {
4922
4970
  var startTime = this.customTimeToSeconds(timeMatch[1]);
4923
4971
  var endTime = this.customTimeToSeconds(timeMatch[2]);
4924
- var text = this.sanitizeSubtitleText(lines.slice(2).join('\n').trim());
4972
+ var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
4925
4973
 
4926
- if (text.length > 0 && startTime < endTime) {
4974
+ if (text && text.length > 0 && startTime < endTime) {
4927
4975
  subtitles.push({
4928
4976
  start: startTime,
4929
4977
  end: endTime,
@@ -4934,20 +4982,97 @@ parseCustomSRT(srtText) {
4934
4982
  }
4935
4983
  }
4936
4984
 
4985
+ if (this.options.debug) console.log('✅ Parsed ' + subtitles.length + ' subtitles');
4937
4986
  return subtitles;
4938
4987
  }
4939
4988
 
4940
- customTimeToSeconds(timeString) {
4941
- var parts = timeString.split(',');
4942
- var time = parts[0];
4943
- var millis = parts[1];
4944
- var timeParts = time.split(':');
4945
- var hours = parseInt(timeParts[0], 10);
4946
- var minutes = parseInt(timeParts[1], 10);
4947
- var seconds = parseInt(timeParts[2], 10);
4948
- var milliseconds = parseInt(millis, 10);
4989
+ loadCustomSubtitleTracks() {
4990
+ var self = this;
4991
+ var tracks = this.video.querySelectorAll('track[kind="subtitles"]');
4992
+ if (tracks.length === 0) return;
4993
+
4994
+ tracks.forEach(function (track, index) {
4995
+ var src = track.getAttribute('src');
4996
+ var label = track.getAttribute('label') || 'Unknown';
4997
+ var srclang = track.getAttribute('srclang') || '';
4998
+
4999
+ // CREA L'OGGETTO PRIMA E AGGIUNGILO SUBITO
5000
+ var trackObj = {
5001
+ label: label,
5002
+ language: srclang,
5003
+ subtitles: [],
5004
+ trackIndex: index
5005
+ };
5006
+ self.customSubtitles.push(trackObj);
5007
+
5008
+ fetch(src)
5009
+ .then(function (response) {
5010
+ return response.text();
5011
+ })
5012
+ .then(function (srtText) {
5013
+ var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
5014
+ var blocks = normalizedText.trim().split('\n\n');
5015
+
5016
+ for (var i = 0; i < blocks.length; i++) {
5017
+ var block = blocks[i].trim();
5018
+ if (!block) continue;
5019
+ var lines = block.split('\n');
5020
+
5021
+ if (lines.length >= 3) {
5022
+ var timeLine = lines[1].trim();
5023
+ var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
5024
+
5025
+ if (timeMatch) {
5026
+ var startParts = timeMatch[1].split(',');
5027
+ var startTimeParts = startParts[0].split(':');
5028
+ var startTime = parseInt(startTimeParts[0], 10) * 3600 + parseInt(startTimeParts[1], 10) * 60 + parseInt(startTimeParts[2], 10) + parseInt(startParts[1], 10) / 1000;
5029
+
5030
+ var endParts = timeMatch[2].split(',');
5031
+ var endTimeParts = endParts[0].split(':');
5032
+ var endTime = parseInt(endTimeParts[0], 10) * 3600 + parseInt(endTimeParts[1], 10) * 60 + parseInt(endTimeParts[2], 10) + parseInt(endParts[1], 10) / 1000;
5033
+
5034
+ var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
5035
+
5036
+ if (text && text.length > 0 && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
5037
+ trackObj.subtitles.push({
5038
+ start: startTime,
5039
+ end: endTime,
5040
+ text: text
5041
+ });
5042
+ }
5043
+ }
5044
+ }
5045
+ }
4949
5046
 
4950
- return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
5047
+ if (self.options.debug) {
5048
+ console.log('✅ Loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
5049
+ }
5050
+ })
5051
+ .catch(function (error) {
5052
+ console.error('❌ Error loading ' + label + ':', error);
5053
+ });
5054
+ });
5055
+ }
5056
+
5057
+ sanitizeSubtitleText(text) {
5058
+ if (!text) return '';
5059
+
5060
+ // Remove HTML tags
5061
+ var sanitized = text.replace(/<[^>]*>/g, '');
5062
+
5063
+ // Remove styling tags common in SRT files
5064
+ sanitized = sanitized.replace(/{\\.*?}/g, '');
5065
+ sanitized = sanitized.replace(/\\N/g, '\n');
5066
+
5067
+ // Clean up multiple spaces
5068
+ sanitized = sanitized.replace(/\s+/g, ' ').trim();
5069
+
5070
+ // Decode HTML entities if present
5071
+ var tempDiv = document.createElement('div');
5072
+ tempDiv.innerHTML = sanitized;
5073
+ sanitized = tempDiv.textContent || tempDiv.innerText || sanitized;
5074
+
5075
+ return sanitized;
4951
5076
  }
4952
5077
 
4953
5078
  enableCustomSubtitleTrack(trackIndex) {
@@ -5040,28 +5165,39 @@ detectTextTracks() {
5040
5165
  enableSubtitleTrack(trackIndex) {
5041
5166
  if (trackIndex < 0 || trackIndex >= this.textTracks.length) return;
5042
5167
 
5168
+ // Disable all tracks first
5043
5169
  this.disableAllTracks();
5044
5170
 
5171
+ // Enable ONLY the custom subtitle system (not native browser)
5045
5172
  var success = this.enableCustomSubtitleTrack(trackIndex);
5046
5173
 
5047
5174
  if (success) {
5048
5175
  this.currentSubtitleTrack = this.textTracks[trackIndex].track;
5049
5176
  this.subtitlesEnabled = true;
5050
5177
 
5178
+ // Make sure native tracks stay DISABLED
5179
+ if (this.video.textTracks && this.video.textTracks[trackIndex]) {
5180
+ this.video.textTracks[trackIndex].mode = 'disabled'; // Keep native disabled
5181
+ }
5182
+
5051
5183
  this.updateSubtitlesButton();
5052
5184
  this.populateSubtitlesMenu();
5053
5185
 
5054
5186
  if (this.options.debug) {
5055
- console.log('✅ Subtitles enabled: ' + this.textTracks[trackIndex].label);
5187
+ console.log('✅ Custom subtitles enabled:', this.textTracks[trackIndex].label);
5056
5188
  }
5057
5189
 
5058
- // Trigger evento
5190
+ // Trigger subtitle change event
5059
5191
  this.triggerEvent('subtitlechange', {
5060
5192
  enabled: true,
5061
5193
  trackIndex: trackIndex,
5062
5194
  trackLabel: this.textTracks[trackIndex].label,
5063
5195
  trackLanguage: this.textTracks[trackIndex].language
5064
5196
  });
5197
+ } else {
5198
+ if (this.options.debug) {
5199
+ console.error('❌ Failed to enable custom subtitles for track', trackIndex);
5200
+ }
5065
5201
  }
5066
5202
  }
5067
5203
 
@@ -5084,11 +5220,15 @@ disableSubtitles() {
5084
5220
  }
5085
5221
 
5086
5222
  disableAllTracks() {
5087
- if (this.video.textTracks) {
5088
- for (var i = 0; i < this.video.textTracks.length; i++) {
5089
- this.video.textTracks[i].mode = 'hidden';
5090
- }
5223
+ if (!this.video || !this.video.textTracks) return;
5224
+
5225
+ // Disable all native tracks
5226
+ for (var i = 0; i < this.video.textTracks.length; i++) {
5227
+ this.video.textTracks[i].mode = 'hidden';
5091
5228
  }
5229
+
5230
+ // Also disable custom subtitles
5231
+ this.disableCustomSubtitles();
5092
5232
  }
5093
5233
 
5094
5234
  getAvailableSubtitles() {
@@ -5169,25 +5309,61 @@ updateSubtitlesUI() {
5169
5309
  bindSubtitleEvents() {
5170
5310
  var self = this;
5171
5311
 
5312
+ if (this.video.textTracks) {
5313
+ this.isChangingSubtitles = false; // flag to prevent loops
5314
+
5315
+ this.video.textTracks.addEventListener('change', function () {
5316
+ // ignore changes initiated by the player itself
5317
+ if (self.isChangingSubtitles) {
5318
+ return;
5319
+ }
5320
+
5321
+ // only update ui
5322
+ self.updateSubtitlesUI();
5323
+ });
5324
+ }
5325
+
5326
+ // Add timeupdate listener for custom subtitle display
5327
+ this.video.addEventListener('timeupdate', () => {
5328
+ if (this.customSubtitlesEnabled) {
5329
+ this.updateCustomSubtitleDisplay();
5330
+ }
5331
+ });
5332
+
5333
+ // Menu click events
5172
5334
  var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
5173
5335
  if (subtitlesMenu) {
5174
5336
  subtitlesMenu.addEventListener('click', function (e) {
5175
- self.handleSubtitlesMenuClick(e);
5337
+ var option = e.target.closest('.subtitles-option');
5338
+ if (!option) return;
5339
+
5340
+ self.isChangingSubtitles = true; // active flag
5341
+
5342
+ var trackIndex = option.getAttribute('data-track');
5343
+ if (trackIndex === 'off') {
5344
+ self.disableSubtitles();
5345
+ } else {
5346
+ self.enableSubtitleTrack(parseInt(trackIndex));
5347
+ }
5348
+
5349
+ setTimeout(function () {
5350
+ self.isChangingSubtitles = false; // disable flag
5351
+ }, 100);
5176
5352
  });
5177
5353
  }
5178
5354
  }
5179
5355
 
5180
-
5181
5356
  handleSubtitlesMenuClick(e) {
5182
- if (!e.target.classList.contains('subtitles-option')) return;
5357
+ var option = e.target.closest('.subtitles-option');
5358
+ if (!option) return; // This prevents button clicks from toggling
5183
5359
 
5184
- var trackData = e.target.getAttribute('data-track');
5360
+ var trackIndex = option.getAttribute('data-track');
5185
5361
 
5186
- if (trackData === 'off') {
5362
+ if (trackIndex === 'off') {
5187
5363
  this.disableSubtitles();
5188
5364
  } else {
5189
- var trackIndex = parseInt(trackData, 10);
5190
- this.enableSubtitleTrack(trackIndex);
5365
+ // Don't check for 'toggle' - just enable the track
5366
+ this.enableSubtitleTrack(parseInt(trackIndex));
5191
5367
  }
5192
5368
 
5193
5369
  this.updateSubtitlesButton();