A music player for the Mac that opens and plays. Point it at a folder, it reads the tags, and you listen. No account, no cloud, no store, no settings screen.
Written in Swift, GPLv3, fully offline, no proprietary services. The
audio stack, the media keys and the Now Playing integration all come
straight from the system. There is an Android app with the same idea in
android/, written in Kotlin on top of the platform's own media stack;
see Android below.
This repository holds CrateCore, the UI-free Swift package, and the
Crate app on top of it. The app works end to end against a real
collection: pick a folder, browse the album grid with covers, search,
mark favorites, play with gapless transitions, and control it from the
media keys and Control Center. Version 0.1.0 is the first release; see CHANGELOG.md
for what is in it.
Each release on the Releases page ships one universal DMG (Apple silicon and Intel) for macOS 12 Monterey or later, and an APK for Android 10 or later. Open it, drag Crate to Applications, and start it once via right-click > Open: the app is ad-hoc signed, there is no Developer ID behind it, and Gatekeeper asks only that first time. Then pick a folder, or choose Crate > Use Crate for Audio Files so that double-clicking a music file in Finder opens Crate.
What the package does:
- Scan a folder recursively for audio files (
LibraryScanner) - Read tags and duration with AVFoundation (
AVFoundationMetadataReader) - Keep an index of folders and tracks, persisted as one JSON file in
Application Support, with incremental rescans: unchanged files are not
re-read, removed files are dropped (
Library,JSONLibraryStore) - Group albums by folder and loosely compared tags (
Album): a compilation without album artist, a title spelled two ways or a stray ";" in one artist tag does not split a folder into several albums, and untagged tracks are named after their folder - Queue tracks with repeat, shuffle, play-next and removal (
PlayQueue) - Play through
AVAudioEngine, with the next track scheduled behind the current one for gapless albums (AudioPlayer,AVAudioEngineBackend;AVAudioPlayerBackendis the simpler fallback) - Find covers embedded in the files or lying next to them and cache
them as thumbnails (
ArtworkReader,ArtworkCache) - Search titles, artists, albums, composers and genres (
SearchQuery) - Level loudness from ReplayGain or iTunes Sound Check tags, limited by
the peak, through a gain stage separate from the volume (
ReplayGain) - Favorites as a set of track paths in a small file of its own, so
a tap on a heart never rewrites the index (
Favorites)
Formats are whatever AVFoundation decodes: MP3, AAC/M4A (including M4B audiobooks), FLAC, ALAC, WAV, AIFF, CAF. Ogg Vorbis and Opus are not supported, because the system cannot play them.
The tag reader goes through AVFoundation's common keys, then ID3 and
iTunes identifiers (including iTunes' binary genre index and the "(17)"
style ID3v1 references). FLAC is parsed by Crate itself
(FLACMetadata: STREAMINFO, Vorbis comments and embedded pictures),
because AVFoundation does not expose Vorbis comments on every macOS
version. Checked against real MP3, M4A and FLAC files; reports with a
sample file for anything that reads wrong are welcome.
swift build
swift testBuilding needs Swift 6 and Xcode (the Command Line Tools alone lack the SwiftUI and Swift Testing macro plugins). The app runs on macOS 12 Monterey and later, on Apple silicon and Intel.
To build the app itself:
scripts/bundle.sh # -> build/Crate.app, ad-hoc signed
open build/Crate.app
scripts/dmg.sh --universal # -> build/Crate-<version>.dmg, what a release ships
scripts/install.sh # -> /Applications/Crate.app, registered with Launch ServicesCrate > Use Crate for Audio Files binds every audio type (MP3, M4A, FLAC, WAV, AIFF, ...) to the running copy, which should be the one in /Applications. A file opened from Finder then plays its album from that track if the file is in the library, or just the opened files if not; a folder dropped on the app is added to the library.
Releases are cut by pushing a v* tag that matches the version in
Packaging/Info.plist and in android/gradle.properties; the workflow
builds the DMG and the signed APK and takes the release notes from
CHANGELOG.md.
To see what the tag reader makes of a real folder:
swift run crate-scan ~/Music/Music/Media.localized # summary + tracks with gaps
swift run crate-scan <folder> --all # every trackandroid/ holds the Android app: the same product, the same album
logic, no shared code with the Mac app (there is nothing to share across
Swift and Kotlin but the ideas). Pick a folder with the system's folder
picker, Crate reads the tags where the files are and plays them through
the system's media session, so the lock screen, headsets and Bluetooth
controls work. Formats are what Android decodes: MP3, AAC/M4A (including M4B
audiobooks), FLAC, WAV, Ogg Vorbis, Opus. Runs on Android 10 and later. MP3, M4A and FLAC tags
are read by Crate's own parsers, a few kilobytes per file, so a scan of a
few thousand files takes seconds; Ogg and Opus go through the platform.
android/core/ UI-free Kotlin library: Track, Album grouping, Library with
incremental rescans, JSON store, FLAC parser, search,
favorites (JVM tests)
android/app/ Jetpack Compose UI, Media3/ExoPlayer playback service,
Storage Access Framework file source, cover cache
cd android
./gradlew :core:test # the library's tests, no emulator needed
./gradlew :app:assembleDebug # -> app/build/outputs/apk/debug/app-debug.apk
./gradlew :app:assembleRelease # unsigned unless the signing env vars are setBuilding needs JDK 17 or later and the Android SDK with platform 37
(ANDROID_HOME or android/local.properties). The scripts/icon.sh
run that draws the Mac icon also writes the Android launcher icon.
Sources/CrateCore/ the package
Sources/Crate/ the SwiftUI app
Sources/crate-scan/ developer tool, not part of the app
Tests/CrateCoreTests/
Packaging/ Info.plist (the one place the macOS version lives) and AppIcon.icns
android/ the Android app, see above; its version lives in
android/gradle.properties
scripts/ bundle.sh (build Crate.app), dmg.sh (release DMG), install.sh
(copy to /Applications and register), icon.sh/icon.swift
(regenerate the icon), screenshot.sh (dev aid)
Design rules the code follows:
- The package has no UI and no global state.
Libraryis an actor,PlayQueueis a value type, everything else is a plain struct or a protocol. - Everything with I/O sits behind a protocol (
MetadataReader,ArtworkReader,LibraryStore,AudioPlayer), so tests run with fakes and the app with the AVFoundation implementations. - Defaults over options. If something can be decided for the user, it is.
GPL-3.0-or-later, see LICENSE.
See AI_POLICY.md for how AI tooling is used in this project,
PRIVACY.md for what the app does with your data (nothing leaves your
device) and THIRD-PARTY-NOTICES.md for dependencies (none beyond the
system frameworks on the Mac; Kotlin, AndroidX and Media3 on Android).