@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 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);
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 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) {
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
- 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);
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
- 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();
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.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;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vapi-ai/web",
3
- "version": "1.0.139",
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,56 +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) {
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.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);
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
- 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();
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.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;
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
  }