myetv-player 1.2.0 → 1.4.0
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/css/myetv-player.css +242 -168
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +638 -203
- package/dist/myetv-player.min.js +548 -170
- package/package.json +35 -16
- package/plugins/twitch/myetv-player-twitch-plugin.js +125 -11
- package/plugins/vimeo/myetv-player-vimeo.js +80 -49
- package/plugins/youtube/README.md +5 -2
- package/plugins/youtube/myetv-player-youtube-plugin.js +766 -6
- package/.github/workflows/codeql.yml +0 -100
- package/.github/workflows/npm-publish.yml +0 -30
- package/SECURITY.md +0 -50
- package/build.js +0 -195
- package/scss/README.md +0 -161
- package/scss/_audio-player.scss +0 -21
- package/scss/_base.scss +0 -116
- package/scss/_controls.scss +0 -204
- package/scss/_loading.scss +0 -111
- package/scss/_menus.scss +0 -432
- package/scss/_mixins.scss +0 -112
- package/scss/_poster.scss +0 -8
- package/scss/_progress-bar.scss +0 -319
- package/scss/_resolution.scss +0 -68
- package/scss/_responsive.scss +0 -1368
- package/scss/_themes.scss +0 -30
- package/scss/_title-overlay.scss +0 -60
- package/scss/_tooltips.scss +0 -7
- package/scss/_variables.scss +0 -49
- package/scss/_video.scss +0 -221
- package/scss/_volume.scss +0 -122
- package/scss/_watermark.scss +0 -128
- package/scss/myetv-player.scss +0 -51
- package/scss/package.json +0 -16
- package/src/README.md +0 -560
- package/src/chapters.js +0 -521
- package/src/controls.js +0 -1242
- package/src/core.js +0 -1922
- package/src/events.js +0 -537
- package/src/fullscreen.js +0 -82
- package/src/i18n.js +0 -374
- package/src/playlist.js +0 -177
- package/src/plugins.js +0 -384
- package/src/quality.js +0 -963
- package/src/streaming.js +0 -346
- package/src/subtitles.js +0 -524
- package/src/utils.js +0 -65
- package/src/watermark.js +0 -246
package/src/subtitles.js
DELETED
|
@@ -1,524 +0,0 @@
|
|
|
1
|
-
/* Subtitles Module for MYETV Video Player
|
|
2
|
-
* Conservative modularization - original code preserved exactly
|
|
3
|
-
* Created by https://www.myetv.tv https://oskarcosimo.com
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
initializeSubtitles() {
|
|
7
|
-
this.detectTextTracks();
|
|
8
|
-
this.updateSubtitlesUI();
|
|
9
|
-
this.bindSubtitleEvents();
|
|
10
|
-
this.initializeCustomSubtitles();
|
|
11
|
-
|
|
12
|
-
if (this.options.debug) console.log('📝 Detected ' + this.textTracks.length + ' subtitles traces');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
initializeCustomSubtitles() {
|
|
16
|
-
// Initialize player variables
|
|
17
|
-
this.customSubtitles = [];
|
|
18
|
-
this.currentCustomSubtitles = [];
|
|
19
|
-
this.customSubtitlesEnabled = false;
|
|
20
|
-
this.customOverlayElement = null;
|
|
21
|
-
this.customUpdateInterval = null;
|
|
22
|
-
this.currentCustomTrackIndex = -1;
|
|
23
|
-
|
|
24
|
-
this.createCustomSubtitleOverlay();
|
|
25
|
-
this.loadCustomSubtitleTracks();
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
createCustomSubtitleOverlay() {
|
|
30
|
-
var existing = document.querySelector('.custom-subtitle-overlay');
|
|
31
|
-
if (existing && existing.parentNode) {
|
|
32
|
-
existing.parentNode.removeChild(existing);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
this.customOverlayElement = document.createElement('div');
|
|
36
|
-
this.customOverlayElement.className = 'custom-subtitle-overlay';
|
|
37
|
-
|
|
38
|
-
// ENHANCED styles with responsive defaults
|
|
39
|
-
this.customOverlayElement.style.cssText =
|
|
40
|
-
'position: absolute;' +
|
|
41
|
-
'bottom: 80px;' +
|
|
42
|
-
'left: 50%;' +
|
|
43
|
-
'transform: translateX(-50%);' +
|
|
44
|
-
'z-index: 999;' +
|
|
45
|
-
'color: white;' +
|
|
46
|
-
'font-family: Arial, sans-serif;' +
|
|
47
|
-
'font-size: clamp(12px, 4vw, 18px);' +
|
|
48
|
-
'font-weight: bold;' +
|
|
49
|
-
'text-align: center;' +
|
|
50
|
-
'text-shadow: 2px 2px 4px rgba(0, 0, 0, 1);' +
|
|
51
|
-
'background-color: rgba(0, 0, 0, 0.6);' +
|
|
52
|
-
'padding: 8px 16px;' +
|
|
53
|
-
'border-radius: 6px;' +
|
|
54
|
-
'max-width: 80%;' +
|
|
55
|
-
'line-height: 1.3;' +
|
|
56
|
-
'white-space: pre-line;' +
|
|
57
|
-
'display: none;' +
|
|
58
|
-
'pointer-events: none;' +
|
|
59
|
-
'box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);';
|
|
60
|
-
|
|
61
|
-
var playerContainer = this.video.parentElement;
|
|
62
|
-
if (playerContainer) {
|
|
63
|
-
playerContainer.style.position = 'relative';
|
|
64
|
-
// ENSURE proper layer stacking
|
|
65
|
-
if (!playerContainer.style.zIndex) {
|
|
66
|
-
playerContainer.style.zIndex = '1';
|
|
67
|
-
}
|
|
68
|
-
playerContainer.appendChild(this.customOverlayElement);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
customTimeToSeconds(timeString) {
|
|
75
|
-
if (!timeString) return 0;
|
|
76
|
-
|
|
77
|
-
var parts = timeString.split(',');
|
|
78
|
-
if (parts.length !== 2) return 0;
|
|
79
|
-
|
|
80
|
-
var time = parts[0];
|
|
81
|
-
var millis = parts[1];
|
|
82
|
-
|
|
83
|
-
var timeParts = time.split(':');
|
|
84
|
-
if (timeParts.length !== 3) return 0;
|
|
85
|
-
|
|
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;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
parseCustomSRT(srtText) {
|
|
100
|
-
var subtitles = [];
|
|
101
|
-
var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
102
|
-
var blocks = normalizedText.trim().split('\n\n');
|
|
103
|
-
|
|
104
|
-
for (var i = 0; i < blocks.length; i++) {
|
|
105
|
-
var block = blocks[i];
|
|
106
|
-
var lines = block.trim().split('\n');
|
|
107
|
-
|
|
108
|
-
if (lines.length >= 3) {
|
|
109
|
-
var timeLine = lines[1].trim();
|
|
110
|
-
var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
|
|
111
|
-
|
|
112
|
-
if (timeMatch) {
|
|
113
|
-
var startTime = this.customTimeToSeconds(timeMatch[1]);
|
|
114
|
-
var endTime = this.customTimeToSeconds(timeMatch[2]);
|
|
115
|
-
var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
|
|
116
|
-
|
|
117
|
-
if (text && text.length > 0 && startTime < endTime) {
|
|
118
|
-
subtitles.push({
|
|
119
|
-
start: startTime,
|
|
120
|
-
end: endTime,
|
|
121
|
-
text: text
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (this.options.debug) console.log('✅ Parsed ' + subtitles.length + ' subtitles');
|
|
129
|
-
return subtitles;
|
|
130
|
-
}
|
|
131
|
-
|
|
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
|
-
}
|
|
189
|
-
|
|
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;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
enableCustomSubtitleTrack(trackIndex) {
|
|
222
|
-
if (trackIndex < 0 || trackIndex >= this.customSubtitles.length) return false;
|
|
223
|
-
|
|
224
|
-
this.disableCustomSubtitles();
|
|
225
|
-
|
|
226
|
-
this.customSubtitlesEnabled = true;
|
|
227
|
-
this.currentCustomTrackIndex = trackIndex;
|
|
228
|
-
this.currentCustomSubtitles = this.customSubtitles[trackIndex].subtitles;
|
|
229
|
-
|
|
230
|
-
var self = this;
|
|
231
|
-
this.customUpdateInterval = setInterval(function () {
|
|
232
|
-
if (self.customSubtitlesEnabled && self.currentCustomSubtitles.length > 0) {
|
|
233
|
-
self.updateCustomSubtitleDisplay();
|
|
234
|
-
}
|
|
235
|
-
}, 100);
|
|
236
|
-
|
|
237
|
-
if (this.options.debug) {
|
|
238
|
-
console.log('✅ Custom subtitles enabled: ' + this.customSubtitles[trackIndex].label);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return true;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
updateCustomSubtitleDisplay() {
|
|
245
|
-
if (!this.customSubtitlesEnabled || this.currentCustomSubtitles.length === 0) return;
|
|
246
|
-
|
|
247
|
-
var currentTime = this.video.currentTime;
|
|
248
|
-
var currentSubtitle = null;
|
|
249
|
-
|
|
250
|
-
for (var i = 0; i < this.currentCustomSubtitles.length; i++) {
|
|
251
|
-
var sub = this.currentCustomSubtitles[i];
|
|
252
|
-
if (currentTime >= sub.start && currentTime <= sub.end) {
|
|
253
|
-
currentSubtitle = sub;
|
|
254
|
-
break;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (currentSubtitle) {
|
|
259
|
-
this.customOverlayElement.textContent = currentSubtitle.text;
|
|
260
|
-
this.customOverlayElement.style.display = 'block';
|
|
261
|
-
} else {
|
|
262
|
-
this.customOverlayElement.style.display = 'none';
|
|
263
|
-
this.customOverlayElement.textContent = '';
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
disableCustomSubtitles() {
|
|
268
|
-
this.customSubtitlesEnabled = false;
|
|
269
|
-
this.currentCustomTrackIndex = -1;
|
|
270
|
-
|
|
271
|
-
if (this.customOverlayElement) {
|
|
272
|
-
this.customOverlayElement.style.display = 'none';
|
|
273
|
-
this.customOverlayElement.textContent = '';
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (this.customUpdateInterval) {
|
|
277
|
-
clearInterval(this.customUpdateInterval);
|
|
278
|
-
this.customUpdateInterval = null;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (this.options.debug) console.log('❌ Custom subtitles disabled');
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
detectTextTracks() {
|
|
285
|
-
this.textTracks = [];
|
|
286
|
-
|
|
287
|
-
if (this.video.textTracks) {
|
|
288
|
-
if (this.options.debug) console.log('🔍 Detecting text tracks... Found: ' + this.video.textTracks.length);
|
|
289
|
-
|
|
290
|
-
for (var i = 0; i < this.video.textTracks.length; i++) {
|
|
291
|
-
var track = this.video.textTracks[i];
|
|
292
|
-
|
|
293
|
-
if (track.kind === 'subtitles' || track.kind === 'captions') {
|
|
294
|
-
this.textTracks.push({
|
|
295
|
-
track: track,
|
|
296
|
-
label: track.label || 'Track ' + (i + 1),
|
|
297
|
-
language: track.language || 'unknown',
|
|
298
|
-
kind: track.kind,
|
|
299
|
-
index: i
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (this.options.debug) console.log('📊 Total subtitle tracks detected: ' + this.textTracks.length);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
enableSubtitleTrack(trackIndex) {
|
|
309
|
-
if (trackIndex < 0 || trackIndex >= this.textTracks.length) return;
|
|
310
|
-
|
|
311
|
-
// Disable all tracks first
|
|
312
|
-
this.disableAllTracks();
|
|
313
|
-
|
|
314
|
-
// Enable ONLY the custom subtitle system (not native browser)
|
|
315
|
-
var success = this.enableCustomSubtitleTrack(trackIndex);
|
|
316
|
-
|
|
317
|
-
if (success) {
|
|
318
|
-
this.currentSubtitleTrack = this.textTracks[trackIndex].track;
|
|
319
|
-
this.subtitlesEnabled = true;
|
|
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
|
-
|
|
326
|
-
this.updateSubtitlesButton();
|
|
327
|
-
this.populateSubtitlesMenu();
|
|
328
|
-
|
|
329
|
-
if (this.options.debug) {
|
|
330
|
-
console.log('✅ Custom subtitles enabled:', this.textTracks[trackIndex].label);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Trigger subtitle change event
|
|
334
|
-
this.triggerEvent('subtitlechange', {
|
|
335
|
-
enabled: true,
|
|
336
|
-
trackIndex: trackIndex,
|
|
337
|
-
trackLabel: this.textTracks[trackIndex].label,
|
|
338
|
-
trackLanguage: this.textTracks[trackIndex].language
|
|
339
|
-
});
|
|
340
|
-
} else {
|
|
341
|
-
if (this.options.debug) {
|
|
342
|
-
console.error('❌ Failed to enable custom subtitles for track', trackIndex);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
disableSubtitles() {
|
|
348
|
-
this.disableCustomSubtitles();
|
|
349
|
-
this.disableAllTracks();
|
|
350
|
-
|
|
351
|
-
this.currentSubtitleTrack = null;
|
|
352
|
-
this.subtitlesEnabled = false;
|
|
353
|
-
|
|
354
|
-
this.updateSubtitlesButton();
|
|
355
|
-
this.populateSubtitlesMenu();
|
|
356
|
-
|
|
357
|
-
if (this.options.debug) console.log('📝 Subtitles disabled');
|
|
358
|
-
|
|
359
|
-
this.triggerEvent('subtitlechange', {
|
|
360
|
-
enabled: false,
|
|
361
|
-
trackIndex: -1
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
disableAllTracks() {
|
|
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';
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Also disable custom subtitles
|
|
374
|
-
this.disableCustomSubtitles();
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
getAvailableSubtitles() {
|
|
378
|
-
return this.textTracks.map(function (t) {
|
|
379
|
-
return {
|
|
380
|
-
label: t.label,
|
|
381
|
-
language: t.language,
|
|
382
|
-
kind: t.kind
|
|
383
|
-
};
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
setSubtitleTrack(trackIndex) {
|
|
388
|
-
if (trackIndex === -1) {
|
|
389
|
-
this.disableSubtitles();
|
|
390
|
-
} else {
|
|
391
|
-
this.enableSubtitleTrack(trackIndex);
|
|
392
|
-
}
|
|
393
|
-
return this;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
getCurrentSubtitleTrack() {
|
|
397
|
-
if (!this.subtitlesEnabled || !this.currentSubtitleTrack) return -1;
|
|
398
|
-
|
|
399
|
-
for (var i = 0; i < this.textTracks.length; i++) {
|
|
400
|
-
if (this.textTracks[i].track === this.currentSubtitleTrack) {
|
|
401
|
-
return i;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
return -1;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
isSubtitlesEnabled() {
|
|
408
|
-
return this.subtitlesEnabled;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
updateSubtitlesButton() {
|
|
412
|
-
var subtitlesBtn = this.controls && this.controls.querySelector('.subtitles-btn');
|
|
413
|
-
if (!subtitlesBtn) return;
|
|
414
|
-
|
|
415
|
-
subtitlesBtn.classList.remove('active');
|
|
416
|
-
|
|
417
|
-
if (this.subtitlesEnabled) {
|
|
418
|
-
subtitlesBtn.title = this.t('subtitlesdisable');
|
|
419
|
-
} else {
|
|
420
|
-
subtitlesBtn.title = this.t('subtitlesenable');
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
populateSubtitlesMenu() {
|
|
425
|
-
var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
|
|
426
|
-
if (!subtitlesMenu) return;
|
|
427
|
-
|
|
428
|
-
var menuHTML = '<div class="subtitles-option ' + (!this.subtitlesEnabled ? 'active' : '') + '" data-track="off">Off</div>';
|
|
429
|
-
|
|
430
|
-
for (var i = 0; i < this.textTracks.length; i++) {
|
|
431
|
-
var trackData = this.textTracks[i];
|
|
432
|
-
var isActive = this.currentSubtitleTrack === trackData.track;
|
|
433
|
-
menuHTML += '<div class="subtitles-option ' + (isActive ? 'active' : '') + '" data-track="' + i + '">' + trackData.label + '</div>';
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
subtitlesMenu.innerHTML = menuHTML;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
updateSubtitlesUI() {
|
|
440
|
-
var subtitlesControl = this.controls && this.controls.querySelector('.subtitles-control');
|
|
441
|
-
|
|
442
|
-
if (this.textTracks.length > 0 && this.options.showSubtitles) {
|
|
443
|
-
if (subtitlesControl) subtitlesControl.style.display = 'block';
|
|
444
|
-
this.populateSubtitlesMenu();
|
|
445
|
-
} else {
|
|
446
|
-
if (subtitlesControl) subtitlesControl.style.display = 'none';
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
this.updateSubtitlesButton();
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
bindSubtitleEvents() {
|
|
453
|
-
var self = this;
|
|
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
|
|
477
|
-
var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
|
|
478
|
-
if (subtitlesMenu) {
|
|
479
|
-
subtitlesMenu.addEventListener('click', function (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);
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
handleSubtitlesMenuClick(e) {
|
|
500
|
-
var option = e.target.closest('.subtitles-option');
|
|
501
|
-
if (!option) return; // This prevents button clicks from toggling
|
|
502
|
-
|
|
503
|
-
var trackIndex = option.getAttribute('data-track');
|
|
504
|
-
|
|
505
|
-
if (trackIndex === 'off') {
|
|
506
|
-
this.disableSubtitles();
|
|
507
|
-
} else {
|
|
508
|
-
// Don't check for 'toggle' - just enable the track
|
|
509
|
-
this.enableSubtitleTrack(parseInt(trackIndex));
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
this.updateSubtitlesButton();
|
|
513
|
-
this.populateSubtitlesMenu();
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
toggleSubtitles() {
|
|
517
|
-
if (this.textTracks.length === 0) return;
|
|
518
|
-
|
|
519
|
-
if (this.subtitlesEnabled) {
|
|
520
|
-
this.disableSubtitles();
|
|
521
|
-
} else {
|
|
522
|
-
this.enableSubtitleTrack(0);
|
|
523
|
-
}
|
|
524
|
-
}
|
package/src/utils.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// Utils Module for MYETV Video Player
|
|
2
|
-
// Conservative modularization - original code preserved exactly
|
|
3
|
-
// Created by https://www.myetv.tv https://oskarcosimo.com
|
|
4
|
-
|
|
5
|
-
getBufferedTime() {
|
|
6
|
-
if (!this.video || !this.video.buffered || this.video.buffered.length === 0) return 0;
|
|
7
|
-
try {
|
|
8
|
-
return this.video.buffered.end(this.video.buffered.length - 1);
|
|
9
|
-
} catch (error) {
|
|
10
|
-
return 0;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
clearTitleTimeout() {
|
|
15
|
-
if (this.titleTimeout) {
|
|
16
|
-
clearTimeout(this.titleTimeout);
|
|
17
|
-
this.titleTimeout = null;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
skipTime(seconds) {
|
|
22
|
-
if (!this.video || !this.video.duration || this.isChangingQuality) return;
|
|
23
|
-
|
|
24
|
-
this.video.currentTime = Math.max(0, Math.min(this.video.duration, this.video.currentTime + seconds));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
updateTimeDisplay() {
|
|
28
|
-
// update current time
|
|
29
|
-
if (this.currentTimeEl && this.video) {
|
|
30
|
-
this.currentTimeEl.textContent = this.formatTime(this.video.currentTime || 0);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// update duration or show badge if encoding
|
|
34
|
-
if (this.durationEl && this.video) {
|
|
35
|
-
const duration = this.video.duration;
|
|
36
|
-
|
|
37
|
-
// check if duration is valid
|
|
38
|
-
if (!duration || isNaN(duration) || !isFinite(duration)) {
|
|
39
|
-
// Video in encoding - show badge instead of duration
|
|
40
|
-
this.durationEl.innerHTML = '<span class="encoding-badge">Encoding in progress</span>';
|
|
41
|
-
this.durationEl.classList.add('encoding-state');
|
|
42
|
-
} else {
|
|
43
|
-
// valid duration - show normal
|
|
44
|
-
this.durationEl.textContent = this.formatTime(duration);
|
|
45
|
-
this.durationEl.classList.remove('encoding-state');
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
formatTime(seconds) {
|
|
52
|
-
if (isNaN(seconds) || seconds < 0) return '0:00';
|
|
53
|
-
|
|
54
|
-
const hours = Math.floor(seconds / 3600);
|
|
55
|
-
const minutes = Math.floor((seconds % 3600) / 60);
|
|
56
|
-
const secs = Math.floor(seconds % 60);
|
|
57
|
-
|
|
58
|
-
if (hours > 0) {
|
|
59
|
-
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
60
|
-
}
|
|
61
|
-
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Utils methods for main class
|
|
65
|
-
// All original functionality preserved exactly
|