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

@sendspin/sendspin-js

v3.2.0

Published

TypeScript client library for the Sendspin synchronized audio protocol

Downloads

1,724

Readme

sendspin-js

npm

TypeScript client library implementing the Sendspin Protocol for clock-synchronized audio streaming.

See the SDK website to see Sendspin JS in action: https://sendspin.github.io/sendspin-js/

A project from the Open Home Foundation

Example

import { SendspinPlayer } from '@sendspin/sendspin-js';

const player = new SendspinPlayer({
  playerId: 'my-player-id',
  baseUrl: 'http://your-server:8095',
  clientName: 'My Web Player',
  // Optional: "sync" (default), "quality" (no pitch shifts; not recommended for bad networks),
  // or "quality-local" (best for unsynced playback)
  correctionMode: 'sync',
  onStateChange: (state) => {
    // Local player state
    console.log('Playing:', state.isPlaying);
    console.log('Volume:', state.volume, 'Muted:', state.muted);

    // Server state (metadata, controller info)
    if (state.serverState?.metadata) {
      const meta = state.serverState.metadata;
      console.log('Track:', meta.title, '-', meta.artist);
    }

    // Group state (playback state, group info)
    if (state.groupState) {
      console.log('Group:', state.groupState.group_name);
      console.log('Playback:', state.groupState.playback_state);
    }
  }
});

// Connect to server
await player.connect();

// Local volume control (affects this player only)
player.setVolume(80);
player.setMuted(false);

// Send commands to server (controls the source)
player.sendCommand('play');
player.sendCommand('pause');
player.sendCommand('stop');
player.sendCommand('next');
player.sendCommand('previous');
player.sendCommand('volume', { volume: 50 });
player.sendCommand('mute', { mute: true });
player.sendCommand('shuffle');
player.sendCommand('unshuffle');
player.sendCommand('repeat_off');
player.sendCommand('repeat_one');
player.sendCommand('repeat_all');
player.sendCommand('switch');  // Switch group

// Disconnect with reason (optional)
player.disconnect('user_request');

Advanced configuration

Bring your own WebSocket

Provide an already-open (or CONNECTING) WebSocket via webSocket to let the player adopt it instead of creating a new one. Useful when the connection is managed by a surrounding app framework. Auto-reconnect is disabled for adopted sockets.

const ws = new WebSocket('ws://your-server:8095/sendspin');
const player = new SendspinPlayer({
  playerId: 'my-player',
  clientName: 'My Player',
  webSocket: ws,
});
await player.connect();

Reconnect behavior

Built-in auto-reconnect uses exponential backoff (1s → 15s, unlimited attempts). Override the bounds, cap the retry count, or hook callbacks to drive UI and fatal-error paths via reconnect.

const player = new SendspinPlayer({
  baseUrl: 'http://your-server:8095',
  reconnect: {
    baseDelayMs: 1000,
    maxDelayMs: 15000,
    maxAttempts: 7,
    onReconnecting: (attempt) => console.log(`Reconnecting (attempt ${attempt})`),
    onReconnected: () => console.log('Reconnected'),
    onExhausted: () => console.log('Giving up'),
  },
});

Reconnection only applies to connections opened via baseUrl; adopted sockets (webSocket) never auto-reconnect.

Tuning correction thresholds

Override the per-mode thresholds that control when/how the scheduler corrects drift. Unspecified fields keep their defaults.

const player = new SendspinPlayer({
  baseUrl: 'http://your-server:8095',
  correctionMode: 'sync',
  correctionThresholds: {
    sync: {
      resyncAboveMs: 400,   // tolerate more drift before hard resync
      deadbandBelowMs: 2,   // ignore errors under 2ms
    },
  },
});

Buffer timing

Report the startup lead time and ongoing jitter buffer the player needs to the server via client/state. Lower values mean lower latency at the risk of underruns. Defaults are requiredLeadTimeMs: 250 and minBufferMs: 250.

const player = new SendspinPlayer({
  baseUrl: 'http://your-server:8095',
  requiredLeadTimeMs: 250,  // startup warmup (codec init, decode, DAC)
  minBufferMs: 250,         // ongoing buffer to absorb network jitter
});

Both can be updated at runtime, e.g. after measuring real lead time post-warmup or on a link-type change. Debounce updates so transient fluctuations don't churn server-side timing.

player.setRequiredLeadTimeMs(300);
player.setMinBufferMs(1500);

Core + scheduler as separate layers

Apps that need the decoded PCM stream (e.g. visualizers) can use SendspinCore on its own and skip the playback layer. SendspinCore emits DecodedAudioChunk events; AudioScheduler is the Web Audio consumer that SendspinPlayer wires for you.

import { SendspinCore } from '@sendspin/sendspin-js';

const core = new SendspinCore({
  baseUrl: 'http://your-server:8095',
});

core.onAudioData = (chunk) => {
  // chunk.samples: Float32Array per channel
  // chunk.sampleRate, chunk.serverTimeUs, chunk.generation
};

await core.connect();

Local development

yarn dev-server

Then browse to http://localhost:6001

Testing

The E2E tests run against a real aiosendspin server, so they need a Python virtualenv at .venv. Bootstrap it once:

./scripts/setup.sh

Then:

yarn test         # unit + E2E
yarn test:watch   # watch mode

To run a single suite, pass the path: npx vitest run tests/unit or npx vitest run tests/e2e.