Patched libmpv and FFmpeg binaries for the apps Plezy and Tortuga ship, built
from one set of pins and patch series:
| Platform group | Artifact | Release |
|---|---|---|
| apple | XCFrameworks for iOS, tvOS, macOS, Mac Catalyst and the iOS/tvOS simulators, as the SwiftPM product MPVKit |
binaries-apple |
| android | libmpv-android-<key>-<abi>.tar.gz per ABI, and the mpv-android-<key>.aar library |
binaries-android |
| linux | libmpv-linux-<key>-<arch>.tar.zst (x86_64, aarch64) |
binaries-linux |
| windows | libmpv-windows-<key>-<arch>.zip (x86_64, aarch64), the mpv-dev package shape |
binaries-windows |
Every push to main publishes the binaries that commit needs, so any commit on
main can be pinned.
| Path | Purpose |
|---|---|
versions.json |
Every component pin (version, URL, hash or commit), with per-platform overrides; a prebuilt's upstream names the source commit it was built from. |
patches/<component>/ |
Patch pool and series files; see Patches. |
toolchain/<group>.txt |
Toolchain pins and the generation that forces a group's full rebuild. |
artifacts.json |
The published binaries, one section per platform group. Written by CI. |
Package.swift |
SwiftPM manifest, rendered from the apple section of artifacts.json. Written by CI. |
Sources/BuildScripts/ |
The Apple build driver (make build). |
Sources/_MPVKit/licenses/ |
The licence texts of everything the Apple package links, generated by scripts/licenses.py apple. |
platforms/<group>/ |
The android, linux and windows drivers, each defined by its group.json. platforms/android/lib/ is the mpv-android library. |
scripts/ |
patches.py (series), keys.py (content keys and the publish gate), licenses.py (licence texts), symbols.py (debug symbols), extract.py (code slicing for tests), and the regression tests. |
docs/ |
Apple and Android details. |
SwiftPM. Add https://github.com/edde746/mpv-build.git and pin a commit (in
Xcode, Branch/Commit → the SHA). The product is MPVKit. Semver tags
exist but carry no assets of their own. Consumer notes (pkg-config, AVPlayer
passthrough, spatial audio): docs/apple.md.
Android AAR. Resolve edde746:mpv-android:<key>@aar through an Ivy
repository on the release URL; the key and sha256 come from artifacts.json.
Setup, API and helpers: docs/android.md.
Linux and Windows. Download the asset named in artifacts.json
(platforms.<group>.libraries.libmpv-<group>.prebuilt.<arch>) from
assetBase and check its sha256. The Windows zip has libmpv-2.dll,
libmpv.dll.a, include/mpv/*.h and licenses/ at its root
(platforms/windows/README.md). The Linux tarball
holds lib/, include/ and licenses/, built in the container
toolchain/linux.txt pins (glibc 2.39).
Pinning a commit. A consumer pins one commit of this repository and reads
that commit's artifacts.json for every non-SwiftPM asset. Assets are
content-addressed: the 12-character key in each name is derived from the
component pins, the resolved patch series, the build flags, the platform
tuple, the build driver and the toolchain file. A published asset never
changes.
Known issue for pins. From the commit that adds mpv patch 0142 (Android: queue the subtitle plane as early as the video) onwards, MediaTek Android TV boxes such as the Google TV Streamer show subtitles one refresh ahead of the picture on HDMI. The cause is the video plane's own lag on that chip (plezy#2894), which 0142 no longer masks; it stays until that lag is fixed here.
- A change lands on
main(from a pull request;ci.ymlruns the patch checks, the host regression tests, the android build and the Demo apps). publish.ymlcomputes each group's keys, builds only what is not published (keys.py stale), uploads to the group's rolling release, and records the assets.- It then commits
artifacts.jsonandPackage.swifttomainaschore: binaries for <sha> [skip binaries]. That binaries commit is the one to pin: the commit before it has no manifest entries for its new assets. - A consumer moves its pin to the binaries commit. Tortuga does it with
tools/set_native_revision.sh <sha|latest>, which rewrites itsmpv-build.lock.json, Gradle verification metadata and Apple project.
python3 scripts/keys.py keys --platform-group apple # what this tree needs
python3 scripts/keys.py stale --platform-group apple # what is not published yet
python3 scripts/keys.py verify --platform-group apple # manifest matches this treeA change to patches/mpv/* moves libmpv's key alone; editing a group's build
driver moves every key in that group. To force a rebuild without a source
change (a new Xcode, a miscompile), bump generation in that group's
toolchain/<group>.txt. check_version.yml opens weekly pull requests that
move the mpv and FFmpeg pins (and the badges above) and the prebuilt
dependencies to new upstream tags. release.yml tags a commit whose
Package.swift passes verify; it builds nothing. Each pin's provenance in
versions.json says why it is pinned; Windows' fontconfig pin is temporary
and goes once winbuild rebases its fontconfig patches.
Apple (macOS, Xcode selected with xcode-select, rustup):
rust_version="$(sed -n 's/^rust=//p' toolchain/apple.txt)"
rustup toolchain install "$rust_version" --profile minimal --no-self-update \
--component rustc,cargo,rust-std \
--target aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios,aarch64-apple-tvos,aarch64-apple-tvos-sim
make build # every platform
make build platform=ios,macos # ios,macos,tvos,tvsimulator,isimulator,maccatalyst,xros,xrsimulator
make build libs=libmpv use-prebuilt # compile libmpv, restore the rest from published binaries
make help
make clean
./mpv.sh [url] # the mpv player from a macOS buildOutput lands in dist/. Repeat the rustup step when toolchain/apple.txt's
rust pin changes.
Android:
bash platforms/android/include/download-sdk.sh
bash platforms/android/download.sh
bash platforms/android/build.sh --arch arm64
bash platforms/android/package.sh --arch arm64
bash platforms/android/lib/build-aar.sh --tarballs dist/releaseSee docs/android.md for the prerequisites and rebuild rules.
Linux (docker; the published path, in the container toolchain/linux.txt pins):
KEY="$(python3 scripts/keys.py keys --platform-group linux \
| python3 -c 'import json, sys; print(json.load(sys.stdin)["libmpv-linux"])')"
KEY="$KEY" ARCH=x86_64 bash platforms/linux/container-build.shWithout KEY it builds but does not package; the script's header lists its
other variables.
Windows (Linux host): platforms/windows/build.sh <x86_64|aarch64> then
platforms/windows/package.sh <x86_64|aarch64>; host requirements are in
platforms/windows/README.md.
Each patched component (mpv, ffmpeg, libass, libplacebo, libdovi) keeps one platform-neutral pool and per-platform series files. Line order is the application order, and each patch's header explains it.
patches/<component>/pool/<name>.patch the patch bytes
patches/<component>/series.common applied on every platform, first
patches/<component>/series.<platform> applied after series.common
python3 scripts/patches.py check # pools, series and versions.json agree
python3 scripts/patches.py resolve mpv apple # one platform's ordered series
python3 scripts/patches.py apply mpv apple <srcdir> --check # the series still applies
python3 scripts/patches.py fetch-pinned mpv apple <srcdir> # fetch the pinned source and applyfetch-pinned checks a git pin's commit or an archive pin's sha256 before
applying. ci.yml applies every nonempty series to its pinned source, so a
version bump that breaks a patch fails before anything builds.
Run any of them with bash scripts/<script>. A harness that tests patched code
fetches the pinned source and applies the series with patches.py fetch-pinned; pass an already-patched source tree as the first argument to run
offline (two trees, mpv then FFmpeg, where both are compiled). Each script's
header says what it covers. "PR" is ci.yml, "main" is publish.yml.
| Script | Guards | CI |
|---|---|---|
test_patches.sh |
patches.py: series rules, check, apply, archive fetching |
PR, main |
test_extract.sh |
extract.py: slicing and hunk parsing over the whole pool |
PR, main |
test_keys.sh |
keys.py: content keys, stale, verify, recording and publishing |
PR, main |
test_symbols.sh |
symbols.py: ELF, PE and PDB identities, matching, reproducible zips |
PR, main |
test_apple_merge.sh |
merge_xcframeworks.py writes byte-identical zips twice (macOS) |
main |
test_build_date.sh <asset>... |
no file in a release asset carries its build's clock; every build leg runs it before upload | main, android.yml |
test_build_process.sh |
the Swift driver's process launch: output capture and exit codes | main |
platforms/linux/test.sh |
the linux driver's build plan and configure flags, all tools stubbed | PR |
platforms/windows/test_pin_packages.py (python3) |
pin_packages.py rewrites winbuild's packages onto the pins |
PR |
test_ao_avfoundation_fallback.sh |
AVPlayer sink: PCM fallback, no-resource-request log, clock anchor; the test drives the clock and feed ticks (macOS) | PR |
test_ao_compressed_audio_option.sh |
the compressed-audio option contract (patch 0019) | main |
test_ao_pcm_lookahead.sh |
bounded PCM lookahead (patch 0015) | main |
test_ao_probe_refusal.sh |
the compressed-audio loader refuses reads past the write head | main |
test_ao_spatial.sh, test_spatial_*.sh |
spatial audio; see docs/apple.md | PR, main |
test_avf_dovi_profile5_rpu.sh |
which frames vo_avfoundation uploads with their Dolby Vision RPU | main |
test_avf_host_present.sh |
host-mode frame stamps after a seek, and when a reset re-seeds the timebase | main |
test_avf_osd_ahead.sh |
how long flip_page may hold video for its subtitles | main |
test_avf_osd_layer_geometry.sh |
the inline OSD layer's placement (macOS) | main |
test_avf_pip_frame_space_osd.sh |
frame-space OSD geometry for PiP | main |
test_avf_timebase_servo.sh |
the timebase servo and rate decisions | main |
test_audio_passthrough_fallback.sh |
the player's AO failure handling and PCM fallback (patch 0040) | PR |
test_audio_reload_held_frame.sh |
an AO reload with a held frame still opens the next AO (patch 0034) | PR, main |
test_audiotrack_timing.sh |
AudioTrack clock, writes, passthrough, recreate retry, stream silence, pause | PR, main |
test_audiotrack_truehd.sh [media] |
raw TrueHD reassembly from IEC 61937 MAT bursts (needs host ffmpeg) | PR |
test_av_log_routing.sh |
FFmpeg log lines reach the mpv instance that logged them (patch 0209) | PR, main |
test_demux_codec_profile.sh |
demuxer-published codec profile matches the decoder's (patch 0204) | PR, main |
test_demux_refresh.sh |
a refresh seek queues nothing twice and keeps the next subtitle (patch 0208) | PR, main |
test_lavc_busy_policy.sh |
decoders that wake the filter themselves are not spun | PR, main |
test_libass_series.sh |
the libass series: change detection, renderer handoff, shape cache | main |
test_libass_rounding.c |
ARMv7 VFP rounding; cross-compile and run on an ARMv7 device (header) | manual |
test_mediacodec_crop.sh |
MediaCodec output crop parsing | PR, main |
test_mediacodec_dv_filter.sh |
the Dolby Vision access-unit filter (patch 0101) | main |
test_mediacodec_eos.sh |
end of stream through the async decoder after a flush | PR, main |
test_mediacodec_error.sh |
codec errors stay the decoder's answer across seeks | PR, main |
test_mediacodec_flush.sh |
when a flush reaches the codec | PR, main |
test_mediacodec_geometry.sh |
vo_mediacodec geometry (patch 0100) | main |
test_mediacodec_input.sh |
an access unit too large for its input buffer is dropped whole | PR, main |
test_mediacodec_letterbox.sh |
the OSD plane's letterbox fill | PR, main |
test_mediacodec_osd.sh |
the shared subtitle render-ahead pipeline (patch 0031) | main |
test_mediacodec_paused.sh |
a paused decoder is not polled and never holds the decode thread | PR, main |
test_mediacodec_rate.sh |
the media3_rate operating-rate option |
PR, main |
test_mediacodec_rebind.sh |
OSD surface retirement without rebuilding the decoder | PR, main |
test_mediacodec_rendered.sh |
the rendered-frame feedback ring | PR, main |
test_mediacodec_reorder.sh |
presentation-order recovery (patch 0026) | PR, main |
test_mediacodec_shed.sh |
pre-decode frame shedding for H.264, HEVC, VP9 and AV1 | PR, main |
test_mediacodec_timing.sh |
vo_mediacodec cadence, vsync snapping, release timing, present stats | PR, main |
test_mediacodec_vsync.sh |
the Choreographer vsync sampler against Android 11's behaviour | PR, main |
test_pgs_palette.sh |
PGS palettes start transparent after an epoch boundary | PR, main |
test_player_queue_frames.sh |
frame queueing and durations under vo_mediacodec | PR, main |
test_player_release_held_frame.sh |
ending a file frees the decoder before the next opens (patch 0139) | PR, main |
test_player_video_restart.sh |
restored video syncs to already-running audio (patch 0210) | PR |
test_tcp_family_lookup.sh |
per-family hostname lookup and connection race (patch 0029) | PR, main |
test_android_glue.sh |
the mpv-android JNI glue; see docs/android.md | PR, main |
The android, linux and windows groups publish one debug-symbols zip per variant beside each binary, so a crash tracker can symbolize libmpv and FFmpeg frames. Each zip holds, at its root, the debug files of what that variant ships, matched by identity (ELF GNU build-id, or a DLL's CodeView GUID and age), never by name.
| Group | Asset, per variant | Contents |
|---|---|---|
| android | mpv-android-<key>-<abi>-symbols.zip |
a debug ELF for every .so in the AAR's jni/<abi>/ (libc++_shared.so only when the NDK has its unstripped copy) |
| linux | libmpv-linux-<key>-<arch>-symbols.zip |
a debug ELF for every shared library in the tarball |
| windows | libmpv-windows-<key>-<arch>-symbols.zip |
libmpv-2.pdb |
They are recorded under the artifact's symbols entry in artifacts.json, and
keys.py treats an entry without every variant's symbols as unpublished.
scripts/symbols.py is the only packer:
python3 scripts/symbols.py id libmpv-2.dll libmpv-2.pdb # identities, as the tracker spells them
python3 scripts/symbols.py match <shipped> <candidate>... # the matching candidate with the most debug info
python3 scripts/symbols.py pack out.zip --pair <shipped> <debug> # checked, reproducible zipFFmpeg is configured with --enable-gpl on every platform, so the binaries are
distributed under the GPL; see
FFmpeg's and
mpv's terms. This
repository is under the GPL-3.0 (LICENSE).
The drivers derive from edde746/MPVKit
(from mpvkit/MPVKit and
kingslay/FFmpegKit),
edde746/libmpv-android (from
jarnedemeulemeester/libmpv-android),
Plezy's linux/packaging, and a pinned
shinchiro/mpv-winbuild-cmake.
Their history lives in those repositories.
Every artifact carries the licence texts of each component linked into it, as
licenses/<component>/<path in that component's source>, copied unchanged from
the source the build compiled. scripts/licenses.py holds the per-component
rules (vendored subtrees, licence comments cut out as <file>.txt, the licence
files of every Rust crate linked).
| Artifact | Where | Components |
|---|---|---|
libmpv-linux-<key>-<arch>.tar.zst |
licenses/ |
the components of platforms/linux/group.json |
libmpv-android-<key>-<abi>.tar.gz |
licenses/ |
the components of platforms/android/group.json, libc++ (the NDK's) and rust-std (in libdovi) |
mpv-android-<key>.aar |
assets/licenses/, so they reach the app's APK |
the tarballs' texts (identical for every ABI), and mpv-android/: the glue's NOTICE and this repository's LICENSE |
libmpv-windows-<key>-<arch>.zip |
licenses/ |
every package linked into libmpv-2.dll, named as in versions.json where it pins one, else by its mpv-winbuild-cmake package name |
| MPVKit (SwiftPM) | MPVKit__MPVKit.bundle/licenses/ in every app (the _MPVKit target's resources) |
every versions.json component whose platforms include apple, and rust-std |
- Linux, Android and Windows collect the texts while building, and packaging
runs
licenses.py check: the artifact must hold exactly one non-empty folder per linked component. A text that moved upstream fails the build.licenses.pyis a driver of those groups, so changing it moves their keys. - Apple: most XCFrameworks are prebuilts that carry no text, so
licenses.py applefetches every Apple component at the commit it was built from (a prebuilt'supstreaminversions.json), addsrust-stdfrom the toolchaintoolchain/apple.txtpins, and writesSources/_MPVKit/licenses/, which is committed. CI'sApple licence textsjob fails if it differs. After changing an Apple pin, anupstreamor a rule, regenerate it (needs git, network and that toolchain through rustup):
python3 scripts/licenses.py apple # rewrite Sources/_MPVKit/licenses/
python3 scripts/licenses.py apple --check # what CI runs