mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 13:16:16 -08:00
docs(downloads): document offline details flow
This commit is contained in:
1 parent
ce0f563735
commit
1fe2692f77
24 files changed
+333
-35
No files matched your search
@@ -3,8 +3,7 @@ type: feature
|
||||
area: downloads
|
||||
---
|
||||
|
||||
Downloads now separate active transfers from a cover-size-aware offline
|
||||
library and group completed episodes by series. Missing files move to Needs
|
||||
attention with a safe Download again action, while ready cards open provider
|
||||
details and keep local Play explicit. Card source details now live in the
|
||||
overflow menu.
|
||||
Downloads now open movies and series in focused offline details with saved
|
||||
metadata and optional TMDB enrichment. Series show only episodes available
|
||||
locally, while View in portal opens the complete online catalog and provider
|
||||
playback.
|
||||
@@ -491,7 +491,11 @@ See `docs/architecture/m3u-playlist-module.md` for complete documentation.
|
||||
- Stalker portal: `/workspace/stalker/:id` (children: `itv`, `vod`, `radio`, `series`, `favorites`, `recent`, `search`, `actor/:personId`, `downloads`) — `libs/portal/stalker/feature/src/lib/stalker-feature.routes.ts`
|
||||
- Global collections: `/workspace/global-favorites`, `/workspace/global-recent`
|
||||
- Global search: `/workspace/search` (Electron-only; a guard redirects the PWA to `/workspace/sources`)
|
||||
- Downloads: `/workspace/downloads`
|
||||
- Downloads: `/workspace/downloads` with focused
|
||||
`/workspace/downloads/:downloadId`; source-scoped equivalents are
|
||||
`/workspace/xtreams/:id/downloads/:downloadId` and
|
||||
`/workspace/stalker/:id/downloads/:downloadId`. Focused download details hide
|
||||
the workspace context panel.
|
||||
- Settings: `/workspace/settings` (`/settings` redirects there)
|
||||
|
||||
**Service Architecture** (Factory Pattern):
|
||||
@@ -897,6 +901,32 @@ engine` (restart required) or
|
||||
chrome, with browser-dependent subtitles. AirPlay, Cast, Document PiP, a PiP
|
||||
keyboard shortcut, and Embedded MPV popup/native support are out of scope.
|
||||
|
||||
**Download Manager**:
|
||||
|
||||
- The desktop-only manager shares one global download store across the global,
|
||||
Xtream-scoped, and Stalker-scoped routes. Completed movie and grouped-series
|
||||
cards use the global Small/Medium/Large cover-grid tokens; missing completed
|
||||
files move to Needs attention instead of remaining in Ready to watch.
|
||||
- Ready movie and grouped-series cards open a focused local detail. Movies play
|
||||
the finalized local file; series list only locally available episode rows and
|
||||
every episode action targets its own downloaded file. Focused routes disable
|
||||
route search and use `contextPanel: 'none'`.
|
||||
- Downloads capture a versioned metadata snapshot from the rendered Xtream or
|
||||
Stalker movie/episode detail at start time, including already-merged TMDB
|
||||
fields. Legacy, sparse, stale, or wrong-language snapshots are safely
|
||||
backfilled from row/provider metadata and optional TMDB enrichment when the
|
||||
focused detail opens.
|
||||
- `View in portal` resolves an exact source target and opens the provider's
|
||||
normal detail in one-shot `provider-only` presentation. Provider playback and
|
||||
the complete catalog remain available, while Offline/local/download actions
|
||||
are hidden. This applies to Xtream movies/series and regular, embedded
|
||||
`series[]`, and Ministra `is_series=1` Stalker flows.
|
||||
- If a finalized file disappears while a focused detail is open, the
|
||||
authoritative download list refreshes and returns to the manager. A failed
|
||||
redirect leaves an actionable missing-file state with Back and Retry.
|
||||
- Canonical contract: `docs/architecture/download-manager.md`; provider handoff:
|
||||
`docs/architecture/portal-detail-navigation.md`.
|
||||
|
||||
**VOD/Series Detail Pages (two-state layout)**:
|
||||
|
||||
- Xtream and Stalker detail pages use the shared `PortalDetailShellComponent` (`libs/ui/components/src/lib/portal-detail-shell/`) with two states: **Browse** (hero with poster/metadata/actions, episodes below) and **Watch** (hero collapses with a ~300ms morph, the inline player takes the full content width, metadata moves to an About block below the episodes)
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Сезон {{season}}",
|
||||
"SEASON_RANGE": "Сезоны {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Скачанные эпизоды",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Доступно офлайн",
|
||||
"PLAY": "Воспроизвести офлайн",
|
||||
"VIEW_IN_PORTAL": "Открыть в портале",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "Контент больше недоступен в исходном портале",
|
||||
"DOWNLOADED_EPISODES": "Скачано эпизодов: {{count}}",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Загрузка не найдена",
|
||||
"NOT_FOUND_BODY": "Эта загрузка больше недоступна.",
|
||||
"BACK": "Назад к загрузкам"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Открыть {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Обложка для {{title}} недоступна",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -1128,6 +1128,17 @@
|
||||
"SEASON": "Season {{season}}",
|
||||
"SEASON_RANGE": "Seasons {{first}}–{{last}}",
|
||||
"DOWNLOADED_EPISODES": "Downloaded episodes",
|
||||
"OFFLINE_DETAIL": {
|
||||
"AVAILABLE": "Available offline",
|
||||
"PLAY": "Play offline",
|
||||
"VIEW_IN_PORTAL": "View in portal",
|
||||
"VIEW_IN_PORTAL_UNAVAILABLE": "The source item is no longer available",
|
||||
"DOWNLOADED_EPISODES": "{{count}} downloaded episodes",
|
||||
"FILE_SIZE": "{{size}}",
|
||||
"NOT_FOUND_TITLE": "Download not found",
|
||||
"NOT_FOUND_BODY": "This download is no longer available.",
|
||||
"BACK": "Back to Downloads"
|
||||
},
|
||||
"ARIA": {
|
||||
"OPEN": "Open {{title}}",
|
||||
"POSTER_UNAVAILABLE": "Artwork unavailable for {{title}}",
|
||||
|
||||
@@ -97,36 +97,45 @@ variants, contextual buttons, and theme-aware styling.
|
||||
playlist scope when the page is opened under a source route. VOD and episode
|
||||
detail views continue to render a paused download as an active Resume button
|
||||
(`DownloadsService.isPaused()` / `resumeDownloadByContent()`). Artwork and
|
||||
titles on completed movie and grouped-series cards open the provider detail
|
||||
page; the explicit Play action starts the local file. A legacy standalone
|
||||
episode without a usable series id stays directly playable because there is
|
||||
no reliable detail route to open. Missing completed rows show `File missing`
|
||||
with `Download again`; Play and Show in folder are withheld. A file-action
|
||||
race that returns `File not found` refreshes the authoritative list before
|
||||
pending state clears.
|
||||
titles on completed movie and grouped-series cards open the focused offline
|
||||
detail for that download; the explicit card Play action still starts the
|
||||
local file. A legacy standalone episode without a usable series id stays
|
||||
directly playable because there is no reliable series detail to build.
|
||||
Missing completed rows show `File missing` under Needs attention with
|
||||
`Download again`; Play and Show in folder are withheld. A file-action race
|
||||
that returns `File not found` refreshes the authoritative list and returns
|
||||
the user from focused details to the manager.
|
||||
|
||||
## Offline detail playback
|
||||
## Focused offline details
|
||||
|
||||
- A completed VOD with an available finalized file renders an Offline tag on
|
||||
both rich and fallback detail shells. Its primary action plays the downloaded
|
||||
file, while a neutral
|
||||
“Play from this source” action, when a usable provider source is available,
|
||||
preserves the existing provider, pinned-source, resume, and restart path.
|
||||
- A completed row whose file is missing does not advertise Offline or local
|
||||
playback on details; the provider playback path remains primary.
|
||||
- Managed MPV/VLC state remains authoritative: Opening disables conflicting
|
||||
playback actions, and Stop closes the matched external session before any
|
||||
local or provider choice can run.
|
||||
- Download rows carry a versioned, display-only metadata snapshot, so the
|
||||
offline detail resolver can work without the owning Xtream or Stalker
|
||||
provider. Sparse, stale, or wrong-language snapshots are refreshed from the
|
||||
typed provider catalog and optional TMDB enrichment. A successful refresh
|
||||
advances its freshness timestamp; transient TMDB failures may retain improved
|
||||
provider fields without advancing the timestamp or language, so a later visit
|
||||
retries. Legitimately sparse successful results are remembered in a bounded,
|
||||
language-scoped runtime throttle for the same TTL, preventing persistence
|
||||
reloads from starting an enrichment loop. Concurrent movie and grouped-series
|
||||
refreshes are request-ordered so only the newest generation may persist.
|
||||
- Ready movies and grouped series open a local-focused detail view rather than
|
||||
a provider catalog page. A movie exposes local Play and Show in folder. A
|
||||
series projects only completed episode rows whose finalized files are still
|
||||
available, grouped into seasons; every episode Play and Show in folder action
|
||||
targets that row's local file. The provider's other seasons and episodes are
|
||||
deliberately absent from this view.
|
||||
- `View in portal` resolves the exact Xtream or Stalker source item before the
|
||||
action becomes available. It then leaves the offline view and opens the
|
||||
provider's normal detail host in explicit `provider-only` presentation: the
|
||||
complete online catalog and provider playback remain available, while local,
|
||||
Offline, and download actions are hidden. Regular provider navigation does
|
||||
not inherit this one-shot presentation state.
|
||||
- A completed row that is no longer locally available is never rendered as a
|
||||
ready offline detail. Direct or stale detail URLs return to the manager; if
|
||||
navigation fails, the detail shell shows the missing-file error with Back and
|
||||
Retry. Invalid or removed download ids render a focused not-found state.
|
||||
- Movie and episode downloads capture a versioned, display-only metadata
|
||||
snapshot at start time from the already-rendered Xtream or Stalker detail,
|
||||
including any TMDB fields already present. The snapshot keeps provider
|
||||
identity/category separately from presentation metadata and lets the offline
|
||||
view render even when the source portal is unavailable.
|
||||
- Legacy rows and sparse, stale, or wrong-language snapshots are backfilled
|
||||
when focused details open: row metadata supplies a safe local fallback,
|
||||
provider data is merged when it can be resolved, and opt-in TMDB enrichment
|
||||
uses the same app language and merge rules as provider details. Successful
|
||||
refreshes persist to the representative row; transient failures keep safe
|
||||
improvements without falsely marking the snapshot fresh, and concurrent
|
||||
refreshes are request-ordered so only the latest generation may write.
|
||||
- Snapshot artwork accepts only safe HTTP(S) image URLs. Renderer normalization
|
||||
and main-process persistence independently reject credential-shaped path and
|
||||
query keys, including `username`, so provider credentials cannot be cached in
|
||||
@@ -144,7 +153,12 @@ variants, contextual buttons, and theme-aware styling.
|
||||
`/workspace/xtreams/:id/downloads` and
|
||||
`/workspace/stalker/:id/downloads`. All three routes render the same global
|
||||
store; the `:id` routes derive a view-only playlist scope.
|
||||
- Each scope exposes an offline detail child at `downloads/:downloadId`.
|
||||
- Each scope exposes the same focused child route:
|
||||
`/workspace/downloads/:downloadId`,
|
||||
`/workspace/xtreams/:id/downloads/:downloadId`, and
|
||||
`/workspace/stalker/:id/downloads/:downloadId`. The workspace shell treats
|
||||
all three as focused content: route search is disabled and the context panel
|
||||
is `none`, so provider categories are not shown beside a local-only item.
|
||||
- Downloads navigation is data-driven: `libs/portal/shared/util/src/lib/navigation/portal-rail-links.ts` emits a `downloads` section link (`path: [...root, 'downloads']`) for both portals, so they reuse the same download page.
|
||||
|
||||
## Queuing, persistence, and UX notes
|
||||
|
||||
@@ -29,6 +29,15 @@ Related:
|
||||
opening the series from the collection grid itself remains detail-only. If the
|
||||
positions load fails, the target stays unconsumed and the handoff degrades to
|
||||
detail-only rather than starting the episode at offset zero.
|
||||
- Ready Download Manager cards open one of the three focused
|
||||
`downloads/:downloadId` routes. These local details hide the workspace
|
||||
context panel, play only finalized local files, and show only locally
|
||||
available episodes for a series.
|
||||
- `View in portal` is the explicit bridge from a focused offline detail to the
|
||||
source catalog. It resolves an exact provider target and passes the one-shot
|
||||
`detailPresentation: 'provider-only'` navigation state. The destination keeps
|
||||
provider playback and the complete provider episode catalog, but hides
|
||||
Offline/local/download presentation.
|
||||
- Do not force both portals into the same browse/detail behavior unless the full
|
||||
portal detail architecture is being changed.
|
||||
|
||||
@@ -95,6 +104,19 @@ Search behavior to preserve:
|
||||
- Selecting an Xtream item from search should still navigate to the canonical
|
||||
Xtream content type/category/item route when the item is not a live stream.
|
||||
|
||||
Download handoff behavior:
|
||||
|
||||
- An Xtream movie handoff requires its exact VOD category and item route; a
|
||||
series handoff requires its exact series category and item route. The
|
||||
download snapshot's provider category is preferred, with the typed catalog
|
||||
used to recover it for legacy rows. The action stays unavailable rather than
|
||||
opening a bare collection route when the target cannot be resolved.
|
||||
- Provider-only mode is read by the normal VOD/series detail components. It
|
||||
preserves provider Play/Resume and every provider episode, while suppressing
|
||||
the local Offline state and download controls. Reusing the route component
|
||||
for another item must clear the mode unless that navigation explicitly
|
||||
carries the marker.
|
||||
|
||||
## Stalker
|
||||
|
||||
Stalker details are represented by store state and inline detail rendering on the current screen.
|
||||
@@ -135,6 +157,18 @@ Behavior to preserve:
|
||||
- Back from the collection-owned detail should restore the previous collection
|
||||
tab and scope instead of resetting the collection screen to its defaults.
|
||||
|
||||
Download handoff behavior:
|
||||
|
||||
- `View in portal` preserves Stalker's inline/store-state architecture. The
|
||||
download target carries `openStalkerItem` into the normal VOD or series
|
||||
category host instead of inventing a canonical item route.
|
||||
- The provider-only marker follows the exact selected item through regular
|
||||
series, embedded VOD `series[]`, and lazy Ministra VOD `is_series=1` flows.
|
||||
All provider seasons/episodes and provider playback remain available, while
|
||||
the shared VOD or series UI suppresses local Offline and download controls.
|
||||
Consuming the marker is identity-scoped so a later ordinary item open returns
|
||||
to normal provider presentation.
|
||||
|
||||
## Decision Rule For Future Changes
|
||||
|
||||
When deciding how a favorites/recent/search click should behave:
|
||||
|
||||
@@ -41,6 +41,8 @@ Primary route tree lives in
|
||||
- `/stalker/:id/search`
|
||||
- `/stalker/:id/actor/:personId`
|
||||
- `/stalker/:id/downloads` (shared `DownloadsComponent` from `@iptvnator/portal/downloads/feature`)
|
||||
- `/stalker/:id/downloads/:downloadId` (focused local movie/series detail with
|
||||
no category context panel)
|
||||
|
||||
## Runtime Architecture
|
||||
|
||||
@@ -361,6 +363,16 @@ The VOD-series contract is cross-surface:
|
||||
- The dashboard resolves episode progress by the parent `seriesXtreamId` and
|
||||
renders the saved season/episode metadata. It does not infer episode numbers
|
||||
from provider payloads.
|
||||
- Episode downloads from regular series, embedded VOD `series[]`, and lazy
|
||||
Ministra VOD `is_series=1` capture the rendered parent/episode metadata and
|
||||
provider category in a versioned offline snapshot. The focused Download
|
||||
Manager detail uses only locally available episode rows; it does not reuse
|
||||
the provider season resource as an offline availability list.
|
||||
- `View in portal` hands the stored Stalker identity back to the normal
|
||||
category/inline detail host with identity-scoped provider-only presentation.
|
||||
All three series modes retain their complete provider seasons, episodes, and
|
||||
provider playback, while Offline/local/download controls stay hidden for
|
||||
that handoff. A normal Stalker item open remains unchanged.
|
||||
|
||||
Core decision logic and normalization are centralized in:
|
||||
|
||||
|
||||
Reference in new issue
Block a user