synxed-sdk 0.1.1 β 0.1.3
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 +108 -39
- package/dist/index.d.mts +138 -15
- package/dist/index.d.ts +138 -15
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,12 +7,12 @@ The official Synxed SDK for frontend developers to integrate high-quality music
|
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
- **
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
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.
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
@@ -28,100 +28,169 @@ npm install hls.js
|
|
|
28
28
|
|
|
29
29
|
## Quick Start
|
|
30
30
|
|
|
31
|
-
### Initialize the
|
|
31
|
+
### Initialize the player
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
34
|
import { SynxedPlayer } from "synxed-sdk";
|
|
35
35
|
|
|
36
36
|
const player = new SynxedPlayer({
|
|
37
37
|
apiKey: "YOUR_SYNXED_API_KEY",
|
|
38
|
-
serverUrl: "https://api.synxed.com", // or your
|
|
38
|
+
serverUrl: "https://api.synxed.com", // or your backend URL
|
|
39
39
|
autoConnect: true,
|
|
40
40
|
});
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
### Play a
|
|
44
|
-
|
|
45
|
-
Provide a playlist code generated from the Synxed platform.
|
|
43
|
+
### Play a playlist
|
|
46
44
|
|
|
47
45
|
```typescript
|
|
48
|
-
// Start playback from a playlist
|
|
49
46
|
player.playPlaylist({
|
|
50
47
|
playlistCode: "sxpl_VZCCGQAQJV",
|
|
51
48
|
});
|
|
52
49
|
```
|
|
53
50
|
|
|
54
|
-
### Play a
|
|
51
|
+
### Play a single song
|
|
55
52
|
|
|
56
53
|
```typescript
|
|
57
|
-
// Play a song from the global catalog
|
|
58
54
|
player.playSong({
|
|
59
55
|
catalogTrackId: "2dcad8e0-3695-4971-9e35-f762f3f9d3a5",
|
|
60
56
|
});
|
|
61
57
|
|
|
62
|
-
// Or an internal track from your own library
|
|
63
58
|
player.playSong({
|
|
64
59
|
internalTrackId: "track-uuid-here",
|
|
65
60
|
});
|
|
66
61
|
```
|
|
67
62
|
|
|
68
|
-
###
|
|
63
|
+
### Live radio
|
|
64
|
+
|
|
65
|
+
Continuous **non-HLS** stream (not seekable). The SDK sends `CONTENT_KIND_RADIO` (`4`) over the same `/sdk` socket; 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):
|
|
74
|
+
|
|
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",
|
|
90
|
+
source: { type: "radio" },
|
|
91
|
+
mode: "wide",
|
|
92
|
+
avatarUrl: "/dj.webp",
|
|
93
|
+
attribution: "DJ Jesse Β· Synxed Γ Your Brand",
|
|
94
|
+
theme: {
|
|
95
|
+
accent: "#ef4444",
|
|
96
|
+
glow: "rgba(239, 68, 68, 0.35)",
|
|
97
|
+
background: "#0a0707",
|
|
98
|
+
},
|
|
99
|
+
position: { placement: "top-left", offsetX: 16, offsetY: 16 },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Later: ui.destroy();
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Mount into your own container instead of a fixed overlay:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
SynxedWebPlayer.mount({
|
|
109
|
+
container: document.getElementById("player-slot")!,
|
|
110
|
+
apiKey: "...",
|
|
111
|
+
serverUrl: "...",
|
|
112
|
+
source: { type: "playlist", playlistCode: "sxpl_..." },
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Modes**: `mini` (avatar only), `wide` (pill bar, default), `large` (card + visualizer). Skip controls are hidden for radio.
|
|
117
|
+
|
|
118
|
+
In React/Vue, create the player in `useEffect` / `onMounted` and call `destroy()` on teardown β same class, no separate React package.
|
|
119
|
+
|
|
120
|
+
### Playback controls
|
|
69
121
|
|
|
70
122
|
```typescript
|
|
71
123
|
player.pause();
|
|
72
124
|
player.resume();
|
|
73
125
|
player.stop();
|
|
74
|
-
player.seek(30000); //
|
|
126
|
+
player.seek(30000); // on-demand only; no-op for radio
|
|
75
127
|
player.setVolume(0.8);
|
|
76
|
-
player.skip();
|
|
128
|
+
player.skip();
|
|
129
|
+
player.previous();
|
|
130
|
+
player.skipTo(2);
|
|
77
131
|
```
|
|
78
132
|
|
|
79
|
-
## Listening for
|
|
133
|
+
## Listening for events
|
|
80
134
|
|
|
81
135
|
```typescript
|
|
82
|
-
// Track state changes (idle, loading, playing, paused, error)
|
|
83
136
|
player.on("stateChange", (state) => {
|
|
84
137
|
console.log("Player status:", state.status);
|
|
85
138
|
});
|
|
86
139
|
|
|
87
|
-
// High-resolution time updates (60fps)
|
|
88
140
|
player.on("timeUpdate", ({ currentTime, duration }) => {
|
|
89
141
|
const progress = (currentTime / duration) * 100;
|
|
90
142
|
console.log(`Progress: ${progress.toFixed(2)}%`);
|
|
91
143
|
});
|
|
92
144
|
|
|
93
|
-
// Error handling
|
|
94
145
|
player.on("error", (err) => {
|
|
95
146
|
console.error("Playback Error:", err.message);
|
|
96
147
|
});
|
|
97
148
|
```
|
|
98
149
|
|
|
99
|
-
## API
|
|
150
|
+
## API reference
|
|
100
151
|
|
|
101
152
|
### `new SynxedPlayer(config)`
|
|
102
153
|
|
|
103
|
-
- `apiKey`: `string` (
|
|
104
|
-
- `serverUrl`: `string` (
|
|
105
|
-
- `autoConnect`: `boolean` (
|
|
154
|
+
- `apiKey`: `string` (required)
|
|
155
|
+
- `serverUrl`: `string` (required)
|
|
156
|
+
- `autoConnect`: `boolean` (default `true`)
|
|
106
157
|
|
|
107
158
|
### Methods
|
|
108
159
|
|
|
109
|
-
- `playSong(options)`: `Promise<void>`
|
|
110
|
-
- `playPlaylist(options)`: `Promise<void>`
|
|
111
|
-
- `
|
|
112
|
-
- `
|
|
113
|
-
- `
|
|
114
|
-
- `
|
|
115
|
-
- `
|
|
116
|
-
- `
|
|
117
|
-
|
|
160
|
+
- `playSong(options)`: `Promise<void>`
|
|
161
|
+
- `playPlaylist(options)`: `Promise<void>`
|
|
162
|
+
- `playRadio(options?)`: `Promise<void>` β live radio (`listenerId` optional)
|
|
163
|
+
- `pause()` / `resume()` / `stop()`
|
|
164
|
+
- `skip()` / `previous()` / `skipTo(index)` β playlist / on-demand only
|
|
165
|
+
- `seek(ms)` β on-demand only (no-op for radio)
|
|
166
|
+
- `setVolume(0β1)`
|
|
167
|
+
- `destroy()`
|
|
168
|
+
|
|
169
|
+
### Getters
|
|
170
|
+
|
|
171
|
+
- `currentTrack`: current `TrackInfo | null`
|
|
172
|
+
- `contentKind`: last `play*` kind (`ContentKind` enum, including `RADIO`)
|
|
173
|
+
|
|
174
|
+
### `fetchRadioNowPlaying(serverUrl, init?)`
|
|
175
|
+
|
|
176
|
+
`Promise<RadioNowPlaying | null>` β wraps `GET {serverUrl}/radio/now-playing`.
|
|
118
177
|
|
|
119
178
|
### Events
|
|
120
179
|
|
|
121
|
-
- `stateChange
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
180
|
+
- `stateChange`, `timeUpdate`, `trackChange`, `queueUpdated`, `error`, `connected`, `disconnected`
|
|
181
|
+
|
|
182
|
+
### `TrackInfo`
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
interface TrackInfo {
|
|
186
|
+
id: string;
|
|
187
|
+
kind: "catalog" | "internal";
|
|
188
|
+
title?: string;
|
|
189
|
+
artist?: string;
|
|
190
|
+
duration?: number;
|
|
191
|
+
albumArt?: string;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
125
194
|
|
|
126
195
|
## License
|
|
127
196
|
|
package/dist/index.d.mts
CHANGED
|
@@ -7,6 +7,23 @@ declare class EventEmitter<Events extends Record<string, any>> {
|
|
|
7
7
|
removeAllListeners(): void;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
declare enum ContentKind {
|
|
11
|
+
UNSPECIFIED = 0,
|
|
12
|
+
SONG = 1,
|
|
13
|
+
PLAYLIST = 2,
|
|
14
|
+
CATEGORY = 3,
|
|
15
|
+
RADIO = 4
|
|
16
|
+
}
|
|
17
|
+
declare enum ErrorCode {
|
|
18
|
+
UNSPECIFIED = 0,
|
|
19
|
+
UNAUTHORIZED = 1,
|
|
20
|
+
VALIDATION_ERROR = 2,
|
|
21
|
+
NOT_FOUND = 3,
|
|
22
|
+
PROCESSING = 4,
|
|
23
|
+
SERVICE_UNAVAILABLE = 5,
|
|
24
|
+
BAD_PROTOBUF = 6
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
interface SynxedConfig {
|
|
11
28
|
apiKey: string;
|
|
12
29
|
serverUrl: string;
|
|
@@ -21,8 +38,18 @@ interface PlayPlaylistOptions {
|
|
|
21
38
|
playlistCode: string;
|
|
22
39
|
listenerId?: string;
|
|
23
40
|
}
|
|
41
|
+
interface PlayRadioOptions {
|
|
42
|
+
listenerId?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Response shape from `GET /radio/now-playing` */
|
|
45
|
+
interface RadioNowPlaying {
|
|
46
|
+
title: string;
|
|
47
|
+
station?: string;
|
|
48
|
+
isLive?: boolean;
|
|
49
|
+
}
|
|
24
50
|
interface TrackInfo {
|
|
25
51
|
id: string;
|
|
52
|
+
kind: "catalog" | "internal";
|
|
26
53
|
title?: string;
|
|
27
54
|
artist?: string;
|
|
28
55
|
duration?: number;
|
|
@@ -30,7 +57,7 @@ interface TrackInfo {
|
|
|
30
57
|
}
|
|
31
58
|
interface PlayerState {
|
|
32
59
|
status: 'idle' | 'loading' | 'playing' | 'paused' | 'error';
|
|
33
|
-
currentTrack
|
|
60
|
+
currentTrack: TrackInfo | null;
|
|
34
61
|
currentTime: number;
|
|
35
62
|
duration: number;
|
|
36
63
|
volume: number;
|
|
@@ -42,6 +69,7 @@ interface SynxedEvents {
|
|
|
42
69
|
duration: number;
|
|
43
70
|
}) => void;
|
|
44
71
|
trackChange: (track: TrackInfo) => void;
|
|
72
|
+
queueUpdated: (tracks: TrackInfo[]) => void;
|
|
45
73
|
error: (error: Error) => void;
|
|
46
74
|
connected: () => void;
|
|
47
75
|
disconnected: (reason: string) => void;
|
|
@@ -53,18 +81,31 @@ declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
|
|
|
53
81
|
private playlist;
|
|
54
82
|
private config;
|
|
55
83
|
private status;
|
|
84
|
+
private volume;
|
|
85
|
+
private activeContentKind;
|
|
86
|
+
private heartbeatTimer;
|
|
56
87
|
constructor(config: SynxedConfig);
|
|
88
|
+
get currentTrack(): TrackInfo | null;
|
|
89
|
+
/** Active playback init kind from the last `play*` call (RADIO, PLAYLIST, SONG, β¦). */
|
|
90
|
+
get contentKind(): ContentKind;
|
|
91
|
+
private controlPositionMs;
|
|
57
92
|
private setupListeners;
|
|
58
93
|
private connect;
|
|
59
94
|
playSong(options: PlaySongOptions): Promise<void>;
|
|
60
95
|
playPlaylist(options: PlayPlaylistOptions): Promise<void>;
|
|
96
|
+
playRadio(options?: PlayRadioOptions): Promise<void>;
|
|
61
97
|
pause(): void;
|
|
62
98
|
resume(): void;
|
|
63
99
|
stop(): void;
|
|
64
100
|
skip(): void;
|
|
101
|
+
previous(): void;
|
|
102
|
+
skipTo(index: number): void;
|
|
65
103
|
seek(ms: number): void;
|
|
66
104
|
setVolume(volume: number): void;
|
|
105
|
+
private positionMsForAnalytics;
|
|
67
106
|
private emitAnalytics;
|
|
107
|
+
private clearHeartbeat;
|
|
108
|
+
private startHeartbeat;
|
|
68
109
|
private handleServerMessage;
|
|
69
110
|
private handleTrackEnded;
|
|
70
111
|
private updateStatus;
|
|
@@ -85,20 +126,102 @@ declare class SynxedProtocolError extends SynxedError {
|
|
|
85
126
|
constructor(message: string);
|
|
86
127
|
}
|
|
87
128
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Fetches live radio "now playing" metadata from the Synxed backend.
|
|
131
|
+
* Poll every 10β15 seconds to keep UI in sync (see integration docs).
|
|
132
|
+
*/
|
|
133
|
+
declare function fetchRadioNowPlaying(serverUrl: string, init?: RequestInit): Promise<RadioNowPlaying | null>;
|
|
134
|
+
|
|
135
|
+
type SynxedWebPlayerMode = 'mini' | 'wide' | 'large';
|
|
136
|
+
type SynxedWebPlayerPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'bottom-center';
|
|
137
|
+
interface SynxedWebPlayerTheme {
|
|
138
|
+
accent?: string;
|
|
139
|
+
accentMuted?: string;
|
|
140
|
+
background?: string;
|
|
141
|
+
backgroundInner?: string;
|
|
142
|
+
border?: string;
|
|
143
|
+
text?: string;
|
|
144
|
+
textMuted?: string;
|
|
145
|
+
stationText?: string;
|
|
146
|
+
liveDot?: string;
|
|
147
|
+
glow?: string;
|
|
93
148
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
149
|
+
interface SynxedWebPlayerPosition {
|
|
150
|
+
placement?: SynxedWebPlayerPlacement;
|
|
151
|
+
offsetX?: number;
|
|
152
|
+
offsetY?: number;
|
|
153
|
+
}
|
|
154
|
+
type SynxedWebPlayerSource = {
|
|
155
|
+
type: 'playlist';
|
|
156
|
+
playlistCode: string;
|
|
157
|
+
} | {
|
|
158
|
+
type: 'radio';
|
|
159
|
+
};
|
|
160
|
+
interface SynxedWebPlayerOptions {
|
|
161
|
+
/** Mount target. If omitted, a fixed overlay root is appended to `document.body`. */
|
|
162
|
+
container?: HTMLElement;
|
|
163
|
+
apiKey: string;
|
|
164
|
+
serverUrl: string;
|
|
165
|
+
source: SynxedWebPlayerSource;
|
|
166
|
+
mode?: SynxedWebPlayerMode;
|
|
167
|
+
theme?: SynxedWebPlayerTheme;
|
|
168
|
+
position?: SynxedWebPlayerPosition;
|
|
169
|
+
avatarUrl?: string;
|
|
170
|
+
attribution?: string;
|
|
171
|
+
nowPlayingPollMs?: number;
|
|
172
|
+
powerByLabel?: string;
|
|
173
|
+
className?: string;
|
|
174
|
+
/** Extra inline styles on the outer shell */
|
|
175
|
+
style?: Partial<CSSStyleDeclaration>;
|
|
176
|
+
onMiniClick?: () => void;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Framework-agnostic web overlay player (vanilla DOM).
|
|
181
|
+
* Works in React, Vue, Angular, or plain HTML β no React dependency.
|
|
182
|
+
*/
|
|
183
|
+
declare class SynxedWebPlayer {
|
|
184
|
+
private readonly options;
|
|
185
|
+
private readonly root;
|
|
186
|
+
private readonly ownsRoot;
|
|
187
|
+
private readonly theme;
|
|
188
|
+
private engine;
|
|
189
|
+
private pollTimer;
|
|
190
|
+
private destroyed;
|
|
191
|
+
private isPlaying;
|
|
192
|
+
private currentTrack;
|
|
193
|
+
private nowPlaying;
|
|
194
|
+
private avatarRing;
|
|
195
|
+
private titleEl;
|
|
196
|
+
private artistInlineEl;
|
|
197
|
+
private artistBlockEl;
|
|
198
|
+
private footerEl;
|
|
199
|
+
private playBtn;
|
|
200
|
+
constructor(options: SynxedWebPlayerOptions);
|
|
201
|
+
/** Convenience factory β same as `new SynxedWebPlayer(options)`. */
|
|
202
|
+
static mount(options: SynxedWebPlayerOptions): SynxedWebPlayer;
|
|
203
|
+
/** Underlying streaming engine (advanced integrations). */
|
|
204
|
+
get player(): SynxedPlayer | null;
|
|
205
|
+
get element(): HTMLElement;
|
|
206
|
+
destroy(): void;
|
|
207
|
+
private get isRadio();
|
|
208
|
+
private get mode();
|
|
209
|
+
private mountShell;
|
|
210
|
+
private buildAvatar;
|
|
211
|
+
private buildMini;
|
|
212
|
+
private buildWide;
|
|
213
|
+
private buildLarge;
|
|
214
|
+
private roundControlStyle;
|
|
215
|
+
private createPlayButton;
|
|
216
|
+
private buildVisualizer;
|
|
217
|
+
private decoLines;
|
|
218
|
+
private initEngine;
|
|
219
|
+
private startNowPlayingPoll;
|
|
220
|
+
private stopNowPlayingPoll;
|
|
221
|
+
private displayLine;
|
|
222
|
+
private refreshLabels;
|
|
223
|
+
private setPlayingVisual;
|
|
224
|
+
private togglePlay;
|
|
102
225
|
}
|
|
103
226
|
|
|
104
|
-
export { ContentKind, ErrorCode, type PlayPlaylistOptions, type PlaySongOptions, type PlayerState, type SynxedConfig, SynxedConnectionError, SynxedError, type SynxedEvents, SynxedPlaybackError, SynxedPlayer, SynxedProtocolError, type TrackInfo };
|
|
227
|
+
export { ContentKind, ErrorCode, type PlayPlaylistOptions, type PlayRadioOptions, type PlaySongOptions, type PlayerState, type RadioNowPlaying, type SynxedConfig, SynxedConnectionError, SynxedError, type SynxedEvents, SynxedPlaybackError, SynxedPlayer, SynxedProtocolError, SynxedWebPlayer, type SynxedWebPlayerMode, type SynxedWebPlayerOptions, type SynxedWebPlayerPlacement, type SynxedWebPlayerPosition, type SynxedWebPlayerSource, type SynxedWebPlayerTheme, type TrackInfo, fetchRadioNowPlaying };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,23 @@ declare class EventEmitter<Events extends Record<string, any>> {
|
|
|
7
7
|
removeAllListeners(): void;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
declare enum ContentKind {
|
|
11
|
+
UNSPECIFIED = 0,
|
|
12
|
+
SONG = 1,
|
|
13
|
+
PLAYLIST = 2,
|
|
14
|
+
CATEGORY = 3,
|
|
15
|
+
RADIO = 4
|
|
16
|
+
}
|
|
17
|
+
declare enum ErrorCode {
|
|
18
|
+
UNSPECIFIED = 0,
|
|
19
|
+
UNAUTHORIZED = 1,
|
|
20
|
+
VALIDATION_ERROR = 2,
|
|
21
|
+
NOT_FOUND = 3,
|
|
22
|
+
PROCESSING = 4,
|
|
23
|
+
SERVICE_UNAVAILABLE = 5,
|
|
24
|
+
BAD_PROTOBUF = 6
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
interface SynxedConfig {
|
|
11
28
|
apiKey: string;
|
|
12
29
|
serverUrl: string;
|
|
@@ -21,8 +38,18 @@ interface PlayPlaylistOptions {
|
|
|
21
38
|
playlistCode: string;
|
|
22
39
|
listenerId?: string;
|
|
23
40
|
}
|
|
41
|
+
interface PlayRadioOptions {
|
|
42
|
+
listenerId?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Response shape from `GET /radio/now-playing` */
|
|
45
|
+
interface RadioNowPlaying {
|
|
46
|
+
title: string;
|
|
47
|
+
station?: string;
|
|
48
|
+
isLive?: boolean;
|
|
49
|
+
}
|
|
24
50
|
interface TrackInfo {
|
|
25
51
|
id: string;
|
|
52
|
+
kind: "catalog" | "internal";
|
|
26
53
|
title?: string;
|
|
27
54
|
artist?: string;
|
|
28
55
|
duration?: number;
|
|
@@ -30,7 +57,7 @@ interface TrackInfo {
|
|
|
30
57
|
}
|
|
31
58
|
interface PlayerState {
|
|
32
59
|
status: 'idle' | 'loading' | 'playing' | 'paused' | 'error';
|
|
33
|
-
currentTrack
|
|
60
|
+
currentTrack: TrackInfo | null;
|
|
34
61
|
currentTime: number;
|
|
35
62
|
duration: number;
|
|
36
63
|
volume: number;
|
|
@@ -42,6 +69,7 @@ interface SynxedEvents {
|
|
|
42
69
|
duration: number;
|
|
43
70
|
}) => void;
|
|
44
71
|
trackChange: (track: TrackInfo) => void;
|
|
72
|
+
queueUpdated: (tracks: TrackInfo[]) => void;
|
|
45
73
|
error: (error: Error) => void;
|
|
46
74
|
connected: () => void;
|
|
47
75
|
disconnected: (reason: string) => void;
|
|
@@ -53,18 +81,31 @@ declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
|
|
|
53
81
|
private playlist;
|
|
54
82
|
private config;
|
|
55
83
|
private status;
|
|
84
|
+
private volume;
|
|
85
|
+
private activeContentKind;
|
|
86
|
+
private heartbeatTimer;
|
|
56
87
|
constructor(config: SynxedConfig);
|
|
88
|
+
get currentTrack(): TrackInfo | null;
|
|
89
|
+
/** Active playback init kind from the last `play*` call (RADIO, PLAYLIST, SONG, β¦). */
|
|
90
|
+
get contentKind(): ContentKind;
|
|
91
|
+
private controlPositionMs;
|
|
57
92
|
private setupListeners;
|
|
58
93
|
private connect;
|
|
59
94
|
playSong(options: PlaySongOptions): Promise<void>;
|
|
60
95
|
playPlaylist(options: PlayPlaylistOptions): Promise<void>;
|
|
96
|
+
playRadio(options?: PlayRadioOptions): Promise<void>;
|
|
61
97
|
pause(): void;
|
|
62
98
|
resume(): void;
|
|
63
99
|
stop(): void;
|
|
64
100
|
skip(): void;
|
|
101
|
+
previous(): void;
|
|
102
|
+
skipTo(index: number): void;
|
|
65
103
|
seek(ms: number): void;
|
|
66
104
|
setVolume(volume: number): void;
|
|
105
|
+
private positionMsForAnalytics;
|
|
67
106
|
private emitAnalytics;
|
|
107
|
+
private clearHeartbeat;
|
|
108
|
+
private startHeartbeat;
|
|
68
109
|
private handleServerMessage;
|
|
69
110
|
private handleTrackEnded;
|
|
70
111
|
private updateStatus;
|
|
@@ -85,20 +126,102 @@ declare class SynxedProtocolError extends SynxedError {
|
|
|
85
126
|
constructor(message: string);
|
|
86
127
|
}
|
|
87
128
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Fetches live radio "now playing" metadata from the Synxed backend.
|
|
131
|
+
* Poll every 10β15 seconds to keep UI in sync (see integration docs).
|
|
132
|
+
*/
|
|
133
|
+
declare function fetchRadioNowPlaying(serverUrl: string, init?: RequestInit): Promise<RadioNowPlaying | null>;
|
|
134
|
+
|
|
135
|
+
type SynxedWebPlayerMode = 'mini' | 'wide' | 'large';
|
|
136
|
+
type SynxedWebPlayerPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'bottom-center';
|
|
137
|
+
interface SynxedWebPlayerTheme {
|
|
138
|
+
accent?: string;
|
|
139
|
+
accentMuted?: string;
|
|
140
|
+
background?: string;
|
|
141
|
+
backgroundInner?: string;
|
|
142
|
+
border?: string;
|
|
143
|
+
text?: string;
|
|
144
|
+
textMuted?: string;
|
|
145
|
+
stationText?: string;
|
|
146
|
+
liveDot?: string;
|
|
147
|
+
glow?: string;
|
|
93
148
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
149
|
+
interface SynxedWebPlayerPosition {
|
|
150
|
+
placement?: SynxedWebPlayerPlacement;
|
|
151
|
+
offsetX?: number;
|
|
152
|
+
offsetY?: number;
|
|
153
|
+
}
|
|
154
|
+
type SynxedWebPlayerSource = {
|
|
155
|
+
type: 'playlist';
|
|
156
|
+
playlistCode: string;
|
|
157
|
+
} | {
|
|
158
|
+
type: 'radio';
|
|
159
|
+
};
|
|
160
|
+
interface SynxedWebPlayerOptions {
|
|
161
|
+
/** Mount target. If omitted, a fixed overlay root is appended to `document.body`. */
|
|
162
|
+
container?: HTMLElement;
|
|
163
|
+
apiKey: string;
|
|
164
|
+
serverUrl: string;
|
|
165
|
+
source: SynxedWebPlayerSource;
|
|
166
|
+
mode?: SynxedWebPlayerMode;
|
|
167
|
+
theme?: SynxedWebPlayerTheme;
|
|
168
|
+
position?: SynxedWebPlayerPosition;
|
|
169
|
+
avatarUrl?: string;
|
|
170
|
+
attribution?: string;
|
|
171
|
+
nowPlayingPollMs?: number;
|
|
172
|
+
powerByLabel?: string;
|
|
173
|
+
className?: string;
|
|
174
|
+
/** Extra inline styles on the outer shell */
|
|
175
|
+
style?: Partial<CSSStyleDeclaration>;
|
|
176
|
+
onMiniClick?: () => void;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Framework-agnostic web overlay player (vanilla DOM).
|
|
181
|
+
* Works in React, Vue, Angular, or plain HTML β no React dependency.
|
|
182
|
+
*/
|
|
183
|
+
declare class SynxedWebPlayer {
|
|
184
|
+
private readonly options;
|
|
185
|
+
private readonly root;
|
|
186
|
+
private readonly ownsRoot;
|
|
187
|
+
private readonly theme;
|
|
188
|
+
private engine;
|
|
189
|
+
private pollTimer;
|
|
190
|
+
private destroyed;
|
|
191
|
+
private isPlaying;
|
|
192
|
+
private currentTrack;
|
|
193
|
+
private nowPlaying;
|
|
194
|
+
private avatarRing;
|
|
195
|
+
private titleEl;
|
|
196
|
+
private artistInlineEl;
|
|
197
|
+
private artistBlockEl;
|
|
198
|
+
private footerEl;
|
|
199
|
+
private playBtn;
|
|
200
|
+
constructor(options: SynxedWebPlayerOptions);
|
|
201
|
+
/** Convenience factory β same as `new SynxedWebPlayer(options)`. */
|
|
202
|
+
static mount(options: SynxedWebPlayerOptions): SynxedWebPlayer;
|
|
203
|
+
/** Underlying streaming engine (advanced integrations). */
|
|
204
|
+
get player(): SynxedPlayer | null;
|
|
205
|
+
get element(): HTMLElement;
|
|
206
|
+
destroy(): void;
|
|
207
|
+
private get isRadio();
|
|
208
|
+
private get mode();
|
|
209
|
+
private mountShell;
|
|
210
|
+
private buildAvatar;
|
|
211
|
+
private buildMini;
|
|
212
|
+
private buildWide;
|
|
213
|
+
private buildLarge;
|
|
214
|
+
private roundControlStyle;
|
|
215
|
+
private createPlayButton;
|
|
216
|
+
private buildVisualizer;
|
|
217
|
+
private decoLines;
|
|
218
|
+
private initEngine;
|
|
219
|
+
private startNowPlayingPoll;
|
|
220
|
+
private stopNowPlayingPoll;
|
|
221
|
+
private displayLine;
|
|
222
|
+
private refreshLabels;
|
|
223
|
+
private setPlayingVisual;
|
|
224
|
+
private togglePlay;
|
|
102
225
|
}
|
|
103
226
|
|
|
104
|
-
export { ContentKind, ErrorCode, type PlayPlaylistOptions, type PlaySongOptions, type PlayerState, type SynxedConfig, SynxedConnectionError, SynxedError, type SynxedEvents, SynxedPlaybackError, SynxedPlayer, SynxedProtocolError, type TrackInfo };
|
|
227
|
+
export { ContentKind, ErrorCode, type PlayPlaylistOptions, type PlayRadioOptions, type PlaySongOptions, type PlayerState, type RadioNowPlaying, type SynxedConfig, SynxedConnectionError, SynxedError, type SynxedEvents, SynxedPlaybackError, SynxedPlayer, SynxedProtocolError, SynxedWebPlayer, type SynxedWebPlayerMode, type SynxedWebPlayerOptions, type SynxedWebPlayerPlacement, type SynxedWebPlayerPosition, type SynxedWebPlayerSource, type SynxedWebPlayerTheme, type TrackInfo, fetchRadioNowPlaying };
|