The core app
Yes. In fact, this is a very good use case for this type of architecture. Many media applications use a similar idea because it separates the core application from the content providers.
Here’s how it could work.
The core app
Your music app is responsible for:
- Searching across plugins
- Showing albums, artists, and songs
- Playing audio
- Managing downloads
- Caching
- Playlists
- User interface
It knows nothing about Spotify, SoundCloud, YouTube, Archive.org, or any future source.
Music App
│
┌──────────────┼──────────────┐
│ │ │
Search Playback Downloads
│
Plugin Manager
│
┌──────────┬──────────┬──────────┐
│ │ │
Plugin A Plugin B Plugin C
Each plugin represents one music source
For example,
Spotify Plugin
SoundCloud Plugin
Archive.org Plugin
Local Library Plugin
Jamendo Plugin
Each plugin knows only how to talk to its own service.
For example,
Spotify Plugin
Search
↓
Spotify API
↓
Return songs
while
SoundCloud Plugin
Search
↓
SoundCloud API
↓
Return songs
The main app doesn’t care how they work internally.
They all implement the same interface
Instead of a socket API like Herdr, you could define a small JSON protocol.
For example, every plugin supports commands like
search
get_album
get_artist
get_track
stream
download
lyrics
recommendations
So when your app wants to search,
App
↓
search("Imagine Dragons")
↓
Plugin
↓
Returns JSON
Example response
{
"tracks": [
{
"id": "123",
"title": "Believer",
"artist": "Imagine Dragons",
"duration": 204,
"streamable": true
}
]
}
Every plugin returns the same structure.
Streaming
Suppose the user presses Play.
User
↓
Music App
↓
Spotify Plugin
↓
Returns stream URL or audio stream
↓
Music Player
The player never talks directly to Spotify.
Downloading
Likewise,
Download
↓
Plugin
↓
Downloads file
↓
Returns path
↓
Music library
or
Plugin
↓
Returns download URL
↓
Main app downloads
Both designs work.
Adding new music sources
Imagine six months later someone builds a plugin for Bandcamp.
Nothing changes in your app.
Before
Spotify
SoundCloud
Archive.org
Later
Spotify
SoundCloud
Archive.org
Bandcamp
The app discovers the new plugin and starts using it.
No changes to the core application.
Discovery
Just like Herdr,
each plugin could include a manifest.
Example
id = "spotify"
name = "Spotify"
version = "1.0"
author = "John"
entry = "./spotify-plugin"
supports = [
"search",
"stream",
"download",
"lyrics"
]
Your app scans
plugins/
spotify/
plugin.toml
soundcloud/
plugin.toml
archive/
plugin.toml
Communication
You have several choices.
Option 1: Local socket (like Herdr)
App
⇄ Socket
⇄ Plugin
Works in every language.
Option 2: HTTP
Plugin starts a small local server.
GET /search
GET /album
GET /track
POST /download
Very easy because every language supports HTTP.
Option 3: JSON over stdin/stdout
The app launches the plugin.
App
↓
stdin
↓
Plugin
↓
stdout
↓
App
This is how tools like Git, the Language Server Protocol (LSP), and many command-line integrations communicate.
Why this architecture is attractive
It has several benefits:
- Extensibility: New music services can be added without changing the core app.
- Language independence: Plugin authors can use Python, Rust, Go, JavaScript, or any language that can produce an executable.
- Isolation: If one plugin crashes, the app can often recover without affecting other plugins.
- Maintainability: Bugs or API changes in one music service are isolated to that plugin.
One important consideration
If your plugins access commercial streaming services (such as Spotify, Apple Music, or YouTube Music), you need to ensure they comply with those services’ APIs, authentication requirements, and terms of service.
Overall architecture
Music App
│
Plugin Manager
│
Reads plugin.toml manifests
│
Starts plugin executables
│
JSON / Socket / HTTP
│
┌────────┬────────┬────────┐
│ │ │
Spotify SoundCloud Archive.org
Plugin Plugin Plugin
│ │ │
▼ ▼ ▼
Service APIs or local media sources
For a music application, I would actually take Herdr’s idea one step further: define a strict plugin API (for search, metadata, streaming, downloading, authentication, etc.) and communicate using JSON-RPC over stdin/stdout or a local socket. This gives you the same language-agnostic flexibility while keeping plugins isolated and easy to develop.