mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 02:46:16 -08:00
a820194ae599f86e8adbb5a64007368aa4672de2
53
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
50d45bc7c0 |
feat(details): redesigned episode list, section rhythm and hero fade (#1859)
* fix(details): continue the hero artwork under the first section The vertical scrim stopped at the hero's bottom edge, so a bright backdrop ended in a visible band above the Episodes heading. The hero now grows by a 140px tail that the shell pulls the sections up over, and the scrim resolves to the exact page surface behind the heading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(details): one section header and a 56px rhythm below the hero Episodes, Cast & crew and Similar now share app-detail-section-header: an 18px/600 title, a muted tabular counter and lead/end slots. The Episodes counter adds the watched count ("8 episodes · 3 watched"), and sections sit 56px apart, with 56px after the last one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(details): flat episode rows with a reserved action slot Each episode is now one app-episode-item, a list row or a grid card: - a number column, a 168px thumbnail with a watched check, a progress bar only for started episodes and a play overlay - title with "46 min · 12 Jan" (time left once started) and a plot clamped to two lines at 74ch - mark watched, download and a "…" menu in a 112px slot that is always reserved and shows on hover or keyboard focus, so nothing shifts - the whole item is one stretched button: Tab focuses it, Enter plays - the playing or last unfinished episode is highlighted The "…" menu holds Episode details and, for a started episode, Play from beginning, which Xtream and Stalker now start at 0. TMDB's episode runtime fills the meta line when the provider sends none. The list is the default view for anyone who has not chosen one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(details): segmented list/grid switch and a 30px season pill The view toggle becomes a 2px-padded segmented track with 30×26 segments and a neutral checked segment; the season pills and the season dropdown share the section header's 30px pill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(details): no season synopsis that repeats the series description Providers often send the series plot, or its first sentences, as every season's description, so the same text showed twice a few hundred pixels apart. The season strip now drops a synopsis that equals the hero's description, or is cut short from it, and clamps its own to two lines at 72ch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(details): draw the episode thumbnail hairline as a border An inset shadow over the artwork is invisible to the surface-contrast checks and to forced-colors mode; a 1px border is what both read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): follow the list-first, flat episode items The list is now the default view, rows and cards share .episode-item classes, and grid titles no longer carry the "N." prefix. The surface checks measure the thumbnail hairline instead of a row border, assert that hovering a row does not move its title, and capture list, grid and hover screenshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(ui): episode list, section rhythm and hero tail contracts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dashboard): continue the hero artwork under the first rail The dashboard hero's fade ended at its edge, so the artwork stopped just above the Continue Watching heading. Like the details hero, it now grows by a 160px tail that the rails are pulled up over: the art fades behind the first rail's heading and reaches the page colour before its cards, so the rail's scroll-edge fades never show as a box. The narrow layout, whose slide carries its own scrim block, keeps no tail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changes): note the smoother hero fade Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(details): season chips up to four, a counts menu from five, no season art The season picker drops every poster: chips for up to four seasons, a menu button from five whose rows read the season and "N episodes · M watched". The season synopsis loses its cover, sits on the number column 24px above the list and renders nothing without a plot of its own. The player's episode panel shares the picker and loses its dropdown thumbnails too; its season strip stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(details): bare rows for seasons without metadata, skeletons while it loads A season whose final episode data has no plot and no usable still (the series poster or season cover repeated as a still counts as none) renders 44px rows: number, title, meta and an inline check or progress bar, the same action slot, no grid toggle. While the provider list or a TMDB lookup that could still fill a bare-looking season is outstanding, the episodes are skeleton rows at the full row's exact geometry instead of a spinner; a season the provider already describes renders at once. Xtream tracks the show match and each season's enrichment in the store; Stalker tracks its show match in the selection state and settled season fetches in its TMDB service, and now reports a regular series' season request as loading instead of an empty series. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(details): loading skeletons laid out like the loaded page The hero skeleton targeted ngx-skeleton-loader's old `.loader` class, so its blocks kept the library's light default fill and margins; `display: block` stacked the chips and buttons into columns; and the details column sat at the top while the loaded one is bottom-aligned, so the title dropped ~150px on load. It is now plain shimmer blocks at the loaded hero's geometry with the stage height kept while loading. The Xtream series page shows an episodes section skeleton under it, and the Stalker series hero holds the Play button's place while seasons load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): the modeled Stalker series renders bare rows Its episodes repeat the series poster and carry no plots, so the shared series surface check now expects 44px bare rows without thumbnails or a grid toggle in both themes. The thumbnail hairline and grid checks stay in the Xtream suite, whose episodes have stills. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style(details): format the season picker stylesheet Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(details): episode skeletons never outlive their metadata The loading state added for TMDB-enriched episode rows could stick or flicker: - an empty season is never enriched, so its host never settled it and the season showed six skeleton rows forever instead of its empty state; - Xtream re-marked an enriched season pending on every selection write, turning rendered rows back into skeletons (and dropping row focus) for each cache read; - a superseded lookup of a reopened series, or of a Stalker item without an id, could settle a newer lookup's pending state or never clear it; - TMDB requests have no timeout, so a blocked TMDB host held provider episodes back for minutes. Metadata now only holds back a season that has episodes, the wait is capped at 4s per season, only the latest lookup of a key (or selection) settles it, a settled Xtream season stays settled within a visit and a new visit starts its seasons afresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(details): described seasons render at once; room for titles on phones Metadata lookups held every season as skeletons until TMDB answered, even one the provider had already described with plots and stills. The state now waits for metadata only when the current data would render bare; described rows show at once and the metadata lands in place. At a pane width of 480px or less the list row kept the 112px thumbnail and the action slot beside the text, leaving a 320px phone about 18px for the title. The thumbnail shrinks to 96px there and the actions take their own row under the text. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(details): keep phone-width grid cards stacked The phone-width grid-area placements applied to grid cards too, which declare no template areas, so a card's artwork and text overlapped in a narrow pane. The placements are scoped to list rows; the surface check now also asserts a card's text stays under its artwork at 360px. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(details): a still that only one episode carries is not a repeat The distinct-stills check counted unique image URLs and treated a single one as the series poster repeated, so a season where one episode has a genuine still and the rest have none lost that still and went bare, with its grid toggle. One image on one episode is now a still; only one image on several episodes counts as a repeat. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(details): phone-width skeleton rows match the rows they precede At a pane width of 480px or less the finished list row uses a 96px thumbnail and an action row under the text, but its skeleton kept the 112px single-row layout, so pictures and text moved when loading ended. The skeleton row now carries an empty 34px action slot and mirrors the phone layout. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(details): no air date from a placeholder or an impossible day The Date constructor turned a provider's "0000-00-00" into a day in 1899 and rolled "2025-02-31" into March, so the episode meta line showed dates nobody sent. An ISO day is now validated part by part and an invalid one leaves the date out. The season menu's container colour goes through mat.menu-overrides(), as the UI guidelines require. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * style(details): spinner colours through mat.progress-spinner-overrides() The episode item set the spinner's indicator colour as raw --mat-* declarations; the UI guidelines want Material tokens set through the component's overrides mixin, which rejects unknown names at build time. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(details): unknown season counts, resume position, hero-skeleton spec A lazy Stalker VOD season the portal has not answered yet showed "0 episodes" in the season menu: the picker now takes the per-season load states and shows no count for such a season. A started episode whose duration nobody knows (no provider or TMDB runtime, none saved with the position) lost its saved position from the meta line; it now reads "Resume at 12:34". The downloads offline-detail spec queried the ngx-skeleton-loader the hero skeleton no longer uses; it queries the hero-skeleton test id. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(playback): a reused external player starts each load at its own offset MPV and VLC are launched with a global --start / --start-time for a resumed title, and a reused process applies that to every later load: "Play from beginning", the next episode and a later resume all began at the first launch's offset. The seek sent right after loadfile does not help — mpv rejects it before the file is loaded, and the command sender never reads the reply (verified against a real mpv over IPC). Every reuse load now carries a per-file start (0 unless an offset is requested): mpv's loadfile options and VLC's :start-time input option. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(details): loading flags follow the detail container, menus know unloaded seasons The fullscreen episode picker reports its season through the same onSeasonSelected as the detail container, so the Stalker view's episode-list and metadata loading flags followed whichever season was picked last: choosing another lazy season in fullscreen turned the detail page's loaded season into skeletons until the portal answered. Both flags now follow the detail container's own selection. The fullscreen panel also shares the season picker but never forwarded its per-season load states, so a lazy season read "0 episodes" in its menu; the states are forwarded now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(details): Xtream metadata loading follows the detail container too Like the Stalker view, the Xtream seasons service keyed its metadata loading flag off the season selected last, which the fullscreen episode picker also sets: picking another season there could hide the detail page's settled bare rows behind skeletons for up to four seconds. The flag now reads the detail container's own selection; the picker's choice still gets enriched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5c7a8a572a |
fix(dashboard): keep finished titles off Continue Watching and continue a series with its next episode (#1841)
Continue Watching lists only what is left to watch: a title counts as watched at 90% (PORTAL_WATCHED_PROGRESS_PERCENT), and a series moves on to the episode after the one watched last (getSeriesNextUp, shared with the series page's play button), ignoring extras (season 0). Episode lists come from DashboardSeriesEpisodesService with bounded, backed-off lookups; a cached list that predates the episode watched last is fetched again. Stalker and Xtream series pages date the rows they write so the quick start resumes the episode just played, and the season tabs open the extras only when nothing else is left to load. Fixes #1838 Co-authored-by: Ramjot Singh <13517857+RamjotSingh@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
2e321cb1bd |
chore(deps): bump mpegts.js from 1.8.1 to 1.8.2 (#1835)
* chore(deps): bump mpegts.js from 1.8.1 to 1.8.2 Bumps [mpegts.js](https://github.com/xqq/mpegts.js) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/xqq/mpegts.js/releases) - [Commits](https://github.com/xqq/mpegts.js/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: mpegts.js dependency-version: 1.8.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * test(playback): accept the mpegts.js 1.8.2 error contract 1.8.2 keeps the public ErrorTypes and ErrorDetails exports unchanged, so the version lock moves to 1.8.2 with the same accepted contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d73bb7ca16 |
chore(deps): bump shaka-player from 5.2.4 to 5.2.12 (#1836)
* chore(deps): bump shaka-player from 5.2.4 to 5.2.12 Bumps [shaka-player](https://github.com/shaka-project/shaka-player) from 5.2.4 to 5.2.12. - [Release notes](https://github.com/shaka-project/shaka-player/releases) - [Changelog](https://github.com/shaka-project/shaka-player/blob/v5.2.12/CHANGELOG.md) - [Commits](https://github.com/shaka-project/shaka-player/compare/v5.2.4...v5.2.12) --- updated-dependencies: - dependency-name: shaka-player dependency-version: 5.2.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * test(playback): accept the Shaka 5.2.12 error contract 5.2.12 keeps the public error severities, categories, codes and request types of 5.2.4, so the version lock moves to v5.2.12 with the same accepted contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2738bc28a1 |
docs(portals): document forced MPV/VLC launch and pending-start contracts (#1809)
* docs(portals): document forced MPV/VLC launch and pending-start contracts The rules #1792 settled for forced external launches and playback-start bookkeeping lived only in code comments and specs. Record them as contracts in the owning documents: - embedded-inline-playback.md: the shared detail-host rules (one external player per title, unconfirmed teardown cancels, ownership rechecked after every await, owner-scoped pending state). - xtream-portal-compatibility.md: the series launch chain, page-token duplicate guard and queued choice. - vod-multi-source.md: the movie menu launch and reset follow the copy the primary button acts on; pending resets are a list of targets. - stalker-portal.md: the per-series launch queue, the batch-held choice and the movie launch/reset pending start. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(portals): correct launch-contract claims against the code - A launching session is published before the launch IPC resolves; what it lacks until then is an exact closer. - The series watched/reset batch and the Xtream movie launch gate are page-wide, not owner-scoped. - Only the Stalker movie hosts retire a pending start, and that does not clear the repeat guard of a launch still in flight. - Name only the specs that exercise the Xtream series rules. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(portals): tighten closer timing and page-wide gate wording A launching session may already have its closer before the launch IPC resolves, the Xtream movie launch gate does not cover the inline player's diagnostic fallback, and the hero-state spec covers only the external-player and reset rows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(portals): fix stale session-timing comment and search guard wording - serial-details-external-launch.ts: Electron publishes a `launching` session as soon as the launch IPC arrives, not only after the launch settles. Comment only; no behaviour change. - stalker-portal.md: the search host's selection check does not include the content type; a switch to a series supersedes the launch through the playback owner key instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
2dfdd65f53 |
refactor(details): give the detail-page files real max-lines headroom (#1813)
* refactor(xtream): split the series details page into focused services serial-details.component.ts sat at the 400-line max-lines limit and its playback service and spec were close behind. Move cohesive concerns out without changing behavior: - route params, the provider-only flag and the (re)load of the addressed series go to SerialDetailsRouteService; the component still registers the effect, so effect order is unchanged - season descriptions, posters and the TMDB season enrichment go to SerialDetailsSeasonsService - actor, Similar and Discover navigation go to injectXtreamDetailNavigation, shared with the movie page - the watched toggles and the episode playback payload leave SerialDetailsPlaybackService for SerialDetailsWatchToggles and buildSerialEpisodePlayback - the spec's TestBed moves to a harness and the watched-toggle cases to serial-details.season-watch.spec.ts Counted lines: component 400 -> 318, playback service 388 -> 342, component spec 1196 -> 674. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(xtream): move VOD route selection and position state out of the page vod-details-route.component.ts grew from 618 to 741 counted lines and vod-details-playback.service.ts reached 395. Extract without changing behavior: - the route-derived read model (selected movie, category, catalog item, fallback view, multi-source identity, session and content keys) goes to VodDetailsSelectionService; the component keeps the same member names - trailer state and the Similar-rail click move into the hero presenter, the cancel-download prompt and the progress-ring geometry into the downloads service, navigation into injectXtreamDetailNavigation - stored positions (last seen, route row, guarded load) become VodDetailsPositionState and the external-launch bookkeeping becomes VodExternalLaunchClaim - drop the unused MatTooltip import (NG8113) and eight unused imports Counted lines: route component 741 -> 492, playback service 395 -> 344. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(stalker): extract series-view positions and watched toggles stalker-series-view.component.ts grew from 1601 to 1813 counted lines and its spec reached 1198 of 1200. Move code out verbatim: - saved positions, their reconcile and the persist/clear writes go to the component-provided StalkerSeriesPositionsService; the ordering of reads and writes goes to StalkerSeriesPositionQueue - episode, season and series watched toggles go to StalkerSeriesWatchToggleService, the batch core to runStalkerWatchToggleBatch - the lazy VOD season loads go to StalkerVodSeasonEpisodeLoader Every effect stays registered in the component constructor in its original order; template bindings and public member names are unchanged. The spec setup moves to a shared harness and the spillover-prefetch and TMDB season cases to their own specs; the 184 cases of the directory are unchanged. Counted lines: component 1813 -> 1136, component spec 1198 -> 670. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(stalker): extract catalog-detail position and search paging - stalker-catalog-detail.component.ts (397): the stored VOD position and its runtime updates become StalkerCatalogVodPosition, the Play/Resume start becomes startStalkerCatalogVodPlayback - stalker-search.component.ts (776, baselined): the paging resource, the accumulated results and the parental-lock bookkeeping become StalkerSearchPagingController, with pure helpers in stalker-search-results.util.ts. The spec reaches the moved members through component.paging; its cases and assertions are unchanged. Counted lines: catalog detail 397 -> 317, search 776 -> 482. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(ui): give season-container and vod-details real line headroom - season-container.component.ts (396): season auto-selection and the seasonSelected emission move to createSeasonAutoSelectState, called at the same place in the constructor so effect order is unchanged; the episode subline becomes buildEpisodeSubline - vod-details.component.ts (393): the cross-portal Similar loader, the provider-only download state and the actor/Similar route helpers move to sibling modules Inputs, outputs, selectors and public members are unchanged. Counted lines: season container 396 -> 341, vod details 393 -> 337. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(web-e2e): move Stalker portal helpers into fixtures stalker.e2e.ts was at 1196 of 1200 counted lines. Move the mock endpoints, scenario MACs and page helpers to stalker-portal.fixture.ts and the embedded-series steps three tests repeated to stalker-embedded-series.fixture.ts. Every test stays in stalker.e2e.ts in the same order, with the same serial mode and OWNED_MACS reset. Counted lines: 1196 -> 981. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: follow the detail-page extractions Name the Stalker watched-toggle and position services that now own the batch and reconcile code, point AUTH_REJECTED_MAC and the scenario MACs at stalker-portal.fixture.ts, and drop two stale statements about components sitting at the max-lines cap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(xtream): note why the seasons service is injected last Its TMDB enrichment effect keeps the position it had as the first effect of the component constructor only while it is created after the other services' effects. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
4adc3ba20f | fix(ui): one watch-progress colour in app chrome and in the player (#1798) | ||
|
|
cb252940b2 | fix(workspace): move detail Back into the header and drop the rail brand (#1789) | ||
|
|
d8229b98fa |
feat(playback): record recently viewed only after the stream plays (#1732)
* feat(playback): record recently viewed only after the stream plays A channel, movie or episode used to enter Recently Viewed (and the dashboard's Continue Watching hero) the moment it was selected or its link was resolved, so streams that failed straight away cluttered the history. Writers now defer the write to a root PlaybackHistoryGate, keyed by the stream URL and/or the playback session key. The inline players confirm those keys once the owned engine's position has advanced by two seconds (seeks, stalls, pauses and a previous stream's progress do not count), the radio player does the same, and a launched MPV/VLC session confirms on `opened`/`playing`. M3U with MPV/VLC configured keeps recording on selection. Covers M3U (live, radio, movie detail), Stalker (live, radio, VOD, series), Xtream VOD and series, and the global live collection. The M3U host's embeddedPlayback is now compared by value: the history write updates the playlist meta mid-playback, and a new but identical playback object remounted the engine and restarted the stream. E2E flows that relied on recording-on-click now play local fixtures (HLS/TS/WebM routed in place of unreachable or public streams) and wait for confirmed playback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): tighten recently viewed confirmation per review - Correlate by session key first: when both the deferred write and the confirmation carry a playbackSessionKey, only that is compared, so the same stream URL played in another playlist no longer records a failed attempt. URLs remain the fallback (portal writes, MPV/VLC sessions). The M3U radio player now receives the host's session key. - Count only playing progress: engines report `playing` (not paused, not seeking) with each time update, so short seeks of paused media no longer confirm a view. - Xtream: a write confirmed after a playlist switch still saves to its own playlist but no longer replaces the current playlist's recent list. - Global live tab: a row confirmed after another row was selected still moves to the top of an open Recently Viewed list (only a disposed tab skips the notification). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): confirm session-keyed history only by its own session A write deferred with a playback session key (M3U) is now confirmed only by that key. The app-wide MPV/VLC session confirmation carries just the URL, so opening the same stream externally from another playlist could still commit an abandoned attempt. An "Open in MPV/VLC" recovery launch is instead confirmed by the WebPlayerViewComponent that requested it, under its own session key, once the launch has opened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(playback): keep the history gate off the initial bundle The `@iptvnator/services` barrel ships in the initial bundle, so adding PlaybackHistoryGate there (and subscribing to it from the app-wide ExternalPlaybackService) grew renderer.initialBytes by 1,141 bytes. - Move the gate to a new lazy-only `playback-data-access` project (`@iptvnator/playback/data-access`; scope:shared, domain:playback, type:data-access) and register it in the coverage policy. - The gate subscribes to MPV/VLC session updates itself; it is created by the first deferred write, which precedes the launch it waits for. ExternalPlaybackService is back to master. - The Xtream "playlist switched before confirmation" check moves to the lazy helper; the initial-path store only takes a `skipListRefresh` flag. Net effect on this branch: +27 bytes over master (master itself is 108 bytes over the ratchet baseline already). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(playback): drop late Xtream confirmations off the initial path The Xtream store ships in the initial bundle, so even the small `skipListRefresh` flag cost 27 bytes there. A confirmation can only arrive after a switch to another playlist from a slow MPV/VLC launch (the inline player goes with the page), so the lazy helper now drops it instead: recording it would misfile the item or replace the other playlist's recent list. with-recent-items is back to master. This branch is now 3 bytes below master on renderer.initialBytes; the ratchet still reports master's pre-existing overage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(playback): pass the spec type-check gate from master - playback-data-access: align tsconfig.spec.json with the epg-data-access config #1705 updated (bundler resolution, global.d.ts for window.electron). - M3U recent-history spec: type the selectSignal override. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): keep late Xtream confirmations in their own playlist history A confirmation that arrives after a switch to another playlist (a slow MPV/VLC launch) is no longer dropped: the lazy helper saves it to the captured playlist through the data source, without reloading the store's recent list, which belongs to the other playlist by then. The store and its barrel ship in the initial bundle, so the save path stays in the feature helper; renderer.initialBytes stays under the baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): correlate global live-tab history by its session key The unified Favorites/Recent live tab deferred its history write by stream URL only, so the same URL played from another playlist could confirm a failed selection, and a switch to catch-up before confirmation could never match. It now defers with the tab's playlist-scoped playbackSessionKey (the key its players confirm with), and the tab's radio player receives it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): keep MPV/VLC rows of the live tab confirmable by URL The live tab's session key can only be confirmed by its own inline players; MPV/VLC confirm the launched URL alone. A row that goes to an external player (also later, after a double-click) now defers by URL, and only rows played inline carry the session key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(playback): per-channel M3U history attempts, capability-based Xtream fallback - M3U: the recently-viewed dedupe key now includes the channel id, so a second row of the same URL defers its own write (its session key) and is recorded when it plays after the first row failed. - Xtream late write: key uncached content by Xtream id per supportsXtreamSqliteDataSource (the data-source factory's contract), not by a generic Electron bridge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4cce4acaad |
feat(portal): season thumbnails in the season dropdown + PR #1628 follow-ups (#1633)
* feat(portal): season thumbnails in the season dropdown + PR #1628 follow-ups Follow-ups to the season posters shipped in #1628: - The >6-seasons dropdown (`SeasonTabsComponent`) now carries a 28×42 season thumbnail at the start of each menu row that has a poster and in the closed trigger for the selected season, fed by a new `seasonPosters` input from the season container and the fullscreen episode panel. Rows without a poster get no placeholder, a failed image is dropped, and the pill row stays text-only as the design review decided. - The fullscreen season strip's episode count uses its own `PORTALS.EPISODE_COUNT_ONE/OTHER` keys instead of borrowing the download manager's; all 18 locales filled through the i18n merger from their existing `DOWNLOADS.EPISODE_COUNT_*` translations. - The Stalker mock's serve targets no longer pin `PORT` (an nx:run-commands `env` entry overrides the shell), and `main.ts` resolves `PORT`, then the Playwright-side `MOCK_PORT` alias, then 3210 — so `MOCK_PORT=3310` now relocates the whole E2E run. The Xtream mock honours `XTREAM_MOCK_PORT` the same way. - `resolveAutoSelectedSeason` gets a direct spec covering every branch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(xtream-mock): mint marketing asset URLs on the port the server bound Greptile P1 on #1633: the listener honoured `XTREAM_MOCK_PORT`, but `marketingAssetOrigin()` still read `PORT` alone, so a run relocated only through the alias sent every poster/backdrop/logo/episode URL to 3211. One resolver (`resolveXtreamMockPortString` in `mock-port.ts`: `PORT`, then `XTREAM_MOCK_PORT`, then 3211) now feeds the environment parser, the marketing asset origin and the demo-guide origin fallback. A spec pins the precedence and that `marketingAssetUrl` follows the bound port. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
7790e68147 |
feat(portal): show each season's own poster beside the season tabs (#1628)
Series detail pages now render the selected season's poster as a season
cover next to the season tabs and description, and the fullscreen episode
panel shows the same poster as a season strip above its tabs.
Resolution is TMDB-first, like the show artwork merge: the lazy season
enrichment stores `/tv/{id}/season/{n}` `poster_path` as a w342 URL in
`tmdb_season_posters` (Xtream) or `StalkerSeriesTmdbSeasonsService.posters()`
(Stalker), under the same write-only-if-changed convergence guard as the
season overview. Xtream falls back to the provider's `seasons[].cover_big`/
`cover` when it is an http(s) URL other than the show poster, because panels
repeat the show poster on every season. Stalker is TMDB-only.
The cover column is not rendered for one-season items, seasons without a
poster, or a failed image, so every fallback is today's markup. It is sized
by a new `--season-cover-width` token (96/120/144px per Settings.coverSize).
The hero poster never follows the season.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
c76a901e8d |
fix(ui): keep one Back arrow on detail pages (#1627)
Movie and series detail pages showed two arrow_back controls while the inline player was open: the shell's sticky arrow (added in #1576 so Back survives scrolling) meant "Close player" in watch state, while the now-playing bar carried a second arrow that meant route-level Back — the same icon with two meanings, next to a "Close player" button that duplicated the first. The shell's sticky arrow is now route-level Back in browse and watch alike, and the bar carries no arrow of its own. Closing the player is the bar's "Close player" button and Escape, which still unwinds one level (close, then back). Hosts without a browse Back target (M3U, downloads) render no arrow in either state. Unit specs for the shell and the inline player cover the new contract; the Electron and web E2E helpers that pressed Back from watch are updated, and the M3U flow closes the player through the bar's button. Docs, the mirrored CLAUDE.md/AGENTS.md paragraph and a release note follow the change. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
fa8ce26991 |
feat(playback): fullscreen episode panel for series playback (#1620)
Series playing in fullscreen get the same slide-in side panel the live channel list has, with season tabs and the episode list: rest the mouse on the left edge, click it, or press C; pick an episode and it plays inline without leaving fullscreen. - Panel contract: `FullscreenChannelPanelHost` gains optional `panelSearchEnabled` and `panelKind`; the template context gains `open`. Pointer/keyboard rules and the four live providers are unchanged. - Series host: `PortalInlinePlayerComponent` provides the token through `createEpisodePanelHost()` and stamps `app-fullscreen-episode-panel` (SeasonTabsComponent over rows with TMDB still or numeral tile, label, runtime, clamped overview, progress, watched check, now-playing marker; playing row centred on open). Episode clicks reuse the Up Next rail's inline path; season tab clicks reach the hosts' `onSeasonSelected` (Xtream TMDB season enrichment, Stalker lazy VOD load with a Retry row after a failed request). - Gates: `Settings.fullscreenChannelPanel` (label now covers both lists in all locales), episode content only, native-view Embedded MPV withheld by the view, external players excluded. - Inline-series e2e moved to `xtream-series-playback.e2e.ts` with shared Xtream helpers in a fixture; adds a fullscreen episode switch through the panel. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
7f724494b9 | feat(portal): mark movies as watched from the detail page (#1605) | ||
|
|
fff022afe4 | fix(ui): keep detail back navigation available while scrolling (#1576) | ||
|
|
c952b55da1 | feat(playback): enrich failure diagnostics and add safe support reports (#1574) | ||
|
|
436825bdec |
fix(xtream): try advertised TS after initial web HLS HTTP failure (#1558)
* fix(xtream): try advertised TS after initial web HLS HTTP failure * refactor(playback): extract fullscreen channel panel state * test(xtream): keep synthetic media within the mock project |
||
|
|
07ccf45f32 |
fix(playback): hide VLC RC console on Windows (#1533)
* fix(playback): hide VLC RC console on Windows * fix(playback): preserve VLC retry arguments containing quiet flag |
||
|
|
5e51c1ea5d |
fix(playback): route non-HLS containers to the native source path (#1510)
The HTML5 player chose its engine by exclusion: anything that was not mpd/ts/mp4 went to hls.js, so .mkv (the default Xtream series/VOD container), .webm, .avi, .mov and .m4v were fed to hls.js as manifests. hls.js raised a manifest error and the player showed the network/provider diagnostic over media Chromium plays natively. Add resolvePlaybackUrlSourceKind() to @iptvnator/playback/util as the one URL-to-engine rule (mpd -> dash, m3u8/m3u -> hls, ts/m2ts/extension-less -> mpegts, everything else -> native) and read it from both the HTML5 player and ArtPlayer's getArtPlayerVideoType(), so the two engines agree. ArtPlayer serves every native container through a single ART_PLAYER_NATIVE_SOURCE_TYPE custom type, keeping the source session the owner of teardown and controls binding. The HTML5 native <source> carries the video/mp4 hint only for MP4-family files: a hint the browser's canPlayType() rejects makes it skip the source, and Chromium demuxes containers it does not advertise there. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
72727a5dfa |
feat(dashboard): detail-first Continue Watching cards with quick actions (#1469)
* feat(dashboard): detail-first continue watching cards with quick actions (#1441) Continue Watching cards now open the detail page on click like movie cards; resuming the saved episode, marking it watched, and removing the entry from history move into a per-card ⋮ menu. Series details land on the earliest season with unwatched episodes (or the latest once all are watched) instead of always season 1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(dashboard): address review findings and season auto-select regressions - A session's own watched toggles no longer re-resolve the selected season when the positions map first fills — marking season 1 watched used to jump the view to season 2 (CI regression in the web and Electron season-watched-toggle E2Es). - The all-watched season fallback skips loaded-but-empty seasons and picks the latest season that has episodes (Greptile P1). - Mark as Watched uses the strict failure-propagating save boundary (Codex P2), and both card mutations surface persistence failures via a snackbar with the new WORKSPACE.DASHBOARD.ACTION_FAILED key in all 19 languages (Greptile P2). - Season E2Es now assert the intended post-reload behavior: the fresh mount lands on the earliest unwatched season while season 1 keeps its watched state behind its tab. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0a2fe263db |
feat(portals): mark a whole series as watched in one click (#1451)
* feat(portals): mark a whole series as watched in one click Adds a series-level watched toggle to the season header's new overflow menu on both Xtream and Stalker series detail pages (issue #1442 v2, building on the season-level toggle from #1447). - Shared: buildSeriesWatchToggleRequest flattens every loaded season with the season builder's mark/unmark semantics; the direction is always the one the label advertised, never re-inferred at persist time. Watch-toggle state math for both scopes moves into the new component-provided SeasonWatchPresenter (the container component sat at the max-lines cap). - Xtream: the series request reuses SerialDetailsSeasonWatchService through a scope-parameterized handle(), the same stillCurrent ownership guard, and the XtreamStore.loadAllPositions badge refresh. - Stalker: the season handler's core is extracted into runWatchToggleBatch (feedback keys per scope). Lazy Ministra VOD hydrates unloaded seasons sequentially first (zero writes on a failed fetch, silent abort on navigation), re-runs the position reconcile synchronously so newly hydrated episodes' legacy rows are cleaned, then rebuilds the request keeping the clicked direction; an all-watched outcome reports an honest count-0 snackbar. - Container: new hasUnloadedSeasons input blocks the fully-watched verdict and the count label while lazy seasons are unloaded, and the empty mark request contract lets the host hydrate-then-rebuild. Six new XTREAM i18n keys, synced to all 18 locales via the i18n-fill workflow. No new IPC: the existing playback-position batch channels are season-agnostic. Refs #1442 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): treat an empty Stalker season answer as loaded, not pending A lazy season the portal ANSWERS for with zero episodes was still counted as unloaded (episodes.length === 0 heuristic): the series label stayed countless forever and every series toggle re-fetched the empty season, while a glitch-empty answer could silently skip a season and still report success as if nothing remained. VodSeriesSeasonVm gains an episodesLoaded flag set by every successful episode fetch — including an empty one — and the series toggle's pending predicate, hydration re-check, and hasUnloadedVodSeasons now key on it. A loaded-and-empty season unblocks the count label and the fully-watched verdict instead of re-fetching; a fresh detail mount still re-fetches, so a one-off glitch self-corrects next session. Addresses the Greptile P1 on PR #1451. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): harden lazy season hydration against malformed and racing loads Two review findings on the series watched toggle: - fetchVodSeriesEpisodes now trusts an empty answer only when the envelope actually carried a well-formed array; a malformed envelope or an answer whose rows contain no recognizable episode rejects, so the load fails instead of the season being recorded loaded-and-empty and silently skipped by the series batch. - loadEpisodesForSeason is single-flight per season: a tab click, the spillover prefetch, the quick-start recursion, and the series-toggle hydration join one in-flight request instead of duplicating portal traffic — previously a second request's failure could abort a series toggle whose original request succeeded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7fc9380bff |
feat(portals): mark a full season as watched in one click (#1447)
* feat(portals): mark a full season as watched in one click Series detail pages on both Xtream and Stalker portals get a season-level watched toggle next to "Download season": marking writes full-progress rows for the unwatched episodes only (real durations survive), a fully watched season flips the action to unwatch-all. Persistence goes through new batch IPC channels (DB_SAVE/CLEAR_PLAYBACK_POSITIONS_BATCH, one SQLite transaction with onConflictDoUpdate().run(); the PWA data source rewrites its localStorage blob once). Stalker deliberately bypasses the batch IPC and loops the existing position-mutation queue so legacy-row reconciliation still runs and the queue coalesces to a single reload; partial failures surface a dedicated snackbar. Also removes the dead toggleEpisodeWatched store method, splits season-container/serial-details-playback under the max-lines cap (season-watch-toggle.util.ts, SerialDetailsSeasonWatchService), and classifies *.spec-data.ts fixtures under the test max-lines ceiling (baseline shrinks by main.preload.spec-data.ts). Closes #1442 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): guard stale season batches and split partial-unwatch feedback Review follow-up (Codex on #1447): - A season batch completing after the user navigated to another series or playlist no longer writes the old series' rows into the freshly reset position state (episode ids can collide across playlists); the Xtream host captures the playlist/series identity before awaiting and skips the rendered-state mutation when it changed. The DB write is unaffected — it carries its own playlistId. - A partially failed "mark season as unwatched" on Stalker now reports a dedicated SEASON_MARKED_UNWATCHED_PARTIAL message instead of the watch-direction "marked" text; translated into all 18 locales. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): exclude the playing episode from season marking and count partial saves Second review round (Codex on #1447): - The episode currently playing (inline or in an external session, or with a launch in flight) is excluded from a season's mark-watched batch: the player persists its live position every ~15 s and would immediately overwrite the just-written full-progress row. The button count reflects the exclusion and the action disables when nothing is markable. Unmarking still clears such an episode — the recreated in-progress row reflects live playback truthfully. - A Stalker StalkerSeriesPositionPartialSaveError (scoped watched row saved and published, only legacy cleanup failed) now counts as a watched success instead of feeding false total-failure feedback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): gate stale season-batch snackbars on the originating page Third review round (Codex on #1447): a batch resolving after the user navigated away no longer shows its contextless success/error snackbar on the newly opened detail page — the same ownership check that guards the state mutation now guards the feedback too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): sync catalog progress badges after toggles and gate Stalker feedback Fourth review round (Codex on #1447): - Any Xtream watched toggle (single episode or season batch) now refreshes XtreamStore.loadAllPositions after persisting — the catalog reads series-progress badges from the store, which otherwise loads positions once per playlist, so returning from the detail kept stale badges. Skipped when the playlist changed mid-flight (the store then belongs to the other playlist; its own init reloads positions). - Stalker's season snackbars are gated on the captured playlist/series identity, matching the Xtream ownership guard — a batch draining after navigation no longer reports on the newly opened page. - Stalker season-toggle specs moved to stalker-series-view.season-watch .spec.ts with their own harness; both prior spec files sat at the 1200-line test ceiling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: describe the season watched toggle in CLAUDE.md Fifth review round (Codex on #1447): the canonical Seasons entry in the VOD/Series detail section now covers the bulk toggle, its playing-episode exclusion, both persistence paths, catalog badge sync, and the stale-completion contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): let only the latest positions load patch the Xtream store Sixth review round (Codex on #1447): loadAllPositions is now latest-load-wins — a fetch superseded while in flight (playlist switch before getAllPlaybackPositions resolves) no longer patches the singleton store with the previous playlist's position maps, which could leave the new catalog showing the old playlist's progress badges. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: reflect the spec-data max-lines classification in CLAUDE.md and AGENTS.md Seventh review round (Codex on #1447): both canonical max-lines descriptions now list **/*.spec-data.ts among the test-ceiling globs so future agents neither treat these fixtures as production files nor remove the exemption unknowingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): parse "N min" durations when marking episodes watched Eighth review round (Codex on #1447): Stalker VOD episodes report durations like "45 min", which parseDuration could not read — bulk (and single) mark-watched then persisted 1/1-second rows. The minute format now parses to seconds, matching what the removed legacy store method already handled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): parse compound hour durations and cover the toggle end-to-end Ninth review round (Codex on #1447): - parseDuration now reads the compound "1h 30min" form the Xtream fixtures emit (hour group optional, so "45 min" keeps working) — bulk-marked episodes no longer persist a minutes-only duration. - New Playwright coverage exercises the season toggle through the real UI on both portals: Xtream (category → series detail → mark → reload-persistence → unmark) and Stalker (embedded-series flow, mark → unmark with the item's actual episode count). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): refresh Stalker catalog progress badges after watched toggles Tenth review round (Codex on #1447): the Stalker mirror of the Xtream catalog sync — StalkerCatalogFacadeService loads its position maps once per playlist and the runtime bridge only pushes external-player updates, so renderer-initiated toggles left grid badges stale. The series view now calls the facade's new ownership-checked refreshPositions after the season batch (including partial successes) and after single toggles; the reload is latest-load-wins like the Xtream store fix. Optional injection keeps collection-detail mounts outside the catalog working. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(portals): cover the season toggle batch IPC end-to-end in Electron Eleventh review round (Codex on #1447): the new Electron E2E marks a season through the real UI, asserts the eight SQLite rows written by DB_SAVE_PLAYBACK_POSITIONS_BATCH directly through the preload bridge, proves persistence with a full app relaunch (renderer and main process die, so state can only come from the database file), and clears again through DB_CLEAR_PLAYBACK_POSITIONS_BATCH back to zero rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(dashboard): keep watched rows out of the series resume target Twelfth review round (Codex on #1447): a watched position row — a natural finish or a manual/bulk "mark watched" marker — is a completion record, not resumable progress. Continue Watching no longer auto-plays such an episode at its end; the handoff stays detail-only and the series page's quick-start picks the first unwatched episode instead. Card progress bars and SxxEyy badges keep their current source. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): fail closed on refresh reads and gate batch APIs by capability Thirteenth review round (Codex on #1447): - Position-cache refreshes now use a failure-propagating read (getAllPlaybackPositionsOrThrow through the Electron data source): a transient IPC failure rejects instead of masquerading as an empty list, so a populated store/facade cache stays stale-but-populated rather than being wiped. All load/refresh call sites handle the new rejection (init loads may retry on the next activation; post-toggle refreshes log and keep the snackbar flow). - The season-batch bridge methods joined playbackPositionStorageMethods, so a bridge lacking them degrades to the in-memory path wholesale instead of throwing mid-action. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3103eba083 |
fix(playback): apply saved player changes to mounted web players (#1437)
* fix(playback): apply saved player changes to mounted web players WebPlayerViewComponent resolved the saved engine from a one-shot StorageMap snapshot taken at mount, so a player switch from the command palette or settings page confirmed via snackbar and persisted the setting while an already-mounted Xtream/Stalker player silently kept the previous engine. The same snapshot also made first play mount the default Video.js engine and swap to the saved one once the async read landed. Resolve the player (and recording folder) from the live SettingsStore signal instead and drop the snapshot entirely. Precedence is unchanged: temporary recovery override -> host playerOverride -> saved player -> Video.js. Hosts passing no override (Xtream/Stalker live layouts, the portal inline detail player) now track saved changes in place; first mount reads the already-loaded store, so the default engine no longer flashes. Regression coverage (all verified to fail with the fix reverted): three unit tests on the component and two Xtream live-route e2e tests — a palette switch reaching the mounted player without a layout remount, and a MutationObserver engines-ever-seen assertion that the saved engine mounts first time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(playback): retain mounted engine when saved player becomes MPV/VLC With the saved player now live-tracked, a mid-session palette switch to managed MPV/VLC cleared the inline binding on hosts without a playerOverride and left a blank viewport — the web player view can neither render nor launch external players. resolveRenderableWebPlayer keeps the mounted engine in that case; the external choice applies when the host starts the next playback. Renderable players, including Embedded MPV, still apply live. Raised by Codex review (P2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
de77c6d467 | fix(playback): update mpegts.js to 1.8.1 (#1412) | ||
|
|
77842b9d04 | fix(playback): update Shaka Player to 5.2.4 (#1411) | ||
|
|
d73acd6bfc | fix(playback): clarify external player launch feedback (#1388) | ||
|
|
fd96b85c19 |
feat(playback): recommend recovery actions (#1374)
* docs(playback): design recovery recommendations * docs(playback): plan recovery recommendations * refactor(playback): extract diagnostic utilities * feat(playback): define recovery recommendation contracts * feat(playback): rank recovery recommendations * feat(playback): track session recovery attempts * feat(playback): identify content recovery sessions * feat(ui): add ranked playback diagnostic panel * feat(playback): switch temporarily to recommended players * test(playback): cover temporary player recommendation * test(playback): verify recommendation capability guards * docs(playback): document recovery recommendations * fix(playback): keep recovery keys credential-free * fix(playback): remove derived tracking ownership * fix(playback): preserve distinct recovery fallbacks * fix(playback): reset resume for new sources * fix(playback): preserve desktop recovery guidance * docs(playback): clarify recovery policy exceptions * fix(playback): reject stale progress updates * fix(playback): keep protected recovery guidance neutral * test(playback): cover stale progress output * fix(playback): neutralize protected diagnostic copy * fix(playback): harden runtime guidance ownership * fix(playback): stabilize recovery application ownership * fix(ci): classify playback util coverage * fix(e2e): preserve playback fixture bytes |
||
|
|
0c59aace71 |
fix(playback): structure MPEG-TS diagnostics (#1327)
* docs(playback): design structured mpegts diagnostics * docs(playback): plan structured mpegts diagnostics * fix(playback): structure mpegts error evidence * fix(playback): structure HTML5 mpegts errors * fix(playback): share mpegts evidence across players * fix(playback): render structured mpegts evidence * docs(playback): document structured mpegts diagnostics * chore(playback): keep diagnostics lint clean * docs(playback): complete mpegts diagnostics plan |
||
|
|
9f4e11d6de |
fix(playback): structure Shaka diagnostics (#1318)
* docs(playback): design structured Shaka diagnostics * fix(playback): structure Shaka diagnostics * docs(playback): document Shaka evidence boundary * docs(playback): fix Shaka validation commands * fix(playback): preserve Shaka fallback evidence * fix(playback): preserve Shaka text error evidence |
||
|
|
9a50e7385b | fix(playback): structure Video.js diagnostics (#1317) | ||
|
|
99d167993d |
fix(playback): structure HLS diagnostics (#1316)
* docs(playback): design structured HLS diagnostics * docs(playback): plan structured HLS diagnostics * fix(playback): structure HLS diagnostics * docs(playback): document structured HLS evidence * fix(playback): keep HLS startup logs private |
||
|
|
bf13849d69 |
fix(playback): avoid false codec diagnostics (#1314)
* docs(playback): design accurate native diagnostics * docs(playback): plan accurate native diagnostics * fix(playback): classify native source errors from evidence * fix(playback): preserve Video.js HTTP error context * fix(playback): show explicit HTTP playback errors * docs(playback): document native error evidence |
||
|
|
4ca2b6852e |
feat(playback): Up Next episode rail for the inline series player (#1231)
* feat(playback): Up Next episode rail for the inline series player On wide windows the inline series player now docks left and fills the leftover stage column with a Netflix-style "Up Next" rail: the rest of the current season plus next-season spillover, the playing episode highlighted, and watch-progress bars from playback positions. Clicking an episode plays it inline through the host's existing episode flow (Xtream serial-details and Stalker series view). - New app-up-next-rail component + buildUpNextRailItems() util in ui/playback; entries carry the host's raw episode object so selection needs no id lookup. - PortalInlinePlayerComponent measures the theater stage with a ResizeObserver and docks the rail only when the leftover beside the 16:9 player is >= 320px; narrower stages keep the centered theater/ambient behavior from #1223. Movies and live never show the rail. - New playerUpNextRail setting (Settings > Playback, default on, built-in web players only), mirroring playerAmbientMode; enforced at runtime for non-web engines. - i18n: SETTINGS.PLAYER_UP_NEXT_RAIL(+_DESCRIPTION) and PORTALS.UP_NEXT in all 18 locales. - The rail renders as an opaque panel on top of the stage, so the ambient fill stays behind it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(playback): address Greptile review on the Up Next rail - Stage overflow: `.player-shell__viewport` had no border-box sizing (the repo has no global reset), so the docked-rail modifier's 12px padding widened the stage past its container and the right edge was clipped. - Width gate: compute the width the rail actually receives (stage minus the docked layout's padding, the height-driven 16:9 player, and the flex gap) instead of raw stage slack, and observe the stage's border box so the modifier's own padding cannot feed back into the measurement. - Stalker lazy seasons: Ministra VOD-series seasons hold no episodes until opened, so the rail's next-season spillover stopped at the current season. Prefetch the following season while an episode plays inline. Adds regression coverage for the gate boundary, gate stability across the padding toggle, and the lazy-season prefetch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(stalker): stop the rail spillover prefetch from retrying forever A failed or genuinely empty Ministra season resets isLoading while leaving episodes empty, so the prefetch effect re-requested the same season on every emission for as long as inline playback continued. Remember which seasons this view already requested and ask at most once each. Regression test asserts the empty-response case fetches exactly once and does not retrigger on further playback in the same season. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(stalker): let a failed spillover prefetch recover on the next episode The previous guard was permanent, so a transient network or authorization failure disabled the rail's next-season prefetch for the component's lifetime. Distinguish the two outcomes instead: - Answered (even with zero episodes) — a real answer, never asked again. - Failed — the claim is released, but pinned to the episode that triggered it, so the retry waits for the next playback change. Retrying immediately would loop, since the failure itself flips isLoading and re-runs the effect. The claim is taken synchronously; awaiting first let the isLoading flip re-run the effect and fire a duplicate request before the answer arrived. `loadEpisodesForSeason` now reports whether the portal answered; existing callers ignore the result and are unaffected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
db70b07093 |
feat(playback): theater stage and opt-in ambient fill for the inline portal player (#1223)
* fix(tmdb): purge obsolete search cache rows * feat(playback): theater stage and opt-in ambient fill for the inline portal player On wide-short windows the VOD/series inline player left a strip of app surface next to the video: with `width: auto`, the viewport's `max-height` transferred through `aspect-ratio` into a max-width (CSS transferred size constraints), re-clamping the stage to 16:9 and leaving the leftover outside it. - Theater stage: give `.player-shell__viewport` a definite `width: 100%` so it always fills the content row; the player renders as the largest 16:9 box that fits the stage height, centered — the leftover is always the stage's black background, never app surface (YouTube-style letterbox). Applies to every inline engine. - Ambient fill: new `playerAmbientMode` setting (default off, Settings > Playback, web players only) renders a blurred, dimmed copy of the poster behind the player, filling the letterbox margins. Enforced at runtime too: Embedded MPV never gets the extra DOM layer. Live channels and non-http(s) poster URLs are excluded. Verified live via CDP at 1720x760 (stage 1362x532, player 946x532 with symmetric 208px margins) and 1280x950 (stage exactly 16:9, no bars). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(i18n): add ambient-mode setting keys to all remaining locales The i18n drift gate requires SETTINGS.PLAYER_AMBIENT_MODE and its description in every locale; the feature commit only covered en and ru. Translated via the i18n-fill workflow (per-locale patch + mechanical merge, glossary-matched against each existing file). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(settings): include playerAmbientMode in expected default settings settings.component.spec asserts the persisted settings object with toEqual; the new default-off field has to be part of the fixture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
b1fc23abbb |
fix(playback): route MKV sources as Matroska (#1210)
* docs(playback): design native MKV source routing * fix(playback): route MKV sources as Matroska * chore(playback): address MKV review feedback |
||
|
|
643dee1be3 |
feat(xtream): resume the latest series episode (#1187)
Dashboard Continue Watching now carries the exact saved season/episode into Xtream series details and starts it at the persisted offset. Successful external MPV/VLC launches persist the launched episode and retarget the series CTA to "Play episode N". Recent-history rows keyed by an episode id resolve their parent series before navigation. Includes maintainer follow-ups: no zero-offset resume on failed position loads, seriesXtreamId-gated resume targets for legacy rows, and patch coverage raised from 76.7% to 93.9%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ce24e16e32 |
feat(portals): two-state VOD detail pages (browse ↔ watch) with season tabs (#1127)
* feat(ui): slim ContentHero to non-scrolling hero block The page scroll container moves out of the hero into the upcoming PortalDetailShellComponent; the hero no longer hosts a default content slot for player/seasons/extras. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): add PortalDetailShellComponent with browse/watch states Two-state detail layout shell: owns page scroll, hero collapse animation (~300ms, reduced-motion aware), Escape-to-close-player handling, and an About block that re-stamps host-provided *detailTags/*detailMeta templates in watch state. The [detail-player] slot is never wrapped in a shell conditional so the host-owned player subtree survives state changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(playback): restyle inline player header as now-playing bar Adds a back button (emits the existing closed output — in watch state back returns to browse, not route navigation) and replaces the hardcoded header strings with i18n keys (PORTALS.NOW_PLAYING, CLOSE_PLAYER, BACK_TO_BROWSE). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(xtream): migrate VOD and serial detail templates to detail shell Hero chips/meta/actions become *detailTags/*detailMeta/*detailActions templates; the inline player moves to the full-width [detail-player] slot; trailer and similar rail move to [detail-extras]. Escape and the now-playing back button close the inline player back to browse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(stalker): migrate shared VodDetails and series view to detail shell The shared VodDetailsComponent (Stalker VOD, collections, search) and StalkerSeriesViewComponent move their hero content into *detailTags/*detailMeta/*detailActions templates and host the inline player in the full-width [detail-player] slot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): season tabs replace two-level seasons navigation SeasonTabsComponent renders a pill row (dropdown beyond 6 seasons), an optional season description and a back-to-playing chip. The season container auto-selects a season (inline-playing episode's season → most recent in-progress → first), emits seasonSelected for auto-selections so lazy-load/enrichment hooks keep firing, highlights the inline-playing episode, and loses the seasons grid + 'Back to seasons' button. Download/progress helpers move to pure utils; season strings are now translatable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(portals): season descriptions under the season tabs Xtream reads season overviews from get_series_info; Stalker exposes the TMDB season overview through TmdbEnrichmentService.getSeason (same cache rows as the episode enrichment) and keys it per tmdbId so views reused across detail navigations cannot leak descriptions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: document two-state detail layout and season tabs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): app-prefix detail template directive selectors, build fixes detailTags/detailMeta/detailActions → appDetailTags/appDetailMeta/ appDetailActions per @angular-eslint/directive-selector; type the Escape host listener as Event; drop a redundant ?? in season tabs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(i18n): translate new detail-page keys across all 17 locales 13 PORTALS.* keys (now-playing bar, About block, season tabs, episode empty states) translated via the i18n-fill workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(playback): keep now-playing bar actions inside the viewport The nowrap title made the header grid track grow to min-content and pushed Copy/Close out of the page; min-width: 0 on the header/title row lets the title truncate instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): expandable season description and episode info dialog Season descriptions get the same show more/less toggle as the hero (new shared ExpandableTextComponent); episode cards and list rows get an info button that opens a dialog with the full plot, duration, air date and a Play action — card heights stay fixed and click-to-play semantics stay intact. Also: drop the per-frame backdrop blur on the player card and make the watch-enter scroll instant so the hero morph stops competing for frame budget. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(portals): re-run TMDB season enrichment when the show match arrives With season tabs the first seasonSelected fires as soon as seasons load — usually before the async show-level TMDB enrichment has written tmdb_id — so the season fetch silently no-oped and was never retried, leaving episode stills/plots and the season description empty. Both detail hosts now key the fetch on (tmdb_id, selected season) in an effect, so it runs whichever arrives last. Also adds breathing room above the About divider in watch state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): move episode info button into the card body, compact overlay actions Three overlay buttons crowded the thumbnail on hover and the new info button had no styling at all. Info belongs with the content text: it now sits as a quiet ghost button at the end of the title row (revealed on card hover, MDC touch target clamped so it cannot swallow the play-on-click card area). Download + watched-toggle stay on the artwork, shrunk to 28px with a subtle edge border. List view already used the shared action style and is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): single watched indicator-toggle on episode cards Watched state was signalled three times on one card: the green badge, the hover toggle in the top-right cluster, and a checkmark appended to the title. Now one control does both jobs — a toggle at the top-left of the artwork that looks like the old green badge when watched (always visible) and appears as a ghost circle on hover when not. The right cluster keeps only the download action; the title checkmark is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(playback): back arrow in watch mode navigates straight to the list Leaving the theater took two clicks: back to browse, then back to the list. Watch state already shows everything browse offers (episodes, About, extras), so the bar's back arrow is now route-level back (new backClicked output wired to each host's goBack), while Close player and Escape keep exiting to browse without navigating. Replaces PORTALS.BACK_TO_BROWSE with a generic top-level BACK key (all locales). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): compact episode stills in the list view When a season has genuinely distinct per-episode art (TMDB stills or provider images), list rows show a 96x54 thumbnail with the episode number riding on it instead of the number square. When every episode repeats the same image (providers often send the series poster), the plain number square stays — a column of identical posters is worse than none. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(ui): compact list-thumbnail spec under the max-lines limit Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): address Greptile review — real fallback asset, untracked overflow measure default-episode.png never existed (pre-existing in the grid card, copied into the list thumbs) — both onerror handlers now fall back to the real default-poster.png. The expandable-text and content-hero measuring effects read the expanded signal via untracked so toggling show more/less no longer rebuilds their ResizeObservers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4094a36fef | Harden Linux embedded MPV packaging (#1043) | ||
|
|
8d672f8b81 | feat(playback): add web player series navigation (#1049) | ||
|
|
06700b318a |
feat(electron): add embedded mpv support for windows and linux (#1031)
Adds experimental embedded MPV support for Windows and Linux, expands packaging/runtime validation, and updates CI coverage for the cross-platform native addon. |
||
|
|
edaa981061 |
refactor(electron): split external player sessions
Split the Electron external-player IPC monolith into focused launch-context, playback-request, runtime, MPV session, and VLC session modules. Includes Greptile follow-up fixes for Homebrew Cask VLC path resolution and VLC spawn-error promise handling, with regression coverage. |
||
|
|
118d4fce32 |
fix(playback): improve inline stream diagnostics (#952)
* fix(playback): improve inline stream diagnostics * fix(playback): preserve headers in diagnostics wrapper * fix(portal): align unified live player imports |
||
|
|
8f79b25f52 | fix(playback): treat portal mpeg-ts vod as non-live | ||
|
|
4a4334cb64 |
feat(playback): improve browser-player diagnostics (#939)
* feat(playback): diagnose browser stream access errors Entire-Checkpoint: f957cd9849e0 * feat(playback): expose browser-player diagnostic details Entire-Checkpoint: f957cd9849e0 * fix(playback): keep media path extensions authoritative Entire-Checkpoint: f957cd9849e0 * fix(playback): tighten browser access diagnostics Entire-Checkpoint: f957cd9849e0 * fix(playback): align diagnostic followups Entire-Checkpoint: f957cd9849e0 * fix(playback): narrow declared stream metadata Entire-Checkpoint: f957cd9849e0 * fix(playback): preserve explicit extension overrides Entire-Checkpoint: f957cd9849e0 * fix(playback): keep mpeg-ts out of unsupported containers Entire-Checkpoint: f957cd9849e0 * refactor(playback): split diagnostics utilities Entire-Checkpoint: f957cd9849e0 * fix(playback): treat mpegts early eof as media fallback Entire-Checkpoint: f957cd9849e0 |
||
|
|
a40d5447e7 |
feat(settings): add external player arguments (#932)
* feat(settings): add external player arguments * fix(settings): address external player argument review * fix(settings): require external player argument settings * feat(settings): add external player argument placeholders Closes https://github.com/4gray/iptvnator/issues/835 |
||
|
|
9ae984ad7f |
feat(portal): add series quick start CTA (#925)
* feat(portal): add series quick start CTA Entire-Checkpoint: f957cd9849e0 * fix(portal): address series quick start review Entire-Checkpoint: f957cd9849e0 * docs: use repo-relative quick start path Entire-Checkpoint: f957cd9849e0 |
||
|
|
e7db920543 |
fix(settings): clarify external player path hints (#929)
* fix(settings): clarify external player path hints * fix(settings): gate external player hints to desktop |
||
|
|
6ae1251ad1 |
fix(playback): use inline player for collection VOD (#928)
* feat(portal): add series quick start CTA Entire-Checkpoint: f957cd9849e0 * fix(playback): use inline player for collection vod * fix(playback): address inline collection review feedback * fix(playback): ignore stale vod position loads |
||
|
|
6305dcf00e |
Add browser playback diagnostics and MPV/VLC fallback (#921)
* feat(playback): add codec diagnostics fallback Entire-Checkpoint: f957cd9849e0 * fix(playback): address diagnostics review feedback Entire-Checkpoint: f957cd9849e0 * fix(playback): ignore recoverable hls diagnostics Entire-Checkpoint: f957cd9849e0 * fix(playback): polish diagnostic fallback banner Entire-Checkpoint: f957cd9849e0 * fix(xtream): preserve live category search scope Entire-Checkpoint: f957cd9849e0 |
||
|
|
e192cba776 |
feat(player): add VLC reuse-instance setting (#893)
VLC was unconditionally spawned per click — VLC's own single-instance preference fails because the per-launch RC args defeat its D-Bus forwarder. Mirror the existing MPV reuse pattern so users can opt in to driving one tracked VLC via its RC interface (clear + add) instead of opening a new window every stream. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: f957cd9849e0 |