* fix(playback): avoid redundant VideoJS source resets
* fix(playback): clean up cleared VideoJS sources
* refactor(playback): use signal APIs in VideoJS player
closes#608
- pruneEphemeralMaps now uses getCached() so TTL-expired entries also
drop their XMLTV preview / channel-id mapping, instead of clinging
to them until the next explicit cache read.
- portal-channels-list attaches a .catch() to the now-async enqueue so
any future internal rejection surfaces in the console rather than
bubbling silently as an unhandled promise rejection.
- XtreamXmltvFallbackService binds the bridge receiver when calling
the extracted function reference, in case a preload wrapper relies
on `this`.
Adds a regression test for the TTL-aware prune.
Live TV channels in Xtream playlists go blank whenever the provider's
get_short_epg returns nothing, even when the user has working XMLTV
URLs in Settings. The XMLTV pipeline already populates epg_programs
but only the M3U module was reading it.
Wire those uploads up as a fallback for Xtream too: when the provider
returns nothing for a channel with an epg_channel_id, look it up in
the local table. A settings toggle flips the priority for users whose
curated XMLTV is better than the provider's auto guide.
EpgQueueService.enqueue() became async to batch the XMLTV lookup once
per viewport change. To keep the queue consistent under fast scroll,
all shared-state mutations now happen behind a generation counter so
only the latest call commits. Per-method bridge gating in the new
XtreamXmltvFallbackService keeps each path working when the preload
exposes only one of the two endpoints.
Covers the embedded MPV player on macOS, toggleable Live TV sidebars,
command palette upgrades, and module fixes. Credits the Telegram tip
about using IINA as the external player and links the related GitHub
issues.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Capture the audit findings and refactor outcome in the canonical embedded-mpv
architecture doc so the next person to extend the player does not re-introduce
the same class of regression.
New "Renderer Architecture And Reactivity" section covers:
- The post-refactor file layout (format utils, shortcuts, overlay-visibility,
ui-state, session-controller, view component) and what each owns.
- The bounds compositing strategy: HIDDEN_BOUNDS for modal overlays,
partial bottom cutout for control popovers, full host rect otherwise —
plus why (NSView paints over WebContents, z-index is unwinnable).
- The reactivity rule learned from the audit: any signal read inside an
effect that feeds a one-shot side effect must be wrapped in untracked().
Only re-trigger sources stay tracked.
- A short ledger of the four bugs from the audit (support write loop,
volume-tracked session restart, scheduleControlsHide transitive deps,
stalled-tracker 2 Hz no-op) so they do not get reintroduced.
- IPC safety notes: gate on sessionId() not session()?.id; guardIpc
swallows addon-side throws.
- Pointer to the powerSaveBlocker integration.
Also documents the bundle 3 native surface additions (sid, speed,
video-aspect-override observers and exports, capability probe via
EmbeddedMpvSupport.capabilities) so renderers can feature-detect against
older addon binaries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f957cd9849e0
Audit followup to the support-loop and volume-restart fixes. Two more
effects pulled in transitive signal deps that would have caused the same
class of regression the next time the surrounding helpers grew.
1. Component session-fan-out effect: the body called
scheduleControlsHide(), which reads isPlaying/menus.anyOpen/statusLabel/
controlsVisible. Those became tracked deps of the effect, so opening a
popover, pausing, or hovering re-ran the whole body — re-emitting
timeUpdate. If a parent ever wires timeUpdate back into
playback.startTime as a "resume where I left off" feature, this would
have been the next stream-restart bug. Wrap the side-effect block in
untracked() so the effect only listens to session changes.
2. Controller stalled-tracker effect: tracked the full session signal
even though only status was needed. The session payload updates ~2 Hz
during playback (positionSeconds advances), making the effect re-run
constantly to call a no-op. Add a sessionStatus computed and track
that instead — fires only on real status transitions.
No behavior change for current users; both fixes are preventative.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f957cd9849e0