Architecture
How the Vue renderer, Electron main process, native addon, and edge services fit together, and why there are two channels into the main process.
Orchard desktop is an Electron application: a Vue 3 renderer (built with Vite and Quasar), an Electron main process holding every privileged capability, and a native C++ addon for audio analysis and transition rendering. Around it sit a set of Cloudflare Workers and a native Android client.
The codebase is roughly 107,000 lines across JavaScript, Vue, and Kotlin.
Process layout
┌─────────────────────────────────────────────────────────┐
│ Renderer (Vue 3 + Quasar, sandboxed, strict CSP) │
│ src/app/ state and actions │
│ src/audio/ Web Audio engine, crossfade runtime │
│ src/components/ views, player, settings │
└───────────┬──────────────────────────┬──────────────────┘
│ preload contextBridge │ loopback Socket.IO
│ (privileged operations) │ (catalog + playback)
┌───────────┴──────────────────────────┴──────────────────┐
│ Main process │
│ electron/main/ composition root │
│ electron/auth/ browser-backed YouTube auth │
│ electron/catalog/ InnerTube browse and normalizers │
│ electron/playback/ stream resolution, proxy, cache │
│ electron/audio/ native analysis, model hosts │
│ electron/connect/ LAN pairing server │
│ electron/integrations/ Discord, Last.fm, updater, … │
│ electron/bridge/ Socket.IO bridge server │
└───────────┬─────────────────────────────────────────────┘
│ N-API
┌───────────┴─────────────────────────────────────────────┐
│ native/ C++ analyzer and transition renderer │
└─────────────────────────────────────────────────────────┘
The two channels into the main process
Orchard deliberately has two, because they carry different kinds of traffic.
The preload bridge (electron/preload/index.cjs) is the security boundary. It is
dependency-free by design, exposes only narrow structured operations through
contextBridge, and never hands the renderer a raw ipcRenderer. Channel literals are
duplicated in the preload rather than imported, because a sandboxed preload cannot load
arbitrary local modules. test/ipcChannels.test.js exists specifically to stop those
literals from drifting away from shared/ipcChannels.js.
Exposed namespaces include orchardWindow, orchardApp, orchardDiscord,
orchardLastfm, orchardSpotify, and others, each a small set of invoke calls.
The loopback Socket.IO bridge (electron/bridge/bridgeServer.js) carries catalog and
playback traffic, which is high-volume, streaming-shaped, and does not want to be an IPC
round trip per item. It binds to loopback only.
The composition root
electron/main/index.js owns Electron’s lifecycle resources and injects capabilities into
services rather than letting services reach for globals. The bridge server, for example,
receives dozens of named functions as parameters. That is what keeps catalog code testable
without an Electron runtime.
Two things happen unusually early:
installInnertubeParserErrorHandler()runs before any InnerTube client exists, because the stock handler throws from inside an asar archive when it encounters an unexpected response.- Graphics mode and session state stores are constructed from
userDatapaths before the window opens, since graphics mode has to be applied via Chromium command-line switches.
Renderer state
State lives in src/app/, organized by domain rather than by technical layer:
| Directory | Owns |
|---|---|
core/ | Application composition, global state, navigation, lifecycle, session |
playback/ | Queue, transport, crossfade actions, Replay, sleep timer, song cache |
browse/ | Home, search, library, playlists, artists, podcasts, radio, pins |
appearance/ | Themes, artwork, immersive backgrounds, artist packs |
platform/ | Desktop integration, updates, Connect, support, system media |
social/ | Listening parties, Last.fm, sharing |
src/app/core/createOrchardApp.js wires these together into the context object (ctx)
that actions receive.
Renderer security
The renderer runs sandboxed under a strict Content Security Policy set in index.html. One
concrete consequence: Zod is configured jitless, because its optional JIT probes
Function() before compiling object validators, which the CSP forbids. See
Audio Engine.
The renderer never talks to YouTube directly for catalog or playback. Everything goes through the main process, which is what allows authentication, format selection, and stream proxying to be controlled in one place.
Authentication
electron/auth/ signs in through an embedded browser session on Google’s own pages. Orchard
stores the resulting cookies locally and can restore a cached sign-in without a fresh
browser flow. Multiple accounts are supported through an account-switch flow.
Playback routing distinguishes several cases: guest InnerTube, browser-backed InnerTube, and
an authenticated retry path specifically for age-gated tracks
(electron/playback/playbackRouting.js, authenticatedYouTubePlayback.js). That last path
is involved enough to have its own page; see
Explicit and Age-Restricted Tracks.
Playback path
- The renderer asks the bridge to resolve a track.
- The main process picks a format (
playbackFormats.js), preferring audio-only at a suitable bitrate, with a music-video fallback. - The stream is proxied over loopback (
streamProxy.js), which is also where the Song Cache intercepts and stores bytes. - The renderer receives a local URL and plays it in a media element, wrapped in the Audio Engine graph.
Proxying rather than handing out remote URLs is what makes caching, range handling, and recovery on expired URLs possible.
Audio analysis
electron/audio/ hosts the analysis services. Model inference runs in separate child
processes (beatModelProcess.js, vocalMaskProcess.js, managed by
modelProcessHost.js), so a model crash cannot take down playback. Tempo, key, energy, and
mel-spectrogram work runs in the native addon. See
Native Audio Analyzer.
Shared code
shared/ holds the small amount of code both sides need: IPC channel names, graphics mode
options, and audio analysis types. Keeping it small is deliberate, since anything here has
to be safe in both a privileged and a sandboxed context.
Edge services
Nine Cloudflare Workers back features that need a credential Orchard must not ship, or a service that must not be run per-client. See Cloudflare Workers.
Mobile
mobile/ is a separate native Android application in Kotlin, not a WebView wrapper and not
a remote control. It reimplements the same architecture on Android. See
Orchard Mobile.
Directory reference
src/ Vue renderer and application state
src/audio/ Live audio engine and Smart Crossfade pipeline
electron/main/ Electron composition root
electron/preload/ Sandboxed renderer bridge
electron/audio/ Native analysis and audio services
electron/auth/ Browser-backed YouTube authentication
electron/bridge/ Loopback Socket.IO transport
electron/catalog/ InnerTube browse and normalization
electron/connect/ Orchard Connect server and pairing UI
electron/integrations/ Discord, Last.fm, Spotify, GitHub, updater
electron/playback/ Stream resolution, proxying, and caching
electron/platform/ Window, tray, media keys, clipboard, screenshots
native/ C++ audio analyzer and N-API bindings
mobile/ Native Android/Kotlin Orchard Mobile app
workers/ Cloudflare Workers and Durable Objects
services/artwork-converter/ Animated-artwork conversion service
models/ Bundled model assets
packaging/ Linux packaging and runner assets
scripts/ Build, launch, and release utilities
shared/ Code shared across the process boundary
test/ Node test suite