@elevenlabs/client 1.8.0 → 1.8.1

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/lib.iife.js CHANGED
@@ -92,7 +92,7 @@ var ElevenLabsClient = (function(exports) {
92
92
  //#region src/sourceInfo.ts
93
93
  let sourceInfo = Object.freeze({
94
94
  name: "js_sdk",
95
- version: "1.8.0"
95
+ version: "1.8.1"
96
96
  });
97
97
  //#endregion
98
98
  //#region src/utils/events.ts
@@ -21955,14 +21955,39 @@ registerProcessor("audioConcatProcessor", AudioConcatProcessor);
21955
21955
  };
21956
21956
  }
21957
21957
  //#endregion
21958
+ //#region src/platform/web/compatibility.ts
21959
+ function isIosDevice() {
21960
+ return [
21961
+ "iPad Simulator",
21962
+ "iPhone Simulator",
21963
+ "iPod Simulator",
21964
+ "iPad",
21965
+ "iPhone",
21966
+ "iPod"
21967
+ ].includes(navigator.platform) || navigator.userAgent.includes("Mac") && "ontouchend" in document;
21968
+ }
21969
+ function isAndroidDevice() {
21970
+ return /android/i.test(navigator.userAgent);
21971
+ }
21972
+ //#endregion
21958
21973
  //#region src/platform/web/output.ts
21974
+ function maybePrimeIosPlayback({ sampleRate, format, worklet, audioElement }) {
21975
+ if (!isIosDevice()) return;
21976
+ const primeFrameCount = Math.floor(sampleRate * 100 / 1e3);
21977
+ const silentBuffer = format === "ulaw" ? new Uint8Array(primeFrameCount).fill(255) : new Int16Array(primeFrameCount);
21978
+ worklet.port.postMessage({
21979
+ type: "buffer",
21980
+ buffer: silentBuffer.buffer
21981
+ });
21982
+ audioElement.play().catch(() => {});
21983
+ }
21959
21984
  var MediaDeviceOutput = class MediaDeviceOutput {
21960
- static async create({ sampleRate, format, outputDeviceId, workletPaths, libsampleratePath }) {
21961
- let context = null;
21985
+ static async create({ sampleRate, format, outputDeviceId, workletPaths, libsampleratePath, audioContext }) {
21986
+ let context = audioContext ?? null;
21962
21987
  let audioElement = null;
21963
21988
  try {
21964
21989
  const supportsSampleRateConstraint = navigator.mediaDevices.getSupportedConstraints().sampleRate;
21965
- context = new AudioContext(supportsSampleRateConstraint ? { sampleRate } : {});
21990
+ if (!context) context = new AudioContext(supportsSampleRateConstraint ? { sampleRate } : {});
21966
21991
  const analyser = context.createAnalyser();
21967
21992
  const gain = context.createGain();
21968
21993
  audioElement = new Audio();
@@ -21988,6 +22013,12 @@ registerProcessor("audioConcatProcessor", AudioConcatProcessor);
21988
22013
  });
21989
22014
  worklet.connect(gain);
21990
22015
  await context.resume();
22016
+ maybePrimeIosPlayback({
22017
+ sampleRate,
22018
+ format,
22019
+ worklet,
22020
+ audioElement
22021
+ });
21991
22022
  if (outputDeviceId && audioElement.setSinkId) await audioElement.setSinkId(outputDeviceId);
21992
22023
  return new MediaDeviceOutput(context, analyser, gain, worklet, audioElement);
21993
22024
  } catch (error) {
@@ -22199,21 +22230,6 @@ class RawAudioProcessor extends AudioWorkletProcessor {
22199
22230
  }
22200
22231
  registerProcessor("rawAudioProcessor", RawAudioProcessor);
22201
22232
  `);
22202
- //#endregion
22203
- //#region src/platform/web/compatibility.ts
22204
- function isIosDevice() {
22205
- return [
22206
- "iPad Simulator",
22207
- "iPhone Simulator",
22208
- "iPod Simulator",
22209
- "iPad",
22210
- "iPhone",
22211
- "iPod"
22212
- ].includes(navigator.platform) || navigator.userAgent.includes("Mac") && "ontouchend" in document;
22213
- }
22214
- function isAndroidDevice() {
22215
- return /android/i.test(navigator.userAgent);
22216
- }
22217
22233
  //#endregion
22218
22234
  //#region src/platform/web/input.ts
22219
22235
  const defaultConstraints = {
@@ -22372,6 +22388,7 @@ registerProcessor("rawAudioProcessor", RawAudioProcessor);
22372
22388
  inputFormat;
22373
22389
  outputFormat;
22374
22390
  outputListeners = /* @__PURE__ */ new Set();
22391
+ pendingAudioEvents = [];
22375
22392
  constructor(socket, conversationId, inputFormat, outputFormat) {
22376
22393
  super();
22377
22394
  this.socket = socket;
@@ -22472,13 +22489,19 @@ registerProcessor("rawAudioProcessor", RawAudioProcessor);
22472
22489
  }
22473
22490
  }
22474
22491
  close() {
22492
+ this.pendingAudioEvents = [];
22475
22493
  this.socket.close(1e3, "User ended conversation");
22476
22494
  }
22477
22495
  sendMessage(message) {
22478
22496
  this.socket.send(JSON.stringify(message));
22479
22497
  }
22480
22498
  addListener(listener) {
22499
+ const hadListeners = this.outputListeners.size > 0;
22481
22500
  this.outputListeners.add(listener);
22501
+ if (hadListeners || this.pendingAudioEvents.length === 0) return;
22502
+ const pending = this.pendingAudioEvents;
22503
+ this.pendingAudioEvents = [];
22504
+ for (const event of pending) listener(event);
22482
22505
  }
22483
22506
  removeListener(listener) {
22484
22507
  this.outputListeners.delete(listener);
@@ -22487,6 +22510,10 @@ registerProcessor("rawAudioProcessor", RawAudioProcessor);
22487
22510
  super.handleMessage(parsedEvent);
22488
22511
  if (parsedEvent.type === "audio" && parsedEvent.audio_event.audio_base_64) {
22489
22512
  const audioEvent = { audio_base_64: parsedEvent.audio_event.audio_base_64 };
22513
+ if (this.outputListeners.size === 0) {
22514
+ this.pendingAudioEvents.push(audioEvent);
22515
+ return;
22516
+ }
22490
22517
  this.outputListeners.forEach((listener) => listener(audioEvent));
22491
22518
  }
22492
22519
  }
@@ -22553,6 +22580,70 @@ registerProcessor("rawAudioProcessor", RawAudioProcessor);
22553
22580
  if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
22554
22581
  }
22555
22582
  //#endregion
22583
+ //#region src/platform/web/audioUnlock.ts
22584
+ const STASH_TTL_MS = 3e4;
22585
+ const UNLOCK_EVENTS = [
22586
+ "touchstart",
22587
+ "touchend",
22588
+ "click"
22589
+ ];
22590
+ let stashedAudioContext = null;
22591
+ let discardTimer = null;
22592
+ let unlockListenerInstalled = false;
22593
+ function unlockAudioContext(ctx) {
22594
+ const buffer = ctx.createBuffer(1, 1, 22050);
22595
+ const source = ctx.createBufferSource();
22596
+ source.buffer = buffer;
22597
+ source.connect(ctx.destination);
22598
+ source.start(0);
22599
+ ctx.resume().catch(() => {});
22600
+ }
22601
+ function scheduleStashDiscard() {
22602
+ if (discardTimer) clearTimeout(discardTimer);
22603
+ discardTimer = setTimeout(() => {
22604
+ discardTimer = null;
22605
+ discardStashedAudioContext();
22606
+ }, STASH_TTL_MS);
22607
+ }
22608
+ function clearStashDiscardTimer() {
22609
+ if (!discardTimer) return;
22610
+ clearTimeout(discardTimer);
22611
+ discardTimer = null;
22612
+ }
22613
+ /** Unlock iOS audio during a user gesture. No-op on non-iOS. */
22614
+ function unlockIosAudioForSession() {
22615
+ if (!isIosDevice() || stashedAudioContext) return;
22616
+ const ctx = new AudioContext();
22617
+ unlockAudioContext(ctx);
22618
+ stashedAudioContext = ctx;
22619
+ scheduleStashDiscard();
22620
+ }
22621
+ /**
22622
+ * Listen for the first user interaction on the page and unlock iOS audio in
22623
+ * the capture phase, before application click handlers run. Covers callers
22624
+ * (such as the convai widget) that `await` before `Conversation.startSession`.
22625
+ */
22626
+ function installIosAudioUnlockListener() {
22627
+ if (!isIosDevice() || unlockListenerInstalled || typeof document === "undefined") return;
22628
+ unlockListenerInstalled = true;
22629
+ const onUserGesture = () => {
22630
+ unlockIosAudioForSession();
22631
+ };
22632
+ for (const event of UNLOCK_EVENTS) document.addEventListener(event, onUserGesture, true);
22633
+ }
22634
+ function takeUnlockedAudioContext() {
22635
+ const ctx = stashedAudioContext;
22636
+ stashedAudioContext = null;
22637
+ clearStashDiscardTimer();
22638
+ return ctx;
22639
+ }
22640
+ function discardStashedAudioContext() {
22641
+ if (!stashedAudioContext) return;
22642
+ stashedAudioContext.close().catch(() => {});
22643
+ stashedAudioContext = null;
22644
+ clearStashDiscardTimer();
22645
+ }
22646
+ //#endregion
22556
22647
  //#region src/platform/web/VoiceSessionSetup.ts
22557
22648
  function detectPlatform() {
22558
22649
  if (isAndroidDevice()) return "android";
@@ -22569,7 +22660,7 @@ registerProcessor("rawAudioProcessor", RawAudioProcessor);
22569
22660
  * Sets up WebSocket-specific input and output controllers using
22570
22661
  * web MediaDevice APIs (AudioContext, AudioWorklet, etc.).
22571
22662
  */
22572
- async function setupWebSocketIO(options, connection) {
22663
+ async function setupWebSocketIO(options, connection, audioContext) {
22573
22664
  const [input, output] = await Promise.all([MediaDeviceInput.create({
22574
22665
  ...connection.inputFormat,
22575
22666
  preferHeadphonesForIosDevices: options.preferHeadphonesForIosDevices,
@@ -22579,7 +22670,8 @@ registerProcessor("rawAudioProcessor", RawAudioProcessor);
22579
22670
  }), MediaDeviceOutput.create({
22580
22671
  ...connection.outputFormat,
22581
22672
  outputDeviceId: options.outputDeviceId,
22582
- workletPaths: options.workletPaths
22673
+ workletPaths: options.workletPaths,
22674
+ audioContext: audioContext ?? void 0
22583
22675
  })]);
22584
22676
  const detachInput = attachInputToConnection(input, connection);
22585
22677
  const detachOutput = attachConnectionToOutput(connection, output);
@@ -22602,6 +22694,7 @@ registerProcessor("rawAudioProcessor", RawAudioProcessor);
22602
22694
  const useWakeLock = options.useWakeLock ?? true;
22603
22695
  let wakeLock = null;
22604
22696
  let preliminaryInputStream = null;
22697
+ let unlockedAudioContext = null;
22605
22698
  try {
22606
22699
  if (useWakeLock) wakeLock = await requestWakeLock();
22607
22700
  preliminaryInputStream = await navigator.mediaDevices.getUserMedia({ audio: true });
@@ -22610,12 +22703,20 @@ registerProcessor("rawAudioProcessor", RawAudioProcessor);
22610
22703
  const connection = await createConnection(options);
22611
22704
  let result;
22612
22705
  try {
22613
- if (connection instanceof WebSocketConnection) result = {
22614
- connection,
22615
- ...await setupWebSocketIO(options, connection)
22616
- };
22617
- else result = setupWebRTCSession(connection);
22706
+ if (connection instanceof WebSocketConnection) {
22707
+ unlockedAudioContext = takeUnlockedAudioContext();
22708
+ result = {
22709
+ connection,
22710
+ ...await setupWebSocketIO(options, connection, unlockedAudioContext)
22711
+ };
22712
+ unlockedAudioContext = null;
22713
+ } else {
22714
+ discardStashedAudioContext();
22715
+ result = setupWebRTCSession(connection);
22716
+ }
22618
22717
  } catch (ioError) {
22718
+ await unlockedAudioContext?.close().catch(() => {});
22719
+ unlockedAudioContext = null;
22619
22720
  connection.close();
22620
22721
  throw ioError;
22621
22722
  }
@@ -22650,6 +22751,7 @@ registerProcessor("rawAudioProcessor", RawAudioProcessor);
22650
22751
  await wakeLock?.release();
22651
22752
  wakeLock = null;
22652
22753
  } catch (_e) {}
22754
+ discardStashedAudioContext();
22653
22755
  throw error;
22654
22756
  }
22655
22757
  }
@@ -23993,6 +24095,7 @@ registerProcessor("scribeAudioProcessor", ScribeAudioProcessor);
23993
24095
  } };
23994
24096
  //#endregion
23995
24097
  //#region src/platform/web/index.ts
24098
+ installIosAudioUnlockListener();
23996
24099
  setWebRTCAudioAdapterFactory(() => new WebAudioAdapter());
23997
24100
  setScribeMicrophoneSetup(webScribeMicrophoneSetup);
23998
24101
  //#endregion