mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-10 17:38:54 -05:00
feat: Focus the player element so keyboard controls work
This commit is contained in:
parent
738d0d9e00
commit
e3291a5d75
@ -770,6 +770,18 @@ button {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
outline: none; /* Remove default browser focus outline */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom focus styles for video player */
|
||||||
|
.video-js:focus {
|
||||||
|
box-shadow: 0 0 0 3px rgba(25, 153, 50, 0.5);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure video player is focusable */
|
||||||
|
.video-js[tabindex] {
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fullscreen video-js player styles for embedded video player */
|
/* Fullscreen video-js player styles for embedded video player */
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
// components/controls/CustomSettingsMenu.js
|
// components/controls/CustomSettingsMenu.js
|
||||||
import videojs from "video.js";
|
import videojs from 'video.js';
|
||||||
import "./CustomSettingsMenu.css";
|
import './CustomSettingsMenu.css';
|
||||||
import UserPreferences from "../../utils/UserPreferences";
|
import UserPreferences from '../../utils/UserPreferences';
|
||||||
|
|
||||||
// Get the Component base class from Video.js
|
// Get the Component base class from Video.js
|
||||||
const Component = videojs.getComponent("Component");
|
const Component = videojs.getComponent('Component');
|
||||||
|
|
||||||
class CustomSettingsMenu extends Component {
|
class CustomSettingsMenu extends Component {
|
||||||
constructor(player, options) {
|
constructor(player, options) {
|
||||||
@ -45,14 +45,14 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createSettingsButton() {
|
createSettingsButton() {
|
||||||
const controlBar = this.player().getChild("controlBar");
|
const controlBar = this.player().getChild('controlBar');
|
||||||
|
|
||||||
// Do NOT hide default playback rate button to avoid control bar layout shifts
|
// Do NOT hide default playback rate button to avoid control bar layout shifts
|
||||||
|
|
||||||
// Create settings button
|
// Create settings button
|
||||||
this.settingsButton = controlBar.addChild("button", {
|
this.settingsButton = controlBar.addChild('button', {
|
||||||
controlText: "Settings",
|
controlText: 'Settings',
|
||||||
className: "vjs-settings-button settings-clicked",
|
className: 'vjs-settings-button settings-clicked',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Style the settings button (gear icon)
|
// Style the settings button (gear icon)
|
||||||
@ -79,15 +79,21 @@ class CustomSettingsMenu extends Component {
|
|||||||
let touchHandled = false;
|
let touchHandled = false;
|
||||||
|
|
||||||
// Handle touchstart
|
// Handle touchstart
|
||||||
buttonEl.addEventListener('touchstart', (e) => {
|
buttonEl.addEventListener(
|
||||||
|
'touchstart',
|
||||||
|
(e) => {
|
||||||
touchStartTime = Date.now();
|
touchStartTime = Date.now();
|
||||||
touchHandled = false;
|
touchHandled = false;
|
||||||
const touch = e.touches[0];
|
const touch = e.touches[0];
|
||||||
touchStartPos = { x: touch.clientX, y: touch.clientY };
|
touchStartPos = { x: touch.clientX, y: touch.clientY };
|
||||||
}, { passive: true });
|
},
|
||||||
|
{ passive: true }
|
||||||
|
);
|
||||||
|
|
||||||
// Handle touchend with proper passive handling
|
// Handle touchend with proper passive handling
|
||||||
buttonEl.addEventListener('touchend', (e) => {
|
buttonEl.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
(e) => {
|
||||||
const touchEndTime = Date.now();
|
const touchEndTime = Date.now();
|
||||||
const touchDuration = touchEndTime - touchStartTime;
|
const touchDuration = touchEndTime - touchStartTime;
|
||||||
|
|
||||||
@ -96,8 +102,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
const touch = e.changedTouches[0];
|
const touch = e.changedTouches[0];
|
||||||
const touchEndPos = { x: touch.clientX, y: touch.clientY };
|
const touchEndPos = { x: touch.clientX, y: touch.clientY };
|
||||||
const distance = Math.sqrt(
|
const distance = Math.sqrt(
|
||||||
Math.pow(touchEndPos.x - touchStartPos.x, 2) +
|
Math.pow(touchEndPos.x - touchStartPos.x, 2) + Math.pow(touchEndPos.y - touchStartPos.y, 2)
|
||||||
Math.pow(touchEndPos.y - touchStartPos.y, 2)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Only trigger if it's a tap (not a swipe)
|
// Only trigger if it's a tap (not a swipe)
|
||||||
@ -108,7 +113,9 @@ class CustomSettingsMenu extends Component {
|
|||||||
this.toggleSettings(e);
|
this.toggleSettings(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, { passive: false });
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
// Handle click events (desktop and mobile fallback)
|
// Handle click events (desktop and mobile fallback)
|
||||||
buttonEl.addEventListener('click', (e) => {
|
buttonEl.addEventListener('click', (e) => {
|
||||||
@ -124,39 +131,34 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createSettingsOverlay() {
|
createSettingsOverlay() {
|
||||||
const controlBar = this.player().getChild("controlBar");
|
const controlBar = this.player().getChild('controlBar');
|
||||||
|
|
||||||
// Create settings overlay
|
// Create settings overlay
|
||||||
this.settingsOverlay = document.createElement("div");
|
this.settingsOverlay = document.createElement('div');
|
||||||
this.settingsOverlay.className = "custom-settings-overlay";
|
this.settingsOverlay.className = 'custom-settings-overlay';
|
||||||
|
|
||||||
// Get current preferences for display
|
// Get current preferences for display
|
||||||
const currentPlaybackRate =
|
const currentPlaybackRate = this.userPreferences.getPreference('playbackRate');
|
||||||
this.userPreferences.getPreference("playbackRate");
|
const currentQuality = this.userPreferences.getPreference('quality');
|
||||||
const currentQuality = this.userPreferences.getPreference("quality");
|
|
||||||
// Find current subtitle selection for label
|
// Find current subtitle selection for label
|
||||||
let currentSubtitleLabel = "Off";
|
let currentSubtitleLabel = 'Off';
|
||||||
try {
|
try {
|
||||||
const tt = this.player().textTracks();
|
const tt = this.player().textTracks();
|
||||||
for (let i = 0; i < tt.length; i++) {
|
for (let i = 0; i < tt.length; i++) {
|
||||||
const t = tt[i];
|
const t = tt[i];
|
||||||
if (t.kind === "subtitles" && t.mode === "showing") {
|
if (t.kind === 'subtitles' && t.mode === 'showing') {
|
||||||
currentSubtitleLabel = t.label || t.language || "Subtitles";
|
currentSubtitleLabel = t.label || t.language || 'Subtitles';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
// Format playback rate for display
|
// Format playback rate for display
|
||||||
const playbackRateLabel =
|
const playbackRateLabel = currentPlaybackRate === 1 ? 'Normal' : `${currentPlaybackRate}`;
|
||||||
currentPlaybackRate === 1 ? "Normal" : `${currentPlaybackRate}`;
|
|
||||||
const qualities = this.getAvailableQualities();
|
const qualities = this.getAvailableQualities();
|
||||||
const activeQuality =
|
const activeQuality = qualities.find((q) => q.value === currentQuality) || qualities[0];
|
||||||
qualities.find((q) => q.value === currentQuality) || qualities[0];
|
|
||||||
const qualityLabelHTML =
|
const qualityLabelHTML =
|
||||||
activeQuality?.displayLabel ||
|
activeQuality?.displayLabel || activeQuality?.label || (currentQuality ? String(currentQuality) : 'Auto');
|
||||||
activeQuality?.label ||
|
|
||||||
(currentQuality ? String(currentQuality) : "Auto");
|
|
||||||
|
|
||||||
// Settings menu content - split into separate variables for maintainability
|
// Settings menu content - split into separate variables for maintainability
|
||||||
const settingsHeader = `
|
const settingsHeader = `
|
||||||
@ -233,21 +235,21 @@ class CustomSettingsMenu extends Component {
|
|||||||
|
|
||||||
createSpeedSubmenu() {
|
createSpeedSubmenu() {
|
||||||
const speedOptions = [
|
const speedOptions = [
|
||||||
{ label: "0.25", value: 0.25 },
|
{ label: '0.25', value: 0.25 },
|
||||||
{ label: "0.5", value: 0.5 },
|
{ label: '0.5', value: 0.5 },
|
||||||
{ label: "0.75", value: 0.75 },
|
{ label: '0.75', value: 0.75 },
|
||||||
{ label: "Normal", value: 1 },
|
{ label: 'Normal', value: 1 },
|
||||||
{ label: "1.25", value: 1.25 },
|
{ label: '1.25', value: 1.25 },
|
||||||
{ label: "1.5", value: 1.5 },
|
{ label: '1.5', value: 1.5 },
|
||||||
{ label: "1.75", value: 1.75 },
|
{ label: '1.75', value: 1.75 },
|
||||||
{ label: "2", value: 2 },
|
{ label: '2', value: 2 },
|
||||||
];
|
];
|
||||||
|
|
||||||
this.speedSubmenu = document.createElement("div");
|
this.speedSubmenu = document.createElement('div');
|
||||||
this.speedSubmenu.className = "speed-submenu";
|
this.speedSubmenu.className = 'speed-submenu';
|
||||||
|
|
||||||
// Get current playback rate for highlighting
|
// Get current playback rate for highlighting
|
||||||
const currentRate = this.userPreferences.getPreference("playbackRate");
|
const currentRate = this.userPreferences.getPreference('playbackRate');
|
||||||
|
|
||||||
this.speedSubmenu.innerHTML = `
|
this.speedSubmenu.innerHTML = `
|
||||||
<div class="submenu-header">
|
<div class="submenu-header">
|
||||||
@ -257,21 +259,21 @@ class CustomSettingsMenu extends Component {
|
|||||||
${speedOptions
|
${speedOptions
|
||||||
.map(
|
.map(
|
||||||
(option) => `
|
(option) => `
|
||||||
<div class="speed-option ${option.value === currentRate ? "active" : ""}" data-speed="${option.value}">
|
<div class="speed-option ${option.value === currentRate ? 'active' : ''}" data-speed="${option.value}">
|
||||||
<span>${option.label}</span>
|
<span>${option.label}</span>
|
||||||
${option.value === currentRate ? '<span class="checkmark">✓</span>' : ""}
|
${option.value === currentRate ? '<span class="checkmark">✓</span>' : ''}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
.join("")}
|
.join('')}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this.settingsOverlay.appendChild(this.speedSubmenu);
|
this.settingsOverlay.appendChild(this.speedSubmenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
createQualitySubmenu(qualities, currentValue) {
|
createQualitySubmenu(qualities, currentValue) {
|
||||||
this.qualitySubmenu = document.createElement("div");
|
this.qualitySubmenu = document.createElement('div');
|
||||||
this.qualitySubmenu.className = "quality-submenu";
|
this.qualitySubmenu.className = 'quality-submenu';
|
||||||
|
|
||||||
const header = `
|
const header = `
|
||||||
<div class="submenu-header">
|
<div class="submenu-header">
|
||||||
@ -283,21 +285,21 @@ class CustomSettingsMenu extends Component {
|
|||||||
const optionsHtml = qualities
|
const optionsHtml = qualities
|
||||||
.map(
|
.map(
|
||||||
(q) => `
|
(q) => `
|
||||||
<div class="quality-option ${q.value === currentValue ? "active" : ""}" data-quality="${q.value}">
|
<div class="quality-option ${q.value === currentValue ? 'active' : ''}" data-quality="${q.value}">
|
||||||
<span class="quality-label">${q.displayLabel || q.label}</span>
|
<span class="quality-label">${q.displayLabel || q.label}</span>
|
||||||
${q.value === currentValue ? '<span class="checkmark">✓</span>' : ""}
|
${q.value === currentValue ? '<span class="checkmark">✓</span>' : ''}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
.join("");
|
.join('');
|
||||||
|
|
||||||
this.qualitySubmenu.innerHTML = header + optionsHtml;
|
this.qualitySubmenu.innerHTML = header + optionsHtml;
|
||||||
this.settingsOverlay.appendChild(this.qualitySubmenu);
|
this.settingsOverlay.appendChild(this.qualitySubmenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
createSubtitlesSubmenu() {
|
createSubtitlesSubmenu() {
|
||||||
this.subtitlesSubmenu = document.createElement("div");
|
this.subtitlesSubmenu = document.createElement('div');
|
||||||
this.subtitlesSubmenu.className = "subtitles-submenu";
|
this.subtitlesSubmenu.className = 'subtitles-submenu';
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
const header = `
|
const header = `
|
||||||
@ -341,12 +343,16 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.innerHTML = items.map((it) => `
|
body.innerHTML = items
|
||||||
|
.map(
|
||||||
|
(it) => `
|
||||||
<div class="subtitle-option ${it.lang === activeLang ? 'active' : ''}" data-lang="${it.lang || ''}">
|
<div class="subtitle-option ${it.lang === activeLang ? 'active' : ''}" data-lang="${it.lang || ''}">
|
||||||
<span>${it.label}</span>
|
<span>${it.label}</span>
|
||||||
${it.lang === activeLang ? '<span class="checkmark">✓</span>' : ''}
|
${it.lang === activeLang ? '<span class="checkmark">✓</span>' : ''}
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`
|
||||||
|
)
|
||||||
|
.join('');
|
||||||
|
|
||||||
// Also update the current subtitle display in main settings
|
// Also update the current subtitle display in main settings
|
||||||
this.updateCurrentSubtitleDisplay();
|
this.updateCurrentSubtitleDisplay();
|
||||||
@ -356,14 +362,14 @@ class CustomSettingsMenu extends Component {
|
|||||||
try {
|
try {
|
||||||
const player = this.player();
|
const player = this.player();
|
||||||
const tracks = player.textTracks();
|
const tracks = player.textTracks();
|
||||||
let currentSubtitleLabel = "Off";
|
let currentSubtitleLabel = 'Off';
|
||||||
let activeTrack = null;
|
let activeTrack = null;
|
||||||
|
|
||||||
// Find the active subtitle track
|
// Find the active subtitle track
|
||||||
for (let i = 0; i < tracks.length; i++) {
|
for (let i = 0; i < tracks.length; i++) {
|
||||||
const t = tracks[i];
|
const t = tracks[i];
|
||||||
if (t.kind === 'subtitles' && t.mode === 'showing') {
|
if (t.kind === 'subtitles' && t.mode === 'showing') {
|
||||||
currentSubtitleLabel = t.label || t.language || "Subtitles";
|
currentSubtitleLabel = t.label || t.language || 'Subtitles';
|
||||||
activeTrack = t;
|
activeTrack = t;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -378,11 +384,11 @@ class CustomSettingsMenu extends Component {
|
|||||||
if (oldValue !== currentSubtitleLabel) {
|
if (oldValue !== currentSubtitleLabel) {
|
||||||
console.log(`Updated current subtitle display: "${oldValue}" → "${currentSubtitleLabel}"`);
|
console.log(`Updated current subtitle display: "${oldValue}" → "${currentSubtitleLabel}"`);
|
||||||
if (activeTrack) {
|
if (activeTrack) {
|
||||||
console.log(`Active track details: language="${activeTrack.language}", label="${activeTrack.label}", mode="${activeTrack.mode}"`);
|
console.log(
|
||||||
|
`Active track details: language="${activeTrack.language}", label="${activeTrack.label}", mode="${activeTrack.mode}"`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.warn('Could not find .current-subtitles element in settings overlay');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating current subtitle display:', error);
|
console.error('Error updating current subtitle display:', error);
|
||||||
@ -420,7 +426,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
dispose() {
|
dispose() {
|
||||||
this.stopSubtitleSync();
|
this.stopSubtitleSync();
|
||||||
// Remove event listeners
|
// Remove event listeners
|
||||||
document.removeEventListener("click", this.handleClickOutside);
|
document.removeEventListener('click', this.handleClickOutside);
|
||||||
// Remove text track change listener
|
// Remove text track change listener
|
||||||
if (this.player()) {
|
if (this.player()) {
|
||||||
this.player().off('texttrackchange');
|
this.player().off('texttrackchange');
|
||||||
@ -429,36 +435,22 @@ class CustomSettingsMenu extends Component {
|
|||||||
|
|
||||||
getAvailableQualities() {
|
getAvailableQualities() {
|
||||||
// Priority: provided options -> MEDIA_DATA JSON -> player sources -> default
|
// Priority: provided options -> MEDIA_DATA JSON -> player sources -> default
|
||||||
const desiredOrder = [
|
const desiredOrder = ['auto', '144p', '240p', '360p', '480p', '720p', '1080p'];
|
||||||
"auto",
|
|
||||||
"144p",
|
|
||||||
"240p",
|
|
||||||
"360p",
|
|
||||||
"480p",
|
|
||||||
"720p",
|
|
||||||
"1080p",
|
|
||||||
];
|
|
||||||
|
|
||||||
if (
|
if (Array.isArray(this.providedQualities) && this.providedQualities.length) {
|
||||||
Array.isArray(this.providedQualities) &&
|
return this.sortAndDecorateQualities(this.providedQualities, desiredOrder);
|
||||||
this.providedQualities.length
|
|
||||||
) {
|
|
||||||
return this.sortAndDecorateQualities(
|
|
||||||
this.providedQualities,
|
|
||||||
desiredOrder
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const md = typeof window !== "undefined" ? window.MEDIA_DATA : null;
|
const md = typeof window !== 'undefined' ? window.MEDIA_DATA : null;
|
||||||
const jsonQualities = md?.data?.qualities;
|
const jsonQualities = md?.data?.qualities;
|
||||||
if (Array.isArray(jsonQualities) && jsonQualities.length) {
|
if (Array.isArray(jsonQualities) && jsonQualities.length) {
|
||||||
// Expected format: [{label: '1080p', value: '1080p', src: '...'}]
|
// Expected format: [{label: '1080p', value: '1080p', src: '...'}]
|
||||||
const normalized = jsonQualities.map((q) => ({
|
const normalized = jsonQualities.map((q) => ({
|
||||||
label: q.label || q.value || "Auto",
|
label: q.label || q.value || 'Auto',
|
||||||
value: (q.value || q.label || "auto").toString().toLowerCase(),
|
value: (q.value || q.label || 'auto').toString().toLowerCase(),
|
||||||
src: q.src || q.url || q.href,
|
src: q.src || q.url || q.href,
|
||||||
type: q.type || "video/mp4",
|
type: q.type || 'video/mp4',
|
||||||
}));
|
}));
|
||||||
return this.sortAndDecorateQualities(normalized, desiredOrder);
|
return this.sortAndDecorateQualities(normalized, desiredOrder);
|
||||||
}
|
}
|
||||||
@ -467,18 +459,13 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Derive from player's current sources
|
// Derive from player's current sources
|
||||||
const sources = this.player().currentSources
|
const sources = this.player().currentSources ? this.player().currentSources() : this.player().currentSrc();
|
||||||
? this.player().currentSources()
|
|
||||||
: this.player().currentSrc();
|
|
||||||
if (Array.isArray(sources) && sources.length > 0) {
|
if (Array.isArray(sources) && sources.length > 0) {
|
||||||
const mapped = sources.map((s, idx) => {
|
const mapped = sources.map((s, idx) => {
|
||||||
const label =
|
const label =
|
||||||
s.label ||
|
s.label || s.res || this.inferLabelFromSrc(s.src) || (idx === 0 ? 'Auto' : `Source ${idx + 1}`);
|
||||||
s.res ||
|
|
||||||
this.inferLabelFromSrc(s.src) ||
|
|
||||||
(idx === 0 ? "Auto" : `Source ${idx + 1}`);
|
|
||||||
const value = String(label).toLowerCase();
|
const value = String(label).toLowerCase();
|
||||||
return { label, value, src: s.src, type: s.type || "video/mp4" };
|
return { label, value, src: s.src, type: s.type || 'video/mp4' };
|
||||||
});
|
});
|
||||||
return this.sortAndDecorateQualities(mapped, desiredOrder);
|
return this.sortAndDecorateQualities(mapped, desiredOrder);
|
||||||
}
|
}
|
||||||
@ -494,16 +481,14 @@ class CustomSettingsMenu extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Only include qualities that have actual sources
|
// Only include qualities that have actual sources
|
||||||
const validQualities = list.filter(q => q.src);
|
const validQualities = list.filter((q) => q.src);
|
||||||
|
|
||||||
const decorated = validQualities
|
const decorated = validQualities
|
||||||
.map((q) => {
|
.map((q) => {
|
||||||
const val = (q.value || q.label || "").toString().toLowerCase();
|
const val = (q.value || q.label || '').toString().toLowerCase();
|
||||||
const baseLabel = q.label || q.value || "";
|
const baseLabel = q.label || q.value || '';
|
||||||
const is1080 = val === "1080p";
|
const is1080 = val === '1080p';
|
||||||
const displayLabel = is1080
|
const displayLabel = is1080 ? `${baseLabel} <sup class="hd-badge">HD</sup>` : baseLabel;
|
||||||
? `${baseLabel} <sup class="hd-badge">HD</sup>`
|
|
||||||
: baseLabel;
|
|
||||||
return { ...q, value: val, label: baseLabel, displayLabel };
|
return { ...q, value: val, label: baseLabel, displayLabel };
|
||||||
})
|
})
|
||||||
.sort((a, b) => orderIndex(a.value) - orderIndex(b.value));
|
.sort((a, b) => orderIndex(a.value) - orderIndex(b.value));
|
||||||
@ -514,9 +499,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
inferLabelFromSrc(src) {
|
inferLabelFromSrc(src) {
|
||||||
if (!src) return null;
|
if (!src) return null;
|
||||||
// Try to detect typical resolution markers in file name or query string
|
// Try to detect typical resolution markers in file name or query string
|
||||||
const match = /(?:_|\.|\/)\D*(1440p|1080p|720p|480p|360p|240p|144p)/i.exec(
|
const match = /(?:_|\.|\/)\D*(1440p|1080p|720p|480p|360p|240p|144p)/i.exec(src);
|
||||||
src
|
|
||||||
);
|
|
||||||
if (match && match[1]) return match[1].toUpperCase();
|
if (match && match[1]) return match[1].toUpperCase();
|
||||||
const m2 = /(\b\d{3,4})p\b/i.exec(src);
|
const m2 = /(\b\d{3,4})p\b/i.exec(src);
|
||||||
if (m2 && m2[1]) return `${m2[1]}p`;
|
if (m2 && m2[1]) return `${m2[1]}p`;
|
||||||
@ -524,8 +507,8 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
positionButton() {
|
positionButton() {
|
||||||
const controlBar = this.player().getChild("controlBar");
|
const controlBar = this.player().getChild('controlBar');
|
||||||
const fullscreenToggle = controlBar.getChild("fullscreenToggle");
|
const fullscreenToggle = controlBar.getChild('fullscreenToggle');
|
||||||
|
|
||||||
if (this.settingsButton && fullscreenToggle) {
|
if (this.settingsButton && fullscreenToggle) {
|
||||||
// Small delay to ensure all buttons are created
|
// Small delay to ensure all buttons are created
|
||||||
@ -533,7 +516,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle);
|
const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle);
|
||||||
controlBar.removeChild(this.settingsButton);
|
controlBar.removeChild(this.settingsButton);
|
||||||
controlBar.addChild(this.settingsButton, {}, fullscreenIndex + 1);
|
controlBar.addChild(this.settingsButton, {}, fullscreenIndex + 1);
|
||||||
console.log("✓ Settings button positioned after fullscreen toggle");
|
console.log('✓ Settings button positioned after fullscreen toggle');
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -544,117 +527,148 @@ class CustomSettingsMenu extends Component {
|
|||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
const closeFunction = (e) => {
|
const closeFunction = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.settingsOverlay.classList.remove("show");
|
this.settingsOverlay.classList.remove('show');
|
||||||
this.settingsOverlay.style.display = "none";
|
this.settingsOverlay.style.display = 'none';
|
||||||
this.speedSubmenu.style.display = "none";
|
this.speedSubmenu.style.display = 'none';
|
||||||
if (this.qualitySubmenu) this.qualitySubmenu.style.display = "none";
|
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
|
||||||
if (this.subtitlesSubmenu) this.subtitlesSubmenu.style.display = "none";
|
if (this.subtitlesSubmenu) this.subtitlesSubmenu.style.display = 'none';
|
||||||
const btnEl = this.settingsButton?.el();
|
const btnEl = this.settingsButton?.el();
|
||||||
if (btnEl) {
|
if (btnEl) {
|
||||||
btnEl.classList.remove("settings-clicked");
|
btnEl.classList.remove('settings-clicked');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
closeButton.addEventListener('click', closeFunction);
|
closeButton.addEventListener('click', closeFunction);
|
||||||
closeButton.addEventListener('touchend', (e) => {
|
closeButton.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
closeFunction(e);
|
closeFunction(e);
|
||||||
}, { passive: false });
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings item clicks
|
// Settings item clicks
|
||||||
this.settingsOverlay.addEventListener("click", (e) => {
|
this.settingsOverlay.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (e.target.closest('[data-setting="playback-speed"]')) {
|
if (e.target.closest('[data-setting="playback-speed"]')) {
|
||||||
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"]')) {
|
||||||
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"]')) {
|
||||||
this.refreshSubtitlesSubmenu();
|
this.refreshSubtitlesSubmenu();
|
||||||
this.subtitlesSubmenu.style.display = "flex";
|
this.subtitlesSubmenu.style.display = 'flex';
|
||||||
this.speedSubmenu.style.display = "none";
|
this.speedSubmenu.style.display = 'none';
|
||||||
this.qualitySubmenu.style.display = "none";
|
this.qualitySubmenu.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Touch scroll detection for settingsOverlay
|
// Touch scroll detection for settingsOverlay
|
||||||
this.settingsOverlay.addEventListener('touchstart', (e) => {
|
this.settingsOverlay.addEventListener(
|
||||||
|
'touchstart',
|
||||||
|
(e) => {
|
||||||
this.touchStartY = e.touches[0].clientY;
|
this.touchStartY = e.touches[0].clientY;
|
||||||
this.isTouchScrolling = false;
|
this.isTouchScrolling = false;
|
||||||
}, { passive: true });
|
},
|
||||||
this.settingsOverlay.addEventListener('touchmove', (e) => {
|
{ passive: true }
|
||||||
|
);
|
||||||
|
this.settingsOverlay.addEventListener(
|
||||||
|
'touchmove',
|
||||||
|
(e) => {
|
||||||
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
||||||
if (dy > 10) this.isTouchScrolling = true;
|
if (dy > 10) this.isTouchScrolling = true;
|
||||||
}, { passive: true });
|
},
|
||||||
|
{ passive: true }
|
||||||
|
);
|
||||||
// Mobile touch events for settings items (tap vs scroll)
|
// Mobile touch events for settings items (tap vs scroll)
|
||||||
this.settingsOverlay.addEventListener("touchend", (e) => {
|
this.settingsOverlay.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (this.isTouchScrolling) { this.isTouchScrolling = false; return; }
|
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();
|
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();
|
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();
|
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';
|
||||||
this.qualitySubmenu.style.display = "none";
|
this.qualitySubmenu.style.display = 'none';
|
||||||
}
|
}
|
||||||
}, { passive: false });
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
// Speed submenu header (back button)
|
// Speed submenu header (back button)
|
||||||
const speedHeader = this.speedSubmenu.querySelector(".submenu-header");
|
const speedHeader = this.speedSubmenu.querySelector('.submenu-header');
|
||||||
speedHeader.addEventListener("click", () => {
|
speedHeader.addEventListener('click', () => {
|
||||||
this.speedSubmenu.style.display = "none";
|
this.speedSubmenu.style.display = 'none';
|
||||||
});
|
});
|
||||||
speedHeader.addEventListener("touchend", (e) => {
|
speedHeader.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.speedSubmenu.style.display = "none";
|
this.speedSubmenu.style.display = 'none';
|
||||||
}, { passive: false });
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
// Quality submenu header (back button)
|
// Quality submenu header (back button)
|
||||||
const qualityHeader = this.qualitySubmenu.querySelector(".submenu-header");
|
const qualityHeader = this.qualitySubmenu.querySelector('.submenu-header');
|
||||||
qualityHeader.addEventListener("click", () => {
|
qualityHeader.addEventListener('click', () => {
|
||||||
this.qualitySubmenu.style.display = "none";
|
this.qualitySubmenu.style.display = 'none';
|
||||||
});
|
});
|
||||||
qualityHeader.addEventListener("touchend", (e) => {
|
qualityHeader.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.qualitySubmenu.style.display = "none";
|
this.qualitySubmenu.style.display = 'none';
|
||||||
}, { passive: false });
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
// Subtitles submenu header (back)
|
// Subtitles submenu header (back)
|
||||||
const subtitlesHeader = this.subtitlesSubmenu.querySelector(".submenu-header");
|
const subtitlesHeader = this.subtitlesSubmenu.querySelector('.submenu-header');
|
||||||
subtitlesHeader.addEventListener("click", () => {
|
subtitlesHeader.addEventListener('click', () => {
|
||||||
this.subtitlesSubmenu.style.display = "none";
|
this.subtitlesSubmenu.style.display = 'none';
|
||||||
});
|
});
|
||||||
subtitlesHeader.addEventListener("touchend", (e) => {
|
subtitlesHeader.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.subtitlesSubmenu.style.display = "none";
|
this.subtitlesSubmenu.style.display = 'none';
|
||||||
}, { passive: false });
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
// Speed option clicks
|
// Speed option clicks
|
||||||
this.speedSubmenu.addEventListener("click", (e) => {
|
this.speedSubmenu.addEventListener('click', (e) => {
|
||||||
const speedOption = e.target.closest(".speed-option");
|
const speedOption = e.target.closest('.speed-option');
|
||||||
if (speedOption) {
|
if (speedOption) {
|
||||||
const speed = parseFloat(speedOption.dataset.speed);
|
const speed = parseFloat(speedOption.dataset.speed);
|
||||||
this.handleSpeedChange(speed, speedOption);
|
this.handleSpeedChange(speed, speedOption);
|
||||||
@ -662,54 +676,84 @@ class CustomSettingsMenu extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Touch scroll detection for speed submenu
|
// Touch scroll detection for speed submenu
|
||||||
this.speedSubmenu.addEventListener('touchstart', (e) => {
|
this.speedSubmenu.addEventListener(
|
||||||
|
'touchstart',
|
||||||
|
(e) => {
|
||||||
this.touchStartY = e.touches[0].clientY;
|
this.touchStartY = e.touches[0].clientY;
|
||||||
this.isTouchScrolling = false;
|
this.isTouchScrolling = false;
|
||||||
}, { passive: true });
|
},
|
||||||
this.speedSubmenu.addEventListener('touchmove', (e) => {
|
{ passive: true }
|
||||||
|
);
|
||||||
|
this.speedSubmenu.addEventListener(
|
||||||
|
'touchmove',
|
||||||
|
(e) => {
|
||||||
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
||||||
if (dy > 10) this.isTouchScrolling = true;
|
if (dy > 10) this.isTouchScrolling = true;
|
||||||
}, { passive: true });
|
},
|
||||||
|
{ passive: true }
|
||||||
|
);
|
||||||
// Mobile touch events for speed options (tap vs scroll)
|
// Mobile touch events for speed options (tap vs scroll)
|
||||||
this.speedSubmenu.addEventListener("touchend", (e) => {
|
this.speedSubmenu.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (this.isTouchScrolling) { this.isTouchScrolling = false; return; }
|
if (this.isTouchScrolling) {
|
||||||
const speedOption = e.target.closest(".speed-option");
|
this.isTouchScrolling = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const speedOption = e.target.closest('.speed-option');
|
||||||
if (speedOption) {
|
if (speedOption) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const speed = parseFloat(speedOption.dataset.speed);
|
const speed = parseFloat(speedOption.dataset.speed);
|
||||||
this.handleSpeedChange(speed, speedOption);
|
this.handleSpeedChange(speed, speedOption);
|
||||||
}
|
}
|
||||||
}, { passive: false });
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
// Quality option clicks
|
// Quality option clicks
|
||||||
this.qualitySubmenu.addEventListener("click", (e) => {
|
this.qualitySubmenu.addEventListener('click', (e) => {
|
||||||
const qualityOption = e.target.closest(".quality-option");
|
const qualityOption = e.target.closest('.quality-option');
|
||||||
if (qualityOption) {
|
if (qualityOption) {
|
||||||
const value = qualityOption.dataset.quality;
|
const value = qualityOption.dataset.quality;
|
||||||
this.handleQualityChange(value, qualityOption);
|
this.handleQualityChange(value, qualityOption);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.qualitySubmenu.addEventListener('touchstart', (e) => {
|
this.qualitySubmenu.addEventListener(
|
||||||
|
'touchstart',
|
||||||
|
(e) => {
|
||||||
this.touchStartY = e.touches[0].clientY;
|
this.touchStartY = e.touches[0].clientY;
|
||||||
this.isTouchScrolling = false;
|
this.isTouchScrolling = false;
|
||||||
}, { passive: true });
|
},
|
||||||
this.qualitySubmenu.addEventListener('touchmove', (e) => {
|
{ passive: true }
|
||||||
|
);
|
||||||
|
this.qualitySubmenu.addEventListener(
|
||||||
|
'touchmove',
|
||||||
|
(e) => {
|
||||||
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
||||||
if (dy > 10) this.isTouchScrolling = true;
|
if (dy > 10) this.isTouchScrolling = true;
|
||||||
}, { passive: true });
|
},
|
||||||
|
{ passive: true }
|
||||||
|
);
|
||||||
// Mobile touch events for quality options (tap vs scroll)
|
// Mobile touch events for quality options (tap vs scroll)
|
||||||
this.qualitySubmenu.addEventListener("touchend", (e) => {
|
this.qualitySubmenu.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (this.isTouchScrolling) { this.isTouchScrolling = false; return; }
|
if (this.isTouchScrolling) {
|
||||||
const qualityOption = e.target.closest(".quality-option");
|
this.isTouchScrolling = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const qualityOption = e.target.closest('.quality-option');
|
||||||
if (qualityOption) {
|
if (qualityOption) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const value = qualityOption.dataset.quality;
|
const value = qualityOption.dataset.quality;
|
||||||
this.handleQualityChange(value, qualityOption);
|
this.handleQualityChange(value, qualityOption);
|
||||||
}
|
}
|
||||||
}, { passive: false });
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
// Subtitle option clicks
|
// Subtitle option clicks
|
||||||
this.subtitlesSubmenu.addEventListener('click', (e) => {
|
this.subtitlesSubmenu.addEventListener('click', (e) => {
|
||||||
@ -721,41 +765,56 @@ class CustomSettingsMenu extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Touch scroll detection for subtitles submenu
|
// Touch scroll detection for subtitles submenu
|
||||||
this.subtitlesSubmenu.addEventListener('touchstart', (e) => {
|
this.subtitlesSubmenu.addEventListener(
|
||||||
|
'touchstart',
|
||||||
|
(e) => {
|
||||||
this.touchStartY = e.touches[0].clientY;
|
this.touchStartY = e.touches[0].clientY;
|
||||||
this.isTouchScrolling = false;
|
this.isTouchScrolling = false;
|
||||||
}, { passive: true });
|
},
|
||||||
this.subtitlesSubmenu.addEventListener('touchmove', (e) => {
|
{ passive: true }
|
||||||
|
);
|
||||||
|
this.subtitlesSubmenu.addEventListener(
|
||||||
|
'touchmove',
|
||||||
|
(e) => {
|
||||||
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
const dy = Math.abs(e.touches[0].clientY - this.touchStartY);
|
||||||
if (dy > 10) this.isTouchScrolling = true;
|
if (dy > 10) this.isTouchScrolling = true;
|
||||||
}, { passive: true });
|
},
|
||||||
|
{ passive: true }
|
||||||
|
);
|
||||||
// Mobile touch events for subtitle options (tap vs scroll)
|
// Mobile touch events for subtitle options (tap vs scroll)
|
||||||
this.subtitlesSubmenu.addEventListener('touchend', (e) => {
|
this.subtitlesSubmenu.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (this.isTouchScrolling) { this.isTouchScrolling = false; return; }
|
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();
|
e.preventDefault();
|
||||||
const lang = opt.dataset.lang || null;
|
const lang = opt.dataset.lang || null;
|
||||||
this.handleSubtitleChange(lang, opt);
|
this.handleSubtitleChange(lang, opt);
|
||||||
}
|
}
|
||||||
}, { passive: false });
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
// Close menu when clicking outside
|
// Close menu when clicking outside
|
||||||
document.addEventListener("click", this.handleClickOutside);
|
document.addEventListener('click', this.handleClickOutside);
|
||||||
|
|
||||||
// Add hover effects
|
// Add hover effects
|
||||||
this.settingsOverlay.addEventListener("mouseover", (e) => {
|
this.settingsOverlay.addEventListener('mouseover', (e) => {
|
||||||
const item = e.target.closest(".settings-item, .speed-option");
|
const item = e.target.closest('.settings-item, .speed-option');
|
||||||
if (item && !item.style.background.includes("0.1")) {
|
if (item && !item.style.background.includes('0.1')) {
|
||||||
item.style.background = "rgba(255, 255, 255, 0.05)";
|
item.style.background = 'rgba(255, 255, 255, 0.05)';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settingsOverlay.addEventListener("mouseout", (e) => {
|
this.settingsOverlay.addEventListener('mouseout', (e) => {
|
||||||
const item = e.target.closest(".settings-item, .speed-option");
|
const item = e.target.closest('.settings-item, .speed-option');
|
||||||
if (item && !item.style.background.includes("0.1")) {
|
if (item && !item.style.background.includes('0.1')) {
|
||||||
item.style.background = "transparent";
|
item.style.background = 'transparent';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -765,24 +824,24 @@ class CustomSettingsMenu extends Component {
|
|||||||
|
|
||||||
toggleSettings(e) {
|
toggleSettings(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const isVisible = this.settingsOverlay.classList.contains("show");
|
const isVisible = this.settingsOverlay.classList.contains('show');
|
||||||
|
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
this.settingsOverlay.classList.remove("show");
|
this.settingsOverlay.classList.remove('show');
|
||||||
this.settingsOverlay.style.display = "none";
|
this.settingsOverlay.style.display = 'none';
|
||||||
} else {
|
} else {
|
||||||
this.settingsOverlay.classList.add("show");
|
this.settingsOverlay.classList.add('show');
|
||||||
this.settingsOverlay.style.display = "block";
|
this.settingsOverlay.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.speedSubmenu.style.display = "none"; // Hide submenu when main menu toggles
|
this.speedSubmenu.style.display = 'none'; // Hide submenu when main menu toggles
|
||||||
if (this.qualitySubmenu) this.qualitySubmenu.style.display = "none";
|
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
|
||||||
const btnEl = this.settingsButton?.el();
|
const btnEl = this.settingsButton?.el();
|
||||||
if (btnEl) {
|
if (btnEl) {
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
btnEl.classList.add("settings-clicked");
|
btnEl.classList.add('settings-clicked');
|
||||||
} else {
|
} else {
|
||||||
btnEl.classList.remove("settings-clicked");
|
btnEl.classList.remove('settings-clicked');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -792,33 +851,29 @@ class CustomSettingsMenu extends Component {
|
|||||||
this.player().playbackRate(speed);
|
this.player().playbackRate(speed);
|
||||||
|
|
||||||
// Save preference
|
// Save preference
|
||||||
this.userPreferences.setPreference("playbackRate", speed);
|
this.userPreferences.setPreference('playbackRate', speed);
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
this.speedSubmenu.querySelectorAll(".speed-option").forEach((opt) => {
|
this.speedSubmenu.querySelectorAll('.speed-option').forEach((opt) => {
|
||||||
opt.classList.remove("active");
|
opt.classList.remove('active');
|
||||||
opt.style.background = "transparent";
|
opt.style.background = 'transparent';
|
||||||
const check = opt.querySelector(".checkmark");
|
const check = opt.querySelector('.checkmark');
|
||||||
if (check) check.remove();
|
if (check) check.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
speedOption.classList.add("active");
|
speedOption.classList.add('active');
|
||||||
speedOption.style.background = "rgba(255, 255, 255, 0.1)";
|
speedOption.style.background = 'rgba(255, 255, 255, 0.1)';
|
||||||
speedOption.insertAdjacentHTML(
|
speedOption.insertAdjacentHTML('beforeend', '<span class="checkmark">✓</span>');
|
||||||
"beforeend",
|
|
||||||
'<span class="checkmark">✓</span>'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update main menu display
|
// Update main menu display
|
||||||
const currentSpeedDisplay =
|
const currentSpeedDisplay = this.settingsOverlay.querySelector('.current-speed');
|
||||||
this.settingsOverlay.querySelector(".current-speed");
|
const speedLabel = speed === 1 ? 'Normal' : `${speed}`;
|
||||||
const speedLabel = speed === 1 ? "Normal" : `${speed}`;
|
|
||||||
currentSpeedDisplay.textContent = speedLabel;
|
currentSpeedDisplay.textContent = speedLabel;
|
||||||
|
|
||||||
// Close only the speed submenu (keep overlay open)
|
// Close only the speed submenu (keep overlay open)
|
||||||
this.speedSubmenu.style.display = "none";
|
this.speedSubmenu.style.display = 'none';
|
||||||
|
|
||||||
console.log("Playback speed preference saved:", speed);
|
console.log('Playback speed preference saved:', speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQualityChange(value, qualityOption) {
|
handleQualityChange(value, qualityOption) {
|
||||||
@ -829,25 +884,20 @@ class CustomSettingsMenu extends Component {
|
|||||||
this.userPreferences.setQualityPreference(value);
|
this.userPreferences.setQualityPreference(value);
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
this.qualitySubmenu.querySelectorAll(".quality-option").forEach((opt) => {
|
this.qualitySubmenu.querySelectorAll('.quality-option').forEach((opt) => {
|
||||||
opt.classList.remove("active");
|
opt.classList.remove('active');
|
||||||
opt.style.background = "transparent";
|
opt.style.background = 'transparent';
|
||||||
const check = opt.querySelector(".checkmark");
|
const check = opt.querySelector('.checkmark');
|
||||||
if (check) check.remove();
|
if (check) check.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
qualityOption.classList.add("active");
|
qualityOption.classList.add('active');
|
||||||
qualityOption.style.background = "rgba(255, 255, 255, 0.1)";
|
qualityOption.style.background = 'rgba(255, 255, 255, 0.1)';
|
||||||
qualityOption.insertAdjacentHTML(
|
qualityOption.insertAdjacentHTML('beforeend', '<span class="checkmark">✓</span>');
|
||||||
"beforeend",
|
|
||||||
'<span class="checkmark">✓</span>'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update main menu display
|
// Update main menu display
|
||||||
const currentQualityDisplay =
|
const currentQualityDisplay = this.settingsOverlay.querySelector('.current-quality');
|
||||||
this.settingsOverlay.querySelector(".current-quality");
|
currentQualityDisplay.innerHTML = selected?.displayLabel || selected?.label || String(value);
|
||||||
currentQualityDisplay.innerHTML =
|
|
||||||
selected?.displayLabel || selected?.label || String(value);
|
|
||||||
|
|
||||||
// Perform source switch if we have src defined
|
// Perform source switch if we have src defined
|
||||||
if (selected?.src) {
|
if (selected?.src) {
|
||||||
@ -889,7 +939,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
src: el.src,
|
src: el.src,
|
||||||
srclang: el.srclang || (el.track && el.track.language) || '',
|
srclang: el.srclang || (el.track && el.track.language) || '',
|
||||||
label: el.label || (el.track && el.track.label) || '',
|
label: el.label || (el.track && el.track.label) || '',
|
||||||
default: !!el.default
|
default: !!el.default,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -907,7 +957,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
src: t.src,
|
src: t.src,
|
||||||
srclang: t.language || '',
|
srclang: t.language || '',
|
||||||
label: t.label || '',
|
label: t.label || '',
|
||||||
default: false
|
default: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -933,8 +983,12 @@ class CustomSettingsMenu extends Component {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
// Restore time and rate
|
// Restore time and rate
|
||||||
try { player.playbackRate(rate); } catch (e) {}
|
try {
|
||||||
try { if (!isNaN(currentTime)) player.currentTime(currentTime); } catch (e) {}
|
player.playbackRate(rate);
|
||||||
|
} catch (e) {}
|
||||||
|
try {
|
||||||
|
if (!isNaN(currentTime)) player.currentTime(currentTime);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
// Resume state
|
// Resume state
|
||||||
if (!wasPaused) {
|
if (!wasPaused) {
|
||||||
@ -951,7 +1005,9 @@ class CustomSettingsMenu extends Component {
|
|||||||
for (let i = 0; i < tt2.length; i++) {
|
for (let i = 0; i < tt2.length; i++) {
|
||||||
const t = tt2[i];
|
const t = tt2[i];
|
||||||
if (t.kind === 'subtitles') {
|
if (t.kind === 'subtitles') {
|
||||||
const match = activeSubtitleLang && (t.language === activeSubtitleLang || t.srclang === activeSubtitleLang);
|
const match =
|
||||||
|
activeSubtitleLang &&
|
||||||
|
(t.language === activeSubtitleLang || t.srclang === activeSubtitleLang);
|
||||||
t.mode = match ? 'showing' : 'disabled';
|
t.mode = match ? 'showing' : 'disabled';
|
||||||
if (match) restored = true;
|
if (match) restored = true;
|
||||||
}
|
}
|
||||||
@ -985,7 +1041,10 @@ class CustomSettingsMenu extends Component {
|
|||||||
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) { el.style.display = ''; el.style.visibility = ''; }
|
if (el) {
|
||||||
|
el.style.display = '';
|
||||||
|
el.style.visibility = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@ -1003,9 +1062,9 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Close only the quality submenu (keep overlay open)
|
// Close only the quality submenu (keep overlay open)
|
||||||
if (this.qualitySubmenu) this.qualitySubmenu.style.display = "none";
|
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
|
||||||
|
|
||||||
console.log("Quality preference saved:", value);
|
console.log('Quality preference saved:', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubtitleChange(lang, optionEl) {
|
handleSubtitleChange(lang, optionEl) {
|
||||||
@ -1036,7 +1095,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
optionEl.insertAdjacentHTML('beforeend', '<span class="checkmark">✓</span>');
|
optionEl.insertAdjacentHTML('beforeend', '<span class="checkmark">✓</span>');
|
||||||
|
|
||||||
// Update label in main settings
|
// Update label in main settings
|
||||||
const label = lang ? (optionEl.querySelector('span')?.textContent || lang) : 'Off';
|
const label = lang ? optionEl.querySelector('span')?.textContent || lang : 'Off';
|
||||||
const currentSubtitlesDisplay = this.settingsOverlay.querySelector('.current-subtitles');
|
const currentSubtitlesDisplay = this.settingsOverlay.querySelector('.current-subtitles');
|
||||||
if (currentSubtitlesDisplay) currentSubtitlesDisplay.textContent = label;
|
if (currentSubtitlesDisplay) currentSubtitlesDisplay.textContent = label;
|
||||||
|
|
||||||
@ -1071,11 +1130,15 @@ class CustomSettingsMenu extends Component {
|
|||||||
t.mode = 'showing';
|
t.mode = 'showing';
|
||||||
restored = true;
|
restored = true;
|
||||||
// Persist enabled flag so iOS applies on next load
|
// Persist enabled flag so iOS applies on next load
|
||||||
try { this.userPreferences.setPreference('subtitleEnabled', true, true); } catch (e) { }
|
try {
|
||||||
|
this.userPreferences.setPreference('subtitleEnabled', true, true);
|
||||||
|
} catch (e) {}
|
||||||
// Refresh UI
|
// Refresh UI
|
||||||
this.refreshSubtitlesSubmenu();
|
this.refreshSubtitlesSubmenu();
|
||||||
this.updateCurrentSubtitleDisplay();
|
this.updateCurrentSubtitleDisplay();
|
||||||
try { player.trigger('texttrackchange'); } catch (e) { }
|
try {
|
||||||
|
player.trigger('texttrackchange');
|
||||||
|
} catch (e) {}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1107,20 +1170,20 @@ class CustomSettingsMenu extends Component {
|
|||||||
!this.settingsOverlay.contains(e.target) &&
|
!this.settingsOverlay.contains(e.target) &&
|
||||||
!this.settingsButton.el().contains(e.target)
|
!this.settingsButton.el().contains(e.target)
|
||||||
) {
|
) {
|
||||||
this.settingsOverlay.classList.remove("show");
|
this.settingsOverlay.classList.remove('show');
|
||||||
this.settingsOverlay.style.display = "none";
|
this.settingsOverlay.style.display = 'none';
|
||||||
this.speedSubmenu.style.display = "none";
|
this.speedSubmenu.style.display = 'none';
|
||||||
if (this.qualitySubmenu) this.qualitySubmenu.style.display = "none";
|
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
|
||||||
const btnEl = this.settingsButton?.el();
|
const btnEl = this.settingsButton?.el();
|
||||||
if (btnEl) {
|
if (btnEl) {
|
||||||
btnEl.classList.remove("settings-clicked");
|
btnEl.classList.remove('settings-clicked');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
// Remove event listeners
|
// Remove event listeners
|
||||||
document.removeEventListener("click", this.handleClickOutside);
|
document.removeEventListener('click', this.handleClickOutside);
|
||||||
|
|
||||||
// Remove DOM elements
|
// Remove DOM elements
|
||||||
if (this.settingsOverlay) {
|
if (this.settingsOverlay) {
|
||||||
@ -1132,9 +1195,9 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set component name for Video.js
|
// Set component name for Video.js
|
||||||
CustomSettingsMenu.prototype.controlText_ = "Settings Menu";
|
CustomSettingsMenu.prototype.controlText_ = 'Settings Menu';
|
||||||
|
|
||||||
// Register the component with Video.js
|
// Register the component with Video.js
|
||||||
videojs.registerComponent("CustomSettingsMenu", CustomSettingsMenu);
|
videojs.registerComponent('CustomSettingsMenu', CustomSettingsMenu);
|
||||||
|
|
||||||
export default CustomSettingsMenu;
|
export default CustomSettingsMenu;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user