@absolutejs/voice 0.0.22-beta.656 → 0.0.22-beta.657
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/angular/index.js +167 -67
- package/dist/client/htmxBootstrap.js +167 -67
- package/dist/client/index.js +167 -67
- package/dist/core/types.d.ts +4 -0
- package/dist/embed/index.js +167 -67
- package/dist/embed/voice-widget.js +8 -8
- package/dist/react/index.js +167 -67
- package/dist/svelte/index.js +167 -67
- package/dist/testing/index.js +168 -68
- package/dist/vue/index.js +167 -67
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -11442,6 +11442,7 @@ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
|
11442
11442
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
11443
11443
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
11444
11444
|
var DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS = 5000;
|
|
11445
|
+
var DEFAULT_PREPARE_RECONNECT_TIMEOUT_MS = 1e4;
|
|
11445
11446
|
var MAX_PENDING_CONTROL_MESSAGES = 100;
|
|
11446
11447
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
11447
11448
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
@@ -11535,6 +11536,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11535
11536
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
11536
11537
|
let lifecycleGeneration = 0;
|
|
11537
11538
|
let disposed = false;
|
|
11539
|
+
let reconnectPreparationAbort = null;
|
|
11538
11540
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
11539
11541
|
const state = {
|
|
11540
11542
|
isConnected: false,
|
|
@@ -11622,12 +11624,30 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11622
11624
|
return;
|
|
11623
11625
|
}
|
|
11624
11626
|
try {
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11627
|
+
if (options.prepareReconnect) {
|
|
11628
|
+
const preparationAbort = new AbortController;
|
|
11629
|
+
reconnectPreparationAbort = preparationAbort;
|
|
11630
|
+
const timeout = setTimeout(() => preparationAbort.abort("Voice reconnect preparation timed out"), options.prepareReconnectTimeoutMs ?? DEFAULT_PREPARE_RECONNECT_TIMEOUT_MS);
|
|
11631
|
+
try {
|
|
11632
|
+
await Promise.race([
|
|
11633
|
+
options.prepareReconnect({
|
|
11634
|
+
attempt: state.reconnectAttempts,
|
|
11635
|
+
path,
|
|
11636
|
+
scenarioId: state.scenarioId,
|
|
11637
|
+
sessionId: state.sessionId,
|
|
11638
|
+
signal: preparationAbort.signal
|
|
11639
|
+
}),
|
|
11640
|
+
new Promise((_, reject) => {
|
|
11641
|
+
preparationAbort.signal.addEventListener("abort", () => reject(new Error("Voice reconnect preparation timed out.")), { once: true });
|
|
11642
|
+
})
|
|
11643
|
+
]);
|
|
11644
|
+
} finally {
|
|
11645
|
+
clearTimeout(timeout);
|
|
11646
|
+
if (reconnectPreparationAbort === preparationAbort) {
|
|
11647
|
+
reconnectPreparationAbort = null;
|
|
11648
|
+
}
|
|
11649
|
+
}
|
|
11650
|
+
}
|
|
11631
11651
|
} catch (error) {
|
|
11632
11652
|
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11633
11653
|
return;
|
|
@@ -11751,6 +11771,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11751
11771
|
state.ws = ws;
|
|
11752
11772
|
};
|
|
11753
11773
|
const sendSerialized = (value) => {
|
|
11774
|
+
if (disposed)
|
|
11775
|
+
return;
|
|
11754
11776
|
if (state.ws?.readyState === WS_OPEN) {
|
|
11755
11777
|
state.ws.send(value);
|
|
11756
11778
|
return;
|
|
@@ -11805,6 +11827,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11805
11827
|
};
|
|
11806
11828
|
const close = (reason = "client-close") => {
|
|
11807
11829
|
disposed = true;
|
|
11830
|
+
reconnectPreparationAbort?.abort();
|
|
11831
|
+
reconnectPreparationAbort = null;
|
|
11808
11832
|
lifecycleGeneration += 1;
|
|
11809
11833
|
clearTimers();
|
|
11810
11834
|
rejectPendingCallControls("Voice connection closed");
|
|
@@ -11822,6 +11846,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11822
11846
|
};
|
|
11823
11847
|
const disconnect = () => {
|
|
11824
11848
|
disposed = true;
|
|
11849
|
+
reconnectPreparationAbort?.abort();
|
|
11850
|
+
reconnectPreparationAbort = null;
|
|
11825
11851
|
lifecycleGeneration += 1;
|
|
11826
11852
|
clearTimers();
|
|
11827
11853
|
rejectPendingCallControls("Voice connection disconnected");
|
|
@@ -12073,7 +12099,10 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
12073
12099
|
getSessionId: () => options.browserMedia ? options.browserMedia.getSessionId?.() ?? connection.getSessionId() : connection.getSessionId()
|
|
12074
12100
|
}) : null;
|
|
12075
12101
|
const subscribers = new Set;
|
|
12102
|
+
let disposed = false;
|
|
12076
12103
|
const start = (input) => Promise.resolve().then(() => {
|
|
12104
|
+
if (disposed)
|
|
12105
|
+
return;
|
|
12077
12106
|
if (!input?.sessionId && !input?.scenarioId) {
|
|
12078
12107
|
return;
|
|
12079
12108
|
}
|
|
@@ -12132,6 +12161,7 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
12132
12161
|
return connection.callControl(message);
|
|
12133
12162
|
},
|
|
12134
12163
|
close(reason) {
|
|
12164
|
+
disposed = true;
|
|
12135
12165
|
unsubscribeConnection();
|
|
12136
12166
|
browserMediaReporter?.close();
|
|
12137
12167
|
connection.close(reason);
|
|
@@ -12139,6 +12169,7 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
12139
12169
|
notify();
|
|
12140
12170
|
},
|
|
12141
12171
|
disconnect() {
|
|
12172
|
+
disposed = true;
|
|
12142
12173
|
unsubscribeConnection();
|
|
12143
12174
|
browserMediaReporter?.close();
|
|
12144
12175
|
connection.disconnect();
|
|
@@ -12336,48 +12367,89 @@ var createMicrophoneCapture = (options) => {
|
|
|
12336
12367
|
let sourceNode = null;
|
|
12337
12368
|
let processorNode = null;
|
|
12338
12369
|
let mediaStream = null;
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12370
|
+
let lifecycleGeneration = 0;
|
|
12371
|
+
let startPromise = null;
|
|
12372
|
+
const release = (resources) => {
|
|
12373
|
+
resources.processorNode?.disconnect();
|
|
12374
|
+
resources.sourceNode?.disconnect();
|
|
12375
|
+
resources.mediaStream?.getTracks().forEach((track) => track.stop());
|
|
12376
|
+
resources.audioContext?.close();
|
|
12377
|
+
};
|
|
12378
|
+
const start = () => {
|
|
12379
|
+
if (mediaStream)
|
|
12380
|
+
return Promise.resolve();
|
|
12381
|
+
if (startPromise)
|
|
12382
|
+
return startPromise;
|
|
12383
|
+
const generation = ++lifecycleGeneration;
|
|
12384
|
+
const operation = async () => {
|
|
12385
|
+
if (!options.stream && (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia)) {
|
|
12386
|
+
throw new Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");
|
|
12387
|
+
}
|
|
12388
|
+
const AudioContextCtor = (typeof window !== "undefined" ? window.AudioContext ?? window.webkitAudioContext : undefined) ?? AudioContext;
|
|
12389
|
+
if (!AudioContextCtor) {
|
|
12390
|
+
throw new Error("Browser microphone capture requires AudioContext support.");
|
|
12391
|
+
}
|
|
12392
|
+
let pendingAudioContext = null;
|
|
12393
|
+
let pendingMediaStream = null;
|
|
12394
|
+
let pendingProcessorNode = null;
|
|
12395
|
+
let pendingSourceNode = null;
|
|
12396
|
+
try {
|
|
12397
|
+
pendingMediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
|
|
12398
|
+
audio: {
|
|
12399
|
+
autoGainControl: true,
|
|
12400
|
+
channelCount: options.channelCount ?? 1,
|
|
12401
|
+
echoCancellation: true,
|
|
12402
|
+
noiseSuppression: true
|
|
12403
|
+
}
|
|
12404
|
+
});
|
|
12405
|
+
if (generation !== lifecycleGeneration) {
|
|
12406
|
+
throw new Error("Microphone capture stopped during startup.");
|
|
12354
12407
|
}
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12408
|
+
pendingAudioContext = new AudioContextCtor;
|
|
12409
|
+
if (pendingAudioContext.state === "suspended") {
|
|
12410
|
+
await pendingAudioContext.resume();
|
|
12411
|
+
}
|
|
12412
|
+
if (generation !== lifecycleGeneration) {
|
|
12413
|
+
throw new Error("Microphone capture stopped during startup.");
|
|
12414
|
+
}
|
|
12415
|
+
pendingSourceNode = pendingAudioContext.createMediaStreamSource(pendingMediaStream);
|
|
12416
|
+
pendingProcessorNode = pendingAudioContext.createScriptProcessor(4096, 1, 1);
|
|
12417
|
+
pendingProcessorNode.onaudioprocess = (event) => {
|
|
12418
|
+
const channel = event.inputBuffer.getChannelData(0);
|
|
12419
|
+
const downsampled = downsampleBuffer(channel, pendingAudioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
|
|
12420
|
+
const pcm = floatTo16BitPCM(downsampled);
|
|
12421
|
+
options.onLevel?.(getPcmLevel(pcm));
|
|
12422
|
+
options.onAudio(pcm);
|
|
12423
|
+
};
|
|
12424
|
+
pendingSourceNode.connect(pendingProcessorNode);
|
|
12425
|
+
pendingProcessorNode.connect(pendingAudioContext.destination);
|
|
12426
|
+
if (generation !== lifecycleGeneration) {
|
|
12427
|
+
throw new Error("Microphone capture stopped during startup.");
|
|
12428
|
+
}
|
|
12429
|
+
audioContext = pendingAudioContext;
|
|
12430
|
+
mediaStream = pendingMediaStream;
|
|
12431
|
+
processorNode = pendingProcessorNode;
|
|
12432
|
+
sourceNode = pendingSourceNode;
|
|
12433
|
+
} catch (error) {
|
|
12434
|
+
release({
|
|
12435
|
+
audioContext: pendingAudioContext,
|
|
12436
|
+
mediaStream: pendingMediaStream,
|
|
12437
|
+
processorNode: pendingProcessorNode,
|
|
12438
|
+
sourceNode: pendingSourceNode
|
|
12439
|
+
});
|
|
12440
|
+
throw error;
|
|
12359
12441
|
}
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
options.onAudio(pcm);
|
|
12368
|
-
};
|
|
12369
|
-
sourceNode.connect(processorNode);
|
|
12370
|
-
processorNode.connect(audioContext.destination);
|
|
12371
|
-
} catch (error) {
|
|
12372
|
-
stop();
|
|
12373
|
-
throw error;
|
|
12374
|
-
}
|
|
12442
|
+
};
|
|
12443
|
+
const pending = operation().finally(() => {
|
|
12444
|
+
if (startPromise === pending)
|
|
12445
|
+
startPromise = null;
|
|
12446
|
+
});
|
|
12447
|
+
startPromise = pending;
|
|
12448
|
+
return pending;
|
|
12375
12449
|
};
|
|
12376
12450
|
const stop = () => {
|
|
12377
|
-
|
|
12378
|
-
sourceNode
|
|
12379
|
-
mediaStream?.getTracks().forEach((track) => track.stop());
|
|
12380
|
-
audioContext?.close();
|
|
12451
|
+
lifecycleGeneration += 1;
|
|
12452
|
+
release({ audioContext, mediaStream, processorNode, sourceNode });
|
|
12381
12453
|
options.onLevel?.(0);
|
|
12382
12454
|
audioContext = null;
|
|
12383
12455
|
mediaStream = null;
|
|
@@ -12733,6 +12805,9 @@ var createVoiceController = (path, options = {}) => {
|
|
|
12733
12805
|
...options.connection
|
|
12734
12806
|
});
|
|
12735
12807
|
let capture = null;
|
|
12808
|
+
let captureGeneration = 0;
|
|
12809
|
+
let disposed = false;
|
|
12810
|
+
let startRecordingPromise = null;
|
|
12736
12811
|
let state = createInitialState2(stream);
|
|
12737
12812
|
const subscribers = new Set;
|
|
12738
12813
|
const notify = () => {
|
|
@@ -12789,6 +12864,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
12789
12864
|
return capture;
|
|
12790
12865
|
};
|
|
12791
12866
|
const stopRecording = () => {
|
|
12867
|
+
captureGeneration += 1;
|
|
12792
12868
|
capture?.stop();
|
|
12793
12869
|
capture = null;
|
|
12794
12870
|
state = {
|
|
@@ -12798,39 +12874,63 @@ var createVoiceController = (path, options = {}) => {
|
|
|
12798
12874
|
notify();
|
|
12799
12875
|
};
|
|
12800
12876
|
const startRecording = async () => {
|
|
12877
|
+
if (disposed) {
|
|
12878
|
+
throw new Error("Voice controller is closed.");
|
|
12879
|
+
}
|
|
12801
12880
|
if (state.isRecording) {
|
|
12802
12881
|
return;
|
|
12803
12882
|
}
|
|
12804
|
-
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
|
|
12826
|
-
|
|
12883
|
+
if (startRecordingPromise)
|
|
12884
|
+
return startRecordingPromise;
|
|
12885
|
+
const generation = ++captureGeneration;
|
|
12886
|
+
const activeCapture = ensureCapture();
|
|
12887
|
+
const operation = (async () => {
|
|
12888
|
+
try {
|
|
12889
|
+
state = {
|
|
12890
|
+
...state,
|
|
12891
|
+
recordingError: null
|
|
12892
|
+
};
|
|
12893
|
+
notify();
|
|
12894
|
+
await activeCapture.start();
|
|
12895
|
+
if (disposed || generation !== captureGeneration || capture !== activeCapture) {
|
|
12896
|
+
activeCapture.stop();
|
|
12897
|
+
return;
|
|
12898
|
+
}
|
|
12899
|
+
state = {
|
|
12900
|
+
...state,
|
|
12901
|
+
isRecording: true
|
|
12902
|
+
};
|
|
12903
|
+
notify();
|
|
12904
|
+
} catch (error) {
|
|
12905
|
+
activeCapture.stop();
|
|
12906
|
+
if (generation !== captureGeneration || capture !== activeCapture) {
|
|
12907
|
+
throw error;
|
|
12908
|
+
}
|
|
12909
|
+
capture = null;
|
|
12910
|
+
state = {
|
|
12911
|
+
...state,
|
|
12912
|
+
isRecording: false,
|
|
12913
|
+
recordingError: error instanceof Error ? error.message : String(error)
|
|
12914
|
+
};
|
|
12915
|
+
notify();
|
|
12916
|
+
throw error;
|
|
12917
|
+
}
|
|
12918
|
+
})();
|
|
12919
|
+
const pending = operation.finally(() => {
|
|
12920
|
+
if (startRecordingPromise === pending)
|
|
12921
|
+
startRecordingPromise = null;
|
|
12922
|
+
});
|
|
12923
|
+
startRecordingPromise = pending;
|
|
12924
|
+
return startRecordingPromise;
|
|
12827
12925
|
};
|
|
12828
12926
|
const close = (reason) => {
|
|
12927
|
+
disposed = true;
|
|
12829
12928
|
unsubscribeStream();
|
|
12830
12929
|
stopRecording();
|
|
12831
12930
|
stream.close(reason);
|
|
12832
12931
|
};
|
|
12833
12932
|
const disconnect = () => {
|
|
12933
|
+
disposed = true;
|
|
12834
12934
|
unsubscribeStream();
|
|
12835
12935
|
stopRecording();
|
|
12836
12936
|
stream.disconnect();
|
package/dist/svelte/index.js
CHANGED
|
@@ -517,48 +517,89 @@ var createMicrophoneCapture = (options) => {
|
|
|
517
517
|
let sourceNode = null;
|
|
518
518
|
let processorNode = null;
|
|
519
519
|
let mediaStream = null;
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
520
|
+
let lifecycleGeneration = 0;
|
|
521
|
+
let startPromise = null;
|
|
522
|
+
const release = (resources) => {
|
|
523
|
+
resources.processorNode?.disconnect();
|
|
524
|
+
resources.sourceNode?.disconnect();
|
|
525
|
+
resources.mediaStream?.getTracks().forEach((track) => track.stop());
|
|
526
|
+
resources.audioContext?.close();
|
|
527
|
+
};
|
|
528
|
+
const start = () => {
|
|
529
|
+
if (mediaStream)
|
|
530
|
+
return Promise.resolve();
|
|
531
|
+
if (startPromise)
|
|
532
|
+
return startPromise;
|
|
533
|
+
const generation = ++lifecycleGeneration;
|
|
534
|
+
const operation = async () => {
|
|
535
|
+
if (!options.stream && (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia)) {
|
|
536
|
+
throw new Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");
|
|
537
|
+
}
|
|
538
|
+
const AudioContextCtor = (typeof window !== "undefined" ? window.AudioContext ?? window.webkitAudioContext : undefined) ?? AudioContext;
|
|
539
|
+
if (!AudioContextCtor) {
|
|
540
|
+
throw new Error("Browser microphone capture requires AudioContext support.");
|
|
541
|
+
}
|
|
542
|
+
let pendingAudioContext = null;
|
|
543
|
+
let pendingMediaStream = null;
|
|
544
|
+
let pendingProcessorNode = null;
|
|
545
|
+
let pendingSourceNode = null;
|
|
546
|
+
try {
|
|
547
|
+
pendingMediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
|
|
548
|
+
audio: {
|
|
549
|
+
autoGainControl: true,
|
|
550
|
+
channelCount: options.channelCount ?? 1,
|
|
551
|
+
echoCancellation: true,
|
|
552
|
+
noiseSuppression: true
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
if (generation !== lifecycleGeneration) {
|
|
556
|
+
throw new Error("Microphone capture stopped during startup.");
|
|
535
557
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
558
|
+
pendingAudioContext = new AudioContextCtor;
|
|
559
|
+
if (pendingAudioContext.state === "suspended") {
|
|
560
|
+
await pendingAudioContext.resume();
|
|
561
|
+
}
|
|
562
|
+
if (generation !== lifecycleGeneration) {
|
|
563
|
+
throw new Error("Microphone capture stopped during startup.");
|
|
564
|
+
}
|
|
565
|
+
pendingSourceNode = pendingAudioContext.createMediaStreamSource(pendingMediaStream);
|
|
566
|
+
pendingProcessorNode = pendingAudioContext.createScriptProcessor(4096, 1, 1);
|
|
567
|
+
pendingProcessorNode.onaudioprocess = (event) => {
|
|
568
|
+
const channel = event.inputBuffer.getChannelData(0);
|
|
569
|
+
const downsampled = downsampleBuffer(channel, pendingAudioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
|
|
570
|
+
const pcm = floatTo16BitPCM(downsampled);
|
|
571
|
+
options.onLevel?.(getPcmLevel(pcm));
|
|
572
|
+
options.onAudio(pcm);
|
|
573
|
+
};
|
|
574
|
+
pendingSourceNode.connect(pendingProcessorNode);
|
|
575
|
+
pendingProcessorNode.connect(pendingAudioContext.destination);
|
|
576
|
+
if (generation !== lifecycleGeneration) {
|
|
577
|
+
throw new Error("Microphone capture stopped during startup.");
|
|
578
|
+
}
|
|
579
|
+
audioContext = pendingAudioContext;
|
|
580
|
+
mediaStream = pendingMediaStream;
|
|
581
|
+
processorNode = pendingProcessorNode;
|
|
582
|
+
sourceNode = pendingSourceNode;
|
|
583
|
+
} catch (error) {
|
|
584
|
+
release({
|
|
585
|
+
audioContext: pendingAudioContext,
|
|
586
|
+
mediaStream: pendingMediaStream,
|
|
587
|
+
processorNode: pendingProcessorNode,
|
|
588
|
+
sourceNode: pendingSourceNode
|
|
589
|
+
});
|
|
590
|
+
throw error;
|
|
540
591
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
options.onAudio(pcm);
|
|
549
|
-
};
|
|
550
|
-
sourceNode.connect(processorNode);
|
|
551
|
-
processorNode.connect(audioContext.destination);
|
|
552
|
-
} catch (error) {
|
|
553
|
-
stop();
|
|
554
|
-
throw error;
|
|
555
|
-
}
|
|
592
|
+
};
|
|
593
|
+
const pending = operation().finally(() => {
|
|
594
|
+
if (startPromise === pending)
|
|
595
|
+
startPromise = null;
|
|
596
|
+
});
|
|
597
|
+
startPromise = pending;
|
|
598
|
+
return pending;
|
|
556
599
|
};
|
|
557
600
|
const stop = () => {
|
|
558
|
-
|
|
559
|
-
sourceNode
|
|
560
|
-
mediaStream?.getTracks().forEach((track) => track.stop());
|
|
561
|
-
audioContext?.close();
|
|
601
|
+
lifecycleGeneration += 1;
|
|
602
|
+
release({ audioContext, mediaStream, processorNode, sourceNode });
|
|
562
603
|
options.onLevel?.(0);
|
|
563
604
|
audioContext = null;
|
|
564
605
|
mediaStream = null;
|
|
@@ -770,6 +811,7 @@ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
|
770
811
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
771
812
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
772
813
|
var DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS = 5000;
|
|
814
|
+
var DEFAULT_PREPARE_RECONNECT_TIMEOUT_MS = 1e4;
|
|
773
815
|
var MAX_PENDING_CONTROL_MESSAGES = 100;
|
|
774
816
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
775
817
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
@@ -863,6 +905,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
863
905
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
864
906
|
let lifecycleGeneration = 0;
|
|
865
907
|
let disposed = false;
|
|
908
|
+
let reconnectPreparationAbort = null;
|
|
866
909
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
867
910
|
const state = {
|
|
868
911
|
isConnected: false,
|
|
@@ -950,12 +993,30 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
950
993
|
return;
|
|
951
994
|
}
|
|
952
995
|
try {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
996
|
+
if (options.prepareReconnect) {
|
|
997
|
+
const preparationAbort = new AbortController;
|
|
998
|
+
reconnectPreparationAbort = preparationAbort;
|
|
999
|
+
const timeout = setTimeout(() => preparationAbort.abort("Voice reconnect preparation timed out"), options.prepareReconnectTimeoutMs ?? DEFAULT_PREPARE_RECONNECT_TIMEOUT_MS);
|
|
1000
|
+
try {
|
|
1001
|
+
await Promise.race([
|
|
1002
|
+
options.prepareReconnect({
|
|
1003
|
+
attempt: state.reconnectAttempts,
|
|
1004
|
+
path,
|
|
1005
|
+
scenarioId: state.scenarioId,
|
|
1006
|
+
sessionId: state.sessionId,
|
|
1007
|
+
signal: preparationAbort.signal
|
|
1008
|
+
}),
|
|
1009
|
+
new Promise((_, reject) => {
|
|
1010
|
+
preparationAbort.signal.addEventListener("abort", () => reject(new Error("Voice reconnect preparation timed out.")), { once: true });
|
|
1011
|
+
})
|
|
1012
|
+
]);
|
|
1013
|
+
} finally {
|
|
1014
|
+
clearTimeout(timeout);
|
|
1015
|
+
if (reconnectPreparationAbort === preparationAbort) {
|
|
1016
|
+
reconnectPreparationAbort = null;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
959
1020
|
} catch (error) {
|
|
960
1021
|
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
961
1022
|
return;
|
|
@@ -1079,6 +1140,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1079
1140
|
state.ws = ws;
|
|
1080
1141
|
};
|
|
1081
1142
|
const sendSerialized = (value) => {
|
|
1143
|
+
if (disposed)
|
|
1144
|
+
return;
|
|
1082
1145
|
if (state.ws?.readyState === WS_OPEN) {
|
|
1083
1146
|
state.ws.send(value);
|
|
1084
1147
|
return;
|
|
@@ -1133,6 +1196,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1133
1196
|
};
|
|
1134
1197
|
const close = (reason = "client-close") => {
|
|
1135
1198
|
disposed = true;
|
|
1199
|
+
reconnectPreparationAbort?.abort();
|
|
1200
|
+
reconnectPreparationAbort = null;
|
|
1136
1201
|
lifecycleGeneration += 1;
|
|
1137
1202
|
clearTimers();
|
|
1138
1203
|
rejectPendingCallControls("Voice connection closed");
|
|
@@ -1150,6 +1215,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1150
1215
|
};
|
|
1151
1216
|
const disconnect = () => {
|
|
1152
1217
|
disposed = true;
|
|
1218
|
+
reconnectPreparationAbort?.abort();
|
|
1219
|
+
reconnectPreparationAbort = null;
|
|
1153
1220
|
lifecycleGeneration += 1;
|
|
1154
1221
|
clearTimers();
|
|
1155
1222
|
rejectPendingCallControls("Voice connection disconnected");
|
|
@@ -1401,7 +1468,10 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
1401
1468
|
getSessionId: () => options.browserMedia ? options.browserMedia.getSessionId?.() ?? connection.getSessionId() : connection.getSessionId()
|
|
1402
1469
|
}) : null;
|
|
1403
1470
|
const subscribers = new Set;
|
|
1471
|
+
let disposed = false;
|
|
1404
1472
|
const start = (input) => Promise.resolve().then(() => {
|
|
1473
|
+
if (disposed)
|
|
1474
|
+
return;
|
|
1405
1475
|
if (!input?.sessionId && !input?.scenarioId) {
|
|
1406
1476
|
return;
|
|
1407
1477
|
}
|
|
@@ -1460,6 +1530,7 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
1460
1530
|
return connection.callControl(message);
|
|
1461
1531
|
},
|
|
1462
1532
|
close(reason) {
|
|
1533
|
+
disposed = true;
|
|
1463
1534
|
unsubscribeConnection();
|
|
1464
1535
|
browserMediaReporter?.close();
|
|
1465
1536
|
connection.close(reason);
|
|
@@ -1467,6 +1538,7 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
1467
1538
|
notify();
|
|
1468
1539
|
},
|
|
1469
1540
|
disconnect() {
|
|
1541
|
+
disposed = true;
|
|
1470
1542
|
unsubscribeConnection();
|
|
1471
1543
|
browserMediaReporter?.close();
|
|
1472
1544
|
connection.disconnect();
|
|
@@ -1870,6 +1942,9 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1870
1942
|
...options.connection
|
|
1871
1943
|
});
|
|
1872
1944
|
let capture = null;
|
|
1945
|
+
let captureGeneration = 0;
|
|
1946
|
+
let disposed = false;
|
|
1947
|
+
let startRecordingPromise = null;
|
|
1873
1948
|
let state = createInitialState2(stream);
|
|
1874
1949
|
const subscribers = new Set;
|
|
1875
1950
|
const notify = () => {
|
|
@@ -1926,6 +2001,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1926
2001
|
return capture;
|
|
1927
2002
|
};
|
|
1928
2003
|
const stopRecording = () => {
|
|
2004
|
+
captureGeneration += 1;
|
|
1929
2005
|
capture?.stop();
|
|
1930
2006
|
capture = null;
|
|
1931
2007
|
state = {
|
|
@@ -1935,39 +2011,63 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1935
2011
|
notify();
|
|
1936
2012
|
};
|
|
1937
2013
|
const startRecording = async () => {
|
|
2014
|
+
if (disposed) {
|
|
2015
|
+
throw new Error("Voice controller is closed.");
|
|
2016
|
+
}
|
|
1938
2017
|
if (state.isRecording) {
|
|
1939
2018
|
return;
|
|
1940
2019
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2020
|
+
if (startRecordingPromise)
|
|
2021
|
+
return startRecordingPromise;
|
|
2022
|
+
const generation = ++captureGeneration;
|
|
2023
|
+
const activeCapture = ensureCapture();
|
|
2024
|
+
const operation = (async () => {
|
|
2025
|
+
try {
|
|
2026
|
+
state = {
|
|
2027
|
+
...state,
|
|
2028
|
+
recordingError: null
|
|
2029
|
+
};
|
|
2030
|
+
notify();
|
|
2031
|
+
await activeCapture.start();
|
|
2032
|
+
if (disposed || generation !== captureGeneration || capture !== activeCapture) {
|
|
2033
|
+
activeCapture.stop();
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
state = {
|
|
2037
|
+
...state,
|
|
2038
|
+
isRecording: true
|
|
2039
|
+
};
|
|
2040
|
+
notify();
|
|
2041
|
+
} catch (error) {
|
|
2042
|
+
activeCapture.stop();
|
|
2043
|
+
if (generation !== captureGeneration || capture !== activeCapture) {
|
|
2044
|
+
throw error;
|
|
2045
|
+
}
|
|
2046
|
+
capture = null;
|
|
2047
|
+
state = {
|
|
2048
|
+
...state,
|
|
2049
|
+
isRecording: false,
|
|
2050
|
+
recordingError: error instanceof Error ? error.message : String(error)
|
|
2051
|
+
};
|
|
2052
|
+
notify();
|
|
2053
|
+
throw error;
|
|
2054
|
+
}
|
|
2055
|
+
})();
|
|
2056
|
+
const pending = operation.finally(() => {
|
|
2057
|
+
if (startRecordingPromise === pending)
|
|
2058
|
+
startRecordingPromise = null;
|
|
2059
|
+
});
|
|
2060
|
+
startRecordingPromise = pending;
|
|
2061
|
+
return startRecordingPromise;
|
|
1964
2062
|
};
|
|
1965
2063
|
const close = (reason) => {
|
|
2064
|
+
disposed = true;
|
|
1966
2065
|
unsubscribeStream();
|
|
1967
2066
|
stopRecording();
|
|
1968
2067
|
stream.close(reason);
|
|
1969
2068
|
};
|
|
1970
2069
|
const disconnect = () => {
|
|
2070
|
+
disposed = true;
|
|
1971
2071
|
unsubscribeStream();
|
|
1972
2072
|
stopRecording();
|
|
1973
2073
|
stream.disconnect();
|