@vapi-ai/web 1.0.140 → 1.0.141
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/player.d.ts +3 -3
- package/dist/player.js +23 -22
- package/dist/vapi.d.ts +5 -8
- package/dist/vapi.js +25 -49
- package/package.json +1 -1
- package/player.ts +32 -13
- package/vapi.ts +28 -69
package/dist/player.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import EventEmitter from "events";
|
|
3
3
|
export declare class ContinuousPlayer extends EventEmitter {
|
|
4
|
-
private
|
|
5
|
-
private
|
|
4
|
+
private atomic;
|
|
5
|
+
private audio;
|
|
6
6
|
constructor();
|
|
7
7
|
start(): void;
|
|
8
|
-
playChunk(audioData: ArrayBuffer):
|
|
8
|
+
playChunk(audioData: ArrayBuffer): void;
|
|
9
9
|
clear(): void;
|
|
10
10
|
}
|
package/dist/player.js
CHANGED
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
14
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
6
|
exports.ContinuousPlayer = void 0;
|
|
7
|
+
const AtomicMediaSource_1 = __importDefault(require("./AtomicMediaSource"));
|
|
16
8
|
const events_1 = __importDefault(require("events"));
|
|
17
9
|
class ContinuousPlayer extends events_1.default {
|
|
18
10
|
constructor() {
|
|
19
11
|
super();
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
12
|
+
this.audio = document.createElement("audio");
|
|
13
|
+
this.audio.addEventListener("playing", () => this.emit("speech-start"));
|
|
14
|
+
this.audio.addEventListener("play", () => this.emit("speech-start"));
|
|
15
|
+
this.audio.addEventListener("canplay", () => {
|
|
16
|
+
this.audio.play();
|
|
17
|
+
});
|
|
18
|
+
this.audio.addEventListener("waiting", () => this.emit("speech-end"));
|
|
19
|
+
this.atomic = new AtomicMediaSource_1.default();
|
|
20
|
+
this.audio.src = URL.createObjectURL(this.atomic.mediaSource);
|
|
21
|
+
document.body.appendChild(this.audio);
|
|
22
|
+
}
|
|
23
|
+
start() {
|
|
24
|
+
this.audio.play();
|
|
22
25
|
}
|
|
23
|
-
start() { }
|
|
24
26
|
playChunk(audioData) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
var _a;
|
|
28
|
+
(_a = this.atomic.sourceBuffer) === null || _a === void 0 ? void 0 : _a.addEventListener("updateend", () => {
|
|
29
|
+
this.audio.play().catch((error) => console.error("Play error:", error));
|
|
30
|
+
}, { once: true });
|
|
31
|
+
this.atomic.appendBuffer(audioData);
|
|
32
|
+
}
|
|
33
|
+
clear() {
|
|
34
|
+
this.audio.pause();
|
|
35
|
+
this.atomic.clearBuffer();
|
|
34
36
|
}
|
|
35
|
-
clear() { }
|
|
36
37
|
}
|
|
37
38
|
exports.ContinuousPlayer = ContinuousPlayer;
|
package/dist/vapi.d.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import { CreateAssistantDTO } from "./api";
|
|
2
|
-
import
|
|
3
|
-
export default class Vapi {
|
|
4
|
-
private audioContext;
|
|
5
|
-
private source;
|
|
3
|
+
import EventEmitter from "events";
|
|
4
|
+
export default class Vapi extends EventEmitter {
|
|
6
5
|
private started;
|
|
7
6
|
private ws;
|
|
8
7
|
private mediaRecorder;
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
constructor(apiToken: string, apiUrl?: string);
|
|
12
|
-
createQueue(): async.QueueObject<ArrayBuffer>;
|
|
8
|
+
private player;
|
|
9
|
+
constructor(apiToken: string, apiBaseUrl?: string);
|
|
13
10
|
start(assistant: CreateAssistantDTO | string): void;
|
|
14
11
|
private startRecording;
|
|
15
12
|
private onMessage;
|
package/dist/vapi.js
CHANGED
|
@@ -3,49 +3,32 @@ 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 player_1 = require("./player");
|
|
7
|
+
const events_1 = __importDefault(require("events"));
|
|
8
8
|
const client_1 = require("./client");
|
|
9
9
|
const base64_arraybuffer_1 = require("base64-arraybuffer");
|
|
10
|
-
class Vapi {
|
|
11
|
-
constructor(apiToken,
|
|
12
|
-
|
|
13
|
-
this.source = null;
|
|
10
|
+
class Vapi extends events_1.default {
|
|
11
|
+
constructor(apiToken, apiBaseUrl) {
|
|
12
|
+
super();
|
|
14
13
|
this.started = false;
|
|
15
14
|
this.ws = null;
|
|
16
15
|
this.mediaRecorder = null;
|
|
17
|
-
|
|
16
|
+
client_1.client.baseUrl = apiBaseUrl !== null && apiBaseUrl !== void 0 ? apiBaseUrl : "https://api.vapi.ai";
|
|
18
17
|
client_1.client.setSecurityData(apiToken);
|
|
19
|
-
|
|
20
|
-
this.
|
|
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);
|
|
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"));
|
|
39
21
|
}
|
|
40
22
|
start(assistant) {
|
|
41
23
|
if (this.started) {
|
|
42
24
|
return;
|
|
43
25
|
}
|
|
44
26
|
this.started = true;
|
|
27
|
+
this.player.start();
|
|
45
28
|
client_1.client.call
|
|
46
29
|
.callControllerCreateWebCall({
|
|
30
|
+
assistant: typeof assistant === "string" ? undefined : assistant,
|
|
47
31
|
assistantId: typeof assistant === "string" ? assistant : undefined,
|
|
48
|
-
assistant: typeof assistant === "object" ? assistant : undefined,
|
|
49
32
|
})
|
|
50
33
|
.then(({ data }) => {
|
|
51
34
|
const { callId, url } = data;
|
|
@@ -53,7 +36,6 @@ class Vapi {
|
|
|
53
36
|
this.ws = socket;
|
|
54
37
|
socket.onopen = () => {
|
|
55
38
|
socket.send(JSON.stringify({ event: "start", callId }));
|
|
56
|
-
this.startRecording();
|
|
57
39
|
};
|
|
58
40
|
socket.onmessage = (event) => {
|
|
59
41
|
if (!socket)
|
|
@@ -73,40 +55,33 @@ class Vapi {
|
|
|
73
55
|
this.mediaRecorder = new MediaRecorder(stream, {
|
|
74
56
|
mimeType: "audio/webm;codecs=opus",
|
|
75
57
|
});
|
|
76
|
-
this.mediaRecorder.start(100);
|
|
77
58
|
this.mediaRecorder.ondataavailable = (event) => {
|
|
78
59
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
79
60
|
this.ws.send(event.data);
|
|
80
61
|
}
|
|
81
62
|
};
|
|
63
|
+
this.mediaRecorder.start(100);
|
|
82
64
|
});
|
|
83
65
|
}
|
|
84
66
|
onMessage(ws, event) {
|
|
85
67
|
const data = JSON.parse(event.data);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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);
|
|
92
79
|
}
|
|
93
80
|
}
|
|
94
81
|
clear() {
|
|
95
|
-
this.
|
|
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;
|
|
82
|
+
this.player.clear();
|
|
105
83
|
}
|
|
106
84
|
stop() {
|
|
107
|
-
if (this.source) {
|
|
108
|
-
this.source.stop();
|
|
109
|
-
}
|
|
110
85
|
this.started = false;
|
|
111
86
|
if (this.ws) {
|
|
112
87
|
this.ws.close();
|
|
@@ -116,6 +91,7 @@ class Vapi {
|
|
|
116
91
|
this.mediaRecorder.stop();
|
|
117
92
|
}
|
|
118
93
|
this.mediaRecorder = null;
|
|
94
|
+
this.emit("stopped");
|
|
119
95
|
}
|
|
120
96
|
}
|
|
121
97
|
exports.default = Vapi;
|
package/package.json
CHANGED
package/player.ts
CHANGED
|
@@ -2,26 +2,45 @@ import AtomicMediaSource from "./AtomicMediaSource";
|
|
|
2
2
|
import EventEmitter from "events";
|
|
3
3
|
|
|
4
4
|
export class ContinuousPlayer extends EventEmitter {
|
|
5
|
-
private
|
|
6
|
-
private
|
|
5
|
+
private atomic: AtomicMediaSource;
|
|
6
|
+
private audio: HTMLAudioElement;
|
|
7
7
|
|
|
8
8
|
constructor() {
|
|
9
9
|
super();
|
|
10
10
|
|
|
11
|
-
this.
|
|
11
|
+
this.audio = document.createElement("audio");
|
|
12
|
+
|
|
13
|
+
this.audio.addEventListener("playing", () => this.emit("speech-start"));
|
|
14
|
+
this.audio.addEventListener("play", () => this.emit("speech-start"));
|
|
15
|
+
this.audio.addEventListener("canplay", () => {
|
|
16
|
+
this.audio.play();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
this.audio.addEventListener("waiting", () => this.emit("speech-end"));
|
|
20
|
+
|
|
21
|
+
this.atomic = new AtomicMediaSource();
|
|
22
|
+
|
|
23
|
+
this.audio.src = URL.createObjectURL(this.atomic.mediaSource);
|
|
24
|
+
document.body.appendChild(this.audio);
|
|
12
25
|
}
|
|
13
26
|
|
|
14
|
-
start() {
|
|
27
|
+
start() {
|
|
28
|
+
this.audio.play();
|
|
29
|
+
}
|
|
15
30
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
playChunk(audioData: ArrayBuffer) {
|
|
32
|
+
this.atomic.sourceBuffer?.addEventListener(
|
|
33
|
+
"updateend",
|
|
34
|
+
() => {
|
|
35
|
+
this.audio.play().catch((error) => console.error("Play error:", error));
|
|
36
|
+
},
|
|
37
|
+
{ once: true }
|
|
38
|
+
);
|
|
39
|
+
this.atomic.appendBuffer(audioData);
|
|
24
40
|
}
|
|
25
41
|
|
|
26
|
-
clear() {
|
|
42
|
+
clear() {
|
|
43
|
+
this.audio.pause();
|
|
44
|
+
this.atomic.clearBuffer();
|
|
45
|
+
}
|
|
27
46
|
}
|
package/vapi.ts
CHANGED
|
@@ -1,57 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
IAudioBufferSourceNode,
|
|
5
|
-
IAudioContext,
|
|
6
|
-
} from "standardized-audio-context";
|
|
7
|
-
import async, { QueueObject } from "async";
|
|
8
|
-
|
|
1
|
+
import { ContinuousPlayer } from "./player";
|
|
2
|
+
import { CreateAssistantDTO } from "./api";
|
|
3
|
+
import EventEmitter from "events";
|
|
9
4
|
import { client } from "./client";
|
|
10
5
|
import { decode } from "base64-arraybuffer";
|
|
11
6
|
|
|
12
|
-
export default class Vapi {
|
|
13
|
-
private audioContext: AudioContext = new AudioContext();
|
|
14
|
-
private source: IAudioBufferSourceNode<IAudioContext> | null = null;
|
|
7
|
+
export default class Vapi extends EventEmitter {
|
|
15
8
|
private started: boolean = false;
|
|
16
9
|
private ws: WebSocket | null = null;
|
|
17
10
|
private mediaRecorder: MediaRecorder | null = null;
|
|
11
|
+
private player: ContinuousPlayer;
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
constructor(apiToken: string, apiUrl?: string) {
|
|
13
|
+
constructor(apiToken: string, apiBaseUrl?: string) {
|
|
14
|
+
super();
|
|
15
|
+
client.baseUrl = apiBaseUrl ?? "https://api.vapi.ai";
|
|
24
16
|
client.setSecurityData(apiToken);
|
|
25
|
-
|
|
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);
|
|
17
|
+
this.player = new ContinuousPlayer();
|
|
48
18
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
callback();
|
|
53
|
-
});
|
|
54
|
-
}, 1);
|
|
19
|
+
this.player.on("speech-start", () => this.emit("speech-start"));
|
|
20
|
+
this.player.on("speech-end", () => this.emit("speech-end"));
|
|
55
21
|
}
|
|
56
22
|
|
|
57
23
|
start(assistant: CreateAssistantDTO | string): void {
|
|
@@ -60,11 +26,12 @@ export default class Vapi {
|
|
|
60
26
|
}
|
|
61
27
|
|
|
62
28
|
this.started = true;
|
|
29
|
+
this.player.start();
|
|
63
30
|
|
|
64
31
|
client.call
|
|
65
32
|
.callControllerCreateWebCall({
|
|
33
|
+
assistant: typeof assistant === "string" ? undefined : assistant,
|
|
66
34
|
assistantId: typeof assistant === "string" ? assistant : undefined,
|
|
67
|
-
assistant: typeof assistant === "object" ? assistant : undefined,
|
|
68
35
|
})
|
|
69
36
|
.then(({ data }) => {
|
|
70
37
|
const { callId, url } = data;
|
|
@@ -74,7 +41,6 @@ export default class Vapi {
|
|
|
74
41
|
|
|
75
42
|
socket.onopen = () => {
|
|
76
43
|
socket.send(JSON.stringify({ event: "start", callId }));
|
|
77
|
-
this.startRecording();
|
|
78
44
|
};
|
|
79
45
|
socket.onmessage = (event) => {
|
|
80
46
|
if (!socket) return;
|
|
@@ -95,45 +61,37 @@ export default class Vapi {
|
|
|
95
61
|
mimeType: "audio/webm;codecs=opus",
|
|
96
62
|
});
|
|
97
63
|
|
|
98
|
-
this.mediaRecorder.start(100);
|
|
99
64
|
this.mediaRecorder.ondataavailable = (event: BlobEvent) => {
|
|
100
65
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
101
66
|
this.ws.send(event.data);
|
|
102
67
|
}
|
|
103
68
|
};
|
|
69
|
+
this.mediaRecorder.start(100);
|
|
104
70
|
});
|
|
105
71
|
}
|
|
106
72
|
|
|
107
73
|
private onMessage(ws: WebSocket, event: MessageEvent): void {
|
|
108
74
|
const data = JSON.parse(event.data);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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);
|
|
115
87
|
}
|
|
116
88
|
}
|
|
117
89
|
|
|
118
90
|
clear(): void {
|
|
119
|
-
this.
|
|
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;
|
|
91
|
+
this.player.clear();
|
|
131
92
|
}
|
|
132
93
|
|
|
133
94
|
stop(): void {
|
|
134
|
-
if (this.source) {
|
|
135
|
-
this.source.stop();
|
|
136
|
-
}
|
|
137
95
|
this.started = false;
|
|
138
96
|
if (this.ws) {
|
|
139
97
|
this.ws.close();
|
|
@@ -143,5 +101,6 @@ export default class Vapi {
|
|
|
143
101
|
this.mediaRecorder.stop();
|
|
144
102
|
}
|
|
145
103
|
this.mediaRecorder = null;
|
|
104
|
+
this.emit("stopped");
|
|
146
105
|
}
|
|
147
106
|
}
|