/** * Copyright (c) Double Symmetry GmbH * Commercial use requires a license. See https://rntp.dev/pricing */ import type { MediaItem } from '../../interfaces/MediaItem'; import type { PlaybackErrorCode } from '../../events/PlaybackError'; /** * A MediaItem after `src/audio.ts` URL resolution: `url`/`artworkUrl` are * canonical strings or `{ uri, headers }` objects — never `require()` numbers. */ export interface ResolvedMediaItem extends Omit< MediaItem, 'url' | 'artworkUrl' > { url: string | { uri: string; headers?: Record }; artworkUrl?: string | { uri: string; headers?: Record }; } /** Extracts the plain URI string from a resolved URL. */ export function uriOf(url: ResolvedMediaItem['url']): string { return typeof url === 'string' ? url : url.uri; } /** Extracts headers from a resolved URL, if any. */ export function headersOf( url: ResolvedMediaItem['url'] ): Record | undefined { return typeof url === 'string' ? undefined : url.headers; } /** * Internal engine state. WebTrackPlayer maps this (plus playWhenReady intent) * onto the public PlaybackState — the RNTP state machine lives in one place, * above the engines. */ export type EngineState = | 'idle' | 'loading' | 'buffering' | 'ready' | 'ended' | 'error'; export interface EngineError { code: PlaybackErrorCode; message: string; } /** Engine → player notifications. Injected at engine construction. */ export interface EngineCallbacks { onStateChange(state: EngineState): void; onEnded(): void; onError(error: EngineError): void; /** Drives PlaybackProgressUpdated; fired from the media element's timeupdate. */ onTimeUpdate(positionSeconds: number): void; } /** * The playback engine boundary. Engines privately own their * HTMLAudioElement(s) — nothing above this interface touches an element. * This is what keeps dual-element gapless and per-engine caching * retrofittable without touching WebTrackPlayer. */ export interface AudioEngine { load( item: ResolvedMediaItem, opts: { autoplay: boolean; position?: number } ): void; play(): void; pause(): void; /** Unload the current source but keep the engine alive. */ stop(): void; seekTo(seconds: number): void; setVolume(volume: number): void; setPlaybackSpeed(rate: number): void; getProgress(): { position: number; duration: number; buffered: number }; // Door-openers — no-op in launch engines: preload(item: ResolvedMediaItem): void; cancelPreload(item: ResolvedMediaItem): void; clearCache(): void; destroy(): void; } /** Creates/caches engines per item. The default provider lives in resolveEngine.ts. */ export interface EngineProvider { engineFor(item: ResolvedMediaItem, callbacks: EngineCallbacks): AudioEngine; destroy(): void; }