- Swift 92.3%
- Python 3.8%
- JavaScript 1.8%
- Shell 1.1%
- HTML 1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Track rows are capped at 560pt and left-aligned, in one column or two, so the title and the duration stay in one glance on a wide display; the separator is pulled in to match. A second column appears only when the first overflows the window. Long titles wrap instead of truncating, as they do on iOS. Cover art is twice the size and opens in its own screen-filling window, zoomable by pinch, double-click or the button bar. A window rather than a sheet: a sheet tall enough for a cover resizes the main window under it, and it stays displaced after the sheet closes. Tag editor fields showed their label twice — a LabeledContent around a TextField that renders its own label in a Form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
| design/icon | ||
| docs | ||
| MuonGapless | ||
| MuonPlayer | ||
| MuonPlayerMac | ||
| MuonPlayerTests | ||
| scripts | ||
| Vendor/FFmpeg | ||
| .gitignore | ||
| CLAUDE.md | ||
| LICENSE | ||
| project.yml | ||
| README.md | ||
| THIRD-PARTY-NOTICES.md | ||
The main repository of this project lives at https://code.pecheny.me/pecheny/muon-player. All issues should be created there.
MuonPlayer
A gapless, FFmpeg-powered music player for iOS and macOS, with a foobar-style queue, a local SQLite library, and Last.fm scrobbling.
- Platforms: iOS 17+ (
MuonPlayer) and macOS 14+ (MuonPlayerMac) - UI: SwiftUI — a separate view layer per platform over shared audio, library and scrobbling code
- Audio: custom gapless engine over statically-linked FFmpeg (
libav*) - Project: generated with XcodeGen
from
project.yml
The two apps share everything but their views. The iOS app indexes a single folder (its Documents directory); the Mac app indexes any number of folders you add, held as security-scoped bookmarks.
Supported formats
A format is supported only if MuonPlayer can both play it and edit its tags in
place, without re-encoding the audio. Formats whose tags it cannot write
(Matroska .mka, ASF .wma, CAF .caf) are deliberately not indexed.
| Extension | Container | Tags live in |
|---|---|---|
.mp3 |
MPEG | ID3v2.4 at the head |
.aac |
raw ADTS, or MP4 — decided by the bytes, not the name | ID3v2.4, or ilst |
.m4a .mp4 .m4b .alac |
MP4 | iTunes ilst atoms |
.flac |
FLAC | Vorbis comment block |
.ogg .oga .opus |
Ogg (Vorbis / Opus) | Vorbis comment packet |
.wav |
RIFF | id3 chunk, mirrored into LIST/INFO |
.aif .aiff |
AIFF / AIFC | ID3 chunk, mirrored into NAME/AUTH |
.ape .wv |
Monkey's Audio / WavPack | APEv2 block at the tail |
Two of these carry tags in more than one place, and FFmpeg lets the native chunk
outrank the ID3 chunk on a duplicate key, so WAV and AIFF are written to both —
otherwise a stale INAM or NAME would quietly outlive an edited title. For the
same reason a trailing ID3v1 block on an APE-tagged file is folded into the APEv2
tag and removed: FFmpeg looks for the APE footer only at the exact end of the
file, so anything parked behind it hides the tag entirely.
License
MuonPlayer's own source is released under the MIT License.
It statically links a lean, audio-only build of FFmpeg (release/7.1) under
the LGPL 2.1 (built without --enable-gpl/--enable-nonfree). Full
attribution, the enabled component list, and relinking instructions are in
THIRD-PARTY-NOTICES.md. The same information is
viewable in-app under Settings → About & Licenses.
Building
Two artifacts are generated locally and are not checked into the repo:
- FFmpeg xcframeworks in
Vendor/FFmpeg/— built byscripts/build-ffmpeg.sh(requires Xcode command-line tools; clones and compiles FFmpegrelease/7.1). MuonPlayer/Secrets.swift— generated byscripts/gen-secrets.shfrom a local.envfile holding your own Last.fm API credentials.
Steps
# 1. FFmpeg (one-time; slow)
./scripts/build-ffmpeg.sh
# 2. Last.fm credentials — create .env, then generate Secrets.swift
cat > .env <<'EOF'
LAST_FM_API_KEY=your_key
LAST_FM_API_SECRET=your_secret
EOF
./scripts/gen-secrets.sh
# 3. Generate the Xcode project and build
xcodegen generate
open MuonPlayer.xcodeproj # then pick the MuonPlayer or MuonPlayerMac scheme
Re-run xcodegen generate after adding, removing or renaming any source file —
the .xcodeproj is generated and git-ignored.
.env and MuonPlayer/Secrets.swift are git-ignored. Get your own Last.fm API
key at https://www.last.fm/api/account/create. The app runs without Last.fm
credentials — scrobbling is simply disabled.
Adding music
iOS — import audio files via the Files app under On My iPhone → MuonPlayer, then Settings → Rescan Library.
macOS — Library → Add Folder to Library… (⌘O), and point it at any folder on disk. The app is sandboxed, so it can only read folders you have picked this way; each one is remembered as a security-scoped bookmark.