@vapi-ai/web 1.0.141 → 1.0.143
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 +1 -6
- package/dist/vapi.js +14 -62
- package/package.json +3 -7
- package/vapi.ts +15 -70
- package/AtomicMediaSource.ts +0 -97
- package/player.ts +0 -46
- package/silent.mp3 +0 -0
package/dist/vapi.d.ts
CHANGED
|
@@ -3,13 +3,8 @@ import { CreateAssistantDTO } from "./api";
|
|
|
3
3
|
import EventEmitter from "events";
|
|
4
4
|
export default class Vapi extends EventEmitter {
|
|
5
5
|
private started;
|
|
6
|
-
private
|
|
7
|
-
private mediaRecorder;
|
|
8
|
-
private player;
|
|
6
|
+
private call;
|
|
9
7
|
constructor(apiToken: string, apiBaseUrl?: string);
|
|
10
8
|
start(assistant: CreateAssistantDTO | string): void;
|
|
11
|
-
private startRecording;
|
|
12
|
-
private onMessage;
|
|
13
|
-
clear(): void;
|
|
14
9
|
stop(): void;
|
|
15
10
|
}
|
package/dist/vapi.js
CHANGED
|
@@ -3,95 +3,47 @@ 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
|
|
6
|
+
const daily_js_1 = __importDefault(require("@daily-co/daily-js"));
|
|
7
7
|
const events_1 = __importDefault(require("events"));
|
|
8
8
|
const client_1 = require("./client");
|
|
9
|
-
const base64_arraybuffer_1 = require("base64-arraybuffer");
|
|
10
9
|
class Vapi extends events_1.default {
|
|
11
10
|
constructor(apiToken, apiBaseUrl) {
|
|
12
11
|
super();
|
|
13
12
|
this.started = false;
|
|
14
|
-
this.
|
|
15
|
-
this.mediaRecorder = null;
|
|
13
|
+
this.call = null;
|
|
16
14
|
client_1.client.baseUrl = apiBaseUrl !== null && apiBaseUrl !== void 0 ? apiBaseUrl : "https://api.vapi.ai";
|
|
17
15
|
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"));
|
|
21
16
|
}
|
|
22
17
|
start(assistant) {
|
|
23
18
|
if (this.started) {
|
|
24
19
|
return;
|
|
25
20
|
}
|
|
26
21
|
this.started = true;
|
|
27
|
-
this.player.start();
|
|
28
22
|
client_1.client.call
|
|
29
23
|
.callControllerCreateWebCall({
|
|
30
24
|
assistant: typeof assistant === "string" ? undefined : assistant,
|
|
31
25
|
assistantId: typeof assistant === "string" ? assistant : undefined,
|
|
32
26
|
})
|
|
33
27
|
.then(({ data }) => {
|
|
34
|
-
const {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.onMessage(socket, event);
|
|
44
|
-
};
|
|
45
|
-
socket.onclose = () => {
|
|
46
|
-
this.stop();
|
|
47
|
-
};
|
|
28
|
+
const { url } = data;
|
|
29
|
+
this.call = daily_js_1.default.createCallObject({
|
|
30
|
+
audioSource: true,
|
|
31
|
+
videoSource: false,
|
|
32
|
+
});
|
|
33
|
+
this.call.join({ url });
|
|
34
|
+
this.call.on("joined-meeting", () => {
|
|
35
|
+
console.log("JOINED");
|
|
36
|
+
});
|
|
48
37
|
})
|
|
49
38
|
.catch((error) => {
|
|
50
39
|
console.error(error);
|
|
51
40
|
});
|
|
52
41
|
}
|
|
53
|
-
startRecording() {
|
|
54
|
-
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
|
|
55
|
-
this.mediaRecorder = new MediaRecorder(stream, {
|
|
56
|
-
mimeType: "audio/webm;codecs=opus",
|
|
57
|
-
});
|
|
58
|
-
this.mediaRecorder.ondataavailable = (event) => {
|
|
59
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
60
|
-
this.ws.send(event.data);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
this.mediaRecorder.start(100);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
onMessage(ws, event) {
|
|
67
|
-
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);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
clear() {
|
|
82
|
-
this.player.clear();
|
|
83
|
-
}
|
|
84
42
|
stop() {
|
|
43
|
+
var _a;
|
|
85
44
|
this.started = false;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
this.ws = null;
|
|
90
|
-
if (this.mediaRecorder) {
|
|
91
|
-
this.mediaRecorder.stop();
|
|
92
|
-
}
|
|
93
|
-
this.mediaRecorder = null;
|
|
94
|
-
this.emit("stopped");
|
|
45
|
+
(_a = this.call) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
46
|
+
this.call = null;
|
|
95
47
|
}
|
|
96
48
|
}
|
|
97
49
|
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.143",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/vapi.js",
|
|
6
6
|
"types": "dist/vapi.d.ts",
|
|
@@ -22,15 +22,11 @@
|
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://github.com/VapiAI/snippet#readme",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"events": "^3.3.0",
|
|
28
|
-
"standardized-audio-context": "^25.3.57"
|
|
25
|
+
"@daily-co/daily-js": "^0.52.0",
|
|
26
|
+
"events": "^3.3.0"
|
|
29
27
|
},
|
|
30
28
|
"devDependencies": {
|
|
31
|
-
"@types/async": "^3.2.21",
|
|
32
29
|
"@types/node": "^20.6.0",
|
|
33
|
-
"@types/ws": "^8.5.5",
|
|
34
30
|
"swagger-typescript-api": "^13.0.3"
|
|
35
31
|
}
|
|
36
32
|
}
|
package/vapi.ts
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import DailyIframe, { DailyCall } from "@daily-co/daily-js";
|
|
2
|
+
|
|
2
3
|
import { CreateAssistantDTO } from "./api";
|
|
3
4
|
import EventEmitter from "events";
|
|
4
5
|
import { client } from "./client";
|
|
5
|
-
import { decode } from "base64-arraybuffer";
|
|
6
6
|
|
|
7
7
|
export default class Vapi extends EventEmitter {
|
|
8
8
|
private started: boolean = false;
|
|
9
|
-
private
|
|
10
|
-
private mediaRecorder: MediaRecorder | null = null;
|
|
11
|
-
private player: ContinuousPlayer;
|
|
9
|
+
private call: DailyCall | null = null;
|
|
12
10
|
|
|
13
11
|
constructor(apiToken: string, apiBaseUrl?: string) {
|
|
14
12
|
super();
|
|
15
13
|
client.baseUrl = apiBaseUrl ?? "https://api.vapi.ai";
|
|
16
14
|
client.setSecurityData(apiToken);
|
|
17
|
-
this.player = new ContinuousPlayer();
|
|
18
|
-
|
|
19
|
-
this.player.on("speech-start", () => this.emit("speech-start"));
|
|
20
|
-
this.player.on("speech-end", () => this.emit("speech-end"));
|
|
21
15
|
}
|
|
22
16
|
|
|
23
17
|
start(assistant: CreateAssistantDTO | string): void {
|
|
@@ -26,7 +20,6 @@ export default class Vapi extends EventEmitter {
|
|
|
26
20
|
}
|
|
27
21
|
|
|
28
22
|
this.started = true;
|
|
29
|
-
this.player.start();
|
|
30
23
|
|
|
31
24
|
client.call
|
|
32
25
|
.callControllerCreateWebCall({
|
|
@@ -34,73 +27,25 @@ export default class Vapi extends EventEmitter {
|
|
|
34
27
|
assistantId: typeof assistant === "string" ? assistant : undefined,
|
|
35
28
|
})
|
|
36
29
|
.then(({ data }) => {
|
|
37
|
-
const {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.onMessage(socket, event);
|
|
48
|
-
};
|
|
49
|
-
socket.onclose = () => {
|
|
50
|
-
this.stop();
|
|
51
|
-
};
|
|
30
|
+
const { url } = data;
|
|
31
|
+
|
|
32
|
+
this.call = DailyIframe.createCallObject({
|
|
33
|
+
audioSource: true,
|
|
34
|
+
videoSource: false,
|
|
35
|
+
});
|
|
36
|
+
this.call.join({ url });
|
|
37
|
+
this.call.on("joined-meeting", () => {
|
|
38
|
+
console.log("JOINED");
|
|
39
|
+
});
|
|
52
40
|
})
|
|
53
41
|
.catch((error) => {
|
|
54
42
|
console.error(error);
|
|
55
43
|
});
|
|
56
44
|
}
|
|
57
45
|
|
|
58
|
-
private startRecording(): void {
|
|
59
|
-
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
|
|
60
|
-
this.mediaRecorder = new MediaRecorder(stream, {
|
|
61
|
-
mimeType: "audio/webm;codecs=opus",
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
this.mediaRecorder.ondataavailable = (event: BlobEvent) => {
|
|
65
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
66
|
-
this.ws.send(event.data);
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
this.mediaRecorder.start(100);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private onMessage(ws: WebSocket, event: MessageEvent): void {
|
|
74
|
-
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);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
clear(): void {
|
|
91
|
-
this.player.clear();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
46
|
stop(): void {
|
|
95
47
|
this.started = false;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
this.ws = null;
|
|
100
|
-
if (this.mediaRecorder) {
|
|
101
|
-
this.mediaRecorder.stop();
|
|
102
|
-
}
|
|
103
|
-
this.mediaRecorder = null;
|
|
104
|
-
this.emit("stopped");
|
|
48
|
+
this.call?.destroy();
|
|
49
|
+
this.call = null;
|
|
105
50
|
}
|
|
106
51
|
}
|
package/AtomicMediaSource.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import async, { QueueObject } from "async";
|
|
2
|
-
|
|
3
|
-
import EventEmitter from "events";
|
|
4
|
-
|
|
5
|
-
declare global {
|
|
6
|
-
interface Window {
|
|
7
|
-
ManagedMediaSource: any;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// ManagedMediaSource needed for iOS Safari
|
|
12
|
-
function getMediaSource() {
|
|
13
|
-
if (window.ManagedMediaSource) {
|
|
14
|
-
return new window.ManagedMediaSource();
|
|
15
|
-
}
|
|
16
|
-
if (window.MediaSource) {
|
|
17
|
-
return new window.MediaSource();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
class AtomicMediaSource extends EventEmitter {
|
|
24
|
-
mediaSource: any;
|
|
25
|
-
sourceBuffer: SourceBuffer | null = null;
|
|
26
|
-
private operationsQueue: QueueObject<() => Promise<void>>;
|
|
27
|
-
|
|
28
|
-
constructor() {
|
|
29
|
-
super();
|
|
30
|
-
this.mediaSource = getMediaSource();
|
|
31
|
-
this.operationsQueue = this.createOperationQueue();
|
|
32
|
-
|
|
33
|
-
if (!this.mediaSource) {
|
|
34
|
-
console.error("MediaSource is not supported in your browser.");
|
|
35
|
-
// Display a message to the user, or handle this situation in another way
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
this.mediaSource.addEventListener("sourceopen", () => {
|
|
40
|
-
this.mediaSource.duration = Number.POSITIVE_INFINITY;
|
|
41
|
-
this.sourceBuffer = this.mediaSource.addSourceBuffer(
|
|
42
|
-
'audio/webm; codecs="opus"'
|
|
43
|
-
);
|
|
44
|
-
if (this.sourceBuffer) {
|
|
45
|
-
this.sourceBuffer.mode = "sequence";
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public appendBuffer(buffer: ArrayBuffer) {
|
|
51
|
-
this.operationsQueue.push(() => {
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
if (this.sourceBuffer) {
|
|
54
|
-
this.sourceBuffer.addEventListener(
|
|
55
|
-
"updateend",
|
|
56
|
-
() => {
|
|
57
|
-
resolve();
|
|
58
|
-
},
|
|
59
|
-
{ once: true }
|
|
60
|
-
);
|
|
61
|
-
this.sourceBuffer.appendBuffer(buffer);
|
|
62
|
-
} else {
|
|
63
|
-
reject();
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
public clearBuffer() {
|
|
70
|
-
// this.operationsQueue.push(() => {
|
|
71
|
-
// return new Promise((resolve, reject) => {
|
|
72
|
-
// if (this.sourceBuffer) {
|
|
73
|
-
// this.sourceBuffer.addEventListener(
|
|
74
|
-
// "updateend",
|
|
75
|
-
// () => {
|
|
76
|
-
// resolve();
|
|
77
|
-
// },
|
|
78
|
-
// { once: true }
|
|
79
|
-
// );
|
|
80
|
-
// if (this.sourceBuffer.buffered.length > 0) {
|
|
81
|
-
// this.sourceBuffer.remove(0, this.sourceBuffer.buffered.end(0));
|
|
82
|
-
// }
|
|
83
|
-
// } else {
|
|
84
|
-
// reject();
|
|
85
|
-
// }
|
|
86
|
-
// });
|
|
87
|
-
// });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private createOperationQueue() {
|
|
91
|
-
return async.queue<() => Promise<void>>((task, callback) => {
|
|
92
|
-
task().then(() => callback());
|
|
93
|
-
}, 1);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export default AtomicMediaSource;
|
package/player.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import AtomicMediaSource from "./AtomicMediaSource";
|
|
2
|
-
import EventEmitter from "events";
|
|
3
|
-
|
|
4
|
-
export class ContinuousPlayer extends EventEmitter {
|
|
5
|
-
private atomic: AtomicMediaSource;
|
|
6
|
-
private audio: HTMLAudioElement;
|
|
7
|
-
|
|
8
|
-
constructor() {
|
|
9
|
-
super();
|
|
10
|
-
|
|
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);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
start() {
|
|
28
|
-
this.audio.play();
|
|
29
|
-
}
|
|
30
|
-
|
|
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);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
clear() {
|
|
43
|
-
this.audio.pause();
|
|
44
|
-
this.atomic.clearBuffer();
|
|
45
|
-
}
|
|
46
|
-
}
|
package/silent.mp3
DELETED
|
Binary file
|