@absolutejs/voice 0.0.22-beta.655 → 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 +204 -70
- package/dist/client/connection.d.ts +3 -0
- package/dist/client/htmxBootstrap.js +204 -70
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +206 -71
- package/dist/core/types.d.ts +4 -0
- package/dist/embed/index.js +204 -70
- package/dist/embed/voice-widget.js +8 -8
- package/dist/react/index.js +204 -70
- package/dist/svelte/index.js +204 -70
- package/dist/testing/index.js +205 -71
- package/dist/vue/index.js +204 -70
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -11442,11 +11442,19 @@ 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;
|
|
11448
11449
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
11449
11450
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
11451
|
+
|
|
11452
|
+
class VoiceReconnectRejectedError extends Error {
|
|
11453
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
11454
|
+
super(message);
|
|
11455
|
+
this.name = "VoiceReconnectRejectedError";
|
|
11456
|
+
}
|
|
11457
|
+
}
|
|
11450
11458
|
var noop = () => {};
|
|
11451
11459
|
var noopAsync = () => Promise.resolve();
|
|
11452
11460
|
var noopUnsubscribe = () => noop;
|
|
@@ -11526,6 +11534,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11526
11534
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
11527
11535
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
11528
11536
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
11537
|
+
let lifecycleGeneration = 0;
|
|
11538
|
+
let disposed = false;
|
|
11539
|
+
let reconnectPreparationAbort = null;
|
|
11529
11540
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
11530
11541
|
const state = {
|
|
11531
11542
|
isConnected: false,
|
|
@@ -11589,6 +11600,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11589
11600
|
pendingCallControls.clear();
|
|
11590
11601
|
};
|
|
11591
11602
|
const scheduleReconnect = () => {
|
|
11603
|
+
if (disposed)
|
|
11604
|
+
return;
|
|
11605
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
11592
11606
|
state.reconnectAttempts += 1;
|
|
11593
11607
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
11594
11608
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -11603,18 +11617,44 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11603
11617
|
type: "connection"
|
|
11604
11618
|
});
|
|
11605
11619
|
state.reconnectTimeout = setTimeout(async () => {
|
|
11620
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11621
|
+
return;
|
|
11606
11622
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
11607
11623
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
11608
11624
|
return;
|
|
11609
11625
|
}
|
|
11610
11626
|
try {
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
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
|
+
}
|
|
11651
|
+
} catch (error) {
|
|
11652
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11653
|
+
return;
|
|
11654
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
11655
|
+
emitTerminalFailure(error.message);
|
|
11656
|
+
return;
|
|
11657
|
+
}
|
|
11618
11658
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
11619
11659
|
scheduleReconnect();
|
|
11620
11660
|
} else {
|
|
@@ -11622,10 +11662,14 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11622
11662
|
}
|
|
11623
11663
|
return;
|
|
11624
11664
|
}
|
|
11665
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11666
|
+
return;
|
|
11625
11667
|
connect();
|
|
11626
11668
|
}, delayMs);
|
|
11627
11669
|
};
|
|
11628
11670
|
const connect = () => {
|
|
11671
|
+
if (disposed)
|
|
11672
|
+
return;
|
|
11629
11673
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
11630
11674
|
ws.binaryType = "arraybuffer";
|
|
11631
11675
|
ws.onopen = () => {
|
|
@@ -11696,6 +11740,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11696
11740
|
state.ws = null;
|
|
11697
11741
|
state.isConnected = false;
|
|
11698
11742
|
clearTimers();
|
|
11743
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
11699
11744
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
11700
11745
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
11701
11746
|
if (authorizationFailure) {
|
|
@@ -11726,6 +11771,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11726
11771
|
state.ws = ws;
|
|
11727
11772
|
};
|
|
11728
11773
|
const sendSerialized = (value) => {
|
|
11774
|
+
if (disposed)
|
|
11775
|
+
return;
|
|
11729
11776
|
if (state.ws?.readyState === WS_OPEN) {
|
|
11730
11777
|
state.ws.send(value);
|
|
11731
11778
|
return;
|
|
@@ -11761,6 +11808,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11761
11808
|
send({ type: "end_turn" });
|
|
11762
11809
|
};
|
|
11763
11810
|
const callControl = (message) => {
|
|
11811
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
11812
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
11813
|
+
}
|
|
11764
11814
|
const requestId = crypto.randomUUID();
|
|
11765
11815
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
11766
11816
|
const timeout = setTimeout(() => {
|
|
@@ -11768,16 +11818,21 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11768
11818
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
11769
11819
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
11770
11820
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
11771
|
-
send({
|
|
11821
|
+
state.ws.send(JSON.stringify({
|
|
11772
11822
|
...message,
|
|
11773
11823
|
requestId,
|
|
11774
11824
|
type: "call_control"
|
|
11775
|
-
});
|
|
11825
|
+
}));
|
|
11776
11826
|
return promise;
|
|
11777
11827
|
};
|
|
11778
11828
|
const close = (reason = "client-close") => {
|
|
11829
|
+
disposed = true;
|
|
11830
|
+
reconnectPreparationAbort?.abort();
|
|
11831
|
+
reconnectPreparationAbort = null;
|
|
11832
|
+
lifecycleGeneration += 1;
|
|
11779
11833
|
clearTimers();
|
|
11780
11834
|
rejectPendingCallControls("Voice connection closed");
|
|
11835
|
+
state.pendingMessages.length = 0;
|
|
11781
11836
|
if (state.ws) {
|
|
11782
11837
|
const ws = state.ws;
|
|
11783
11838
|
state.ws = null;
|
|
@@ -11790,8 +11845,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11790
11845
|
listeners.clear();
|
|
11791
11846
|
};
|
|
11792
11847
|
const disconnect = () => {
|
|
11848
|
+
disposed = true;
|
|
11849
|
+
reconnectPreparationAbort?.abort();
|
|
11850
|
+
reconnectPreparationAbort = null;
|
|
11851
|
+
lifecycleGeneration += 1;
|
|
11793
11852
|
clearTimers();
|
|
11794
11853
|
rejectPendingCallControls("Voice connection disconnected");
|
|
11854
|
+
state.pendingMessages.length = 0;
|
|
11795
11855
|
if (state.ws) {
|
|
11796
11856
|
const ws = state.ws;
|
|
11797
11857
|
state.ws = null;
|
|
@@ -12039,7 +12099,10 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
12039
12099
|
getSessionId: () => options.browserMedia ? options.browserMedia.getSessionId?.() ?? connection.getSessionId() : connection.getSessionId()
|
|
12040
12100
|
}) : null;
|
|
12041
12101
|
const subscribers = new Set;
|
|
12102
|
+
let disposed = false;
|
|
12042
12103
|
const start = (input) => Promise.resolve().then(() => {
|
|
12104
|
+
if (disposed)
|
|
12105
|
+
return;
|
|
12043
12106
|
if (!input?.sessionId && !input?.scenarioId) {
|
|
12044
12107
|
return;
|
|
12045
12108
|
}
|
|
@@ -12098,6 +12161,7 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
12098
12161
|
return connection.callControl(message);
|
|
12099
12162
|
},
|
|
12100
12163
|
close(reason) {
|
|
12164
|
+
disposed = true;
|
|
12101
12165
|
unsubscribeConnection();
|
|
12102
12166
|
browserMediaReporter?.close();
|
|
12103
12167
|
connection.close(reason);
|
|
@@ -12105,6 +12169,7 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
12105
12169
|
notify();
|
|
12106
12170
|
},
|
|
12107
12171
|
disconnect() {
|
|
12172
|
+
disposed = true;
|
|
12108
12173
|
unsubscribeConnection();
|
|
12109
12174
|
browserMediaReporter?.close();
|
|
12110
12175
|
connection.disconnect();
|
|
@@ -12302,48 +12367,89 @@ var createMicrophoneCapture = (options) => {
|
|
|
12302
12367
|
let sourceNode = null;
|
|
12303
12368
|
let processorNode = null;
|
|
12304
12369
|
let mediaStream = null;
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
|
|
12310
|
-
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
|
|
12314
|
-
|
|
12315
|
-
|
|
12316
|
-
|
|
12317
|
-
|
|
12318
|
-
|
|
12319
|
-
|
|
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.");
|
|
12320
12407
|
}
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
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;
|
|
12325
12441
|
}
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
options.onAudio(pcm);
|
|
12334
|
-
};
|
|
12335
|
-
sourceNode.connect(processorNode);
|
|
12336
|
-
processorNode.connect(audioContext.destination);
|
|
12337
|
-
} catch (error) {
|
|
12338
|
-
stop();
|
|
12339
|
-
throw error;
|
|
12340
|
-
}
|
|
12442
|
+
};
|
|
12443
|
+
const pending = operation().finally(() => {
|
|
12444
|
+
if (startPromise === pending)
|
|
12445
|
+
startPromise = null;
|
|
12446
|
+
});
|
|
12447
|
+
startPromise = pending;
|
|
12448
|
+
return pending;
|
|
12341
12449
|
};
|
|
12342
12450
|
const stop = () => {
|
|
12343
|
-
|
|
12344
|
-
sourceNode
|
|
12345
|
-
mediaStream?.getTracks().forEach((track) => track.stop());
|
|
12346
|
-
audioContext?.close();
|
|
12451
|
+
lifecycleGeneration += 1;
|
|
12452
|
+
release({ audioContext, mediaStream, processorNode, sourceNode });
|
|
12347
12453
|
options.onLevel?.(0);
|
|
12348
12454
|
audioContext = null;
|
|
12349
12455
|
mediaStream = null;
|
|
@@ -12699,6 +12805,9 @@ var createVoiceController = (path, options = {}) => {
|
|
|
12699
12805
|
...options.connection
|
|
12700
12806
|
});
|
|
12701
12807
|
let capture = null;
|
|
12808
|
+
let captureGeneration = 0;
|
|
12809
|
+
let disposed = false;
|
|
12810
|
+
let startRecordingPromise = null;
|
|
12702
12811
|
let state = createInitialState2(stream);
|
|
12703
12812
|
const subscribers = new Set;
|
|
12704
12813
|
const notify = () => {
|
|
@@ -12755,6 +12864,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
12755
12864
|
return capture;
|
|
12756
12865
|
};
|
|
12757
12866
|
const stopRecording = () => {
|
|
12867
|
+
captureGeneration += 1;
|
|
12758
12868
|
capture?.stop();
|
|
12759
12869
|
capture = null;
|
|
12760
12870
|
state = {
|
|
@@ -12764,39 +12874,63 @@ var createVoiceController = (path, options = {}) => {
|
|
|
12764
12874
|
notify();
|
|
12765
12875
|
};
|
|
12766
12876
|
const startRecording = async () => {
|
|
12877
|
+
if (disposed) {
|
|
12878
|
+
throw new Error("Voice controller is closed.");
|
|
12879
|
+
}
|
|
12767
12880
|
if (state.isRecording) {
|
|
12768
12881
|
return;
|
|
12769
12882
|
}
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
|
|
12780
|
-
|
|
12781
|
-
|
|
12782
|
-
|
|
12783
|
-
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
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;
|
|
12793
12925
|
};
|
|
12794
12926
|
const close = (reason) => {
|
|
12927
|
+
disposed = true;
|
|
12795
12928
|
unsubscribeStream();
|
|
12796
12929
|
stopRecording();
|
|
12797
12930
|
stream.close(reason);
|
|
12798
12931
|
};
|
|
12799
12932
|
const disconnect = () => {
|
|
12933
|
+
disposed = true;
|
|
12800
12934
|
unsubscribeStream();
|
|
12801
12935
|
stopRecording();
|
|
12802
12936
|
stream.disconnect();
|