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.
This commit is contained in:
+19
-11
@@ -290,6 +290,10 @@ let overlayCounter = 0;
|
||||
const nextOverlayToken = () => ++overlayCounter;
|
||||
// Overlay history entries we've actually pushed. Reconciled toward overlayStack.length.
|
||||
let historyDepth = 0;
|
||||
// The entry this document was loaded on, when it was an overlay's (a reload under an open player):
|
||||
// the first overlay to open takes it over rather than pushing another over it — pushed, Back would
|
||||
// close the player onto that same page again, one dead step per reload. Until the first push or Back.
|
||||
let reloadedOverlayEntry: unknown = window.history.state?._ov ?? null;
|
||||
// Programmatic history.back() calls still awaiting their popstate — each must be ignored.
|
||||
let pendingProgrammatic = 0;
|
||||
let reconcileScheduled = false;
|
||||
@@ -307,11 +311,13 @@ function reconcileHistory(): void {
|
||||
const want = overlayStack.length;
|
||||
while (historyDepth < want && pendingProgrammatic === 0) {
|
||||
historyDepth++;
|
||||
window.history.pushState(
|
||||
{ ...window.history.state, _ov: nextOverlayToken() },
|
||||
"",
|
||||
overlayHref(historyDepth),
|
||||
);
|
||||
const here = window.history.state?._ov;
|
||||
if (historyDepth === 1 && here != null && here === reloadedOverlayEntry) {
|
||||
reloadedOverlayEntry = null;
|
||||
continue;
|
||||
}
|
||||
reloadedOverlayEntry = null;
|
||||
window.history.pushState({ ...window.history.state, _ov: nextOverlayToken() }, "");
|
||||
}
|
||||
while (historyDepth > want) {
|
||||
historyDepth--;
|
||||
@@ -321,20 +327,21 @@ function reconcileHistory(): void {
|
||||
} else {
|
||||
// Nothing of ours below to step back to (see backStaysInDocument): the entry stays, unmarked.
|
||||
const { _ov: _drop, ...st } = (window.history.state ?? {}) as Record<string, unknown>;
|
||||
window.history.replaceState(st, "", overlayHref(historyDepth));
|
||||
window.history.replaceState(st, "");
|
||||
}
|
||||
}
|
||||
nameOverlaysHere();
|
||||
syncWaiters.forEach((cb) => cb());
|
||||
}
|
||||
|
||||
// The address of the entry `depth` overlays up: the keys of those overlays, none of the others'.
|
||||
// Undefined (the address as it is) while no overlay names any.
|
||||
function overlayHref(depth: number): string | undefined {
|
||||
// The address of the entry the open overlays stand on: their keys, every other overlay's cleared.
|
||||
// Undefined (the address as it is) while no overlay names any. Written by `nameOverlaysHere` alone:
|
||||
// a pushed entry starts as a copy of the one under it and is named once it is the current one.
|
||||
function overlayHref(): string | undefined {
|
||||
if (overlayUrlKeys.size === 0) return undefined;
|
||||
const updates: OverlayUrl = {};
|
||||
for (const k of overlayUrlKeys) updates[k] = null;
|
||||
for (const o of overlayStack.slice(0, depth)) Object.assign(updates, o.url);
|
||||
for (const o of overlayStack) Object.assign(updates, o.url);
|
||||
return urlWithParams(updates);
|
||||
}
|
||||
|
||||
@@ -342,7 +349,7 @@ function overlayHref(depth: number): string | undefined {
|
||||
// that, the current entry may be one about to be pushed over or stepped back from).
|
||||
function nameOverlaysHere(): void {
|
||||
if (historyDepth !== overlayStack.length || pendingProgrammatic > 0) return;
|
||||
const href = overlayHref(historyDepth);
|
||||
const href = overlayHref();
|
||||
if (href != null && href !== hrefNow())
|
||||
window.history.replaceState(window.history.state, "", href);
|
||||
}
|
||||
@@ -359,6 +366,7 @@ function ensureOverlayListener(): void {
|
||||
if (listenerInstalled) return;
|
||||
listenerInstalled = true;
|
||||
window.addEventListener("popstate", () => {
|
||||
reloadedOverlayEntry = null;
|
||||
if (pendingProgrammatic > 0) {
|
||||
pendingProgrammatic--; // our own back(): consume and ignore
|
||||
// The last one landed: an overlay that opened while it was in flight still owes its entry.
|
||||
|
||||
Reference in New Issue
Block a user