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 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 currentAd;
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
- type SynxedWebPlayerMode = 'mini' | 'wide' | 'large';
182
- type SynxedWebPlayerPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'bottom-center';
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: 'playlist';
326
+ type: "playlist";
202
327
  playlistCode: string;
203
328
  } | {
204
- type: 'radio';
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 currentAd;
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
- type SynxedWebPlayerMode = 'mini' | 'wide' | 'large';
182
- type SynxedWebPlayerPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'bottom-center';
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: 'playlist';
326
+ type: "playlist";
202
327
  playlistCode: string;
203
328
  } | {
204
- type: 'radio';
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 };