Tick the playing quality, and hide the menu with the HUD
The quality menu now marks the format that is actually on screen when it opens, read live from the player rather than recorded at bind time, so the tick follows an automatic pick or a fallback after a failed candidate, not only a manual choice. Labels drop the container (mp4 told the viewer nothing about a quality choice) and gain the extractor's format_note when it says something the quality doesn't already. The menu sits outside .cp-hud so it can escape the bar's overflow, which means the idle fade never reached it -- the player and the reels feed now close it along with the rest of the HUD. Opening it restarts the idle countdown (the feed's window is only a second), and on desktop a mouse resting on the open menu holds the HUD up, same as the bars. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd
This commit is contained in:
@@ -91,8 +91,12 @@ App.customPlayer = App.customPlayer || {};
|
||||
const height = App.videos.coerceNumber(fmt.height);
|
||||
const fps = App.videos.coerceNumber(fmt.fps);
|
||||
if (height) parts.push(`${height}p${fps > 30 ? Math.round(fps) : ''}`);
|
||||
const ext = (fmt.ext || fmt.video_ext || '').toString();
|
||||
if (ext) parts.push(ext);
|
||||
// The container (mp4/webm) tells the viewer nothing useful about a
|
||||
// quality choice. The extractor's own note does -- but only when it
|
||||
// says something the quality doesn't already ("HDR", "source", a
|
||||
// codec), so drop one that merely restates it ("1080p", "1080p60").
|
||||
const note = (fmt.format_note || '').toString().trim();
|
||||
if (note && note.toLowerCase() !== (parts[0] || '').toLowerCase()) parts.push(note);
|
||||
if (!parts.length) {
|
||||
const vcodec = (fmt.vcodec || '').toString();
|
||||
parts.push(vcodec && vcodec !== 'none' ? vcodec : 'Auto');
|
||||
@@ -114,8 +118,15 @@ App.customPlayer = App.customPlayer || {};
|
||||
// calling onSelect(fmt) when the user picks one. Hides the button when
|
||||
// there's nothing to pick from. Shared by the standalone player and the
|
||||
// reels feed so both present an identical menu. Returns a destroy() fn.
|
||||
App.customPlayer.bindFormatMenu = function(btn, menu, videoData, onSelect) {
|
||||
// `options.getCurrentUrl` (optional) returns the URL the player is actually
|
||||
// feeding to the media element right now; the matching entry is marked
|
||||
// active every time the menu opens. Reading it live rather than at bind
|
||||
// time keeps the mark honest when playback moved on by itself -- an
|
||||
// automatic pick, a fallback to the next candidate after a failure, or a
|
||||
// re-resolve -- not just when the viewer chose from this menu.
|
||||
App.customPlayer.bindFormatMenu = function(btn, menu, videoData, onSelect, opts) {
|
||||
if (!btn || !menu) return function destroy() {};
|
||||
const getCurrentUrl = (opts && opts.getCurrentUrl) || null;
|
||||
const options = App.customPlayer.buildFormatOptions(videoData);
|
||||
if (!options.length) {
|
||||
btn.hidden = true;
|
||||
@@ -126,8 +137,27 @@ App.customPlayer = App.customPlayer || {};
|
||||
btn.hidden = false;
|
||||
menu.hidden = true;
|
||||
menu.innerHTML = options.map((opt, i) =>
|
||||
`<button class="cp-format-option" type="button" data-index="${i}">${opt.label}</button>`
|
||||
`<button class="cp-format-option" type="button" role="menuitemradio" aria-checked="false" data-index="${i}">${opt.label}</button>`
|
||||
).join('');
|
||||
const markActive = (activeBtn) => {
|
||||
menu.querySelectorAll('.cp-format-option').forEach((b) => {
|
||||
const isActive = b === activeBtn;
|
||||
b.classList.toggle('is-active', isActive);
|
||||
b.setAttribute('aria-checked', isActive ? 'true' : 'false');
|
||||
});
|
||||
};
|
||||
const syncActive = () => {
|
||||
const current = getCurrentUrl ? (getCurrentUrl() || '') : '';
|
||||
let match = null;
|
||||
if (current) {
|
||||
options.forEach((opt, i) => {
|
||||
if (!match && opt.fmt && opt.fmt.url === current) {
|
||||
match = menu.querySelector(`.cp-format-option[data-index="${i}"]`);
|
||||
}
|
||||
});
|
||||
}
|
||||
markActive(match);
|
||||
};
|
||||
const cleanups = [];
|
||||
menu.querySelectorAll('.cp-format-option').forEach((optBtn) => {
|
||||
const onClick = (event) => {
|
||||
@@ -135,8 +165,7 @@ App.customPlayer = App.customPlayer || {};
|
||||
const idx = parseInt(optBtn.dataset.index, 10);
|
||||
const opt = options[idx];
|
||||
menu.hidden = true;
|
||||
menu.querySelectorAll('.cp-format-option').forEach((b) => b.classList.remove('is-active'));
|
||||
optBtn.classList.add('is-active');
|
||||
markActive(optBtn);
|
||||
if (opt) onSelect(opt.fmt);
|
||||
};
|
||||
optBtn.addEventListener('click', onClick);
|
||||
@@ -144,6 +173,13 @@ App.customPlayer = App.customPlayer || {};
|
||||
});
|
||||
const onBtnClick = (event) => {
|
||||
event.stopPropagation();
|
||||
if (menu.hidden) {
|
||||
syncActive();
|
||||
// Opening the menu restarts the HUD's idle countdown: the menu
|
||||
// hides with the HUD, and the viewer needs the full window to
|
||||
// read the list, not whatever was left of the previous one.
|
||||
if (opts && opts.onOpen) opts.onOpen();
|
||||
}
|
||||
menu.hidden = !menu.hidden;
|
||||
};
|
||||
btn.addEventListener('click', onBtnClick);
|
||||
|
||||
Reference in New Issue
Block a user