PrismCore
FFmpeg demuxes, Apple plays.
Apple’s playback stack does things no third-party player can — hardware decode, Dolby Atmos passthrough, Dolby Vision, Match Content. It just won’t open an MKV. PrismCore remuxes any container libavformat can read into HLS-fMP4, serves it from a loopback server and hands a plain AVPlayer a playlist URL. Same file, Apple’s engine.
Shipping in
Aether
The default playback engine on iPhone, iPad, Mac, Apple TV and Apple Vision Pro — on for every install, not behind a switch.
It is a Swift package, not a framework you have to build. Anything that already hands a URL to AVPlayer can use it.
What changes when you press play
PrismCore is meant to be invisible: you open a film and it plays. What it buys you is everything a player that draws its own picture has to give up.
Your receiver gets the real soundtrack
Dolby Atmos leaves the file as a bitstream and arrives at the receiver as one — never decoded, never flattened on the way. The receiver lights up Atmos because it is being handed Atmos, not a stereo approximation of it.
Your TV actually switches into Dolby Vision
On Apple TV the panel is programmed before playback begins — frame rate, dynamic range, Dolby Vision — the same handshake Apple’s own apps get. A TV that refuses a Dolby Vision master is offered the film again without the claim, so you get a picture instead of an error.
It is the player you already know
The track menu, subtitles, Picture in Picture, AirPlay, Match Content, scrubbing with the remote: all of it is Apple’s own playback UI, because it really is Apple’s own player underneath.
Cool device, unbothered battery
The picture is decoded by the dedicated video silicon rather than the CPU, so an MKV costs the same as a file Apple ships support for. Nothing is transcoded — not on your device, and not on the server it came from.
Skipping around stays instant
A map of the whole file is published before the first frame is served, so jumping into the middle of a three-hour film lands there and plays instead of preparing the film again from the start. Dragging the scrub bar shows you the frame you are dragging towards, even on a file no server ever generated previews for.
Nothing leaves your device
The repackaging happens on the device and is served to the player over 127.0.0.1 — a loopback address that never reaches the network. There is no cloud step, no account, and nothing about what you watch to send anywhere.
The trick, in one picture
MKV · MPEG-TS
The container Apple won’t open
Demux
FFmpeg reads the streams
HLS-fMP4
Repacked on 127.0.0.1
AVPlayer
Apple’s own playback stack
Video · Audio · Subtitles
It reads the file instead of converting it
FFmpeg’s demuxer opens the container — MKV, MPEG-TS, AVI — and separates what is inside into its streams: the picture, each audio language, each subtitle. Nothing is decoded and nothing is converted; the file is only being read apart.
The streams are repacked, untouched
The same compressed picture and sound are placed into fragmented MP4 pieces, described by a playlist of the kind AVPlayer opens natively. It is a change of packaging, not of content — bit for bit the same video and the same audio that were in the file.
A local address serves it
Those pieces are handed to AVPlayer from 127.0.0.1, the address that never leaves the device, as if the film were being streamed. That is the whole trick: Apple’s player will gladly stream something it refuses to open as a file.
Apple’s engine does the rest
Hardware decoding, Atmos passthrough, the Dolby Vision handshake, the track menu, PiP and AirPlay — none of it is reimplemented, because from AVPlayer’s point of view nothing unusual is happening.
Two things cannot be repacked, and get their own treatment. Subtitles that are pictures of text rather than text (the PGS in Blu-ray rips) are read on the device by Vision’s OCR and join as ordinary subtitles. And a handful of codecs Apple never decodes at all — VP9, MPEG-2, VC-1 — take a second path where PrismCore decodes the picture itself and draws the frames; you lose the system player’s chrome there, but the film plays.
Questions people ask
Does it re-encode my files?
No. The picture and sound are copied across exactly as they were read — the only thing that changes is the box they sit in. Your file is not modified either: PrismCore only ever reads it.
Then why can’t AVPlayer just open an MKV itself?
Apple ships support for a fixed list of containers and MKV is not on it — even when the video and audio inside are formats Apple decodes perfectly well. It is a packaging problem rather than a decoding one, which is exactly why repacking is enough to solve it.
What happens with TrueHD or DTS audio?
Apple’s stack does not accept those as a bitstream, so they cannot be passed through untouched. Rather than collapse them to stereo, PrismCore converts them to EAC3 with every channel in place — surround stays surround. Atmos carried inside TrueHD is the one thing that cannot survive that trip; Atmos in EAC3, which is what most remuxes carry, passes through untouched.
Does my Plex or Jellyfin server have to transcode?
No, and that is much of the point. The server sends the original file and the repacking happens on your device, so there is no server-side transcode to melt a NAS, and no quality lost to one.
Can I turn it off?
Yes — Settings ▸ Playback in Aether. It is on by default; switch it off and playback falls back to the other engines exactly as it did before, per install, with no reinstall or reset involved.
Does it take longer to start?
No. The source is opened once and only its header is read, which is tens of milliseconds on a local file and around a tenth of a second over a network — you see “Preparing” for about as long as you would with a file Apple opens directly.
Everything below is about putting the engine in your own app. If you came here as a viewer, you have the whole story already — the rest is plumbing.
What it handles
Containers and codecs
Anything libavformat can read — MKV, MPEG-TS, AVI. HEVC, H.264 and AV1 stream-copy straight through alongside AAC, AC3, EAC3, FLAC and ALAC. TrueHD, DTS, Opus and the rest go through the audio bridge and arrive as EAC3 instead of being flattened. An anamorphic source keeps the shape its container declares, in the pasp box AVPlayer honors.
Dolby Atmos, not a downmix
EAC3+JOC is copied untouched and never decoded to PCM — and the dec3 box FFmpeg drops on a stream copy is patched back in, the one signal that makes AVFoundation take the Dolby route instead of playing plain DD+. Verified on a receiver over HDMI.
Dolby Vision and HDR
Profile 5 keeps its dvh1 sample entry, Profile 7 is converted to single-layer 8.1 through libdovi, and on tvOS the panel is programmed before AVPlayer ever sees the playlist — the handshake has to happen first or the variant is rejected outright. A master a TV refuses is retried without the claim rather than failing.
Every track, and every subtitle
Each audio track becomes an alternate rendition carrying its name, language and channel count, so AVKit’s track menu just works. Text subtitles become segmented WebVTT that survives PiP and AirPlay; PGS and VOBSUB carry only bitmaps, so they are read on the device with Vision’s OCR and join as ordinary renditions — a track does nothing until a player actually fetches it, which is what makes a remux carrying dozens of PGS tracks free.
Seeking like a local file
A keyframe-aligned segment plan is published before the first packet, and a seek outside the produced window re-anchors the demuxer to the right keyframe on demand. A byte-budgeted cache bounds disk, not seekability — evicted segments are simply reproduced, and a segment being served is never the one evicted. Production paces itself against the last segment the player actually fetched, about a minute ahead, so a multi-gigabyte file never races its own cache — and the pull from the source settles near the film’s real bitrate instead of running at line speed. A container carrying no index of its own is not stuck with that: the first sequential play harvests every keyframe it reads and persists the map, so the next play plans on it from the first second. The same map answers SeekPreviewService, which decodes the keyframe covering any position into a thumbnail for a host’s own scrub HUD.
A software path for the rest
Codecs AVPlayer cannot decode at all — VP9, MPEG-2, VC-1 — go through libavcodec into an AVSampleBufferDisplayLayer, zero-copy wherever VideoToolbox has a hardware decoder. AV1 asks the actual silicon rather than the chip name, and verified-interlaced sources leave the native path so a deinterlacer can take them — on the GPU, on the frames the decoder already produced, where the FFmpeg build carries that filter.
Starting fast
A playback used to open its source twice — once for the host’s routing decision, once to produce — and over a network that second open is a round trip inside the wait the user is watching. The probe now hands its open context to the session, every open reads only what a self-describing container needs, and the internal waits check at 10 ms rather than 100. A 5.4 GB HEVC/EAC3 remux over HTTP goes from probe to playlist in about 135 ms instead of 520 — and a file with no seek index, which would otherwise take a minute to scan, starts anyway on a bounded plan.
A licence that works in the App Store
LGPL-2.1-or-later with an Application Store Exception. A signed app cannot let anyone relink it, which put plain LGPL and store distribution in tension; the exception lifts that one requirement for store builds, while modifications to PrismCore itself stay LGPL and still get published. The public API has been stable since 1.0 — breaking changes mean a major version, and every release is written down in the changelog.
How it compares
The usual choice on Apple platforms is between a player that keeps the good stuff but only opens Apple’s containers, and one that opens everything but renders its own frames and decodes audio to PCM — which is where Atmos and Dolby Vision die. PrismCore is not a player at all: it feeds AVPlayer, so there is nothing to give up.
| PrismCore | AVPlayer | VLCKit | libmpv | |
|---|---|---|---|---|
| Plays MKV, MPEG-TS, AVI | ||||
| Dolby Atmos passed through as a bitstream | ||||
| Dolby Vision engaged on the display | ||||
| Match Content on tvOS — frame rate and range | ||||
| Apple’s own player UI: track menu, PiP, AirPlay | ||||
| VP9, MPEG-2 and VC-1 |
VLCKit and libmpv draw into their own layer and hand you PCM, which is why the middle rows fail for them — a consequence of owning the render path, not an oversight. AVPlayer fails the first and last rows because it only accepts containers Apple ships support for.
Get started
Add the package, then hand it a URL. One entry point probes the source and gives back either a playlist for AVPlayer or a software pipeline, already started.
.package(url: "https://github.com/Wenzlik/PrismCore.git", from: "1.8.7")switch try await PrismCoreEngine.open(url: mkvURL) {
case .remux(let session, let playlist):
player.replaceCurrentItem(with: AVPlayerItem(url: playlist))
…
await session.stop()
case .software(let pipeline):
hostView.layer.addSublayer(pipeline.displayLayer!)
pipeline.play()
}Two opt-ins are worth knowing about once the first playback works. Give the session a cache directory and a container with no index of its own harvests one as it plays, so the next play seeks like an indexed file. The same directory backs scrub-bar thumbnails, which are their own small pipeline — a separate context, decoded on the CPU, cached per keyframe — so they never stall the playback they belong to, whichever engine is on screen.
let session = try PrismCoreSession(url: mkvURL, keyframeIndexCacheDirectory: caches)
let previews = SeekPreviewService(url: mkvURL, keyframeIndexCacheDirectory: caches)
let frame: CGImage = try await previews.thumbnail(at: 42 * 60)Platform floors are iOS 16, tvOS 17, macOS 14 and visionOS 1. FFmpeg arrives through MPVKit’s dynamic xcframeworks, so a host that already ships MPVKit adds no new binary dependencies. If your CI resolves dependencies for you, pin an exact version and keep the committed Package.resolved in step with it — otherwise a device build and a TestFlight build can quietly run different engines.
Just want to watch something?
That was the developer half of the page. If you are here to watch your own films and shows, PrismCore is already doing its job inside Aether — you never have to think about it.
See AetherPrismCore is built by Václav Zmrhal · zmrhal.cz