@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 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 audioCtx;
5
- private source;
4
+ private atomic;
5
+ private audio;
6
6
  constructor();
7
7
  start(): void;
8
- playChunk(audioData: ArrayBuffer): Promise<void>;
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.source = null;
21
- this.audioCtx = new AudioContext();
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
- 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
- });
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 async from "async";
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 nextClipTime;
10
- private decodeQueue;
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 standardized_audio_context_1 = require("standardized-audio-context");
7
- const async_1 = __importDefault(require("async"));
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, apiUrl) {
12
- this.audioContext = new standardized_audio_context_1.AudioContext();
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
- this.nextClipTime = 0;
16
+ client_1.client.baseUrl = apiBaseUrl !== null && apiBaseUrl !== void 0 ? apiBaseUrl : "https://api.vapi.ai";
18
17
  client_1.client.setSecurityData(apiToken);
19
- client_1.client.baseUrl = apiUrl !== null && apiUrl !== void 0 ? apiUrl : "https://api.vapi.ai";
20
- this.decodeQueue = this.createQueue();
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
- if (data.event === "media") {
87
- const audioData = (0, base64_arraybuffer_1.decode)(data.media.payload);
88
- this.decodeQueue.push(audioData);
89
- }
90
- if (data.event === "clear") {
91
- this.clear();
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.decodeQueue.kill();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vapi-ai/web",
3
- "version": "1.0.140",
3
+ "version": "1.0.141",
4
4
  "description": "",
5
5
  "main": "dist/vapi.js",
6
6
  "types": "dist/vapi.d.ts",
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 audioCtx: AudioContext;
6
- private source: AudioBufferSourceNode | null = null;
5
+ private atomic: AtomicMediaSource;
6
+ private audio: HTMLAudioElement;
7
7
 
8
8
  constructor() {
9
9
  super();
10
10
 
11
- this.audioCtx = new AudioContext();
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
- 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");
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 { Assistant, CreateAssistantDTO } from "./api";
2
- import {
3
- AudioContext,
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
- private nextClipTime: number = 0;
20
-
21
- private decodeQueue: QueueObject<ArrayBuffer>;
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
- client.baseUrl = apiUrl ?? "https://api.vapi.ai";
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
- // Schedule the next clip time
50
- this.nextClipTime += buffer.duration;
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
- if (data.event === "media") {
110
- const audioData = decode(data.media.payload);
111
- this.decodeQueue.push(audioData);
112
- }
113
- if (data.event === "clear") {
114
- this.clear();
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.decodeQueue.kill();
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
  }