Fix expiring favorites, empty quality menus, rotation scroll jumps
Favorites persisted the *resolved* stream metadata (resolveAndProbe mutates video.meta with yt-dlp's CDN format URLs), so a favorite opened the next day replayed a dead link. They now store only the page URL and identifying fields, and ignore any stale meta left in localStorage -- playback, download and info re-resolve through the backend, which resolves a page URL live in /api/stream. That left the quality switcher empty for anything not yet resolved (favorites, cards clicked before their hover-resolve landed, feed slides), so App.videos.ensureFormats now resolves formats once per session -- cached by video id rather than per object, so any object describing the same video gets them -- and both the player and the reels feed rebuild their format menu when they arrive. Playback isn't blocked: it already starts from the page URL via the proxy. Rotating a phone also jumped the grid to a completely different place: the anchor was read inside the resize handler (by which point the browser has already moved the scroll) and asserted once, and every re-pack discarded known card heights for the 16:9 placeholder estimate. The virtualizer now tracks the anchor on every scroll pass, re-asserts it across a short settling window (ending early on a real gesture), and remembers each thumbnail's true aspect ratio so a re-pack places cards at their real heights. Verified in headless Chrome across a portrait/landscape/portrait cycle: visible videos 37-39 -> 36-40 -> 36-38, against 37-39 -> 34-37 -> 29-30 before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd
This commit is contained in:
@@ -806,11 +806,30 @@ App.player = App.player || {};
|
||||
bindFavorite(source);
|
||||
bindTimeline(cp.video);
|
||||
bindTransport(cp.video);
|
||||
addCleanup(App.customPlayer.bindFormatMenu(q('.cp-format-btn'), q('.cp-format-menu'), source, (fmt) => {
|
||||
|
||||
cp.source = source;
|
||||
const bindFormats = () => App.customPlayer.bindFormatMenu(q('.cp-format-btn'), q('.cp-format-menu'), source, (fmt) => {
|
||||
cp.formatOverride = fmt;
|
||||
const resumeAt = cp.video.currentTime || 0;
|
||||
playSources(source, { resumeAt, originEl: cp.originEl });
|
||||
}));
|
||||
});
|
||||
let destroyFormatMenu = bindFormats();
|
||||
addCleanup(() => destroyFormatMenu());
|
||||
// Items that arrive without formats -- a listing card clicked before its
|
||||
// hover-resolve finished, or a favorite (which deliberately stores no
|
||||
// resolved formats, since their URLs expire) -- would otherwise show an
|
||||
// empty quality menu. Playback already starts from the page URL via the
|
||||
// proxy, so resolve in the background and rebuild the menu with the real
|
||||
// qualities as soon as they land.
|
||||
if (App.videos && typeof App.videos.ensureFormats === 'function') {
|
||||
App.videos.ensureFormats(source).then((meta) => {
|
||||
// A later open() may have taken over in the meantime; its own
|
||||
// bindFormats() owns the menu then.
|
||||
if (!meta || cp.source !== source) return;
|
||||
destroyFormatMenu();
|
||||
destroyFormatMenu = bindFormats();
|
||||
});
|
||||
}
|
||||
bindGestures(cp.video);
|
||||
bindKeyboard(cp.video);
|
||||
bindClose();
|
||||
@@ -859,6 +878,7 @@ App.player = App.player || {};
|
||||
cp.originEl = null;
|
||||
}
|
||||
cp.data = null;
|
||||
cp.source = null; // voids a still-pending format resolve for this open
|
||||
cp.formatOverride = null;
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user