synxed-sdk
v0.3.40
Published
Synxed music SDK — integrate streaming music playback into any web app
Readme
Synxed SDK
Add licensed music to a website, game, or app in a few minutes. Synxed provides streaming playback, playlists, mixes, live radio, station schedules, voice commands, ads support, resumable playback, and a ready-made web player UI.
Synxed handles the music licensing required to make the catalog available through your integration.
Full implementation guide: docs/docs.md
Requirements
You need:
- A Synxed API key from your developer account:
https://portal.synxed.com - One of these content sources:
- a song ID
- a playlist code
- a mix ID
- live radio
- a station ID
Install
npm install synxed-sdkIf you use the built-in web player UI, import the stylesheet once in your app entry file:
import "synxed-sdk/player.css";The stylesheet is required for the player animations and is CSP-compatible. The SDK does not inject a <style> tag at runtime.
Choose an Integration
Use SynxedWebPlayer if you want the fastest setup with a ready-made player.
Use SynxedPlayer if you want to build your own controls, layouts, queue UI, station browser, or game-specific audio UI.
| Need | Use |
| ------------------------------------ | ----------------- |
| Floating player bar or mini player | SynxedWebPlayer |
| Custom buttons and layout | SynxedPlayer |
| Play one song | either |
| Play a playlist | either |
| Play live radio | either |
| Play a mix | SynxedPlayer |
| Play a scheduled station | SynxedPlayer |
| Fetch stations, schedules, and mixes | discovery helpers |
| Voice commands | either |
Built-In Web Player
The built-in player works in React, Vue, Angular, plain HTML, and most browser-based game/app shells.
import "synxed-sdk/player.css";
import { SynxedWebPlayer } from "synxed-sdk";
const player = SynxedWebPlayer.mount({
apiKey: "YOUR_SYNXED_API_KEY",
source: { type: "playlist", playlistCode: "sxpl_YOUR_CODE" },
});Call destroy() when your page, route, or component unmounts:
player.destroy();Play One Song
Use the song ID copied from the Synxed developer portal.
SynxedWebPlayer.mount({
apiKey: "YOUR_SYNXED_API_KEY",
source: { type: "song", songId: "SYNX_EX_SONG_ID" },
});Play a Playlist
SynxedWebPlayer.mount({
apiKey: "YOUR_SYNXED_API_KEY",
source: { type: "playlist", playlistCode: "sxpl_YOUR_CODE" },
});Play Live Radio
SynxedWebPlayer.mount({
apiKey: "YOUR_SYNXED_API_KEY",
source: { type: "radio" },
});Live radio always stays live. It does not resume from an old timestamp after refresh.
Mount Inside Your Own Container
<div id="synxed-player"></div>SynxedWebPlayer.mount({
container: document.getElementById("synxed-player")!,
apiKey: "YOUR_SYNXED_API_KEY",
source: { type: "playlist", playlistCode: "sxpl_YOUR_CODE" },
});When container is omitted, the player creates its own floating root and appends it to document.body.
Use a Custom Backend URL
The SDK uses https://api.synxed.com by default. Override serverUrl only for local development, staging, or self-hosted backend environments.
SynxedWebPlayer.mount({
apiKey: "YOUR_SYNXED_API_KEY",
serverUrl: "https://your-synxed-backend.example.com",
source: { type: "playlist", playlistCode: "sxpl_YOUR_CODE" },
});Web Player Appearance
Modes
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "playlist", playlistCode: "sxpl_..." },
mode: "wide",
});| Mode | Behavior |
| ------- | --------------------------------------------------- |
| wide | Compact horizontal bar. This is the default. |
| mini | Circular DJ avatar button with a title pill. |
| large | Larger card with visualizer and transport controls. |
Width
The wide player has a default responsive width. You can override it with any valid CSS width value.
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "playlist", playlistCode: "sxpl_..." },
width: "720px",
});Responsive example:
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "playlist", playlistCode: "sxpl_..." },
width: "min(92vw, 820px)",
});Position and Dragging
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "radio" },
position: {
placement: "bottom-center",
offsetX: 16,
offsetY: 24,
},
draggable: true,
});Available placements:
top-lefttop-rightbottom-leftbottom-rightbottom-center
Theme
The player is gold by default. To match your brand, set theme.accent.
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "playlist", playlistCode: "sxpl_..." },
theme: {
accent: "#22c55e",
background: "#050805",
text: "#ffffff",
},
});border, liveDot, and glow follow accent by default. Override them only when those colors need to be different.
theme: {
accent: "#22c55e",
border: "rgba(34, 197, 94, 0.55)",
liveDot: "#22c55e",
glow: "rgba(34, 197, 94, 0.35)",
}Custom Labels and Classes
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "playlist", playlistCode: "sxpl_..." },
powerByLabel: "Powered by Synxed",
className: "my-synxed-player",
style: {
zIndex: "9999",
},
});Resumable Playback
The built-in web player resumes songs and playlists by default after a refresh, using a stable anonymous listener ID stored locally.
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "song", songId: "SYNX_EX_SONG_ID" },
resumePlayback: true,
});Turn it off when you always want playback to start fresh:
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "playlist", playlistCode: "sxpl_..." },
resumePlayback: false,
});Resume behavior:
- songs, playlists, and mixes can resume
- live radio and scheduled stations stay live and do not resume old positions
- checkpoint writes are throttled to avoid storage pressure
- the SDK stores playback position metadata, not API keys or audio files
For a custom UI, enable resume on SynxedPlayer and pass the same listenerId whenever you start content:
import { SynxedPlayer } from "synxed-sdk";
const listenerId = "user_123";
const player = new SynxedPlayer({
apiKey: "YOUR_SYNXED_API_KEY",
autoConnect: true,
resumePlayback: true,
});
await player.playPlaylist({
playlistCode: "sxpl_YOUR_CODE",
listenerId,
});Voice Commands
Voice is enabled by default in the built-in web player. The user holds the DJ avatar, speaks, and releases. The SDK records only after that user gesture.
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "playlist", playlistCode: "sxpl_..." },
enableVoice: true,
});Turn voice off:
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "playlist", playlistCode: "sxpl_..." },
enableVoice: false,
});Customize the hold delay:
SynxedWebPlayer.mount({
apiKey: "...",
serverUrl: "https://api.synxed.com",
source: { type: "playlist", playlistCode: "sxpl_..." },
voiceHoldMs: 500,
});For custom controls:
const djButton = document.getElementById("dj-button")!;
djButton.addEventListener("pointerdown", () => {
void player.beginVoiceHold({ listenerId: "user_123" });
});
djButton.addEventListener("pointerup", () => {
void player.endVoiceHold();
});You can also cancel an active voice hold:
player.cancelVoiceHold();Custom Player UI
Use SynxedPlayer when you need full UI control.
import { SynxedPlayer } from "synxed-sdk";
const player = new SynxedPlayer({
apiKey: "YOUR_SYNXED_API_KEY",
autoConnect: true,
resumePlayback: true,
voice: {
enabled: true,
autoEndOnSilence: true,
maxDurationMs: 30000,
},
});Play Content
await player.playSong({
songId: "SYNX_EX_SONG_ID",
listenerId: "user_123",
});await player.playPlaylist({
playlistCode: "sxpl_YOUR_CODE",
listenerId: "user_123",
});await player.playMix({
mixId: "mix_YOUR_ID",
listenerId: "user_123",
});await player.playRadio({
listenerId: "user_123",
});await player.playStation({
stationId: "station_YOUR_ID",
listenerId: "user_123",
});Playback Controls
player.pause();
player.resume();
player.stop();
player.skip();
player.previous();
player.skipTo(2);
player.seek(45000);
player.setVolume(0.8);Notes:
seek(ms)expects milliseconds.setVolume(volume)accepts0to1.skip()andprevious()apply to playlists and mixes.- live radio and stations are continuous streams, so skip/previous are not intended for them.
Ads
The SDK emits ad events so custom UIs can show companion banners and skip countdowns.
player.on("adStart", (ad) => {
console.log(ad.campaignName, ad.companionBannerUrl);
});
player.on("adSkipUpdate", (state) => {
if (state.canSkip) {
console.log("Skip available");
} else {
console.log(`Skip in ${state.countdownSeconds}s`);
}
});
player.on("adEnd", () => {
console.log("Ad finished");
});Skip or click the current ad:
if (player.canSkipAd()) {
player.skipAd();
}
player.clickAd();Player Events
Use events to keep your UI synced with playback state.
player.on("stateChange", (state) => {
console.log(state.status, state.currentTrack);
});
player.on("trackChange", (track) => {
console.log(track.title, track.artist, track.albumArt);
});
player.on("timeUpdate", ({ currentTime, duration }) => {
console.log(currentTime, duration);
});
player.on("queueUpdated", (tracks) => {
console.log(tracks);
});
player.on("connected", () => {
console.log("Connected to Synxed");
});
player.on("disconnected", (reason) => {
console.log("Disconnected", reason);
});
player.on("error", (error) => {
console.error(error);
});Voice events:
player.on("voiceStateChange", (state) => {
console.log(state);
});
player.on("voiceResult", (result) => {
console.log(result.transcript, result.playlistCode);
});
player.on("voiceUserSpeakingChange", (speaking) => {
console.log("speaking", speaking);
});Discovery Helpers
The SDK includes lightweight fetch helpers for station and mix discovery.
import { fetchMixes, fetchStations, fetchStationSchedule } from "synxed-sdk";Fetch Mixes
const mixes = await fetchMixes("https://api.synxed.com", {
headers: {
Authorization: "Bearer YOUR_SYNXED_API_KEY",
},
});
const firstMix = mixes[0];Then play a mix with the lower-level player:
await player.playMix({
mixId: firstMix.id,
listenerId: "user_123",
});Fetch Stations
const stations = await fetchStations("https://api.synxed.com", {
headers: {
Authorization: "Bearer YOUR_SYNXED_API_KEY",
},
});
const firstStation = stations[0];Then play a station:
await player.playStation({
stationId: firstStation.id,
listenerId: "user_123",
});Fetch a Station Schedule
const schedule = await fetchStationSchedule(
"https://api.synxed.com",
firstStation.id,
{
headers: {
Authorization: "Bearer YOUR_SYNXED_API_KEY",
},
},
);Radio Now Playing
For live radio metadata:
import { fetchRadioNowPlaying } from "synxed-sdk";
const info = await fetchRadioNowPlaying("https://api.synxed.com");
if (info) {
console.log(info.title, info.station, info.isLive);
}Poll every 10 to 15 seconds if you show this in your own UI. Do not poll faster than that in production.
React Example
import { useEffect, useRef } from "react";
import "synxed-sdk/player.css";
import { SynxedWebPlayer } from "synxed-sdk";
export function MusicPlayer() {
const playerRef = useRef<SynxedWebPlayer | null>(null);
useEffect(() => {
playerRef.current = SynxedWebPlayer.mount({
apiKey: "YOUR_SYNXED_API_KEY",
source: { type: "playlist", playlistCode: "sxpl_YOUR_CODE" },
mode: "wide",
resumePlayback: true,
});
return () => {
playerRef.current?.destroy();
playerRef.current = null;
};
}, []);
return null;
}Next.js Example
The player needs browser APIs, so mount it from a client component.
"use client";
import { useEffect, useRef } from "react";
import "synxed-sdk/player.css";
import { SynxedWebPlayer } from "synxed-sdk";
export default function SynxedClientPlayer() {
const playerRef = useRef<SynxedWebPlayer | null>(null);
useEffect(() => {
playerRef.current = SynxedWebPlayer.mount({
apiKey: process.env.NEXT_PUBLIC_SYNXED_API_KEY!,
source: { type: "radio" },
});
return () => {
playerRef.current?.destroy();
};
}, []);
return null;
}Keep browser-exposed keys prefixed with NEXT_PUBLIC_. Do not put private backend secrets in client code.
Plain HTML Example
Copy the browser files into your app's public assets folder:
mkdir -p public/vendor/synxed
cp node_modules/synxed-sdk/dist/synxed.js public/vendor/synxed/synxed.js
cp node_modules/synxed-sdk/dist/player.css public/vendor/synxed/player.cssPowerShell:
New-Item -ItemType Directory -Force public/vendor/synxed | Out-Null
Copy-Item node_modules/synxed-sdk/dist/synxed.js public/vendor/synxed/synxed.js
Copy-Item node_modules/synxed-sdk/dist/player.css public/vendor/synxed/player.cssThen use normal HTML tags:
<link rel="stylesheet" href="/vendor/synxed/player.css" />
<script src="/vendor/synxed/synxed.js"></script>
<script>
const player = window.Synxed.SynxedWebPlayer.mount({
apiKey: "YOUR_SYNXED_API_KEY",
source: { type: "radio" },
});
</script>Cleanup
Always destroy SDK instances when the owning page, route, component, scene, or game state is removed.
player.destroy();This closes the WebSocket, stops audio, clears timers, removes lifecycle listeners, and releases voice resources.
TypeScript Reference
SynxedWebPlayer.mount(options)
type SynxedWebPlayerSource =
| { type: "song"; songId: string }
| { type: "playlist"; playlistCode: string }
| { type: "radio" };
type SynxedWebPlayerMode = "mini" | "wide" | "large";
interface SynxedWebPlayerOptions {
container?: HTMLElement;
apiKey: string;
serverUrl?: string;
source: SynxedWebPlayerSource;
mode?: SynxedWebPlayerMode;
width?: string;
theme?: SynxedWebPlayerTheme;
position?: SynxedWebPlayerPosition;
nowPlayingPollMs?: number;
powerByLabel?: string;
className?: string;
style?: Partial<CSSStyleDeclaration>;
onMiniClick?: () => void;
draggable?: boolean;
enableVoice?: boolean;
voiceHoldMs?: number;
listenerId?: string;
resumePlayback?: boolean;
debug?: boolean;
}SynxedPlayer
interface SynxedConfig {
apiKey: string;
serverUrl?: string;
autoConnect?: boolean;
debug?: boolean;
gameContext?: string;
deviceType?: string;
voice?: VoiceHoldOptions;
resumePlayback?: boolean;
}Production Checklist
- Import
synxed-sdk/player.cssif usingSynxedWebPlayer. - Call
destroy()when unmounting. - Use a stable
listenerIdif you want resume to work in a custom UI. - Keep
resumePlayback: falsefor experiences that must always start fresh. - Do not poll radio metadata faster than every 10 to 15 seconds.
- Use HTTPS in production.
- Do not expose private backend secrets in browser code.
- Handle
error,disconnected, andstateChangeevents in custom UIs.
Troubleshooting
The player is invisible or animations are missing
Import the CSS once:
import "synxed-sdk/player.css";Playback does not start automatically
Browsers may block autoplay until the user interacts with the page. Let the user click play, tap the mini player, or trigger playback from a user gesture.
Resume does not work in a custom UI
Make sure all of these are true:
resumePlayback: trueis set onSynxedPlayer- the same
listenerIdis passed toplaySong,playPlaylist, orplayMix - the content is a song, playlist, or mix
- the previous checkpoint has not expired
Radio or stations do not resume after refresh
This is expected. Live radio and scheduled stations stay live.
Voice does not start
Voice capture requires a user gesture and microphone permission. Call beginVoiceHold() from a pointer, mouse, touch, or keyboard interaction.
Artwork URLs should show Synxed domains
Synxed API responses should return artwork through Synxed proxy URLs. If you see a third-party provider domain in a Synxed response, update the backend response mapper before shipping.
License
Copyright (c) Synxed.com
