@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.
@@ -525,29 +525,34 @@ var createMicrophoneCapture = (options) => {
525
525
  if (!AudioContextCtor) {
526
526
  throw new Error("Browser microphone capture requires AudioContext support.");
527
527
  }
528
- mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
529
- audio: {
530
- autoGainControl: true,
531
- channelCount: options.channelCount ?? 1,
532
- echoCancellation: true,
533
- noiseSuppression: true
528
+ try {
529
+ mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
530
+ audio: {
531
+ autoGainControl: true,
532
+ channelCount: options.channelCount ?? 1,
533
+ echoCancellation: true,
534
+ noiseSuppression: true
535
+ }
536
+ });
537
+ audioContext = new AudioContextCtor;
538
+ if (audioContext.state === "suspended") {
539
+ await audioContext.resume();
534
540
  }
535
- });
536
- audioContext = new AudioContextCtor;
537
- if (audioContext.state === "suspended") {
538
- await audioContext.resume();
539
- }
540
- sourceNode = audioContext.createMediaStreamSource(mediaStream);
541
- processorNode = audioContext.createScriptProcessor(4096, 1, 1);
542
- processorNode.onaudioprocess = (event) => {
543
- const channel = event.inputBuffer.getChannelData(0);
544
- const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
545
- const pcm = floatTo16BitPCM(downsampled);
546
- options.onLevel?.(getPcmLevel(pcm));
547
- options.onAudio(pcm);
548
- };
549
- sourceNode.connect(processorNode);
550
- processorNode.connect(audioContext.destination);
541
+ sourceNode = audioContext.createMediaStreamSource(mediaStream);
542
+ processorNode = audioContext.createScriptProcessor(4096, 1, 1);
543
+ processorNode.onaudioprocess = (event) => {
544
+ const channel = event.inputBuffer.getChannelData(0);
545
+ const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
546
+ const pcm = floatTo16BitPCM(downsampled);
547
+ options.onLevel?.(getPcmLevel(pcm));
548
+ options.onAudio(pcm);
549
+ };
550
+ sourceNode.connect(processorNode);
551
+ processorNode.connect(audioContext.destination);
552
+ } catch (error) {
553
+ stop();
554
+ throw error;
555
+ }
551
556
  };
552
557
  const stop = () => {
553
558
  processorNode?.disconnect();
@@ -767,6 +772,7 @@ var noopUnsubscribe = () => noop;
767
772
  var NOOP_CONNECTION = {
768
773
  callControl: noop,
769
774
  close: noop,
775
+ disconnect: noop,
770
776
  endTurn: noop,
771
777
  send: noop,
772
778
  sendAudio: noop,
@@ -778,7 +784,7 @@ var NOOP_CONNECTION = {
778
784
  start: () => {}
779
785
  };
780
786
  var createSessionId = () => crypto.randomUUID();
781
- var buildWsUrl = (path, sessionId, scenarioId) => {
787
+ var buildWsUrl = (path, sessionId, scenarioId, query) => {
782
788
  const { hostname, port, protocol } = window.location;
783
789
  const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
784
790
  const portSuffix = port ? `:${port}` : "";
@@ -787,6 +793,11 @@ var buildWsUrl = (path, sessionId, scenarioId) => {
787
793
  if (scenarioId) {
788
794
  url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
789
795
  }
796
+ for (const [key, value] of Object.entries(query)) {
797
+ if (key !== "sessionId" && key !== DEFAULT_SCENARIO_QUERY_PARAM && value) {
798
+ url.searchParams.set(key, value);
799
+ }
800
+ }
790
801
  return url.toString();
791
802
  };
792
803
  var isVoiceServerMessage = (value) => {
@@ -896,7 +907,7 @@ var createVoiceConnection = (path, options = {}) => {
896
907
  }, delayMs);
897
908
  };
898
909
  const connect = () => {
899
- const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
910
+ const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
900
911
  ws.binaryType = "arraybuffer";
901
912
  ws.onopen = () => {
902
913
  const wasReconnecting = state.reconnectAttempts > 0;
@@ -992,7 +1003,19 @@ var createVoiceConnection = (path, options = {}) => {
992
1003
  type: "call_control"
993
1004
  });
994
1005
  };
995
- const close = () => {
1006
+ const close = (reason = "client-close") => {
1007
+ clearTimers();
1008
+ if (state.ws) {
1009
+ if (state.ws.readyState === WS_OPEN) {
1010
+ state.ws.send(JSON.stringify({ reason, type: "close" }));
1011
+ }
1012
+ state.ws.close(WS_NORMAL_CLOSURE);
1013
+ state.ws = null;
1014
+ }
1015
+ state.isConnected = false;
1016
+ listeners.clear();
1017
+ };
1018
+ const disconnect = () => {
996
1019
  clearTimers();
997
1020
  if (state.ws) {
998
1021
  state.ws.close(WS_NORMAL_CLOSURE);
@@ -1016,6 +1039,7 @@ var createVoiceConnection = (path, options = {}) => {
1016
1039
  return {
1017
1040
  callControl,
1018
1041
  close,
1042
+ disconnect,
1019
1043
  endTurn,
1020
1044
  send,
1021
1045
  sendAudio,
@@ -1296,10 +1320,17 @@ var createVoiceStream = (path, options = {}) => {
1296
1320
  callControl(message) {
1297
1321
  connection.callControl(message);
1298
1322
  },
1299
- close() {
1323
+ close(reason) {
1300
1324
  unsubscribeConnection();
1301
1325
  browserMediaReporter?.close();
1302
- connection.close();
1326
+ connection.close(reason);
1327
+ store.dispatch({ type: "disconnected" });
1328
+ notify();
1329
+ },
1330
+ disconnect() {
1331
+ unsubscribeConnection();
1332
+ browserMediaReporter?.close();
1333
+ connection.disconnect();
1303
1334
  store.dispatch({ type: "disconnected" });
1304
1335
  notify();
1305
1336
  },
@@ -1781,6 +1812,7 @@ var createVoiceController = (path, options = {}) => {
1781
1812
  };
1782
1813
  notify();
1783
1814
  } catch (error) {
1815
+ capture?.stop();
1784
1816
  capture = null;
1785
1817
  state = {
1786
1818
  ...state,
@@ -1791,13 +1823,19 @@ var createVoiceController = (path, options = {}) => {
1791
1823
  throw error;
1792
1824
  }
1793
1825
  };
1794
- const close = () => {
1826
+ const close = (reason) => {
1827
+ unsubscribeStream();
1828
+ stopRecording();
1829
+ stream.close(reason);
1830
+ };
1831
+ const disconnect = () => {
1795
1832
  unsubscribeStream();
1796
1833
  stopRecording();
1797
- stream.close();
1834
+ stream.disconnect();
1798
1835
  };
1799
1836
  return {
1800
1837
  close,
1838
+ disconnect,
1801
1839
  startRecording,
1802
1840
  stopRecording,
1803
1841
  get assistantAudio() {
@@ -2746,29 +2746,34 @@ var createMicrophoneCapture = (options) => {
2746
2746
  if (!AudioContextCtor) {
2747
2747
  throw new Error("Browser microphone capture requires AudioContext support.");
2748
2748
  }
2749
- mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
2750
- audio: {
2751
- autoGainControl: true,
2752
- channelCount: options.channelCount ?? 1,
2753
- echoCancellation: true,
2754
- noiseSuppression: true
2755
- }
2756
- });
2757
- audioContext = new AudioContextCtor;
2758
- if (audioContext.state === "suspended") {
2759
- await audioContext.resume();
2760
- }
2761
- sourceNode = audioContext.createMediaStreamSource(mediaStream);
2762
- processorNode = audioContext.createScriptProcessor(4096, 1, 1);
2763
- processorNode.onaudioprocess = (event) => {
2764
- const channel = event.inputBuffer.getChannelData(0);
2765
- const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
2766
- const pcm = floatTo16BitPCM(downsampled);
2767
- options.onLevel?.(getPcmLevel(pcm));
2768
- options.onAudio(pcm);
2769
- };
2770
- sourceNode.connect(processorNode);
2771
- processorNode.connect(audioContext.destination);
2749
+ try {
2750
+ mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
2751
+ audio: {
2752
+ autoGainControl: true,
2753
+ channelCount: options.channelCount ?? 1,
2754
+ echoCancellation: true,
2755
+ noiseSuppression: true
2756
+ }
2757
+ });
2758
+ audioContext = new AudioContextCtor;
2759
+ if (audioContext.state === "suspended") {
2760
+ await audioContext.resume();
2761
+ }
2762
+ sourceNode = audioContext.createMediaStreamSource(mediaStream);
2763
+ processorNode = audioContext.createScriptProcessor(4096, 1, 1);
2764
+ processorNode.onaudioprocess = (event) => {
2765
+ const channel = event.inputBuffer.getChannelData(0);
2766
+ const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
2767
+ const pcm = floatTo16BitPCM(downsampled);
2768
+ options.onLevel?.(getPcmLevel(pcm));
2769
+ options.onAudio(pcm);
2770
+ };
2771
+ sourceNode.connect(processorNode);
2772
+ processorNode.connect(audioContext.destination);
2773
+ } catch (error) {
2774
+ stop();
2775
+ throw error;
2776
+ }
2772
2777
  };
2773
2778
  const stop = () => {
2774
2779
  processorNode?.disconnect();
@@ -2988,6 +2993,7 @@ var noopUnsubscribe = () => noop;
2988
2993
  var NOOP_CONNECTION = {
2989
2994
  callControl: noop,
2990
2995
  close: noop,
2996
+ disconnect: noop,
2991
2997
  endTurn: noop,
2992
2998
  send: noop,
2993
2999
  sendAudio: noop,
@@ -2999,7 +3005,7 @@ var NOOP_CONNECTION = {
2999
3005
  start: () => {}
3000
3006
  };
3001
3007
  var createSessionId = () => crypto.randomUUID();
3002
- var buildWsUrl = (path, sessionId, scenarioId) => {
3008
+ var buildWsUrl = (path, sessionId, scenarioId, query) => {
3003
3009
  const { hostname, port, protocol } = window.location;
3004
3010
  const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
3005
3011
  const portSuffix = port ? `:${port}` : "";
@@ -3008,6 +3014,11 @@ var buildWsUrl = (path, sessionId, scenarioId) => {
3008
3014
  if (scenarioId) {
3009
3015
  url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
3010
3016
  }
3017
+ for (const [key, value] of Object.entries(query)) {
3018
+ if (key !== "sessionId" && key !== DEFAULT_SCENARIO_QUERY_PARAM && value) {
3019
+ url.searchParams.set(key, value);
3020
+ }
3021
+ }
3011
3022
  return url.toString();
3012
3023
  };
3013
3024
  var isVoiceServerMessage = (value) => {
@@ -3117,7 +3128,7 @@ var createVoiceConnection = (path, options = {}) => {
3117
3128
  }, delayMs);
3118
3129
  };
3119
3130
  const connect = () => {
3120
- const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
3131
+ const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
3121
3132
  ws.binaryType = "arraybuffer";
3122
3133
  ws.onopen = () => {
3123
3134
  const wasReconnecting = state.reconnectAttempts > 0;
@@ -3213,7 +3224,19 @@ var createVoiceConnection = (path, options = {}) => {
3213
3224
  type: "call_control"
3214
3225
  });
3215
3226
  };
3216
- const close = () => {
3227
+ const close = (reason = "client-close") => {
3228
+ clearTimers();
3229
+ if (state.ws) {
3230
+ if (state.ws.readyState === WS_OPEN) {
3231
+ state.ws.send(JSON.stringify({ reason, type: "close" }));
3232
+ }
3233
+ state.ws.close(WS_NORMAL_CLOSURE);
3234
+ state.ws = null;
3235
+ }
3236
+ state.isConnected = false;
3237
+ listeners.clear();
3238
+ };
3239
+ const disconnect = () => {
3217
3240
  clearTimers();
3218
3241
  if (state.ws) {
3219
3242
  state.ws.close(WS_NORMAL_CLOSURE);
@@ -3237,6 +3260,7 @@ var createVoiceConnection = (path, options = {}) => {
3237
3260
  return {
3238
3261
  callControl,
3239
3262
  close,
3263
+ disconnect,
3240
3264
  endTurn,
3241
3265
  send,
3242
3266
  sendAudio,
@@ -3517,10 +3541,17 @@ var createVoiceStream = (path, options = {}) => {
3517
3541
  callControl(message) {
3518
3542
  connection.callControl(message);
3519
3543
  },
3520
- close() {
3544
+ close(reason) {
3545
+ unsubscribeConnection();
3546
+ browserMediaReporter?.close();
3547
+ connection.close(reason);
3548
+ store.dispatch({ type: "disconnected" });
3549
+ notify();
3550
+ },
3551
+ disconnect() {
3521
3552
  unsubscribeConnection();
3522
3553
  browserMediaReporter?.close();
3523
- connection.close();
3554
+ connection.disconnect();
3524
3555
  store.dispatch({ type: "disconnected" });
3525
3556
  notify();
3526
3557
  },
@@ -4002,6 +4033,7 @@ var createVoiceController = (path, options = {}) => {
4002
4033
  };
4003
4034
  notify();
4004
4035
  } catch (error) {
4036
+ capture?.stop();
4005
4037
  capture = null;
4006
4038
  state = {
4007
4039
  ...state,
@@ -4012,13 +4044,19 @@ var createVoiceController = (path, options = {}) => {
4012
4044
  throw error;
4013
4045
  }
4014
4046
  };
4015
- const close = () => {
4047
+ const close = (reason) => {
4016
4048
  unsubscribeStream();
4017
4049
  stopRecording();
4018
- stream.close();
4050
+ stream.close(reason);
4051
+ };
4052
+ const disconnect = () => {
4053
+ unsubscribeStream();
4054
+ stopRecording();
4055
+ stream.disconnect();
4019
4056
  };
4020
4057
  return {
4021
4058
  close,
4059
+ disconnect,
4022
4060
  startRecording,
4023
4061
  stopRecording,
4024
4062
  get assistantAudio() {
@@ -7702,7 +7740,7 @@ var createVoiceSession = (options) => {
7702
7740
  clearSilenceTimer();
7703
7741
  let didFail = false;
7704
7742
  const session = await writeSession((currentSession) => {
7705
- if (currentSession.status === "failed") {
7743
+ if (currentSession.status === "completed" || currentSession.status === "failed") {
7706
7744
  return;
7707
7745
  }
7708
7746
  didFail = true;
package/dist/vue/index.js CHANGED
@@ -10840,6 +10840,7 @@ var noopUnsubscribe = () => noop;
10840
10840
  var NOOP_CONNECTION = {
10841
10841
  callControl: noop,
10842
10842
  close: noop,
10843
+ disconnect: noop,
10843
10844
  endTurn: noop,
10844
10845
  send: noop,
10845
10846
  sendAudio: noop,
@@ -10851,7 +10852,7 @@ var NOOP_CONNECTION = {
10851
10852
  start: () => {}
10852
10853
  };
10853
10854
  var createSessionId = () => crypto.randomUUID();
10854
- var buildWsUrl = (path, sessionId, scenarioId) => {
10855
+ var buildWsUrl = (path, sessionId, scenarioId, query) => {
10855
10856
  const { hostname, port, protocol } = window.location;
10856
10857
  const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
10857
10858
  const portSuffix = port ? `:${port}` : "";
@@ -10860,6 +10861,11 @@ var buildWsUrl = (path, sessionId, scenarioId) => {
10860
10861
  if (scenarioId) {
10861
10862
  url.searchParams.set(DEFAULT_SCENARIO_QUERY_PARAM, scenarioId);
10862
10863
  }
10864
+ for (const [key, value] of Object.entries(query)) {
10865
+ if (key !== "sessionId" && key !== DEFAULT_SCENARIO_QUERY_PARAM && value) {
10866
+ url.searchParams.set(key, value);
10867
+ }
10868
+ }
10863
10869
  return url.toString();
10864
10870
  };
10865
10871
  var isVoiceServerMessage = (value) => {
@@ -10969,7 +10975,7 @@ var createVoiceConnection = (path, options = {}) => {
10969
10975
  }, delayMs);
10970
10976
  };
10971
10977
  const connect = () => {
10972
- const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
10978
+ const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
10973
10979
  ws.binaryType = "arraybuffer";
10974
10980
  ws.onopen = () => {
10975
10981
  const wasReconnecting = state.reconnectAttempts > 0;
@@ -11065,7 +11071,19 @@ var createVoiceConnection = (path, options = {}) => {
11065
11071
  type: "call_control"
11066
11072
  });
11067
11073
  };
11068
- const close = () => {
11074
+ const close = (reason = "client-close") => {
11075
+ clearTimers();
11076
+ if (state.ws) {
11077
+ if (state.ws.readyState === WS_OPEN) {
11078
+ state.ws.send(JSON.stringify({ reason, type: "close" }));
11079
+ }
11080
+ state.ws.close(WS_NORMAL_CLOSURE);
11081
+ state.ws = null;
11082
+ }
11083
+ state.isConnected = false;
11084
+ listeners.clear();
11085
+ };
11086
+ const disconnect = () => {
11069
11087
  clearTimers();
11070
11088
  if (state.ws) {
11071
11089
  state.ws.close(WS_NORMAL_CLOSURE);
@@ -11089,6 +11107,7 @@ var createVoiceConnection = (path, options = {}) => {
11089
11107
  return {
11090
11108
  callControl,
11091
11109
  close,
11110
+ disconnect,
11092
11111
  endTurn,
11093
11112
  send,
11094
11113
  sendAudio,
@@ -11369,10 +11388,17 @@ var createVoiceStream = (path, options = {}) => {
11369
11388
  callControl(message) {
11370
11389
  connection.callControl(message);
11371
11390
  },
11372
- close() {
11391
+ close(reason) {
11392
+ unsubscribeConnection();
11393
+ browserMediaReporter?.close();
11394
+ connection.close(reason);
11395
+ store.dispatch({ type: "disconnected" });
11396
+ notify();
11397
+ },
11398
+ disconnect() {
11373
11399
  unsubscribeConnection();
11374
11400
  browserMediaReporter?.close();
11375
- connection.close();
11401
+ connection.disconnect();
11376
11402
  store.dispatch({ type: "disconnected" });
11377
11403
  notify();
11378
11404
  },
@@ -11596,29 +11622,34 @@ var createMicrophoneCapture = (options) => {
11596
11622
  if (!AudioContextCtor) {
11597
11623
  throw new Error("Browser microphone capture requires AudioContext support.");
11598
11624
  }
11599
- mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
11600
- audio: {
11601
- autoGainControl: true,
11602
- channelCount: options.channelCount ?? 1,
11603
- echoCancellation: true,
11604
- noiseSuppression: true
11625
+ try {
11626
+ mediaStream = options.stream ?? await navigator.mediaDevices.getUserMedia({
11627
+ audio: {
11628
+ autoGainControl: true,
11629
+ channelCount: options.channelCount ?? 1,
11630
+ echoCancellation: true,
11631
+ noiseSuppression: true
11632
+ }
11633
+ });
11634
+ audioContext = new AudioContextCtor;
11635
+ if (audioContext.state === "suspended") {
11636
+ await audioContext.resume();
11605
11637
  }
11606
- });
11607
- audioContext = new AudioContextCtor;
11608
- if (audioContext.state === "suspended") {
11609
- await audioContext.resume();
11610
- }
11611
- sourceNode = audioContext.createMediaStreamSource(mediaStream);
11612
- processorNode = audioContext.createScriptProcessor(4096, 1, 1);
11613
- processorNode.onaudioprocess = (event) => {
11614
- const channel = event.inputBuffer.getChannelData(0);
11615
- const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
11616
- const pcm = floatTo16BitPCM(downsampled);
11617
- options.onLevel?.(getPcmLevel(pcm));
11618
- options.onAudio(pcm);
11619
- };
11620
- sourceNode.connect(processorNode);
11621
- processorNode.connect(audioContext.destination);
11638
+ sourceNode = audioContext.createMediaStreamSource(mediaStream);
11639
+ processorNode = audioContext.createScriptProcessor(4096, 1, 1);
11640
+ processorNode.onaudioprocess = (event) => {
11641
+ const channel = event.inputBuffer.getChannelData(0);
11642
+ const downsampled = downsampleBuffer(channel, audioContext?.sampleRate ?? 48000, options.sampleRateHz ?? 16000);
11643
+ const pcm = floatTo16BitPCM(downsampled);
11644
+ options.onLevel?.(getPcmLevel(pcm));
11645
+ options.onAudio(pcm);
11646
+ };
11647
+ sourceNode.connect(processorNode);
11648
+ processorNode.connect(audioContext.destination);
11649
+ } catch (error) {
11650
+ stop();
11651
+ throw error;
11652
+ }
11622
11653
  };
11623
11654
  const stop = () => {
11624
11655
  processorNode?.disconnect();
@@ -12061,6 +12092,7 @@ var createVoiceController = (path, options = {}) => {
12061
12092
  };
12062
12093
  notify();
12063
12094
  } catch (error) {
12095
+ capture?.stop();
12064
12096
  capture = null;
12065
12097
  state = {
12066
12098
  ...state,
@@ -12071,13 +12103,19 @@ var createVoiceController = (path, options = {}) => {
12071
12103
  throw error;
12072
12104
  }
12073
12105
  };
12074
- const close = () => {
12106
+ const close = (reason) => {
12075
12107
  unsubscribeStream();
12076
12108
  stopRecording();
12077
- stream.close();
12109
+ stream.close(reason);
12110
+ };
12111
+ const disconnect = () => {
12112
+ unsubscribeStream();
12113
+ stopRecording();
12114
+ stream.disconnect();
12078
12115
  };
12079
12116
  return {
12080
12117
  close,
12118
+ disconnect,
12081
12119
  startRecording,
12082
12120
  stopRecording,
12083
12121
  get assistantAudio() {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.647",
3
+ "version": "0.0.22-beta.649",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "https://github.com/absolutejs/voice.git"
7
+ "url": "git+https://github.com/absolutejs/voice.git"
8
8
  },
9
9
  "homepage": "https://github.com/absolutejs/voice",
10
10
  "bugs": {