Inspiration

Every audio app assumes the content comes from a cloud. If you actually own files — audiobooks you bought outright, FLACs you ripped, podcast archives, recordings — there has been no decent way to live with them.

Apple Music tries to sync your files to the cloud and matches the wrong version. A general file manager will play them, but has no idea what a book or a chapter is and forgets where you stopped. Most third-party players want a login, or show ads, or both.

Listenry is a tool, not a content platform. It ships no catalogue. It makes the audio you already have usable.

What it does

Turns a folder of audio files into a library you can live in.

  • Audiobook mode organises by book and chapter, including M4B chapter marks, and remembers where you stopped in every book independently
  • Music mode organises by artist and album
  • One player underneath both, with background playback, lock screen controls, headphone and Bluetooth handling, and interruption recovery
  • Speed presets with per-book memory, a sleep timer, auto-rewind when you come back
  • Search across the whole library, and smart lists for unfinished, recent and newly added
  • Import from Files, AirDrop or drag and drop; backup and restore

No account. No network requests at all.

How I built it

Native SwiftUI, iOS 18 and up. One storage and playback core shared by both modes, with the difference expressed only as organisation and playback policy, not as two separate stacks.

Three product rules settled every argument that came up:

  1. The user's files are the user's. Nothing is renamed, transcoded, uploaded, or "intelligently" deleted. Import copies into the app and leaves the original alone.
  2. Offline is not a feature, it is a premise. Any feature that would require a network does not get built. That ruled out online cover fetching and online metadata completion, which were both tempting.
  3. Audiobooks and music are two ways of listening, not two kinds of content.

RevenueCat carries the one-time unlock, and is the only third-party service in the project.

Challenges I ran into

Deciding what to refuse. The hard part of a local-first player is not the playback, it is holding the line when every missing convenience has an obvious online fix. No cover art fetching. No metadata completion. No cloud sync in v1. Each of those is a day of work and a permanent dependency, and each one breaks rule two.

Where to put the paywall took three passes, and the record is in the repo. The product plan's own feature table was wrong twice; the store listing now instructs the reader to take the split from the paywall trigger in code rather than from the plan. Search was never meant to be paid. Smart lists moved between the free and paid columns four times before the argument that settled it: hiding a list behind a button and then charging for that button are not compatible, so one of them had to give. Categories and batch filing were removed from both columns entirely rather than left ambiguous.

DRM is a wall you describe, not solve. Audible .aax and Apple Books files cannot be opened, and the honest move is to say so in the store listing before the download rather than after the one-star review.

Accomplishments that I'm proud of

The free tier is the whole app. Import, both modes, per-book progress and speed memory, M4B chapters, sleep timer, auto-rewind, search across the library, smart lists, backup and restore — all free, with no cap on library size or import count, ever. A number wall is the most resented design in this category and it is not in here.

What I learned

That writing down which features you deliberately will not build is worth more than the roadmap. The "will not do" list in the plan is what kept a local-first player local-first through eleven revisions, most of which were me trying to talk myself into one small network call.

What's next

The Android build exists and is in closed testing. Beyond that, the list is short on purpose.

Built With

  • avfoundation
  • ios
  • jetpack-compose
  • kotlin
  • media-player
  • media3
  • revenuecat
  • swift
  • swift-concurrency
  • swiftdata
  • swiftui
  • xcodegen
  • xctest
Share this project:

Updates

Submission history