@vapi-ai/web 1.0.138 → 1.0.140

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/vapi.d.ts CHANGED
@@ -1,12 +1,15 @@
1
- /// <reference types="node" />
2
1
  import { CreateAssistantDTO } from "./api";
3
- import EventEmitter from "events";
4
- export default class Vapi extends EventEmitter {
2
+ import async from "async";
3
+ export default class Vapi {
4
+ private audioContext;
5
+ private source;
5
6
  private started;
6
7
  private ws;
7
8
  private mediaRecorder;
8
- private player;
9
- constructor(apiToken: string, apiBaseUrl?: string);
9
+ private nextClipTime;
10
+ private decodeQueue;
11
+ constructor(apiToken: string, apiUrl?: string);
12
+ createQueue(): async.QueueObject<ArrayBuffer>;
10
13
  start(assistant: CreateAssistantDTO | string): void;
11
14
  private startRecording;
12
15
  private onMessage;
package/dist/vapi.js CHANGED
@@ -3,32 +3,49 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const player_1 = require("./player");
7
- const events_1 = __importDefault(require("events"));
6
+ const standardized_audio_context_1 = require("standardized-audio-context");
7
+ const async_1 = __importDefault(require("async"));
8
8
  const client_1 = require("./client");
9
9
  const base64_arraybuffer_1 = require("base64-arraybuffer");
10
- class Vapi extends events_1.default {
11
- constructor(apiToken, apiBaseUrl) {
12
- super();
10
+ class Vapi {
11
+ constructor(apiToken, apiUrl) {
12
+ this.audioContext = new standardized_audio_context_1.AudioContext();
13
+ this.source = null;
13
14
  this.started = false;
14
15
  this.ws = null;
15
16
  this.mediaRecorder = null;
16
- client_1.client.baseUrl = apiBaseUrl !== null && apiBaseUrl !== void 0 ? apiBaseUrl : "https://api.vapi.ai";
17
+ this.nextClipTime = 0;
17
18
  client_1.client.setSecurityData(apiToken);
18
- this.player = new player_1.ContinuousPlayer();
19
- this.player.on("speech-start", () => this.emit("speech-start"));
20
- this.player.on("speech-end", () => this.emit("speech-end"));
19
+ client_1.client.baseUrl = apiUrl !== null && apiUrl !== void 0 ? apiUrl : "https://api.vapi.ai";
20
+ this.decodeQueue = this.createQueue();
21
+ }
22
+ createQueue() {
23
+ return async_1.default.queue((task, callback) => {
24
+ this.source = this.audioContext.createBufferSource();
25
+ this.audioContext.decodeAudioData(task, (buffer) => {
26
+ if (!this.source)
27
+ return;
28
+ this.source.buffer = buffer;
29
+ this.source.connect(this.audioContext.destination);
30
+ // If the next clip time is in the past, set it to the current time
31
+ this.nextClipTime = Math.max(this.nextClipTime, this.audioContext.currentTime);
32
+ // Start the source at the next clip time
33
+ this.source.start(this.nextClipTime);
34
+ // Schedule the next clip time
35
+ this.nextClipTime += buffer.duration;
36
+ callback();
37
+ });
38
+ }, 1);
21
39
  }
22
40
  start(assistant) {
23
41
  if (this.started) {
24
42
  return;
25
43
  }
26
44
  this.started = true;
27
- this.player.start();
28
45
  client_1.client.call
29
46
  .callControllerCreateWebCall({
30
- assistant: typeof assistant === "string" ? undefined : assistant,
31
47
  assistantId: typeof assistant === "string" ? assistant : undefined,
48
+ assistant: typeof assistant === "object" ? assistant : undefined,
32
49
  })
33
50
  .then(({ data }) => {
34
51
  const { callId, url } = data;
@@ -36,6 +53,7 @@ class Vapi extends events_1.default {
36
53
  this.ws = socket;
37
54
  socket.onopen = () => {
38
55
  socket.send(JSON.stringify({ event: "start", callId }));
56
+ this.startRecording();
39
57
  };
40
58
  socket.onmessage = (event) => {
41
59
  if (!socket)
@@ -55,33 +73,40 @@ class Vapi extends events_1.default {
55
73
  this.mediaRecorder = new MediaRecorder(stream, {
56
74
  mimeType: "audio/webm;codecs=opus",
57
75
  });
76
+ this.mediaRecorder.start(100);
58
77
  this.mediaRecorder.ondataavailable = (event) => {
59
78
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
60
79
  this.ws.send(event.data);
61
80
  }
62
81
  };
63
- this.mediaRecorder.start(100);
64
82
  });
65
83
  }
66
84
  onMessage(ws, event) {
67
85
  const data = JSON.parse(event.data);
68
- switch (data.event) {
69
- case "connected":
70
- this.startRecording();
71
- this.emit("started");
72
- break;
73
- case "clear":
74
- this.clear();
75
- break;
76
- case "media":
77
- const audioData = (0, base64_arraybuffer_1.decode)(data.media.payload);
78
- this.player.playChunk(audioData);
86
+ if (data.event === "media") {
87
+ const audioData = (0, base64_arraybuffer_1.decode)(data.media.payload);
88
+ this.decodeQueue.push(audioData);
89
+ }
90
+ if (data.event === "clear") {
91
+ this.clear();
79
92
  }
80
93
  }
81
94
  clear() {
82
- this.player.clear();
95
+ this.decodeQueue.kill();
96
+ this.decodeQueue = this.createQueue();
97
+ // Stop the source if it exists
98
+ if (this.source) {
99
+ this.source.stop();
100
+ this.source.disconnect();
101
+ this.source = null;
102
+ }
103
+ // Reset the next clip time
104
+ this.nextClipTime = 0;
83
105
  }
84
106
  stop() {
107
+ if (this.source) {
108
+ this.source.stop();
109
+ }
85
110
  this.started = false;
86
111
  if (this.ws) {
87
112
  this.ws.close();
@@ -91,7 +116,6 @@ class Vapi extends events_1.default {
91
116
  this.mediaRecorder.stop();
92
117
  }
93
118
  this.mediaRecorder = null;
94
- this.emit("stopped");
95
119
  }
96
120
  }
97
121
  exports.default = Vapi;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vapi-ai/web",
3
- "version": "1.0.138",
3
+ "version": "1.0.140",
4
4
  "description": "",
5
5
  "main": "dist/vapi.js",
6
6
  "types": "dist/vapi.d.ts",
@@ -24,7 +24,8 @@
24
24
  "dependencies": {
25
25
  "async": "^3.2.4",
26
26
  "base64-arraybuffer": "^1.0.2",
27
- "events": "^3.3.0"
27
+ "events": "^3.3.0",
28
+ "standardized-audio-context": "^25.3.57"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/async": "^3.2.21",
package/vapi.ts CHANGED
@@ -1,23 +1,57 @@
1
- import { ContinuousPlayer } from "./player";
2
- import { CreateAssistantDTO } from "./api";
3
- import EventEmitter from "events";
1
+ import { Assistant, CreateAssistantDTO } from "./api";
2
+ import {
3
+ AudioContext,
4
+ IAudioBufferSourceNode,
5
+ IAudioContext,
6
+ } from "standardized-audio-context";
7
+ import async, { QueueObject } from "async";
8
+
4
9
  import { client } from "./client";
5
10
  import { decode } from "base64-arraybuffer";
6
11
 
7
- export default class Vapi extends EventEmitter {
12
+ export default class Vapi {
13
+ private audioContext: AudioContext = new AudioContext();
14
+ private source: IAudioBufferSourceNode<IAudioContext> | null = null;
8
15
  private started: boolean = false;
9
16
  private ws: WebSocket | null = null;
10
17
  private mediaRecorder: MediaRecorder | null = null;
11
- private player: ContinuousPlayer;
12
18
 
13
- constructor(apiToken: string, apiBaseUrl?: string) {
14
- super();
15
- client.baseUrl = apiBaseUrl ?? "https://api.vapi.ai";
19
+ private nextClipTime: number = 0;
20
+
21
+ private decodeQueue: QueueObject<ArrayBuffer>;
22
+
23
+ constructor(apiToken: string, apiUrl?: string) {
16
24
  client.setSecurityData(apiToken);
17
- this.player = new ContinuousPlayer();
25
+ client.baseUrl = apiUrl ?? "https://api.vapi.ai";
26
+
27
+ this.decodeQueue = this.createQueue();
28
+ }
29
+
30
+ createQueue() {
31
+ return async.queue<ArrayBuffer>((task, callback) => {
32
+ this.source = this.audioContext.createBufferSource();
33
+
34
+ this.audioContext.decodeAudioData(task, (buffer) => {
35
+ if (!this.source) return;
36
+
37
+ this.source.buffer = buffer;
38
+ this.source.connect(this.audioContext.destination);
39
+
40
+ // If the next clip time is in the past, set it to the current time
41
+ this.nextClipTime = Math.max(
42
+ this.nextClipTime,
43
+ this.audioContext.currentTime
44
+ );
45
+
46
+ // Start the source at the next clip time
47
+ this.source.start(this.nextClipTime);
18
48
 
19
- this.player.on("speech-start", () => this.emit("speech-start"));
20
- this.player.on("speech-end", () => this.emit("speech-end"));
49
+ // Schedule the next clip time
50
+ this.nextClipTime += buffer.duration;
51
+
52
+ callback();
53
+ });
54
+ }, 1);
21
55
  }
22
56
 
23
57
  start(assistant: CreateAssistantDTO | string): void {
@@ -26,12 +60,11 @@ export default class Vapi extends EventEmitter {
26
60
  }
27
61
 
28
62
  this.started = true;
29
- this.player.start();
30
63
 
31
64
  client.call
32
65
  .callControllerCreateWebCall({
33
- assistant: typeof assistant === "string" ? undefined : assistant,
34
66
  assistantId: typeof assistant === "string" ? assistant : undefined,
67
+ assistant: typeof assistant === "object" ? assistant : undefined,
35
68
  })
36
69
  .then(({ data }) => {
37
70
  const { callId, url } = data;
@@ -41,6 +74,7 @@ export default class Vapi extends EventEmitter {
41
74
 
42
75
  socket.onopen = () => {
43
76
  socket.send(JSON.stringify({ event: "start", callId }));
77
+ this.startRecording();
44
78
  };
45
79
  socket.onmessage = (event) => {
46
80
  if (!socket) return;
@@ -61,37 +95,45 @@ export default class Vapi extends EventEmitter {
61
95
  mimeType: "audio/webm;codecs=opus",
62
96
  });
63
97
 
98
+ this.mediaRecorder.start(100);
64
99
  this.mediaRecorder.ondataavailable = (event: BlobEvent) => {
65
100
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
66
101
  this.ws.send(event.data);
67
102
  }
68
103
  };
69
- this.mediaRecorder.start(100);
70
104
  });
71
105
  }
72
106
 
73
107
  private onMessage(ws: WebSocket, event: MessageEvent): void {
74
108
  const data = JSON.parse(event.data);
75
-
76
- switch (data.event) {
77
- case "connected":
78
- this.startRecording();
79
- this.emit("started");
80
- break;
81
- case "clear":
82
- this.clear();
83
- break;
84
- case "media":
85
- const audioData = decode(data.media.payload);
86
- this.player.playChunk(audioData);
109
+ if (data.event === "media") {
110
+ const audioData = decode(data.media.payload);
111
+ this.decodeQueue.push(audioData);
112
+ }
113
+ if (data.event === "clear") {
114
+ this.clear();
87
115
  }
88
116
  }
89
117
 
90
118
  clear(): void {
91
- this.player.clear();
119
+ this.decodeQueue.kill();
120
+ this.decodeQueue = this.createQueue();
121
+
122
+ // Stop the source if it exists
123
+ if (this.source) {
124
+ this.source.stop();
125
+ this.source.disconnect();
126
+ this.source = null;
127
+ }
128
+
129
+ // Reset the next clip time
130
+ this.nextClipTime = 0;
92
131
  }
93
132
 
94
133
  stop(): void {
134
+ if (this.source) {
135
+ this.source.stop();
136
+ }
95
137
  this.started = false;
96
138
  if (this.ws) {
97
139
  this.ws.close();
@@ -101,6 +143,5 @@ export default class Vapi extends EventEmitter {
101
143
  this.mediaRecorder.stop();
102
144
  }
103
145
  this.mediaRecorder = null;
104
- this.emit("stopped");
105
146
  }
106
147
  }