-
-
Notifications
You must be signed in to change notification settings - Fork 5
Orchard Mobile Architecture
Orchard Mobile is a native Android application in Kotlin and Jetpack Compose, around 35,000 lines across 154 source files, plus a C++ layer for audio analysis and transition rendering.
It is a standalone player rather than a WebView wrapper or a remote control for the desktop. It reimplements the same ideas natively, and in the case of age-restricted playback it implemented them first, with the desktop ported from it. See Explicit and Age-Restricted Tracks.
mobile/ the app
app/ OrchardApp, OrchardViewModel, composition root
playback/ Media3 service, resolvers, queue, cache
playback/smart/ Analysis, transition planning, rendering
catalog/ YouTube Music API boundary and parsing
library/ Library repository and offline cache
download/ Offline downloads
auth/ Cookie-session auth and Keystore storage
artwork/ Static and animated cover art providers
lyrics/ Lyrics resolver chain
connect/ Target selection, transfer, reconnect policy
discord/ Gateway client, OAuth, presence
songlinks/ Shareable Orchard Song Links
spotify/ Spotify Canvas support
audio/ Output, volume, and car-connection monitoring
settings/ DataStore-backed settings
security/ AndroidKeystore cipher
network/ Connectivity monitoring
model/ Shared data models
ui/ Compose theme, navigation, screens, components
connect/ Typed Socket.IO protocol and pairing client
protocol/ client/ session/ transport/ discovery/ ui/ app/
cpp/
analyzer/ Log-mel front end and tempo analysis
transition/ Time-stretch and transition rendering
vendor/rubberband/ Vendored Rubber Band (GPL-2.0-or-later)
Playback is built on Media3. OrchardPlaybackService is a MediaLibraryService, which
is what gives Orchard a media notification, lock-screen transport, headset and Bluetooth
button handling, audio focus, and Android Auto browsing from one integration rather than
several.
OrchardMediaLibrary supplies the browsable tree that Android Auto and voice search use.
Resolution walks a ladder of YouTube player clients, falling through on failure:
ANDROID_VR
→ retry with a fresh visitor identity
→ ANDROID
→ IOS
→ WEB_REMIX
→ TVHTML5
A per-video failure record keeps a failing track from being retried in a tight loop, and a budget stops the ladder from walking every client on errors that will clearly not recover.
NewPipeStreamResolver is a separate path used for the Max audio quality setting, which
wants the highest-bitrate stream available rather than the best one the standard clients
return.
Which format the ladder's response yields is decided by the audio quality setting. See Orchard Mobile Settings.
YouTubeChallengeSolver runs the real YouTube player JS in a headless WebView to solve
signature and n challenges, discovering the live player build rather than pinning one.
This is the machinery behind age-restricted playback, and the reasoning is documented in
Explicit and Age-Restricted Tracks.
QueueEditor owns queue mutations, and PlaybackStateStore persists the queue so it
survives the app being killed.
Shuffle is a real Fisher-Yates permutation, unbiased and uniform in O(n), rather than
repeated random selection. AutoplayRecommendations supplies continuation tracks when the
queue runs dry.
StreamCache holds streamed audio up to the configured size limit. HLS manifests are
deliberately kept out of it, since a manifest is not a progressive stream and caching one
produces confusing failures.
Offline Downloads are stored separately and are never evicted.
Everything Smart Crossfade needs is computed on the phone from the audio itself.
| Stage | Component |
|---|---|
| Decode to PCM | AudioDecoder |
| Log-mel front end |
MelSpectrogram (C++) |
| Beat and downbeat tracking |
BeatTracker, quantized Beat This! via ONNX Runtime |
| Vocal presence |
VocalTracker, open-unmix |
| Tempo, key, energy | native analyzer (C++) |
| Feature assembly |
TrackAnalyzer, TrackFeatures, TrackAnalysis
|
The results feed TransitionPolicy and TransitionPlanner, which mirror the desktop's
tiered design: measure first, then decide how ambitious a transition the evidence supports.
WsolaPlanner, TransitionRenderer, TransitionAudio, TransitionFilter, and
TransitionHandoff carry out the chosen plan, with TransitionPreparer doing the work
ahead of the transition.
BestMixSorter implements Best Mix on the same analysis data.
PlaybackTargetCoordinator is a pure state machine for choosing where audio plays. Its
central invariant:
A transfer commits only after its destination is available. This invariant is what prevents local and remote sessions from being marked active together.
If a selected remote device stops being reachable, control returns to the phone and playback pauses, rather than leaving two sessions believing they are active.
ConnectReconnectPolicy handles dropped connections with exponential backoff, doubling from
one second and capping at 30 seconds.
The connect/ package (separate from mobile/connect/) holds the typed Socket.IO protocol,
transport, session handling, and pairing client shared with the desktop's protocol version.
See Orchard Connect.
Android Auto detection is deliberately minimal. Projection over USB leaves the phone process
in its normal UI mode, so neither the audio route nor UiModeManager reveals that a car is
attached. Android Auto publishes its state through a content provider instead, and Orchard
queries that provider directly:
content://androidx.car.app.connection/carconnection
That is the same source androidx.car.app's CarConnection reads. Querying it directly
keeps the entire car-app library out of the build for the sake of one integer. Orchard
distinguishes projection (Android Auto) from native (Android Automotive OS, where the head
unit is the device), and treats a refused read as not connected.
AndroidKeystoreCipher wraps AES/GCM under an AndroidKeyStore-backed key. Orchard Connect
pairing tokens are encrypted with it, bound to the remembered host, and excluded from
Android backups.
Settings live in a DataStore in app-private storage. See Privacy and Data.
Jetpack Compose throughout, with OrchardViewModel as the single state holder and
OrchardApp as the composition root. Screens cover home, search, library, detail pages, now
playing, queue, devices, settings, and the welcome flow.
Theming supports Orchard's own accent or Material You wallpaper colors, with artwork-derived tinting and optional animated backgrounds.
cd mobile/android
./gradlew testDebugUnitTest assembleDebug lintDebugUnit tests cover auth signing, queue edits and restoration, playback state, artwork matching, transition filtering and planning, pairing, reconnect policy, and device transfers.
Instrumented tests cover the analysis models, which need a real device because they exercise ONNX Runtime and the native analyzer.
JDK 17 and Android SDK 36:
cd mobile/android
./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apkRelease builds are signed from ANDROID_KEYSTORE_FILE, ANDROID_KEYSTORE_PASSWORD,
ANDROID_KEY_ALIAS, and ANDROID_KEY_PASSWORD. See
Building from Source.
- Orchard Mobile for the feature overview
- Orchard Mobile Settings for every option
- Offline Downloads
- Architecture for the desktop equivalent
Orchard is licensed AGPL-3.0-or-later from 4.0.0 onward. Copyright © 2025–2026 SFG545. Orchard is not affiliated with or endorsed by YouTube or Google.
Using Orchard
Features
- Playback and Queues
- Explicit and Age-Restricted Tracks
- Best Mix
- Smart Crossfade
- Audio Engine
- Song Cache
- Library and Discovery
- Replay
- Listening Parties
- Orchard Connect
- Integrations
- Appearance
- Artist Packs
Mobile
Development
- Architecture
- Building from Source
- Native Audio Analyzer
- Cloudflare Workers
- Releases and Updates
- Contributing
Policy