mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
Drop the startup deferral gate, its kill switch and the deferred loads: they lowered renderer.ipcCallsToFirstCard but moved neither spawnToFirstCardMs nor load->card beyond drift, and they grew renderer.initialBytes. Keep the shared in-flight inventory read, inline it in PlaylistsService with short property names, and copy the joiner's result with structuredClone (the Electron renderer has it; the services test setup polyfills it for jsdom). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1409 lines
48 KiB
TypeScript
1409 lines
48 KiB
TypeScript
import type { AppUpdateChannel } from './app-update-channel.util';
|
||
import type { SourceProbeContext, SourceHealthResult } from './source-health';
|
||
import type { XtreamConnectionFailure } from './xtream-connection-test';
|
||
import type { ZoomLevelAction } from './zoom-level.util';
|
||
import type {
|
||
CatchupDownloadMetadata,
|
||
DownloadRecoveryResult,
|
||
DownloadContentType,
|
||
} from './catchup-download.interface';
|
||
import {
|
||
EmbeddedMpvBounds,
|
||
EmbeddedMpvRecordingStartOptions,
|
||
EmbeddedMpvSession,
|
||
EmbeddedMpvSubtitleStyle,
|
||
EmbeddedMpvSupport,
|
||
} from './embedded-mpv-session.interface';
|
||
import { ContentMetadataPatch } from './content-metadata.interface';
|
||
import { DownloadMetadataSnapshot } from './download-metadata.interface';
|
||
import { EpgChannelMetadata } from './epg-channel-metadata.model';
|
||
import { EpgProgram } from './epg-program.model';
|
||
import { ExternalPlayerSession } from './external-player-session.interface';
|
||
import {
|
||
GlobalSearchPaginationOptions,
|
||
GlobalSearchResult,
|
||
GlobalSearchResultSource,
|
||
} from './global-search-result.interface';
|
||
import { M3uFavoriteChannel } from './m3u-favorite-channel.interface';
|
||
import {
|
||
RecordingProgramSnapshot,
|
||
RecordingSourceType,
|
||
RecordingStatus,
|
||
} from './recording-metadata.interface';
|
||
import { PlaybackPositionData } from './playback-position.interface';
|
||
import { AutoUpdatePlaylistsResult } from './playlist-auto-update.interface';
|
||
import {
|
||
XtreamBackupFavoriteItem,
|
||
XtreamBackupHiddenCategory,
|
||
XtreamBackupRecentlyViewedItem,
|
||
} from './playlist-backup.interface';
|
||
import {
|
||
PlaylistRefreshCancelledResult,
|
||
PlaylistRefreshEvent,
|
||
PlaylistRefreshPayload,
|
||
} from './playlist-refresh.interface';
|
||
import { Playlist } from './playlist.interface';
|
||
import {
|
||
PlayerContentInfo,
|
||
ResolvedPortalPlayback,
|
||
} from './portal-playback.interface';
|
||
import { PortalDebugEvent } from './portal-debug.interface';
|
||
import { CatalogTitleMatch } from './catalog-title-match.interface';
|
||
import {
|
||
StreamProbeHeaders,
|
||
VodSourceCandidateRow,
|
||
VodSourcePin,
|
||
} from './vod-source.interface';
|
||
import { Settings } from './settings.interface';
|
||
import {
|
||
TmdbCacheEntry,
|
||
TmdbCacheMediaType,
|
||
TmdbCacheStats,
|
||
} from './tmdb.interface';
|
||
import { XtreamCategory } from './xtream-category.interface';
|
||
|
||
export const ELECTRON_BRIDGE_CONTENT_TYPES = {
|
||
Episode: 'episode',
|
||
Live: 'live',
|
||
Movie: 'movie',
|
||
Series: 'series',
|
||
Vod: 'vod',
|
||
} as const;
|
||
|
||
export type ElectronBridgeContentType =
|
||
(typeof ELECTRON_BRIDGE_CONTENT_TYPES)[keyof typeof ELECTRON_BRIDGE_CONTENT_TYPES];
|
||
|
||
export type ElectronBridgePortalContentType =
|
||
| typeof ELECTRON_BRIDGE_CONTENT_TYPES.Live
|
||
| typeof ELECTRON_BRIDGE_CONTENT_TYPES.Movie
|
||
| typeof ELECTRON_BRIDGE_CONTENT_TYPES.Series;
|
||
|
||
export type ElectronBridgePlaybackContentType =
|
||
| typeof ELECTRON_BRIDGE_CONTENT_TYPES.Vod
|
||
| typeof ELECTRON_BRIDGE_CONTENT_TYPES.Episode;
|
||
|
||
export const ELECTRON_BRIDGE_PLAYLIST_TYPES = {
|
||
M3uFile: 'm3u-file',
|
||
M3uText: 'm3u-text',
|
||
M3uUrl: 'm3u-url',
|
||
Stalker: 'stalker',
|
||
Xtream: 'xtream',
|
||
} as const;
|
||
|
||
export type ElectronBridgePlaylistType =
|
||
(typeof ELECTRON_BRIDGE_PLAYLIST_TYPES)[keyof typeof ELECTRON_BRIDGE_PLAYLIST_TYPES];
|
||
|
||
export const ELECTRON_BRIDGE_EPG_PROGRESS_STATUSES = {
|
||
Cancelled: 'cancelled',
|
||
Complete: 'complete',
|
||
Error: 'error',
|
||
Loading: 'loading',
|
||
Queued: 'queued',
|
||
} as const;
|
||
|
||
export type ElectronBridgeEpgProgressStatus =
|
||
(typeof ELECTRON_BRIDGE_EPG_PROGRESS_STATUSES)[keyof typeof ELECTRON_BRIDGE_EPG_PROGRESS_STATUSES];
|
||
|
||
export const ELECTRON_BRIDGE_SECURITY_ERROR_CODES = {
|
||
EpgPrivateNetworkBlocked: 'epg-private-network-blocked',
|
||
InvalidTlsCertificate: 'invalid-tls-certificate',
|
||
} as const;
|
||
|
||
export type ElectronBridgeSecurityErrorCode =
|
||
(typeof ELECTRON_BRIDGE_SECURITY_ERROR_CODES)[keyof typeof ELECTRON_BRIDGE_SECURITY_ERROR_CODES];
|
||
|
||
export const ELECTRON_BRIDGE_DB_OPERATION_STATUSES = {
|
||
Cancelled: 'cancelled',
|
||
Completed: 'completed',
|
||
Error: 'error',
|
||
Progress: 'progress',
|
||
Started: 'started',
|
||
} as const;
|
||
|
||
export type ElectronBridgeDbOperationStatus =
|
||
(typeof ELECTRON_BRIDGE_DB_OPERATION_STATUSES)[keyof typeof ELECTRON_BRIDGE_DB_OPERATION_STATUSES];
|
||
|
||
export const ELECTRON_BRIDGE_REMOTE_CONTROL_COMMAND_TYPES = {
|
||
ChannelSelectNumber: 'channel-select-number',
|
||
VolumeDown: 'volume-down',
|
||
VolumeToggleMute: 'volume-toggle-mute',
|
||
VolumeUp: 'volume-up',
|
||
} as const;
|
||
|
||
export type ElectronBridgeRemoteControlCommandType =
|
||
(typeof ELECTRON_BRIDGE_REMOTE_CONTROL_COMMAND_TYPES)[keyof typeof ELECTRON_BRIDGE_REMOTE_CONTROL_COMMAND_TYPES];
|
||
|
||
export const ELECTRON_BRIDGE_REMOTE_PORTALS = {
|
||
M3u: 'm3u',
|
||
Stalker: 'stalker',
|
||
Unknown: 'unknown',
|
||
Xtream: 'xtream',
|
||
} as const;
|
||
|
||
export type ElectronBridgeRemotePortal =
|
||
(typeof ELECTRON_BRIDGE_REMOTE_PORTALS)[keyof typeof ELECTRON_BRIDGE_REMOTE_PORTALS];
|
||
|
||
export const ELECTRON_BRIDGE_DOWNLOAD_STATUSES = {
|
||
Canceled: 'canceled',
|
||
Completed: 'completed',
|
||
Downloading: 'downloading',
|
||
Failed: 'failed',
|
||
Paused: 'paused',
|
||
Queued: 'queued',
|
||
} as const;
|
||
|
||
export type ElectronBridgeDownloadStatus =
|
||
(typeof ELECTRON_BRIDGE_DOWNLOAD_STATUSES)[keyof typeof ELECTRON_BRIDGE_DOWNLOAD_STATUSES];
|
||
|
||
export const ELECTRON_BRIDGE_DOWNLOAD_START_REASONS = {
|
||
AlreadyDownloaded: 'already-downloaded',
|
||
AlreadyInProgress: 'already-in-progress',
|
||
} as const;
|
||
|
||
export type ElectronBridgeDownloadStartReason =
|
||
(typeof ELECTRON_BRIDGE_DOWNLOAD_START_REASONS)[keyof typeof ELECTRON_BRIDGE_DOWNLOAD_START_REASONS];
|
||
|
||
export const ELECTRON_BRIDGE_EPISODE_IDENTITY_SCOPES = {
|
||
StalkerEmbeddedVod: 'stalker-embedded-vod',
|
||
StalkerLazyVod: 'stalker-lazy-vod',
|
||
StalkerRegularSeries: 'stalker-regular-series',
|
||
} as const;
|
||
|
||
export type ElectronBridgeEpisodeIdentityScope =
|
||
(typeof ELECTRON_BRIDGE_EPISODE_IDENTITY_SCOPES)[keyof typeof ELECTRON_BRIDGE_EPISODE_IDENTITY_SCOPES];
|
||
|
||
export type ElectronDownloadFileAvailability =
|
||
'available' | 'missing' | 'not-applicable';
|
||
|
||
export const ELECTRON_BRIDGE_APP_UPDATE_STATUSES = {
|
||
Unsupported: 'unsupported',
|
||
Idle: 'idle',
|
||
Checking: 'checking',
|
||
Available: 'available',
|
||
NotAvailable: 'not-available',
|
||
Downloading: 'downloading',
|
||
Downloaded: 'downloaded',
|
||
Error: 'error',
|
||
} as const;
|
||
|
||
export type ElectronBridgeAppUpdateStatusValue =
|
||
(typeof ELECTRON_BRIDGE_APP_UPDATE_STATUSES)[keyof typeof ELECTRON_BRIDGE_APP_UPDATE_STATUSES];
|
||
|
||
export const ELECTRON_BRIDGE_GLOBAL_RECENTLY_ADDED_KINDS = {
|
||
All: 'all',
|
||
Series: 'series',
|
||
Vod: 'vod',
|
||
} as const;
|
||
|
||
export type ElectronBridgeGlobalRecentlyAddedKind =
|
||
(typeof ELECTRON_BRIDGE_GLOBAL_RECENTLY_ADDED_KINDS)[keyof typeof ELECTRON_BRIDGE_GLOBAL_RECENTLY_ADDED_KINDS];
|
||
|
||
export interface ElectronBridgeResult {
|
||
success: boolean;
|
||
}
|
||
|
||
export interface ElectronBridgeErrorResult extends ElectronBridgeResult {
|
||
error?: string;
|
||
}
|
||
|
||
export interface ElectronBridgeCountResult extends ElectronBridgeResult {
|
||
count: number;
|
||
}
|
||
|
||
export interface ElectronBridgeDialogFilter {
|
||
name: string;
|
||
extensions: string[];
|
||
}
|
||
|
||
export interface ElectronBridgeWindowState {
|
||
isMaximized: boolean;
|
||
isFullScreen: boolean;
|
||
}
|
||
|
||
/**
|
||
* Portal credentials for an auth-gated stream, passed alongside the scoped
|
||
* header override. The main process attaches them only to requests going to
|
||
* the exact origin of the override's `scopeUrl`, keeps them in memory only,
|
||
* and drops them when the scoped override is cleared or replaced.
|
||
*/
|
||
export interface ElectronBridgeStreamCredentials {
|
||
authorization?: string | null;
|
||
cookie?: string | null;
|
||
}
|
||
|
||
/**
|
||
* A playlist file the operating system asked the app to open — a command line
|
||
* argument, a file association double-click, or macOS' `open-file` event. The
|
||
* main process resolves the path to an absolute one before handing it over.
|
||
*/
|
||
export interface ElectronBridgePlaylistOpenRequest {
|
||
fileName: string;
|
||
filePath: string;
|
||
/** Pass back to `acknowledgePlaylistOpenRequest` once received. */
|
||
requestId: string;
|
||
}
|
||
|
||
export interface ElectronBridgeAppUpdateRelease {
|
||
version: string;
|
||
releaseDate?: string;
|
||
releaseName?: string | null;
|
||
releaseNotes?: string | null;
|
||
}
|
||
|
||
export interface ElectronBridgeAppUpdateProgress {
|
||
bytesPerSecond?: number;
|
||
percent: number;
|
||
total?: number;
|
||
transferred?: number;
|
||
}
|
||
|
||
export interface ElectronBridgeAppUpdateStatus {
|
||
status: ElectronBridgeAppUpdateStatusValue;
|
||
currentVersion: string;
|
||
latestVersion?: string;
|
||
release?: ElectronBridgeAppUpdateRelease;
|
||
progress?: ElectronBridgeAppUpdateProgress;
|
||
error?: string;
|
||
supportedSelfUpdate: boolean;
|
||
/** Release page for the configured channel; opened for manual installs. */
|
||
manualDownloadUrl: string;
|
||
/** Channel the updater checks (`Settings.updateChannel`, mirrored). */
|
||
channel: AppUpdateChannel;
|
||
/**
|
||
* Channel the current verdict — `latestVersion`, `release`, a download
|
||
* in flight or finished — was produced on. Absent until the first
|
||
* check. Differs from `channel` after a channel change saved while a
|
||
* download was running or done: the download is kept, the new channel
|
||
* has not been checked yet.
|
||
*/
|
||
verdictChannel?: AppUpdateChannel;
|
||
/** Channel the running build came from, read off its version string. */
|
||
installedChannel: AppUpdateChannel;
|
||
}
|
||
|
||
export type ElectronBridgeAppUpdateReleaseNotesDirection = 'previous' | 'next';
|
||
|
||
export interface ElectronBridgeAppUpdateReleaseNotesRequest {
|
||
version?: string;
|
||
direction?: ElectronBridgeAppUpdateReleaseNotesDirection;
|
||
fallbackToLatest?: boolean;
|
||
}
|
||
|
||
export interface ElectronBridgeAppUpdateReleaseNotes {
|
||
version: string;
|
||
tagName: string;
|
||
releaseName?: string | null;
|
||
publishedAt?: string | null;
|
||
bodyMarkdown: string;
|
||
htmlUrl: string;
|
||
hasPrevious: boolean;
|
||
hasNext: boolean;
|
||
}
|
||
|
||
export interface ElectronBridgeAiSettings {
|
||
aiProvider: string;
|
||
aiModelName: string;
|
||
aiApiKey: string;
|
||
}
|
||
|
||
export interface ElectronBridgeStalkerRequestPayload {
|
||
probe?: SourceProbeContext;
|
||
url: string;
|
||
macAddress: string;
|
||
params: Record<string, string>;
|
||
token?: string;
|
||
serialNumber?: string;
|
||
requestId?: string;
|
||
/**
|
||
* Endpoint-discovery probes only: exempt this request from the main
|
||
* process' per-host connectivity guard. Discovery walks several candidate
|
||
* paths on one host and expects most of them to fail, so its failures must
|
||
* neither count towards the guard nor be fast-failed by it.
|
||
*/
|
||
skipConnectionGuard?: boolean;
|
||
}
|
||
|
||
export interface ElectronBridgeXtreamRequestPayload {
|
||
probe?: SourceProbeContext;
|
||
connectionTest?: boolean;
|
||
url: string;
|
||
params: Record<string, string>;
|
||
requestId?: string;
|
||
sessionId?: string;
|
||
suppressErrorLog?: boolean;
|
||
}
|
||
|
||
export interface ElectronBridgeXtreamResponse {
|
||
connectionFailure?: XtreamConnectionFailure;
|
||
payload: unknown;
|
||
action: string;
|
||
}
|
||
|
||
export interface ElectronBridgeXtreamCancelResult extends ElectronBridgeResult {
|
||
cancelled: number;
|
||
}
|
||
|
||
export interface ElectronBridgeXtreamProbeResult {
|
||
/**
|
||
* HTTP status, or 0 when no response was obtained.
|
||
*
|
||
* 0 covers a timeout and a URL rejected by the redirect-safety policy as
|
||
* well as a dead host, so it means "could not check" — never "offline".
|
||
*/
|
||
status: number;
|
||
url: string;
|
||
/** Round-trip time; present whenever the request completed either way. */
|
||
latencyMs?: number;
|
||
error?: string;
|
||
}
|
||
|
||
export interface ElectronBridgeEpgFetchResult extends ElectronBridgeResult {
|
||
message?: string;
|
||
skipped?: string[];
|
||
}
|
||
|
||
export interface ElectronBridgeTrustOptions {
|
||
trustedPrivateNetworkEpgUrls?: string[];
|
||
trustedInsecureTlsHosts?: string[];
|
||
}
|
||
|
||
export interface ElectronBridgePlaylistFetchOptions extends ElectronBridgeTrustOptions {
|
||
userAgent?: string;
|
||
}
|
||
|
||
export interface ElectronBridgeEpgFreshnessResult {
|
||
staleUrls: string[];
|
||
freshUrls: string[];
|
||
}
|
||
|
||
export interface ElectronBridgeEpgMapping {
|
||
id: number;
|
||
channelKey: string;
|
||
epgChannelId: string;
|
||
playlistId: string | null;
|
||
}
|
||
|
||
/** A programme-search hit: the programme plus the XMLTV channel's display name. */
|
||
export interface ElectronBridgeEpgSearchProgram extends EpgProgram {
|
||
channelName: string | null;
|
||
}
|
||
|
||
export interface ElectronBridgeEpgSearchResult {
|
||
id: string;
|
||
displayName: string;
|
||
iconUrl: string | null;
|
||
}
|
||
|
||
export interface ElectronBridgeEpgLookupOptions {
|
||
sourceUrls?: string[];
|
||
}
|
||
|
||
/**
|
||
* Options for the batched "currently airing" lookup. `nowMs` is the instant
|
||
* the lookup is evaluated at: the renderer passes its wall clock shifted into
|
||
* the provider's EPG clock (`epgProviderClockMs`, see
|
||
* `epg-display-offset.util.ts`) so the row the backend picks is the one the
|
||
* renderer will also present as "now". Absent, the main process uses its own
|
||
* clock.
|
||
*/
|
||
export interface ElectronBridgeCurrentProgramsOptions extends ElectronBridgeEpgLookupOptions {
|
||
nowMs?: number;
|
||
}
|
||
|
||
export interface ElectronBridgeEpgGuideWindow extends ElectronBridgeEpgLookupOptions {
|
||
/**
|
||
* Playlist channel lookup keys (tvg-id, else name), ≤100 keys for
|
||
* programmes, ≤2000 for coverage; larger batches are truncated and the
|
||
* cut keys are absent from the answer.
|
||
*/
|
||
channelIds: string[];
|
||
/** Provider-clock window bounds in epoch ms. */
|
||
fromMs: number;
|
||
toMs: number;
|
||
}
|
||
|
||
export interface ElectronBridgeEpgProgressStats {
|
||
totalChannels: number;
|
||
totalPrograms: number;
|
||
}
|
||
|
||
export interface ElectronBridgeEpgProgress {
|
||
url: string;
|
||
generation?: number;
|
||
status: ElectronBridgeEpgProgressStatus;
|
||
stats?: ElectronBridgeEpgProgressStats;
|
||
error?: string;
|
||
errorCode?: ElectronBridgeSecurityErrorCode;
|
||
errorHost?: string;
|
||
queuePosition?: number;
|
||
}
|
||
|
||
export interface ElectronBridgeEpgChannelSummary {
|
||
id: string;
|
||
displayName: string;
|
||
}
|
||
|
||
export interface ElectronBridgeEpgChannelListResult {
|
||
channels: ElectronBridgeEpgChannelSummary[];
|
||
/** Always empty for this endpoint; retained for wire-format compatibility. */
|
||
programs: [];
|
||
}
|
||
|
||
export interface ElectronBridgeDbOperationEvent {
|
||
operationId?: string;
|
||
operation: string;
|
||
playlistId?: string;
|
||
status: ElectronBridgeDbOperationStatus;
|
||
phase?: string;
|
||
current?: number;
|
||
total?: number;
|
||
increment?: number;
|
||
error?: string;
|
||
}
|
||
|
||
export interface ElectronBridgePlaylistInput {
|
||
id?: string;
|
||
_id?: string;
|
||
name?: string;
|
||
title?: string;
|
||
serverUrl?: string;
|
||
username?: string;
|
||
password?: string;
|
||
macAddress?: string;
|
||
url?: string;
|
||
type?: ElectronBridgePlaylistType | string;
|
||
lastUpdated?: string;
|
||
}
|
||
|
||
export interface ElectronBridgePlaylistRow {
|
||
id: string;
|
||
name: string;
|
||
serverUrl: string;
|
||
username: string;
|
||
password: string;
|
||
type: string;
|
||
macAddress?: string;
|
||
url?: string;
|
||
lastUpdated?: string;
|
||
}
|
||
|
||
export type ElectronBridgePlaylistUpsertInput =
|
||
Playlist | ElectronBridgePlaylistInput;
|
||
|
||
export interface ElectronBridgeCategoryRow {
|
||
id: number;
|
||
name: string;
|
||
playlist_id: string;
|
||
type: 'movies' | 'live' | 'series';
|
||
xtream_id: number;
|
||
hidden: boolean;
|
||
/** Parental lock index, stamped from the renderer's lock store. */
|
||
locked: boolean;
|
||
}
|
||
|
||
export interface ElectronBridgeXtreamContent {
|
||
id: number;
|
||
category_id: number;
|
||
title: string;
|
||
rating: string;
|
||
added: string;
|
||
poster_url: string;
|
||
backdrop_url?: string | null;
|
||
epg_channel_id?: string | null;
|
||
tv_archive?: number | null;
|
||
tv_archive_duration?: number | null;
|
||
direct_source?: string | null;
|
||
xtream_id: number;
|
||
type: string;
|
||
added_at?: string;
|
||
viewed_at?: string;
|
||
position?: number | null;
|
||
}
|
||
|
||
export type ElectronBridgeXtreamContentStream =
|
||
| {
|
||
category_id: string | number;
|
||
rating?: string | number;
|
||
rating_imdb?: string;
|
||
last_modified?: string;
|
||
added?: string;
|
||
stream_icon?: string;
|
||
poster?: string;
|
||
cover?: string;
|
||
name?: string;
|
||
title?: string;
|
||
epg_channel_id?: string;
|
||
tv_archive?: string | number;
|
||
tv_archive_duration?: string | number;
|
||
direct_source?: string;
|
||
series_id?: string | number;
|
||
stream_id?: string | number;
|
||
}
|
||
| Record<string, unknown>;
|
||
|
||
export type ElectronBridgeGlobalSearchResult = GlobalSearchResult;
|
||
|
||
export interface ElectronBridgeGlobalRecentItem extends ElectronBridgeXtreamContent {
|
||
playlist_id: string;
|
||
playlist_name: string;
|
||
viewed_at: string;
|
||
}
|
||
|
||
export interface ElectronBridgeGlobalFavoriteItem extends ElectronBridgeXtreamContent {
|
||
playlist_id: string;
|
||
playlist_name: string;
|
||
added_at: string;
|
||
}
|
||
|
||
export interface ElectronBridgeGlobalRecentlyAddedItem extends ElectronBridgeXtreamContent {
|
||
playlist_id: string;
|
||
playlist_name: string;
|
||
added_at: string;
|
||
}
|
||
|
||
export interface ElectronBridgeFavoriteReorderUpdate {
|
||
content_id: number;
|
||
/** Favorites are playlist-scoped — scope the position write per playlist */
|
||
playlist_id: string;
|
||
position: number;
|
||
}
|
||
|
||
export interface ElectronBridgeRecentItemsBatchItem {
|
||
contentId: number;
|
||
playlistId: string;
|
||
}
|
||
|
||
export interface ElectronBridgeRemoteControlCommand {
|
||
type: ElectronBridgeRemoteControlCommandType;
|
||
number?: number;
|
||
}
|
||
|
||
export interface ElectronBridgeRemoteControlStatus {
|
||
portal: ElectronBridgeRemotePortal;
|
||
isLiveView: boolean;
|
||
channelName?: string;
|
||
channelNumber?: number;
|
||
epgTitle?: string;
|
||
epgStart?: string;
|
||
epgEnd?: string;
|
||
supportsVolume?: boolean;
|
||
volume?: number;
|
||
muted?: boolean;
|
||
}
|
||
|
||
export interface ElectronBridgePlayerError {
|
||
player: string;
|
||
error: string;
|
||
originalError: string;
|
||
}
|
||
|
||
export interface ElectronBridgePlaybackPositionInput extends Omit<
|
||
PlaybackPositionData,
|
||
'playlistId' | 'updatedAt'
|
||
> {
|
||
playlistType?: ElectronBridgePlaylistType;
|
||
}
|
||
|
||
export interface ElectronBridgeDownloadHeaders {
|
||
userAgent?: string;
|
||
referer?: string;
|
||
origin?: string;
|
||
}
|
||
|
||
export interface ElectronBridgeDownloadStartPayload {
|
||
playlistId: string;
|
||
xtreamId: number;
|
||
contentType: DownloadContentType;
|
||
catchup?: CatchupDownloadMetadata | null;
|
||
title: string;
|
||
url: string;
|
||
posterUrl?: string;
|
||
metadataSnapshot?: DownloadMetadataSnapshot;
|
||
downloadFolder: string;
|
||
headers?: ElectronBridgeDownloadHeaders;
|
||
seriesXtreamId?: number;
|
||
seasonNumber?: number;
|
||
episodeNumber?: number;
|
||
episodeIdentityScope?: ElectronBridgeEpisodeIdentityScope;
|
||
playlistName?: string;
|
||
playlistType?: ElectronBridgePlaylistType;
|
||
serverUrl?: string;
|
||
portalUrl?: string;
|
||
macAddress?: string;
|
||
}
|
||
|
||
export interface ElectronBridgeDownloadStartResult extends ElectronBridgeErrorResult {
|
||
id?: number;
|
||
reason?: ElectronBridgeDownloadStartReason;
|
||
}
|
||
|
||
export interface ElectronBridgeDownloadRedownloadResult extends ElectronBridgeErrorResult {
|
||
recovered?: boolean;
|
||
}
|
||
|
||
export interface ElectronDownloadItem {
|
||
id: number;
|
||
playlistId: string;
|
||
xtreamId: number;
|
||
contentType: DownloadContentType;
|
||
catchup?: CatchupDownloadMetadata | null;
|
||
seriesXtreamId?: number;
|
||
seasonNumber?: number;
|
||
episodeNumber?: number;
|
||
episodeIdentityScope?: ElectronBridgeEpisodeIdentityScope | null;
|
||
title: string;
|
||
url: string;
|
||
fileName?: string;
|
||
filePath?: string;
|
||
posterUrl?: string;
|
||
metadataSnapshot?: DownloadMetadataSnapshot;
|
||
status: ElectronBridgeDownloadStatus;
|
||
fileAvailability: ElectronDownloadFileAvailability;
|
||
bytesDownloaded?: number;
|
||
totalBytes?: number;
|
||
errorMessage?: string;
|
||
createdAt?: string;
|
||
updatedAt?: string;
|
||
}
|
||
|
||
/**
|
||
* Renderer-facing live-TV recording row. `fileAvailability` is derived at
|
||
* read time (never persisted) and `programs` is the decoded `programs_json`
|
||
* column. Recordings reuse the download availability vocabulary so the
|
||
* manager UI can share its missing-file affordances — extended by
|
||
* `'unknown'` for an inconclusive probe (timeout, permission or I/O error):
|
||
* only proven absence may move a recording to Needs attention or hide its
|
||
* file actions, so consumers gate on `=== 'missing'`, never on
|
||
* `!== 'available'`.
|
||
*/
|
||
export interface ElectronRecordingItem {
|
||
id: number;
|
||
sessionId?: string;
|
||
status: RecordingStatus;
|
||
filePath: string;
|
||
fileSizeBytes?: number;
|
||
channelName: string;
|
||
channelLogoUrl?: string;
|
||
playlistId?: string;
|
||
playlistName?: string;
|
||
sourceType?: RecordingSourceType;
|
||
epgChannelId?: string;
|
||
programTitle?: string;
|
||
programDescription?: string;
|
||
programStart?: string;
|
||
programStop?: string;
|
||
programs?: RecordingProgramSnapshot[];
|
||
errorMessage?: string;
|
||
startedAt: string;
|
||
endedAt?: string;
|
||
createdAt?: string;
|
||
updatedAt?: string;
|
||
fileAvailability: ElectronDownloadFileAvailability | 'unknown';
|
||
}
|
||
|
||
export interface ElectronBridgeApi {
|
||
onPortalDebugEvent?: (
|
||
callback: (data: PortalDebugEvent) => void
|
||
) => () => void;
|
||
onPlaylistRefreshEvent?: (
|
||
callback: (data: PlaylistRefreshEvent) => void
|
||
) => () => void;
|
||
getAppVersion: () => Promise<string>;
|
||
platform: string;
|
||
getAppUpdateStatus: () => Promise<ElectronBridgeAppUpdateStatus>;
|
||
checkForAppUpdate: () => Promise<ElectronBridgeAppUpdateStatus>;
|
||
downloadAppUpdate: () => Promise<ElectronBridgeAppUpdateStatus>;
|
||
installAppUpdate: () => Promise<ElectronBridgeAppUpdateStatus>;
|
||
getAppUpdateReleaseNotes: (
|
||
request?: ElectronBridgeAppUpdateReleaseNotesRequest
|
||
) => Promise<ElectronBridgeAppUpdateReleaseNotes>;
|
||
onAppUpdateStatusChange: (
|
||
callback: (status: ElectronBridgeAppUpdateStatus) => void
|
||
) => () => void;
|
||
minimizeWindow: () => Promise<void>;
|
||
toggleMaximizeWindow: () => Promise<ElectronBridgeWindowState>;
|
||
/**
|
||
* Toggles OS-level window fullscreen (F11). Reports the requested state;
|
||
* the `onWindowStateChange` push stays authoritative once the window
|
||
* manager has acted.
|
||
*/
|
||
toggleFullScreenWindow: () => Promise<ElectronBridgeWindowState>;
|
||
closeWindow: () => Promise<void>;
|
||
/**
|
||
* Zoom shortcuts (Cmd/Ctrl and +/−/0). Synchronous and preload-local:
|
||
* steps the FRAME-BOUND temporary zoom level through `webFrame` (never a
|
||
* main-process `webContents.setZoomLevel`, whose per-URL entry the app's
|
||
* `file://` path routing resets — issue #1109) by `stepZoomLevel`'s
|
||
* rules — one `ZOOM_LEVEL_STEP` inside `ZOOM_LEVEL_MIN..ZOOM_LEVEL_MAX`,
|
||
* a stored level already outside them never moved against the request —
|
||
* and returns the level now applied, which is therefore not itself
|
||
* guaranteed to be within the limits. Persistence needs nothing from the
|
||
* caller: the main process reads the live level back on close, quit and
|
||
* reload.
|
||
*/
|
||
adjustZoomLevel: (action: ZoomLevelAction) => number;
|
||
getWindowState: () => Promise<ElectronBridgeWindowState>;
|
||
onWindowStateChange: (
|
||
callback: (state: ElectronBridgeWindowState) => void
|
||
) => () => void;
|
||
/**
|
||
* While active, the main process intercepts window close / app quit and
|
||
* pushes a close request to the renderer instead of closing. Used by the
|
||
* settings page while its form holds unsaved edits.
|
||
*/
|
||
setWindowCloseGuard: (active: boolean) => Promise<void>;
|
||
/**
|
||
* Completes a close the guard intercepted: the main process re-runs the
|
||
* original intent (window close or app quit) with the guard bypassed.
|
||
*/
|
||
confirmWindowClose: () => Promise<void>;
|
||
/**
|
||
* Abandons a close the guard intercepted (the user stays), so the
|
||
* remembered close-vs-quit intent cannot leak into a later attempt.
|
||
* Carries the id of the request being abandoned: a cancellation that
|
||
* arrives after a NEWER interception must not wipe that newer intent.
|
||
*/
|
||
cancelWindowClose: (requestId?: number) => Promise<void>;
|
||
onWindowCloseRequested: (
|
||
callback: (requestId: number) => void
|
||
) => () => void;
|
||
/**
|
||
* While active, the main process holds a
|
||
* `powerSaveBlocker('prevent-display-sleep')` so the screen stays awake
|
||
* during built-in video playback. The flag is cleared automatically when
|
||
* the renderer navigates, reloads, or dies, so a crashed page can never
|
||
* leave the display pinned awake.
|
||
*/
|
||
setPlaybackKeepAwake: (active: boolean) => Promise<void>;
|
||
/**
|
||
* Parental lock enforcement for the SQLite worker: `true` withholds
|
||
* every row in a locked category from content reads, `false` returns
|
||
* them. Reset to the mirrored `parentalLockEnabled` setting when the
|
||
* renderer reloads or dies, so a crashed page can never leave the
|
||
* library unlocked.
|
||
*/
|
||
setParentalLockState: (active: boolean) => Promise<void>;
|
||
fetchPlaylistByUrl: (
|
||
url: string,
|
||
title?: string,
|
||
options?: ElectronBridgePlaylistFetchOptions
|
||
) => Promise<Playlist>;
|
||
updatePlaylistFromFilePath: (
|
||
filePath: string,
|
||
title: string
|
||
) => Promise<Playlist>;
|
||
openPlaylistFromFile: () => Promise<Playlist | null>;
|
||
onPlaylistOpenRequest: (
|
||
callback: (request: ElectronBridgePlaylistOpenRequest) => void
|
||
) => () => void;
|
||
/**
|
||
* Tells the main process that an `onPlaylistOpenRequest` listener is
|
||
* attached, so it can push the playlist files the OS handed over — both
|
||
* the ones queued before this renderer existed and any that arrive later.
|
||
* Call it *after* subscribing.
|
||
*/
|
||
announcePlaylistOpenListener: () => Promise<void>;
|
||
/**
|
||
* Confirms a pushed request reached this renderer. Until it does, the main
|
||
* process keeps the request and replays it to the next renderer.
|
||
*/
|
||
acknowledgePlaylistOpenRequest: (requestId: string) => Promise<void>;
|
||
getPathForFile: (file: File) => string;
|
||
saveFileDialog: (
|
||
defaultPath: string,
|
||
filters?: ElectronBridgeDialogFilter[]
|
||
) => Promise<string | null>;
|
||
writeFile: (
|
||
filePath: string,
|
||
content: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
setUserAgent: (
|
||
userAgent?: string | null,
|
||
referer?: string | null,
|
||
scopeUrl?: string | null,
|
||
credentials?: ElectronBridgeStreamCredentials | null
|
||
) => Promise<boolean>;
|
||
openInMpv: (
|
||
url: string,
|
||
title: string,
|
||
thumbnail: string,
|
||
userAgent: string | undefined,
|
||
referer?: string,
|
||
origin?: string,
|
||
contentInfo?: PlayerContentInfo,
|
||
startTime?: number,
|
||
headers?: Record<string, string>
|
||
) => Promise<ExternalPlayerSession>;
|
||
openInVlc: (
|
||
url: string,
|
||
title: string,
|
||
thumbnail: string,
|
||
userAgent: string | undefined,
|
||
referer?: string,
|
||
origin?: string,
|
||
contentInfo?: PlayerContentInfo,
|
||
startTime?: number,
|
||
headers?: Record<string, string>
|
||
) => Promise<ExternalPlayerSession>;
|
||
autoUpdatePlaylists: (
|
||
playlists: Playlist[],
|
||
options?: ElectronBridgeTrustOptions
|
||
) => Promise<AutoUpdatePlaylistsResult>;
|
||
fetchEpg: (
|
||
urls: string[],
|
||
options?: ElectronBridgeTrustOptions
|
||
) => Promise<ElectronBridgeEpgFetchResult>;
|
||
getChannelPrograms: (
|
||
channelId: string,
|
||
options?: ElectronBridgeEpgLookupOptions
|
||
) => Promise<EpgProgram[]>;
|
||
getCurrentProgramsBatch: (
|
||
channelIds: string[],
|
||
options?: ElectronBridgeCurrentProgramsOptions
|
||
) => Promise<Record<string, EpgProgram | null>>;
|
||
getEpgChannelMetadata: (
|
||
channelIds: string[],
|
||
options?: ElectronBridgeEpgLookupOptions
|
||
) => Promise<Record<string, EpgChannelMetadata | null>>;
|
||
getEpgChannels: () => Promise<ElectronBridgeEpgChannelListResult>;
|
||
getEpgProgramsForChannels: (
|
||
window: ElectronBridgeEpgGuideWindow
|
||
) => Promise<Record<string, EpgProgram[]>>;
|
||
getEpgProgramCoverage: (
|
||
window: ElectronBridgeEpgGuideWindow
|
||
) => Promise<string[]>;
|
||
forceFetchEpg: (
|
||
url: string,
|
||
options?: ElectronBridgeTrustOptions
|
||
) => Promise<ElectronBridgeEpgFetchResult>;
|
||
/** Native picker for a local XMLTV file; resolves null when cancelled. */
|
||
openEpgFileDialog: () => Promise<string | null>;
|
||
clearEpgData: () => Promise<ElectronBridgeResult>;
|
||
reconcileEpgSources: (urls: string[]) => Promise<ElectronBridgeResult>;
|
||
clearEpgDataForSource: (sourceUrl: string) => Promise<ElectronBridgeResult>;
|
||
checkEpgFreshness: (
|
||
urls: string[],
|
||
maxAgeHours?: number
|
||
) => Promise<ElectronBridgeEpgFreshnessResult>;
|
||
searchEpgPrograms: (
|
||
searchTerm: string,
|
||
limit?: number
|
||
) => Promise<ElectronBridgeEpgSearchProgram[]>;
|
||
|
||
// EPG channel mapping (manual user overrides)
|
||
getEpgMapping: (
|
||
channelKey: string
|
||
) => Promise<ElectronBridgeEpgMapping | null>;
|
||
getEpgMappingsBatch: (
|
||
channelKeys: string[]
|
||
) => Promise<Record<string, string>>;
|
||
setEpgMapping: (
|
||
channelKey: string,
|
||
epgChannelId: string,
|
||
playlistId?: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
deleteEpgMapping: (channelKey: string) => Promise<ElectronBridgeResult>;
|
||
searchEpgChannels: (
|
||
searchTerm: string,
|
||
limit?: number
|
||
) => Promise<ElectronBridgeEpgSearchResult[]>;
|
||
|
||
updateSettings: (settings: Partial<Settings>) => Promise<void>;
|
||
getAiSettings: () => Promise<ElectronBridgeAiSettings>;
|
||
setMpvPlayerPath: (mpvPlayerPath: string) => Promise<void>;
|
||
setVlcPlayerPath: (vlcPlayerPath: string) => Promise<void>;
|
||
stalkerRequest: (
|
||
payload: ElectronBridgeStalkerRequestPayload
|
||
) => Promise<Record<string, unknown>>;
|
||
/**
|
||
* Forgets the connection failures recorded for the host `url` points at, so
|
||
* the next request contacts it for real instead of being fast-failed.
|
||
*/
|
||
resetHostConnectivityGuard: (url: string) => Promise<ElectronBridgeResult>;
|
||
xtreamRequest: (
|
||
payload: ElectronBridgeXtreamRequestPayload
|
||
) => Promise<ElectronBridgeXtreamResponse>;
|
||
xtreamCancelSession: (
|
||
sessionId: string
|
||
) => Promise<ElectronBridgeXtreamCancelResult>;
|
||
xtreamProbeUrl: (
|
||
url: string,
|
||
method?: 'GET' | 'HEAD'
|
||
) => Promise<ElectronBridgeXtreamProbeResult>;
|
||
/** Generic stream reachability probe (VOD multi-source availability) */
|
||
cancelSourceProbe: (id: string) => Promise<void>;
|
||
probeM3uSource: (payload: {
|
||
url: string;
|
||
userAgent?: string;
|
||
trustedInsecureTlsHosts?: string[];
|
||
probe: SourceProbeContext;
|
||
}) => Promise<SourceHealthResult>;
|
||
probeStreamUrl: (
|
||
url: string,
|
||
method?: 'GET' | 'HEAD',
|
||
/** Playback headers the owning playlist requires, when it has any. */
|
||
headers?: StreamProbeHeaders
|
||
) => Promise<ElectronBridgeXtreamProbeResult>;
|
||
refreshPlaylist: (
|
||
payload: PlaylistRefreshPayload
|
||
) => Promise<Playlist | PlaylistRefreshCancelledResult>;
|
||
cancelPlaylistRefresh: (
|
||
operationId: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbCreatePlaylist: (
|
||
playlist: ElectronBridgePlaylistUpsertInput
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbGetPlaylist: (
|
||
playlistId: string
|
||
) => Promise<ElectronBridgePlaylistRow | null>;
|
||
/**
|
||
* Records the panel clock learned from account info as one conditional
|
||
* UPDATE on the row payload; a no-op unless the row still points at the
|
||
* given connection (issue #1562).
|
||
*/
|
||
dbSetPlaylistServerTimezone: (
|
||
playlistId: string,
|
||
connection: { serverUrl: string; username: string; password: string },
|
||
serverTimezone: string
|
||
) => Promise<{ updated: boolean }>;
|
||
dbUpsertAppPlaylist: (
|
||
playlist: Playlist,
|
||
/**
|
||
* Instrumentation-only; stripped before the DB invoke.
|
||
* Never enters the DB worker payload or persisted playlist data.
|
||
*/
|
||
operationId?: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbMigrateAppPlaylists: (
|
||
playlists: Playlist[]
|
||
) => Promise<{ success: boolean; count: number }>;
|
||
dbRecoverLegacyPlaylists: () => Promise<void>;
|
||
dbUpsertAppPlaylists: (
|
||
playlists: Playlist[]
|
||
) => Promise<ElectronBridgeCountResult>;
|
||
dbGetAppPlaylists: () => Promise<Playlist[]>;
|
||
dbGetAppPlaylistMetas: () => Promise<Playlist[]>;
|
||
dbGetAppPlaylist: (
|
||
playlistId: string,
|
||
/**
|
||
* Instrumentation-only; stripped before the DB invoke.
|
||
* Never enters the DB worker payload or persisted playlist data.
|
||
*/
|
||
operationId?: string
|
||
) => Promise<Playlist | null>;
|
||
dbGetAppPlaylistFavoriteChannels: (
|
||
playlistId: string
|
||
) => Promise<M3uFavoriteChannel[]>;
|
||
dbUpdatePlaylist: (
|
||
playlistId: string,
|
||
updates: Partial<Playlist> | ElectronBridgePlaylistInput
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbDeletePlaylist: (
|
||
playlistId: string,
|
||
operationId?: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbDeleteXtreamContent: (
|
||
playlistId: string,
|
||
operationId?: string
|
||
) => Promise<{
|
||
success: boolean;
|
||
favorites: XtreamBackupFavoriteItem[];
|
||
recentlyViewed: XtreamBackupRecentlyViewedItem[];
|
||
hiddenCategories: XtreamBackupHiddenCategory[];
|
||
}>;
|
||
dbRestoreXtreamUserData: (
|
||
playlistId: string,
|
||
favorites: XtreamBackupFavoriteItem[],
|
||
recentlyViewed: XtreamBackupRecentlyViewedItem[],
|
||
operationId?: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbHasCategories: (playlistId: string, type: string) => Promise<boolean>;
|
||
dbGetCategories: (
|
||
playlistId: string,
|
||
type: string
|
||
) => Promise<ElectronBridgeCategoryRow[]>;
|
||
dbSaveCategories: (
|
||
playlistId: string,
|
||
categories: XtreamCategory[],
|
||
type: string,
|
||
hiddenCategoryXtreamIds?: number[],
|
||
lockedCategoryXtreamIds?: number[]
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbGetAllCategories: (
|
||
playlistId: string,
|
||
type: string
|
||
) => Promise<ElectronBridgeCategoryRow[]>;
|
||
dbUpdateCategoryVisibility: (
|
||
categoryIds: number[],
|
||
hidden: boolean
|
||
) => Promise<ElectronBridgeResult>;
|
||
/**
|
||
* Re-stamps `categories.locked` for one playlist and type from the given
|
||
* provider category ids: rows in the list become locked, every other row
|
||
* of that playlist/type unlocked. Idempotent, so the renderer can replay
|
||
* its lock store after a refresh or a backup restore.
|
||
*/
|
||
dbSetCategoryLocks: (
|
||
playlistId: string,
|
||
type: string,
|
||
lockedXtreamIds: number[]
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbHasContent: (playlistId: string, type: string) => Promise<boolean>;
|
||
dbGetContent: (
|
||
playlistId: string,
|
||
type: string
|
||
) => Promise<ElectronBridgeXtreamContent[]>;
|
||
dbSaveContent: (
|
||
playlistId: string,
|
||
streams: ElectronBridgeXtreamContentStream[],
|
||
type: string,
|
||
operationId?: string
|
||
) => Promise<ElectronBridgeCountResult>;
|
||
dbClearXtreamImportCache: (
|
||
playlistId: string,
|
||
type: ElectronBridgePortalContentType
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbSearchContent: (
|
||
playlistId: string,
|
||
searchTerm: string,
|
||
types: string[],
|
||
excludeHidden?: boolean
|
||
) => Promise<ElectronBridgeXtreamContent[]>;
|
||
dbGlobalSearch: (
|
||
searchTerm: string,
|
||
types: string[],
|
||
excludeHidden?: boolean,
|
||
sources?: GlobalSearchResultSource[],
|
||
options?: GlobalSearchPaginationOptions
|
||
) => Promise<ElectronBridgeGlobalSearchResult[]>;
|
||
dbGetGlobalRecentlyAdded: (
|
||
kind: ElectronBridgeGlobalRecentlyAddedKind,
|
||
limit?: number,
|
||
playlistType?: ElectronBridgePlaylistType
|
||
) => Promise<ElectronBridgeGlobalRecentlyAddedItem[]>;
|
||
dbGetRecentlyViewed: () => Promise<ElectronBridgeGlobalRecentItem[]>;
|
||
dbClearRecentlyViewed: () => Promise<ElectronBridgeResult>;
|
||
dbAddFavorite: (
|
||
contentId: number,
|
||
playlistId: string,
|
||
backdropUrl?: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbRemoveFavorite: (
|
||
contentId: number,
|
||
playlistId: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbIsFavorite: (contentId: number, playlistId: string) => Promise<boolean>;
|
||
dbGetFavorites: (
|
||
playlistId: string
|
||
) => Promise<ElectronBridgeXtreamContent[]>;
|
||
dbGetGlobalFavorites: () => Promise<ElectronBridgeGlobalFavoriteItem[]>;
|
||
dbGetAllGlobalFavorites: () => Promise<ElectronBridgeGlobalFavoriteItem[]>;
|
||
dbReorderGlobalFavorites: (
|
||
updates: ElectronBridgeFavoriteReorderUpdate[]
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbGetRecentItems: (
|
||
playlistId: string
|
||
) => Promise<ElectronBridgeXtreamContent[]>;
|
||
dbAddRecentItem: (
|
||
contentId: number,
|
||
playlistId: string,
|
||
backdropUrl?: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbClearPlaylistRecentItems: (
|
||
playlistId: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbRemoveRecentItem: (
|
||
contentId: number,
|
||
playlistId: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbRemoveRecentItemsBatch: (
|
||
items: ElectronBridgeRecentItemsBatchItem[]
|
||
) => Promise<ElectronBridgeCountResult>;
|
||
dbGetContentByXtreamId: (
|
||
xtreamId: number,
|
||
playlistId: string,
|
||
contentType?: ElectronBridgePortalContentType
|
||
) => Promise<ElectronBridgeXtreamContent | null>;
|
||
dbSetContentMetadataIfMissing: (
|
||
contentId: number,
|
||
patch?: ContentMetadataPatch
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbGetAppState: (key: string) => Promise<string | null>;
|
||
dbSetAppState: (
|
||
key: string,
|
||
value: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
// TMDB metadata cache
|
||
dbGetTmdbMetadata: (
|
||
mediaType: TmdbCacheMediaType,
|
||
lookupKey: string,
|
||
language: string
|
||
) => Promise<TmdbCacheEntry | null>;
|
||
dbSetTmdbMetadata: (entry: TmdbCacheEntry) => Promise<ElectronBridgeResult>;
|
||
/** Row count + payload bytes for the settings cache panel */
|
||
dbGetTmdbCacheStats: () => Promise<TmdbCacheStats>;
|
||
dbClearTmdbMetadata: () => Promise<
|
||
ElectronBridgeResult & { deleted: number }
|
||
>;
|
||
/** Cross-playlist title matching (actor page "All portals" scope) */
|
||
dbMatchTitles: (titles: string[]) => Promise<CatalogTitleMatch[]>;
|
||
/** VOD multi-source: the same movie in the user's other Xtream playlists */
|
||
dbFindTitleSources: (request: {
|
||
title: string;
|
||
year?: number | null;
|
||
excludePlaylistId?: string | null;
|
||
/** A stream id inside the excluded playlist to keep anyway (a pin). */
|
||
keepContentId?: number | null;
|
||
}) => Promise<VodSourceCandidateRow[]>;
|
||
/** Per-movie pinned source; keys are passed most-trusted first */
|
||
dbGetVodSourcePin: (matchKeys: string[]) => Promise<VodSourcePin | null>;
|
||
/** Every pin pointing at this playlist — used by playlist backup. */
|
||
dbListVodSourcePins: (playlistId: string) => Promise<VodSourcePin[]>;
|
||
/** Bulk clear: not keyed, so no `MAX_KEYS_PER_LOOKUP` truncation. */
|
||
dbClearVodSourcePinsForPlaylist: (
|
||
playlistId: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
/**
|
||
* `aliasKeys` receive the same pin, and `retireKeys` are removed, all in
|
||
* the SAME transaction as the write. Aliases keep a pin readable under the
|
||
* poorer key forms the movie had before enrichment.
|
||
*/
|
||
dbSetVodSourcePin: (
|
||
pin: VodSourcePin,
|
||
retireKeys?: string[],
|
||
aliasKeys?: string[]
|
||
) => Promise<ElectronBridgeResult>;
|
||
/** Makes the playlist's pins exactly `pins`, in one transaction. */
|
||
dbReplaceVodSourcePins: (
|
||
playlistId: string,
|
||
pins: VodSourcePin[]
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbClearVodSourcePin: (matchKeys: string[]) => Promise<ElectronBridgeResult>;
|
||
onChannelChange?: (
|
||
callback: (data: { direction: 'up' | 'down' }) => void
|
||
) => () => void;
|
||
onRemoteControlCommand?: (
|
||
callback: (data: ElectronBridgeRemoteControlCommand) => void
|
||
) => () => void;
|
||
updateRemoteControlStatus?: (
|
||
status: ElectronBridgeRemoteControlStatus
|
||
) => void;
|
||
onPlayerError?: (
|
||
callback: (data: ElectronBridgePlayerError) => void
|
||
) => void;
|
||
getLocalIpAddresses: () => Promise<string[]>;
|
||
onEpgProgress?: (
|
||
callback: (data: ElectronBridgeEpgProgress) => void
|
||
) => void;
|
||
onDbSaveContentProgress: (callback: (count: number) => void) => void;
|
||
removeDbSaveContentProgress: () => void;
|
||
onDbOperationEvent?: (
|
||
callback: (data: ElectronBridgeDbOperationEvent) => void
|
||
) => () => void;
|
||
dbDeleteAllPlaylists: (
|
||
operationId?: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbCancelOperation: (operationId: string) => Promise<ElectronBridgeResult>;
|
||
dbSavePlaybackPosition: (
|
||
playlistId: string,
|
||
data: ElectronBridgePlaybackPositionInput
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbGetPlaybackPosition: (
|
||
playlistId: string,
|
||
contentXtreamId: number,
|
||
contentType: ElectronBridgePlaybackContentType
|
||
) => Promise<PlaybackPositionData | null>;
|
||
dbGetSeriesPlaybackPositions: (
|
||
playlistId: string,
|
||
seriesXtreamId: number
|
||
) => Promise<PlaybackPositionData[]>;
|
||
dbGetRecentPlaybackPositions: (
|
||
playlistId: string,
|
||
limit?: number
|
||
) => Promise<PlaybackPositionData[]>;
|
||
dbGetAllPlaybackPositions: (
|
||
playlistId: string
|
||
) => Promise<PlaybackPositionData[]>;
|
||
dbClearAllPlaybackPositions: (
|
||
playlistId: string
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbClearPlaybackPosition: (
|
||
playlistId: string,
|
||
contentXtreamId: number,
|
||
contentType: ElectronBridgePlaybackContentType
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbSavePlaybackPositionsBatch: (
|
||
playlistId: string,
|
||
items: ElectronBridgePlaybackPositionInput[]
|
||
) => Promise<ElectronBridgeResult>;
|
||
dbClearPlaybackPositionsBatch: (
|
||
playlistId: string,
|
||
items: {
|
||
contentXtreamId: number;
|
||
contentType: ElectronBridgePlaybackContentType;
|
||
}[]
|
||
) => Promise<ElectronBridgeResult>;
|
||
onPlaybackPositionUpdate: (
|
||
callback: (data: PlaybackPositionData) => void
|
||
) => () => void;
|
||
onExternalPlayerSessionUpdate?: (
|
||
callback: (data: ExternalPlayerSession) => void
|
||
) => () => void;
|
||
onEmbeddedMpvSessionUpdate?: (
|
||
callback: (data: EmbeddedMpvSession) => void
|
||
) => () => void;
|
||
closeExternalPlayerSession: (
|
||
sessionId: string
|
||
) => Promise<ExternalPlayerSession | null>;
|
||
getEmbeddedMpvSupport: () => Promise<EmbeddedMpvSupport>;
|
||
prepareEmbeddedMpv?: () => Promise<EmbeddedMpvSupport>;
|
||
createEmbeddedMpvSession: (
|
||
bounds: EmbeddedMpvBounds,
|
||
title?: string,
|
||
initialVolume?: number
|
||
) => Promise<EmbeddedMpvSession>;
|
||
loadEmbeddedMpvPlayback: (
|
||
sessionId: string,
|
||
playback: ResolvedPortalPlayback
|
||
) => Promise<void>;
|
||
setEmbeddedMpvBounds: (
|
||
sessionId: string,
|
||
bounds: EmbeddedMpvBounds
|
||
) => Promise<void>;
|
||
setEmbeddedMpvPaused: (
|
||
sessionId: string,
|
||
paused: boolean
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
seekEmbeddedMpv: (
|
||
sessionId: string,
|
||
seconds: number
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
/**
|
||
* Relative seek by `deltaSeconds` (negative = backwards), resolved by mpv
|
||
* against its own playback position. Keyboard and button steps must use
|
||
* this instead of `seekEmbeddedMpv(position + delta)`: the renderer's
|
||
* `positionSeconds` is a whole-second snapshot refreshed at most every
|
||
* 500 ms and a seek reply does not carry the new position yet, so rapid
|
||
* presses computed from it collapse onto one target. mpv merges queued
|
||
* relative seeks instead, so presses accumulate.
|
||
*/
|
||
seekEmbeddedMpvBy?: (
|
||
sessionId: string,
|
||
deltaSeconds: number
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
setEmbeddedMpvVolume: (
|
||
sessionId: string,
|
||
volume: number
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
setEmbeddedMpvAudioTrack: (
|
||
sessionId: string,
|
||
trackId: number
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
setEmbeddedMpvSubtitleTrack?: (
|
||
sessionId: string,
|
||
trackId: number
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
/** Loads an external subtitle file (absolute path) via mpv `sub-add`. */
|
||
addEmbeddedMpvSubtitle?: (
|
||
sessionId: string,
|
||
filePath: string
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
setEmbeddedMpvSubtitleDelay?: (
|
||
sessionId: string,
|
||
seconds: number
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
setEmbeddedMpvSubtitleStyle?: (
|
||
sessionId: string,
|
||
style: EmbeddedMpvSubtitleStyle
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
/** Opens the main-process subtitle file dialog; null when cancelled. */
|
||
selectEmbeddedMpvSubtitleFile?: () => Promise<string | null>;
|
||
setEmbeddedMpvSpeed?: (
|
||
sessionId: string,
|
||
speed: number
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
setEmbeddedMpvAspect?: (
|
||
sessionId: string,
|
||
aspect: string
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
startEmbeddedMpvRecording?: (
|
||
sessionId: string,
|
||
options: EmbeddedMpvRecordingStartOptions
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
stopEmbeddedMpvRecording?: (
|
||
sessionId: string
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
getEmbeddedMpvDefaultRecordingFolder?: () => Promise<string>;
|
||
selectEmbeddedMpvRecordingFolder?: () => Promise<string | null>;
|
||
disposeEmbeddedMpvSession: (
|
||
sessionId: string
|
||
) => Promise<EmbeddedMpvSession | null>;
|
||
/**
|
||
* Frame-copy engine only: start/stop the preload frame pump that
|
||
* uploads helper frames onto the renderer's
|
||
* `<canvas data-embedded-mpv-frame>` element. Optional because older
|
||
* preload builds do not ship the pump.
|
||
*/
|
||
attachEmbeddedMpvFrameView?: (sessionId: string) => Promise<boolean>;
|
||
detachEmbeddedMpvFrameView?: () => void;
|
||
downloadsStart: (
|
||
data: ElectronBridgeDownloadStartPayload
|
||
) => Promise<ElectronBridgeDownloadStartResult>;
|
||
downloadsCancel: (downloadId: number) => Promise<ElectronBridgeErrorResult>;
|
||
downloadsPause: (downloadId: number) => Promise<ElectronBridgeErrorResult>;
|
||
downloadsResume: (
|
||
downloadId: number,
|
||
downloadFolder: string
|
||
) => Promise<ElectronBridgeErrorResult>;
|
||
downloadsRetry: (
|
||
downloadId: number,
|
||
downloadFolder: string
|
||
) => Promise<ElectronBridgeErrorResult>;
|
||
downloadsRedownloadMissing: (
|
||
downloadId: number
|
||
) => Promise<ElectronBridgeDownloadRedownloadResult>;
|
||
downloadsRemove: (downloadId: number) => Promise<DownloadRecoveryResult>;
|
||
downloadsGetList: (playlistId?: string) => Promise<ElectronDownloadItem[]>;
|
||
downloadsGet: (downloadId: number) => Promise<ElectronDownloadItem | null>;
|
||
downloadsUpdateMetadata: (
|
||
downloadId: number,
|
||
metadataSnapshot: DownloadMetadataSnapshot
|
||
) => Promise<ElectronBridgeErrorResult>;
|
||
downloadsGetDefaultFolder: () => Promise<string>;
|
||
downloadsSelectFolder: () => Promise<string | null>;
|
||
downloadsRevealFile: (
|
||
filePath: string
|
||
) => Promise<ElectronBridgeErrorResult>;
|
||
downloadsPlayFile: (filePath: string) => Promise<ElectronBridgeErrorResult>;
|
||
downloadsClearCompleted: (
|
||
playlistId?: string
|
||
) => Promise<DownloadRecoveryResult>;
|
||
onDownloadsUpdate: (callback: () => void) => () => void;
|
||
// Live-TV recordings surface. Optional: older Electron builds have no
|
||
// recordings bridge, and `supportsRecordings` (not `supportsDownloads`)
|
||
// gates every consumer.
|
||
recordingsGetList?: (
|
||
playlistId?: string
|
||
) => Promise<ElectronRecordingItem[]>;
|
||
recordingsGet?: (
|
||
recordingId: number
|
||
) => Promise<ElectronRecordingItem | null>;
|
||
recordingsStop?: (
|
||
recordingId: number
|
||
) => Promise<ElectronBridgeErrorResult>;
|
||
recordingsRemove?: (
|
||
recordingId: number
|
||
) => Promise<ElectronBridgeErrorResult>;
|
||
recordingsUpdatePrograms?: (
|
||
targetPath: string,
|
||
programs: RecordingProgramSnapshot[]
|
||
) => Promise<ElectronBridgeErrorResult>;
|
||
recordingsRevealFile?: (
|
||
filePath: string
|
||
) => Promise<ElectronBridgeErrorResult>;
|
||
recordingsPlayFile?: (
|
||
filePath: string
|
||
) => Promise<ElectronBridgeErrorResult>;
|
||
onRecordingsUpdate?: (callback: () => void) => () => void;
|
||
}
|