myetv-player 1.7.1 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +461 -155
- package/dist/myetv-player.js +391 -82
- package/dist/myetv-player.min.js +314 -53
- package/package.json +1 -1
- package/plugins/autosub/myetv-autosub-plugin.js +303 -155
- package/plugins/autosub/readme.md +63 -69
- package/plugins/radio/api.php +154 -0
- package/plugins/radio/icons/bolt-lightning-solid.png +0 -0
- package/plugins/radio/icons/compact-disc-solid.png +0 -0
- package/plugins/radio/icons/guitar-solid.png +0 -0
- package/plugins/radio/icons/headphones-solid.png +0 -0
- package/plugins/radio/icons/medal-solid.png +0 -0
- package/plugins/radio/icons/music-solid.png +0 -0
- package/plugins/radio/icons/newspaper-solid.png +0 -0
- package/plugins/radio/icons/radio-solid.png +0 -0
- package/plugins/radio/icons/readme.md +1 -0
- package/plugins/radio/myetv-radio-plugin.js +1655 -0
- package/plugins/radio/php/data/readme.md +1 -0
- package/plugins/radio/php/radio_stats.php +203 -0
- package/plugins/radio/proxy.php +26 -0
- package/plugins/radio/readme.md +176 -0
- package/plugins/radio/station_example.json +47 -0
- package/plugins/video-loading/myetv-video-loading-plugin.js +157 -0
- package/plugins/video-loading/readme.md +87 -0
package/dist/myetv-player.js
CHANGED
|
@@ -674,6 +674,9 @@ constructor(videoElement, options = {}) {
|
|
|
674
674
|
showPictureInPicture: true, // Enable PiP button
|
|
675
675
|
showSubtitles: true, // Enable subtitles button
|
|
676
676
|
subtitlesEnabled: false, // Enable subtitles by default if available
|
|
677
|
+
externalSubtitleTracks: [],// external subtitles track from url
|
|
678
|
+
externalSubtitleLabel: 'Default Subtitles', //label for the subtitle track url
|
|
679
|
+
externalSubtitleLang: 'en', // language of the subtitles track url
|
|
677
680
|
showSettingsMenu: true, // Show settings menu in top bar
|
|
678
681
|
autoHide: true, // auto-hide controls when idle
|
|
679
682
|
autoHideDelay: 3000, // hide controls after ... seconds of inactivity (specificed in milliseconds)
|
|
@@ -841,9 +844,33 @@ constructor(videoElement, options = {}) {
|
|
|
841
844
|
if (this.options.language && this.isI18nAvailable()) {
|
|
842
845
|
VideoPlayerTranslations.setLanguage(this.options.language);
|
|
843
846
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
+
|
|
848
|
+
this.savedAutoplayIntent = options.autoplay || false;
|
|
849
|
+
this.options.autoplay = false;
|
|
850
|
+
|
|
851
|
+
if (this.video) {
|
|
852
|
+
if (this.video.hasAttribute('autoplay')) {
|
|
853
|
+
this.savedAutoplayIntent = true;
|
|
854
|
+
this.video.removeAttribute('autoplay');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Physically stop the browser engine from pre-loading and playing
|
|
858
|
+
this.video.pause();
|
|
859
|
+
this.video.preload = 'none';
|
|
860
|
+
|
|
861
|
+
if (this.video.hasAttribute('poster')) {
|
|
862
|
+
this.savedPoster = this.video.getAttribute('poster');
|
|
863
|
+
this.video.setAttribute('poster', 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
this.video.pause();
|
|
867
|
+
this.video.preload = 'none';
|
|
868
|
+
|
|
869
|
+
this.video.removeAttribute('src');
|
|
870
|
+
this.video.load();
|
|
871
|
+
|
|
872
|
+
this.video.style.opacity = '0';
|
|
873
|
+
this.video.style.visibility = 'hidden';
|
|
847
874
|
}
|
|
848
875
|
|
|
849
876
|
try {
|
|
@@ -935,17 +962,8 @@ constructor(videoElement, options = {}) {
|
|
|
935
962
|
this.updateVolumeSliderVisual();
|
|
936
963
|
this.initVolumeTooltip();
|
|
937
964
|
this.updateTooltips();
|
|
938
|
-
this.markPlayerReady();
|
|
939
|
-
this.initializePluginSystem();
|
|
940
|
-
this.restoreSourcesAsync();
|
|
941
|
-
|
|
942
|
-
this.initializeSubtitles();
|
|
943
|
-
this.initializeQualityMonitoring();
|
|
944
965
|
|
|
945
|
-
this.
|
|
946
|
-
this.initializeChapters();
|
|
947
|
-
this.initializePoster();
|
|
948
|
-
this.initializeWatermark();
|
|
966
|
+
this.initializePluginSystem();
|
|
949
967
|
|
|
950
968
|
} catch (error) {
|
|
951
969
|
if (this.options.debug) console.error('Video player initialization error:', error);
|
|
@@ -1078,6 +1096,8 @@ interceptAutoLoading() {
|
|
|
1078
1096
|
|
|
1079
1097
|
this.hideNativePlayer();
|
|
1080
1098
|
|
|
1099
|
+
this.video.load();
|
|
1100
|
+
|
|
1081
1101
|
if (this.options.debug) console.log('📁 Sources temporarily disabled to prevent blocking');
|
|
1082
1102
|
}
|
|
1083
1103
|
|
|
@@ -1246,6 +1266,7 @@ markPlayerReady() {
|
|
|
1246
1266
|
this.video.style.visibility = '';
|
|
1247
1267
|
this.video.style.opacity = '';
|
|
1248
1268
|
this.video.style.pointerEvents = '';
|
|
1269
|
+
this.video.style.display = '';
|
|
1249
1270
|
}
|
|
1250
1271
|
|
|
1251
1272
|
// UPDATE SETTINGS MENU VISIBILITY IF APPLICABLE
|
|
@@ -2030,6 +2051,12 @@ updateSeekTooltip(e) {
|
|
|
2030
2051
|
}
|
|
2031
2052
|
|
|
2032
2053
|
play() {
|
|
2054
|
+
if (!this.isPlayerReady) {
|
|
2055
|
+
this.savedAutoplayIntent = true;
|
|
2056
|
+
if (this.options.debug) console.log('🛑 Play requested but player is booting. Intent saved for later.');
|
|
2057
|
+
return Promise ? Promise.resolve() : undefined;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2033
2060
|
if (!this.video || this.isChangingQuality) return;
|
|
2034
2061
|
|
|
2035
2062
|
this.video.play().catch(err => {
|
|
@@ -2039,7 +2066,6 @@ play() {
|
|
|
2039
2066
|
if (this.playIcon) this.playIcon.classList.add('hidden');
|
|
2040
2067
|
if (this.pauseIcon) this.pauseIcon.classList.remove('hidden');
|
|
2041
2068
|
|
|
2042
|
-
// Trigger event played
|
|
2043
2069
|
this.triggerEvent('played', {
|
|
2044
2070
|
currentTime: this.getCurrentTime(),
|
|
2045
2071
|
duration: this.getDuration()
|
|
@@ -2047,6 +2073,11 @@ play() {
|
|
|
2047
2073
|
}
|
|
2048
2074
|
|
|
2049
2075
|
pause() {
|
|
2076
|
+
if (!this.isPlayerReady) {
|
|
2077
|
+
this.savedAutoplayIntent = false;
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2050
2081
|
if (!this.video) return;
|
|
2051
2082
|
|
|
2052
2083
|
this.video.pause();
|
|
@@ -4416,6 +4447,53 @@ populateSettingsMenu() {
|
|
|
4416
4447
|
settingsMenu.innerHTML = menuHTML;
|
|
4417
4448
|
}
|
|
4418
4449
|
|
|
4450
|
+
/**
|
|
4451
|
+
* Updates specific UI elements inside the settings menu
|
|
4452
|
+
* without recreating the entire DOM (prevents accordions from closing)
|
|
4453
|
+
*/
|
|
4454
|
+
updateSettingsMenuUI() {
|
|
4455
|
+
const settingsMenu = this.container?.querySelector('.settings-menu');
|
|
4456
|
+
if (!settingsMenu) return;
|
|
4457
|
+
|
|
4458
|
+
if (this.options.showSubtitles) {
|
|
4459
|
+
const subtitlesTrigger = settingsMenu.querySelector('[data-action="subtitles_expand"]');
|
|
4460
|
+
|
|
4461
|
+
if (subtitlesTrigger) {
|
|
4462
|
+
// 1. Update the label text (e.g. "Subtitles: English")
|
|
4463
|
+
const label = subtitlesTrigger.querySelector('.settings-option-label');
|
|
4464
|
+
if (label) {
|
|
4465
|
+
const subtitlesLabel = this.t('subtitles') || 'Subtitles';
|
|
4466
|
+
const currentTrack = this.currentSubtitleTrack;
|
|
4467
|
+
|
|
4468
|
+
let currentLabelText = this.t('subtitlesoff') || 'Off';
|
|
4469
|
+
if (this.subtitlesEnabled && currentTrack) {
|
|
4470
|
+
currentLabelText = currentTrack.label || 'Unknown';
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4473
|
+
label.innerHTML = `${subtitlesLabel} <strong>${currentLabelText}</strong>`;
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
// 2. Update the "active" classes in the dropdown list
|
|
4477
|
+
const submenuContent = subtitlesTrigger.nextElementSibling;
|
|
4478
|
+
if (submenuContent && submenuContent.classList.contains('settings-expandable-content')) {
|
|
4479
|
+
submenuContent.querySelectorAll('.settings-suboption').forEach(opt => {
|
|
4480
|
+
const trackData = opt.getAttribute('data-track');
|
|
4481
|
+
opt.classList.remove('active');
|
|
4482
|
+
|
|
4483
|
+
if (trackData === 'off' && !this.subtitlesEnabled) {
|
|
4484
|
+
opt.classList.add('active');
|
|
4485
|
+
} else if (this.subtitlesEnabled && trackData !== 'off') {
|
|
4486
|
+
const trackIndex = parseInt(trackData);
|
|
4487
|
+
if (this.textTracks[trackIndex] && this.textTracks[trackIndex].track === this.currentSubtitleTrack) {
|
|
4488
|
+
opt.classList.add('active');
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
});
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
|
|
4419
4497
|
/**
|
|
4420
4498
|
* Add scrollbar to settings menu on mobile
|
|
4421
4499
|
*/
|
|
@@ -6097,6 +6175,33 @@ createCustomSubtitleOverlay() {
|
|
|
6097
6175
|
if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
|
|
6098
6176
|
}
|
|
6099
6177
|
|
|
6178
|
+
// Universal time parser for both SRT (00:00:00,000) and VTT (00:00.000)
|
|
6179
|
+
parseTimeToSecondsUniversal(timeString) {
|
|
6180
|
+
if (!timeString) return 0;
|
|
6181
|
+
|
|
6182
|
+
// Normalize comma to dot for consistency
|
|
6183
|
+
let normalized = timeString.replace(',', '.');
|
|
6184
|
+
let parts = normalized.split('.');
|
|
6185
|
+
|
|
6186
|
+
let timeParts = parts[0].split(':');
|
|
6187
|
+
let ms = parts[1] ? parseInt(parts[1], 10) / 1000 : 0;
|
|
6188
|
+
|
|
6189
|
+
let hours = 0, minutes = 0, seconds = 0;
|
|
6190
|
+
|
|
6191
|
+
if (timeParts.length === 3) {
|
|
6192
|
+
// SRT Format: HH:MM:SS
|
|
6193
|
+
hours = parseInt(timeParts[0], 10);
|
|
6194
|
+
minutes = parseInt(timeParts[1], 10);
|
|
6195
|
+
seconds = parseInt(timeParts[2], 10);
|
|
6196
|
+
} else if (timeParts.length === 2) {
|
|
6197
|
+
// VTT Short Format: MM:SS
|
|
6198
|
+
minutes = parseInt(timeParts[0], 10);
|
|
6199
|
+
seconds = parseInt(timeParts[1], 10);
|
|
6200
|
+
}
|
|
6201
|
+
|
|
6202
|
+
return (hours * 3600) + (minutes * 60) + seconds + ms;
|
|
6203
|
+
}
|
|
6204
|
+
|
|
6100
6205
|
customTimeToSeconds(timeString) {
|
|
6101
6206
|
if (!timeString) return 0;
|
|
6102
6207
|
|
|
@@ -6122,6 +6227,33 @@ customTimeToSeconds(timeString) {
|
|
|
6122
6227
|
return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
|
|
6123
6228
|
}
|
|
6124
6229
|
|
|
6230
|
+
// Universal time parser for both SRT (00:00:00,000) and VTT (00:00.000)
|
|
6231
|
+
parseTimeToSecondsUniversal(timeString) {
|
|
6232
|
+
if (!timeString) return 0;
|
|
6233
|
+
|
|
6234
|
+
// Normalize comma to dot for consistency
|
|
6235
|
+
const normalized = timeString.replace(',', '.');
|
|
6236
|
+
const parts = normalized.split('.');
|
|
6237
|
+
|
|
6238
|
+
const timeParts = parts[0].split(':');
|
|
6239
|
+
const ms = parts[1] ? parseInt(parts[1], 10) / 1000 : 0;
|
|
6240
|
+
|
|
6241
|
+
let hours = 0, minutes = 0, seconds = 0;
|
|
6242
|
+
|
|
6243
|
+
if (timeParts.length === 3) {
|
|
6244
|
+
// Full format: HH:MM:SS
|
|
6245
|
+
hours = parseInt(timeParts[0], 10);
|
|
6246
|
+
minutes = parseInt(timeParts[1], 10);
|
|
6247
|
+
seconds = parseInt(timeParts[2], 10);
|
|
6248
|
+
} else if (timeParts.length === 2) {
|
|
6249
|
+
// Short format (common in VTT): MM:SS
|
|
6250
|
+
minutes = parseInt(timeParts[0], 10);
|
|
6251
|
+
seconds = parseInt(timeParts[1], 10);
|
|
6252
|
+
}
|
|
6253
|
+
|
|
6254
|
+
return (hours * 3600) + (minutes * 60) + seconds + ms;
|
|
6255
|
+
}
|
|
6256
|
+
|
|
6125
6257
|
parseCustomSRT(srtText) {
|
|
6126
6258
|
var subtitles = [];
|
|
6127
6259
|
var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
@@ -6155,6 +6287,63 @@ parseCustomSRT(srtText) {
|
|
|
6155
6287
|
return subtitles;
|
|
6156
6288
|
}
|
|
6157
6289
|
|
|
6290
|
+
// Method to inject multiple external subtitle tracks
|
|
6291
|
+
injectExternalSubtitleTracks(tracksArray) {
|
|
6292
|
+
// Check if video exists and if tracksArray is actually an array
|
|
6293
|
+
if (!this.video || !Array.isArray(tracksArray) || tracksArray.length === 0) return;
|
|
6294
|
+
|
|
6295
|
+
this.video.crossOrigin = "anonymous";
|
|
6296
|
+
let loadedCount = 0;
|
|
6297
|
+
|
|
6298
|
+
// Loop through each track object in the array
|
|
6299
|
+
tracksArray.forEach(trackData => {
|
|
6300
|
+
const trackEl = document.createElement('track');
|
|
6301
|
+
trackEl.kind = 'subtitles';
|
|
6302
|
+
trackEl.label = trackData.label;
|
|
6303
|
+
trackEl.srclang = trackData.lang;
|
|
6304
|
+
trackEl.src = trackData.src;
|
|
6305
|
+
|
|
6306
|
+
// Set default if specified in the JSON
|
|
6307
|
+
if (trackData.default) {
|
|
6308
|
+
trackEl.default = true;
|
|
6309
|
+
trackEl.setAttribute('default', 'default');
|
|
6310
|
+
trackEl.setAttribute('data-default', 'true');
|
|
6311
|
+
}
|
|
6312
|
+
|
|
6313
|
+
// Listen for the load event on EACH track
|
|
6314
|
+
trackEl.addEventListener('load', () => {
|
|
6315
|
+
if (trackEl.track) {
|
|
6316
|
+
// Only show the default one, keep others hidden but ready
|
|
6317
|
+
trackEl.track.mode = trackData.default ? 'showing' : 'hidden';
|
|
6318
|
+
}
|
|
6319
|
+
|
|
6320
|
+
loadedCount++;
|
|
6321
|
+
|
|
6322
|
+
// Re-initialize UI ONLY when ALL tracks have finished loading
|
|
6323
|
+
if (loadedCount === tracksArray.length) {
|
|
6324
|
+
console.log(`MYETV Player: ${loadedCount} external subtitle tracks loaded.`);
|
|
6325
|
+
|
|
6326
|
+
if (typeof this.initializeSubtitles === 'function') {
|
|
6327
|
+
this.initializeSubtitles();
|
|
6328
|
+
} else if (typeof this.initializeCustomSubtitles === 'function') {
|
|
6329
|
+
this.initializeCustomSubtitles();
|
|
6330
|
+
} else if (typeof this.loadCustomSubtitleTracks === 'function') {
|
|
6331
|
+
this.loadCustomSubtitleTracks();
|
|
6332
|
+
}
|
|
6333
|
+
}
|
|
6334
|
+
});
|
|
6335
|
+
|
|
6336
|
+
trackEl.addEventListener('error', () => {
|
|
6337
|
+
console.warn(`MYETV Player: Failed to load track ${trackData.lang}`);
|
|
6338
|
+
// Increment anyway so we don't block the UI initialization
|
|
6339
|
+
loadedCount++;
|
|
6340
|
+
});
|
|
6341
|
+
|
|
6342
|
+
// Append to DOM
|
|
6343
|
+
this.video.appendChild(trackEl);
|
|
6344
|
+
});
|
|
6345
|
+
}
|
|
6346
|
+
|
|
6158
6347
|
loadCustomSubtitleTracks() {
|
|
6159
6348
|
var self = this;
|
|
6160
6349
|
var tracks = this.video.querySelectorAll('track[kind="subtitles"]');
|
|
@@ -6165,7 +6354,8 @@ loadCustomSubtitleTracks() {
|
|
|
6165
6354
|
var label = track.getAttribute('label') || 'Unknown';
|
|
6166
6355
|
var srclang = track.getAttribute('srclang') || '';
|
|
6167
6356
|
|
|
6168
|
-
|
|
6357
|
+
var isDefault = track.default || track.hasAttribute('default');
|
|
6358
|
+
|
|
6169
6359
|
var trackObj = {
|
|
6170
6360
|
label: label,
|
|
6171
6361
|
language: srclang,
|
|
@@ -6179,43 +6369,83 @@ loadCustomSubtitleTracks() {
|
|
|
6179
6369
|
return response.text();
|
|
6180
6370
|
})
|
|
6181
6371
|
.then(function (srtText) {
|
|
6372
|
+
// Normalize newlines
|
|
6182
6373
|
var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
6374
|
+
|
|
6375
|
+
// Split into blocks by double newline
|
|
6183
6376
|
var blocks = normalizedText.trim().split('\n\n');
|
|
6184
6377
|
|
|
6185
6378
|
for (var i = 0; i < blocks.length; i++) {
|
|
6186
6379
|
var block = blocks[i].trim();
|
|
6187
|
-
if (!block) continue;
|
|
6380
|
+
if (!block || block.toUpperCase().startsWith('WEBVTT')) continue;
|
|
6381
|
+
|
|
6188
6382
|
var lines = block.split('\n');
|
|
6383
|
+
var timeMatch = null;
|
|
6384
|
+
var textLines = [];
|
|
6385
|
+
|
|
6386
|
+
for (var j = 0; j < lines.length; j++) {
|
|
6387
|
+
var line = lines[j].trim();
|
|
6388
|
+
|
|
6389
|
+
// Regex matches both SRT and VTT formats (with or without hours, dot or comma)
|
|
6390
|
+
if (!timeMatch) {
|
|
6391
|
+
var match = line.match(/(\d{2,}:\d{2}:\d{2}[.,]\d{3}|\d{2}:\d{2}[.,]\d{3})\s*-->\s*(\d{2,}:\d{2}:\d{2}[.,]\d{3}|\d{2}:\d{2}[.,]\d{3})/);
|
|
6392
|
+
if (match) {
|
|
6393
|
+
timeMatch = match;
|
|
6394
|
+
continue;
|
|
6395
|
+
}
|
|
6396
|
+
}
|
|
6189
6397
|
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6398
|
+
// Skip numeric IDs common in SRT files
|
|
6399
|
+
if (!timeMatch && /^\d+$/.test(line)) {
|
|
6400
|
+
continue;
|
|
6401
|
+
}
|
|
6193
6402
|
|
|
6403
|
+
// Everything after the timestamp is the actual subtitle text
|
|
6194
6404
|
if (timeMatch) {
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
}
|
|
6405
|
+
textLines.push(line);
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
|
|
6409
|
+
// Store the parsed subtitle block
|
|
6410
|
+
if (timeMatch && textLines.length > 0) {
|
|
6411
|
+
var startTime = self.parseTimeToSecondsUniversal(timeMatch[1]);
|
|
6412
|
+
var endTime = self.parseTimeToSecondsUniversal(timeMatch[2]);
|
|
6413
|
+
var text = textLines.join('\n').trim().replace(/<[^>]*>/g, '');
|
|
6414
|
+
|
|
6415
|
+
if (text && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
|
|
6416
|
+
trackObj.subtitles.push({
|
|
6417
|
+
start: startTime,
|
|
6418
|
+
end: endTime,
|
|
6419
|
+
text: text
|
|
6420
|
+
});
|
|
6212
6421
|
}
|
|
6213
6422
|
}
|
|
6214
6423
|
}
|
|
6215
6424
|
|
|
6216
6425
|
if (self.options.debug) {
|
|
6217
|
-
console.log('✅
|
|
6426
|
+
console.log('✅ Custom parser loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
|
|
6427
|
+
}
|
|
6428
|
+
|
|
6429
|
+
// =========================================================
|
|
6430
|
+
// AUTO-ENABLE SUBTITLES
|
|
6431
|
+
// =========================================================
|
|
6432
|
+
var isDefault = track.default || track.hasAttribute('default') || track.getAttribute('data-default') === 'true';
|
|
6433
|
+
|
|
6434
|
+
if (isDefault) {
|
|
6435
|
+
if (self.options.debug) {
|
|
6436
|
+
console.log('⏳ Waiting 500ms for UI alignment... track:', label);
|
|
6437
|
+
}
|
|
6438
|
+
|
|
6439
|
+
// Delay execution to ensure the player has finished
|
|
6440
|
+
// building the menu and populating this.textTracks array!
|
|
6441
|
+
setTimeout(function () {
|
|
6442
|
+
if (self.options.debug) {
|
|
6443
|
+
console.log('🎯 Forced auto-enable triggered now!');
|
|
6444
|
+
}
|
|
6445
|
+
self.enableSubtitleTrack(index);
|
|
6446
|
+
}, 500); // 500 milliseconds of delay
|
|
6218
6447
|
}
|
|
6448
|
+
|
|
6219
6449
|
})
|
|
6220
6450
|
.catch(function (error) {
|
|
6221
6451
|
console.error('❌ Error loading ' + label + ':', error);
|
|
@@ -6334,39 +6564,38 @@ detectTextTracks() {
|
|
|
6334
6564
|
enableSubtitleTrack(trackIndex) {
|
|
6335
6565
|
if (trackIndex < 0 || trackIndex >= this.textTracks.length) return;
|
|
6336
6566
|
|
|
6337
|
-
// Disable all tracks first
|
|
6567
|
+
// 1. Disable all tracks first to prevent overlapping
|
|
6338
6568
|
this.disableAllTracks();
|
|
6339
6569
|
|
|
6340
|
-
// Enable ONLY the custom subtitle system
|
|
6570
|
+
// 2. Enable ONLY the custom WebView-safe subtitle system
|
|
6341
6571
|
var success = this.enableCustomSubtitleTrack(trackIndex);
|
|
6342
6572
|
|
|
6343
6573
|
if (success) {
|
|
6344
6574
|
this.currentSubtitleTrack = this.textTracks[trackIndex].track;
|
|
6345
6575
|
this.subtitlesEnabled = true;
|
|
6346
6576
|
|
|
6347
|
-
//
|
|
6577
|
+
// 3. Kill the native browser track to avoid double rendering or crashes
|
|
6348
6578
|
if (this.video.textTracks && this.video.textTracks[trackIndex]) {
|
|
6349
|
-
this.video.textTracks[trackIndex].mode = 'disabled';
|
|
6579
|
+
this.video.textTracks[trackIndex].mode = 'disabled';
|
|
6350
6580
|
}
|
|
6351
6581
|
|
|
6352
6582
|
this.updateSubtitlesButton();
|
|
6353
6583
|
this.populateSubtitlesMenu();
|
|
6354
6584
|
|
|
6585
|
+
if (typeof this.updateSettingsMenuUI === 'function') {
|
|
6586
|
+
this.updateSettingsMenuUI();
|
|
6587
|
+
}
|
|
6588
|
+
|
|
6355
6589
|
if (this.options.debug) {
|
|
6356
|
-
console.log('✅ Custom subtitles
|
|
6590
|
+
console.log('✅ Custom UI subtitles strictly enforced for track', trackIndex);
|
|
6357
6591
|
}
|
|
6358
6592
|
|
|
6359
|
-
// Trigger subtitle change event
|
|
6360
6593
|
this.triggerEvent('subtitlechange', {
|
|
6361
6594
|
enabled: true,
|
|
6362
6595
|
trackIndex: trackIndex,
|
|
6363
6596
|
trackLabel: this.textTracks[trackIndex].label,
|
|
6364
6597
|
trackLanguage: this.textTracks[trackIndex].language
|
|
6365
6598
|
});
|
|
6366
|
-
} else {
|
|
6367
|
-
if (this.options.debug) {
|
|
6368
|
-
console.error('❌ Failed to enable custom subtitles for track', trackIndex);
|
|
6369
|
-
}
|
|
6370
6599
|
}
|
|
6371
6600
|
}
|
|
6372
6601
|
|
|
@@ -6380,6 +6609,11 @@ disableSubtitles() {
|
|
|
6380
6609
|
this.updateSubtitlesButton();
|
|
6381
6610
|
this.populateSubtitlesMenu();
|
|
6382
6611
|
|
|
6612
|
+
// 👉 L'AGGIUNTA FONDAMENTALE: Sincronizza il menu Settings!
|
|
6613
|
+
if (typeof this.updateSettingsMenuUI === 'function') {
|
|
6614
|
+
this.updateSettingsMenuUI();
|
|
6615
|
+
}
|
|
6616
|
+
|
|
6383
6617
|
if (this.options.debug) console.log('📝 Subtitles disabled');
|
|
6384
6618
|
|
|
6385
6619
|
this.triggerEvent('subtitlechange', {
|
|
@@ -8634,48 +8868,123 @@ setDashQuality(qualityIndex) {
|
|
|
8634
8868
|
// CLASS METHODS - Will be placed INSIDE the class
|
|
8635
8869
|
// ===================================================================
|
|
8636
8870
|
|
|
8637
|
-
/**
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
'beforeInit': [],
|
|
8648
|
-
'afterInit': [],
|
|
8649
|
-
'beforePlay': [],
|
|
8650
|
-
'afterPlay': [],
|
|
8651
|
-
'beforePause': [],
|
|
8652
|
-
'afterPause': [],
|
|
8653
|
-
'beforeQualityChange': [],
|
|
8654
|
-
'afterQualityChange': [],
|
|
8655
|
-
'beforeDestroy': [],
|
|
8656
|
-
'afterDestroy': []
|
|
8657
|
-
};
|
|
8871
|
+
/**
|
|
8872
|
+
* Initialize plugin system for player instance
|
|
8873
|
+
*/
|
|
8874
|
+
initializePluginSystem() {
|
|
8875
|
+
this.plugins = {};
|
|
8876
|
+
this.pluginHooks = {
|
|
8877
|
+
'beforeInit': [], 'afterInit': [], 'beforePlay': [], 'afterPlay': [],
|
|
8878
|
+
'beforePause': [], 'afterPause': [], 'beforeQualityChange': [],
|
|
8879
|
+
'afterQualityChange': [], 'beforeDestroy': [], 'afterDestroy': []
|
|
8880
|
+
};
|
|
8658
8881
|
|
|
8659
|
-
|
|
8660
|
-
|
|
8882
|
+
if (this.options.debug) console.log('🔌 Plugin system initialized');
|
|
8883
|
+
|
|
8884
|
+
// Load plugins sequentially if present, otherwise finalize player immediately
|
|
8885
|
+
if (this.options.plugins && typeof this.options.plugins === 'object') {
|
|
8886
|
+
this.loadPluginsSequentially(this.options.plugins);
|
|
8887
|
+
} else {
|
|
8888
|
+
this.finalizePlayerSetup();
|
|
8889
|
+
}
|
|
8661
8890
|
}
|
|
8662
8891
|
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8892
|
+
/**
|
|
8893
|
+
* Loads plugins one by one based on their 'order' option.
|
|
8894
|
+
* Prevents any plugin from loading until the previous blocking plugin finishes.
|
|
8895
|
+
* @param {Object} pluginsConfig
|
|
8896
|
+
*/
|
|
8897
|
+
async loadPluginsSequentially(pluginsConfig) {
|
|
8898
|
+
// 1. Sort plugins by order (default 99 means load last)
|
|
8899
|
+
const pluginList = Object.keys(pluginsConfig).map(pluginName => {
|
|
8900
|
+
return {
|
|
8901
|
+
name: pluginName,
|
|
8902
|
+
config: pluginsConfig[pluginName],
|
|
8903
|
+
order: pluginsConfig[pluginName] && pluginsConfig[pluginName].order !== undefined
|
|
8904
|
+
? pluginsConfig[pluginName].order : 99
|
|
8905
|
+
};
|
|
8906
|
+
}).sort((a, b) => a.order - b.order);
|
|
8907
|
+
|
|
8908
|
+
// 2. Process each plugin sequentially
|
|
8909
|
+
for (const pluginData of pluginList) {
|
|
8910
|
+
if (this.options.debug) console.log(`🔌 Loading plugin: ${pluginData.name} (order: ${pluginData.order})`);
|
|
8911
|
+
|
|
8912
|
+
// Instantiate the plugin NOW (plugins with higher order do not exist yet)
|
|
8913
|
+
const pluginInstance = this.usePlugin(pluginData.name, pluginData.config);
|
|
8914
|
+
|
|
8915
|
+
// If the plugin has an async barrier, pause the ENTIRE loop
|
|
8916
|
+
if (pluginInstance && typeof pluginInstance.waitForCompletion === 'function') {
|
|
8917
|
+
if (this.options.debug) console.log(`🔌 Pausing queue. Waiting for ${pluginData.name} to complete...`);
|
|
8918
|
+
await pluginInstance.waitForCompletion();
|
|
8919
|
+
if (this.options.debug) console.log(`🔌 ${pluginData.name} completed. Resuming queue.`);
|
|
8920
|
+
}
|
|
8666
8921
|
}
|
|
8922
|
+
|
|
8923
|
+
// 3. All plugins processed. Now we can safely boot the main HTML5 player
|
|
8924
|
+
this.finalizePlayerSetup();
|
|
8667
8925
|
}
|
|
8668
8926
|
|
|
8669
|
-
/**
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8927
|
+
/**
|
|
8928
|
+
* Boots up the core HTML5 player features after all blocking plugins have finished.
|
|
8929
|
+
* This function ensures that the main player components (like poster, sources, and watermark)
|
|
8930
|
+
* are initialized only after the plugin queue (including the loader) is fully resolved.
|
|
8931
|
+
*/
|
|
8932
|
+
finalizePlayerSetup() {
|
|
8933
|
+
if (this.options.debug) {
|
|
8934
|
+
console.log('🚀 Finalizing main player setup...');
|
|
8935
|
+
}
|
|
8936
|
+
|
|
8937
|
+
// Restore the autoplay intent BEFORE initializing the poster.
|
|
8938
|
+
// This ensures initializePoster() knows we are autoplaying and won't flash the image.
|
|
8939
|
+
if (this.savedAutoplayIntent) {
|
|
8940
|
+
this.options.autoplay = true;
|
|
8941
|
+
}
|
|
8942
|
+
|
|
8943
|
+
// Trigger the global 'playerready' event.
|
|
8944
|
+
// Firing it ONLY NOW guarantees that third-party plugins (like YouTube)
|
|
8945
|
+
// safely instantiate their iframes only after the loader has finished its countdown.
|
|
8946
|
+
this.markPlayerReady();
|
|
8947
|
+
|
|
8948
|
+
this.restoreSourcesAsync();
|
|
8949
|
+
this.initializeSubtitles();
|
|
8950
|
+
this.initializeQualityMonitoring();
|
|
8951
|
+
this.initializeResolution();
|
|
8952
|
+
this.initializeChapters();
|
|
8953
|
+
|
|
8954
|
+
// Prevent the final poster flash if a third-party iframe (YouTube/Vimeo/Twitch) is active.
|
|
8955
|
+
const hasExternalIframe = this.options.plugins && (
|
|
8956
|
+
this.options.plugins.youtube ||
|
|
8957
|
+
this.options.plugins.vimeo ||
|
|
8958
|
+
this.options.plugins.twitch
|
|
8959
|
+
);
|
|
8960
|
+
|
|
8961
|
+
if (!hasExternalIframe) {
|
|
8962
|
+
this.initializePoster();
|
|
8963
|
+
}
|
|
8964
|
+
|
|
8965
|
+
this.initializeWatermark();
|
|
8966
|
+
|
|
8967
|
+
// Check if external subtitle tracks were provided and the array is not empty
|
|
8968
|
+
if (this.options.externalSubtitleTracks && this.options.externalSubtitleTracks.length > 0) {
|
|
8969
|
+
this.injectExternalSubtitleTracks(this.options.externalSubtitleTracks);
|
|
8970
|
+
}
|
|
8971
|
+
|
|
8972
|
+
// 🚀 Restore Autoplay ONLY AFTER everything is fully loaded and the queue is unlocked
|
|
8973
|
+
if (this.savedAutoplayIntent) {
|
|
8974
|
+
if (this.options.debug) console.log('🚀 Triggering delayed autoplay...');
|
|
8975
|
+
|
|
8976
|
+
if (this.video) {
|
|
8977
|
+
this.video.setAttribute('autoplay', '');
|
|
8978
|
+
this.video.style.opacity = '1';
|
|
8979
|
+
this.video.style.visibility = '';
|
|
8678
8980
|
}
|
|
8981
|
+
|
|
8982
|
+
setTimeout(() => {
|
|
8983
|
+
this.play();
|
|
8984
|
+
}, 300);
|
|
8985
|
+
} else if (this.video) {
|
|
8986
|
+
this.video.style.opacity = '1';
|
|
8987
|
+
this.video.style.visibility = '';
|
|
8679
8988
|
}
|
|
8680
8989
|
}
|
|
8681
8990
|
|