mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 15:38:53 -05:00
fix: Touch scroll detection for settingsOverlay
This commit is contained in:
parent
34bad434c8
commit
7f90b54b3c
@ -42,13 +42,33 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-header {padding: 12px 16px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); font-weight: bold;}
|
.settings-header {
|
||||||
.settings-item { padding: 12px 16px; cursor: pointer; display: flex; justify-content: space-between; align-items: center;
|
padding: 12px 16px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1); transition: background 0.2s ease; gap:10px;}
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
.settings-item .settings-left span{ display:flex;}
|
font-weight: bold;
|
||||||
.custom-settings-overlay .settings-left span.vjs-icon-placeholder {transform: inherit !important;}
|
}
|
||||||
.settings-item:last-child { border-bottom: none;}
|
.settings-item {
|
||||||
.settings-item:hover { background: rgba(255, 255, 255, 0.05);}
|
padding: 12px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.settings-item .settings-left span {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.custom-settings-overlay .settings-left span.vjs-icon-placeholder {
|
||||||
|
transform: inherit !important;
|
||||||
|
}
|
||||||
|
.settings-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.settings-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
/* Speed submenu */
|
/* Speed submenu */
|
||||||
.speed-submenu {
|
.speed-submenu {
|
||||||
@ -60,6 +80,10 @@
|
|||||||
background: rgba(28, 28, 28, 0.95);
|
background: rgba(28, 28, 28, 0.95);
|
||||||
display: none;
|
display: none;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
touch-action: pan-y;
|
||||||
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Quality submenu mirrors speed submenu */
|
/* Quality submenu mirrors speed submenu */
|
||||||
@ -72,6 +96,10 @@
|
|||||||
background: rgba(28, 28, 28, 0.95);
|
background: rgba(28, 28, 28, 0.95);
|
||||||
display: none;
|
display: none;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
touch-action: pan-y;
|
||||||
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subtitles submenu styling mirrors speed/quality */
|
/* Subtitles submenu styling mirrors speed/quality */
|
||||||
@ -84,6 +112,10 @@
|
|||||||
background: rgba(28, 28, 28, 0.95);
|
background: rgba(28, 28, 28, 0.95);
|
||||||
display: none;
|
display: none;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
touch-action: pan-y;
|
||||||
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
.subtitle-option {
|
.subtitle-option {
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
@ -93,8 +125,12 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
transition: background 0.2s ease;
|
transition: background 0.2s ease;
|
||||||
}
|
}
|
||||||
.subtitle-option:hover { background: rgba(255,255,255,0.05); }
|
.subtitle-option:hover {
|
||||||
.subtitle-option.active { background: rgba(255,255,255,0.1); }
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
.subtitle-option.active {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
/* Submenu header */
|
/* Submenu header */
|
||||||
.submenu-header {
|
.submenu-header {
|
||||||
@ -159,7 +195,7 @@
|
|||||||
.settings-right {
|
.settings-right {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align:right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
/* .vjs-icon-cog:before {
|
/* .vjs-icon-cog:before {
|
||||||
font-size: 20px !important;
|
font-size: 20px !important;
|
||||||
|
|||||||
@ -18,6 +18,9 @@ class CustomSettingsMenu extends Component {
|
|||||||
this.userPreferences = options?.userPreferences || new UserPreferences();
|
this.userPreferences = options?.userPreferences || new UserPreferences();
|
||||||
this.providedQualities = options?.qualities || null;
|
this.providedQualities = options?.qualities || null;
|
||||||
this.hasSubtitles = options?.hasSubtitles || false;
|
this.hasSubtitles = options?.hasSubtitles || false;
|
||||||
|
// Touch scroll detection (mobile)
|
||||||
|
this.isTouchScrolling = false;
|
||||||
|
this.touchStartY = 0;
|
||||||
|
|
||||||
// Bind methods
|
// Bind methods
|
||||||
this.createSettingsButton = this.createSettingsButton.bind(this);
|
this.createSettingsButton = this.createSettingsButton.bind(this);
|
||||||
@ -581,22 +584,34 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mobile touch events for settings items
|
// Touch scroll detection for settingsOverlay
|
||||||
|
this.settingsOverlay.addEventListener('touchstart', (e) => {
|
||||||
|
this.touchStartY = e.touches[0].clientY;
|
||||||
|
this.isTouchScrolling = false;
|
||||||
|
}, { passive: true });
|
||||||
|
this.settingsOverlay.addEventListener('touchmove', (e) => {
|
||||||
|
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
||||||
|
if (dy > 10) this.isTouchScrolling = true;
|
||||||
|
}, { passive: true });
|
||||||
|
// Mobile touch events for settings items (tap vs scroll)
|
||||||
this.settingsOverlay.addEventListener("touchend", (e) => {
|
this.settingsOverlay.addEventListener("touchend", (e) => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (this.isTouchScrolling) { this.isTouchScrolling = false; return; }
|
||||||
|
|
||||||
if (e.target.closest('[data-setting="playback-speed"]')) {
|
if (e.target.closest('[data-setting="playback-speed"]')) {
|
||||||
|
e.preventDefault();
|
||||||
this.speedSubmenu.style.display = "flex";
|
this.speedSubmenu.style.display = "flex";
|
||||||
this.qualitySubmenu.style.display = "none";
|
this.qualitySubmenu.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.target.closest('[data-setting="quality"]')) {
|
if (e.target.closest('[data-setting="quality"]')) {
|
||||||
|
e.preventDefault();
|
||||||
this.qualitySubmenu.style.display = "flex";
|
this.qualitySubmenu.style.display = "flex";
|
||||||
this.speedSubmenu.style.display = "none";
|
this.speedSubmenu.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.target.closest('[data-setting="subtitles"]')) {
|
if (e.target.closest('[data-setting="subtitles"]')) {
|
||||||
|
e.preventDefault();
|
||||||
this.refreshSubtitlesSubmenu();
|
this.refreshSubtitlesSubmenu();
|
||||||
this.subtitlesSubmenu.style.display = "flex";
|
this.subtitlesSubmenu.style.display = "flex";
|
||||||
this.speedSubmenu.style.display = "none";
|
this.speedSubmenu.style.display = "none";
|
||||||
@ -646,12 +661,22 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mobile touch events for speed options
|
// Touch scroll detection for speed submenu
|
||||||
|
this.speedSubmenu.addEventListener('touchstart', (e) => {
|
||||||
|
this.touchStartY = e.touches[0].clientY;
|
||||||
|
this.isTouchScrolling = false;
|
||||||
|
}, { passive: true });
|
||||||
|
this.speedSubmenu.addEventListener('touchmove', (e) => {
|
||||||
|
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
||||||
|
if (dy > 10) this.isTouchScrolling = true;
|
||||||
|
}, { passive: true });
|
||||||
|
// Mobile touch events for speed options (tap vs scroll)
|
||||||
this.speedSubmenu.addEventListener("touchend", (e) => {
|
this.speedSubmenu.addEventListener("touchend", (e) => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (this.isTouchScrolling) { this.isTouchScrolling = false; return; }
|
||||||
const speedOption = e.target.closest(".speed-option");
|
const speedOption = e.target.closest(".speed-option");
|
||||||
if (speedOption) {
|
if (speedOption) {
|
||||||
|
e.preventDefault();
|
||||||
const speed = parseFloat(speedOption.dataset.speed);
|
const speed = parseFloat(speedOption.dataset.speed);
|
||||||
this.handleSpeedChange(speed, speedOption);
|
this.handleSpeedChange(speed, speedOption);
|
||||||
}
|
}
|
||||||
@ -666,12 +691,21 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mobile touch events for quality options
|
this.qualitySubmenu.addEventListener('touchstart', (e) => {
|
||||||
|
this.touchStartY = e.touches[0].clientY;
|
||||||
|
this.isTouchScrolling = false;
|
||||||
|
}, { passive: true });
|
||||||
|
this.qualitySubmenu.addEventListener('touchmove', (e) => {
|
||||||
|
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
||||||
|
if (dy > 10) this.isTouchScrolling = true;
|
||||||
|
}, { passive: true });
|
||||||
|
// Mobile touch events for quality options (tap vs scroll)
|
||||||
this.qualitySubmenu.addEventListener("touchend", (e) => {
|
this.qualitySubmenu.addEventListener("touchend", (e) => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (this.isTouchScrolling) { this.isTouchScrolling = false; return; }
|
||||||
const qualityOption = e.target.closest(".quality-option");
|
const qualityOption = e.target.closest(".quality-option");
|
||||||
if (qualityOption) {
|
if (qualityOption) {
|
||||||
|
e.preventDefault();
|
||||||
const value = qualityOption.dataset.quality;
|
const value = qualityOption.dataset.quality;
|
||||||
this.handleQualityChange(value, qualityOption);
|
this.handleQualityChange(value, qualityOption);
|
||||||
}
|
}
|
||||||
@ -686,12 +720,22 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mobile touch events for subtitle options
|
// Touch scroll detection for subtitles submenu
|
||||||
|
this.subtitlesSubmenu.addEventListener('touchstart', (e) => {
|
||||||
|
this.touchStartY = e.touches[0].clientY;
|
||||||
|
this.isTouchScrolling = false;
|
||||||
|
}, { passive: true });
|
||||||
|
this.subtitlesSubmenu.addEventListener('touchmove', (e) => {
|
||||||
|
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
||||||
|
if (dy > 10) this.isTouchScrolling = true;
|
||||||
|
}, { passive: true });
|
||||||
|
// Mobile touch events for subtitle options (tap vs scroll)
|
||||||
this.subtitlesSubmenu.addEventListener('touchend', (e) => {
|
this.subtitlesSubmenu.addEventListener('touchend', (e) => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (this.isTouchScrolling) { this.isTouchScrolling = false; return; }
|
||||||
const opt = e.target.closest('.subtitle-option');
|
const opt = e.target.closest('.subtitle-option');
|
||||||
if (opt) {
|
if (opt) {
|
||||||
|
e.preventDefault();
|
||||||
const lang = opt.dataset.lang || null;
|
const lang = opt.dataset.lang || null;
|
||||||
this.handleSubtitleChange(lang, opt);
|
this.handleSubtitleChange(lang, opt);
|
||||||
}
|
}
|
||||||
@ -812,81 +856,150 @@ class CustomSettingsMenu extends Component {
|
|||||||
const currentTime = player.currentTime();
|
const currentTime = player.currentTime();
|
||||||
const rate = player.playbackRate();
|
const rate = player.playbackRate();
|
||||||
|
|
||||||
// Try to preserve active subtitle track
|
// Capture active subtitle language and existing remote tracks
|
||||||
const textTracks = player.textTracks();
|
|
||||||
let activeSubtitleLang = null;
|
let activeSubtitleLang = null;
|
||||||
for (let i = 0; i < textTracks.length; i++) {
|
try {
|
||||||
const track = textTracks[i];
|
const tt = player.textTracks();
|
||||||
if (track.kind === "subtitles" && track.mode === "showing") {
|
for (let i = 0; i < tt.length; i++) {
|
||||||
activeSubtitleLang = track.language;
|
const t = tt[i];
|
||||||
break;
|
if (t.kind === 'subtitles' && t.mode === 'showing') {
|
||||||
|
activeSubtitleLang = t.language || t.srclang || null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// Persist active subtitle language so it survives reloads
|
||||||
|
if (activeSubtitleLang) {
|
||||||
|
this.userPreferences.setPreference('subtitleLanguage', activeSubtitleLang, true);
|
||||||
|
// Also mark subtitles as enabled so applySubtitlePreference() runs on load
|
||||||
|
this.userPreferences.setPreference('subtitleEnabled', true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefer remoteTextTrackEls (have src reliably)
|
||||||
|
const subtitleTracksInfo = [];
|
||||||
|
try {
|
||||||
|
const els = player.remoteTextTrackEls ? player.remoteTextTrackEls() : [];
|
||||||
|
for (let i = 0; i < els.length; i++) {
|
||||||
|
const el = els[i];
|
||||||
|
// Only keep subtitle tracks
|
||||||
|
if ((el.kind || '').toLowerCase() === 'subtitles') {
|
||||||
|
subtitleTracksInfo.push({
|
||||||
|
kind: 'subtitles',
|
||||||
|
src: el.src,
|
||||||
|
srclang: el.srclang || (el.track && el.track.language) || '',
|
||||||
|
label: el.label || (el.track && el.track.label) || '',
|
||||||
|
default: !!el.default
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
player.addClass("vjs-changing-resolution");
|
// Fallback: try TextTracks if no elements found and track.src exists
|
||||||
player.isChangingQuality = true; // Flag to prevent seek indicator during quality change
|
if (subtitleTracksInfo.length === 0) {
|
||||||
player.src({ src: selected.src, type: selected.type || "video/mp4" });
|
try {
|
||||||
|
const tt = player.textTracks();
|
||||||
|
for (let i = 0; i < tt.length; i++) {
|
||||||
|
const t = tt[i];
|
||||||
|
if (t.kind === 'subtitles' && t.src) {
|
||||||
|
subtitleTracksInfo.push({
|
||||||
|
kind: 'subtitles',
|
||||||
|
src: t.src,
|
||||||
|
srclang: t.language || '',
|
||||||
|
label: t.label || '',
|
||||||
|
default: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
player.addClass('vjs-changing-resolution');
|
||||||
|
player.isChangingQuality = true; // prevent seek indicator during quality change
|
||||||
|
player.src({ src: selected.src, type: selected.type || 'video/mp4' });
|
||||||
|
|
||||||
if (wasPaused) {
|
if (wasPaused) {
|
||||||
player.pause();
|
player.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
const onLoaded = () => {
|
const finishRestore = () => {
|
||||||
// Restore time, rate, subtitles
|
// Re-add remote tracks
|
||||||
try {
|
try {
|
||||||
player.playbackRate(rate);
|
subtitleTracksInfo.forEach((trackInfo) => {
|
||||||
|
if (trackInfo && trackInfo.src) {
|
||||||
|
player.addRemoteTextTrack(trackInfo, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
try {
|
|
||||||
if (!isNaN(currentTime)) player.currentTime(currentTime);
|
// Restore time and rate
|
||||||
} catch (e) {}
|
try { player.playbackRate(rate); } catch (e) {}
|
||||||
// Play or pause based on previous state
|
try { if (!isNaN(currentTime)) player.currentTime(currentTime); } catch (e) {}
|
||||||
|
|
||||||
|
// Resume state
|
||||||
if (!wasPaused) {
|
if (!wasPaused) {
|
||||||
player.play().catch(() => {});
|
player.play().catch(() => {});
|
||||||
} else {
|
} else {
|
||||||
player.pause();
|
player.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore subtitles
|
// Restore the previously active subtitle language
|
||||||
if (activeSubtitleLang) {
|
setTimeout(() => {
|
||||||
const tt = player.textTracks();
|
try {
|
||||||
for (let i = 0; i < tt.length; i++) {
|
const tt2 = player.textTracks();
|
||||||
const t = tt[i];
|
let restored = false;
|
||||||
if (t.kind === "subtitles") {
|
for (let i = 0; i < tt2.length; i++) {
|
||||||
t.mode =
|
const t = tt2[i];
|
||||||
t.language === activeSubtitleLang ? "showing" : "disabled";
|
if (t.kind === 'subtitles') {
|
||||||
|
const match = activeSubtitleLang && (t.language === activeSubtitleLang || t.srclang === activeSubtitleLang);
|
||||||
|
t.mode = match ? 'showing' : 'disabled';
|
||||||
|
if (match) restored = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// If nothing restored but a preference exists, try to apply it
|
||||||
}
|
if (!restored) {
|
||||||
|
const pref = this.userPreferences.getPreference('subtitleLanguage');
|
||||||
|
if (pref) {
|
||||||
|
for (let i = 0; i < tt2.length; i++) {
|
||||||
|
const t = tt2[i];
|
||||||
|
if (t.kind === 'subtitles' && (t.language === pref || t.srclang === pref)) {
|
||||||
|
t.mode = 'showing';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
// Sync UI
|
||||||
|
this.refreshSubtitlesSubmenu();
|
||||||
|
this.updateCurrentSubtitleDisplay();
|
||||||
|
player.trigger('texttrackchange');
|
||||||
|
}, 150);
|
||||||
|
|
||||||
// Ensure Subtitles (CC) button remains visible after source switch
|
// Ensure Subtitles (CC) button remains visible after source switch
|
||||||
try {
|
try {
|
||||||
const controlBar = player.getChild("controlBar");
|
const controlBar = player.getChild('controlBar');
|
||||||
const names = [
|
const names = ['subtitlesButton','textTrackButton','subsCapsButton'];
|
||||||
"subtitlesButton",
|
|
||||||
"textTrackButton",
|
|
||||||
"subsCapsButton",
|
|
||||||
];
|
|
||||||
for (const n of names) {
|
for (const n of names) {
|
||||||
const btn = controlBar && controlBar.getChild(n);
|
const btn = controlBar && controlBar.getChild(n);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
if (typeof btn.show === "function") btn.show();
|
if (typeof btn.show === 'function') btn.show();
|
||||||
const el = btn.el && btn.el();
|
const el = btn.el && btn.el();
|
||||||
if (el) {
|
if (el) { el.style.display = ''; el.style.visibility = ''; }
|
||||||
el.style.display = "";
|
|
||||||
el.style.visibility = "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
// noop
|
|
||||||
}
|
|
||||||
|
|
||||||
player.removeClass("vjs-changing-resolution");
|
player.removeClass('vjs-changing-resolution');
|
||||||
player.off("loadedmetadata", onLoaded);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
player.on("loadedmetadata", onLoaded);
|
// Wait for metadata/data to be ready, then restore
|
||||||
|
const onLoadedMeta = () => {
|
||||||
|
player.off('loadedmetadata', onLoadedMeta);
|
||||||
|
// Some browsers need loadeddata to have text track list ready
|
||||||
|
player.one('loadeddata', finishRestore);
|
||||||
|
};
|
||||||
|
player.one('loadedmetadata', onLoadedMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close only the quality submenu (keep overlay open)
|
// Close only the quality submenu (keep overlay open)
|
||||||
@ -909,6 +1022,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
|
|
||||||
// Save preference via UserPreferences (force set)
|
// Save preference via UserPreferences (force set)
|
||||||
this.userPreferences.setPreference('subtitleLanguage', lang || null, true);
|
this.userPreferences.setPreference('subtitleLanguage', lang || null, true);
|
||||||
|
this.userPreferences.setPreference('subtitleEnabled', !!lang, true); // for iphones
|
||||||
|
|
||||||
// Update UI selection
|
// Update UI selection
|
||||||
this.subtitlesSubmenu.querySelectorAll('.subtitle-option').forEach((opt) => {
|
this.subtitlesSubmenu.querySelectorAll('.subtitle-option').forEach((opt) => {
|
||||||
@ -932,30 +1046,57 @@ class CustomSettingsMenu extends Component {
|
|||||||
|
|
||||||
restoreSubtitlePreference() {
|
restoreSubtitlePreference() {
|
||||||
const savedLanguage = this.userPreferences.getPreference('subtitleLanguage');
|
const savedLanguage = this.userPreferences.getPreference('subtitleLanguage');
|
||||||
|
|
||||||
if (savedLanguage) {
|
if (savedLanguage) {
|
||||||
setTimeout(() => {
|
const tryRestore = (attempt = 1) => {
|
||||||
const player = this.player();
|
try {
|
||||||
const tracks = player.textTracks();
|
const player = this.player();
|
||||||
|
const tracks = player.textTracks();
|
||||||
for (let i = 0; i < tracks.length; i++) {
|
const saved = String(savedLanguage || '').toLowerCase();
|
||||||
const track = tracks[i];
|
// First disable all subtitle tracks
|
||||||
if (track.kind === 'subtitles') {
|
for (let i = 0; i < tracks.length; i++) {
|
||||||
track.mode = 'disabled';
|
const t = tracks[i];
|
||||||
|
if (t.kind === 'subtitles') t.mode = 'disabled';
|
||||||
}
|
}
|
||||||
}
|
// Helper for robust language matching (language or srclang; en vs en-US)
|
||||||
|
const matches = (t) => {
|
||||||
|
const tl = String(t.language || t.srclang || '').toLowerCase();
|
||||||
|
if (!tl || !saved) return false;
|
||||||
|
return tl === saved || tl.startsWith(saved + '-') || saved.startsWith(tl + '-');
|
||||||
|
};
|
||||||
|
|
||||||
for (let i = 0; i < tracks.length; i++) {
|
let restored = false;
|
||||||
const track = tracks[i];
|
for (let i = 0; i < tracks.length; i++) {
|
||||||
if (track.kind === 'subtitles' && track.language === savedLanguage) {
|
const t = tracks[i];
|
||||||
track.mode = 'showing';
|
if (t.kind === 'subtitles' && matches(t)) {
|
||||||
console.log('✓ Restored subtitle preference:', savedLanguage, track.label);
|
t.mode = 'showing';
|
||||||
this.refreshSubtitlesSubmenu();
|
restored = true;
|
||||||
break;
|
// Persist enabled flag so iOS applies on next load
|
||||||
|
try { this.userPreferences.setPreference('subtitleEnabled', true, true); } catch (e) { }
|
||||||
|
// Refresh UI
|
||||||
|
this.refreshSubtitlesSubmenu();
|
||||||
|
this.updateCurrentSubtitleDisplay();
|
||||||
|
try { player.trigger('texttrackchange'); } catch (e) { }
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!restored && attempt < 8) {
|
||||||
|
// Retry with incremental delay for iOS where tracks may not be ready
|
||||||
|
const delay = 150 * attempt;
|
||||||
|
setTimeout(() => tryRestore(attempt + 1), delay);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (attempt < 8) setTimeout(() => tryRestore(attempt + 1), 150 * attempt);
|
||||||
}
|
}
|
||||||
}, 500);
|
};
|
||||||
|
setTimeout(() => tryRestore(1), 300);
|
||||||
|
try {
|
||||||
|
const p = this.player();
|
||||||
|
const once = (ev) => p.one(ev, () => setTimeout(() => tryRestore(1), 50));
|
||||||
|
once('loadedmetadata');
|
||||||
|
once('loadeddata');
|
||||||
|
once('canplay');
|
||||||
|
} catch (e) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user