@vapi-ai/web 1.0.137 → 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/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 atomic;
5
- private audio;
4
+ private audioCtx;
5
+ private source;
6
6
  constructor();
7
7
  start(): void;
8
- playChunk(audioData: ArrayBuffer): void;
8
+ playChunk(audioData: ArrayBuffer): Promise<void>;
9
9
  clear(): void;
10
10
  }
package/dist/player.js CHANGED
@@ -1,38 +1,37 @@
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
+ };
2
11
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
13
  };
5
14
  Object.defineProperty(exports, "__esModule", { value: true });
6
15
  exports.ContinuousPlayer = void 0;
7
- const AtomicMediaSource_1 = __importDefault(require("./AtomicMediaSource"));
8
16
  const events_1 = __importDefault(require("events"));
9
17
  class ContinuousPlayer extends events_1.default {
10
18
  constructor() {
11
19
  super();
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();
20
+ this.source = null;
21
+ this.audioCtx = new AudioContext();
25
22
  }
23
+ start() { }
26
24
  playChunk(audioData) {
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();
25
+ return __awaiter(this, void 0, void 0, function* () {
26
+ const audioBuffer = yield this.audioCtx.decodeAudioData(audioData);
27
+ this.source = this.audioCtx.createBufferSource();
28
+ this.source.buffer = audioBuffer;
29
+ this.source.connect(this.audioCtx.destination);
30
+ this.source.start();
31
+ this.emit("speech-start");
32
+ this.source.onended = () => this.emit("speech-end");
33
+ });
36
34
  }
35
+ clear() { }
37
36
  }
38
37
  exports.ContinuousPlayer = ContinuousPlayer;
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);
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 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) {
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
+ 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
- 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);
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.player.clear();
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.137",
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/player.ts CHANGED
@@ -2,45 +2,26 @@ import AtomicMediaSource from "./AtomicMediaSource";
2
2
  import EventEmitter from "events";
3
3
 
4
4
  export class ContinuousPlayer extends EventEmitter {
5
- private atomic: AtomicMediaSource;
6
- private audio: HTMLAudioElement;
5
+ private audioCtx: AudioContext;
6
+ private source: AudioBufferSourceNode | null = null;
7
7
 
8
8
  constructor() {
9
9
  super();
10
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);
11
+ this.audioCtx = new AudioContext();
25
12
  }
26
13
 
27
- start() {
28
- this.audio.play();
29
- }
14
+ start() {}
30
15
 
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);
16
+ async playChunk(audioData: ArrayBuffer) {
17
+ const audioBuffer = await this.audioCtx.decodeAudioData(audioData);
18
+ this.source = this.audioCtx.createBufferSource();
19
+ this.source.buffer = audioBuffer;
20
+ this.source.connect(this.audioCtx.destination);
21
+ this.source.start();
22
+ this.emit("speech-start");
23
+ this.source.onended = () => this.emit("speech-end");
40
24
  }
41
25
 
42
- clear() {
43
- this.audio.pause();
44
- this.atomic.clearBuffer();
45
- }
26
+ clear() {}
46
27
  }
package/vapi.ts CHANGED
@@ -1,23 +1,56 @@
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) {
16
24
  client.setSecurityData(apiToken);
17
- this.player = new ContinuousPlayer();
18
25
 
19
- this.player.on("speech-start", () => this.emit("speech-start"));
20
- this.player.on("speech-end", () => this.emit("speech-end"));
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
- 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);
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.player.clear();
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
  }