synxed-sdk 0.2.2 → 0.2.4
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 +2 -0
- package/dist/index.d.mts +151 -7
- package/dist/index.d.ts +151 -7
- package/dist/index.js +31 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -10
package/README.md
CHANGED
|
@@ -96,6 +96,7 @@ const ui = SynxedWebPlayer.mount({
|
|
|
96
96
|
background: "#0a0707",
|
|
97
97
|
},
|
|
98
98
|
position: { placement: "top-left", offsetX: 16, offsetY: 16 },
|
|
99
|
+
draggable: true,
|
|
99
100
|
});
|
|
100
101
|
|
|
101
102
|
// Later: ui.destroy();
|
|
@@ -113,6 +114,7 @@ SynxedWebPlayer.mount({
|
|
|
113
114
|
```
|
|
114
115
|
|
|
115
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.
|
|
116
118
|
|
|
117
119
|
In React/Vue, create the player in `useEffect` / `onMounted` and call `destroy()` on teardown — same class, no separate React package.
|
|
118
120
|
|
package/dist/index.d.mts
CHANGED
|
@@ -12,7 +12,14 @@ declare enum ContentKind {
|
|
|
12
12
|
SONG = 1,
|
|
13
13
|
PLAYLIST = 2,
|
|
14
14
|
CATEGORY = 3,
|
|
15
|
-
RADIO = 4
|
|
15
|
+
RADIO = 4,
|
|
16
|
+
VOICE = 5
|
|
17
|
+
}
|
|
18
|
+
declare enum SdkVoiceAction {
|
|
19
|
+
VOICE_UNSPECIFIED = 0,
|
|
20
|
+
VOICE_START = 1,
|
|
21
|
+
VOICE_CHUNK = 2,
|
|
22
|
+
VOICE_END = 3
|
|
16
23
|
}
|
|
17
24
|
declare enum ErrorCode {
|
|
18
25
|
UNSPECIFIED = 0,
|
|
@@ -32,6 +39,8 @@ interface SynxedConfig {
|
|
|
32
39
|
/** Listening context sent on init for ad targeting (e.g. "menu", "gameplay"). */
|
|
33
40
|
gameContext?: string;
|
|
34
41
|
deviceType?: string;
|
|
42
|
+
/** Default voice silence detection settings (used by `beginVoiceHold`). */
|
|
43
|
+
voice?: Omit<VoiceHoldOptions, 'listenerId'>;
|
|
35
44
|
}
|
|
36
45
|
interface PlaySongOptions {
|
|
37
46
|
catalogTrackId?: string;
|
|
@@ -66,6 +75,26 @@ interface PlayerState {
|
|
|
66
75
|
duration: number;
|
|
67
76
|
volume: number;
|
|
68
77
|
}
|
|
78
|
+
type VoiceState = 'idle' | 'listening' | 'processing' | 'ready' | 'error';
|
|
79
|
+
interface VoiceResult {
|
|
80
|
+
status: string;
|
|
81
|
+
transcript?: string;
|
|
82
|
+
playlistCode?: string;
|
|
83
|
+
playlistName?: string;
|
|
84
|
+
}
|
|
85
|
+
interface VoiceHoldOptions {
|
|
86
|
+
listenerId?: string;
|
|
87
|
+
/** Auto-end capture when the user stops talking. Default `true`. */
|
|
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
|
+
/** Max capture length in ms. Default `30000`. */
|
|
96
|
+
maxDurationMs?: number;
|
|
97
|
+
}
|
|
69
98
|
interface SynxedEvents {
|
|
70
99
|
stateChange: (state: PlayerState) => void;
|
|
71
100
|
timeUpdate: (time: {
|
|
@@ -76,6 +105,13 @@ interface SynxedEvents {
|
|
|
76
105
|
queueUpdated: (tracks: TrackInfo[]) => void;
|
|
77
106
|
adStart: (ad: AdInfo) => void;
|
|
78
107
|
adEnd: (ad: AdInfo) => void;
|
|
108
|
+
adSkipUpdate: (state: AdSkipState) => void;
|
|
109
|
+
voiceStateChange: (state: VoiceState) => void;
|
|
110
|
+
voiceResult: (result: VoiceResult) => void;
|
|
111
|
+
/** Mic level crossed above silence threshold — user started speaking. */
|
|
112
|
+
voiceSpeechStart: () => void;
|
|
113
|
+
/** Silence detected after speech — user stopped talking. */
|
|
114
|
+
voiceSpeechEnd: () => void;
|
|
79
115
|
error: (error: Error) => void;
|
|
80
116
|
connected: () => void;
|
|
81
117
|
disconnected: (reason: string) => void;
|
|
@@ -93,29 +129,62 @@ interface AdInfo {
|
|
|
93
129
|
companionBannerUrl: string;
|
|
94
130
|
campaignName: string;
|
|
95
131
|
}
|
|
132
|
+
/** Emitted while a skippable ad plays — use for custom skip UI (YouTube-style countdown). */
|
|
133
|
+
interface AdSkipState {
|
|
134
|
+
ad: AdInfo;
|
|
135
|
+
/** Seconds remaining before skip is allowed (0 when `canSkip` is true). */
|
|
136
|
+
countdownSeconds: number;
|
|
137
|
+
/** True once the ad has played for `skipAfterSeconds` (e.g. 5s). */
|
|
138
|
+
canSkip: boolean;
|
|
139
|
+
}
|
|
96
140
|
|
|
97
141
|
declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
|
|
98
142
|
private transport;
|
|
99
143
|
private audio;
|
|
100
144
|
private playlist;
|
|
145
|
+
private voiceCapture;
|
|
101
146
|
private config;
|
|
102
147
|
private status;
|
|
103
148
|
private volume;
|
|
104
149
|
private activeContentKind;
|
|
105
150
|
private heartbeatTimer;
|
|
106
151
|
private adPlaying;
|
|
107
|
-
private
|
|
152
|
+
private _currentAd;
|
|
153
|
+
private _voiceState;
|
|
154
|
+
private voiceMimeType;
|
|
155
|
+
private voiceListenerId;
|
|
156
|
+
private pendingPlaybackLoad;
|
|
157
|
+
private voiceAutoEndOnSilence;
|
|
108
158
|
constructor(config: SynxedConfig);
|
|
109
159
|
get currentTrack(): TrackInfo | null;
|
|
110
160
|
/** Active playback init kind from the last `play*` call (RADIO, PLAYLIST, SONG, …). */
|
|
111
161
|
get contentKind(): ContentKind;
|
|
162
|
+
get currentAd(): AdInfo | null;
|
|
163
|
+
get isAdPlaying(): boolean;
|
|
164
|
+
/** True when a skippable ad has played for at least `skipAfterSeconds` (default 5s). */
|
|
165
|
+
canSkipAd(): boolean;
|
|
166
|
+
/** Seconds until skip unlocks; `0` when skip is available; `null` when not in a skippable ad. */
|
|
167
|
+
getAdSkipCountdownSeconds(): number | null;
|
|
168
|
+
get voiceState(): VoiceState;
|
|
112
169
|
private controlPositionMs;
|
|
113
170
|
private setupListeners;
|
|
171
|
+
private resolveVoiceOptions;
|
|
114
172
|
private connect;
|
|
115
173
|
private buildInitExtras;
|
|
116
174
|
playSong(options: PlaySongOptions): Promise<void>;
|
|
117
175
|
playPlaylist(options: PlayPlaylistOptions): Promise<void>;
|
|
118
176
|
playRadio(options?: PlayRadioOptions): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Press-and-hold the DJ avatar (or call manually): start mic capture and stream chunks.
|
|
179
|
+
* Ends automatically when the user stops talking (`voiceSpeechEnd`) or call `endVoiceHold()`.
|
|
180
|
+
*/
|
|
181
|
+
beginVoiceHold(options?: VoiceHoldOptions): Promise<void>;
|
|
182
|
+
/**
|
|
183
|
+
* Release press-and-hold: flush final audio chunk and request playlist generation.
|
|
184
|
+
*/
|
|
185
|
+
endVoiceHold(): Promise<void>;
|
|
186
|
+
/** Cancel an in-progress voice hold without sending audio to the server. */
|
|
187
|
+
cancelVoiceHold(): void;
|
|
119
188
|
pause(): void;
|
|
120
189
|
resume(): void;
|
|
121
190
|
stop(): void;
|
|
@@ -129,7 +198,11 @@ declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
|
|
|
129
198
|
private clearHeartbeat;
|
|
130
199
|
private startHeartbeat;
|
|
131
200
|
private applyContentSummary;
|
|
201
|
+
private emitAdSkipUpdate;
|
|
202
|
+
private setVoiceState;
|
|
203
|
+
private handleVoiceAck;
|
|
132
204
|
private handleServerMessage;
|
|
205
|
+
private startContentPlayback;
|
|
133
206
|
private handleAdPlayback;
|
|
134
207
|
skipAd(): void;
|
|
135
208
|
clickAd(): void;
|
|
@@ -168,6 +241,15 @@ declare class TransportManager extends EventEmitter<TransportEvents> {
|
|
|
168
241
|
sendInit(params: any): void;
|
|
169
242
|
sendControl(params: any): void;
|
|
170
243
|
sendAnalytics(params: any): void;
|
|
244
|
+
sendVoice(params: {
|
|
245
|
+
action: number;
|
|
246
|
+
audioData?: Uint8Array;
|
|
247
|
+
mimeType?: string;
|
|
248
|
+
sequence?: number;
|
|
249
|
+
listenerId?: string;
|
|
250
|
+
deviceType?: string;
|
|
251
|
+
gameContext?: string;
|
|
252
|
+
}): void;
|
|
171
253
|
get isConnected(): boolean;
|
|
172
254
|
private sendBytes;
|
|
173
255
|
}
|
|
@@ -178,8 +260,51 @@ declare class TransportManager extends EventEmitter<TransportEvents> {
|
|
|
178
260
|
*/
|
|
179
261
|
declare function fetchRadioNowPlaying(serverUrl: string, init?: RequestInit): Promise<RadioNowPlaying | null>;
|
|
180
262
|
|
|
181
|
-
|
|
182
|
-
|
|
263
|
+
interface VoiceCaptureEvents {
|
|
264
|
+
chunk: (data: Uint8Array, sequence: number) => void;
|
|
265
|
+
error: (error: Error) => void;
|
|
266
|
+
/** User started speaking (level crossed above silence threshold). */
|
|
267
|
+
speechStart: () => void;
|
|
268
|
+
/** User stopped speaking (silence after speech, or max duration reached). */
|
|
269
|
+
speechEnd: () => void;
|
|
270
|
+
}
|
|
271
|
+
interface VoiceCaptureOptions {
|
|
272
|
+
/** RMS level below which audio counts as silence (0–1). Default `0.018`. */
|
|
273
|
+
silenceThreshold?: number;
|
|
274
|
+
/** Ms of silence after speech before `speechEnd`. Default `1500`. */
|
|
275
|
+
silenceDurationMs?: number;
|
|
276
|
+
/** Minimum speech ms before silence can end capture. Default `400`. */
|
|
277
|
+
minSpeechMs?: number;
|
|
278
|
+
/** Safety cap — auto `speechEnd` after this many ms. Default `30000`. */
|
|
279
|
+
maxDurationMs?: number;
|
|
280
|
+
/** Monitor mic levels for end-of-speech. Default `true`. */
|
|
281
|
+
detectSilence?: boolean;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Native MediaRecorder capture with optional Web Audio silence detection.
|
|
285
|
+
*/
|
|
286
|
+
declare class VoiceCaptureManager extends EventEmitter<VoiceCaptureEvents> {
|
|
287
|
+
private stream;
|
|
288
|
+
private recorder;
|
|
289
|
+
private sequence;
|
|
290
|
+
private capturing;
|
|
291
|
+
private mimeType;
|
|
292
|
+
private audioContext;
|
|
293
|
+
private silenceRaf;
|
|
294
|
+
private speechEndEmitted;
|
|
295
|
+
private captureOptions;
|
|
296
|
+
get isCapturing(): boolean;
|
|
297
|
+
get activeMimeType(): string;
|
|
298
|
+
start(options?: VoiceCaptureOptions): Promise<string>;
|
|
299
|
+
stop(): Promise<void>;
|
|
300
|
+
cancel(): void;
|
|
301
|
+
private startSilenceMonitor;
|
|
302
|
+
private stopSilenceMonitor;
|
|
303
|
+
private releaseStream;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
type SynxedWebPlayerMode = "mini" | "wide" | "large";
|
|
307
|
+
type SynxedWebPlayerPlacement = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "bottom-center";
|
|
183
308
|
interface SynxedWebPlayerTheme {
|
|
184
309
|
accent?: string;
|
|
185
310
|
accentMuted?: string;
|
|
@@ -198,10 +323,10 @@ interface SynxedWebPlayerPosition {
|
|
|
198
323
|
offsetY?: number;
|
|
199
324
|
}
|
|
200
325
|
type SynxedWebPlayerSource = {
|
|
201
|
-
type:
|
|
326
|
+
type: "playlist";
|
|
202
327
|
playlistCode: string;
|
|
203
328
|
} | {
|
|
204
|
-
type:
|
|
329
|
+
type: "radio";
|
|
205
330
|
};
|
|
206
331
|
interface SynxedWebPlayerOptions {
|
|
207
332
|
/** Mount target. If omitted, a fixed overlay root is appended to `document.body`. */
|
|
@@ -213,6 +338,7 @@ interface SynxedWebPlayerOptions {
|
|
|
213
338
|
theme?: SynxedWebPlayerTheme;
|
|
214
339
|
position?: SynxedWebPlayerPosition;
|
|
215
340
|
avatarUrl?: string;
|
|
341
|
+
voiceAvatarUrl?: string;
|
|
216
342
|
attribution?: string;
|
|
217
343
|
nowPlayingPollMs?: number;
|
|
218
344
|
powerByLabel?: string;
|
|
@@ -221,6 +347,9 @@ interface SynxedWebPlayerOptions {
|
|
|
221
347
|
style?: Partial<CSSStyleDeclaration>;
|
|
222
348
|
onMiniClick?: () => void;
|
|
223
349
|
draggable?: boolean;
|
|
350
|
+
/** Press-and-hold DJ avatar for voice commands. Set `false` to disable. Default `true`. */
|
|
351
|
+
enableVoice?: boolean;
|
|
352
|
+
listenerId?: string;
|
|
224
353
|
}
|
|
225
354
|
|
|
226
355
|
/**
|
|
@@ -248,6 +377,9 @@ declare class SynxedWebPlayer {
|
|
|
248
377
|
private artistBlockEl;
|
|
249
378
|
private footerEl;
|
|
250
379
|
private playBtn;
|
|
380
|
+
private avatarVoiceHolding;
|
|
381
|
+
private suppressMiniClick;
|
|
382
|
+
private voiceAvatarPreview;
|
|
251
383
|
constructor(options: SynxedWebPlayerOptions);
|
|
252
384
|
/** Convenience factory — same as `new SynxedWebPlayer(options)`. */
|
|
253
385
|
static mount(options: SynxedWebPlayerOptions): SynxedWebPlayer;
|
|
@@ -255,12 +387,14 @@ declare class SynxedWebPlayer {
|
|
|
255
387
|
get player(): SynxedPlayer | null;
|
|
256
388
|
get element(): HTMLElement;
|
|
257
389
|
destroy(): void;
|
|
390
|
+
private get voiceEnabled();
|
|
258
391
|
private get isRadio();
|
|
259
392
|
private get mode();
|
|
260
393
|
private mountShell;
|
|
261
394
|
private makeDraggable;
|
|
262
395
|
private buildAvatar;
|
|
263
396
|
private buildMini;
|
|
397
|
+
private handleMiniActivate;
|
|
264
398
|
private buildWide;
|
|
265
399
|
private buildLarge;
|
|
266
400
|
private roundControlStyle;
|
|
@@ -268,8 +402,18 @@ declare class SynxedWebPlayer {
|
|
|
268
402
|
private buildVisualizer;
|
|
269
403
|
private decoLines;
|
|
270
404
|
private initEngine;
|
|
405
|
+
/** Press-and-hold DJ avatar to stream voice; release or silence auto-ends capture. */
|
|
406
|
+
private wireAvatarVoiceHold;
|
|
407
|
+
private applyVoiceVisual;
|
|
408
|
+
private isVoiceAvatarActive;
|
|
409
|
+
private getAvatarImageUrl;
|
|
410
|
+
private refreshAvatarImage;
|
|
271
411
|
private startNowPlayingPoll;
|
|
272
412
|
private stopNowPlayingPoll;
|
|
413
|
+
private handleSkipClick;
|
|
414
|
+
/** YouTube-style ad skip pill: "Ad · 5" countdown → "Skip Ad". */
|
|
415
|
+
private applyAdSkipUi;
|
|
416
|
+
private restoreTrackSkipButton;
|
|
273
417
|
private displayLine;
|
|
274
418
|
private refreshLabels;
|
|
275
419
|
private updateAvatar;
|
|
@@ -277,4 +421,4 @@ declare class SynxedWebPlayer {
|
|
|
277
421
|
private togglePlay;
|
|
278
422
|
}
|
|
279
423
|
|
|
280
|
-
export { type AdInfo, 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, type TransportEvents, TransportManager, buildSdkWebSocketUrl, fetchRadioNowPlaying };
|
|
424
|
+
export { type AdInfo, type AdSkipState, ContentKind, ErrorCode, type PlayPlaylistOptions, type PlayRadioOptions, type PlaySongOptions, type PlayerState, type RadioNowPlaying, SdkVoiceAction, type SynxedConfig, SynxedConnectionError, SynxedError, type SynxedEvents, SynxedPlaybackError, SynxedPlayer, SynxedProtocolError, SynxedWebPlayer, type SynxedWebPlayerMode, type SynxedWebPlayerOptions, type SynxedWebPlayerPlacement, type SynxedWebPlayerPosition, type SynxedWebPlayerSource, type SynxedWebPlayerTheme, type TrackInfo, type TransportEvents, TransportManager, VoiceCaptureManager, type VoiceCaptureOptions, type VoiceHoldOptions, type VoiceResult, type VoiceState, buildSdkWebSocketUrl, fetchRadioNowPlaying };
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,14 @@ declare enum ContentKind {
|
|
|
12
12
|
SONG = 1,
|
|
13
13
|
PLAYLIST = 2,
|
|
14
14
|
CATEGORY = 3,
|
|
15
|
-
RADIO = 4
|
|
15
|
+
RADIO = 4,
|
|
16
|
+
VOICE = 5
|
|
17
|
+
}
|
|
18
|
+
declare enum SdkVoiceAction {
|
|
19
|
+
VOICE_UNSPECIFIED = 0,
|
|
20
|
+
VOICE_START = 1,
|
|
21
|
+
VOICE_CHUNK = 2,
|
|
22
|
+
VOICE_END = 3
|
|
16
23
|
}
|
|
17
24
|
declare enum ErrorCode {
|
|
18
25
|
UNSPECIFIED = 0,
|
|
@@ -32,6 +39,8 @@ interface SynxedConfig {
|
|
|
32
39
|
/** Listening context sent on init for ad targeting (e.g. "menu", "gameplay"). */
|
|
33
40
|
gameContext?: string;
|
|
34
41
|
deviceType?: string;
|
|
42
|
+
/** Default voice silence detection settings (used by `beginVoiceHold`). */
|
|
43
|
+
voice?: Omit<VoiceHoldOptions, 'listenerId'>;
|
|
35
44
|
}
|
|
36
45
|
interface PlaySongOptions {
|
|
37
46
|
catalogTrackId?: string;
|
|
@@ -66,6 +75,26 @@ interface PlayerState {
|
|
|
66
75
|
duration: number;
|
|
67
76
|
volume: number;
|
|
68
77
|
}
|
|
78
|
+
type VoiceState = 'idle' | 'listening' | 'processing' | 'ready' | 'error';
|
|
79
|
+
interface VoiceResult {
|
|
80
|
+
status: string;
|
|
81
|
+
transcript?: string;
|
|
82
|
+
playlistCode?: string;
|
|
83
|
+
playlistName?: string;
|
|
84
|
+
}
|
|
85
|
+
interface VoiceHoldOptions {
|
|
86
|
+
listenerId?: string;
|
|
87
|
+
/** Auto-end capture when the user stops talking. Default `true`. */
|
|
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
|
+
/** Max capture length in ms. Default `30000`. */
|
|
96
|
+
maxDurationMs?: number;
|
|
97
|
+
}
|
|
69
98
|
interface SynxedEvents {
|
|
70
99
|
stateChange: (state: PlayerState) => void;
|
|
71
100
|
timeUpdate: (time: {
|
|
@@ -76,6 +105,13 @@ interface SynxedEvents {
|
|
|
76
105
|
queueUpdated: (tracks: TrackInfo[]) => void;
|
|
77
106
|
adStart: (ad: AdInfo) => void;
|
|
78
107
|
adEnd: (ad: AdInfo) => void;
|
|
108
|
+
adSkipUpdate: (state: AdSkipState) => void;
|
|
109
|
+
voiceStateChange: (state: VoiceState) => void;
|
|
110
|
+
voiceResult: (result: VoiceResult) => void;
|
|
111
|
+
/** Mic level crossed above silence threshold — user started speaking. */
|
|
112
|
+
voiceSpeechStart: () => void;
|
|
113
|
+
/** Silence detected after speech — user stopped talking. */
|
|
114
|
+
voiceSpeechEnd: () => void;
|
|
79
115
|
error: (error: Error) => void;
|
|
80
116
|
connected: () => void;
|
|
81
117
|
disconnected: (reason: string) => void;
|
|
@@ -93,29 +129,62 @@ interface AdInfo {
|
|
|
93
129
|
companionBannerUrl: string;
|
|
94
130
|
campaignName: string;
|
|
95
131
|
}
|
|
132
|
+
/** Emitted while a skippable ad plays — use for custom skip UI (YouTube-style countdown). */
|
|
133
|
+
interface AdSkipState {
|
|
134
|
+
ad: AdInfo;
|
|
135
|
+
/** Seconds remaining before skip is allowed (0 when `canSkip` is true). */
|
|
136
|
+
countdownSeconds: number;
|
|
137
|
+
/** True once the ad has played for `skipAfterSeconds` (e.g. 5s). */
|
|
138
|
+
canSkip: boolean;
|
|
139
|
+
}
|
|
96
140
|
|
|
97
141
|
declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
|
|
98
142
|
private transport;
|
|
99
143
|
private audio;
|
|
100
144
|
private playlist;
|
|
145
|
+
private voiceCapture;
|
|
101
146
|
private config;
|
|
102
147
|
private status;
|
|
103
148
|
private volume;
|
|
104
149
|
private activeContentKind;
|
|
105
150
|
private heartbeatTimer;
|
|
106
151
|
private adPlaying;
|
|
107
|
-
private
|
|
152
|
+
private _currentAd;
|
|
153
|
+
private _voiceState;
|
|
154
|
+
private voiceMimeType;
|
|
155
|
+
private voiceListenerId;
|
|
156
|
+
private pendingPlaybackLoad;
|
|
157
|
+
private voiceAutoEndOnSilence;
|
|
108
158
|
constructor(config: SynxedConfig);
|
|
109
159
|
get currentTrack(): TrackInfo | null;
|
|
110
160
|
/** Active playback init kind from the last `play*` call (RADIO, PLAYLIST, SONG, …). */
|
|
111
161
|
get contentKind(): ContentKind;
|
|
162
|
+
get currentAd(): AdInfo | null;
|
|
163
|
+
get isAdPlaying(): boolean;
|
|
164
|
+
/** True when a skippable ad has played for at least `skipAfterSeconds` (default 5s). */
|
|
165
|
+
canSkipAd(): boolean;
|
|
166
|
+
/** Seconds until skip unlocks; `0` when skip is available; `null` when not in a skippable ad. */
|
|
167
|
+
getAdSkipCountdownSeconds(): number | null;
|
|
168
|
+
get voiceState(): VoiceState;
|
|
112
169
|
private controlPositionMs;
|
|
113
170
|
private setupListeners;
|
|
171
|
+
private resolveVoiceOptions;
|
|
114
172
|
private connect;
|
|
115
173
|
private buildInitExtras;
|
|
116
174
|
playSong(options: PlaySongOptions): Promise<void>;
|
|
117
175
|
playPlaylist(options: PlayPlaylistOptions): Promise<void>;
|
|
118
176
|
playRadio(options?: PlayRadioOptions): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Press-and-hold the DJ avatar (or call manually): start mic capture and stream chunks.
|
|
179
|
+
* Ends automatically when the user stops talking (`voiceSpeechEnd`) or call `endVoiceHold()`.
|
|
180
|
+
*/
|
|
181
|
+
beginVoiceHold(options?: VoiceHoldOptions): Promise<void>;
|
|
182
|
+
/**
|
|
183
|
+
* Release press-and-hold: flush final audio chunk and request playlist generation.
|
|
184
|
+
*/
|
|
185
|
+
endVoiceHold(): Promise<void>;
|
|
186
|
+
/** Cancel an in-progress voice hold without sending audio to the server. */
|
|
187
|
+
cancelVoiceHold(): void;
|
|
119
188
|
pause(): void;
|
|
120
189
|
resume(): void;
|
|
121
190
|
stop(): void;
|
|
@@ -129,7 +198,11 @@ declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
|
|
|
129
198
|
private clearHeartbeat;
|
|
130
199
|
private startHeartbeat;
|
|
131
200
|
private applyContentSummary;
|
|
201
|
+
private emitAdSkipUpdate;
|
|
202
|
+
private setVoiceState;
|
|
203
|
+
private handleVoiceAck;
|
|
132
204
|
private handleServerMessage;
|
|
205
|
+
private startContentPlayback;
|
|
133
206
|
private handleAdPlayback;
|
|
134
207
|
skipAd(): void;
|
|
135
208
|
clickAd(): void;
|
|
@@ -168,6 +241,15 @@ declare class TransportManager extends EventEmitter<TransportEvents> {
|
|
|
168
241
|
sendInit(params: any): void;
|
|
169
242
|
sendControl(params: any): void;
|
|
170
243
|
sendAnalytics(params: any): void;
|
|
244
|
+
sendVoice(params: {
|
|
245
|
+
action: number;
|
|
246
|
+
audioData?: Uint8Array;
|
|
247
|
+
mimeType?: string;
|
|
248
|
+
sequence?: number;
|
|
249
|
+
listenerId?: string;
|
|
250
|
+
deviceType?: string;
|
|
251
|
+
gameContext?: string;
|
|
252
|
+
}): void;
|
|
171
253
|
get isConnected(): boolean;
|
|
172
254
|
private sendBytes;
|
|
173
255
|
}
|
|
@@ -178,8 +260,51 @@ declare class TransportManager extends EventEmitter<TransportEvents> {
|
|
|
178
260
|
*/
|
|
179
261
|
declare function fetchRadioNowPlaying(serverUrl: string, init?: RequestInit): Promise<RadioNowPlaying | null>;
|
|
180
262
|
|
|
181
|
-
|
|
182
|
-
|
|
263
|
+
interface VoiceCaptureEvents {
|
|
264
|
+
chunk: (data: Uint8Array, sequence: number) => void;
|
|
265
|
+
error: (error: Error) => void;
|
|
266
|
+
/** User started speaking (level crossed above silence threshold). */
|
|
267
|
+
speechStart: () => void;
|
|
268
|
+
/** User stopped speaking (silence after speech, or max duration reached). */
|
|
269
|
+
speechEnd: () => void;
|
|
270
|
+
}
|
|
271
|
+
interface VoiceCaptureOptions {
|
|
272
|
+
/** RMS level below which audio counts as silence (0–1). Default `0.018`. */
|
|
273
|
+
silenceThreshold?: number;
|
|
274
|
+
/** Ms of silence after speech before `speechEnd`. Default `1500`. */
|
|
275
|
+
silenceDurationMs?: number;
|
|
276
|
+
/** Minimum speech ms before silence can end capture. Default `400`. */
|
|
277
|
+
minSpeechMs?: number;
|
|
278
|
+
/** Safety cap — auto `speechEnd` after this many ms. Default `30000`. */
|
|
279
|
+
maxDurationMs?: number;
|
|
280
|
+
/** Monitor mic levels for end-of-speech. Default `true`. */
|
|
281
|
+
detectSilence?: boolean;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Native MediaRecorder capture with optional Web Audio silence detection.
|
|
285
|
+
*/
|
|
286
|
+
declare class VoiceCaptureManager extends EventEmitter<VoiceCaptureEvents> {
|
|
287
|
+
private stream;
|
|
288
|
+
private recorder;
|
|
289
|
+
private sequence;
|
|
290
|
+
private capturing;
|
|
291
|
+
private mimeType;
|
|
292
|
+
private audioContext;
|
|
293
|
+
private silenceRaf;
|
|
294
|
+
private speechEndEmitted;
|
|
295
|
+
private captureOptions;
|
|
296
|
+
get isCapturing(): boolean;
|
|
297
|
+
get activeMimeType(): string;
|
|
298
|
+
start(options?: VoiceCaptureOptions): Promise<string>;
|
|
299
|
+
stop(): Promise<void>;
|
|
300
|
+
cancel(): void;
|
|
301
|
+
private startSilenceMonitor;
|
|
302
|
+
private stopSilenceMonitor;
|
|
303
|
+
private releaseStream;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
type SynxedWebPlayerMode = "mini" | "wide" | "large";
|
|
307
|
+
type SynxedWebPlayerPlacement = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "bottom-center";
|
|
183
308
|
interface SynxedWebPlayerTheme {
|
|
184
309
|
accent?: string;
|
|
185
310
|
accentMuted?: string;
|
|
@@ -198,10 +323,10 @@ interface SynxedWebPlayerPosition {
|
|
|
198
323
|
offsetY?: number;
|
|
199
324
|
}
|
|
200
325
|
type SynxedWebPlayerSource = {
|
|
201
|
-
type:
|
|
326
|
+
type: "playlist";
|
|
202
327
|
playlistCode: string;
|
|
203
328
|
} | {
|
|
204
|
-
type:
|
|
329
|
+
type: "radio";
|
|
205
330
|
};
|
|
206
331
|
interface SynxedWebPlayerOptions {
|
|
207
332
|
/** Mount target. If omitted, a fixed overlay root is appended to `document.body`. */
|
|
@@ -213,6 +338,7 @@ interface SynxedWebPlayerOptions {
|
|
|
213
338
|
theme?: SynxedWebPlayerTheme;
|
|
214
339
|
position?: SynxedWebPlayerPosition;
|
|
215
340
|
avatarUrl?: string;
|
|
341
|
+
voiceAvatarUrl?: string;
|
|
216
342
|
attribution?: string;
|
|
217
343
|
nowPlayingPollMs?: number;
|
|
218
344
|
powerByLabel?: string;
|
|
@@ -221,6 +347,9 @@ interface SynxedWebPlayerOptions {
|
|
|
221
347
|
style?: Partial<CSSStyleDeclaration>;
|
|
222
348
|
onMiniClick?: () => void;
|
|
223
349
|
draggable?: boolean;
|
|
350
|
+
/** Press-and-hold DJ avatar for voice commands. Set `false` to disable. Default `true`. */
|
|
351
|
+
enableVoice?: boolean;
|
|
352
|
+
listenerId?: string;
|
|
224
353
|
}
|
|
225
354
|
|
|
226
355
|
/**
|
|
@@ -248,6 +377,9 @@ declare class SynxedWebPlayer {
|
|
|
248
377
|
private artistBlockEl;
|
|
249
378
|
private footerEl;
|
|
250
379
|
private playBtn;
|
|
380
|
+
private avatarVoiceHolding;
|
|
381
|
+
private suppressMiniClick;
|
|
382
|
+
private voiceAvatarPreview;
|
|
251
383
|
constructor(options: SynxedWebPlayerOptions);
|
|
252
384
|
/** Convenience factory — same as `new SynxedWebPlayer(options)`. */
|
|
253
385
|
static mount(options: SynxedWebPlayerOptions): SynxedWebPlayer;
|
|
@@ -255,12 +387,14 @@ declare class SynxedWebPlayer {
|
|
|
255
387
|
get player(): SynxedPlayer | null;
|
|
256
388
|
get element(): HTMLElement;
|
|
257
389
|
destroy(): void;
|
|
390
|
+
private get voiceEnabled();
|
|
258
391
|
private get isRadio();
|
|
259
392
|
private get mode();
|
|
260
393
|
private mountShell;
|
|
261
394
|
private makeDraggable;
|
|
262
395
|
private buildAvatar;
|
|
263
396
|
private buildMini;
|
|
397
|
+
private handleMiniActivate;
|
|
264
398
|
private buildWide;
|
|
265
399
|
private buildLarge;
|
|
266
400
|
private roundControlStyle;
|
|
@@ -268,8 +402,18 @@ declare class SynxedWebPlayer {
|
|
|
268
402
|
private buildVisualizer;
|
|
269
403
|
private decoLines;
|
|
270
404
|
private initEngine;
|
|
405
|
+
/** Press-and-hold DJ avatar to stream voice; release or silence auto-ends capture. */
|
|
406
|
+
private wireAvatarVoiceHold;
|
|
407
|
+
private applyVoiceVisual;
|
|
408
|
+
private isVoiceAvatarActive;
|
|
409
|
+
private getAvatarImageUrl;
|
|
410
|
+
private refreshAvatarImage;
|
|
271
411
|
private startNowPlayingPoll;
|
|
272
412
|
private stopNowPlayingPoll;
|
|
413
|
+
private handleSkipClick;
|
|
414
|
+
/** YouTube-style ad skip pill: "Ad · 5" countdown → "Skip Ad". */
|
|
415
|
+
private applyAdSkipUi;
|
|
416
|
+
private restoreTrackSkipButton;
|
|
273
417
|
private displayLine;
|
|
274
418
|
private refreshLabels;
|
|
275
419
|
private updateAvatar;
|
|
@@ -277,4 +421,4 @@ declare class SynxedWebPlayer {
|
|
|
277
421
|
private togglePlay;
|
|
278
422
|
}
|
|
279
423
|
|
|
280
|
-
export { type AdInfo, 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, type TransportEvents, TransportManager, buildSdkWebSocketUrl, fetchRadioNowPlaying };
|
|
424
|
+
export { type AdInfo, type AdSkipState, ContentKind, ErrorCode, type PlayPlaylistOptions, type PlayRadioOptions, type PlaySongOptions, type PlayerState, type RadioNowPlaying, SdkVoiceAction, type SynxedConfig, SynxedConnectionError, SynxedError, type SynxedEvents, SynxedPlaybackError, SynxedPlayer, SynxedProtocolError, SynxedWebPlayer, type SynxedWebPlayerMode, type SynxedWebPlayerOptions, type SynxedWebPlayerPlacement, type SynxedWebPlayerPosition, type SynxedWebPlayerSource, type SynxedWebPlayerTheme, type TrackInfo, type TransportEvents, TransportManager, VoiceCaptureManager, type VoiceCaptureOptions, type VoiceHoldOptions, type VoiceResult, type VoiceState, buildSdkWebSocketUrl, fetchRadioNowPlaying };
|