@absolutejs/voice 0.0.22-beta.647 → 0.0.22-beta.649
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 +67 -29
- package/dist/client/connection.d.ts +2 -1
- package/dist/client/htmxBootstrap.js +67 -29
- package/dist/client/index.js +67 -29
- package/dist/core/types.d.ts +21 -2
- package/dist/embed/index.js +66 -28
- package/dist/embed/voice-widget.js +7 -7
- package/dist/index.js +34 -1
- package/dist/react/index.js +67 -29
- package/dist/svelte/index.js +67 -29
- package/dist/testing/index.js +69 -31
- package/dist/vue/index.js +67 -29
- package/package.json +2 -2
package/dist/angular/index.js
CHANGED
|
@@ -343,29 +343,34 @@ var createMicrophoneCapture = (options) => {
|
|
|
343
343
|
if (!AudioContextCtor) {
|
|
344
344
|
throw new Error("Browser microphone capture requires AudioContext support.");
|
|
345
345
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
346
|
+
try {
|
|
347
|
+
mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
|
|
348
|
+
audio: {
|
|
349
|
+
autoGainControl: true,
|
|
350
|
+
channelCount: options.channelCount ?? 1,
|
|
351
|
+
echoCancellation: true,
|
|
352
|
+
noiseSuppression: true
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
audioContext = new AudioContextCtor;
|
|
356
|
+
if (audioContext.state === "suspended") {
|
|
357
|
+
await audioContext.resume();
|
|
352
358
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
processorNode.connect(audioContext.destination);
|
|
359
|
+
sourceNode = audioContext.createMediaStreamSource(mediaStream);
|
|
360
|
+
processorNode = audioContext.createScriptProcessor(4096, 1, 1);
|
|
361
|
+
processorNode.onaudioprocess = (event) => {
|
|
362
|
+
const channel = event.inputBuffer.getChannelData(0);
|
|
363
|
+
const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
|
|
364
|
+
const pcm = floatTo16BitPCM(downsampled);
|
|
365
|
+
options.onLevel?.(getPcmLevel(pcm));
|
|
366
|
+
options.onAudio(pcm);
|
|
367
|
+
};
|
|
368
|
+
sourceNode.connect(processorNode);
|
|
369
|
+
processorNode.connect(audioContext.destination);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
stop();
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
369
374
|
};
|
|
370
375
|
const stop = () => {
|
|
371
376
|
processorNode?.disconnect();
|
|
@@ -585,6 +590,7 @@ var noopUnsubscribe = () => noop;
|
|
|
585
590
|
var NOOP_CONNECTION = {
|
|
586
591
|
callControl: noop,
|
|
587
592
|
close: noop,
|
|
593
|
+
disconnect: noop,
|
|
588
594
|
endTurn: noop,
|
|
589
595
|
send: noop,
|
|
590
596
|
sendAudio: noop,
|
|
@@ -596,7 +602,7 @@ var NOOP_CONNECTION = {
|
|
|
596
602
|
start: () => {}
|
|
597
603
|
};
|
|
598
604
|
var createSessionId = () => crypto.randomUUID();
|
|
599
|
-
var buildWsUrl = (path, sessionId, scenarioId) => {
|
|
605
|
+
var buildWsUrl = (path, sessionId, scenarioId, query) => {
|
|
600
606
|
const { hostname, port, protocol } = window.location;
|
|
601
607
|
const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
|
|
602
608
|
const portSuffix = port ? `:${port}` : "";
|
|
@@ -605,6 +611,11 @@ var buildWsUrl = (path, sessionId, scenarioId) => {
|
|
|
605
611
|
if (scenarioId) {
|
|
606
612
|
url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
|
|
607
613
|
}
|
|
614
|
+
for (const [key, value] of Object.entries(query)) {
|
|
615
|
+
if (key !== "sessionId" && key !== DEFAULT_SCENARIO_QUERY_PARAM && value) {
|
|
616
|
+
url.searchParams.set(key, value);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
608
619
|
return url.toString();
|
|
609
620
|
};
|
|
610
621
|
var isVoiceServerMessage = (value) => {
|
|
@@ -714,7 +725,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
714
725
|
}, delayMs);
|
|
715
726
|
};
|
|
716
727
|
const connect = () => {
|
|
717
|
-
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
|
728
|
+
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
718
729
|
ws.binaryType = "arraybuffer";
|
|
719
730
|
ws.onopen = () => {
|
|
720
731
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
@@ -810,7 +821,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
810
821
|
type: "call_control"
|
|
811
822
|
});
|
|
812
823
|
};
|
|
813
|
-
const close = () => {
|
|
824
|
+
const close = (reason = "client-close") => {
|
|
825
|
+
clearTimers();
|
|
826
|
+
if (state.ws) {
|
|
827
|
+
if (state.ws.readyState === WS_OPEN) {
|
|
828
|
+
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
829
|
+
}
|
|
830
|
+
state.ws.close(WS_NORMAL_CLOSURE);
|
|
831
|
+
state.ws = null;
|
|
832
|
+
}
|
|
833
|
+
state.isConnected = false;
|
|
834
|
+
listeners.clear();
|
|
835
|
+
};
|
|
836
|
+
const disconnect = () => {
|
|
814
837
|
clearTimers();
|
|
815
838
|
if (state.ws) {
|
|
816
839
|
state.ws.close(WS_NORMAL_CLOSURE);
|
|
@@ -834,6 +857,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
834
857
|
return {
|
|
835
858
|
callControl,
|
|
836
859
|
close,
|
|
860
|
+
disconnect,
|
|
837
861
|
endTurn,
|
|
838
862
|
send,
|
|
839
863
|
sendAudio,
|
|
@@ -1114,10 +1138,17 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
1114
1138
|
callControl(message) {
|
|
1115
1139
|
connection.callControl(message);
|
|
1116
1140
|
},
|
|
1117
|
-
close() {
|
|
1141
|
+
close(reason) {
|
|
1118
1142
|
unsubscribeConnection();
|
|
1119
1143
|
browserMediaReporter?.close();
|
|
1120
|
-
connection.close();
|
|
1144
|
+
connection.close(reason);
|
|
1145
|
+
store.dispatch({ type: "disconnected" });
|
|
1146
|
+
notify();
|
|
1147
|
+
},
|
|
1148
|
+
disconnect() {
|
|
1149
|
+
unsubscribeConnection();
|
|
1150
|
+
browserMediaReporter?.close();
|
|
1151
|
+
connection.disconnect();
|
|
1121
1152
|
store.dispatch({ type: "disconnected" });
|
|
1122
1153
|
notify();
|
|
1123
1154
|
},
|
|
@@ -1599,6 +1630,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1599
1630
|
};
|
|
1600
1631
|
notify();
|
|
1601
1632
|
} catch (error) {
|
|
1633
|
+
capture?.stop();
|
|
1602
1634
|
capture = null;
|
|
1603
1635
|
state = {
|
|
1604
1636
|
...state,
|
|
@@ -1609,13 +1641,19 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1609
1641
|
throw error;
|
|
1610
1642
|
}
|
|
1611
1643
|
};
|
|
1612
|
-
const close = () => {
|
|
1644
|
+
const close = (reason) => {
|
|
1645
|
+
unsubscribeStream();
|
|
1646
|
+
stopRecording();
|
|
1647
|
+
stream.close(reason);
|
|
1648
|
+
};
|
|
1649
|
+
const disconnect = () => {
|
|
1613
1650
|
unsubscribeStream();
|
|
1614
1651
|
stopRecording();
|
|
1615
|
-
stream.
|
|
1652
|
+
stream.disconnect();
|
|
1616
1653
|
};
|
|
1617
1654
|
return {
|
|
1618
1655
|
close,
|
|
1656
|
+
disconnect,
|
|
1619
1657
|
startRecording,
|
|
1620
1658
|
stopRecording,
|
|
1621
1659
|
get assistantAudio() {
|
|
@@ -11,7 +11,8 @@ type VoiceConnectionHandle = {
|
|
|
11
11
|
sessionId?: string;
|
|
12
12
|
scenarioId?: string;
|
|
13
13
|
}) => void;
|
|
14
|
-
close: () => void;
|
|
14
|
+
close: (reason?: string) => void;
|
|
15
|
+
disconnect: () => void;
|
|
15
16
|
endTurn: () => void;
|
|
16
17
|
getReadyState: () => number;
|
|
17
18
|
getScenarioId: () => string;
|
|
@@ -111,29 +111,34 @@ var createMicrophoneCapture = (options) => {
|
|
|
111
111
|
if (!AudioContextCtor) {
|
|
112
112
|
throw new Error("Browser microphone capture requires AudioContext support.");
|
|
113
113
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
try {
|
|
115
|
+
mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
|
|
116
|
+
audio: {
|
|
117
|
+
autoGainControl: true,
|
|
118
|
+
channelCount: options.channelCount ?? 1,
|
|
119
|
+
echoCancellation: true,
|
|
120
|
+
noiseSuppression: true
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
audioContext = new AudioContextCtor;
|
|
124
|
+
if (audioContext.state === "suspended") {
|
|
125
|
+
await audioContext.resume();
|
|
120
126
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
processorNode.connect(audioContext.destination);
|
|
127
|
+
sourceNode = audioContext.createMediaStreamSource(mediaStream);
|
|
128
|
+
processorNode = audioContext.createScriptProcessor(4096, 1, 1);
|
|
129
|
+
processorNode.onaudioprocess = (event) => {
|
|
130
|
+
const channel = event.inputBuffer.getChannelData(0);
|
|
131
|
+
const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
|
|
132
|
+
const pcm = floatTo16BitPCM(downsampled);
|
|
133
|
+
options.onLevel?.(getPcmLevel(pcm));
|
|
134
|
+
options.onAudio(pcm);
|
|
135
|
+
};
|
|
136
|
+
sourceNode.connect(processorNode);
|
|
137
|
+
processorNode.connect(audioContext.destination);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
stop();
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
137
142
|
};
|
|
138
143
|
const stop = () => {
|
|
139
144
|
processorNode?.disconnect();
|
|
@@ -502,6 +507,7 @@ var noopUnsubscribe = () => noop;
|
|
|
502
507
|
var NOOP_CONNECTION = {
|
|
503
508
|
callControl: noop,
|
|
504
509
|
close: noop,
|
|
510
|
+
disconnect: noop,
|
|
505
511
|
endTurn: noop,
|
|
506
512
|
send: noop,
|
|
507
513
|
sendAudio: noop,
|
|
@@ -513,7 +519,7 @@ var NOOP_CONNECTION = {
|
|
|
513
519
|
start: () => {}
|
|
514
520
|
};
|
|
515
521
|
var createSessionId = () => crypto.randomUUID();
|
|
516
|
-
var buildWsUrl = (path, sessionId, scenarioId) => {
|
|
522
|
+
var buildWsUrl = (path, sessionId, scenarioId, query) => {
|
|
517
523
|
const { hostname, port, protocol } = window.location;
|
|
518
524
|
const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
|
|
519
525
|
const portSuffix = port ? `:${port}` : "";
|
|
@@ -522,6 +528,11 @@ var buildWsUrl = (path, sessionId, scenarioId) => {
|
|
|
522
528
|
if (scenarioId) {
|
|
523
529
|
url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
|
|
524
530
|
}
|
|
531
|
+
for (const [key, value] of Object.entries(query)) {
|
|
532
|
+
if (key !== "sessionId" && key !== DEFAULT_SCENARIO_QUERY_PARAM && value) {
|
|
533
|
+
url.searchParams.set(key, value);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
525
536
|
return url.toString();
|
|
526
537
|
};
|
|
527
538
|
var isVoiceServerMessage = (value) => {
|
|
@@ -631,7 +642,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
631
642
|
}, delayMs);
|
|
632
643
|
};
|
|
633
644
|
const connect = () => {
|
|
634
|
-
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
|
645
|
+
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
635
646
|
ws.binaryType = "arraybuffer";
|
|
636
647
|
ws.onopen = () => {
|
|
637
648
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
@@ -727,7 +738,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
727
738
|
type: "call_control"
|
|
728
739
|
});
|
|
729
740
|
};
|
|
730
|
-
const close = () => {
|
|
741
|
+
const close = (reason = "client-close") => {
|
|
742
|
+
clearTimers();
|
|
743
|
+
if (state.ws) {
|
|
744
|
+
if (state.ws.readyState === WS_OPEN) {
|
|
745
|
+
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
746
|
+
}
|
|
747
|
+
state.ws.close(WS_NORMAL_CLOSURE);
|
|
748
|
+
state.ws = null;
|
|
749
|
+
}
|
|
750
|
+
state.isConnected = false;
|
|
751
|
+
listeners.clear();
|
|
752
|
+
};
|
|
753
|
+
const disconnect = () => {
|
|
731
754
|
clearTimers();
|
|
732
755
|
if (state.ws) {
|
|
733
756
|
state.ws.close(WS_NORMAL_CLOSURE);
|
|
@@ -751,6 +774,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
751
774
|
return {
|
|
752
775
|
callControl,
|
|
753
776
|
close,
|
|
777
|
+
disconnect,
|
|
754
778
|
endTurn,
|
|
755
779
|
send,
|
|
756
780
|
sendAudio,
|
|
@@ -1031,10 +1055,17 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
1031
1055
|
callControl(message) {
|
|
1032
1056
|
connection.callControl(message);
|
|
1033
1057
|
},
|
|
1034
|
-
close() {
|
|
1058
|
+
close(reason) {
|
|
1035
1059
|
unsubscribeConnection();
|
|
1036
1060
|
browserMediaReporter?.close();
|
|
1037
|
-
connection.close();
|
|
1061
|
+
connection.close(reason);
|
|
1062
|
+
store.dispatch({ type: "disconnected" });
|
|
1063
|
+
notify();
|
|
1064
|
+
},
|
|
1065
|
+
disconnect() {
|
|
1066
|
+
unsubscribeConnection();
|
|
1067
|
+
browserMediaReporter?.close();
|
|
1068
|
+
connection.disconnect();
|
|
1038
1069
|
store.dispatch({ type: "disconnected" });
|
|
1039
1070
|
notify();
|
|
1040
1071
|
},
|
|
@@ -1479,6 +1510,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1479
1510
|
};
|
|
1480
1511
|
notify();
|
|
1481
1512
|
} catch (error) {
|
|
1513
|
+
capture?.stop();
|
|
1482
1514
|
capture = null;
|
|
1483
1515
|
state = {
|
|
1484
1516
|
...state,
|
|
@@ -1489,13 +1521,19 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1489
1521
|
throw error;
|
|
1490
1522
|
}
|
|
1491
1523
|
};
|
|
1492
|
-
const close = () => {
|
|
1524
|
+
const close = (reason) => {
|
|
1525
|
+
unsubscribeStream();
|
|
1526
|
+
stopRecording();
|
|
1527
|
+
stream.close(reason);
|
|
1528
|
+
};
|
|
1529
|
+
const disconnect = () => {
|
|
1493
1530
|
unsubscribeStream();
|
|
1494
1531
|
stopRecording();
|
|
1495
|
-
stream.
|
|
1532
|
+
stream.disconnect();
|
|
1496
1533
|
};
|
|
1497
1534
|
return {
|
|
1498
1535
|
close,
|
|
1536
|
+
disconnect,
|
|
1499
1537
|
startRecording,
|
|
1500
1538
|
stopRecording,
|
|
1501
1539
|
get assistantAudio() {
|
package/dist/client/index.js
CHANGED
|
@@ -115,6 +115,7 @@ var noopUnsubscribe = () => noop;
|
|
|
115
115
|
var NOOP_CONNECTION = {
|
|
116
116
|
callControl: noop,
|
|
117
117
|
close: noop,
|
|
118
|
+
disconnect: noop,
|
|
118
119
|
endTurn: noop,
|
|
119
120
|
send: noop,
|
|
120
121
|
sendAudio: noop,
|
|
@@ -126,7 +127,7 @@ var NOOP_CONNECTION = {
|
|
|
126
127
|
start: () => {}
|
|
127
128
|
};
|
|
128
129
|
var createSessionId = () => crypto.randomUUID();
|
|
129
|
-
var buildWsUrl = (path, sessionId, scenarioId) => {
|
|
130
|
+
var buildWsUrl = (path, sessionId, scenarioId, query) => {
|
|
130
131
|
const { hostname, port, protocol } = window.location;
|
|
131
132
|
const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
|
|
132
133
|
const portSuffix = port ? `:${port}` : "";
|
|
@@ -135,6 +136,11 @@ var buildWsUrl = (path, sessionId, scenarioId) => {
|
|
|
135
136
|
if (scenarioId) {
|
|
136
137
|
url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
|
|
137
138
|
}
|
|
139
|
+
for (const [key, value] of Object.entries(query)) {
|
|
140
|
+
if (key !== "sessionId" && key !== DEFAULT_SCENARIO_QUERY_PARAM && value) {
|
|
141
|
+
url.searchParams.set(key, value);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
138
144
|
return url.toString();
|
|
139
145
|
};
|
|
140
146
|
var isVoiceServerMessage = (value) => {
|
|
@@ -244,7 +250,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
244
250
|
}, delayMs);
|
|
245
251
|
};
|
|
246
252
|
const connect = () => {
|
|
247
|
-
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
|
|
253
|
+
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
248
254
|
ws.binaryType = "arraybuffer";
|
|
249
255
|
ws.onopen = () => {
|
|
250
256
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
@@ -340,7 +346,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
340
346
|
type: "call_control"
|
|
341
347
|
});
|
|
342
348
|
};
|
|
343
|
-
const close = () => {
|
|
349
|
+
const close = (reason = "client-close") => {
|
|
350
|
+
clearTimers();
|
|
351
|
+
if (state.ws) {
|
|
352
|
+
if (state.ws.readyState === WS_OPEN) {
|
|
353
|
+
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
354
|
+
}
|
|
355
|
+
state.ws.close(WS_NORMAL_CLOSURE);
|
|
356
|
+
state.ws = null;
|
|
357
|
+
}
|
|
358
|
+
state.isConnected = false;
|
|
359
|
+
listeners.clear();
|
|
360
|
+
};
|
|
361
|
+
const disconnect = () => {
|
|
344
362
|
clearTimers();
|
|
345
363
|
if (state.ws) {
|
|
346
364
|
state.ws.close(WS_NORMAL_CLOSURE);
|
|
@@ -364,6 +382,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
364
382
|
return {
|
|
365
383
|
callControl,
|
|
366
384
|
close,
|
|
385
|
+
disconnect,
|
|
367
386
|
endTurn,
|
|
368
387
|
send,
|
|
369
388
|
sendAudio,
|
|
@@ -1563,10 +1582,17 @@ var createVoiceStream = (path, options = {}) => {
|
|
|
1563
1582
|
callControl(message) {
|
|
1564
1583
|
connection.callControl(message);
|
|
1565
1584
|
},
|
|
1566
|
-
close() {
|
|
1585
|
+
close(reason) {
|
|
1586
|
+
unsubscribeConnection();
|
|
1587
|
+
browserMediaReporter?.close();
|
|
1588
|
+
connection.close(reason);
|
|
1589
|
+
store.dispatch({ type: "disconnected" });
|
|
1590
|
+
notify();
|
|
1591
|
+
},
|
|
1592
|
+
disconnect() {
|
|
1567
1593
|
unsubscribeConnection();
|
|
1568
1594
|
browserMediaReporter?.close();
|
|
1569
|
-
connection.
|
|
1595
|
+
connection.disconnect();
|
|
1570
1596
|
store.dispatch({ type: "disconnected" });
|
|
1571
1597
|
notify();
|
|
1572
1598
|
},
|
|
@@ -1730,29 +1756,34 @@ var createMicrophoneCapture = (options) => {
|
|
|
1730
1756
|
if (!AudioContextCtor) {
|
|
1731
1757
|
throw new Error("Browser microphone capture requires AudioContext support.");
|
|
1732
1758
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1759
|
+
try {
|
|
1760
|
+
mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
|
|
1761
|
+
audio: {
|
|
1762
|
+
autoGainControl: true,
|
|
1763
|
+
channelCount: options.channelCount ?? 1,
|
|
1764
|
+
echoCancellation: true,
|
|
1765
|
+
noiseSuppression: true
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
audioContext = new AudioContextCtor;
|
|
1769
|
+
if (audioContext.state === "suspended") {
|
|
1770
|
+
await audioContext.resume();
|
|
1739
1771
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
processorNode.connect(audioContext.destination);
|
|
1772
|
+
sourceNode = audioContext.createMediaStreamSource(mediaStream);
|
|
1773
|
+
processorNode = audioContext.createScriptProcessor(4096, 1, 1);
|
|
1774
|
+
processorNode.onaudioprocess = (event) => {
|
|
1775
|
+
const channel = event.inputBuffer.getChannelData(0);
|
|
1776
|
+
const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
|
|
1777
|
+
const pcm = floatTo16BitPCM(downsampled);
|
|
1778
|
+
options.onLevel?.(getPcmLevel(pcm));
|
|
1779
|
+
options.onAudio(pcm);
|
|
1780
|
+
};
|
|
1781
|
+
sourceNode.connect(processorNode);
|
|
1782
|
+
processorNode.connect(audioContext.destination);
|
|
1783
|
+
} catch (error) {
|
|
1784
|
+
stop();
|
|
1785
|
+
throw error;
|
|
1786
|
+
}
|
|
1756
1787
|
};
|
|
1757
1788
|
const stop = () => {
|
|
1758
1789
|
processorNode?.disconnect();
|
|
@@ -2195,6 +2226,7 @@ var createVoiceController = (path, options = {}) => {
|
|
|
2195
2226
|
};
|
|
2196
2227
|
notify();
|
|
2197
2228
|
} catch (error) {
|
|
2229
|
+
capture?.stop();
|
|
2198
2230
|
capture = null;
|
|
2199
2231
|
state = {
|
|
2200
2232
|
...state,
|
|
@@ -2205,13 +2237,19 @@ var createVoiceController = (path, options = {}) => {
|
|
|
2205
2237
|
throw error;
|
|
2206
2238
|
}
|
|
2207
2239
|
};
|
|
2208
|
-
const close = () => {
|
|
2240
|
+
const close = (reason) => {
|
|
2241
|
+
unsubscribeStream();
|
|
2242
|
+
stopRecording();
|
|
2243
|
+
stream.close(reason);
|
|
2244
|
+
};
|
|
2245
|
+
const disconnect = () => {
|
|
2209
2246
|
unsubscribeStream();
|
|
2210
2247
|
stopRecording();
|
|
2211
|
-
stream.
|
|
2248
|
+
stream.disconnect();
|
|
2212
2249
|
};
|
|
2213
2250
|
return {
|
|
2214
2251
|
close,
|
|
2252
|
+
disconnect,
|
|
2215
2253
|
startRecording,
|
|
2216
2254
|
stopRecording,
|
|
2217
2255
|
get assistantAudio() {
|
package/dist/core/types.d.ts
CHANGED
|
@@ -871,6 +871,19 @@ export type VoicePluginConfig<TContext = unknown, TSession extends VoiceSessionR
|
|
|
871
871
|
};
|
|
872
872
|
costTelemetry?: VoiceCostTelemetryConfig<TContext, TSession, TResult>;
|
|
873
873
|
path: string;
|
|
874
|
+
/**
|
|
875
|
+
* Authorize a WebSocket before the plugin supersedes an existing session or
|
|
876
|
+
* creates provider-backed resources. Return false to close it with 4401.
|
|
877
|
+
* Applications can validate a short-lived admission credential from `query`
|
|
878
|
+
* without coupling Voice to a particular authentication implementation.
|
|
879
|
+
*/
|
|
880
|
+
authorizeConnection?: (input: {
|
|
881
|
+
context: TContext;
|
|
882
|
+
path: string;
|
|
883
|
+
query: Record<string, unknown>;
|
|
884
|
+
scenarioId?: string;
|
|
885
|
+
sessionId: string;
|
|
886
|
+
}) => boolean | Promise<boolean>;
|
|
874
887
|
greeting?: string | ((input: {
|
|
875
888
|
session: TSession;
|
|
876
889
|
}) => string | Promise<string>);
|
|
@@ -1331,6 +1344,8 @@ export type VoiceConnectionOptions = {
|
|
|
1331
1344
|
* the caller losing the call. */
|
|
1332
1345
|
reconnectMaxDelayMs?: number;
|
|
1333
1346
|
pingInterval?: number;
|
|
1347
|
+
/** Additional query values sent on every initial or reconnecting socket. */
|
|
1348
|
+
query?: Record<string, string>;
|
|
1334
1349
|
sessionId?: string;
|
|
1335
1350
|
};
|
|
1336
1351
|
export type VoiceBrowserMediaReportPayload = {
|
|
@@ -1502,7 +1517,9 @@ export type VoiceStreamState<TResult = unknown> = {
|
|
|
1502
1517
|
export type VoiceStream<TResult = unknown> = {
|
|
1503
1518
|
call: VoiceCallLifecycleState | null;
|
|
1504
1519
|
callControl: (message: Omit<VoiceClientCallControlMessage, "type">) => void;
|
|
1505
|
-
close: () => void;
|
|
1520
|
+
close: (reason?: string) => void;
|
|
1521
|
+
/** Release the transport without terminally completing the server session. */
|
|
1522
|
+
disconnect: () => void;
|
|
1506
1523
|
start: (input?: {
|
|
1507
1524
|
scenarioId?: string;
|
|
1508
1525
|
sessionId?: string;
|
|
@@ -1597,7 +1614,9 @@ export type VoiceController<TResult = unknown> = {
|
|
|
1597
1614
|
bindHTMX: (options: VoiceHTMXBindingOptions) => () => void;
|
|
1598
1615
|
call: VoiceCallLifecycleState | null;
|
|
1599
1616
|
callControl: (message: Omit<VoiceClientCallControlMessage, "type">) => void;
|
|
1600
|
-
close: () => void;
|
|
1617
|
+
close: (reason?: string) => void;
|
|
1618
|
+
/** Release the transport while leaving the server session resumable. */
|
|
1619
|
+
disconnect: () => void;
|
|
1601
1620
|
endTurn: () => void;
|
|
1602
1621
|
start: (input?: {
|
|
1603
1622
|
scenarioId?: string;
|