Version: 1.0
Audience: Senior Engineers
Author: me
CO-Author: LLM - full conversation here
Overview
This SDK enables artists to self-host music players on their own websites with full control over content, monetization, and UI. It allows users to create personal, browser-stored playlists across multiple artist sites — without any central authority or cross-site tracking.
A shared iframe (on a neutral origin) acts as a client-side data store and playback engine, communicating with host pages via postMessage
. All track storage, playlist assembly, and cross-tab coordination happen locally in the browser.
System Goals
- Artist-owned hosting and access control
- User-controlled browser-local playlists
- No login, cloud storage, or vendor lock-in
- Cross-origin, cross-tab interop
- Full support for albums, not just single tracks
- Support for purchasing, subscriptions, and free tracks
- Optional headless or custom player UI
- Secure origin-based access enforcement
- Optional external backup/sync of user library
Architecture Overview
... something like this
+----------------------------+ +--------------------------+
| artist-a.com frontend | | artist-b.net frontend |
| - Loads sdk.js | | - Loads sdk.js |
| - Calls sdk.sync(album) | | - Calls sdk.sync(album) |
| | | |
| +---------------+ | | +---------------+ |
| | player iframe |<------------->| player iframe | |
| | (player-hub) | Broadcast +---------------+ |
| +---------------+ Channel ^ ^ |
| ^ ^ | | | | |
+-------------|------|-------+ +------|-----|-------------+
| | | |
| +----------------------------+ |
| | artist-a.com backend | |
| | - Serves authorized media | |
| +----------------------------+ |
| |
+-------------------------------------+
| other-media-backend.io |
| - Serves authorized media |
+-------------------------------------+
Components
1. sdk.js
(Vanilla JS SDK, framework-agnostic)
- Automatically mounts the shared iframe
- Provides a JS API to host pages
- Handles message protocol and event subscriptions
- Can be wrapped by React/Vue/Svelte adapters
SDK Public API:
playerSDK.init(options?: {
headless?: boolean,
allowOrigins?: string[], // e.g. ["artist-a.com"]
ui?: "compact" | "full" | "none"
})
playerSDK.sync({
collection: string,
title?: string,
cover?: string,
origin?: string,
tracks: TrackMetadata[]
})
playerSDK.on(event: string, callback: (payload) => void)
playerSDK.requestPlayback(trackId: string)
2. player.html
(Shared iframe on https://player-hub.net/player.html
)
- Owns the
<audio>
element - Stores all track metadata and albums in
IndexedDB
- Renders secure modals for track sync confirmations
- Enforces track origin allowlists
- Handles playback logic
- Emits state changes via
postMessage
andBroadcastChannel
- Supports backup/restore via external adapters
Storage Schema:
type TrackMetadata = {
id: string
title: string
url: string
artist: string
cover?: string
duration?: number
access?: {
type: "free" | "subscription" | "purchase"
allowedOrigins?: string[]
auth?: {
method: "token" | "header" | "cookie"
endpoint: string
}
}
}
Security & Origin Validation
- The origin of the embedding page is determined via
event.origin
from thepostMessage
API. - This value is never supplied by the sender, ensuring it's not spoofable.
- All access enforcement uses the host portion of
event.origin
, ignoring the protocol.
Message Protocol
Host → Iframe:
{ type: "sync", collection: string, tracks: TrackMetadata[], title?: string, cover?: string }
{ type: "play", trackId: string }
{ type: "pause" }
{ type: "requestPlaylist" }
{ type: "seek", seconds: number }
Iframe → Host:
{ type: "confirmationRequired", collection: string, tracks: TrackMetadata[] }
{ type: "syncComplete", collection: string, acceptedIds: string[] }
{ type: "playlistUpdated", playlist: TrackMetadata[] }
{ type: "playbackState", playing: boolean, trackId?: string }
{ type: "authRequired", trackId: string, reason: string }
Access Models & Auth Handling
Free Track:
access.type = "free"
- Iframe streams directly from URL
Protected Track (purchase/subscription):
access.type = "purchase" | "subscription"
- Iframe checks
auth.endpoint
on the track’s origin (fromevent.origin
) - Server may return:
- A signed URL
- A bearer token
Confirmation & Permissions
- Tracks are never stored or played without user confirmation
- Iframe displays a native modal UI (never rendered by host)
- Can use
window.open()
as fallback
Cross-Tab Communication
Uses BroadcastChannel('player-hub')
:
- Syncs playback state
- Shares playlist updates
External Backup / Sync Layer
The iframe supports registering an external sync adapter:
window.playerHub.registerSyncAdapter({
name: "dropbox" | "solid" | "localExport",
export(): Promise<BackupData>,
import(data: BackupData): Promise<void>
})
Optional: Central Player Page
URL: https://player-hub.net/library
- Neutral user interface
- View/edit playlist
- Manual backup/restore
Future Considerations
- Album schema spec
- Playlist file format (
playlist.v1.json
) - Public read-only playlist sharing
- Token expiration + refresh
- Streamlined UX for first-time confirmations
- Can we do effective DRM?