synxed-sdk 0.2.3 → 0.2.5

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/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,69 @@ 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 currentAd;
152
+ private _currentAd;
153
+ private _voiceState;
154
+ private voiceMimeType;
155
+ private voiceListenerId;
156
+ private pendingPlaybackLoad;
157
+ private voiceAutoEndOnSilence;
158
+ private pausedForVoice;
108
159
  constructor(config: SynxedConfig);
109
160
  get currentTrack(): TrackInfo | null;
110
161
  /** Active playback init kind from the last `play*` call (RADIO, PLAYLIST, SONG, …). */
111
162
  get contentKind(): ContentKind;
163
+ get currentAd(): AdInfo | null;
164
+ get isAdPlaying(): boolean;
165
+ /** True when a skippable ad has played for at least `skipAfterSeconds` (default 5s). */
166
+ canSkipAd(): boolean;
167
+ /** Seconds until skip unlocks; `0` when skip is available; `null` when not in a skippable ad. */
168
+ getAdSkipCountdownSeconds(): number | null;
169
+ get voiceState(): VoiceState;
112
170
  private controlPositionMs;
113
171
  private setupListeners;
172
+ private resolveVoiceOptions;
114
173
  private connect;
115
174
  private buildInitExtras;
116
175
  playSong(options: PlaySongOptions): Promise<void>;
117
176
  playPlaylist(options: PlayPlaylistOptions): Promise<void>;
118
177
  playRadio(options?: PlayRadioOptions): Promise<void>;
178
+ /**
179
+ * Press-and-hold the DJ avatar (or call manually): start mic capture and stream chunks.
180
+ * Ends automatically when the user stops talking (`voiceSpeechEnd`) or call `endVoiceHold()`.
181
+ */
182
+ beginVoiceHold(options?: VoiceHoldOptions): Promise<void>;
183
+ /**
184
+ * Release press-and-hold: flush final audio chunk and request playlist generation.
185
+ */
186
+ endVoiceHold(): Promise<void>;
187
+ /** Cancel an in-progress voice hold without sending audio to the server. */
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;
119
195
  pause(): void;
120
196
  resume(): void;
121
197
  stop(): void;
@@ -129,7 +205,11 @@ declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
129
205
  private clearHeartbeat;
130
206
  private startHeartbeat;
131
207
  private applyContentSummary;
208
+ private emitAdSkipUpdate;
209
+ private setVoiceState;
210
+ private handleVoiceAck;
132
211
  private handleServerMessage;
212
+ private startContentPlayback;
133
213
  private handleAdPlayback;
134
214
  skipAd(): void;
135
215
  clickAd(): void;
@@ -168,6 +248,15 @@ declare class TransportManager extends EventEmitter<TransportEvents> {
168
248
  sendInit(params: any): void;
169
249
  sendControl(params: any): void;
170
250
  sendAnalytics(params: any): void;
251
+ sendVoice(params: {
252
+ action: number;
253
+ audioData?: Uint8Array;
254
+ mimeType?: string;
255
+ sequence?: number;
256
+ listenerId?: string;
257
+ deviceType?: string;
258
+ gameContext?: string;
259
+ }): void;
171
260
  get isConnected(): boolean;
172
261
  private sendBytes;
173
262
  }
@@ -178,8 +267,51 @@ declare class TransportManager extends EventEmitter<TransportEvents> {
178
267
  */
179
268
  declare function fetchRadioNowPlaying(serverUrl: string, init?: RequestInit): Promise<RadioNowPlaying | null>;
180
269
 
181
- type SynxedWebPlayerMode = 'mini' | 'wide' | 'large';
182
- type SynxedWebPlayerPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'bottom-center';
270
+ interface VoiceCaptureEvents {
271
+ chunk: (data: Uint8Array, sequence: number) => void;
272
+ error: (error: Error) => void;
273
+ /** User started speaking (level crossed above silence threshold). */
274
+ speechStart: () => void;
275
+ /** User stopped speaking (silence after speech, or max duration reached). */
276
+ speechEnd: () => void;
277
+ }
278
+ 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
+ /** Safety cap — auto `speechEnd` after this many ms. Default `30000`. */
286
+ maxDurationMs?: number;
287
+ /** Monitor mic levels for end-of-speech. Default `true`. */
288
+ detectSilence?: boolean;
289
+ }
290
+ /**
291
+ * Native MediaRecorder capture with optional Web Audio silence detection.
292
+ */
293
+ declare class VoiceCaptureManager extends EventEmitter<VoiceCaptureEvents> {
294
+ private stream;
295
+ private recorder;
296
+ private sequence;
297
+ private capturing;
298
+ private mimeType;
299
+ private audioContext;
300
+ private silenceRaf;
301
+ private speechEndEmitted;
302
+ private captureOptions;
303
+ get isCapturing(): boolean;
304
+ get activeMimeType(): string;
305
+ start(options?: VoiceCaptureOptions): Promise<string>;
306
+ stop(): Promise<void>;
307
+ cancel(): void;
308
+ private startSilenceMonitor;
309
+ private stopSilenceMonitor;
310
+ private releaseStream;
311
+ }
312
+
313
+ type SynxedWebPlayerMode = "mini" | "wide" | "large";
314
+ type SynxedWebPlayerPlacement = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "bottom-center";
183
315
  interface SynxedWebPlayerTheme {
184
316
  accent?: string;
185
317
  accentMuted?: string;
@@ -198,10 +330,10 @@ interface SynxedWebPlayerPosition {
198
330
  offsetY?: number;
199
331
  }
200
332
  type SynxedWebPlayerSource = {
201
- type: 'playlist';
333
+ type: "playlist";
202
334
  playlistCode: string;
203
335
  } | {
204
- type: 'radio';
336
+ type: "radio";
205
337
  };
206
338
  interface SynxedWebPlayerOptions {
207
339
  /** Mount target. If omitted, a fixed overlay root is appended to `document.body`. */
@@ -213,6 +345,7 @@ interface SynxedWebPlayerOptions {
213
345
  theme?: SynxedWebPlayerTheme;
214
346
  position?: SynxedWebPlayerPosition;
215
347
  avatarUrl?: string;
348
+ voiceAvatarUrl?: string;
216
349
  attribution?: string;
217
350
  nowPlayingPollMs?: number;
218
351
  powerByLabel?: string;
@@ -221,6 +354,11 @@ interface SynxedWebPlayerOptions {
221
354
  style?: Partial<CSSStyleDeclaration>;
222
355
  onMiniClick?: () => void;
223
356
  draggable?: boolean;
357
+ /** Press-and-hold DJ avatar for voice. Set `false` to disable. Default `true`. */
358
+ enableVoice?: boolean;
359
+ /** Ms to hold before voice starts (prevents opening AI on quick tap). Default `450`. */
360
+ voiceHoldMs?: number;
361
+ listenerId?: string;
224
362
  }
225
363
 
226
364
  /**
@@ -248,6 +386,13 @@ declare class SynxedWebPlayer {
248
386
  private artistBlockEl;
249
387
  private footerEl;
250
388
  private playBtn;
389
+ private avatarVoiceHolding;
390
+ private suppressMiniClick;
391
+ private voiceAvatarPreview;
392
+ private voiceHoldActivated;
393
+ private voiceUiDismissed;
394
+ private voiceHoldTimer;
395
+ private readonly defaultVoiceHoldMs;
251
396
  constructor(options: SynxedWebPlayerOptions);
252
397
  /** Convenience factory — same as `new SynxedWebPlayer(options)`. */
253
398
  static mount(options: SynxedWebPlayerOptions): SynxedWebPlayer;
@@ -255,12 +400,14 @@ declare class SynxedWebPlayer {
255
400
  get player(): SynxedPlayer | null;
256
401
  get element(): HTMLElement;
257
402
  destroy(): void;
403
+ private get voiceEnabled();
258
404
  private get isRadio();
259
405
  private get mode();
260
406
  private mountShell;
261
407
  private makeDraggable;
262
408
  private buildAvatar;
263
409
  private buildMini;
410
+ private handleMiniActivate;
264
411
  private buildWide;
265
412
  private buildLarge;
266
413
  private roundControlStyle;
@@ -268,8 +415,22 @@ declare class SynxedWebPlayer {
268
415
  private buildVisualizer;
269
416
  private decoLines;
270
417
  private initEngine;
418
+ /** Press-and-hold DJ avatar to stream voice; release or silence auto-ends capture. */
419
+ private wireAvatarVoiceHold;
420
+ private isVoiceUiOpen;
421
+ private dismissVoiceUi;
422
+ private resetVoiceHoldState;
423
+ private applyVoiceVisual;
424
+ private isVoiceAvatarActive;
425
+ private getAvatarImageUrl;
426
+ private refreshAvatarImage;
271
427
  private startNowPlayingPoll;
272
428
  private stopNowPlayingPoll;
429
+ private handleSkipClick;
430
+ /** Countdown in the skip control during ads — no extra chrome. */
431
+ private applyAdSkipUi;
432
+ private clearAdSkipStyleOverrides;
433
+ private restoreTrackSkipButton;
273
434
  private displayLine;
274
435
  private refreshLabels;
275
436
  private updateAvatar;
@@ -277,4 +438,4 @@ declare class SynxedWebPlayer {
277
438
  private togglePlay;
278
439
  }
279
440
 
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 };
441
+ 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,69 @@ 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 currentAd;
152
+ private _currentAd;
153
+ private _voiceState;
154
+ private voiceMimeType;
155
+ private voiceListenerId;
156
+ private pendingPlaybackLoad;
157
+ private voiceAutoEndOnSilence;
158
+ private pausedForVoice;
108
159
  constructor(config: SynxedConfig);
109
160
  get currentTrack(): TrackInfo | null;
110
161
  /** Active playback init kind from the last `play*` call (RADIO, PLAYLIST, SONG, …). */
111
162
  get contentKind(): ContentKind;
163
+ get currentAd(): AdInfo | null;
164
+ get isAdPlaying(): boolean;
165
+ /** True when a skippable ad has played for at least `skipAfterSeconds` (default 5s). */
166
+ canSkipAd(): boolean;
167
+ /** Seconds until skip unlocks; `0` when skip is available; `null` when not in a skippable ad. */
168
+ getAdSkipCountdownSeconds(): number | null;
169
+ get voiceState(): VoiceState;
112
170
  private controlPositionMs;
113
171
  private setupListeners;
172
+ private resolveVoiceOptions;
114
173
  private connect;
115
174
  private buildInitExtras;
116
175
  playSong(options: PlaySongOptions): Promise<void>;
117
176
  playPlaylist(options: PlayPlaylistOptions): Promise<void>;
118
177
  playRadio(options?: PlayRadioOptions): Promise<void>;
178
+ /**
179
+ * Press-and-hold the DJ avatar (or call manually): start mic capture and stream chunks.
180
+ * Ends automatically when the user stops talking (`voiceSpeechEnd`) or call `endVoiceHold()`.
181
+ */
182
+ beginVoiceHold(options?: VoiceHoldOptions): Promise<void>;
183
+ /**
184
+ * Release press-and-hold: flush final audio chunk and request playlist generation.
185
+ */
186
+ endVoiceHold(): Promise<void>;
187
+ /** Cancel an in-progress voice hold without sending audio to the server. */
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;
119
195
  pause(): void;
120
196
  resume(): void;
121
197
  stop(): void;
@@ -129,7 +205,11 @@ declare class SynxedPlayer extends EventEmitter<SynxedEvents> {
129
205
  private clearHeartbeat;
130
206
  private startHeartbeat;
131
207
  private applyContentSummary;
208
+ private emitAdSkipUpdate;
209
+ private setVoiceState;
210
+ private handleVoiceAck;
132
211
  private handleServerMessage;
212
+ private startContentPlayback;
133
213
  private handleAdPlayback;
134
214
  skipAd(): void;
135
215
  clickAd(): void;
@@ -168,6 +248,15 @@ declare class TransportManager extends EventEmitter<TransportEvents> {
168
248
  sendInit(params: any): void;
169
249
  sendControl(params: any): void;
170
250
  sendAnalytics(params: any): void;
251
+ sendVoice(params: {
252
+ action: number;
253
+ audioData?: Uint8Array;
254
+ mimeType?: string;
255
+ sequence?: number;
256
+ listenerId?: string;
257
+ deviceType?: string;
258
+ gameContext?: string;
259
+ }): void;
171
260
  get isConnected(): boolean;
172
261
  private sendBytes;
173
262
  }
@@ -178,8 +267,51 @@ declare class TransportManager extends EventEmitter<TransportEvents> {
178
267
  */
179
268
  declare function fetchRadioNowPlaying(serverUrl: string, init?: RequestInit): Promise<RadioNowPlaying | null>;
180
269
 
181
- type SynxedWebPlayerMode = 'mini' | 'wide' | 'large';
182
- type SynxedWebPlayerPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'bottom-center';
270
+ interface VoiceCaptureEvents {
271
+ chunk: (data: Uint8Array, sequence: number) => void;
272
+ error: (error: Error) => void;
273
+ /** User started speaking (level crossed above silence threshold). */
274
+ speechStart: () => void;
275
+ /** User stopped speaking (silence after speech, or max duration reached). */
276
+ speechEnd: () => void;
277
+ }
278
+ 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
+ /** Safety cap — auto `speechEnd` after this many ms. Default `30000`. */
286
+ maxDurationMs?: number;
287
+ /** Monitor mic levels for end-of-speech. Default `true`. */
288
+ detectSilence?: boolean;
289
+ }
290
+ /**
291
+ * Native MediaRecorder capture with optional Web Audio silence detection.
292
+ */
293
+ declare class VoiceCaptureManager extends EventEmitter<VoiceCaptureEvents> {
294
+ private stream;
295
+ private recorder;
296
+ private sequence;
297
+ private capturing;
298
+ private mimeType;
299
+ private audioContext;
300
+ private silenceRaf;
301
+ private speechEndEmitted;
302
+ private captureOptions;
303
+ get isCapturing(): boolean;
304
+ get activeMimeType(): string;
305
+ start(options?: VoiceCaptureOptions): Promise<string>;
306
+ stop(): Promise<void>;
307
+ cancel(): void;
308
+ private startSilenceMonitor;
309
+ private stopSilenceMonitor;
310
+ private releaseStream;
311
+ }
312
+
313
+ type SynxedWebPlayerMode = "mini" | "wide" | "large";
314
+ type SynxedWebPlayerPlacement = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "bottom-center";
183
315
  interface SynxedWebPlayerTheme {
184
316
  accent?: string;
185
317
  accentMuted?: string;
@@ -198,10 +330,10 @@ interface SynxedWebPlayerPosition {
198
330
  offsetY?: number;
199
331
  }
200
332
  type SynxedWebPlayerSource = {
201
- type: 'playlist';
333
+ type: "playlist";
202
334
  playlistCode: string;
203
335
  } | {
204
- type: 'radio';
336
+ type: "radio";
205
337
  };
206
338
  interface SynxedWebPlayerOptions {
207
339
  /** Mount target. If omitted, a fixed overlay root is appended to `document.body`. */
@@ -213,6 +345,7 @@ interface SynxedWebPlayerOptions {
213
345
  theme?: SynxedWebPlayerTheme;
214
346
  position?: SynxedWebPlayerPosition;
215
347
  avatarUrl?: string;
348
+ voiceAvatarUrl?: string;
216
349
  attribution?: string;
217
350
  nowPlayingPollMs?: number;
218
351
  powerByLabel?: string;
@@ -221,6 +354,11 @@ interface SynxedWebPlayerOptions {
221
354
  style?: Partial<CSSStyleDeclaration>;
222
355
  onMiniClick?: () => void;
223
356
  draggable?: boolean;
357
+ /** Press-and-hold DJ avatar for voice. Set `false` to disable. Default `true`. */
358
+ enableVoice?: boolean;
359
+ /** Ms to hold before voice starts (prevents opening AI on quick tap). Default `450`. */
360
+ voiceHoldMs?: number;
361
+ listenerId?: string;
224
362
  }
225
363
 
226
364
  /**
@@ -248,6 +386,13 @@ declare class SynxedWebPlayer {
248
386
  private artistBlockEl;
249
387
  private footerEl;
250
388
  private playBtn;
389
+ private avatarVoiceHolding;
390
+ private suppressMiniClick;
391
+ private voiceAvatarPreview;
392
+ private voiceHoldActivated;
393
+ private voiceUiDismissed;
394
+ private voiceHoldTimer;
395
+ private readonly defaultVoiceHoldMs;
251
396
  constructor(options: SynxedWebPlayerOptions);
252
397
  /** Convenience factory — same as `new SynxedWebPlayer(options)`. */
253
398
  static mount(options: SynxedWebPlayerOptions): SynxedWebPlayer;
@@ -255,12 +400,14 @@ declare class SynxedWebPlayer {
255
400
  get player(): SynxedPlayer | null;
256
401
  get element(): HTMLElement;
257
402
  destroy(): void;
403
+ private get voiceEnabled();
258
404
  private get isRadio();
259
405
  private get mode();
260
406
  private mountShell;
261
407
  private makeDraggable;
262
408
  private buildAvatar;
263
409
  private buildMini;
410
+ private handleMiniActivate;
264
411
  private buildWide;
265
412
  private buildLarge;
266
413
  private roundControlStyle;
@@ -268,8 +415,22 @@ declare class SynxedWebPlayer {
268
415
  private buildVisualizer;
269
416
  private decoLines;
270
417
  private initEngine;
418
+ /** Press-and-hold DJ avatar to stream voice; release or silence auto-ends capture. */
419
+ private wireAvatarVoiceHold;
420
+ private isVoiceUiOpen;
421
+ private dismissVoiceUi;
422
+ private resetVoiceHoldState;
423
+ private applyVoiceVisual;
424
+ private isVoiceAvatarActive;
425
+ private getAvatarImageUrl;
426
+ private refreshAvatarImage;
271
427
  private startNowPlayingPoll;
272
428
  private stopNowPlayingPoll;
429
+ private handleSkipClick;
430
+ /** Countdown in the skip control during ads — no extra chrome. */
431
+ private applyAdSkipUi;
432
+ private clearAdSkipStyleOverrides;
433
+ private restoreTrackSkipButton;
273
434
  private displayLine;
274
435
  private refreshLabels;
275
436
  private updateAvatar;
@@ -277,4 +438,4 @@ declare class SynxedWebPlayer {
277
438
  private togglePlay;
278
439
  }
279
440
 
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 };
441
+ 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 };