Video player built in Swift using mpv and ffmpeg for MacOS, supports local and torrent playback
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗macmpv is a native macOS video player built with SwiftUI. It embeds mpv for playback and uses ffprobe (from FFmpeg) to read media metadata.
Website: https://macmpv.pages.dev — download the latest release.
.magnet, .torrent, and magnet links through WebTorrent CLI, including multi-file torrentsmpv and ffmpeg with pkg-confignpm install -g webtorrent-cli)Verify your toolchain:
xcode-select -p
swift --version
pkg-config --modversion mpv # should print 2.x
brew install mpv ffmpeg pkg-config node
npm install -g webtorrent-cli
make app && open dist/macmpv.app
brew install mpv ffmpeg pkg-config node
npm install -g webtorrent-cli
If pkg-config --modversion mpv fails, ensure Homebrew's pkg-config is on your PATH:
brew --prefix
export PKG_CONFIG_PATH="$(brew --prefix)/lib/pkgconfig:$PKG_CONFIG_PATH"
Anything mpv / FFmpeg can play. Common local extensions:
mp4, m4v, mov, mkv, mka, webm, avi, flv, ts, mts, m2ts, mpeg, mpg, vob, wmv, asf, divx, f4v, rm, rmvb, 3gp, 3g2, ogv, ogm, ogg, oga, opus, mp3, m4a, aac, ac3, dts, flac, alac, ape, aiff, caf, wav, wma plus m3u/m3u8 playlists.
Right-click a local queue item or collection to move its files to Trash. Clear Queue removes the queue entries without moving files.
Network streams: http, https, rtmp, rtsp, and magnet. A .magnet file must contain a valid magnet: URI; binary .torrent files can be opened directly. After torrent metadata resolves, every supported video or audio file is added to the queue in natural filename order while retaining its original WebTorrent index for playback. The first file starts automatically, and selecting any row streams that file to the embedded player over localhost; non-media extras are hidden. Torrent media is saved under ~/Movies/macmpv/Torrents/<info-hash>/. Completed files and partial downloads survive app shutdown and are verified and reused when you reopen the same torrent. The currently selected video gets all available download capacity; once it finishes, the remaining supported media files download in natural filename order. Selecting another video immediately changes download priority within the same connection. Once every queued video from the torrent is saved, macmpv stops sharing it and leaves the swarm; playback continues from the saved files. The queue sidebar shows each torrent video's download percentage, or LOCAL once it is fully saved and plays from disk; the helper keeps this progress in .metadata/<info-hash>.progress.json so it is shown for torrents that are not running too. Files the helper has verified as saved play straight from disk, like any local file, so switching to another collection does not wait for WebTorrent to re-verify the torrent; the helper still runs in the background when other queued files from that torrent need downloading. Files it has not reported, such as downloads from older versions, are measured on disk: WebTorrent writes only verified pieces into sparse files, so the space a file occupies is how much of it is saved. Downloads stop when the app closes or you leave the torrent; the helper also exits on its own if macmpv quits unexpectedly. Right-click a torrent file or collection to move its downloads to Trash; removing a collection also moves its cached metadata and local .torrent or .magnet source to Trash. Use Show Torrent Downloads in the queue menu to inspect the files. URL schemes and extension checks are defined in Sources/Cinewave/MediaModels.swift.
| Shortcut | Action |
|---|---|
⌘ O | Open media files |
Space | Play / Pause |
← / → | Back / Forward 10 seconds |
↑ / ↓ | Volume up / down |
⌘ ← / ⌘ → | Previous / Next item in queue |
M | Mute / Unmute |
[ / ] | Decrease / Increase playback speed |
\ | Reset playback speed to 1× |
⇧ ⌘ O | Open external subtitle file |
⇧ ⌘ S | Save screenshot |
⌘ ⌥ S | Show / Hide queue sidebar |
Double-click | Toggle full screen |
Click | Play / Pause (when media loaded) |
Esc | Exit full screen (system) |
Menu commands are defined in Sources/Cinewave/CinewaveApp.swift.
# debug build and run
swift run macmpv
make run # same as above
# with media
swift run macmpv -- /path/to/video.mp4
| Command | What it does |
|---|---|
make build | swift build (debug) |
make app | swift build -c release + assembles dist/macmpv.app |
make clean | swift package clean |
Build the app bundle:
make app
open dist/macmpv.app
The local app bundle links to the Homebrew libmpv installation. Keep mpv and
FFmpeg installed while using macmpv.
You can also build directly:
swift build -c release
zsh Scripts/build-app.sh release
open dist/macmpv.app
make dmg
Scripts/package-dmg.sh bundles libmpv, its full Homebrew dependency closure,
and ffprobe inside the app (Contents/Frameworks + Contents/MacOS/ffprobe),
rewrites all load commands to @rpath, re-signs, and packs
dist/macmpv-<version>-<arch>.dmg. The bundled app runs without Homebrew
installed, on the same architecture and macOS 26 or newer. WebTorrent CLI stays
optional and external — torrent playback still requires it on the target Mac.
A "+ Torrents" variant also bundles a node runtime and WebTorrent CLI. It is larger than the standard build, but torrent playback works with nothing installed:
make dmg-torrents # dist/macmpv-<version>-<arch>-torrents.dmg
The npm tree is pruned (docs, type definitions, foreign-platform native
prebuilds) and lives in Contents/Resources, where codesign seals it as data;
the node launcher sits in Contents/Helpers. The app prefers the bundled
runtime automatically when present, and falls back to an installed CLI.
The dmg is ad-hoc signed. On another Mac, Gatekeeper will challenge the first launch because the download has no verified developer signature: open System Settings → Privacy & Security and click Open Anyway, or clear the quarantine attribute:
xattr -dr com.apple.quarantine /Applications/macmpv.app
For friction-free distribution, sign with a Developer ID certificate and notarize before distributing:
export MACMPV_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)"
make dmg
xcrun notarytool submit dist/macmpv-*.dmg --keychain-profile <profile> --wait
xcrun stapler staple dist/macmpv-*.dmg
A static marketing/download site lives in site/ (dark glass theme, no
dependencies, plain HTML/CSS/JS), deployed at https://macmpv.pages.dev.
It is ready for Cloudflare Pages:
siteDownload buttons point at GitHub Releases, each pinned to its exact tag
(releases/download/vX.Y/... and releases/download/vX.Yt/... for the
"+ Torrents" build). Both DMGs exceed Cloudflare Pages' 25 MB static asset
limit, so binaries must be hosted on GitHub Releases (or R2), not in the Pages
project.
Releases are automated: make release (or make release VERSION=1.3 to bump
Info.plist first — the bump is committed so the tag points at the version
built) builds both dmgs, patches the site's download URLs, sizes, SHA-256, and
the version and release-notes link under the download button,
and publishes both GitHub releases (vX.Y and vX.Yt) via the gh CLI — see
Scripts/release.sh. It refuses a dirty working tree, pushes the exact source commit, and explicitly targets that commit for both tags
and leaves the site changes for you to commit and push, which deploys them via
Cloudflare Pages.
Packaged builds update themselves with Sparkle.
At launch and once a day, the app reads its variant's feed
(https://macmpv.pages.dev/appcast.xml, or appcast-torrents.xml for the
"+ Torrents" build), downloads a newer DMG from GitHub Releases in the
background, and installs it when the app quits. Check for Updates… and
Install Updates Automatically live in the macmpv menu. Development builds
(swift run, make app) have no feed and never update themselves;
MACMPV_UPDATE_FEED=<url> points any packaged build at a test feed.
Every update must carry an EdDSA signature matching SUPublicEDKey in
Resources/Info.plist, so a replaced GitHub asset alone cannot ship code.
The private key lives in the login keychain under the account macmpv
(created with Sparkle's generate_keys --account macmpv). make release
signs both DMGs with it, writes the two feeds into site/, and gives each
release a unique build number (CFBundleVersion = date and time), which
Sparkle uses to decide what is newer. Back up the key somewhere safe; without
it, existing installs cannot accept new updates:
.build/artifacts/sparkle/Sparkle/bin/generate_keys --account macmpv -x macmpv-sparkle-key.txt
Never commit that file. Versions before 1.8 have no updater, so their users install 1.8 manually once.
sandbox-exec: sandbox_apply: Operation not permitted
You are in a restricted sandbox (Muse sandbox, some CI). Workaround:
swift build --disable-sandbox
swift build -c release --disable-sandbox
swift build -c release --disable-sandbox --show-bin-path
# or for the app bundle:
swift build -c release --disable-sandbox && zsh Scripts/build-app.sh release
# Scripts/build-app.sh will also use --disable-sandbox automatically if needed
Do not use --disable-sandbox for normal local builds outside a sandbox.
pkg-config / mpv not found
brew reinstall mpv pkg-config
pkg-config --modversion mpv
App opens but video is black / no audio
Ensure mpv still installed: brew list mpv and otool -L dist/macmpv.app/Contents/MacOS/macmpv should show /opt/homebrew/opt/mpv/lib/libmpv.2.dylib.
No video in a VM or headless session
macmpv prefers an accelerated OpenGL 3.2 context and falls back to a software renderer when that is all the system offers. If no OpenGL surface can be created at all, the player shows an in-app error instead of crashing; playback is not possible in that configuration.
macmpv wraps libmpv via Sources/CMPV (pkg-config mpv) and renders with MPVEngine / MPVGLView (Sources/Cinewave/MPVEngine.swift, Sources/Cinewave/VideoSurface.swift). Metadata is probed out-of-process with ffprobe (Sources/Cinewave/MediaProbe.swift).
Playback decodes with VideoToolbox straight into GPU textures (hwdec=auto-safe)
and sets video-timing-offset=0, so mpv hands over each frame at its display time
and rendering never waits on the main thread; the view's display link paces frames.
Position updates from mpv are coalesced to four per second unless a seek or another
property changes. Scrubbing coalesces fast-seek previews (throttled to ~180 ms, absolute+keyframes) while dragging the slider, then performs an exact seek on release — see PlayerModel.previewSeekInterval and MPVEngine. Natural EOF is consumed once from mpv’s eof-reached property, including when the last frame stays visible. Queue operations and persistence live in PlayerModel+Queue.swift and PlayerModel+Persistence.swift. Resume positions keep the 500 most recently watched files (older undated entries migrate automatically), and markers are written only when they change. Metadata probes run at most two child processes at once, with blocking pipe reads isolated from Swift concurrency workers. Torrent requests verify their identity after asynchronous reads so canceled work cannot stop a newer helper.
Teardown drains the dedicated mpv event queue before destroying the handle, and subtitle clearance is recomputed from the letterboxed video rect and the measured height of the controls bar.
Seek-bar hover previews use a separate silent libmpv decoder, leaving playback position and audio untouched. While the cursor moves, each preview seeks to the keyframe at or before the hovered time, which decodes a single frame (about 15 ms for 1080p H.264 and 50 ms for 4K HEVC). Once the cursor rests for 300 ms, the exact frame is decoded and replaces it; any movement cancels that work so it never delays the next preview. An 80 ms delay applies only when entering the seek bar; subsequent movement requests frames immediately, without another delay. Cached previews activate immediately, including on re-entry. The decoder stays open for repeated seeks, including after a canceled request, and caches up to 120 thumbnails per source. Cursor movement coalesces pending requests; the last image and its timestamp remain visible until the next frame is ready. Before the first frame arrives, or when a preview is unavailable, the popover shows only the timestamp. Leaving the bar cancels pending work, and the decoder closes after five seconds idle or when media changes. Torrent previews read through the existing local stream, which reuses verified downloaded pieces. Read-ahead is disabled and waits are bounded; parts that have not downloaded yet may still take time. Temporary images are removed after extraction. No extra executable is required.
libmpv)ffprobe for metadata probing (ffmpeg Homebrew package)Run make test for Swift regression tests and static website validation. The Swift
suite covers EOF and repeat modes, stop/error handling, queue persistence, resume
position migration and pruning, stale
torrent callbacks, large stderr output, and concurrent metadata timeouts. Process
tests use local fake helpers and do not contact torrent peers or remote media.
Seek-preview tests decode a generated red/green/blue video fixture and check
timestamps, cache reuse, hover cancellation, and stale results after a file switch.
They also check decoder reuse, backward seeking, coalescing cursor movement, and
keyframe versus exact previews on a single-keyframe fixture, including canceling an
exact decode when the cursor moves.
They also use a loopback HTTP fixture to check byte-range seeking, non-seekable
sources, stalled reads, and timeouts without contacting external servers.
The website preserves its static Cloudflare Pages deployment: pushing main
publishes site/. Its native download picker and FAQ remain usable without
JavaScript; the layout adapts to small screens and respects reduced motion.
Run make test-torrents with the bundled torrent app present to test real downloads against a local web seed, priority switching, and offline reuse after restart. These tests do not contact public peers. Set MACMPV_WEBTORRENT_MODULE to a WebTorrent index.js and TORRENT_NODE to a compatible Node executable to use external dependencies.
Packaged apps build mpv 0.41.0 with the upstream CoreAudio initialization fix for
mpv#18274. Without this fix,
connecting or disconnecting Bluetooth audio devices after a failed audio output
initialization can crash the app. Packaging requires meson and ninja
(brew install meson ninja) in addition to the existing mpv dependencies.
See the backport notes. macOS 27 also uses interleaved
float audio to avoid CoreAudio rejecting planar output. The minimum macOS version
remains 26.
Run make test-coreaudio to build the patched library and inject a silent audio
initialization failure, checking that device-change callbacks are cleaned up.
zsh Scripts/test-coreaudio.sh /path/to/libmpv.2.dylib tests a specific library.
This regression does not connect, disconnect, or change the default audio device.
Development runs using Homebrew's libmpv directly do not include the backport.