Stream provider plugins
A stream provider adds a music source to Lydex — Subsonic, a personal server, any catalog. It is a separate Android app that answers a small control-plane contract; the Lydex engine does the fetching, decoding and playback.
protocolVersion you target; the host rejects versions it does not understand.How it works
A plugin has two planes. The control plane is a small AIDL service your app exports — the host binds to it to browse your catalog and resolve a track to a fetch recipe. The data plane is the Lydex engine itself: given that recipe, it streams the raw bytes and decodes them. Audio never crosses your app.
The rule that makes Lydex Lydex: the engine plays the original bytes — no transcoding, no resampling. Your job is to point it at the source file; hand back the real container (FLAC, ALAC, AAC, MP3, …) and let the engine decode it, straight to the DAC.
Declare the plugin
The host finds plugins by scanning installed packages for a fixed set of <meta-data> entries under <application>, plus a discovery <service>/<receiver> the package manager can match.
| meta-data | Meaning |
|---|---|
| dev.lydex.plugin.kind | "stream" — marks this APK as a streaming-source plugin. |
| dev.lydex.plugin.id | A stable, unique id (e.g. "subsonic"). Becomes the stream:// authority for this source. |
| dev.lydex.plugin.serviceClass | Fully-qualified class name of your StreamProviderService subclass — the control-plane entry point. |
| dev.lydex.plugin.protocolVersion | The contract version your plugin targets. The host refuses versions it does not understand. |
Implement the service
Subclass the provider service and implement three methods. Keep them fast and cancellable — the host calls them off the UI thread and may drop a request if the user moves on.
| Method | What it does |
|---|---|
| capabilities() | Declare what the provider supports — authentication, search, loudness, and which container formats it can serve. |
| browse(nodeId) | Return the child nodes of a browse node: containers (albums, playlists, folders) and tracks. This is your catalog tree. |
| resolve(trackId) | Return a StreamHandle for one track — everything the engine needs to fetch its exact bytes, on demand. |
Deliver the bytes
resolve() returns a StreamHandle: the container format, an optional expiry, and how the engine should fetch the data. Pick the lightest tier the source allows — a direct URL is almost always the right answer.
| Delivery tier | When to use it |
|---|---|
| Direct URL | A plain HTTP(S) URL. The engine fetches it with Range requests — simplest and most efficient. Use it whenever the source exposes a stable file URL. |
| Segmented | A list of segments / byte ranges the engine stitches together — for chunked delivery or per-segment signed URLs. |
| Opaque | The plugin supplies the bytes itself over the control plane, for sources with no fetchable URL. Highest overhead; last resort. |
Reference implementation
The Subsonic provider is open source and is the recommended starting point — a complete, shipping plugin that covers auth, browse, search and direct-URL delivery. Copy its structure, swap in your source’s API, and you have a working provider.
View the Subsonic example ›The contract (stream_plugin_api) ›