@vapi-ai/web 1.0.139 → 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 -48
- package/package.json +1 -1
- package/player.ts +32 -13
- package/vapi.ts +28 -68
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);
|
|
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,48 +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
|
-
this.
|
|
20
|
-
|
|
21
|
-
|
|
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);
|
|
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"));
|
|
38
21
|
}
|
|
39
22
|
start(assistant) {
|
|
40
23
|
if (this.started) {
|
|
41
24
|
return;
|
|
42
25
|
}
|
|
43
26
|
this.started = true;
|
|
27
|
+
this.player.start();
|
|
44
28
|
client_1.client.call
|
|
45
29
|
.callControllerCreateWebCall({
|
|
30
|
+
assistant: typeof assistant === "string" ? undefined : assistant,
|
|
46
31
|
assistantId: typeof assistant === "string" ? assistant : undefined,
|
|
47
|
-
assistant: typeof assistant === "object" ? assistant : undefined,
|
|
48
32
|
})
|
|
49
33
|
.then(({ data }) => {
|
|
50
34
|
const { callId, url } = data;
|
|
@@ -52,7 +36,6 @@ class Vapi {
|
|
|
52
36
|
this.ws = socket;
|
|
53
37
|
socket.onopen = () => {
|
|
54
38
|
socket.send(JSON.stringify({ event: "start", callId }));
|
|
55
|
-
this.startRecording();
|
|
56
39
|
};
|
|
57
40
|
socket.onmessage = (event) => {
|
|
58
41
|
if (!socket)
|
|
@@ -72,40 +55,33 @@ class Vapi {
|
|
|
72
55
|
this.mediaRecorder = new MediaRecorder(stream, {
|
|
73
56
|
mimeType: "audio/webm;codecs=opus",
|
|
74
57
|
});
|
|
75
|
-
this.mediaRecorder.start(100);
|
|
76
58
|
this.mediaRecorder.ondataavailable = (event) => {
|
|
77
59
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
78
60
|
this.ws.send(event.data);
|
|
79
61
|
}
|
|
80
62
|
};
|
|
63
|
+
this.mediaRecorder.start(100);
|
|
81
64
|
});
|
|
82
65
|
}
|
|
83
66
|
onMessage(ws, event) {
|
|
84
67
|
const data = JSON.parse(event.data);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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);
|
|
91
79
|
}
|
|
92
80
|
}
|
|
93
81
|
clear() {
|
|
94
|
-
this.
|
|
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;
|
|
82
|
+
this.player.clear();
|
|
104
83
|
}
|
|
105
84
|
stop() {
|
|
106
|
-
if (this.source) {
|
|
107
|
-
this.source.stop();
|
|
108
|
-
}
|
|
109
85
|
this.started = false;
|
|
110
86
|
if (this.ws) {
|
|
111
87
|
this.ws.close();
|
|
@@ -115,6 +91,7 @@ class Vapi {
|
|
|
115
91
|
this.mediaRecorder.stop();
|
|
116
92
|
}
|
|
117
93
|
this.mediaRecorder = null;
|
|
94
|
+
this.emit("stopped");
|
|
118
95
|
}
|
|
119
96
|
}
|
|
120
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,56 +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) {
|
|
13
|
+
constructor(apiToken: string, apiBaseUrl?: string) {
|
|
14
|
+
super();
|
|
15
|
+
client.baseUrl = apiBaseUrl ?? "https://api.vapi.ai";
|
|
24
16
|
client.setSecurityData(apiToken);
|
|
17
|
+
this.player = new ContinuousPlayer();
|
|
25
18
|
|
|
26
|
-
this.
|
|
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);
|
|
19
|
+
this.player.on("speech-start", () => this.emit("speech-start"));
|
|
20
|
+
this.player.on("speech-end", () => this.emit("speech-end"));
|
|
54
21
|
}
|
|
55
22
|
|
|
56
23
|
start(assistant: CreateAssistantDTO | string): void {
|
|
@@ -59,11 +26,12 @@ export default class Vapi {
|
|
|
59
26
|
}
|
|
60
27
|
|
|
61
28
|
this.started = true;
|
|
29
|
+
this.player.start();
|
|
62
30
|
|
|
63
31
|
client.call
|
|
64
32
|
.callControllerCreateWebCall({
|
|
33
|
+
assistant: typeof assistant === "string" ? undefined : assistant,
|
|
65
34
|
assistantId: typeof assistant === "string" ? assistant : undefined,
|
|
66
|
-
assistant: typeof assistant === "object" ? assistant : undefined,
|
|
67
35
|
})
|
|
68
36
|
.then(({ data }) => {
|
|
69
37
|
const { callId, url } = data;
|
|
@@ -73,7 +41,6 @@ export default class Vapi {
|
|
|
73
41
|
|
|
74
42
|
socket.onopen = () => {
|
|
75
43
|
socket.send(JSON.stringify({ event: "start", callId }));
|
|
76
|
-
this.startRecording();
|
|
77
44
|
};
|
|
78
45
|
socket.onmessage = (event) => {
|
|
79
46
|
if (!socket) return;
|
|
@@ -94,45 +61,37 @@ export default class Vapi {
|
|
|
94
61
|
mimeType: "audio/webm;codecs=opus",
|
|
95
62
|
});
|
|
96
63
|
|
|
97
|
-
this.mediaRecorder.start(100);
|
|
98
64
|
this.mediaRecorder.ondataavailable = (event: BlobEvent) => {
|
|
99
65
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
100
66
|
this.ws.send(event.data);
|
|
101
67
|
}
|
|
102
68
|
};
|
|
69
|
+
this.mediaRecorder.start(100);
|
|
103
70
|
});
|
|
104
71
|
}
|
|
105
72
|
|
|
106
73
|
private onMessage(ws: WebSocket, event: MessageEvent): void {
|
|
107
74
|
const data = JSON.parse(event.data);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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);
|
|
114
87
|
}
|
|
115
88
|
}
|
|
116
89
|
|
|
117
90
|
clear(): void {
|
|
118
|
-
this.
|
|
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;
|
|
91
|
+
this.player.clear();
|
|
130
92
|
}
|
|
131
93
|
|
|
132
94
|
stop(): void {
|
|
133
|
-
if (this.source) {
|
|
134
|
-
this.source.stop();
|
|
135
|
-
}
|
|
136
95
|
this.started = false;
|
|
137
96
|
if (this.ws) {
|
|
138
97
|
this.ws.close();
|
|
@@ -142,5 +101,6 @@ export default class Vapi {
|
|
|
142
101
|
this.mediaRecorder.stop();
|
|
143
102
|
}
|
|
144
103
|
this.mediaRecorder = null;
|
|
104
|
+
this.emit("stopped");
|
|
145
105
|
}
|
|
146
106
|
}
|