Skip to content
edde746Public

About

Unified mpv build and patch pipeline for Plezy: per-commit content-addressed libmpv binaries for Apple, Android, Linux and Windows

Resources

Stars

6 stars

Watchers

1 watching

Forks

Repository files navigation

mpv-build

mpv ffmpeg license

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.

Layout

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.

Using it

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.

Updating

  1. A change lands on main (from a pull request; ci.yml runs the patch checks, the host regression tests, the android build and the Demo apps).
  2. publish.yml computes each group's keys, builds only what is not published (keys.py stale), uploads to the group's rolling release, and records the assets.
  3. It then commits artifacts.json and Package.swift to main as chore: 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.
  4. A consumer moves its pin to the binaries commit. Tortuga does it with tools/set_native_revision.sh <sha|latest>, which rewrites its mpv-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 tree

A 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.

Building locally

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 build

Output 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/release

See 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.sh

Without 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.

Patches

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 apply

fetch-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.

Tests

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

Debug symbols

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 zip

Licence

FFmpeg 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.

Licence texts

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.py is a driver of those groups, so changing it moves their keys.
  • Apple: most XCFrameworks are prebuilts that carry no text, so licenses.py apple fetches every Apple component at the commit it was built from (a prebuilt's upstream in versions.json), adds rust-std from the toolchain toolchain/apple.txt pins, and writes Sources/_MPVKit/licenses/, which is committed. CI's Apple licence texts job fails if it differs. After changing an Apple pin, an upstream or 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

About

Unified mpv build and patch pipeline for Plezy: per-commit content-addressed libmpv binaries for Apple, Android, Linux and Windows

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages