Skip to main content

Player app overview

The player (audiosilo-frontend) is the read side of AudioSilo. One codebase ships to a web PWA, iOS and Android. It consumes the server's JSON API through hand-mirrored types with no codegen (see the cross-repo contract), and it addresses all content by (library_id, rel_path). Most of its complexity is in playback: the whole-book timeline and the three engines.

Stack​

ConcernChoice
FrameworkExpo SDK 58, React Native 0.88 (new architecture), React 19.3
RoutingExpo Router (file-based, routes live in src/app/). iOS and Android use NativeTabs; the web uses headless expo-router/ui Tabs. See Routes and navigation and The app shell.
StylingUniwind (Tailwind v4): className on core RN components, on every platform. The web player needs Safari 16.4, Chrome 111 or Firefox 128 at least, because of Tailwind v4's CSS.
Design systemStacks (STYLEGUIDE.md, authoritative): semantic light and dark tokens, Figtree, Bricolage Grotesque and JetBrains Mono, and react-native-reusables primitives (shadcn/ui for React Native on @rn-primitives) restyled to it. See Styling and the design system.
Server stateTanStack Query (src/api/hooks.ts, provider in src/api/provider.tsx)
Client stateZustand (src/stores/, plus the playback and downloads stores)
ListsFlashList v2 (@shopify/flash-list) for the cover shelves and grids (ShelfRow, CoverGrid)
AudioA custom native Expo module, modules/audiosilo-player: AVQueuePlayer on iOS and Media3/ExoPlayer on Android. The web uses HTML5 Audio and Media Session. There is no react-native-track-player dependency, and older docs that mention it are stale.
IconsFontAwesome Pro 7 glyphs vendored as raw SVG path data in src/components/ui/icon-data.ts, drawn with react-native-svg. There is no @fortawesome/* dependency, so a build needs no private npm token.
Secretsexpo-secure-store (Keychain and Keystore) holds session tokens (src/lib/secure-store.ts). On iOS they are readable after first unlock, for CarPlay (see Native integrations). AsyncStorage holds everything else (src/lib/storage.ts).
Apple WatchA standalone SwiftUI watchOS app (targets/watch/, added by @bacons/apple-targets), with a WatchConnectivity module on the phone (modules/audiosilo-watch). See Apple Watch.
Wear OSA standalone Kotlin and Compose for Wear OS app (wear/, with its pure core in wear-core/, wired by plugins/withWearApp.js), with a Data Layer half in the same phone module. See Wear OS.
Car and widgetsCarPlay (our own CPTemplateApplicationScene code) and Android Auto (a Media3 MediaLibraryService) live in the native module. iOS widgets and the sleep timer Live Activity use expo-widgets. See Native integrations.
i18ni18next + react-i18next + expo-localization (src/i18n/). See Internationalisation.

Source layout​

src/app/ Expo Router routes: (app) with its five tab groups, connect/
onboarding, the player + finished modals, demo landing,
+html.tsx web shell, +native-intent.tsx (app links as paths),
+not-found.tsx (any unknown path)
src/api/ client.ts (typed fetch wrapper), types.ts (wire mirrors),
hooks.ts (React Query), provider.tsx (multi-connection registry),
reachability.ts (online/offline tracking), server-snapshots.ts
(each server's last /server answer, kept for an offline launch),
address-route.ts + address-runner.ts + server-id-probe.ts (home
and away addresses)
src/playback/ PlaybackService interface + per-platform engines, the player store,
book-queue (timeline math), progress-sync (offline-safe saves),
sleep-timer (+ auto-sleep, use-shake-to-extend, drift, last-
interaction), transcode (web negotiation), up-next-resolver
(what plays next), jump-undo, time-left, rate helpers
src/downloads/ offline downloads: native/web engines, registry store, the
keep-ahead planner + controller, failure classification, the
offline companion (offline-meta: community metadata kept with a
download) and the Downloads page's pure view model (a sibling of
playback, not inside it)
src/components/ ui/ (the Stacks primitives - Text, Icon, Button, Card, Input,
Dialog, Select, Tabs, Toast, Sheet... see Styling), shell/ (tab
destinations, auth gate, phone header + tab bar, top bar, sub-nav,
docked player, accessory player, sub-nav slot), layout/
(reconnect + offline banners, ContentScope, the At home and away
card), player/,
library/ (covers, shelves, grids, the Library modes, the
collection/person/series route screens), series/ (bookcase,
person pages), book/ (the book page), annotations/
(bookmark and note rows, editors, labels), journal/ (the
Journal and its export), you/ (the You hub; stats/, year/),
settings/ (the Settings panes), account/ (a server's Account
page), connect/ (onboarding), home/, search/, upnext/, downloads/
(the Downloads tab), house/ (Listening in the house), profiles/
(Who's listening?, the PIN dialog, the kids library picker,
removing a person), household/ (the Household page: On this
device, members, the add sheet, member cards), kids/
(kids mode), brand/
src/car/ CarPlay + Android Auto, the JS side: the car snapshot (car-model),
car covers, the car controller, the AudiosiloCar headless task
src/cast/ Chromecast: the route switch (routed-service), the cast engine
(cast-service), cast tokens, the native and web drivers, the
Cast button (see Chromecast)
src/reader/ the ebook reader: the screen, the web host (iframe) and the apps'
host (WebView, the book in chunks, the EPUB cache), the message
protocol, the reading-place queue (see The ebook reader)
src/widgets/ iOS widget + sleep timer Live Activity ('widget' functions) and
their sync (widget-model, widget-sync.ios)
src/watch/ the phone's side of both watches: the message model, the link
(a no-op on web), the store, Send to watch (see Apple Watch, Wear OS)
src/stores/ Zustand: session (connections + tokens), profiles (people on this
device), settings, search
(+ recent searches), series-orderings, library-selection
src/i18n/ i18next init, LanguageProvider, locale catalogs (locales/*.json)
src/theme/ tokens.json (the colour source) -> generated tokens.ts,
ThemeProvider (fonts + theme), scheme-pref (the default-theme
rule), useThemeColors (a context ThemeProvider fills)
src/lib/ storage, secure-store, paths, format, hhmm (wall-clock "HH:MM"),
ticker (one start/stop interval), pairing, known-servers, device,
base-url, layout (the three form factors), utils (cn),
storage-migration (the one launch-time storage migration),
connection-id + identity-migration (connection ids,
<server_id>.<user_id>, and their one-time key migration),
bootstrap (the launch steps, shared with the car task),
register-sw, and other pure helpers
modules/audiosilo-player/ the local Expo module (Swift + Kotlin + TS bridge)
modules/audiosilo-watch/ the phone's watch link: WatchConnectivity on iOS, the Wear
Data Layer on Android
targets/watch/ the Apple Watch app (Swift, not bundled by Metro): Core/ (Foundation
only), Net/, Storage/, Phone/, Playback/, UI/
watch-core/ SwiftPM package over targets/watch/Core, for its tests on the host
wear/ the Wear OS app, Gradle module :wear (Kotlin, Compose for Wear OS)
wear-core/ the Wear OS app's pure Kotlin/JVM rules and their JUnit tests
plugins/ config plugins (CarPlay scenes, Cast, the watch scheme, the Xcode 26
fix); withCarPlay, withCast and withWatchScheme have jest tests,
widgets-entitlements.test.ts checks that no target asks for push
and build-properties.test.ts pins the expo-build-properties options
index.ts the app entry (package.json main): registers the car's headless
task, then expo-router/entry
public/ sw.js (service worker) + manifest.json (PWA), copied verbatim
into the web export
STYLEGUIDE.md Stacks, the player's design system (authoritative for its look)

Two conventions keep this layout healthy:

  • Logic stays out of src/app/** screens. Screens compose components and hooks. Anything with behaviour worth testing lives in src/lib, src/api, src/playback, src/downloads or src/stores, where it gets a co-located unit test. The coverage config excludes src/app/** entirely (see Testing).

    The redesigned screens go one step further. Their route file is a one-line re-export (export default HomeScreen), and the screen lives with its components under src/components/<feature>/. Each feature keeps its rules in a pure, tested *-model.ts: home-model.ts, series-model.ts, search-model.ts, up-next-model.ts, books-view.ts, downloads-view.ts, keep-ahead.ts, book-page-model.ts, diary-model.ts, editor-model.ts, you-model.ts, stats-model.ts, year-model.ts, settings-model.ts, account-model.ts and connect-model.ts.

  • Path is identity, scoped by connection. Every content call passes ?path=<rel_path>, never a database id. The app can be signed in to several servers at once, and two of them can share a library id. So durable and cached client state keys on (connectionId, library_id, path) (see State & data).

Where each topic lives​

TopicPage
Every route, its file and its screen; the root layout; the rules for tabs, pushes and deep linksRoutes and navigation
The chrome around the pages: form factors, the command palette, the sub-nav, the Up next drawer and sheet, the profile menu, toastsThe app shell
className, tokens, fonts, <Text>, icons, the src/components/ui/ primitives, overlays, theme switchingStyling and the design system
How the web build starts fast, and the /web base pathThe web build
The Library tab's modes, the covers and shelves, the series and person pagesThe Library and browse screens
The engines, the timeline math, the player storePlayback, Smart speed and Voice boost
The mini player, the docked bar, the full player, its sheetsPlayer UI
What happens when a book finishes, and what plays nextThe end of a book
The sleep timerThe sleep timer
The book page, the reader, podcastsThe book page, The ebook reader, Podcasts
Bookmarks, notes and the JournalBookmarks and notes, The Journal
The You hub, Settings and AccountYou, Settings and Account
People on the device and in the householdProfiles, Household page and members, Kids mode, Listening in the house
Connecting to a server, home and away addressesConnect and home/away addresses
The API client, React Query, the stores, progress syncState & data
Downloads and the PWAOffline & PWA
CarPlay, Android Auto, widgets, the watches, CastNative integrations, Apple Watch, Wear OS, Chromecast
LanguagesInternationalisation
Tests and native checksTesting the player

Environment gotchas​

Read this before running anything

These are the four failure modes that cost the most time on a fresh checkout. The workspace guide's Environment gotchas covers each one in full.

  1. Node 24 is required. .nvmrc pins 24.16.0; run nvm use first. An older system Node crashes the Expo CLI as soon as a .env file exists (see the toolchain matrix).
  2. Native runs need a dev build, not Expo Go. The audiosilo-player module, react-native-svg and expo-secure-store are native code, which Expo Go can't load. Run npx expo prebuild, then npx expo run:ios or run:android. The web (npm run web) needs no native build.
  3. An edit under modules/audiosilo-player/{ios,android} needs a full rebuild (run:ios or run:android). A Metro or JS reload does not pick it up. This is the most common reason a native fix seems to do nothing.
  4. Web development needs CORS. The Metro dev server runs at http://localhost:8081. Set the server's cors_origins to that origin, or serve same-origin through the baked export (see the Configuration reference).

Run tool commands from the repo root. A stray cd into node_modules breaks Expo's config resolution. Before calling any change done, run the full gate (npx tsc --noEmit && npm run lint && npm run format && npm test). CI enforces it, plus the dependency checks (see Gates and CI).