myetv-player 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.js +9 -3
- package/css/myetv-player.css +44 -0
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +296 -120
- package/dist/myetv-player.min.js +5405 -5719
- package/package.json +3 -1
- package/plugins/youtube/myetv-player-youtube-plugin.js +378 -130
- package/scss/_menus.scss +49 -0
- package/src/controls.js +122 -51
- package/src/subtitles.js +174 -69
package/dist/myetv-player.js
CHANGED
|
@@ -3360,7 +3360,9 @@ updateSettingsMenuVisibility() {
|
|
|
3360
3360
|
}
|
|
3361
3361
|
}
|
|
3362
3362
|
|
|
3363
|
-
|
|
3363
|
+
/**
|
|
3364
|
+
* Populate settings menu with controls
|
|
3365
|
+
*/
|
|
3364
3366
|
populateSettingsMenu() {
|
|
3365
3367
|
const settingsMenu = this.controls?.querySelector('.settings-menu');
|
|
3366
3368
|
if (!settingsMenu) return;
|
|
@@ -3375,54 +3377,104 @@ populateSettingsMenu() {
|
|
|
3375
3377
|
</div>`;
|
|
3376
3378
|
}
|
|
3377
3379
|
|
|
3378
|
-
// Speed Control
|
|
3380
|
+
// Speed Control - expandable
|
|
3379
3381
|
if (this.options.showSpeedControl) {
|
|
3380
3382
|
const speedLabel = this.t('playback_speed') || 'Playback Speed';
|
|
3381
3383
|
const currentSpeed = this.video ? this.video.playbackRate : 1;
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
<
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
<div class="settings-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3384
|
+
|
|
3385
|
+
menuHTML += `
|
|
3386
|
+
<div class="settings-expandable-wrapper">
|
|
3387
|
+
<div class="settings-option expandable-trigger" data-action="speed-expand">
|
|
3388
|
+
<span class="settings-option-label">${speedLabel}: ${currentSpeed}x</span>
|
|
3389
|
+
<span class="expand-arrow">▼</span>
|
|
3390
|
+
</div>
|
|
3391
|
+
<div class="settings-expandable-content" style="display: none;">`;
|
|
3392
|
+
|
|
3393
|
+
const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
3394
|
+
speeds.forEach(speed => {
|
|
3395
|
+
const isActive = Math.abs(speed - currentSpeed) < 0.01;
|
|
3396
|
+
menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-speed="${speed}">${speed}x</div>`;
|
|
3397
|
+
});
|
|
3398
|
+
|
|
3399
|
+
menuHTML += `</div></div>`;
|
|
3394
3400
|
}
|
|
3395
3401
|
|
|
3396
|
-
// Subtitles
|
|
3402
|
+
// Subtitles - expandable
|
|
3397
3403
|
if (this.options.showSubtitles && this.textTracks && this.textTracks.length > 0) {
|
|
3398
3404
|
const subtitlesLabel = this.t('subtitles') || 'Subtitles';
|
|
3399
3405
|
const currentTrack = this.currentSubtitleTrack;
|
|
3400
|
-
const currentLabel = this.subtitlesEnabled && currentTrack
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
<div class="settings-
|
|
3409
|
-
|
|
3410
|
-
|
|
3406
|
+
const currentLabel = this.subtitlesEnabled && currentTrack ? currentTrack.label : (this.t('subtitlesoff') || 'Off');
|
|
3407
|
+
|
|
3408
|
+
menuHTML += `
|
|
3409
|
+
<div class="settings-expandable-wrapper">
|
|
3410
|
+
<div class="settings-option expandable-trigger" data-action="subtitles-expand">
|
|
3411
|
+
<span class="settings-option-label">${subtitlesLabel}: ${currentLabel}</span>
|
|
3412
|
+
<span class="expand-arrow">▼</span>
|
|
3413
|
+
</div>
|
|
3414
|
+
<div class="settings-expandable-content" style="display: none;">`;
|
|
3415
|
+
|
|
3416
|
+
// Off option
|
|
3417
|
+
menuHTML += `<div class="settings-suboption ${!this.subtitlesEnabled ? 'active' : ''}" data-track="off">${this.t('subtitlesoff') || 'Off'}</div>`;
|
|
3411
3418
|
|
|
3419
|
+
// Subtitle tracks
|
|
3412
3420
|
this.textTracks.forEach((trackData, index) => {
|
|
3413
3421
|
const isActive = this.currentSubtitleTrack === trackData.track;
|
|
3414
|
-
menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}"
|
|
3415
|
-
${trackData.label}
|
|
3416
|
-
</div>`;
|
|
3422
|
+
menuHTML += `<div class="settings-suboption ${isActive ? 'active' : ''}" data-track="${index}">${trackData.label}</div>`;
|
|
3417
3423
|
});
|
|
3418
3424
|
|
|
3419
|
-
menuHTML +=
|
|
3425
|
+
menuHTML += `</div></div>`;
|
|
3420
3426
|
}
|
|
3421
3427
|
|
|
3422
3428
|
settingsMenu.innerHTML = menuHTML;
|
|
3429
|
+
|
|
3430
|
+
// Add scrollbar if needed
|
|
3431
|
+
this.addSettingsMenuScrollbar();
|
|
3423
3432
|
}
|
|
3424
3433
|
|
|
3425
|
-
|
|
3434
|
+
/**
|
|
3435
|
+
* Add scrollbar to settings menu on mobile
|
|
3436
|
+
*/
|
|
3437
|
+
addSettingsMenuScrollbar() {
|
|
3438
|
+
const settingsMenu = this.controls?.querySelector('.settings-menu');
|
|
3439
|
+
if (!settingsMenu) return;
|
|
3440
|
+
|
|
3441
|
+
const playerHeight = this.container.offsetHeight;
|
|
3442
|
+
const maxMenuHeight = playerHeight - 100;
|
|
3443
|
+
|
|
3444
|
+
settingsMenu.style.maxHeight = `${maxMenuHeight}px`;
|
|
3445
|
+
settingsMenu.style.overflowY = 'auto';
|
|
3446
|
+
settingsMenu.style.overflowX = 'hidden';
|
|
3447
|
+
|
|
3448
|
+
// Add scrollbar styling
|
|
3449
|
+
if (!document.getElementById('player-settings-scrollbar-style')) {
|
|
3450
|
+
const scrollbarStyle = document.createElement('style');
|
|
3451
|
+
scrollbarStyle.id = 'player-settings-scrollbar-style';
|
|
3452
|
+
scrollbarStyle.textContent = `
|
|
3453
|
+
.settings-menu::-webkit-scrollbar {
|
|
3454
|
+
width: 6px;
|
|
3455
|
+
}
|
|
3456
|
+
.settings-menu::-webkit-scrollbar-track {
|
|
3457
|
+
background: rgba(255,255,255,0.05);
|
|
3458
|
+
border-radius: 3px;
|
|
3459
|
+
}
|
|
3460
|
+
.settings-menu::-webkit-scrollbar-thumb {
|
|
3461
|
+
background: rgba(255,255,255,0.3);
|
|
3462
|
+
border-radius: 3px;
|
|
3463
|
+
}
|
|
3464
|
+
.settings-menu::-webkit-scrollbar-thumb:hover {
|
|
3465
|
+
background: rgba(255,255,255,0.5);
|
|
3466
|
+
}
|
|
3467
|
+
`;
|
|
3468
|
+
document.head.appendChild(scrollbarStyle);
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
settingsMenu.style.scrollbarWidth = 'thin';
|
|
3472
|
+
settingsMenu.style.scrollbarColor = 'rgba(255,255,255,0.3) transparent';
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
/**
|
|
3476
|
+
* Bind settings menu events
|
|
3477
|
+
*/
|
|
3426
3478
|
bindSettingsMenuEvents() {
|
|
3427
3479
|
const settingsMenu = this.controls?.querySelector('.settings-menu');
|
|
3428
3480
|
if (!settingsMenu) return;
|
|
@@ -3430,7 +3482,26 @@ bindSettingsMenuEvents() {
|
|
|
3430
3482
|
settingsMenu.addEventListener('click', (e) => {
|
|
3431
3483
|
e.stopPropagation();
|
|
3432
3484
|
|
|
3433
|
-
// Handle
|
|
3485
|
+
// Handle expandable triggers
|
|
3486
|
+
if (e.target.classList.contains('expandable-trigger') || e.target.closest('.expandable-trigger')) {
|
|
3487
|
+
const trigger = e.target.classList.contains('expandable-trigger') ? e.target : e.target.closest('.expandable-trigger');
|
|
3488
|
+
const wrapper = trigger.closest('.settings-expandable-wrapper');
|
|
3489
|
+
const content = wrapper.querySelector('.settings-expandable-content');
|
|
3490
|
+
const arrow = trigger.querySelector('.expand-arrow');
|
|
3491
|
+
|
|
3492
|
+
const isExpanded = content.style.display !== 'none';
|
|
3493
|
+
|
|
3494
|
+
if (isExpanded) {
|
|
3495
|
+
content.style.display = 'none';
|
|
3496
|
+
arrow.style.transform = 'rotate(0deg)';
|
|
3497
|
+
} else {
|
|
3498
|
+
content.style.display = 'block';
|
|
3499
|
+
arrow.style.transform = 'rotate(180deg)';
|
|
3500
|
+
}
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
// Handle direct actions (like PiP)
|
|
3434
3505
|
if (e.target.classList.contains('settings-option') || e.target.closest('.settings-option')) {
|
|
3435
3506
|
const option = e.target.classList.contains('settings-option') ? e.target : e.target.closest('.settings-option');
|
|
3436
3507
|
const action = option.getAttribute('data-action');
|
|
@@ -3443,32 +3514,31 @@ bindSettingsMenuEvents() {
|
|
|
3443
3514
|
|
|
3444
3515
|
// Handle submenu actions
|
|
3445
3516
|
if (e.target.classList.contains('settings-suboption')) {
|
|
3446
|
-
const
|
|
3447
|
-
const
|
|
3517
|
+
const wrapper = e.target.closest('.settings-expandable-wrapper');
|
|
3518
|
+
const trigger = wrapper.querySelector('.expandable-trigger');
|
|
3519
|
+
const action = trigger.getAttribute('data-action');
|
|
3448
3520
|
|
|
3449
|
-
if (action === 'speed') {
|
|
3521
|
+
if (action === 'speed-expand') {
|
|
3450
3522
|
const speed = parseFloat(e.target.getAttribute('data-speed'));
|
|
3451
3523
|
if (speed && speed > 0 && this.video && !this.isChangingQuality) {
|
|
3452
3524
|
this.video.playbackRate = speed;
|
|
3453
3525
|
|
|
3454
3526
|
// Update active states
|
|
3455
|
-
|
|
3456
|
-
opt.classList.remove('active');
|
|
3457
|
-
});
|
|
3527
|
+
wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
|
|
3458
3528
|
e.target.classList.add('active');
|
|
3459
3529
|
|
|
3460
|
-
// Update
|
|
3461
|
-
const
|
|
3462
|
-
if (
|
|
3530
|
+
// Update trigger text
|
|
3531
|
+
const label = trigger.querySelector('.settings-option-label');
|
|
3532
|
+
if (label) {
|
|
3533
|
+
const speedLabel = this.t('playback_speed') || 'Playback Speed';
|
|
3534
|
+
label.textContent = `${speedLabel}: ${speed}x`;
|
|
3535
|
+
}
|
|
3463
3536
|
|
|
3464
3537
|
// Trigger event
|
|
3465
3538
|
this.triggerEvent('speedchange', { speed, previousSpeed: this.video.playbackRate });
|
|
3466
3539
|
}
|
|
3467
|
-
}
|
|
3468
|
-
|
|
3469
|
-
else if (action === 'subtitles') {
|
|
3540
|
+
} else if (action === 'subtitles-expand') {
|
|
3470
3541
|
const trackData = e.target.getAttribute('data-track');
|
|
3471
|
-
|
|
3472
3542
|
if (trackData === 'off') {
|
|
3473
3543
|
this.disableSubtitles();
|
|
3474
3544
|
} else {
|
|
@@ -3477,14 +3547,15 @@ bindSettingsMenuEvents() {
|
|
|
3477
3547
|
}
|
|
3478
3548
|
|
|
3479
3549
|
// Update active states
|
|
3480
|
-
|
|
3481
|
-
opt.classList.remove('active');
|
|
3482
|
-
});
|
|
3550
|
+
wrapper.querySelectorAll('.settings-suboption').forEach(opt => opt.classList.remove('active'));
|
|
3483
3551
|
e.target.classList.add('active');
|
|
3484
3552
|
|
|
3485
|
-
// Update
|
|
3486
|
-
const
|
|
3487
|
-
if (
|
|
3553
|
+
// Update trigger text
|
|
3554
|
+
const label = trigger.querySelector('.settings-option-label');
|
|
3555
|
+
if (label) {
|
|
3556
|
+
const subtitlesLabel = this.t('subtitles') || 'Subtitles';
|
|
3557
|
+
label.textContent = `${subtitlesLabel}: ${e.target.textContent}`;
|
|
3558
|
+
}
|
|
3488
3559
|
}
|
|
3489
3560
|
}
|
|
3490
3561
|
});
|
|
@@ -4827,10 +4898,10 @@ createCustomSubtitleOverlay() {
|
|
|
4827
4898
|
'bottom: 80px;' +
|
|
4828
4899
|
'left: 50%;' +
|
|
4829
4900
|
'transform: translateX(-50%);' +
|
|
4830
|
-
'z-index:
|
|
4901
|
+
'z-index: 999;' +
|
|
4831
4902
|
'color: white;' +
|
|
4832
4903
|
'font-family: Arial, sans-serif;' +
|
|
4833
|
-
'font-size: clamp(12px, 4vw, 18px);' +
|
|
4904
|
+
'font-size: clamp(12px, 4vw, 18px);' +
|
|
4834
4905
|
'font-weight: bold;' +
|
|
4835
4906
|
'text-align: center;' +
|
|
4836
4907
|
'text-shadow: 2px 2px 4px rgba(0, 0, 0, 1);' +
|
|
@@ -4857,52 +4928,29 @@ createCustomSubtitleOverlay() {
|
|
|
4857
4928
|
if (this.options.debug) console.log('✅ Custom subtitle overlay created with responsive settings');
|
|
4858
4929
|
}
|
|
4859
4930
|
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
var trackElements = document.querySelectorAll('track[kind="subtitles"], track[kind="captions"]');
|
|
4863
|
-
var loadPromises = [];
|
|
4864
|
-
|
|
4865
|
-
if (this.options.debug) console.log('📥 Loading ' + trackElements.length + ' subtitle files...');
|
|
4866
|
-
|
|
4867
|
-
for (var i = 0; i < trackElements.length; i++) {
|
|
4868
|
-
var track = trackElements[i];
|
|
4869
|
-
|
|
4870
|
-
(function (trackElement, index) {
|
|
4871
|
-
var promise = fetch(trackElement.src)
|
|
4872
|
-
.then(function (response) {
|
|
4873
|
-
return response.text();
|
|
4874
|
-
})
|
|
4875
|
-
.then(function (srtText) {
|
|
4876
|
-
var subtitles = self.parseCustomSRT(srtText);
|
|
4877
|
-
self.customSubtitles.push({
|
|
4878
|
-
label: trackElement.label || 'Track ' + (index + 1),
|
|
4879
|
-
language: trackElement.srclang || 'unknown',
|
|
4880
|
-
subtitles: subtitles
|
|
4881
|
-
});
|
|
4931
|
+
customTimeToSeconds(timeString) {
|
|
4932
|
+
if (!timeString) return 0;
|
|
4882
4933
|
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
}
|
|
4886
|
-
})
|
|
4887
|
-
.catch(function (error) {
|
|
4888
|
-
if (self.options.debug) {
|
|
4889
|
-
console.error('❌ Error loading ' + trackElement.src + ':', error);
|
|
4890
|
-
}
|
|
4891
|
-
});
|
|
4934
|
+
var parts = timeString.split(',');
|
|
4935
|
+
if (parts.length !== 2) return 0;
|
|
4892
4936
|
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
}
|
|
4937
|
+
var time = parts[0];
|
|
4938
|
+
var millis = parts[1];
|
|
4896
4939
|
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
console.log('✅ All custom subtitle tracks loaded');
|
|
4900
|
-
}
|
|
4940
|
+
var timeParts = time.split(':');
|
|
4941
|
+
if (timeParts.length !== 3) return 0;
|
|
4901
4942
|
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4943
|
+
var hours = parseInt(timeParts[0], 10);
|
|
4944
|
+
var minutes = parseInt(timeParts[1], 10);
|
|
4945
|
+
var seconds = parseInt(timeParts[2], 10);
|
|
4946
|
+
var milliseconds = parseInt(millis, 10);
|
|
4947
|
+
|
|
4948
|
+
if (isNaN(hours) || isNaN(minutes) || isNaN(seconds) || isNaN(milliseconds)) {
|
|
4949
|
+
console.error('❌ customTimeToSeconds failed for:', timeString);
|
|
4950
|
+
return 0;
|
|
4951
|
+
}
|
|
4952
|
+
|
|
4953
|
+
return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000;
|
|
4906
4954
|
}
|
|
4907
4955
|
|
|
4908
4956
|
parseCustomSRT(srtText) {
|
|
@@ -4921,9 +4969,9 @@ parseCustomSRT(srtText) {
|
|
|
4921
4969
|
if (timeMatch) {
|
|
4922
4970
|
var startTime = this.customTimeToSeconds(timeMatch[1]);
|
|
4923
4971
|
var endTime = this.customTimeToSeconds(timeMatch[2]);
|
|
4924
|
-
var text =
|
|
4972
|
+
var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
|
|
4925
4973
|
|
|
4926
|
-
if (text.length > 0 && startTime < endTime) {
|
|
4974
|
+
if (text && text.length > 0 && startTime < endTime) {
|
|
4927
4975
|
subtitles.push({
|
|
4928
4976
|
start: startTime,
|
|
4929
4977
|
end: endTime,
|
|
@@ -4934,20 +4982,97 @@ parseCustomSRT(srtText) {
|
|
|
4934
4982
|
}
|
|
4935
4983
|
}
|
|
4936
4984
|
|
|
4985
|
+
if (this.options.debug) console.log('✅ Parsed ' + subtitles.length + ' subtitles');
|
|
4937
4986
|
return subtitles;
|
|
4938
4987
|
}
|
|
4939
4988
|
|
|
4940
|
-
|
|
4941
|
-
var
|
|
4942
|
-
var
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4989
|
+
loadCustomSubtitleTracks() {
|
|
4990
|
+
var self = this;
|
|
4991
|
+
var tracks = this.video.querySelectorAll('track[kind="subtitles"]');
|
|
4992
|
+
if (tracks.length === 0) return;
|
|
4993
|
+
|
|
4994
|
+
tracks.forEach(function (track, index) {
|
|
4995
|
+
var src = track.getAttribute('src');
|
|
4996
|
+
var label = track.getAttribute('label') || 'Unknown';
|
|
4997
|
+
var srclang = track.getAttribute('srclang') || '';
|
|
4998
|
+
|
|
4999
|
+
// CREA L'OGGETTO PRIMA E AGGIUNGILO SUBITO
|
|
5000
|
+
var trackObj = {
|
|
5001
|
+
label: label,
|
|
5002
|
+
language: srclang,
|
|
5003
|
+
subtitles: [],
|
|
5004
|
+
trackIndex: index
|
|
5005
|
+
};
|
|
5006
|
+
self.customSubtitles.push(trackObj);
|
|
5007
|
+
|
|
5008
|
+
fetch(src)
|
|
5009
|
+
.then(function (response) {
|
|
5010
|
+
return response.text();
|
|
5011
|
+
})
|
|
5012
|
+
.then(function (srtText) {
|
|
5013
|
+
var normalizedText = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
5014
|
+
var blocks = normalizedText.trim().split('\n\n');
|
|
5015
|
+
|
|
5016
|
+
for (var i = 0; i < blocks.length; i++) {
|
|
5017
|
+
var block = blocks[i].trim();
|
|
5018
|
+
if (!block) continue;
|
|
5019
|
+
var lines = block.split('\n');
|
|
5020
|
+
|
|
5021
|
+
if (lines.length >= 3) {
|
|
5022
|
+
var timeLine = lines[1].trim();
|
|
5023
|
+
var timeMatch = timeLine.match(/(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})/);
|
|
5024
|
+
|
|
5025
|
+
if (timeMatch) {
|
|
5026
|
+
var startParts = timeMatch[1].split(',');
|
|
5027
|
+
var startTimeParts = startParts[0].split(':');
|
|
5028
|
+
var startTime = parseInt(startTimeParts[0], 10) * 3600 + parseInt(startTimeParts[1], 10) * 60 + parseInt(startTimeParts[2], 10) + parseInt(startParts[1], 10) / 1000;
|
|
5029
|
+
|
|
5030
|
+
var endParts = timeMatch[2].split(',');
|
|
5031
|
+
var endTimeParts = endParts[0].split(':');
|
|
5032
|
+
var endTime = parseInt(endTimeParts[0], 10) * 3600 + parseInt(endTimeParts[1], 10) * 60 + parseInt(endTimeParts[2], 10) + parseInt(endParts[1], 10) / 1000;
|
|
5033
|
+
|
|
5034
|
+
var text = lines.slice(2).join('\n').trim().replace(/<[^>]*>/g, '');
|
|
5035
|
+
|
|
5036
|
+
if (text && text.length > 0 && !isNaN(startTime) && !isNaN(endTime) && startTime < endTime) {
|
|
5037
|
+
trackObj.subtitles.push({
|
|
5038
|
+
start: startTime,
|
|
5039
|
+
end: endTime,
|
|
5040
|
+
text: text
|
|
5041
|
+
});
|
|
5042
|
+
}
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
4949
5046
|
|
|
4950
|
-
|
|
5047
|
+
if (self.options.debug) {
|
|
5048
|
+
console.log('✅ Loaded ' + trackObj.subtitles.length + ' subtitles for ' + label);
|
|
5049
|
+
}
|
|
5050
|
+
})
|
|
5051
|
+
.catch(function (error) {
|
|
5052
|
+
console.error('❌ Error loading ' + label + ':', error);
|
|
5053
|
+
});
|
|
5054
|
+
});
|
|
5055
|
+
}
|
|
5056
|
+
|
|
5057
|
+
sanitizeSubtitleText(text) {
|
|
5058
|
+
if (!text) return '';
|
|
5059
|
+
|
|
5060
|
+
// Remove HTML tags
|
|
5061
|
+
var sanitized = text.replace(/<[^>]*>/g, '');
|
|
5062
|
+
|
|
5063
|
+
// Remove styling tags common in SRT files
|
|
5064
|
+
sanitized = sanitized.replace(/{\\.*?}/g, '');
|
|
5065
|
+
sanitized = sanitized.replace(/\\N/g, '\n');
|
|
5066
|
+
|
|
5067
|
+
// Clean up multiple spaces
|
|
5068
|
+
sanitized = sanitized.replace(/\s+/g, ' ').trim();
|
|
5069
|
+
|
|
5070
|
+
// Decode HTML entities if present
|
|
5071
|
+
var tempDiv = document.createElement('div');
|
|
5072
|
+
tempDiv.innerHTML = sanitized;
|
|
5073
|
+
sanitized = tempDiv.textContent || tempDiv.innerText || sanitized;
|
|
5074
|
+
|
|
5075
|
+
return sanitized;
|
|
4951
5076
|
}
|
|
4952
5077
|
|
|
4953
5078
|
enableCustomSubtitleTrack(trackIndex) {
|
|
@@ -5040,28 +5165,39 @@ detectTextTracks() {
|
|
|
5040
5165
|
enableSubtitleTrack(trackIndex) {
|
|
5041
5166
|
if (trackIndex < 0 || trackIndex >= this.textTracks.length) return;
|
|
5042
5167
|
|
|
5168
|
+
// Disable all tracks first
|
|
5043
5169
|
this.disableAllTracks();
|
|
5044
5170
|
|
|
5171
|
+
// Enable ONLY the custom subtitle system (not native browser)
|
|
5045
5172
|
var success = this.enableCustomSubtitleTrack(trackIndex);
|
|
5046
5173
|
|
|
5047
5174
|
if (success) {
|
|
5048
5175
|
this.currentSubtitleTrack = this.textTracks[trackIndex].track;
|
|
5049
5176
|
this.subtitlesEnabled = true;
|
|
5050
5177
|
|
|
5178
|
+
// Make sure native tracks stay DISABLED
|
|
5179
|
+
if (this.video.textTracks && this.video.textTracks[trackIndex]) {
|
|
5180
|
+
this.video.textTracks[trackIndex].mode = 'disabled'; // Keep native disabled
|
|
5181
|
+
}
|
|
5182
|
+
|
|
5051
5183
|
this.updateSubtitlesButton();
|
|
5052
5184
|
this.populateSubtitlesMenu();
|
|
5053
5185
|
|
|
5054
5186
|
if (this.options.debug) {
|
|
5055
|
-
console.log('✅
|
|
5187
|
+
console.log('✅ Custom subtitles enabled:', this.textTracks[trackIndex].label);
|
|
5056
5188
|
}
|
|
5057
5189
|
|
|
5058
|
-
// Trigger
|
|
5190
|
+
// Trigger subtitle change event
|
|
5059
5191
|
this.triggerEvent('subtitlechange', {
|
|
5060
5192
|
enabled: true,
|
|
5061
5193
|
trackIndex: trackIndex,
|
|
5062
5194
|
trackLabel: this.textTracks[trackIndex].label,
|
|
5063
5195
|
trackLanguage: this.textTracks[trackIndex].language
|
|
5064
5196
|
});
|
|
5197
|
+
} else {
|
|
5198
|
+
if (this.options.debug) {
|
|
5199
|
+
console.error('❌ Failed to enable custom subtitles for track', trackIndex);
|
|
5200
|
+
}
|
|
5065
5201
|
}
|
|
5066
5202
|
}
|
|
5067
5203
|
|
|
@@ -5084,11 +5220,15 @@ disableSubtitles() {
|
|
|
5084
5220
|
}
|
|
5085
5221
|
|
|
5086
5222
|
disableAllTracks() {
|
|
5087
|
-
if (this.video.textTracks)
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5223
|
+
if (!this.video || !this.video.textTracks) return;
|
|
5224
|
+
|
|
5225
|
+
// Disable all native tracks
|
|
5226
|
+
for (var i = 0; i < this.video.textTracks.length; i++) {
|
|
5227
|
+
this.video.textTracks[i].mode = 'hidden';
|
|
5091
5228
|
}
|
|
5229
|
+
|
|
5230
|
+
// Also disable custom subtitles
|
|
5231
|
+
this.disableCustomSubtitles();
|
|
5092
5232
|
}
|
|
5093
5233
|
|
|
5094
5234
|
getAvailableSubtitles() {
|
|
@@ -5169,25 +5309,61 @@ updateSubtitlesUI() {
|
|
|
5169
5309
|
bindSubtitleEvents() {
|
|
5170
5310
|
var self = this;
|
|
5171
5311
|
|
|
5312
|
+
if (this.video.textTracks) {
|
|
5313
|
+
this.isChangingSubtitles = false; // flag to prevent loops
|
|
5314
|
+
|
|
5315
|
+
this.video.textTracks.addEventListener('change', function () {
|
|
5316
|
+
// ignore changes initiated by the player itself
|
|
5317
|
+
if (self.isChangingSubtitles) {
|
|
5318
|
+
return;
|
|
5319
|
+
}
|
|
5320
|
+
|
|
5321
|
+
// only update ui
|
|
5322
|
+
self.updateSubtitlesUI();
|
|
5323
|
+
});
|
|
5324
|
+
}
|
|
5325
|
+
|
|
5326
|
+
// Add timeupdate listener for custom subtitle display
|
|
5327
|
+
this.video.addEventListener('timeupdate', () => {
|
|
5328
|
+
if (this.customSubtitlesEnabled) {
|
|
5329
|
+
this.updateCustomSubtitleDisplay();
|
|
5330
|
+
}
|
|
5331
|
+
});
|
|
5332
|
+
|
|
5333
|
+
// Menu click events
|
|
5172
5334
|
var subtitlesMenu = this.controls && this.controls.querySelector('.subtitles-menu');
|
|
5173
5335
|
if (subtitlesMenu) {
|
|
5174
5336
|
subtitlesMenu.addEventListener('click', function (e) {
|
|
5175
|
-
|
|
5337
|
+
var option = e.target.closest('.subtitles-option');
|
|
5338
|
+
if (!option) return;
|
|
5339
|
+
|
|
5340
|
+
self.isChangingSubtitles = true; // active flag
|
|
5341
|
+
|
|
5342
|
+
var trackIndex = option.getAttribute('data-track');
|
|
5343
|
+
if (trackIndex === 'off') {
|
|
5344
|
+
self.disableSubtitles();
|
|
5345
|
+
} else {
|
|
5346
|
+
self.enableSubtitleTrack(parseInt(trackIndex));
|
|
5347
|
+
}
|
|
5348
|
+
|
|
5349
|
+
setTimeout(function () {
|
|
5350
|
+
self.isChangingSubtitles = false; // disable flag
|
|
5351
|
+
}, 100);
|
|
5176
5352
|
});
|
|
5177
5353
|
}
|
|
5178
5354
|
}
|
|
5179
5355
|
|
|
5180
|
-
|
|
5181
5356
|
handleSubtitlesMenuClick(e) {
|
|
5182
|
-
|
|
5357
|
+
var option = e.target.closest('.subtitles-option');
|
|
5358
|
+
if (!option) return; // This prevents button clicks from toggling
|
|
5183
5359
|
|
|
5184
|
-
var
|
|
5360
|
+
var trackIndex = option.getAttribute('data-track');
|
|
5185
5361
|
|
|
5186
|
-
if (
|
|
5362
|
+
if (trackIndex === 'off') {
|
|
5187
5363
|
this.disableSubtitles();
|
|
5188
5364
|
} else {
|
|
5189
|
-
|
|
5190
|
-
this.enableSubtitleTrack(trackIndex);
|
|
5365
|
+
// Don't check for 'toggle' - just enable the track
|
|
5366
|
+
this.enableSubtitleTrack(parseInt(trackIndex));
|
|
5191
5367
|
}
|
|
5192
5368
|
|
|
5193
5369
|
this.updateSubtitlesButton();
|