Skip to content

AetherEngine 2.1.0

Choose a tag to compare

@superuser404notfound superuser404notfound released this 31 May 07:56
· 362 commits to main since this release

New public API: off-playback still-image extraction.

Added

FrameExtractor — still CGImages from a media URL, fully isolated from playback.

FrameExtractor decodes through its own FFmpeg context with no contact with the playback pipeline, the HLS loopback server, or shared engine state, so a scrub-preview decode can't perturb the frame on screen. Two modes share one decode core:

  • thumbnail(at:maxWidth:) snaps to the nearest keyframe, no forward decode, downscaled to maxWidth (default 320). Cheap and fast, built for scrub previews and Recents lists.
  • snapshot(at:maxSize:) decodes forward to the exact PTS at full or maxSize-clamped resolution, built for user-triggered stills.

It is an actor: blocking FFmpeg work runs on a dedicated serial queue off the cooperative pool, the decode context opens lazily on first use, a superseded request cancels the in-flight decode so the latest scrub position wins, results land in a bounded LRU cache (mode-isolated stores, second-bucketed thumbnails), and the context idle-closes after 10 s (the next request reopens lazily). shutdown() is the explicit, permanent teardown that awaits release of the FFmpeg demuxer / codec / sws resources.

// Currently-playing item:
let frames = engine.makeFrameExtractor()           // nil if nothing is loaded
// Arbitrary item (e.g. a Recents row):
let frames = FrameExtractor(url: url, httpHeaders: headers)

await frames.prewarm()                             // optional: hide cold-start
let preview = await frames.thumbnail(at: 612.0)    // CGImage?, nearest keyframe
let still   = await frames.snapshot(at: 612.0)     // CGImage?, frame-accurate
await frames.shutdown()                            // prompt teardown

AetherEngine.makeFrameExtractor() vends an extractor for the currently loaded URL (carrying its HTTP headers). The engine does not retain it; the caller owns its lifecycle.

aetherctl extract subcommand for still extraction plus leak testing (--at, --snapshot, --width, --loops), backed by the same public API. --loops N pairs with leaks --atExit to validate clean decode-context teardown.

Compatibility

Purely additive public API, no breaking changes. Existing 2.0.x callers compile and run unchanged. Pinning from: "2.0.0" already picks this up.

Full changelog: 2.0.2...2.1.0