synxed-sdk 0.2.4 → 0.2.6

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 Synxed music to your website or app in a few minutes 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
@@ -155,6 +155,7 @@ declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
155
155
  private voiceListenerId;
156
156
  private pendingPlaybackLoad;
157
157
  private voiceAutoEndOnSilence;
158
+ private pausedForVoice;
158
159
  constructor(config: SynxedConfig);
159
160
  get currentTrack(): TrackInfo | null;
160
161
  /** Active playback init kind from the last `play*` call (RADIO, PLAYLIST, SONG, …). */
@@ -185,6 +186,12 @@ declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
185
186
  endVoiceHold(): Promise<void>;
186
187
  /** Cancel an in-progress voice hold without sending audio to the server. */
187
188
  cancelVoiceHold(): void;
189
+ /**
190
+ * Close the voice UI: cancel recording if active and resume playback if it was
191
+ * paused for voice.
192
+ */
193
+ dismissVoiceAndResume(): void;
194
+ get isVoiceUiActive(): boolean;
188
195
  pause(): void;
189
196
  resume(): void;
190
197
  stop(): void;
@@ -337,8 +344,6 @@ interface SynxedWebPlayerOptions {
337
344
  mode?: SynxedWebPlayerMode;
338
345
  theme?: SynxedWebPlayerTheme;
339
346
  position?: SynxedWebPlayerPosition;
340
- avatarUrl?: string;
341
- voiceAvatarUrl?: string;
342
347
  attribution?: string;
343
348
  nowPlayingPollMs?: number;
344
349
  powerByLabel?: string;
@@ -347,8 +352,10 @@ interface SynxedWebPlayerOptions {
347
352
  style?: Partial<CSSStyleDeclaration>;
348
353
  onMiniClick?: () => void;
349
354
  draggable?: boolean;
350
- /** Press-and-hold DJ avatar for voice commands. Set `false` to disable. Default `true`. */
355
+ /** Press-and-hold DJ avatar for voice. Set `false` to disable. Default `true`. */
351
356
  enableVoice?: boolean;
357
+ /** Ms to hold before voice starts (prevents opening AI on quick tap). Default `450`. */
358
+ voiceHoldMs?: number;
352
359
  listenerId?: string;
353
360
  }
354
361
 
@@ -380,6 +387,12 @@ declare class SynxedWebPlayer {
380
387
  private avatarVoiceHolding;
381
388
  private suppressMiniClick;
382
389
  private voiceAvatarPreview;
390
+ private voiceHoldActivated;
391
+ private voiceUiDismissed;
392
+ private voiceHoldTimer;
393
+ private readonly defaultVoiceHoldMs;
394
+ /** Avatar to restore after an ad (DJ / album art — not ad or voice AI). */
395
+ private avatarImageBeforeAd;
383
396
  constructor(options: SynxedWebPlayerOptions);
384
397
  /** Convenience factory — same as `new SynxedWebPlayer(options)`. */
385
398
  static mount(options: SynxedWebPlayerOptions): SynxedWebPlayer;
@@ -404,14 +417,21 @@ declare class SynxedWebPlayer {
404
417
  private initEngine;
405
418
  /** Press-and-hold DJ avatar to stream voice; release or silence auto-ends capture. */
406
419
  private wireAvatarVoiceHold;
420
+ private isVoiceUiOpen;
421
+ private dismissVoiceUi;
422
+ private resetVoiceHoldState;
407
423
  private applyVoiceVisual;
408
424
  private isVoiceAvatarActive;
425
+ private getContentAvatarUrl;
409
426
  private getAvatarImageUrl;
410
427
  private refreshAvatarImage;
411
428
  private startNowPlayingPoll;
412
429
  private stopNowPlayingPoll;
413
430
  private handleSkipClick;
414
- /** YouTube-style ad skip pill: "Ad · 5" countdown → "Skip Ad". */
431
+ /**
432
+ * During ads: swap skip icon for countdown text in the same control (no style changes).
433
+ * After ads: restore skip icon for the next track.
434
+ */
415
435
  private applyAdSkipUi;
416
436
  private restoreTrackSkipButton;
417
437
  private displayLine;
package/dist/index.d.ts CHANGED
@@ -155,6 +155,7 @@ declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
155
155
  private voiceListenerId;
156
156
  private pendingPlaybackLoad;
157
157
  private voiceAutoEndOnSilence;
158
+ private pausedForVoice;
158
159
  constructor(config: SynxedConfig);
159
160
  get currentTrack(): TrackInfo | null;
160
161
  /** Active playback init kind from the last `play*` call (RADIO, PLAYLIST, SONG, …). */
@@ -185,6 +186,12 @@ declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
185
186
  endVoiceHold(): Promise<void>;
186
187
  /** Cancel an in-progress voice hold without sending audio to the server. */
187
188
  cancelVoiceHold(): void;
189
+ /**
190
+ * Close the voice UI: cancel recording if active and resume playback if it was
191
+ * paused for voice.
192
+ */
193
+ dismissVoiceAndResume(): void;
194
+ get isVoiceUiActive(): boolean;
188
195
  pause(): void;
189
196
  resume(): void;
190
197
  stop(): void;
@@ -337,8 +344,6 @@ interface SynxedWebPlayerOptions {
337
344
  mode?: SynxedWebPlayerMode;
338
345
  theme?: SynxedWebPlayerTheme;
339
346
  position?: SynxedWebPlayerPosition;
340
- avatarUrl?: string;
341
- voiceAvatarUrl?: string;
342
347
  attribution?: string;
343
348
  nowPlayingPollMs?: number;
344
349
  powerByLabel?: string;
@@ -347,8 +352,10 @@ interface SynxedWebPlayerOptions {
347
352
  style?: Partial<CSSStyleDeclaration>;
348
353
  onMiniClick?: () => void;
349
354
  draggable?: boolean;
350
- /** Press-and-hold DJ avatar for voice commands. Set `false` to disable. Default `true`. */
355
+ /** Press-and-hold DJ avatar for voice. Set `false` to disable. Default `true`. */
351
356
  enableVoice?: boolean;
357
+ /** Ms to hold before voice starts (prevents opening AI on quick tap). Default `450`. */
358
+ voiceHoldMs?: number;
352
359
  listenerId?: string;
353
360
  }
354
361
 
@@ -380,6 +387,12 @@ declare class SynxedWebPlayer {
380
387
  private avatarVoiceHolding;
381
388
  private suppressMiniClick;
382
389
  private voiceAvatarPreview;
390
+ private voiceHoldActivated;
391
+ private voiceUiDismissed;
392
+ private voiceHoldTimer;
393
+ private readonly defaultVoiceHoldMs;
394
+ /** Avatar to restore after an ad (DJ / album art — not ad or voice AI). */
395
+ private avatarImageBeforeAd;
383
396
  constructor(options: SynxedWebPlayerOptions);
384
397
  /** Convenience factory — same as `new SynxedWebPlayer(options)`. */
385
398
  static mount(options: SynxedWebPlayerOptions): SynxedWebPlayer;
@@ -404,14 +417,21 @@ declare class SynxedWebPlayer {
404
417
  private initEngine;
405
418
  /** Press-and-hold DJ avatar to stream voice; release or silence auto-ends capture. */
406
419
  private wireAvatarVoiceHold;
420
+ private isVoiceUiOpen;
421
+ private dismissVoiceUi;
422
+ private resetVoiceHoldState;
407
423
  private applyVoiceVisual;
408
424
  private isVoiceAvatarActive;
425
+ private getContentAvatarUrl;
409
426
  private getAvatarImageUrl;
410
427
  private refreshAvatarImage;
411
428
  private startNowPlayingPoll;
412
429
  private stopNowPlayingPoll;
413
430
  private handleSkipClick;
414
- /** YouTube-style ad skip pill: "Ad · 5" countdown → "Skip Ad". */
431
+ /**
432
+ * During ads: swap skip icon for countdown text in the same control (no style changes).
433
+ * After ads: restore skip icon for the next track.
434
+ */
415
435
  private applyAdSkipUi;
416
436
  private restoreTrackSkipButton;
417
437
  private displayLine;