OrchardDocs

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 userData paths 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:

DirectoryOwns
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

  1. The renderer asks the bridge to resolve a track.
  2. The main process picks a format (playbackFormats.js), preferring audio-only at a suitable bitrate, with a music-video fallback.
  3. The stream is proxied over loopback (streamProxy.js), which is also where the Song Cache intercepts and stores bytes.
  4. 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