npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

npm package

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-sdk

If 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-left
  • top-right
  • bottom-left
  • bottom-right
  • bottom-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) accepts 0 to 1.
  • skip() and previous() 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.css

PowerShell:

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.css

Then 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.css if using SynxedWebPlayer.
  • Call destroy() when unmounting.
  • Use a stable listenerId if you want resume to work in a custom UI.
  • Keep resumePlayback: false for 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, and stateChange events 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: true is set on SynxedPlayer
  • the same listenerId is passed to playSong, playPlaylist, or playMix
  • 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