# Embedded Inline Playback This document records the current contract for embedded playback in portal detail views. ## Summary - Embedded web players are `videojs`, `html5`, and `artplayer`. - `embedded-mpv` exists as a hidden desktop experimental harness backed by a native MPV addon. macOS and Windows use in-process `libmpv`; Linux uses an X11/Xwayland child window with an out-of-process `mpv --wid` backend. - Controlled external players are `mpv` and `vlc`. - macOS `.app` bundle paths are resolved only for real MPV/VLC apps. IINA may launch through the MPV path field when the user supplies an executable path such as `/Applications/IINA.app/Contents/MacOS/iina-cli`, but IPTVnator controls, position polling, and instance reuse are not guaranteed for IINA. - Flatpak launches external players on the host via `flatpak-spawn --host`. - Live playback stays inline in dedicated live layouts. - VOD and series detail playback stays inline on canonical detail, collection, favorites, recent, and search surfaces. - Xtream and Stalker series detail heroes expose a quick-start CTA driven by saved episode playback positions. - Embedded playback UI is always hosted by the current view. `PlayerService` launches MPV/VLC only and does not open an embedded-player dialog. - Browser-player failures are diagnosed client-side and can offer explicit MPV/VLC fallback actions without changing the saved player setting. ## Scope Inline embedded playback is required for these VOD/series entry points: - Xtream VOD detail route - Xtream series detail route - Stalker VOD detail view - Stalker series detail view - unified favorites collection details - unified recently viewed collection details - Stalker advanced search result details Collection/search VOD surfaces that expose embedded playback must host `ResolvedPortalPlayback` inline state locally. They must not call `PlayerService.openPlayer(...)` or `PlayerService.openResolvedPlayback(...)` to create embedded UI. ## Embedded MPV Harness The repository now contains a first-pass native embedded MPV harness for Electron: - shared setting id: `embedded-mpv` - native addon owner: `/Users/4gray/Code/iptvnator/apps/electron-backend/src/app/services/embedded-mpv-native.service.ts` - IPC bridge: `/Users/4gray/Code/iptvnator/apps/electron-backend/src/app/events/embedded-mpv.events.ts` - renderer host: `/Users/4gray/Code/iptvnator/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.ts` - native architecture and release-readiness details: `/Users/4gray/Code/iptvnator/docs/architecture/embedded-mpv-native.md` Current contract: - desktop only: macOS, Windows x64, and Linux x64 under X11/Xwayland - experimental opt-in - enabled in local development only when `IPTVNATOR_ENABLE_EMBEDDED_MPV_EXPERIMENT=1` - Linux Wayland sessions must start Electron through Xwayland, for example with `pnpm nx run electron-backend:serve-electron --args=--ozone-platform=x11` during local development or `iptvnator --ozone-platform=x11` for a packaged app - enabled in packaged desktop builds only when the bundled native addon/runtime prerequisites are present; Linux additionally requires an `mpv` executable on `PATH` - uses IPTVnator-owned controls and `ResolvedPortalPlayback` payloads - uses the libmpv render API on macOS and renders through an IPTVnator-owned native `NSView` - uses mpv `wid` embedding on Windows and Linux through IPTVnator-owned native child windows - Linux starts `mpv --wid=` out of process so MPV does not share Electron's FFmpeg or graphics symbols - Linux controls that out-of-process MPV instance through a private JSON IPC socket so duration, position, pause, volume, and seek state come from MPV instead of renderer guesses - Linux starts that MPV process with `WAYLAND_DISPLAY` removed, `XDG_SESSION_TYPE=x11`, and X11 video output options; otherwise MPV can pick Wayland inside a Wayland desktop session, ignore `--wid`, and open a separate window instead of embedding - macOS/Windows default to libmpv's OpenGL render backend with `hwdec=auto-safe` - keeps the previous software renderer as a debug fallback via `IPTVNATOR_EMBEDDED_MPV_RENDERER=sw` - emits lightweight native diagnostics when `IPTVNATOR_TRACE_EMBEDDED_MPV=1` is set; Linux also writes MPV's own trace log to `/tmp/iptvnator-embedded-mpv.log` - exposes an IPTVnator-owned fullscreen button that uses the renderer fullscreen API and resyncs the native MPV view bounds after fullscreen transitions - auto-hides IPTVnator-owned controls while playback is active and restores them on pointer/focus interaction - exposes audio-track metadata from MPV and switches tracks through the `aid` property without reloading the stream - passes VOD/episode resume offsets to MPV through the `loadfile` options map; live catchup URLs are treated as already-positioned streams - applies the initial volume during session creation and uses async libmpv control calls after startup where the in-process libmpv backend is active - VLC remains external-only Current limitation: - the current feasibility harness is still experimental and platform-specific - the original macOS `wid` embedding path produced audio with a black video surface inside Electron, so the harness now avoids foreign-window embedding on macOS - Windows and Linux use the mpv `wid` path and still need OS-native packaged smoke coverage before public exposure - Linux native Wayland is not supported in this implementation; the Electron process must have `DISPLAY` through X11/Xwayland and a real X11 window handle - the OpenGL render path avoids the old per-frame `CGImage` copy path, but it still needs broader interaction, resize, and packaging coverage - startup deadlocks seen during early macOS playback bring-up are mitigated, but the feature is still kept behind the explicit experiment flag until more interaction and packaging coverage is proven - because of that, the setting is auto-sanitized back to the default inline player unless support detection reports that the experimental runtime is available - this follows the rollout gate: keep the native work in-tree, but do not leave it user-facing until playback, resize, focus, and packaging are stable ## Two-State Detail Layout (Browse ↔ Watch) Portal VOD/series detail pages are hosted by the shared `PortalDetailShellComponent` (`libs/ui/components/src/lib/portal-detail-shell/portal-detail-shell.component.ts`), which owns the page scroll container and two layout states: - **Browse** (`playbackActive=false`): the hero (`ContentHeroComponent`, now a natural-height, non-scrolling block) renders poster, title, chips, description, credits, and actions. Episodes render below. - **Watch** (`playbackActive=true`, bound by hosts to `inlinePlayback() !== null`): the hero collapses (~300ms CSS morph, disabled under `prefers-reduced-motion`), the host-projected `[detail-player]` slot renders the inline player at full content width, and the shell renders an About block (`ContentAboutComponent`) below the `[detail-episodes]` slot so the hero metadata stays reachable. Contracts: - Hosts provide hero chips/meta/actions as `*appDetailTags` / `*appDetailMeta` / `*appDetailActions` templates; the shell stamps them into the hero and again into the About block. Degradation stays "missing → not rendered". - The shell never wraps the `[detail-player]` slot in a conditional; the host's `@if (inlinePlayback())` is the only creator/destroyer of the player subtree, so shell state changes cannot recreate the `