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/dist/myetv-player.js
CHANGED
|
@@ -466,7 +466,6 @@ constructor(videoElement, options = {}) {
|
|
|
466
466
|
this.currentQualityIndex = 0;
|
|
467
467
|
this.qualities = [];
|
|
468
468
|
this.originalSources = [];
|
|
469
|
-
this.setupMenuToggles(); // Initialize menu toggle system
|
|
470
469
|
this.isPiPSupported = this.checkPiPSupport();
|
|
471
470
|
this.seekTooltip = null;
|
|
472
471
|
this.titleOverlay = null;
|
|
@@ -578,6 +577,7 @@ constructor(videoElement, options = {}) {
|
|
|
578
577
|
this.interceptAutoLoading();
|
|
579
578
|
this.createPlayerStructure();
|
|
580
579
|
this.initializeElements();
|
|
580
|
+
this.setupMenuToggles(); // Initialize menu toggle system
|
|
581
581
|
// audio player adaptation
|
|
582
582
|
this.adaptToAudioFile = function () {
|
|
583
583
|
if (this.options.audiofile) {
|
|
@@ -1194,74 +1194,84 @@ initializeElements() {
|
|
|
1194
1194
|
this.speedMenu = this.controls?.querySelector('.speed-menu');
|
|
1195
1195
|
this.qualityMenu = this.controls?.querySelector('.quality-menu');
|
|
1196
1196
|
this.subtitlesMenu = this.controls?.querySelector('.subtitles-menu');
|
|
1197
|
+
// Apply seek handle shape from options
|
|
1198
|
+
if (this.progressHandle && this.options.seekHandleShape) {
|
|
1199
|
+
this.setSeekHandleShape(this.options.seekHandleShape);
|
|
1200
|
+
}
|
|
1197
1201
|
}
|
|
1198
1202
|
|
|
1199
1203
|
// Generic method to close all active menus (works with plugins too)
|
|
1200
1204
|
closeAllMenus() {
|
|
1201
|
-
|
|
1202
|
-
const allMenus = this.controls?.querySelectorAll('[class*="-menu"].active');
|
|
1203
|
-
allMenus?.forEach(menu => {
|
|
1204
|
-
menu.classList.remove('active');
|
|
1205
|
-
});
|
|
1205
|
+
if (!this.controls) return;
|
|
1206
1206
|
|
|
1207
|
-
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1207
|
+
const menus = this.controls.querySelectorAll('.speed-menu, .quality-menu, .subtitles-menu, .settings-menu');
|
|
1208
|
+
const buttons = this.controls.querySelectorAll('.control-btn');
|
|
1209
|
+
|
|
1210
|
+
menus.forEach(menu => menu.classList.remove('active'));
|
|
1211
|
+
buttons.forEach(btn => btn.classList.remove('active'));
|
|
1212
|
+
|
|
1213
|
+
this.currentOpenMenu = null;
|
|
1214
|
+
|
|
1215
|
+
if (this.options.debug) {
|
|
1216
|
+
console.log('All menus closed');
|
|
1217
|
+
}
|
|
1212
1218
|
}
|
|
1213
1219
|
|
|
1214
1220
|
// Generic menu toggle setup (works with core menus and plugin menus)
|
|
1215
1221
|
setupMenuToggles() {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
const menu = this.controls.querySelector('.' + menuName);
|
|
1233
|
-
if (menu) {
|
|
1234
|
-
menuClass = menuName;
|
|
1235
|
-
break;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1222
|
+
if (!this.controls) return;
|
|
1223
|
+
|
|
1224
|
+
this.currentOpenMenu = null;
|
|
1225
|
+
|
|
1226
|
+
this.controls.addEventListener('click', (e) => {
|
|
1227
|
+
const button = e.target.closest('.control-btn');
|
|
1228
|
+
if (!button) return;
|
|
1229
|
+
|
|
1230
|
+
const buttonClasses = Array.from(button.classList);
|
|
1231
|
+
let menuElement = null;
|
|
1232
|
+
|
|
1233
|
+
for (const cls of buttonClasses) {
|
|
1234
|
+
if (cls.endsWith('-btn')) {
|
|
1235
|
+
const menuClass = cls.replace('-btn', '-menu');
|
|
1236
|
+
menuElement = this.controls.querySelector(`.${menuClass}`);
|
|
1237
|
+
if (menuElement) break;
|
|
1238
1238
|
}
|
|
1239
|
+
}
|
|
1239
1240
|
|
|
1240
|
-
|
|
1241
|
+
if (!menuElement) return;
|
|
1241
1242
|
|
|
1242
|
-
|
|
1243
|
+
e.stopPropagation();
|
|
1244
|
+
e.preventDefault();
|
|
1243
1245
|
|
|
1244
|
-
|
|
1245
|
-
const menu = this.controls.querySelector('.' + menuClass);
|
|
1246
|
-
const isOpen = menu.classList.contains('active');
|
|
1246
|
+
const isOpen = menuElement.classList.contains('active');
|
|
1247
1247
|
|
|
1248
|
-
|
|
1249
|
-
this.closeAllMenus();
|
|
1248
|
+
this.closeAllMenus();
|
|
1250
1249
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1250
|
+
if (!isOpen) {
|
|
1251
|
+
menuElement.classList.add('active');
|
|
1252
|
+
button.classList.add('active');
|
|
1253
|
+
this.currentOpenMenu = menuElement;
|
|
1254
|
+
if (this.options.debug) {
|
|
1255
|
+
console.log('Menu opened:', menuElement.className);
|
|
1255
1256
|
}
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1257
|
+
} else {
|
|
1258
|
+
this.currentOpenMenu = null;
|
|
1259
|
+
if (this.options.debug) {
|
|
1260
|
+
console.log('Menu closed:', menuElement.className);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1258
1264
|
|
|
1259
|
-
// Close menus when clicking outside controls
|
|
1260
1265
|
document.addEventListener('click', (e) => {
|
|
1261
|
-
if (!this.controls
|
|
1266
|
+
if (!this.controls) return;
|
|
1267
|
+
if (!this.controls.contains(e.target)) {
|
|
1262
1268
|
this.closeAllMenus();
|
|
1263
1269
|
}
|
|
1264
1270
|
});
|
|
1271
|
+
|
|
1272
|
+
if (this.options.debug) {
|
|
1273
|
+
console.log('✅ Menu toggle system initialized (click-based, auto-close)');
|
|
1274
|
+
}
|
|
1265
1275
|
}
|
|
1266
1276
|
|
|
1267
1277
|
updateVolumeSliderVisual() {
|
|
@@ -1572,9 +1582,11 @@ updateBuffer() {
|
|
|
1572
1582
|
}
|
|
1573
1583
|
|
|
1574
1584
|
startSeeking(e) {
|
|
1585
|
+
if (e.cancelable) e.preventDefault();
|
|
1575
1586
|
if (this.isChangingQuality) return;
|
|
1576
1587
|
|
|
1577
1588
|
this.isUserSeeking = true;
|
|
1589
|
+
this.progressContainer.classList.add('seeking');
|
|
1578
1590
|
this.seek(e);
|
|
1579
1591
|
e.preventDefault();
|
|
1580
1592
|
|
|
@@ -1586,6 +1598,7 @@ startSeeking(e) {
|
|
|
1586
1598
|
}
|
|
1587
1599
|
|
|
1588
1600
|
continueSeeking(e) {
|
|
1601
|
+
if (e.cancelable) e.preventDefault();
|
|
1589
1602
|
if (this.isUserSeeking && !this.isChangingQuality) {
|
|
1590
1603
|
this.seek(e);
|
|
1591
1604
|
}
|
|
@@ -1593,9 +1606,13 @@ continueSeeking(e) {
|
|
|
1593
1606
|
|
|
1594
1607
|
endSeeking() {
|
|
1595
1608
|
this.isUserSeeking = false;
|
|
1609
|
+
this.progressContainer.classList.remove('seeking');
|
|
1596
1610
|
}
|
|
1597
1611
|
|
|
1598
1612
|
seek(e) {
|
|
1613
|
+
if (e.cancelable) {
|
|
1614
|
+
e.preventDefault();
|
|
1615
|
+
}
|
|
1599
1616
|
if (!this.video || !this.progressContainer || !this.progressFilled || !this.progressHandle || this.isChangingQuality) return;
|
|
1600
1617
|
|
|
1601
1618
|
const rect = this.progressContainer.getBoundingClientRect();
|
|
@@ -2016,7 +2033,7 @@ bindPosterEvents() {
|
|
|
2016
2033
|
// Hide poster when video is loading/playing
|
|
2017
2034
|
this.video.addEventListener('playing', () => {
|
|
2018
2035
|
this.hidePoster();
|
|
2019
|
-
|
|
2036
|
+
});
|
|
2020
2037
|
|
|
2021
2038
|
// Show poster on load if not autoplay
|
|
2022
2039
|
if (!this.options.autoplay) {
|
|
@@ -2460,6 +2477,7 @@ addEventListener(eventType, callback) {
|
|
|
2460
2477
|
// Playback events
|
|
2461
2478
|
this.video.addEventListener('playing', () => {
|
|
2462
2479
|
this.hideLoading();
|
|
2480
|
+
this.closeAllMenus();
|
|
2463
2481
|
// Trigger playing event - video is now actually playing
|
|
2464
2482
|
this.triggerEvent('playing', {
|
|
2465
2483
|
currentTime: this.getCurrentTime(),
|
|
@@ -2684,6 +2702,10 @@ addEventListener(eventType, callback) {
|
|
|
2684
2702
|
// Mouse events (desktop)
|
|
2685
2703
|
this.progressContainer.addEventListener('click', (e) => this.seek(e));
|
|
2686
2704
|
this.progressContainer.addEventListener('mousedown', (e) => this.startSeeking(e));
|
|
2705
|
+
if (this.progressHandle) {
|
|
2706
|
+
this.progressHandle.addEventListener('mousedown', this.startSeeking.bind(this));
|
|
2707
|
+
this.progressHandle.addEventListener('touchstart', this.startSeeking.bind(this), { passive: false });
|
|
2708
|
+
}
|
|
2687
2709
|
|
|
2688
2710
|
// Touch events (mobile)
|
|
2689
2711
|
this.progressContainer.addEventListener('touchstart', (e) => {
|
|
@@ -3106,13 +3128,13 @@ createControls() {
|
|
|
3106
3128
|
const controlsHTML = `
|
|
3107
3129
|
<div class="controls" id="${controlsId}">
|
|
3108
3130
|
<div class="progress-container">
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3131
|
+
<div class="progress-bar">
|
|
3132
|
+
<div class="progress-buffer"></div>
|
|
3133
|
+
<div class="progress-filled"></div>
|
|
3134
|
+
</div>
|
|
3135
|
+
<div class="progress-handle progress-handle-${this.options.seekHandleShape}"></div> <!-- ✅ Fuori da progress-bar -->
|
|
3136
|
+
${this.options.showSeekTooltip ? '<div class="seek-tooltip">0:00</div>' : ''}
|
|
3137
|
+
</div>
|
|
3116
3138
|
|
|
3117
3139
|
<div class="controls-main">
|
|
3118
3140
|
<div class="controls-left">
|
|
@@ -4805,10 +4827,10 @@ createCustomSubtitleOverlay() {
|
|
|
4805
4827
|
'bottom: 80px;' +
|
|
4806
4828
|
'left: 50%;' +
|
|
4807
4829
|
'transform: translateX(-50%);' +
|
|
4808
|
-
'z-index:
|
|
4830
|
+
'z-index: 999;' +
|
|
4809
4831
|
'color: white;' +
|
|
4810
4832
|
'font-family: Arial, sans-serif;' +
|
|
4811
|
-
'font-size: clamp(12px, 4vw, 18px);' +
|
|
4833
|
+
'font-size: clamp(12px, 4vw, 18px);' +
|
|
4812
4834
|
'font-weight: bold;' +
|
|
4813
4835
|
'text-align: center;' +
|
|
4814
4836
|
'text-shadow: 2px 2px 4px rgba(0, 0, 0, 1);' +
|
|
@@ -4835,52 +4857,29 @@ createCustomSubtitleOverlay() {
|
|
|
4835
4857
|
if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
|
|
4836
4858
|
}
|
|
4837
4859
|
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
var trackElements = document.querySelectorAll('track[kind="subtitles"], track[kind="captions"]');
|
|
4841
|
-
var loadPromises = [];
|
|
4842
|
-
|
|
4843
|
-
if (this.options.debug) console.log('📥 Loading ' + trackElements.length + ' subtitle files...');
|
|
4844
|
-
|
|
4845
|
-
for (var i = 0; i < trackElements.length; i++) {
|
|
4846
|
-
var track = trackElements[i];
|
|
4847
|
-
|
|
4848
|
-
(function (trackElement, index) {
|
|
4849
|
-
var promise = fetch(trackElement.src)
|
|
4850
|
-
.then(function (response) {
|
|
4851
|
-
return response.text();
|
|
4852
|
-
})
|
|
4853
|
-
.then(function (srtText) {
|
|
4854
|
-
var subtitles = self.parseCustomSRT(srtText);
|
|
4855
|
-
self.customSubtitles.push({
|
|
4856
|
-
label: trackElement.label || 'Track ' + (index + 1),
|
|
4857
|
-
language: trackElement.srclang || 'unknown',
|
|
4858
|
-
subtitles: subtitles
|
|
4859
|
-
});
|
|
4860
|
+
customTimeToSeconds(timeString) {
|
|
4861
|
+
if (!timeString) return 0;
|
|
4860
4862
|
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
}
|
|
4864
|
-
})
|
|
4865
|
-
.catch(function (error) {
|
|
4866
|
-
if (self.options.debug) {
|
|
4867
|
-
console.error('❌ Error loading ' + trackElement.src + ':', error);
|
|
4868
|
-
}
|
|
4869
|
-
});
|
|
4863
|
+
var parts = timeString.split(',');
|
|
4864
|
+
if (parts.length !== 2) return 0;
|
|
4870
4865
|
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
}
|
|
4866
|
+
var time = parts[0];
|
|
4867
|
+
var millis = parts[1];
|
|
4874
4868
|
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
console.log('✅ All custom subtitle tracks loaded');
|
|
4878
|
-
}
|
|
4869
|
+
var timeParts = time.split(':');
|
|
4870
|
+
if (timeParts.length !== 3) return 0;
|
|
4879
4871
|
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4872
|
+
var hours = parseInt(timeParts[0], 10);
|
|
4873
|
+
var minutes = parseInt(timeParts[1], 10);
|
|
4874
|
+
var seconds = parseInt(timeParts[2], 10);
|
|
4875
|
+
var milliseconds = parseInt(millis, 10);
|
|
4876
|
+
|
|
4877
|
+
if (isNaN(hours) || isNaN(minutes) || isNaN(seconds) || isNaN(milliseconds)) {
|
|
4878
|
+
console.error('❌ customTimeToSeconds failed for:', timeString);
|
|
4879
|
+
return 0;
|
|
4880
|
+
}
|
|
4881
|
+
|
|
4882
|
+
return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
|
|
4884
4883
|
}
|
|
4885
4884
|
|
|
4886
4885
|
parseCustomSRT(srtText) {
|
|
@@ -4899,9 +4898,9 @@ parseCustomSRT(srtText) {
|
|
|
4899
4898
|
if (timeMatch) {
|
|
4900
4899
|
var startTime = this.customTimeToSeconds(timeMatch[1]);
|
|
4901
4900
|
var endTime = this.customTimeToSeconds(timeMatch[2]);
|
|
4902
|
-
var text =
|
|
4901
|
+
var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
|
|
4903
4902
|
|
|
4904
|
-
if (text.length > 0 && startTime < endTime) {
|
|
4903
|
+
if (text && text.length > 0 && startTime < endTime) {
|
|
4905
4904
|
subtitles.push({
|
|
4906
4905
|
start: startTime,
|
|
4907
4906
|
end: endTime,
|
|
@@ -4912,20 +4911,97 @@ parseCustomSRT(srtText) {
|
|
|
4912
4911
|
}
|
|
4913
4912
|
}
|
|
4914
4913
|
|
|
4914
|
+
if (this.options.debug) console.log('✅ Parsed ' + subtitles.length + ' subtitles');
|
|
4915
4915
|
return subtitles;
|
|
4916
4916
|
}
|
|
4917
4917
|
|
|
4918
|
-
|
|
4919
|
-
var
|
|
4920
|
-
var
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4918
|
+
loadCustomSubtitleTracks() {
|
|
4919
|
+
var self = this;
|
|
4920
|
+
var tracks = this.video.querySelectorAll('track[kind="subtitles"]');
|
|
4921
|
+
if (tracks.length === 0) return;
|
|
4922
|
+
|
|
4923
|
+
tracks.forEach(function (track, index) {
|
|
4924
|
+
var src = track.getAttribute('src');
|
|
4925
|
+
var label = track.getAttribute('label') || 'Unknown';
|
|
4926
|
+
var srclang = track.getAttribute('srclang') || '';
|
|
4927
|
+
|
|
4928
|
+
// CREA L'OGGETTO PRIMA E AGGIUNGILO SUBITO
|
|
4929
|
+
var trackObj = {
|
|
4930
|
+
label: label,
|
|
4931
|
+
language: srclang,
|
|
4932
|
+
subtitles: [],
|
|
4933
|
+
trackIndex: index
|
|
4934
|
+
};
|
|
4935
|
+
self.customSubtitles.push(trackObj);
|
|
4936
|
+
|
|
4937
|
+
fetch(src)
|
|
4938
|
+
.then(function (response) {
|
|
4939
|
+
return response.text();
|
|
4940
|
+
})
|
|
4941
|
+
.then(function (srtText) {
|
|
4942
|
+
var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
4943
|
+
var blocks = normalizedText.trim().split('\n\n');
|
|
4944
|
+
|
|
4945
|
+
for (var i = 0; i < blocks.length; i++) {
|
|
4946
|
+
var block = blocks[i].trim();
|
|
4947
|
+
if (!block) continue;
|
|
4948
|
+
var lines = block.split('\n');
|
|
4949
|
+
|
|
4950
|
+
if (lines.length >= 3) {
|
|
4951
|
+
var timeLine = lines[1].trim();
|
|
4952
|
+
var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
|
|
4953
|
+
|
|
4954
|
+
if (timeMatch) {
|
|
4955
|
+
var startParts = timeMatch[1].split(',');
|
|
4956
|
+
var startTimeParts = startParts[0].split(':');
|
|
4957
|
+
var startTime = parseInt(startTimeParts[0], 10) * 3600 + parseInt(startTimeParts[1], 10) * 60 + parseInt(startTimeParts[2], 10) + parseInt(startParts[1], 10) / 1000;
|
|
4958
|
+
|
|
4959
|
+
var endParts = timeMatch[2].split(',');
|
|
4960
|
+
var endTimeParts = endParts[0].split(':');
|
|
4961
|
+
var endTime = parseInt(endTimeParts[0], 10) * 3600 + parseInt(endTimeParts[1], 10) * 60 + parseInt(endTimeParts[2], 10) + parseInt(endParts[1], 10) / 1000;
|
|
4962
|
+
|
|
4963
|
+
var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
|
|
4964
|
+
|
|
4965
|
+
if (text && text.length > 0 && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
|
|
4966
|
+
trackObj.subtitles.push({
|
|
4967
|
+
start: startTime,
|
|
4968
|
+
end: endTime,
|
|
4969
|
+
text: text
|
|
4970
|
+
});
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4927
4975
|
|
|
4928
|
-
|
|
4976
|
+
if (self.options.debug) {
|
|
4977
|
+
console.log('✅ Loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
|
|
4978
|
+
}
|
|
4979
|
+
})
|
|
4980
|
+
.catch(function (error) {
|
|
4981
|
+
console.error('❌ Error loading ' + label + ':', error);
|
|
4982
|
+
});
|
|
4983
|
+
});
|
|
4984
|
+
}
|
|
4985
|
+
|
|
4986
|
+
sanitizeSubtitleText(text) {
|
|
4987
|
+
if (!text) return '';
|
|
4988
|
+
|
|
4989
|
+
// Remove HTML tags
|
|
4990
|
+
var sanitized = text.replace(/<[^>]*>/g, '');
|
|
4991
|
+
|
|
4992
|
+
// Remove styling tags common in SRT files
|
|
4993
|
+
sanitized = sanitized.replace(/{\\.*?}/g, '');
|
|
4994
|
+
sanitized = sanitized.replace(/\\N/g, '\n');
|
|
4995
|
+
|
|
4996
|
+
// Clean up multiple spaces
|
|
4997
|
+
sanitized = sanitized.replace(/\s+/g, ' ').trim();
|
|
4998
|
+
|
|
4999
|
+
// Decode HTML entities if present
|
|
5000
|
+
var tempDiv = document.createElement('div');
|
|
5001
|
+
tempDiv.innerHTML = sanitized;
|
|
5002
|
+
sanitized = tempDiv.textContent || tempDiv.innerText || sanitized;
|
|
5003
|
+
|
|
5004
|
+
return sanitized;
|
|
4929
5005
|
}
|
|
4930
5006
|
|
|
4931
5007
|
enableCustomSubtitleTrack(trackIndex) {
|
|
@@ -5018,28 +5094,39 @@ detectTextTracks() {
|
|
|
5018
5094
|
enableSubtitleTrack(trackIndex) {
|
|
5019
5095
|
if (trackIndex < 0 || trackIndex >= this.textTracks.length) return;
|
|
5020
5096
|
|
|
5097
|
+
// Disable all tracks first
|
|
5021
5098
|
this.disableAllTracks();
|
|
5022
5099
|
|
|
5100
|
+
// Enable ONLY the custom subtitle system (not native browser)
|
|
5023
5101
|
var success = this.enableCustomSubtitleTrack(trackIndex);
|
|
5024
5102
|
|
|
5025
5103
|
if (success) {
|
|
5026
5104
|
this.currentSubtitleTrack = this.textTracks[trackIndex].track;
|
|
5027
5105
|
this.subtitlesEnabled = true;
|
|
5028
5106
|
|
|
5107
|
+
// Make sure native tracks stay DISABLED
|
|
5108
|
+
if (this.video.textTracks && this.video.textTracks[trackIndex]) {
|
|
5109
|
+
this.video.textTracks[trackIndex].mode = 'disabled'; // Keep native disabled
|
|
5110
|
+
}
|
|
5111
|
+
|
|
5029
5112
|
this.updateSubtitlesButton();
|
|
5030
5113
|
this.populateSubtitlesMenu();
|
|
5031
5114
|
|
|
5032
5115
|
if (this.options.debug) {
|
|
5033
|
-
console.log('✅
|
|
5116
|
+
console.log('✅ Custom subtitles enabled:', this.textTracks[trackIndex].label);
|
|
5034
5117
|
}
|
|
5035
5118
|
|
|
5036
|
-
// Trigger
|
|
5119
|
+
// Trigger subtitle change event
|
|
5037
5120
|
this.triggerEvent('subtitlechange', {
|
|
5038
5121
|
enabled: true,
|
|
5039
5122
|
trackIndex: trackIndex,
|
|
5040
5123
|
trackLabel: this.textTracks[trackIndex].label,
|
|
5041
5124
|
trackLanguage: this.textTracks[trackIndex].language
|
|
5042
5125
|
});
|
|
5126
|
+
} else {
|
|
5127
|
+
if (this.options.debug) {
|
|
5128
|
+
console.error('❌ Failed to enable custom subtitles for track', trackIndex);
|
|
5129
|
+
}
|
|
5043
5130
|
}
|
|
5044
5131
|
}
|
|
5045
5132
|
|
|
@@ -5062,11 +5149,15 @@ disableSubtitles() {
|
|
|
5062
5149
|
}
|
|
5063
5150
|
|
|
5064
5151
|
disableAllTracks() {
|
|
5065
|
-
if (this.video.textTracks)
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5152
|
+
if (!this.video || !this.video.textTracks) return;
|
|
5153
|
+
|
|
5154
|
+
// Disable all native tracks
|
|
5155
|
+
for (var i = 0; i < this.video.textTracks.length; i++) {
|
|
5156
|
+
this.video.textTracks[i].mode = 'hidden';
|
|
5069
5157
|
}
|
|
5158
|
+
|
|
5159
|
+
// Also disable custom subtitles
|
|
5160
|
+
this.disableCustomSubtitles();
|
|
5070
5161
|
}
|
|
5071
5162
|
|
|
5072
5163
|
getAvailableSubtitles() {
|
|
@@ -5107,11 +5198,11 @@ updateSubtitlesButton() {
|
|
|
5107
5198
|
var subtitlesBtn = this.controls && this.controls.querySelector('.subtitles-btn');
|
|
5108
5199
|
if (!subtitlesBtn) return;
|
|
5109
5200
|
|
|
5201
|
+
subtitlesBtn.classList.remove('active');
|
|
5202
|
+
|
|
5110
5203
|
if (this.subtitlesEnabled) {
|
|
5111
|
-
subtitlesBtn.classList.add('active');
|
|
5112
5204
|
subtitlesBtn.title = this.t('subtitlesdisable');
|
|
5113
5205
|
} else {
|
|
5114
|
-
subtitlesBtn.classList.remove('active');
|
|
5115
5206
|
subtitlesBtn.title = this.t('subtitlesenable');
|
|
5116
5207
|
}
|
|
5117
5208
|
}
|
|
@@ -5147,32 +5238,61 @@ updateSubtitlesUI() {
|
|
|
5147
5238
|
bindSubtitleEvents() {
|
|
5148
5239
|
var self = this;
|
|
5149
5240
|
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5241
|
+
if (this.video.textTracks) {
|
|
5242
|
+
this.isChangingSubtitles = false; // flag to prevent loops
|
|
5243
|
+
|
|
5244
|
+
this.video.textTracks.addEventListener('change', function () {
|
|
5245
|
+
// ignore changes initiated by the player itself
|
|
5246
|
+
if (self.isChangingSubtitles) {
|
|
5247
|
+
return;
|
|
5248
|
+
}
|
|
5249
|
+
|
|
5250
|
+
// only update ui
|
|
5251
|
+
self.updateSubtitlesUI();
|
|
5155
5252
|
});
|
|
5156
5253
|
}
|
|
5157
5254
|
|
|
5255
|
+
// Add timeupdate listener for custom subtitle display
|
|
5256
|
+
this.video.addEventListener('timeupdate', () => {
|
|
5257
|
+
if (this.customSubtitlesEnabled) {
|
|
5258
|
+
this.updateCustomSubtitleDisplay();
|
|
5259
|
+
}
|
|
5260
|
+
});
|
|
5261
|
+
|
|
5262
|
+
// Menu click events
|
|
5158
5263
|
var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
|
|
5159
5264
|
if (subtitlesMenu) {
|
|
5160
5265
|
subtitlesMenu.addEventListener('click', function (e) {
|
|
5161
|
-
|
|
5266
|
+
var option = e.target.closest('.subtitles-option');
|
|
5267
|
+
if (!option) return;
|
|
5268
|
+
|
|
5269
|
+
self.isChangingSubtitles = true; // active flag
|
|
5270
|
+
|
|
5271
|
+
var trackIndex = option.getAttribute('data-track');
|
|
5272
|
+
if (trackIndex === 'off') {
|
|
5273
|
+
self.disableSubtitles();
|
|
5274
|
+
} else {
|
|
5275
|
+
self.enableSubtitleTrack(parseInt(trackIndex));
|
|
5276
|
+
}
|
|
5277
|
+
|
|
5278
|
+
setTimeout(function () {
|
|
5279
|
+
self.isChangingSubtitles = false; // disable flag
|
|
5280
|
+
}, 100);
|
|
5162
5281
|
});
|
|
5163
5282
|
}
|
|
5164
5283
|
}
|
|
5165
5284
|
|
|
5166
5285
|
handleSubtitlesMenuClick(e) {
|
|
5167
|
-
|
|
5286
|
+
var option = e.target.closest('.subtitles-option');
|
|
5287
|
+
if (!option) return; // This prevents button clicks from toggling
|
|
5168
5288
|
|
|
5169
|
-
var
|
|
5289
|
+
var trackIndex = option.getAttribute('data-track');
|
|
5170
5290
|
|
|
5171
|
-
if (
|
|
5291
|
+
if (trackIndex === 'off') {
|
|
5172
5292
|
this.disableSubtitles();
|
|
5173
5293
|
} else {
|
|
5174
|
-
|
|
5175
|
-
this.enableSubtitleTrack(trackIndex);
|
|
5294
|
+
// Don't check for 'toggle' - just enable the track
|
|
5295
|
+
this.enableSubtitleTrack(parseInt(trackIndex));
|
|
5176
5296
|
}
|
|
5177
5297
|
|
|
5178
5298
|
this.updateSubtitlesButton();
|