PrismCore / Guides

Keyframe cache identity

When a stored keyframe map may be reused for a source, how PrismCore proves the file has not changed, and how a host names a source whose URL changes every launch.

A stored keyframe map is only as good as the proof that the file has not changed under it. A map applied to different bytes would publish a plan whose segments start on frames that are not keyframes. So every map in keyframeIndexCacheDirectory is bound to an identity, and a source that cannot prove its identity neither uses a stored map nor stores one — it plans from the source every time.

The default identity: the URL plus a version proof

Without a host identity, a map's key is a SHA-256 digest over:

  • the cache format version,
  • the full URL, including its query (a query can select the media, not only carry a token),
  • the byte size and the container duration,
  • a version proof — what the open actually read:

    • a local file: URL: its modification time;
    • a remote URL: the strong ETag the coordinated HTTP reader saw, together with the URL that served it (after a redirect, the target — a tag is only unique per resource).

Only the digest is written to disk; the URL and any token in it never are.

A remote source cannot prove its version, and so gets no cache, when it is read:

  • by FFmpeg's own HTTP (the default transport — no coordinatedHTTP),
  • from an origin or proxy that sends no ETag, only a weak W/ one, or only Last-Modified,
  • through a host PrismCoreInput on a non-file: URL.

A file replaced on the server under the same URL (new strong ETag) or a rotated query token costs one rebuild rather than a wrong map. Sidecars written by 3.4.0 and earlier are ignored and rebuilt once.

A host-supplied identity

The URL-based identity cannot survive an address that changes while the bytes do not. A localhost range proxy on a new port with a new token after every app launch is a miss every time. A host that knows what it is playing can name it instead, with SourceCacheIdentity: a namespace (the server), the item, and a strong ETag the host has from its server's metadata.

Swift
let identity = SourceCacheIdentity(namespace: serverID, item: itemID, etag: itemETag)

let session = try PrismCoreSession(
    url: proxyURL,                                   // port and token change per launch
    display: await DisplayCapabilities.current(),
    keyframeIndexCacheDirectory: cachesDirectory,
    keyframeIndexCacheIdentity: identity
)

// A scrub preview must be given the same identity to share the map.
let previews = SeekPreviewService(
    url: proxyURL,
    keyframeIndexCacheDirectory: cachesDirectory,
    keyframeIndexCacheIdentity: identity
)

With an identity set:

  • The key is the cache format version, the namespace, the item, the ETag, the byte size and the container duration. The URL and whatever the transport reports stop counting, so it works over FFmpeg's own HTTP and a host PrismCoreInput as well as coordinatedHTTP.
  • Fields are length-prefixed, so no two identities can produce the same key.
  • A weak (W/…) or empty ETag, or an empty namespace or item, turns the cache off for that source — it does not fall back to the URL.
  • The host vouches for the tag: it must change whenever the file does.
  • A clone made with makeSession(changing:) carries it; Options.keyframeIndexCacheIdentity is read-only, because it names the title.

From a terminal, prismcore-cli serve and bench take --cache-identity SERVER:ITEM:ETAG.

What gets stored

  • A plan built from the container's own index, when the index provably reaches the end of the source.
  • A map harvested during a sequential play: complete at end of file, or partial with its covered end. A partial entry never replaces a better one, and nothing — including the seek preview — trusts a partial map past its covered end.
  • The cache holds at most 64 entries; a lookup refreshes an entry, and the least recently used are pruned.