@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
|
@@ -103,48 +103,89 @@ var createMicrophoneCapture = (options) => {
|
|
|
103
103
|
let sourceNode = null;
|
|
104
104
|
let processorNode = null;
|
|
105
105
|
let mediaStream = null;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
106
|
+
let lifecycleGeneration = 0;
|
|
107
|
+
let startPromise = null;
|
|
108
|
+
const release = (resources) => {
|
|
109
|
+
resources.processorNode?.disconnect();
|
|
110
|
+
resources.sourceNode?.disconnect();
|
|
111
|
+
resources.mediaStream?.getTracks().forEach((track) => track.stop());
|
|
112
|
+
resources.audioContext?.close();
|
|
113
|
+
};
|
|
114
|
+
const start = () => {
|
|
115
|
+
if (mediaStream)
|
|
116
|
+
return Promise.resolve();
|
|
117
|
+
if (startPromise)
|
|
118
|
+
return startPromise;
|
|
119
|
+
const generation = ++lifecycleGeneration;
|
|
120
|
+
const operation = async () => {
|
|
121
|
+
if (!options.stream && (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia)) {
|
|
122
|
+
throw new Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");
|
|
123
|
+
}
|
|
124
|
+
const AudioContextCtor = (typeof window !== "undefined" ? window.AudioContext ?? window.webkitAudioContext : undefined) ?? AudioContext;
|
|
125
|
+
if (!AudioContextCtor) {
|
|
126
|
+
throw new Error("Browser microphone capture requires AudioContext support.");
|
|
127
|
+
}
|
|
128
|
+
let pendingAudioContext = null;
|
|
129
|
+
let pendingMediaStream = null;
|
|
130
|
+
let pendingProcessorNode = null;
|
|
131
|
+
let pendingSourceNode = null;
|
|
132
|
+
try {
|
|
133
|
+
pendingMediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
|
|
134
|
+
audio: {
|
|
135
|
+
autoGainControl: true,
|
|
136
|
+
channelCount: options.channelCount ?? 1,
|
|
137
|
+
echoCancellation: true,
|
|
138
|
+
noiseSuppression: true
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
if (generation !== lifecycleGeneration) {
|
|
142
|
+
throw new Error("Microphone capture stopped during startup.");
|
|
121
143
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
144
|
+
pendingAudioContext = new AudioContextCtor;
|
|
145
|
+
if (pendingAudioContext.state === "suspended") {
|
|
146
|
+
await pendingAudioContext.resume();
|
|
147
|
+
}
|
|
148
|
+
if (generation !== lifecycleGeneration) {
|
|
149
|
+
throw new Error("Microphone capture stopped during startup.");
|
|
150
|
+
}
|
|
151
|
+
pendingSourceNode = pendingAudioContext.createMediaStreamSource(pendingMediaStream);
|
|
152
|
+
pendingProcessorNode = pendingAudioContext.createScriptProcessor(4096, 1, 1);
|
|
153
|
+
pendingProcessorNode.onaudioprocess = (event) => {
|
|
154
|
+
const channel = event.inputBuffer.getChannelData(0);
|
|
155
|
+
const downsampled = downsampleBuffer(channel, pendingAudioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
|
|
156
|
+
const pcm = floatTo16BitPCM(downsampled);
|
|
157
|
+
options.onLevel?.(getPcmLevel(pcm));
|
|
158
|
+
options.onAudio(pcm);
|
|
159
|
+
};
|
|
160
|
+
pendingSourceNode.connect(pendingProcessorNode);
|
|
161
|
+
pendingProcessorNode.connect(pendingAudioContext.destination);
|
|
162
|
+
if (generation !== lifecycleGeneration) {
|
|
163
|
+
throw new Error("Microphone capture stopped during startup.");
|
|
164
|
+
}
|
|
165
|
+
audioContext = pendingAudioContext;
|
|
166
|
+
mediaStream = pendingMediaStream;
|
|
167
|
+
processorNode = pendingProcessorNode;
|
|
168
|
+
sourceNode = pendingSourceNode;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
release({
|
|
171
|
+
audioContext: pendingAudioContext,
|
|
172
|
+
mediaStream: pendingMediaStream,
|
|
173
|
+
processorNode: pendingProcessorNode,
|
|
174
|
+
sourceNode: pendingSourceNode
|
|
175
|
+
});
|
|
176
|
+
throw error;
|
|
126
177
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
options.onAudio(pcm);
|
|
135
|
-
};
|
|
136
|
-
sourceNode.connect(processorNode);
|
|
137
|
-
processorNode.connect(audioContext.destination);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
stop();
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
178
|
+
};
|
|
179
|
+
const pending = operation().finally(() => {
|
|
180
|
+
if (startPromise === pending)
|
|
181
|
+
startPromise = null;
|
|
182
|
+
});
|
|
183
|
+
startPromise = pending;
|
|
184
|
+
return pending;
|
|
142
185
|
};
|
|
143
186
|
const stop = () => {
|
|
144
|
-
|
|
145
|
-
sourceNode
|
|
146
|
-
mediaStream?.getTracks().forEach((track) => track.stop());
|
|
147
|
-
audioContext?.close();
|
|
187
|
+
lifecycleGeneration += 1;
|
|
188
|
+
release({ audioContext, mediaStream, processorNode, sourceNode });
|
|
148
189
|
options.onLevel?.(0);
|
|
149
190
|
audioContext = null;
|
|
150
191
|
mediaStream = null;
|
|
@@ -505,11 +546,19 @@ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
|
505
546
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
506
547
|
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
507
548
|
var DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS = 5000;
|
|
549
|
+
var DEFAULT_PREPARE_RECONNECT_TIMEOUT_MS = 1e4;
|
|
508
550
|
var MAX_PENDING_CONTROL_MESSAGES = 100;
|
|
509
551
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
510
552
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
511
553
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
512
554
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
555
|
+
|
|
556
|
+
class VoiceReconnectRejectedError extends Error {
|
|
557
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
558
|
+
super(message);
|
|
559
|
+
this.name = "VoiceReconnectRejectedError";
|
|
560
|
+
}
|
|
561
|
+
}
|
|
513
562
|
var noop = () => {};
|
|
514
563
|
var noopAsync = () => Promise.resolve();
|
|
515
564
|
var noopUnsubscribe = () => noop;
|
|
@@ -589,6 +638,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
589
638
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
590
639
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
591
640
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
641
|
+
let lifecycleGeneration = 0;
|
|
642
|
+
let disposed = false;
|
|
643
|
+
let reconnectPreparationAbort = null;
|
|
592
644
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
593
645
|
const state = {
|
|
594
646
|
isConnected: false,
|
|
@@ -652,6 +704,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
652
704
|
pendingCallControls.clear();
|
|
653
705
|
};
|
|
654
706
|
const scheduleReconnect = () => {
|
|
707
|
+
if (disposed)
|
|
708
|
+
return;
|
|
709
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
655
710
|
state.reconnectAttempts += 1;
|
|
656
711
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
657
712
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -666,18 +721,44 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
666
721
|
type: "connection"
|
|
667
722
|
});
|
|
668
723
|
state.reconnectTimeout = setTimeout(async () => {
|
|
724
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
725
|
+
return;
|
|
669
726
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
670
727
|
emitTerminalFailure("Voice connection could not be restored.");
|
|
671
728
|
return;
|
|
672
729
|
}
|
|
673
730
|
try {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
731
|
+
if (options.prepareReconnect) {
|
|
732
|
+
const preparationAbort = new AbortController;
|
|
733
|
+
reconnectPreparationAbort = preparationAbort;
|
|
734
|
+
const timeout = setTimeout(() => preparationAbort.abort("Voice reconnect preparation timed out"), options.prepareReconnectTimeoutMs ?? DEFAULT_PREPARE_RECONNECT_TIMEOUT_MS);
|
|
735
|
+
try {
|
|
736
|
+
await Promise.race([
|
|
737
|
+
options.prepareReconnect({
|
|
738
|
+
attempt: state.reconnectAttempts,
|
|
739
|
+
path,
|
|
740
|
+
scenarioId: state.scenarioId,
|
|
741
|
+
sessionId: state.sessionId,
|
|
742
|
+
signal: preparationAbort.signal
|
|
743
|
+
}),
|
|
744
|
+
new Promise((_, reject) => {
|
|
745
|
+
preparationAbort.signal.addEventListener("abort", () => reject(new Error("Voice reconnect preparation timed out.")), { once: true });
|
|
746
|
+
})
|
|
747
|
+
]);
|
|
748
|
+
} finally {
|
|
749
|
+
clearTimeout(timeout);
|
|
750
|
+
if (reconnectPreparationAbort === preparationAbort) {
|
|
751
|
+
reconnectPreparationAbort = null;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
} catch (error) {
|
|
756
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
757
|
+
return;
|
|
758
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
759
|
+
emitTerminalFailure(error.message);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
681
762
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
682
763
|
scheduleReconnect();
|
|
683
764
|
} else {
|
|
@@ -685,10 +766,14 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
685
766
|
}
|
|
686
767
|
return;
|
|
687
768
|
}
|
|
769
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
770
|
+
return;
|
|
688
771
|
connect();
|
|
689
772
|
}, delayMs);
|
|
690
773
|
};
|
|
691
774
|
const connect = () => {
|
|
775
|
+
if (disposed)
|
|
776
|
+
return;
|
|
692
777
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
693
778
|
ws.binaryType = "arraybuffer";
|
|
694
779
|
ws.onopen = () => {
|
|
@@ -759,6 +844,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
759
844
|
state.ws = null;
|
|
760
845
|
state.isConnected = false;
|
|
761
846
|
clearTimers();
|
|
847
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
762
848
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
763
849
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
764
850
|
if (authorizationFailure) {
|
|
@@ -789,6 +875,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
789
875
|
state.ws = ws;
|
|
790
876
|
};
|
|
791
877
|
const sendSerialized = (value) => {
|
|
878
|
+
if (disposed)
|
|
879
|
+
return;
|
|
792
880
|
if (state.ws?.readyState === WS_OPEN) {
|
|
793
881
|
state.ws.send(value);
|
|
794
882
|
return;
|
|
@@ -824,6 +912,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
824
912
|
send({ type: "end_turn" });
|
|
825
913
|
};
|
|
826
914
|
const callControl = (message) => {
|
|
915
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
916
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
917
|
+
}
|
|
827
918
|
const requestId = crypto.randomUUID();
|
|
828
919
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
829
920
|
const timeout = setTimeout(() => {
|
|
@@ -831,16 +922,21 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
831
922
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
832
923
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
833
924
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
834
|
-
send({
|
|
925
|
+
state.ws.send(JSON.stringify({
|
|
835
926
|
...message,
|
|
836
927
|
requestId,
|
|
837
928
|
type: "call_control"
|
|
838
|
-
});
|
|
929
|
+
}));
|
|
839
930
|
return promise;
|
|
840
931
|
};
|
|
841
932
|
const close = (reason = "client-close") => {
|
|
933
|
+
disposed = true;
|
|
934
|
+
reconnectPreparationAbort?.abort();
|
|
935
|
+
reconnectPreparationAbort = null;
|
|
936
|
+
lifecycleGeneration += 1;
|
|
842
937
|
clearTimers();
|
|
843
938
|
rejectPendingCallControls("Voice connection closed");
|
|
939
|
+
state.pendingMessages.length = 0;
|
|
844
940
|
if (state.ws) {
|
|
845
941
|
const ws = state.ws;
|
|
846
942
|
state.ws = null;
|
|
@@ -853,8 +949,13 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
853
949
|
listeners.clear();
|
|
854
950
|
};
|
|
855
951
|
const disconnect = () => {
|
|
952
|
+
disposed = true;
|
|
953
|
+
reconnectPreparationAbort?.abort();
|
|
954
|
+
reconnectPreparationAbort = null;
|
|
955
|
+
lifecycleGeneration += 1;
|
|
856
956
|
clearTimers();
|
|
857
957
|
rejectPendingCallControls("Voice connection disconnected");
|
|
958
|
+
state.pendingMessages.length = 0;
|
|
858
959
|
if (state.ws) {
|
|
859
960
|
const ws = state.ws;
|
|
860
961
|
state.ws = null;
|
|
@@ -1102,7 +1203,10 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
1102
1203
|
getSessionId: () => options.browserMedia ? options.browserMedia.getSessionId?.() ?? connection.getSessionId() : connection.getSessionId()
|
|
1103
1204
|
}) : null;
|
|
1104
1205
|
const subscribers = new Set;
|
|
1206
|
+
let disposed = false;
|
|
1105
1207
|
const start = (input) => Promise.resolve().then(() => {
|
|
1208
|
+
if (disposed)
|
|
1209
|
+
return;
|
|
1106
1210
|
if (!input?.sessionId && !input?.scenarioId) {
|
|
1107
1211
|
return;
|
|
1108
1212
|
}
|
|
@@ -1161,6 +1265,7 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
1161
1265
|
return connection.callControl(message);
|
|
1162
1266
|
},
|
|
1163
1267
|
close(reason) {
|
|
1268
|
+
disposed = true;
|
|
1164
1269
|
unsubscribeConnection();
|
|
1165
1270
|
browserMediaReporter?.close();
|
|
1166
1271
|
connection.close(reason);
|
|
@@ -1168,6 +1273,7 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
1168
1273
|
notify();
|
|
1169
1274
|
},
|
|
1170
1275
|
disconnect() {
|
|
1276
|
+
disposed = true;
|
|
1171
1277
|
unsubscribeConnection();
|
|
1172
1278
|
browserMediaReporter?.close();
|
|
1173
1279
|
connection.disconnect();
|
|
@@ -1534,6 +1640,9 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1534
1640
|
...options.connection
|
|
1535
1641
|
});
|
|
1536
1642
|
let capture = null;
|
|
1643
|
+
let captureGeneration = 0;
|
|
1644
|
+
let disposed = false;
|
|
1645
|
+
let startRecordingPromise = null;
|
|
1537
1646
|
let state = createInitialState2(stream);
|
|
1538
1647
|
const subscribers = new Set;
|
|
1539
1648
|
const notify = () => {
|
|
@@ -1590,6 +1699,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1590
1699
|
return capture;
|
|
1591
1700
|
};
|
|
1592
1701
|
const stopRecording = () => {
|
|
1702
|
+
captureGeneration += 1;
|
|
1593
1703
|
capture?.stop();
|
|
1594
1704
|
capture = null;
|
|
1595
1705
|
state = {
|
|
@@ -1599,39 +1709,63 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1599
1709
|
notify();
|
|
1600
1710
|
};
|
|
1601
1711
|
const startRecording = async () => {
|
|
1712
|
+
if (disposed) {
|
|
1713
|
+
throw new Error("Voice controller is closed.");
|
|
1714
|
+
}
|
|
1602
1715
|
if (state.isRecording) {
|
|
1603
1716
|
return;
|
|
1604
1717
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1718
|
+
if (startRecordingPromise)
|
|
1719
|
+
return startRecordingPromise;
|
|
1720
|
+
const generation = ++captureGeneration;
|
|
1721
|
+
const activeCapture = ensureCapture();
|
|
1722
|
+
const operation = (async () => {
|
|
1723
|
+
try {
|
|
1724
|
+
state = {
|
|
1725
|
+
...state,
|
|
1726
|
+
recordingError: null
|
|
1727
|
+
};
|
|
1728
|
+
notify();
|
|
1729
|
+
await activeCapture.start();
|
|
1730
|
+
if (disposed || generation !== captureGeneration || capture !== activeCapture) {
|
|
1731
|
+
activeCapture.stop();
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
state = {
|
|
1735
|
+
...state,
|
|
1736
|
+
isRecording: true
|
|
1737
|
+
};
|
|
1738
|
+
notify();
|
|
1739
|
+
} catch (error) {
|
|
1740
|
+
activeCapture.stop();
|
|
1741
|
+
if (generation !== captureGeneration || capture !== activeCapture) {
|
|
1742
|
+
throw error;
|
|
1743
|
+
}
|
|
1744
|
+
capture = null;
|
|
1745
|
+
state = {
|
|
1746
|
+
...state,
|
|
1747
|
+
isRecording: false,
|
|
1748
|
+
recordingError: error instanceof Error ? error.message : String(error)
|
|
1749
|
+
};
|
|
1750
|
+
notify();
|
|
1751
|
+
throw error;
|
|
1752
|
+
}
|
|
1753
|
+
})();
|
|
1754
|
+
const pending = operation.finally(() => {
|
|
1755
|
+
if (startRecordingPromise === pending)
|
|
1756
|
+
startRecordingPromise = null;
|
|
1757
|
+
});
|
|
1758
|
+
startRecordingPromise = pending;
|
|
1759
|
+
return startRecordingPromise;
|
|
1628
1760
|
};
|
|
1629
1761
|
const close = (reason) => {
|
|
1762
|
+
disposed = true;
|
|
1630
1763
|
unsubscribeStream();
|
|
1631
1764
|
stopRecording();
|
|
1632
1765
|
stream.close(reason);
|
|
1633
1766
|
};
|
|
1634
1767
|
const disconnect = () => {
|
|
1768
|
+
disposed = true;
|
|
1635
1769
|
unsubscribeStream();
|
|
1636
1770
|
stopRecording();
|
|
1637
1771
|
stream.disconnect();
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { bindVoiceReactiveSource, voiceSseReactiveSource, } from "./reactiveSource";
|
|
2
2
|
export type { VoiceReactiveSource, VoiceSseReactiveSourceOptions, } from "./reactiveSource";
|
|
3
|
-
export { createVoiceConnection } from "./connection";
|
|
3
|
+
export { createVoiceConnection, VoiceReconnectRejectedError, } from "./connection";
|
|
4
4
|
export { checkBrowserVoiceSupport, probeBrowserVoiceSupport, } from "./browserVoiceSupport";
|
|
5
5
|
export type { BrowserVoiceSupportProbe, BrowserVoiceSupportReport, } from "./browserVoiceSupport";
|
|
6
6
|
export { createVoiceAudioPlayer, decodeVoiceAudioChunk } from "./audioPlayer";
|