Files
iptvnator/libs/shared/interfaces/src/lib/settings.interface.ts
T
4grayandClaude Opus 5 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>
2026-07-25 13:16:09 +02:00

169 lines
5.7 KiB
TypeScript

import { Language } from './language.enum';
import { StreamFormat } from './stream-format.enum';
import { Theme } from './theme.enum';
import { TmdbSettings } from './tmdb.interface';
/**
* Contains all types of supported video players
*/
export enum VideoPlayer {
VideoJs = 'videojs',
Html5Player = 'html5',
EmbeddedMpv = 'embedded-mpv',
MPV = 'mpv',
VLC = 'vlc',
ArtPlayer = 'artplayer',
}
export enum StartupBehavior {
FirstView = 'first-view',
RestoreLastView = 'restore-last-view',
}
export type CoverSize = 'small' | 'medium' | 'large';
/** Rendering of the live EPG panel under the player. */
export type EpgViewMode = 'timeline' | 'list';
export interface DashboardRailsSettings {
hero: boolean;
continueWatching: boolean;
liveFavorites: boolean;
recentlyWatchedLive: boolean;
favoriteMoviesAndSeries: boolean;
recentSources: boolean;
xtreamRecentlyAdded: boolean;
/** TMDB "Trending this week" rail (needs the TMDB opt-in; Electron) */
tmdbTrending: boolean;
}
export const DEFAULT_DASHBOARD_RAILS_SETTINGS: DashboardRailsSettings = {
hero: true,
continueWatching: true,
liveFavorites: true,
recentlyWatchedLive: true,
favoriteMoviesAndSeries: true,
recentSources: true,
xtreamRecentlyAdded: true,
tmdbTrending: true,
};
export type DashboardRailsSettingsInput = Partial<
Record<keyof DashboardRailsSettings, boolean | null | undefined>
>;
export function normalizeDashboardRailsSettings(
settings?: DashboardRailsSettingsInput | null
): DashboardRailsSettings {
const normalized = { ...DEFAULT_DASHBOARD_RAILS_SETTINGS };
if (!settings) {
return normalized;
}
const keys = Object.keys(
DEFAULT_DASHBOARD_RAILS_SETTINGS
) as (keyof DashboardRailsSettings)[];
for (const key of keys) {
if (typeof settings[key] === 'boolean') {
normalized[key] = settings[key];
}
}
return normalized;
}
/**
* Describes all available settings options of the application
*/
export interface Settings {
player: VideoPlayer;
/**
* Use IPTVnator's shared controls in HTML5, Video.js, and ArtPlayer.
* Missing values remain off for compatibility with older saved settings.
*/
webPlayerSharedControls?: boolean;
/**
* Fill the empty space around the inline VOD/series player with a blurred,
* dimmed copy of the poster (YouTube "Ambient mode" style) instead of plain
* black bars. Off by default; only affects the built-in web players.
*/
playerAmbientMode?: boolean;
/**
* Dock the inline series player to the left and show an "Up Next"
* episode rail in the leftover stage column on wide windows. On by
* default (the rail only appears when there is genuinely unused space);
* missing values mean enabled. Only affects the built-in web players.
*/
playerUpNextRail?: boolean;
epgUrl: string[];
streamFormat: StreamFormat;
openStreamOnDoubleClick: boolean;
language: Language;
showCaptions: boolean;
showDashboard: boolean;
startupBehavior: StartupBehavior;
/** Show the desktop footer bar for external playback status */
showExternalPlaybackBar?: boolean;
/** Strip country/group prefixes like "US | " or "UK - " from channel names */
stripCountryPrefix?: boolean;
theme: Theme;
mpvPlayerPath: string;
/**
* Extra MPV CLI arguments entered one argument per line. Applied only when
* starting a new external MPV process.
*/
mpvPlayerArguments: string;
mpvReuseInstance: boolean;
vlcPlayerPath: string;
/**
* Extra VLC CLI arguments entered one argument per line. Applied only when
* starting a new external VLC process.
*/
vlcPlayerArguments: string;
vlcReuseInstance: boolean;
remoteControl: boolean;
remoteControlPort: number;
/** Custom download folder path (uses system Downloads folder if not set) */
downloadFolder?: string;
/** Custom live recording folder path (uses system Downloads folder if not set) */
recordingFolder?: string;
/**
* Embedded MPV frame-copy engine (experimental, macOS Apple Silicon and Linux).
* Applied on the next app start — the engine relaxes the window sandbox
* for its preload frame pump, which is fixed at window creation.
*/
embeddedMpvFrameCopy?: boolean;
/** Cover/poster sizing preset applied across grids and rails */
coverSize?: CoverSize;
/** Live EPG panel layout: horizontal timeline (default) or vertical list */
epgViewMode?: EpgViewMode;
/** Per-rail dashboard visibility preferences. Missing keys default on. */
dashboardRails?: DashboardRailsSettings;
/**
* When true, the locally-parsed XMLTV programs (loaded from `epgUrl`)
* take precedence over the Xtream provider's EPG for live TV channels.
* When false (default), the Xtream provider's EPG is preferred and
* XMLTV is consulted only when the provider returns no programs.
* Only meaningful for Xtream playlists in Electron.
*/
preferUploadedEpgOverXtream?: boolean;
/**
* Exact EPG source URLs the user has allowed to resolve to private/LAN
* network addresses. Kept source-scoped instead of disabling SSRF
* protection globally.
*/
trustedPrivateNetworkEpgUrls?: string[];
/**
* Lowercase hostnames whose invalid TLS certificates the user has chosen
* to trust. This is host-scoped and does not disable TLS validation for
* unrelated playlist or EPG hosts.
*/
trustedInsecureTlsHosts?: string[];
/**
* Opt-in TMDB metadata enrichment for VOD/series detail views.
* Disabled by default because enrichment sends content titles to TMDB.
*/
tmdb?: TmdbSettings;
}