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