myetv-player 1.1.1 → 1.1.3
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/build.js +9 -3
- package/css/myetv-player.css +726 -11937
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +251 -131
- package/dist/myetv-player.min.js +5362 -5720
- package/package.json +3 -1
- package/plugins/youtube/myetv-player-youtube-plugin.js +54 -6
- package/scss/_controls.scss +120 -317
- package/scss/_menus.scss +117 -4023
- package/scss/_progress-bar.scss +168 -2052
- package/scss/_title-overlay.scss +10 -2239
- package/scss/_video.scss +62 -2167
- package/scss/_volume.scss +25 -1846
- package/src/controls.js +7 -7
- package/src/core.js +66 -49
- package/src/events.js +5 -0
- package/src/subtitles.js +173 -75
package/src/controls.js
CHANGED
|
@@ -368,13 +368,13 @@ createControls() {
|
|
|
368
368
|
const controlsHTML = `
|
|
369
369
|
<div class="controls" id="${controlsId}">
|
|
370
370
|
<div class="progress-container">
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
371
|
+
<div class="progress-bar">
|
|
372
|
+
<div class="progress-buffer"></div>
|
|
373
|
+
<div class="progress-filled"></div>
|
|
374
|
+
</div>
|
|
375
|
+
<div class="progress-handle progress-handle-${this.options.seekHandleShape}"></div> <!-- ✅ Fuori da progress-bar -->
|
|
376
|
+
${this.options.showSeekTooltip ? '<div class="seek-tooltip">0:00</div>' : ''}
|
|
377
|
+
</div>
|
|
378
378
|
|
|
379
379
|
<div class="controls-main">
|
|
380
380
|
<div class="controls-left">
|
package/src/core.js
CHANGED
|
@@ -70,7 +70,6 @@ constructor(videoElement, options = {}) {
|
|
|
70
70
|
this.currentQualityIndex = 0;
|
|
71
71
|
this.qualities = [];
|
|
72
72
|
this.originalSources = [];
|
|
73
|
-
this.setupMenuToggles(); // Initialize menu toggle system
|
|
74
73
|
this.isPiPSupported = this.checkPiPSupport();
|
|
75
74
|
this.seekTooltip = null;
|
|
76
75
|
this.titleOverlay = null;
|
|
@@ -182,6 +181,7 @@ constructor(videoElement, options = {}) {
|
|
|
182
181
|
this.interceptAutoLoading();
|
|
183
182
|
this.createPlayerStructure();
|
|
184
183
|
this.initializeElements();
|
|
184
|
+
this.setupMenuToggles(); // Initialize menu toggle system
|
|
185
185
|
// audio player adaptation
|
|
186
186
|
this.adaptToAudioFile = function () {
|
|
187
187
|
if (this.options.audiofile) {
|
|
@@ -798,74 +798,84 @@ initializeElements() {
|
|
|
798
798
|
this.speedMenu = this.controls?.querySelector('.speed-menu');
|
|
799
799
|
this.qualityMenu = this.controls?.querySelector('.quality-menu');
|
|
800
800
|
this.subtitlesMenu = this.controls?.querySelector('.subtitles-menu');
|
|
801
|
+
// Apply seek handle shape from options
|
|
802
|
+
if (this.progressHandle && this.options.seekHandleShape) {
|
|
803
|
+
this.setSeekHandleShape(this.options.seekHandleShape);
|
|
804
|
+
}
|
|
801
805
|
}
|
|
802
806
|
|
|
803
807
|
// Generic method to close all active menus (works with plugins too)
|
|
804
808
|
closeAllMenus() {
|
|
805
|
-
|
|
806
|
-
const allMenus = this.controls?.querySelectorAll('[class*="-menu"].active');
|
|
807
|
-
allMenus?.forEach(menu => {
|
|
808
|
-
menu.classList.remove('active');
|
|
809
|
-
});
|
|
809
|
+
if (!this.controls) return;
|
|
810
810
|
|
|
811
|
-
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
811
|
+
const menus = this.controls.querySelectorAll('.speed-menu, .quality-menu, .subtitles-menu, .settings-menu');
|
|
812
|
+
const buttons = this.controls.querySelectorAll('.control-btn');
|
|
813
|
+
|
|
814
|
+
menus.forEach(menu => menu.classList.remove('active'));
|
|
815
|
+
buttons.forEach(btn => btn.classList.remove('active'));
|
|
816
|
+
|
|
817
|
+
this.currentOpenMenu = null;
|
|
818
|
+
|
|
819
|
+
if (this.options.debug) {
|
|
820
|
+
console.log('All menus closed');
|
|
821
|
+
}
|
|
816
822
|
}
|
|
817
823
|
|
|
818
824
|
// Generic menu toggle setup (works with core menus and plugin menus)
|
|
819
825
|
setupMenuToggles() {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const menu = this.controls.querySelector('.' + menuName);
|
|
837
|
-
if (menu) {
|
|
838
|
-
menuClass = menuName;
|
|
839
|
-
break;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
826
|
+
if (!this.controls) return;
|
|
827
|
+
|
|
828
|
+
this.currentOpenMenu = null;
|
|
829
|
+
|
|
830
|
+
this.controls.addEventListener('click', (e) => {
|
|
831
|
+
const button = e.target.closest('.control-btn');
|
|
832
|
+
if (!button) return;
|
|
833
|
+
|
|
834
|
+
const buttonClasses = Array.from(button.classList);
|
|
835
|
+
let menuElement = null;
|
|
836
|
+
|
|
837
|
+
for (const cls of buttonClasses) {
|
|
838
|
+
if (cls.endsWith('-btn')) {
|
|
839
|
+
const menuClass = cls.replace('-btn', '-menu');
|
|
840
|
+
menuElement = this.controls.querySelector(`.${menuClass}`);
|
|
841
|
+
if (menuElement) break;
|
|
842
842
|
}
|
|
843
|
+
}
|
|
843
844
|
|
|
844
|
-
|
|
845
|
+
if (!menuElement) return;
|
|
845
846
|
|
|
846
|
-
|
|
847
|
+
e.stopPropagation();
|
|
848
|
+
e.preventDefault();
|
|
847
849
|
|
|
848
|
-
|
|
849
|
-
const menu = this.controls.querySelector('.' + menuClass);
|
|
850
|
-
const isOpen = menu.classList.contains('active');
|
|
850
|
+
const isOpen = menuElement.classList.contains('active');
|
|
851
851
|
|
|
852
|
-
|
|
853
|
-
this.closeAllMenus();
|
|
852
|
+
this.closeAllMenus();
|
|
854
853
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
854
|
+
if (!isOpen) {
|
|
855
|
+
menuElement.classList.add('active');
|
|
856
|
+
button.classList.add('active');
|
|
857
|
+
this.currentOpenMenu = menuElement;
|
|
858
|
+
if (this.options.debug) {
|
|
859
|
+
console.log('Menu opened:', menuElement.className);
|
|
859
860
|
}
|
|
860
|
-
}
|
|
861
|
-
|
|
861
|
+
} else {
|
|
862
|
+
this.currentOpenMenu = null;
|
|
863
|
+
if (this.options.debug) {
|
|
864
|
+
console.log('Menu closed:', menuElement.className);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
});
|
|
862
868
|
|
|
863
|
-
// Close menus when clicking outside controls
|
|
864
869
|
document.addEventListener('click', (e) => {
|
|
865
|
-
if (!this.controls
|
|
870
|
+
if (!this.controls) return;
|
|
871
|
+
if (!this.controls.contains(e.target)) {
|
|
866
872
|
this.closeAllMenus();
|
|
867
873
|
}
|
|
868
874
|
});
|
|
875
|
+
|
|
876
|
+
if (this.options.debug) {
|
|
877
|
+
console.log('✅ Menu toggle system initialized (click-based, auto-close)');
|
|
878
|
+
}
|
|
869
879
|
}
|
|
870
880
|
|
|
871
881
|
updateVolumeSliderVisual() {
|
|
@@ -1176,9 +1186,11 @@ updateBuffer() {
|
|
|
1176
1186
|
}
|
|
1177
1187
|
|
|
1178
1188
|
startSeeking(e) {
|
|
1189
|
+
if (e.cancelable) e.preventDefault();
|
|
1179
1190
|
if (this.isChangingQuality) return;
|
|
1180
1191
|
|
|
1181
1192
|
this.isUserSeeking = true;
|
|
1193
|
+
this.progressContainer.classList.add('seeking');
|
|
1182
1194
|
this.seek(e);
|
|
1183
1195
|
e.preventDefault();
|
|
1184
1196
|
|
|
@@ -1190,6 +1202,7 @@ startSeeking(e) {
|
|
|
1190
1202
|
}
|
|
1191
1203
|
|
|
1192
1204
|
continueSeeking(e) {
|
|
1205
|
+
if (e.cancelable) e.preventDefault();
|
|
1193
1206
|
if (this.isUserSeeking && !this.isChangingQuality) {
|
|
1194
1207
|
this.seek(e);
|
|
1195
1208
|
}
|
|
@@ -1197,9 +1210,13 @@ continueSeeking(e) {
|
|
|
1197
1210
|
|
|
1198
1211
|
endSeeking() {
|
|
1199
1212
|
this.isUserSeeking = false;
|
|
1213
|
+
this.progressContainer.classList.remove('seeking');
|
|
1200
1214
|
}
|
|
1201
1215
|
|
|
1202
1216
|
seek(e) {
|
|
1217
|
+
if (e.cancelable) {
|
|
1218
|
+
e.preventDefault();
|
|
1219
|
+
}
|
|
1203
1220
|
if (!this.video || !this.progressContainer || !this.progressFilled || !this.progressHandle || this.isChangingQuality) return;
|
|
1204
1221
|
|
|
1205
1222
|
const rect = this.progressContainer.getBoundingClientRect();
|
|
@@ -1620,7 +1637,7 @@ bindPosterEvents() {
|
|
|
1620
1637
|
// Hide poster when video is loading/playing
|
|
1621
1638
|
this.video.addEventListener('playing', () => {
|
|
1622
1639
|
this.hidePoster();
|
|
1623
|
-
|
|
1640
|
+
});
|
|
1624
1641
|
|
|
1625
1642
|
// Show poster on load if not autoplay
|
|
1626
1643
|
if (!this.options.autoplay) {
|
package/src/events.js
CHANGED
|
@@ -170,6 +170,7 @@
|
|
|
170
170
|
// Playback events
|
|
171
171
|
this.video.addEventListener('playing', () => {
|
|
172
172
|
this.hideLoading();
|
|
173
|
+
this.closeAllMenus();
|
|
173
174
|
// Trigger playing event - video is now actually playing
|
|
174
175
|
this.triggerEvent('playing', {
|
|
175
176
|
currentTime: this.getCurrentTime(),
|
|
@@ -394,6 +395,10 @@
|
|
|
394
395
|
// Mouse events (desktop)
|
|
395
396
|
this.progressContainer.addEventListener('click', (e) => this.seek(e));
|
|
396
397
|
this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
|
|
398
|
+
if (this.progressHandle) {
|
|
399
|
+
this.progressHandle.addEventListener('mousedown', this.startSeeking.bind(this));
|
|
400
|
+
this.progressHandle.addEventListener('touchstart', this.startSeeking.bind(this), { passive: false });
|
|
401
|
+
}
|
|
397
402
|
|
|
398
403
|
// Touch events (mobile)
|
|
399
404
|
this.progressContainer.addEventListener('touchstart', (e) => {
|
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:
|
|
44
|
+
'z-index: 999;' +
|
|
45
45
|
'color: white;' +
|
|
46
46
|
'font-family: Arial, sans-serif;' +
|
|
47
|
-
'font-size: clamp(12px, 4vw, 18px);' +
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
80
|
+
var time = parts[0];
|
|
81
|
+
var millis = parts[1];
|
|
110
82
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
console.log('✅ All custom subtitle tracks loaded');
|
|
114
|
-
}
|
|
83
|
+
var timeParts = time.split(':');
|
|
84
|
+
if (timeParts.length !== 3) return 0;
|
|
115
85
|
|
|
116
|
-
|
|
117
|
-
|
|
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 =
|
|
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
|
-
|
|
155
|
-
var
|
|
156
|
-
var
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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('✅
|
|
330
|
+
console.log('✅ Custom subtitles enabled:', this.textTracks[trackIndex].label);
|
|
270
331
|
}
|
|
271
332
|
|
|
272
|
-
// Trigger
|
|
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
|
-
|
|
303
|
-
|
|
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() {
|
|
@@ -343,11 +412,11 @@ updateSubtitlesButton() {
|
|
|
343
412
|
var subtitlesBtn = this.controls && this.controls.querySelector('.subtitles-btn');
|
|
344
413
|
if (!subtitlesBtn) return;
|
|
345
414
|
|
|
415
|
+
subtitlesBtn.classList.remove('active');
|
|
416
|
+
|
|
346
417
|
if (this.subtitlesEnabled) {
|
|
347
|
-
subtitlesBtn.classList.add('active');
|
|
348
418
|
subtitlesBtn.title = this.t('subtitlesdisable');
|
|
349
419
|
} else {
|
|
350
|
-
subtitlesBtn.classList.remove('active');
|
|
351
420
|
subtitlesBtn.title = this.t('subtitlesenable');
|
|
352
421
|
}
|
|
353
422
|
}
|
|
@@ -383,32 +452,61 @@ updateSubtitlesUI() {
|
|
|
383
452
|
bindSubtitleEvents() {
|
|
384
453
|
var self = this;
|
|
385
454
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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();
|
|
391
466
|
});
|
|
392
467
|
}
|
|
393
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
|
|
394
477
|
var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
|
|
395
478
|
if (subtitlesMenu) {
|
|
396
479
|
subtitlesMenu.addEventListener('click', function (e) {
|
|
397
|
-
|
|
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);
|
|
398
495
|
});
|
|
399
496
|
}
|
|
400
497
|
}
|
|
401
498
|
|
|
402
499
|
handleSubtitlesMenuClick(e) {
|
|
403
|
-
|
|
500
|
+
var option = e.target.closest('.subtitles-option');
|
|
501
|
+
if (!option) return; // This prevents button clicks from toggling
|
|
404
502
|
|
|
405
|
-
var
|
|
503
|
+
var trackIndex = option.getAttribute('data-track');
|
|
406
504
|
|
|
407
|
-
if (
|
|
505
|
+
if (trackIndex === 'off') {
|
|
408
506
|
this.disableSubtitles();
|
|
409
507
|
} else {
|
|
410
|
-
|
|
411
|
-
this.enableSubtitleTrack(trackIndex);
|
|
508
|
+
// Don't check for 'toggle' - just enable the track
|
|
509
|
+
this.enableSubtitleTrack(parseInt(trackIndex));
|
|
412
510
|
}
|
|
413
511
|
|
|
414
512
|
this.updateSubtitlesButton();
|