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.
package/scss/_menus.scss CHANGED
@@ -381,3 +381,52 @@
381
381
  display: none !important;
382
382
  }
383
383
  }
384
+ // Expandable settings menu styles
385
+ .settings-expandable-wrapper {
386
+ position: relative;
387
+ display: block;
388
+ }
389
+
390
+ .settings-option.expandable-trigger {
391
+ display: flex;
392
+ justify-content: space-between;
393
+ align-items: center;
394
+ cursor: pointer;
395
+ font-size: 10px !important;
396
+
397
+ .settings-option-label {
398
+ font-size: 10px !important;
399
+ }
400
+
401
+ .expand-arrow {
402
+ font-size: 8px;
403
+ transition: transform 0.2s ease;
404
+ margin-left: 8px;
405
+ }
406
+ }
407
+
408
+ .settings-expandable-content {
409
+ padding-left: 15px;
410
+ margin-top: 4px;
411
+
412
+ .settings-suboption {
413
+ padding: 6px 12px;
414
+ cursor: pointer;
415
+ color: white;
416
+ font-size: 10px;
417
+ white-space: normal;
418
+ word-wrap: break-word;
419
+ opacity: 0.8;
420
+ transition: opacity 0.2s;
421
+
422
+ &.active {
423
+ opacity: 1;
424
+ font-weight: bold;
425
+ }
426
+
427
+ &:hover {
428
+ opacity: 1;
429
+ background: rgba(255, 255, 255, 0.1);
430
+ }
431
+ }
432
+ }
package/src/controls.js CHANGED
@@ -600,7 +600,9 @@ updateSettingsMenuVisibility() {
600
600
  }
601
601
  }
602
602
 
603
- /* Populate settings menu with controls */
603
+ /**
604
+ * Populate settings menu with controls
605
+ */
604
606
  populateSettingsMenu() {
605
607
  const settingsMenu = this.controls?.querySelector('.settings-menu');
606
608
  if (!settingsMenu) return;
@@ -615,54 +617,104 @@ populateSettingsMenu() {
615
617
  </div>`;
616
618
  }
617
619
 
618
- // Speed Control submenu
620
+ // Speed Control - expandable
619
621
  if (this.options.showSpeedControl) {
620
622
  const speedLabel = this.t('playback_speed') || 'Playback Speed';
621
623
  const currentSpeed = this.video ? this.video.playbackRate : 1;
622
- menuHTML += `<div class="settings-option" data-action="speed">
623
- <span class="settings-option-label">${speedLabel}</span>
624
- <span class="settings-option-value">${currentSpeed}x</span>
625
- <div class="settings-submenu speed-submenu">
626
- <div class="settings-suboption ${currentSpeed === 0.5 ? 'active' : ''}" data-speed="0.5">0.5x</div>
627
- <div class="settings-suboption ${currentSpeed === 0.75 ? 'active' : ''}" data-speed="0.75">0.75x</div>
628
- <div class="settings-suboption ${currentSpeed === 1 ? 'active' : ''}" data-speed="1">1x</div>
629
- <div class="settings-suboption ${currentSpeed === 1.25 ? 'active' : ''}" data-speed="1.25">1.25x</div>
630
- <div class="settings-suboption ${currentSpeed === 1.5 ? 'active' : ''}" data-speed="1.5">1.5x</div>
631
- <div class="settings-suboption ${currentSpeed === 2 ? 'active' : ''}" data-speed="2">2x</div>
632
- </div>
633
- </div>`;
624
+
625
+ menuHTML += `
626
+ <div class="settings-expandable-wrapper">
627
+ <div class="settings-option expandable-trigger" data-action="speed-expand">
628
+ <span class="settings-option-label">${speedLabel}: ${currentSpeed}x</span>
629
+ <span class="expand-arrow">▼</span>
630
+ </div>
631
+ <div class="settings-expandable-content" style="display: none;">`;
632
+
633
+ const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
634
+ speeds.forEach(speed => {
635
+ const isActive = Math.abs(speed - currentSpeed) < 0.01;
636
+ menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-speed="${speed}">${speed}x</div>`;
637
+ });
638
+
639
+ menuHTML += `</div></div>`;
634
640
  }
635
641
 
636
- // Subtitles submenu
642
+ // Subtitles - expandable
637
643
  if (this.options.showSubtitles && this.textTracks && this.textTracks.length > 0) {
638
644
  const subtitlesLabel = this.t('subtitles') || 'Subtitles';
639
645
  const currentTrack = this.currentSubtitleTrack;
640
- const currentLabel = this.subtitlesEnabled && currentTrack
641
- ? (currentTrack.label || 'Track')
642
- : (this.t('subtitlesoff') || 'Off');
643
-
644
- menuHTML += `<div class="settings-option" data-action="subtitles">
645
- <span class="settings-option-label">${subtitlesLabel}</span>
646
- <span class="settings-option-value">${currentLabel}</span>
647
- <div class="settings-submenu subtitles-submenu">
648
- <div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">
649
- ${this.t('subtitlesoff') || 'Off'}
650
- </div>`;
646
+ const currentLabel = this.subtitlesEnabled && currentTrack ? currentTrack.label : (this.t('subtitlesoff') || 'Off');
647
+
648
+ menuHTML += `
649
+ <div class="settings-expandable-wrapper">
650
+ <div class="settings-option expandable-trigger" data-action="subtitles-expand">
651
+ <span class="settings-option-label">${subtitlesLabel}: ${currentLabel}</span>
652
+ <span class="expand-arrow">▼</span>
653
+ </div>
654
+ <div class="settings-expandable-content" style="display: none;">`;
651
655
 
656
+ // Off option
657
+ menuHTML += `<div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">${this.t('subtitlesoff') || 'Off'}</div>`;
658
+
659
+ // Subtitle tracks
652
660
  this.textTracks.forEach((trackData, index) => {
653
661
  const isActive = this.currentSubtitleTrack === trackData.track;
654
- menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">
655
- ${trackData.label}
656
- </div>`;
662
+ menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">${trackData.label}</div>`;
657
663
  });
658
664
 
659
- menuHTML += '</div></div>';
665
+ menuHTML += `</div></div>`;
660
666
  }
661
667
 
662
668
  settingsMenu.innerHTML = menuHTML;
669
+
670
+ // Add scrollbar if needed
671
+ this.addSettingsMenuScrollbar();
663
672
  }
664
673
 
665
- /* Bind settings menu events */
674
+ /**
675
+ * Add scrollbar to settings menu on mobile
676
+ */
677
+ addSettingsMenuScrollbar() {
678
+ const settingsMenu = this.controls?.querySelector('.settings-menu');
679
+ if (!settingsMenu) return;
680
+
681
+ const playerHeight = this.container.offsetHeight;
682
+ const maxMenuHeight = playerHeight - 100;
683
+
684
+ settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
685
+ settingsMenu.style.overflowY = 'auto';
686
+ settingsMenu.style.overflowX = 'hidden';
687
+
688
+ // Add scrollbar styling
689
+ if (!document.getElementById('player-settings-scrollbar-style')) {
690
+ const scrollbarStyle = document.createElement('style');
691
+ scrollbarStyle.id = 'player-settings-scrollbar-style';
692
+ scrollbarStyle.textContent = `
693
+ .settings-menu::-webkit-scrollbar {
694
+ width: 6px;
695
+ }
696
+ .settings-menu::-webkit-scrollbar-track {
697
+ background: rgba(255,255,255,0.05);
698
+ border-radius: 3px;
699
+ }
700
+ .settings-menu::-webkit-scrollbar-thumb {
701
+ background: rgba(255,255,255,0.3);
702
+ border-radius: 3px;
703
+ }
704
+ .settings-menu::-webkit-scrollbar-thumb:hover {
705
+ background: rgba(255,255,255,0.5);
706
+ }
707
+ `;
708
+ document.head.appendChild(scrollbarStyle);
709
+ }
710
+
711
+ settingsMenu.style.scrollbarWidth = 'thin';
712
+ settingsMenu.style.scrollbarColor = 'rgba(255,255,255,0.3) transparent';
713
+ }
714
+
715
+ /**
716
+ * Bind settings menu events
717
+ */
666
718
  bindSettingsMenuEvents() {
667
719
  const settingsMenu = this.controls?.querySelector('.settings-menu');
668
720
  if (!settingsMenu) return;
@@ -670,7 +722,26 @@ bindSettingsMenuEvents() {
670
722
  settingsMenu.addEventListener('click', (e) => {
671
723
  e.stopPropagation();
672
724
 
673
- // Handle direct actions
725
+ // Handle expandable triggers
726
+ if (e.target.classList.contains('expandable-trigger') || e.target.closest('.expandable-trigger')) {
727
+ const trigger = e.target.classList.contains('expandable-trigger') ? e.target : e.target.closest('.expandable-trigger');
728
+ const wrapper = trigger.closest('.settings-expandable-wrapper');
729
+ const content = wrapper.querySelector('.settings-expandable-content');
730
+ const arrow = trigger.querySelector('.expand-arrow');
731
+
732
+ const isExpanded = content.style.display !== 'none';
733
+
734
+ if (isExpanded) {
735
+ content.style.display = 'none';
736
+ arrow.style.transform = 'rotate(0deg)';
737
+ } else {
738
+ content.style.display = 'block';
739
+ arrow.style.transform = 'rotate(180deg)';
740
+ }
741
+ return;
742
+ }
743
+
744
+ // Handle direct actions (like PiP)
674
745
  if (e.target.classList.contains('settings-option') || e.target.closest('.settings-option')) {
675
746
  const option = e.target.classList.contains('settings-option') ? e.target : e.target.closest('.settings-option');
676
747
  const action = option.getAttribute('data-action');
@@ -683,32 +754,31 @@ bindSettingsMenuEvents() {
683
754
 
684
755
  // Handle submenu actions
685
756
  if (e.target.classList.contains('settings-suboption')) {
686
- const parent = e.target.closest('.settings-option');
687
- const action = parent?.getAttribute('data-action');
757
+ const wrapper = e.target.closest('.settings-expandable-wrapper');
758
+ const trigger = wrapper.querySelector('.expandable-trigger');
759
+ const action = trigger.getAttribute('data-action');
688
760
 
689
- if (action === 'speed') {
761
+ if (action === 'speed-expand') {
690
762
  const speed = parseFloat(e.target.getAttribute('data-speed'));
691
763
  if (speed && speed > 0 && this.video && !this.isChangingQuality) {
692
764
  this.video.playbackRate = speed;
693
765
 
694
766
  // Update active states
695
- parent.querySelectorAll('.settings-suboption').forEach(opt => {
696
- opt.classList.remove('active');
697
- });
767
+ wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
698
768
  e.target.classList.add('active');
699
769
 
700
- // Update value display
701
- const valueEl = parent.querySelector('.settings-option-value');
702
- if (valueEl) valueEl.textContent = speed + 'x';
770
+ // Update trigger text
771
+ const label = trigger.querySelector('.settings-option-label');
772
+ if (label) {
773
+ const speedLabel = this.t('playback_speed') || 'Playback Speed';
774
+ label.textContent = `${speedLabel}: ${speed}x`;
775
+ }
703
776
 
704
777
  // Trigger event
705
778
  this.triggerEvent('speedchange', { speed, previousSpeed: this.video.playbackRate });
706
779
  }
707
- }
708
-
709
- else if (action === 'subtitles') {
780
+ } else if (action === 'subtitles-expand') {
710
781
  const trackData = e.target.getAttribute('data-track');
711
-
712
782
  if (trackData === 'off') {
713
783
  this.disableSubtitles();
714
784
  } else {
@@ -717,14 +787,15 @@ bindSettingsMenuEvents() {
717
787
  }
718
788
 
719
789
  // Update active states
720
- parent.querySelectorAll('.settings-suboption').forEach(opt => {
721
- opt.classList.remove('active');
722
- });
790
+ wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
723
791
  e.target.classList.add('active');
724
792
 
725
- // Update value display
726
- const valueEl = parent.querySelector('.settings-option-value');
727
- if (valueEl) valueEl.textContent = e.target.textContent;
793
+ // Update trigger text
794
+ const label = trigger.querySelector('.settings-option-label');
795
+ if (label) {
796
+ const subtitlesLabel = this.t('subtitles') || 'Subtitles';
797
+ label.textContent = `${subtitlesLabel}: ${e.target.textContent}`;
798
+ }
728
799
  }
729
800
  }
730
801
  });
package/src/subtitles.js CHANGED
@@ -41,10 +41,10 @@ createCustomSubtitleOverlay() {
41
41
  'bottom: 80px;' +
42
42
  'left: 50%;' +
43
43
  'transform: translateX(-50%);' +
44
- 'z-index: 5;' +
44
+ 'z-index: 999;' +
45
45
  'color: white;' +
46
46
  'font-family: Arial, sans-serif;' +
47
- 'font-size: clamp(12px, 4vw, 18px);' + // RESPONSIVE font-size
47
+ 'font-size: clamp(12px, 4vw, 18px);' +
48
48
  'font-weight: bold;' +
49
49
  'text-align: center;' +
50
50
  'text-shadow: 2px 2px 4px rgba(0, 0, 0, 1);' +
@@ -71,52 +71,29 @@ createCustomSubtitleOverlay() {
71
71
  if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
72
72
  }
73
73
 
74
- loadCustomSubtitleTracks() {
75
- var self = this;
76
- var trackElements = document.querySelectorAll('track[kind="subtitles"], track[kind="captions"]');
77
- var loadPromises = [];
78
-
79
- if (this.options.debug) console.log('📥 Loading ' + trackElements.length + ' subtitle files...');
80
-
81
- for (var i = 0; i < trackElements.length; i++) {
82
- var track = trackElements[i];
83
-
84
- (function (trackElement, index) {
85
- var promise = fetch(trackElement.src)
86
- .then(function (response) {
87
- return response.text();
88
- })
89
- .then(function (srtText) {
90
- var subtitles = self.parseCustomSRT(srtText);
91
- self.customSubtitles.push({
92
- label: trackElement.label || 'Track ' + (index + 1),
93
- language: trackElement.srclang || 'unknown',
94
- subtitles: subtitles
95
- });
74
+ customTimeToSeconds(timeString) {
75
+ if (!timeString) return 0;
96
76
 
97
- if (self.options.debug) {
98
- console.log('✅ Loaded: ' + trackElement.label + ' (' + subtitles.length + ' subtitles)');
99
- }
100
- })
101
- .catch(function (error) {
102
- if (self.options.debug) {
103
- console.error('❌ Error loading ' + trackElement.src + ':', error);
104
- }
105
- });
77
+ var parts = timeString.split(',');
78
+ if (parts.length !== 2) return 0;
106
79
 
107
- loadPromises.push(promise);
108
- })(track, i);
109
- }
80
+ var time = parts[0];
81
+ var millis = parts[1];
110
82
 
111
- Promise.all(loadPromises).then(function () {
112
- if (self.options.debug && self.customSubtitles.length > 0) {
113
- console.log('✅ All custom subtitle tracks loaded');
114
- }
83
+ var timeParts = time.split(':');
84
+ if (timeParts.length !== 3) return 0;
115
85
 
116
- if (self.options.debug) {
117
- console.log('📝 Subtitles loaded but NOT auto-enabled - user must activate manually');
118
- }
119
- });
86
+ var hours = parseInt(timeParts[0], 10);
87
+ var minutes = parseInt(timeParts[1], 10);
88
+ var seconds = parseInt(timeParts[2], 10);
89
+ var milliseconds = parseInt(millis, 10);
90
+
91
+ if (isNaN(hours) || isNaN(minutes) || isNaN(seconds) || isNaN(milliseconds)) {
92
+ console.error('❌ customTimeToSeconds failed for:', timeString);
93
+ return 0;
94
+ }
95
+
96
+ return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
120
97
  }
121
98
 
122
99
  parseCustomSRT(srtText) {
@@ -135,9 +112,9 @@ parseCustomSRT(srtText) {
135
112
  if (timeMatch) {
136
113
  var startTime = this.customTimeToSeconds(timeMatch[1]);
137
114
  var endTime = this.customTimeToSeconds(timeMatch[2]);
138
- var text = this.sanitizeSubtitleText(lines.slice(2).join('\n').trim());
115
+ var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
139
116
 
140
- if (text.length > 0 && startTime < endTime) {
117
+ if (text && text.length > 0 && startTime < endTime) {
141
118
  subtitles.push({
142
119
  start: startTime,
143
120
  end: endTime,
@@ -148,20 +125,97 @@ parseCustomSRT(srtText) {
148
125
  }
149
126
  }
150
127
 
128
+ if (this.options.debug) console.log('✅ Parsed ' + subtitles.length + ' subtitles');
151
129
  return subtitles;
152
130
  }
153
131
 
154
- customTimeToSeconds(timeString) {
155
- var parts = timeString.split(',');
156
- var time = parts[0];
157
- var millis = parts[1];
158
- var timeParts = time.split(':');
159
- var hours = parseInt(timeParts[0], 10);
160
- var minutes = parseInt(timeParts[1], 10);
161
- var seconds = parseInt(timeParts[2], 10);
162
- var milliseconds = parseInt(millis, 10);
132
+ loadCustomSubtitleTracks() {
133
+ var self = this;
134
+ var tracks = this.video.querySelectorAll('track[kind="subtitles"]');
135
+ if (tracks.length === 0) return;
136
+
137
+ tracks.forEach(function (track, index) {
138
+ var src = track.getAttribute('src');
139
+ var label = track.getAttribute('label') || 'Unknown';
140
+ var srclang = track.getAttribute('srclang') || '';
141
+
142
+ // CREA L'OGGETTO PRIMA E AGGIUNGILO SUBITO
143
+ var trackObj = {
144
+ label: label,
145
+ language: srclang,
146
+ subtitles: [],
147
+ trackIndex: index
148
+ };
149
+ self.customSubtitles.push(trackObj);
150
+
151
+ fetch(src)
152
+ .then(function (response) {
153
+ return response.text();
154
+ })
155
+ .then(function (srtText) {
156
+ var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
157
+ var blocks = normalizedText.trim().split('\n\n');
158
+
159
+ for (var i = 0; i < blocks.length; i++) {
160
+ var block = blocks[i].trim();
161
+ if (!block) continue;
162
+ var lines = block.split('\n');
163
+
164
+ if (lines.length >= 3) {
165
+ var timeLine = lines[1].trim();
166
+ var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
167
+
168
+ if (timeMatch) {
169
+ var startParts = timeMatch[1].split(',');
170
+ var startTimeParts = startParts[0].split(':');
171
+ var startTime = parseInt(startTimeParts[0], 10) * 3600 + parseInt(startTimeParts[1], 10) * 60 + parseInt(startTimeParts[2], 10) + parseInt(startParts[1], 10) / 1000;
172
+
173
+ var endParts = timeMatch[2].split(',');
174
+ var endTimeParts = endParts[0].split(':');
175
+ var endTime = parseInt(endTimeParts[0], 10) * 3600 + parseInt(endTimeParts[1], 10) * 60 + parseInt(endTimeParts[2], 10) + parseInt(endParts[1], 10) / 1000;
176
+
177
+ var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
178
+
179
+ if (text && text.length > 0 && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
180
+ trackObj.subtitles.push({
181
+ start: startTime,
182
+ end: endTime,
183
+ text: text
184
+ });
185
+ }
186
+ }
187
+ }
188
+ }
163
189
 
164
- return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
190
+ if (self.options.debug) {
191
+ console.log('✅ Loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
192
+ }
193
+ })
194
+ .catch(function (error) {
195
+ console.error('❌ Error loading ' + label + ':', error);
196
+ });
197
+ });
198
+ }
199
+
200
+ sanitizeSubtitleText(text) {
201
+ if (!text) return '';
202
+
203
+ // Remove HTML tags
204
+ var sanitized = text.replace(/<[^>]*>/g, '');
205
+
206
+ // Remove styling tags common in SRT files
207
+ sanitized = sanitized.replace(/{\\.*?}/g, '');
208
+ sanitized = sanitized.replace(/\\N/g, '\n');
209
+
210
+ // Clean up multiple spaces
211
+ sanitized = sanitized.replace(/\s+/g, ' ').trim();
212
+
213
+ // Decode HTML entities if present
214
+ var tempDiv = document.createElement('div');
215
+ tempDiv.innerHTML = sanitized;
216
+ sanitized = tempDiv.textContent || tempDiv.innerText || sanitized;
217
+
218
+ return sanitized;
165
219
  }
166
220
 
167
221
  enableCustomSubtitleTrack(trackIndex) {
@@ -254,28 +308,39 @@ detectTextTracks() {
254
308
  enableSubtitleTrack(trackIndex) {
255
309
  if (trackIndex < 0 || trackIndex >= this.textTracks.length) return;
256
310
 
311
+ // Disable all tracks first
257
312
  this.disableAllTracks();
258
313
 
314
+ // Enable ONLY the custom subtitle system (not native browser)
259
315
  var success = this.enableCustomSubtitleTrack(trackIndex);
260
316
 
261
317
  if (success) {
262
318
  this.currentSubtitleTrack = this.textTracks[trackIndex].track;
263
319
  this.subtitlesEnabled = true;
264
320
 
321
+ // Make sure native tracks stay DISABLED
322
+ if (this.video.textTracks && this.video.textTracks[trackIndex]) {
323
+ this.video.textTracks[trackIndex].mode = 'disabled'; // Keep native disabled
324
+ }
325
+
265
326
  this.updateSubtitlesButton();
266
327
  this.populateSubtitlesMenu();
267
328
 
268
329
  if (this.options.debug) {
269
- console.log('✅ Subtitles enabled: ' + this.textTracks[trackIndex].label);
330
+ console.log('✅ Custom subtitles enabled:', this.textTracks[trackIndex].label);
270
331
  }
271
332
 
272
- // Trigger evento
333
+ // Trigger subtitle change event
273
334
  this.triggerEvent('subtitlechange', {
274
335
  enabled: true,
275
336
  trackIndex: trackIndex,
276
337
  trackLabel: this.textTracks[trackIndex].label,
277
338
  trackLanguage: this.textTracks[trackIndex].language
278
339
  });
340
+ } else {
341
+ if (this.options.debug) {
342
+ console.error('❌ Failed to enable custom subtitles for track', trackIndex);
343
+ }
279
344
  }
280
345
  }
281
346
 
@@ -298,11 +363,15 @@ disableSubtitles() {
298
363
  }
299
364
 
300
365
  disableAllTracks() {
301
- if (this.video.textTracks) {
302
- for (var i = 0; i < this.video.textTracks.length; i++) {
303
- this.video.textTracks[i].mode = 'hidden';
304
- }
366
+ if (!this.video || !this.video.textTracks) return;
367
+
368
+ // Disable all native tracks
369
+ for (var i = 0; i < this.video.textTracks.length; i++) {
370
+ this.video.textTracks[i].mode = 'hidden';
305
371
  }
372
+
373
+ // Also disable custom subtitles
374
+ this.disableCustomSubtitles();
306
375
  }
307
376
 
308
377
  getAvailableSubtitles() {
@@ -383,25 +452,61 @@ updateSubtitlesUI() {
383
452
  bindSubtitleEvents() {
384
453
  var self = this;
385
454
 
455
+ if (this.video.textTracks) {
456
+ this.isChangingSubtitles = false; // flag to prevent loops
457
+
458
+ this.video.textTracks.addEventListener('change', function () {
459
+ // ignore changes initiated by the player itself
460
+ if (self.isChangingSubtitles) {
461
+ return;
462
+ }
463
+
464
+ // only update ui
465
+ self.updateSubtitlesUI();
466
+ });
467
+ }
468
+
469
+ // Add timeupdate listener for custom subtitle display
470
+ this.video.addEventListener('timeupdate', () => {
471
+ if (this.customSubtitlesEnabled) {
472
+ this.updateCustomSubtitleDisplay();
473
+ }
474
+ });
475
+
476
+ // Menu click events
386
477
  var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
387
478
  if (subtitlesMenu) {
388
479
  subtitlesMenu.addEventListener('click', function (e) {
389
- self.handleSubtitlesMenuClick(e);
480
+ var option = e.target.closest('.subtitles-option');
481
+ if (!option) return;
482
+
483
+ self.isChangingSubtitles = true; // active flag
484
+
485
+ var trackIndex = option.getAttribute('data-track');
486
+ if (trackIndex === 'off') {
487
+ self.disableSubtitles();
488
+ } else {
489
+ self.enableSubtitleTrack(parseInt(trackIndex));
490
+ }
491
+
492
+ setTimeout(function () {
493
+ self.isChangingSubtitles = false; // disable flag
494
+ }, 100);
390
495
  });
391
496
  }
392
497
  }
393
498
 
394
-
395
499
  handleSubtitlesMenuClick(e) {
396
- if (!e.target.classList.contains('subtitles-option')) return;
500
+ var option = e.target.closest('.subtitles-option');
501
+ if (!option) return; // This prevents button clicks from toggling
397
502
 
398
- var trackData = e.target.getAttribute('data-track');
503
+ var trackIndex = option.getAttribute('data-track');
399
504
 
400
- if (trackData === 'off') {
505
+ if (trackIndex === 'off') {
401
506
  this.disableSubtitles();
402
507
  } else {
403
- var trackIndex = parseInt(trackData, 10);
404
- this.enableSubtitleTrack(trackIndex);
508
+ // Don't check for 'toggle' - just enable the track
509
+ this.enableSubtitleTrack(parseInt(trackIndex));
405
510
  }
406
511
 
407
512
  this.updateSubtitlesButton();