Files
siftlode/frontend/src/lib/playerUrlState.ts
T
peter 3d26ca0efa feat(player): a bookmark reopens every player as it was, once
The Plex player names the playlist it plays through (v=player:<id>:playlist:<pid>), so a bookmark
rebuilds the queue from the playlist's own order while the item is still in it.

Found in the browser and fixed:
- after a reload the shell's player opened a second one over Home and the feed: it read the catalog
  row the page had fetched (a disabled query still reads the shared cache); it now waits for ready.
- every reload under an open overlay left a dead history step: the first overlay to open takes over
  the entry the document was loaded on.
- the feed's address was implicit, the same as the start-page landing, so a reloaded or bookmarked
  feed opened as Home: the feed now names itself (page=feed) like every page.
- a linked file or video that is gone was said, but stayed in the address and was said again on
  every reload: its keys are taken off.

The overlay address is written in one place (the entry is named once it is current), and the player
keys only the codec reads are no longer exported.
2026-10-27 07:33:55 +01:00

43 lines
2.3 KiB
TypeScript

// The YouTube player's address-bar keys. The player opens over any page (the feed, a channel page, Home,
// a playlist, a channel's watched videos), so its keys are the shell's: no module owns them, a page
// switch clears them, and the page under it keeps its own. The player names them in its own history
// entry (lib/history's `useBackToClose`), so Back lands on the page as it was, without the player.
// The list it plays from is the page's: the feed's filters, the open playlist (`pl`) — already named.
// No position: the player resumes from the server's (`video_states`), which is where it was left.
const PLAYER_WATCH_KEY = "watch"; // the item of the list playing now
const PLAYER_RELATED_KEY = "wrel"; // a related video played in place over it
const PLAYER_PANEL_KEY = "wtab"; // the wide panel's tab, when it is not the description
export const PLAYER_KEYS = [PLAYER_WATCH_KEY, PLAYER_RELATED_KEY, PLAYER_PANEL_KEY] as const;
export type PlayerPanelTab = "description" | "related";
export type PlayerUrlState = { watch: string; related: string | null; panel: PlayerPanelTab };
/** The player's keys for its entry. The panel's tab is written only when it is not the default: no
* browser remembers it, so a link without one opens the description as this one would. */
export function playerParamUpdates(state: PlayerUrlState): Record<string, string | null> {
return {
[PLAYER_WATCH_KEY]: state.watch,
[PLAYER_RELATED_KEY]: state.related,
[PLAYER_PANEL_KEY]: state.panel === "related" ? "related" : null,
};
}
/** The player an address names, or null when it names none. */
function playerFrom(params: URLSearchParams): PlayerUrlState | null {
const watch = params.get(PLAYER_WATCH_KEY);
if (!watch) return null;
return {
watch,
related: params.get(PLAYER_RELATED_KEY) || null,
panel: params.get(PLAYER_PANEL_KEY) === "related" ? "related" : "description",
};
}
/** The player the address names now: read by a host as it mounts (a reload, a bookmark), and by the
* player as it opens. No entry but the player's own carries these keys once the history has caught
* up, so a host mounted later (a page switch back) finds none. */
export function linkedPlayer(): PlayerUrlState | null {
return playerFrom(new URLSearchParams(window.location.search));
}