Skip to main content

Type Alias: SourceResolver

type SourceResolver = (request: SourceResolutionRequest) => string | Promise<string>;

Defined in: packages/player/src/source-resolver.ts:58

Turns the logical URI in a playlist into the concrete URL mpv should open.

Parameters

ParameterTypeDescription
requestSourceResolutionRequestThe URI mpv is about to open. request.entryId is mpv's playlist entry id and is present only when the request came from the prefetch path — see SourceResolutionRequest.

Returns

string | Promise<string>

The URL to open, synchronously or as a promise.

Remarks

Determinism is a requirement, not a nicety. While a queue is active the same input must produce the same output. mpv opens each entry twice — once speculatively on the prefetch path, once for real — and decides whether the prefetched stream can be reused by comparing the two resulting URLs byte-for-byte (open_demux_reentrant, mpv 0.41.0 player/loadfile.c:1223). A resolver that mints a fresh nonce or a fresh signature per call therefore silently defeats prefetching: mpv logs "Dropping finished prefetch of wrong URL", joins the doomed opener thread on its core thread at the track boundary, and opens cold — which is worse than never prefetching at all.

This library removes most of that hazard for you by caching the first answer per URI (see resolverTtlMs) and replaying it for the second pass, so a resolver only has to be deterministic for as long as its answer is cached. Mint your signed URL once per track, not once per call, and you are fine.

The resolver runs on the JavaScript thread and it is allowed to be slow — within a budget. Resolution happens ahead of time wherever possible: the current and next entries are resolved as soon as the queue moves, which is typically a whole track's worth of wall time before mpv needs the answer. When mpv asks for a URI that is not cached yet, and it asks at play time, mpv's core is held open until you answer or resolverTimeoutMs elapses. On timeout the logical URI is used unchanged and mpv fails the load on its own terms, which arrives as an ordinary typed error event.

URIs pass through untouched when no resolver is installed. The hooks are registered for the life of the core (see Player.setSourceResolver for why), but a disarmed handler reads nothing and rewrites nothing — it continues the hook immediately, so what mpv opens is byte-for-byte the logical URI. That includes local files and live streams, which usually need no resolution: a resolver that has nothing to do for a URI should return it unchanged, and the identity answer costs one map lookup.

Example

player.setSourceResolver(async ({ uri }) => {
if (!uri.startsWith('library://')) return uri // nothing to do
const { url } = await api.signPlaybackUrl(uri.slice('library://'.length))
return url
})