synxed-sdk 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,198 +1,181 @@
1
1
  # Synxed SDK
2
2
 
3
- The official Synxed SDK for frontend developers to integrate high-quality music streaming into their applications.
3
+ Add Music to your website, Game or App in a few minutes Stream Songs, playlists, live radio, voice commands, and a ready-made player UI.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/synxed-sdk.svg)](https://www.npmjs.com/package/synxed-sdk)
6
- [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
7
6
 
8
- ## Features
7
+ ## What you need
9
8
 
10
- - **Simple integration**: Play songs, playlists, or **24/7 live radio** with a few lines of code.
11
- - **High-fidelity streaming**: Robust HLS support via `hls.js` for seamless playback across browsers (on-demand content).
12
- - **Built-in analytics**: Session `stream_start` plus automatic **`heartbeat`** intervals from the server `initAck` (including radio, which uses `positionMs: 0`).
13
- - **Framework-agnostic core**: `SynxedPlayer` works in React, Vue, Angular, or vanilla JS.
14
- - **Optional web UI**: Vanilla DOM overlay (`SynxedWebPlayer`) with **mini**, **wide**, and **large** layouts — works in any framework; **theme** and **placement** are fully configurable.
15
- - **Type-safe**: Written in TypeScript with full definitions.
9
+ 1. A **Synxed API key** (from your Synxed developer account `https://portal.synxed.com`).
10
+ 2. Your **API URL** (Our api url: `https://api.synxed.com`).
11
+ 3. A **playlist code**, **Single Song** or choose **live radio**.
16
12
 
17
- ## Installation
13
+ ## Install
18
14
 
19
15
  ```bash
20
16
  npm install synxed-sdk
21
17
  ```
22
18
 
23
- > **Note**: For HLS streaming support in browsers like Chrome, Firefox, and Edge, please ensure `hls.js` is installed in your project.
19
+ ## Easiest setup built-in player
24
20
 
25
- ```bash
26
- npm install hls.js
27
- ```
21
+ This adds a floating music bar to your page. No React or special framework required.
28
22
 
29
- ## Quick Start
23
+ ### Play a playlist
30
24
 
31
- ### Initialize the player
25
+ ```html
26
+ <script type="module">
27
+ import { SynxedWebPlayer } from "synxed-sdk";
32
28
 
33
- ```typescript
34
- import { SynxedPlayer } from "synxed-sdk";
29
+ SynxedWebPlayer.mount({
30
+ apiKey: "YOUR_SYNXED_API_KEY",
31
+ serverUrl: "https://api.synxed.com",
32
+ source: { type: "playlist", playlistCode: "sxpl_YOUR_CODE" },
33
+ });
34
+ </script>
35
+ ```
35
36
 
36
- const player = new SynxedPlayer({
37
+ ### Play live radio
38
+
39
+ ```javascript
40
+ SynxedWebPlayer.mount({
37
41
  apiKey: "YOUR_SYNXED_API_KEY",
38
42
  serverUrl: "https://api.synxed.com",
39
- autoConnect: true,
43
+ source: { type: "radio" },
40
44
  });
41
45
  ```
42
46
 
43
- ### Play a playlist
47
+ ### Put the player inside your own box
44
48
 
45
- ```typescript
46
- player.playPlaylist({
47
- playlistCode: "sxpl_VZCCGQAQJV",
49
+ ```javascript
50
+ SynxedWebPlayer.mount({
51
+ container: document.getElementById("my-player"),
52
+ apiKey: "YOUR_SYNXED_API_KEY",
53
+ serverUrl: "https://api.synxed.com",
54
+ source: { type: "playlist", playlistCode: "sxpl_YOUR_CODE" },
48
55
  });
49
56
  ```
50
57
 
51
- ### Play a single song
58
+ ## Player sizes
52
59
 
53
- ```typescript
54
- player.playSong({
55
- catalogTrackId: "2dcad8e0-3695-4971-9e35-f762f3f9d3a5",
56
- });
60
+ | Mode | What it looks like |
61
+ | ------- | ------------------------------- |
62
+ | `wide` | Bar across the bottom (default) |
63
+ | `mini` | Small round DJ button |
64
+ | `large` | Bigger card with visualizer |
57
65
 
58
- player.playSong({
59
- internalTrackId: "track-uuid-here",
66
+ ```javascript
67
+ SynxedWebPlayer.mount({
68
+ apiKey: "...",
69
+ serverUrl: "...",
70
+ source: { type: "playlist", playlistCode: "sxpl_..." },
71
+ mode: "mini",
60
72
  });
61
73
  ```
62
74
 
63
- ### Live radio
64
-
65
- Continuous **non-HLS** stream (not seekable). The SDK sends `CONTENT_KIND_RADIO` (`4`) over the same native `/sdk` WebSocket; the server returns a direct `playbackUrl` (e.g. MPEG stream).
66
-
67
- ```typescript
68
- await player.playRadio({ listenerId: "optional-stable-id" });
69
- ```
70
-
71
- - **`skip` / `previous` / `skipTo` / `seek`**: no-ops for radio (the live edge keeps moving on the server).
72
- - **`pause` / `resume`**: local audio only; when the user resumes, they rejoin the live stream.
73
- - **Metadata**: poll the REST endpoint (unauthenticated on most deployments):
75
+ ## Move the player on screen
74
76
 
75
- ```typescript
76
- import { fetchRadioNowPlaying } from "synxed-sdk";
77
-
78
- const np = await fetchRadioNowPlaying("https://api.synxed.com");
79
- // { title, station?, isLive? } — poll every 10–15s for “now playing” UI
80
- ```
81
-
82
- ### Optional web player UI (vanilla — no React required)
83
-
84
- ```typescript
85
- import { SynxedWebPlayer } from "synxed-sdk";
86
-
87
- const ui = SynxedWebPlayer.mount({
88
- apiKey: "YOUR_SYNXED_API_KEY",
89
- serverUrl: "https://api.synxed.com",
77
+ ```javascript
78
+ SynxedWebPlayer.mount({
79
+ apiKey: "...",
80
+ serverUrl: "...",
90
81
  source: { type: "radio" },
91
- mode: "wide",
92
- attribution: "DJ Jesse · Synxed × Your Brand",
93
- theme: {
94
- accent: "#ef4444",
95
- glow: "rgba(239, 68, 68, 0.35)",
96
- background: "#0a0707",
97
- },
98
82
  position: { placement: "top-left", offsetX: 16, offsetY: 16 },
99
83
  draggable: true,
100
84
  });
101
-
102
- // Later: ui.destroy();
103
85
  ```
104
86
 
105
- Mount into your own container instead of a fixed overlay:
87
+ ## Match your brand colors
106
88
 
107
- ```typescript
89
+ ```javascript
108
90
  SynxedWebPlayer.mount({
109
- container: document.getElementById("player-slot")!,
110
91
  apiKey: "...",
111
92
  serverUrl: "...",
112
93
  source: { type: "playlist", playlistCode: "sxpl_..." },
94
+ theme: {
95
+ accent: "#22c55e",
96
+ background: "#0a0a0a",
97
+ glow: "rgba(34, 197, 94, 0.35)",
98
+ },
113
99
  });
114
100
  ```
115
101
 
116
- **Modes**: `mini` (avatar only), `wide` (pill bar, default), `large` (card + visualizer). Skip controls are hidden for radio.
117
- **Draggable**: Set `draggable: true` (default is `false`) to allow users to move the floating player around the screen so it doesn't obstruct their view.
102
+ ## Talk to the DJ (voice playlists)
103
+
104
+ **Hold** the Synxed DJ avatar (the spinning circle) and say what you want to hear — for example _“play upbeat workout songs”_.
118
105
 
119
- In React/Vue, create the player in `useEffect` / `onMounted` and call `destroy()` on teardown — same class, no separate React package.
106
+ - **Tap** the DJ play or pause the music.
107
+ - **Hold** the DJ → speak your request; release when done (or stop talking and it sends automatically).
108
+ - While you speak, the DJ icon switches to the AI listening view.
109
+ - Music pauses while you talk and can resume when you tap again.
120
110
 
121
- ### Playback controls
111
+ To turn voice off:
122
112
 
123
- ```typescript
124
- player.pause();
125
- player.resume();
126
- player.stop();
127
- player.seek(30000); // on-demand only; no-op for radio
128
- player.setVolume(0.8);
129
- player.skip();
130
- player.previous();
131
- player.skipTo(2);
113
+ ```javascript
114
+ SynxedWebPlayer.mount({
115
+ enableVoice: false,
116
+ // ...other options
117
+ });
132
118
  ```
133
119
 
134
- ## Listening for events
120
+ ## Skip button and ads
135
121
 
136
- ```typescript
137
- player.on("stateChange", (state) => {
138
- console.log("Player status:", state.status);
139
- });
122
+ - During normal playback, the **skip** button jumps to the next song.
123
+ - During an ad, the same button shows a **short countdown** (about 5 seconds), then you can skip the ad.
124
+ - After the ad, the skip button works again for the next song.
140
125
 
141
- player.on("timeUpdate", ({ currentTime, duration }) => {
142
- const progress = (currentTime / duration) * 100;
143
- console.log(`Progress: ${progress.toFixed(2)}%`);
144
- });
126
+ ## Build your own UI (optional)
145
127
 
146
- player.on("error", (err) => {
147
- console.error("Playback Error:", err.message);
148
- });
149
- ```
128
+ If you want full control over buttons and layout, use `SynxedPlayer` instead of `SynxedWebPlayer`:
150
129
 
151
- ## API reference
130
+ ```javascript
131
+ import { SynxedPlayer } from "synxed-sdk";
152
132
 
153
- ### `new SynxedPlayer(config)`
133
+ const player = new SynxedPlayer({
134
+ apiKey: "YOUR_SYNXED_API_KEY",
135
+ serverUrl: "https://api.synxed.com",
136
+ autoConnect: true,
137
+ });
154
138
 
155
- - `apiKey`: `string` (required)
156
- - `serverUrl`: `string` (required)
157
- - `autoConnect`: `boolean` (default `true`)
139
+ await player.playPlaylist({ playlistCode: "sxpl_YOUR_CODE" });
158
140
 
159
- ### Methods
141
+ document.getElementById("play").onclick = () => player.resume();
142
+ document.getElementById("pause").onclick = () => player.pause();
143
+ document.getElementById("skip").onclick = () => player.skip();
144
+ ```
160
145
 
161
- - `playSong(options)`: `Promise<void>`
162
- - `playPlaylist(options)`: `Promise<void>`
163
- - `playRadio(options?)`: `Promise<void>` — live radio (`listenerId` optional)
164
- - `pause()` / `resume()` / `stop()`
165
- - `skip()` / `previous()` / `skipTo(index)` — playlist / on-demand only
166
- - `seek(ms)` — on-demand only (no-op for radio)
167
- - `setVolume(0–1)`
168
- - `destroy()`
146
+ ### Voice with your own buttons
169
147
 
170
- ### Getters
148
+ ```javascript
149
+ const dj = document.getElementById("dj-avatar");
171
150
 
172
- - `currentTrack`: current `TrackInfo | null`
173
- - `contentKind`: last `play*` kind (`ContentKind` enum, including `RADIO`)
151
+ dj.addEventListener("pointerdown", () => player.beginVoiceHold());
152
+ dj.addEventListener("pointerup", () => player.endVoiceHold());
153
+ ```
174
154
 
175
- ### `fetchRadioNowPlaying(serverUrl, init?)`
155
+ ## Clean up when leaving the page
176
156
 
177
- `Promise<RadioNowPlaying | null>` — wraps `GET {serverUrl}/radio/now-playing`.
157
+ ```javascript
158
+ const ui = SynxedWebPlayer.mount({
159
+ /* ... */
160
+ });
178
161
 
179
- ### Events
162
+ // When your app unmounts or navigates away:
163
+ ui.destroy();
164
+ ```
180
165
 
181
- - `stateChange`, `timeUpdate`, `trackChange`, `queueUpdated`, `error`, `connected`, `disconnected`
166
+ ## Radio “now playing” title
182
167
 
183
- ### `TrackInfo`
168
+ For radio, you can show the current song name on your own label:
184
169
 
185
- ```typescript
186
- interface TrackInfo {
187
- id: string;
188
- kind: "catalog" | "internal";
189
- title?: string;
190
- artist?: string;
191
- duration?: number;
192
- albumArt?: string;
193
- }
170
+ ```javascript
171
+ import { fetchRadioNowPlaying } from "synxed-sdk";
172
+
173
+ const info = await fetchRadioNowPlaying("https://api.synxed.com");
174
+ if (info) console.log(info.title);
194
175
  ```
195
176
 
177
+ Poll every 10–15 seconds to keep the title fresh.
178
+
196
179
  ## License
197
180
 
198
181
  Copyright © [Synxed.com](https://synxed.com)
package/dist/index.d.mts CHANGED
@@ -86,12 +86,6 @@ interface VoiceHoldOptions {
86
86
  listenerId?: string;
87
87
  /** Auto-end capture when the user stops talking. Default `true`. */
88
88
  autoEndOnSilence?: boolean;
89
- /** RMS level below which audio counts as silence (0–1). Default `0.018`. */
90
- silenceThreshold?: number;
91
- /** Ms of silence after speech before auto-end. Default `1500`. */
92
- silenceDurationMs?: number;
93
- /** Minimum speech ms before silence can trigger end. Default `400`. */
94
- minSpeechMs?: number;
95
89
  /** Max capture length in ms. Default `30000`. */
96
90
  maxDurationMs?: number;
97
91
  }
@@ -270,43 +264,46 @@ declare function fetchRadioNowPlaying(serverUrl: string, init?: RequestInit): Pr
270
264
  interface VoiceCaptureEvents {
271
265
  chunk: (data: Uint8Array, sequence: number) => void;
272
266
  error: (error: Error) => void;
273
- /** User started speaking (level crossed above silence threshold). */
274
267
  speechStart: () => void;
275
- /** User stopped speaking (silence after speech, or max duration reached). */
276
268
  speechEnd: () => void;
277
269
  }
278
270
  interface VoiceCaptureOptions {
279
- /** RMS level below which audio counts as silence (0–1). Default `0.018`. */
280
- silenceThreshold?: number;
281
- /** Ms of silence after speech before `speechEnd`. Default `1500`. */
282
- silenceDurationMs?: number;
283
- /** Minimum speech ms before silence can end capture. Default `400`. */
284
- minSpeechMs?: number;
285
271
  /** Safety cap — auto `speechEnd` after this many ms. Default `30000`. */
286
272
  maxDurationMs?: number;
287
- /** Monitor mic levels for end-of-speech. Default `true`. */
288
- detectSilence?: boolean;
273
+ /**
274
+ * Base URL where `@ricky0123/vad-web` model / worklet assets are served.
275
+ * Defaults to the jsDelivr CDN.
276
+ */
277
+ vadBaseAssetPath?: string;
278
+ /**
279
+ * Base URL where ONNX Runtime WASM binaries are served.
280
+ * Defaults to the jsDelivr CDN.
281
+ */
282
+ onnxWasmBasePath?: string;
289
283
  }
290
284
  /**
291
- * Native MediaRecorder capture with optional Web Audio silence detection.
285
+ * Captures microphone audio and buffers it locally. Once the user is finished
286
+ * talking (detected via Silero VAD), the complete audio file is emitted to the
287
+ * backend as a single chunk, followed by the stream termination.
292
288
  */
293
289
  declare class VoiceCaptureManager extends EventEmitter<VoiceCaptureEvents> {
294
290
  private stream;
295
- private recorder;
291
+ private audioContext;
292
+ private mediaStreamSource;
293
+ private processor;
294
+ private muteNode;
296
295
  private sequence;
297
296
  private capturing;
298
297
  private mimeType;
299
- private audioContext;
300
- private silenceRaf;
298
+ private micVad;
301
299
  private speechEndEmitted;
302
- private captureOptions;
303
300
  get isCapturing(): boolean;
304
301
  get activeMimeType(): string;
305
302
  start(options?: VoiceCaptureOptions): Promise<string>;
306
303
  stop(): Promise<void>;
307
304
  cancel(): void;
308
- private startSilenceMonitor;
309
- private stopSilenceMonitor;
305
+ private startVadMonitor;
306
+ private teardownVad;
310
307
  private releaseStream;
311
308
  }
312
309
 
@@ -344,8 +341,6 @@ interface SynxedWebPlayerOptions {
344
341
  mode?: SynxedWebPlayerMode;
345
342
  theme?: SynxedWebPlayerTheme;
346
343
  position?: SynxedWebPlayerPosition;
347
- avatarUrl?: string;
348
- voiceAvatarUrl?: string;
349
344
  attribution?: string;
350
345
  nowPlayingPollMs?: number;
351
346
  powerByLabel?: string;
@@ -393,6 +388,8 @@ declare class SynxedWebPlayer {
393
388
  private voiceUiDismissed;
394
389
  private voiceHoldTimer;
395
390
  private readonly defaultVoiceHoldMs;
391
+ /** Avatar to restore after an ad (DJ / album art — not ad or voice AI). */
392
+ private avatarImageBeforeAd;
396
393
  constructor(options: SynxedWebPlayerOptions);
397
394
  /** Convenience factory — same as `new SynxedWebPlayer(options)`. */
398
395
  static mount(options: SynxedWebPlayerOptions): SynxedWebPlayer;
@@ -422,14 +419,17 @@ declare class SynxedWebPlayer {
422
419
  private resetVoiceHoldState;
423
420
  private applyVoiceVisual;
424
421
  private isVoiceAvatarActive;
422
+ private getContentAvatarUrl;
425
423
  private getAvatarImageUrl;
426
424
  private refreshAvatarImage;
427
425
  private startNowPlayingPoll;
428
426
  private stopNowPlayingPoll;
429
427
  private handleSkipClick;
430
- /** Countdown in the skip control during ads — no extra chrome. */
428
+ /**
429
+ * During ads: swap skip icon for countdown text in the same control (no style changes).
430
+ * After ads: restore skip icon for the next track.
431
+ */
431
432
  private applyAdSkipUi;
432
- private clearAdSkipStyleOverrides;
433
433
  private restoreTrackSkipButton;
434
434
  private displayLine;
435
435
  private refreshLabels;
package/dist/index.d.ts CHANGED
@@ -86,12 +86,6 @@ interface VoiceHoldOptions {
86
86
  listenerId?: string;
87
87
  /** Auto-end capture when the user stops talking. Default `true`. */
88
88
  autoEndOnSilence?: boolean;
89
- /** RMS level below which audio counts as silence (0–1). Default `0.018`. */
90
- silenceThreshold?: number;
91
- /** Ms of silence after speech before auto-end. Default `1500`. */
92
- silenceDurationMs?: number;
93
- /** Minimum speech ms before silence can trigger end. Default `400`. */
94
- minSpeechMs?: number;
95
89
  /** Max capture length in ms. Default `30000`. */
96
90
  maxDurationMs?: number;
97
91
  }
@@ -270,43 +264,46 @@ declare function fetchRadioNowPlaying(serverUrl: string, init?: RequestInit): Pr
270
264
  interface VoiceCaptureEvents {
271
265
  chunk: (data: Uint8Array, sequence: number) => void;
272
266
  error: (error: Error) => void;
273
- /** User started speaking (level crossed above silence threshold). */
274
267
  speechStart: () => void;
275
- /** User stopped speaking (silence after speech, or max duration reached). */
276
268
  speechEnd: () => void;
277
269
  }
278
270
  interface VoiceCaptureOptions {
279
- /** RMS level below which audio counts as silence (0–1). Default `0.018`. */
280
- silenceThreshold?: number;
281
- /** Ms of silence after speech before `speechEnd`. Default `1500`. */
282
- silenceDurationMs?: number;
283
- /** Minimum speech ms before silence can end capture. Default `400`. */
284
- minSpeechMs?: number;
285
271
  /** Safety cap — auto `speechEnd` after this many ms. Default `30000`. */
286
272
  maxDurationMs?: number;
287
- /** Monitor mic levels for end-of-speech. Default `true`. */
288
- detectSilence?: boolean;
273
+ /**
274
+ * Base URL where `@ricky0123/vad-web` model / worklet assets are served.
275
+ * Defaults to the jsDelivr CDN.
276
+ */
277
+ vadBaseAssetPath?: string;
278
+ /**
279
+ * Base URL where ONNX Runtime WASM binaries are served.
280
+ * Defaults to the jsDelivr CDN.
281
+ */
282
+ onnxWasmBasePath?: string;
289
283
  }
290
284
  /**
291
- * Native MediaRecorder capture with optional Web Audio silence detection.
285
+ * Captures microphone audio and buffers it locally. Once the user is finished
286
+ * talking (detected via Silero VAD), the complete audio file is emitted to the
287
+ * backend as a single chunk, followed by the stream termination.
292
288
  */
293
289
  declare class VoiceCaptureManager extends EventEmitter<VoiceCaptureEvents> {
294
290
  private stream;
295
- private recorder;
291
+ private audioContext;
292
+ private mediaStreamSource;
293
+ private processor;
294
+ private muteNode;
296
295
  private sequence;
297
296
  private capturing;
298
297
  private mimeType;
299
- private audioContext;
300
- private silenceRaf;
298
+ private micVad;
301
299
  private speechEndEmitted;
302
- private captureOptions;
303
300
  get isCapturing(): boolean;
304
301
  get activeMimeType(): string;
305
302
  start(options?: VoiceCaptureOptions): Promise<string>;
306
303
  stop(): Promise<void>;
307
304
  cancel(): void;
308
- private startSilenceMonitor;
309
- private stopSilenceMonitor;
305
+ private startVadMonitor;
306
+ private teardownVad;
310
307
  private releaseStream;
311
308
  }
312
309
 
@@ -344,8 +341,6 @@ interface SynxedWebPlayerOptions {
344
341
  mode?: SynxedWebPlayerMode;
345
342
  theme?: SynxedWebPlayerTheme;
346
343
  position?: SynxedWebPlayerPosition;
347
- avatarUrl?: string;
348
- voiceAvatarUrl?: string;
349
344
  attribution?: string;
350
345
  nowPlayingPollMs?: number;
351
346
  powerByLabel?: string;
@@ -393,6 +388,8 @@ declare class SynxedWebPlayer {
393
388
  private voiceUiDismissed;
394
389
  private voiceHoldTimer;
395
390
  private readonly defaultVoiceHoldMs;
391
+ /** Avatar to restore after an ad (DJ / album art — not ad or voice AI). */
392
+ private avatarImageBeforeAd;
396
393
  constructor(options: SynxedWebPlayerOptions);
397
394
  /** Convenience factory — same as `new SynxedWebPlayer(options)`. */
398
395
  static mount(options: SynxedWebPlayerOptions): SynxedWebPlayer;
@@ -422,14 +419,17 @@ declare class SynxedWebPlayer {
422
419
  private resetVoiceHoldState;
423
420
  private applyVoiceVisual;
424
421
  private isVoiceAvatarActive;
422
+ private getContentAvatarUrl;
425
423
  private getAvatarImageUrl;
426
424
  private refreshAvatarImage;
427
425
  private startNowPlayingPoll;
428
426
  private stopNowPlayingPoll;
429
427
  private handleSkipClick;
430
- /** Countdown in the skip control during ads — no extra chrome. */
428
+ /**
429
+ * During ads: swap skip icon for countdown text in the same control (no style changes).
430
+ * After ads: restore skip icon for the next track.
431
+ */
431
432
  private applyAdSkipUi;
432
- private clearAdSkipStyleOverrides;
433
433
  private restoreTrackSkipButton;
434
434
  private displayLine;
435
435
  private refreshLabels;