iOS music player app built with SwiftUI + AVAudioEngine
  • Swift 92.3%
  • Python 3.8%
  • JavaScript 1.8%
  • Shell 1.1%
  • HTML 1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Alexander Pecheny 7f07176599 Give the Mac album page room to breathe
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>
2026-07-28 15:52:36 +02:00
design/icon Icon: lighten gradient mid (#ff23a7 -> #ff3e8a) 2026-07-10 22:44:49 +02:00
docs Declare privacy, and say plainly that nothing is collected 2026-07-28 00:41:44 +02:00
MuonGapless Gapless: say which seams the repair would actually close 2026-07-12 20:04:31 +02:00
MuonPlayer Give the Mac album page room to breathe 2026-07-28 15:52:36 +02:00
MuonPlayerMac Give the Mac album page room to breathe 2026-07-28 15:52:36 +02:00
MuonPlayerTests Pick the artwork accent by luminance, from several candidates 2026-07-26 19:14:03 +02:00
scripts Archive and upload to TestFlight from one script 2026-07-28 00:41:58 +02:00
Vendor/FFmpeg Scope scan-progress observation; add wavpack/APE decoders 2026-07-09 15:14:56 +02:00
.gitignore Ignore design/icon/icon.png (rendered by deno task icon) 2026-07-09 16:25:03 +02:00
CLAUDE.md Archive and upload to TestFlight from one script 2026-07-28 00:41:58 +02:00
LICENSE Add open-source licensing: MIT license, FFmpeg LGPL notices, in-app About 2026-07-04 18:57:09 +02:00
project.yml Archive and upload to TestFlight from one script 2026-07-28 00:41:58 +02:00
README.md Keep the Last.fm password out of the shipped binary 2026-07-28 00:41:37 +02:00
THIRD-PARTY-NOTICES.md Add open-source licensing: MIT license, FFmpeg LGPL notices, in-app About 2026-07-04 18:57:09 +02:00

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:

  1. FFmpeg xcframeworks in Vendor/FFmpeg/ — built by scripts/build-ffmpeg.sh (requires Xcode command-line tools; clones and compiles FFmpeg release/7.1).
  2. MuonPlayer/Secrets.swift — generated by scripts/gen-secrets.sh from a local .env file 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.

macOSLibrary → 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.