Skip to content

Orchard Mobile Architecture

SFG545 edited this page Aug 13, 2026 · 1 revision

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.

Package layout

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

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.

Stream resolution

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.

Challenge solving

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.

Queue

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.

Caching

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.

On-device analysis

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.

Orchard Connect

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

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.

Security

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.

UI

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.

Testing

cd mobile/android
./gradlew testDebugUnitTest assembleDebug lintDebug

Unit 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.

Building

JDK 17 and Android SDK 36:

cd mobile/android
./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk

Release builds are signed from ANDROID_KEYSTORE_FILE, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, and ANDROID_KEY_PASSWORD. See Building from Source.

Related

Clone this wiki locally