@vapi-ai/web 1.0.61 → 1.0.62

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/README.md CHANGED
@@ -24,7 +24,7 @@ Then, create a new instance of the Vapi class, passing your Client Token as a pa
24
24
  const vapi = new Vapi('your-web-token');
25
25
  ```
26
26
 
27
- You can start a new call by calling the `start` method and passing an `assistant` object or `assistantId`:
27
+ You can start a new call by calling the `start` method and passing an `agent` object or `agentId`:
28
28
 
29
29
  ```javascript
30
30
  vapi.start({
@@ -34,7 +34,7 @@ vapi.start({
34
34
  });
35
35
  ```
36
36
  ```javascript
37
- vapi.start('your-assistant-id');
37
+ vapi.start('your-agent-id');
38
38
  ```
39
39
 
40
40
  The `start` method will initiate a new call.
package/api.ts CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  export interface PhoneCallParams {
13
13
  callSid?: string;
14
- status?: string;
14
+ status?: string[];
15
15
  customerPhoneNumber: string;
16
16
  twilioPhoneNumber: string;
17
17
  twilioAccountSid: string;
@@ -227,8 +227,8 @@ export interface PhoneNumber {
227
227
  stripeSubscriptionId?: string;
228
228
  number: string;
229
229
  assistantId?: string;
230
- twilioAccountSid: string;
231
- twilioAuthToken: string;
230
+ twilioAccountSid?: string;
231
+ twilioAuthToken?: string;
232
232
  }
233
233
 
234
234
  export interface ImportTwilioPhoneNumberDto {
@@ -266,16 +266,22 @@ export interface FullRequestParams extends Omit<RequestInit, "body"> {
266
266
  cancelToken?: CancelToken;
267
267
  }
268
268
 
269
- export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">;
269
+ export type RequestParams = Omit<
270
+ FullRequestParams,
271
+ "body" | "method" | "query" | "path"
272
+ >;
270
273
 
271
274
  export interface ApiConfig<SecurityDataType = unknown> {
272
275
  baseUrl?: string;
273
276
  baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
274
- securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void;
277
+ securityWorker?: (
278
+ securityData: SecurityDataType | null
279
+ ) => Promise<RequestParams | void> | RequestParams | void;
275
280
  customFetch?: typeof fetch;
276
281
  }
277
282
 
278
- export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
283
+ export interface HttpResponse<D extends unknown, E extends unknown = unknown>
284
+ extends Response {
279
285
  data: D;
280
286
  error: E;
281
287
  }
@@ -294,7 +300,8 @@ export class HttpClient<SecurityDataType = unknown> {
294
300
  private securityData: SecurityDataType | null = null;
295
301
  private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
296
302
  private abortControllers = new Map<CancelToken, AbortController>();
297
- private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams);
303
+ private customFetch = (...fetchParams: Parameters<typeof fetch>) =>
304
+ fetch(...fetchParams);
298
305
 
299
306
  private baseApiParams: RequestParams = {
300
307
  credentials: "same-origin",
@@ -313,7 +320,9 @@ export class HttpClient<SecurityDataType = unknown> {
313
320
 
314
321
  protected encodeQueryParam(key: string, value: any) {
315
322
  const encodedKey = encodeURIComponent(key);
316
- return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
323
+ return `${encodedKey}=${encodeURIComponent(
324
+ typeof value === "number" ? value : `${value}`
325
+ )}`;
317
326
  }
318
327
 
319
328
  protected addQueryParam(query: QueryParamsType, key: string) {
@@ -327,9 +336,15 @@ export class HttpClient<SecurityDataType = unknown> {
327
336
 
328
337
  protected toQueryString(rawQuery?: QueryParamsType): string {
329
338
  const query = rawQuery || {};
330
- const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
339
+ const keys = Object.keys(query).filter(
340
+ (key) => "undefined" !== typeof query[key]
341
+ );
331
342
  return keys
332
- .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
343
+ .map((key) =>
344
+ Array.isArray(query[key])
345
+ ? this.addArrayQueryParam(query, key)
346
+ : this.addQueryParam(query, key)
347
+ )
333
348
  .join("&");
334
349
  }
335
350
 
@@ -340,8 +355,13 @@ export class HttpClient<SecurityDataType = unknown> {
340
355
 
341
356
  private contentFormatters: Record<ContentType, (input: any) => any> = {
342
357
  [ContentType.Json]: (input: any) =>
343
- input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
344
- [ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input),
358
+ input !== null && (typeof input === "object" || typeof input === "string")
359
+ ? JSON.stringify(input)
360
+ : input,
361
+ [ContentType.Text]: (input: any) =>
362
+ input !== null && typeof input !== "string"
363
+ ? JSON.stringify(input)
364
+ : input,
345
365
  [ContentType.FormData]: (input: any) =>
346
366
  Object.keys(input || {}).reduce((formData, key) => {
347
367
  const property = input[key];
@@ -351,14 +371,17 @@ export class HttpClient<SecurityDataType = unknown> {
351
371
  ? property
352
372
  : typeof property === "object" && property !== null
353
373
  ? JSON.stringify(property)
354
- : `${property}`,
374
+ : `${property}`
355
375
  );
356
376
  return formData;
357
377
  }, new FormData()),
358
378
  [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
359
379
  };
360
380
 
361
- protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
381
+ protected mergeRequestParams(
382
+ params1: RequestParams,
383
+ params2?: RequestParams
384
+ ): RequestParams {
362
385
  return {
363
386
  ...this.baseApiParams,
364
387
  ...params1,
@@ -371,7 +394,9 @@ export class HttpClient<SecurityDataType = unknown> {
371
394
  };
372
395
  }
373
396
 
374
- protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
397
+ protected createAbortSignal = (
398
+ cancelToken: CancelToken
399
+ ): AbortSignal | undefined => {
375
400
  if (this.abortControllers.has(cancelToken)) {
376
401
  const abortController = this.abortControllers.get(cancelToken);
377
402
  if (abortController) {
@@ -415,15 +440,28 @@ export class HttpClient<SecurityDataType = unknown> {
415
440
  const payloadFormatter = this.contentFormatters[type || ContentType.Json];
416
441
  const responseFormat = format || requestParams.format;
417
442
 
418
- return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, {
419
- ...requestParams,
420
- headers: {
421
- ...(requestParams.headers || {}),
422
- ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
423
- },
424
- signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
425
- body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
426
- }).then(async (response) => {
443
+ return this.customFetch(
444
+ `${baseUrl || this.baseUrl || ""}${path}${
445
+ queryString ? `?${queryString}` : ""
446
+ }`,
447
+ {
448
+ ...requestParams,
449
+ headers: {
450
+ ...(requestParams.headers || {}),
451
+ ...(type && type !== ContentType.FormData
452
+ ? { "Content-Type": type }
453
+ : {}),
454
+ },
455
+ signal:
456
+ (cancelToken
457
+ ? this.createAbortSignal(cancelToken)
458
+ : requestParams.signal) || null,
459
+ body:
460
+ typeof body === "undefined" || body === null
461
+ ? null
462
+ : payloadFormatter(body),
463
+ }
464
+ ).then(async (response) => {
427
465
  const r = response as HttpResponse<T, E>;
428
466
  r.data = null as unknown as T;
429
467
  r.error = null as unknown as E;
@@ -461,7 +499,9 @@ export class HttpClient<SecurityDataType = unknown> {
461
499
  *
462
500
  * API for making voice assistants
463
501
  */
464
- export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> {
502
+ export class Api<
503
+ SecurityDataType extends unknown,
504
+ > extends HttpClient<SecurityDataType> {
465
505
  call = {
466
506
  /**
467
507
  * No description
@@ -503,7 +543,10 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
503
543
  * @summary Create Phone Call
504
544
  * @request POST:/call/phone
505
545
  */
506
- callControllerCreatePhoneCall: (data: CreateOutboundCallDto, params: RequestParams = {}) =>
546
+ callControllerCreatePhoneCall: (
547
+ data: CreateOutboundCallDto,
548
+ params: RequestParams = {}
549
+ ) =>
507
550
  this.request<Call, any>({
508
551
  path: `/call/phone`,
509
552
  method: "POST",
@@ -521,7 +564,10 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
521
564
  * @summary Create Web Call
522
565
  * @request POST:/call/web
523
566
  */
524
- callControllerCreateWebCall: (data: CreateWebCallDto, params: RequestParams = {}) =>
567
+ callControllerCreateWebCall: (
568
+ data: CreateWebCallDto,
569
+ params: RequestParams = {}
570
+ ) =>
525
571
  this.request<WebCallResponseDto, any>({
526
572
  path: `/call/web`,
527
573
  method: "POST",
@@ -540,7 +586,10 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
540
586
  * @summary Create Assistant
541
587
  * @request POST:/assistant
542
588
  */
543
- assistantControllerCreate: (data: CreateAssistantDTO, params: RequestParams = {}) =>
589
+ assistantControllerCreate: (
590
+ data: CreateAssistantDTO,
591
+ params: RequestParams = {}
592
+ ) =>
544
593
  this.request<Assistant, any>({
545
594
  path: `/assistant`,
546
595
  method: "POST",
@@ -590,7 +639,11 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
590
639
  * @summary Update Assistant
591
640
  * @request PATCH:/assistant/{id}
592
641
  */
593
- assistantControllerUpdate: (id: string, data: UpdateAssistantDTO, params: RequestParams = {}) =>
642
+ assistantControllerUpdate: (
643
+ id: string,
644
+ data: UpdateAssistantDTO,
645
+ params: RequestParams = {}
646
+ ) =>
594
647
  this.request<Assistant, any>({
595
648
  path: `/assistant/${id}`,
596
649
  method: "PATCH",
@@ -624,7 +677,10 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
624
677
  * @summary Buy Phone Number
625
678
  * @request POST:/phone-number/buy
626
679
  */
627
- phoneNumberControllerBuy: (data: BuyPhoneNumberDto, params: RequestParams = {}) =>
680
+ phoneNumberControllerBuy: (
681
+ data: BuyPhoneNumberDto,
682
+ params: RequestParams = {}
683
+ ) =>
628
684
  this.request<PhoneNumber, any>({
629
685
  path: `/phone-number/buy`,
630
686
  method: "POST",
@@ -642,7 +698,10 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
642
698
  * @summary Import Twilio Number
643
699
  * @request POST:/phone-number/import
644
700
  */
645
- phoneNumberControllerImport: (data: ImportTwilioPhoneNumberDto, params: RequestParams = {}) =>
701
+ phoneNumberControllerImport: (
702
+ data: ImportTwilioPhoneNumberDto,
703
+ params: RequestParams = {}
704
+ ) =>
646
705
  this.request<PhoneNumber, any>({
647
706
  path: `/phone-number/import`,
648
707
  method: "POST",
@@ -692,7 +751,11 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
692
751
  * @summary Update Phone Number
693
752
  * @request PATCH:/phone-number/{id}
694
753
  */
695
- phoneNumberControllerUpdate: (id: string, data: UpdatePhoneNumberDto, params: RequestParams = {}) =>
754
+ phoneNumberControllerUpdate: (
755
+ id: string,
756
+ data: UpdatePhoneNumberDto,
757
+ params: RequestParams = {}
758
+ ) =>
696
759
  this.request<PhoneNumber, any>({
697
760
  path: `/phone-number/${id}`,
698
761
  method: "PATCH",
package/dist/api.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export interface PhoneCallParams {
2
2
  callSid?: string;
3
- status?: string;
3
+ status?: string[];
4
4
  customerPhoneNumber: string;
5
5
  twilioPhoneNumber: string;
6
6
  twilioAccountSid: string;
@@ -154,8 +154,8 @@ export interface PhoneNumber {
154
154
  stripeSubscriptionId?: string;
155
155
  number: string;
156
156
  assistantId?: string;
157
- twilioAccountSid: string;
158
- twilioAuthToken: string;
157
+ twilioAccountSid?: string;
158
+ twilioAuthToken?: string;
159
159
  }
160
160
  export interface ImportTwilioPhoneNumberDto {
161
161
  /** This is the area code of the phone number to purchase. */
package/dist/api.js CHANGED
@@ -54,8 +54,12 @@ class HttpClient {
54
54
  this.securityData = data;
55
55
  };
56
56
  this.contentFormatters = {
57
- [ContentType.Json]: (input) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
58
- [ContentType.Text]: (input) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input),
57
+ [ContentType.Json]: (input) => input !== null && (typeof input === "object" || typeof input === "string")
58
+ ? JSON.stringify(input)
59
+ : input,
60
+ [ContentType.Text]: (input) => input !== null && typeof input !== "string"
61
+ ? JSON.stringify(input)
62
+ : input,
59
63
  [ContentType.FormData]: (input) => Object.keys(input || {}).reduce((formData, key) => {
60
64
  const property = input[key];
61
65
  formData.append(key, property instanceof Blob
@@ -96,7 +100,13 @@ class HttpClient {
96
100
  const queryString = query && this.toQueryString(query);
97
101
  const payloadFormatter = this.contentFormatters[type || ContentType.Json];
98
102
  const responseFormat = format || requestParams.format;
99
- return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, Object.assign(Object.assign({}, requestParams), { headers: Object.assign(Object.assign({}, (requestParams.headers || {})), (type && type !== ContentType.FormData ? { "Content-Type": type } : {})), signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null, body: typeof body === "undefined" || body === null ? null : payloadFormatter(body) })).then((response) => __awaiter(this, void 0, void 0, function* () {
103
+ return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, Object.assign(Object.assign({}, requestParams), { headers: Object.assign(Object.assign({}, (requestParams.headers || {})), (type && type !== ContentType.FormData
104
+ ? { "Content-Type": type }
105
+ : {})), signal: (cancelToken
106
+ ? this.createAbortSignal(cancelToken)
107
+ : requestParams.signal) || null, body: typeof body === "undefined" || body === null
108
+ ? null
109
+ : payloadFormatter(body) })).then((response) => __awaiter(this, void 0, void 0, function* () {
100
110
  const r = response;
101
111
  r.data = null;
102
112
  r.error = null;
@@ -141,7 +151,9 @@ class HttpClient {
141
151
  const query = rawQuery || {};
142
152
  const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
143
153
  return keys
144
- .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
154
+ .map((key) => Array.isArray(query[key])
155
+ ? this.addArrayQueryParam(query, key)
156
+ : this.addQueryParam(query, key))
145
157
  .join("&");
146
158
  }
147
159
  addQueryParams(rawQuery) {
package/dist/player.d.ts CHANGED
@@ -1,13 +1,12 @@
1
1
  /// <reference types="node" />
2
2
  import EventEmitter from "events";
3
3
  export declare class ContinuousPlayer extends EventEmitter {
4
- private mediaSource;
5
- private sourceBuffer;
6
- private audio;
4
+ private audioCtx;
5
+ private nextStartTime;
7
6
  private audioQueue;
8
- constructor();
9
- start(): void;
7
+ private crossFadeDuration;
8
+ constructor(crossFadeDuration?: number);
9
+ private createQueue;
10
10
  playChunk(audioData: ArrayBuffer): void;
11
- private appendNextChunk;
12
- clear(): void;
11
+ clear(): Promise<void>;
13
12
  }
package/dist/player.js CHANGED
@@ -1,60 +1,68 @@
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 async_1 = __importDefault(require("async"));
16
+ const async_1 = require("async"); // You need to install the async package
8
17
  const events_1 = __importDefault(require("events"));
9
18
  class ContinuousPlayer extends events_1.default {
10
- constructor() {
19
+ constructor(crossFadeDuration = 0.5) {
11
20
  super();
12
- this.sourceBuffer = null;
13
- this.mediaSource = new MediaSource();
14
- this.audio = document.createElement("audio");
15
- this.audio.src = URL.createObjectURL(this.mediaSource);
16
- document.body.appendChild(this.audio);
17
- this.audioQueue = async_1.default.queue((task, callback) => {
18
- this.appendNextChunk(task, callback);
19
- }, 1);
20
- this.mediaSource.addEventListener("sourceopen", () => {
21
- this.sourceBuffer = this.mediaSource.addSourceBuffer('audio/webm; codecs="opus"');
22
- // this.sourceBuffer.mode = "sequence";
23
- this.sourceBuffer.addEventListener("updateend", () => {
24
- this.audioQueue.resume();
25
- console.log("RESUMING");
26
- });
27
- });
21
+ this.audioCtx = new AudioContext();
22
+ this.nextStartTime = 0;
23
+ this.audioQueue = this.createQueue();
24
+ this.crossFadeDuration = crossFadeDuration;
28
25
  }
29
- start() {
30
- this.audio.play();
26
+ createQueue() {
27
+ const q = (0, async_1.queue)((audioPromise, callback) => __awaiter(this, void 0, void 0, function* () {
28
+ const audioBuffer = yield audioPromise;
29
+ const source = this.audioCtx.createBufferSource();
30
+ source.buffer = audioBuffer;
31
+ source.connect(this.audioCtx.destination);
32
+ // Create a gain node for crossfading
33
+ const gainNode = this.audioCtx.createGain();
34
+ source.connect(gainNode);
35
+ gainNode.connect(this.audioCtx.destination);
36
+ // Start the source at the next start time
37
+ source.start(this.nextStartTime);
38
+ // Schedule the crossfade
39
+ gainNode.gain.setValueAtTime(0, this.nextStartTime);
40
+ gainNode.gain.linearRampToValueAtTime(1, this.nextStartTime + this.crossFadeDuration);
41
+ gainNode.gain.setValueAtTime(1, this.nextStartTime + audioBuffer.duration - this.crossFadeDuration);
42
+ gainNode.gain.linearRampToValueAtTime(0, this.nextStartTime + audioBuffer.duration);
43
+ source.onended = () => {
44
+ this.nextStartTime += audioBuffer.duration;
45
+ callback();
46
+ };
47
+ }), 1);
48
+ q.drain(() => {
49
+ this.emit("speech-end");
50
+ });
51
+ q.saturated(() => {
52
+ this.emit("speech-start");
53
+ });
54
+ return q;
31
55
  }
32
56
  playChunk(audioData) {
33
- this.audioQueue.push(audioData);
34
- }
35
- appendNextChunk(chunk, callback) {
36
- if (this.sourceBuffer && !this.sourceBuffer.updating) {
37
- console.log("PAUSE");
38
- this.audioQueue.pause();
39
- this.sourceBuffer.appendBuffer(chunk);
40
- }
41
- callback();
57
+ const decodePromise = this.audioCtx.decodeAudioData(audioData);
58
+ this.audioQueue.push(decodePromise);
42
59
  }
43
60
  clear() {
44
- if (this.sourceBuffer) {
45
- if (this.sourceBuffer.updating) {
46
- this.sourceBuffer.addEventListener("updateend", () => this.clear(), {
47
- once: true,
48
- });
49
- }
50
- else {
51
- this.sourceBuffer.abort();
52
- while (this.sourceBuffer.buffered.length > 0) {
53
- this.sourceBuffer.remove(0, this.sourceBuffer.buffered.end(0));
54
- }
55
- }
56
- this.sourceBuffer.timestampOffset = 0;
57
- }
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ this.audioQueue.kill();
63
+ this.audioQueue = this.createQueue();
64
+ this.nextStartTime = 0;
65
+ });
58
66
  }
59
67
  }
60
68
  exports.ContinuousPlayer = ContinuousPlayer;
package/dist/vapi.d.ts CHANGED
@@ -7,7 +7,7 @@ export default class Vapi extends EventEmitter {
7
7
  private mediaRecorder;
8
8
  private player;
9
9
  constructor(apiToken: string);
10
- start(assistant: CreateAssistantDTO | string): void;
10
+ start(agent: CreateAssistantDTO | string): void;
11
11
  private startRecording;
12
12
  private onMessage;
13
13
  clear(): void;
package/dist/vapi.js CHANGED
@@ -18,16 +18,15 @@ class Vapi extends events_1.default {
18
18
  this.player.on("speech-start", () => this.emit("speech-start"));
19
19
  this.player.on("speech-end", () => this.emit("speech-end"));
20
20
  }
21
- start(assistant) {
21
+ start(agent) {
22
22
  if (this.started) {
23
23
  return;
24
24
  }
25
25
  this.started = true;
26
- this.player.start();
27
26
  client_1.client.call
28
27
  .callControllerCreateWebCall({
29
- assistant: typeof assistant === "string" ? undefined : assistant,
30
- assistantId: typeof assistant === "string" ? assistant : undefined,
28
+ assistant: typeof agent === "string" ? undefined : agent,
29
+ assistantId: typeof agent === "string" ? agent : undefined,
31
30
  })
32
31
  .then(({ data }) => {
33
32
  const { callId, url } = data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vapi-ai/web",
3
- "version": "1.0.61",
3
+ "version": "1.0.62",
4
4
  "description": "",
5
5
  "main": "dist/vapi.js",
6
6
  "types": "dist/vapi.d.ts",
package/player.ts CHANGED
@@ -1,71 +1,78 @@
1
- import async, { QueueObject } from "async";
1
+ import { QueueObject, queue } from "async"; // You need to install the async package
2
2
 
3
3
  import EventEmitter from "events";
4
4
 
5
5
  export class ContinuousPlayer extends EventEmitter {
6
- private mediaSource: MediaSource;
7
- private sourceBuffer: SourceBuffer | null = null;
8
- private audio: HTMLAudioElement;
9
- private audioQueue: QueueObject<ArrayBuffer>;
6
+ private audioCtx: AudioContext;
7
+ private nextStartTime: number;
8
+ private audioQueue: QueueObject<Promise<AudioBuffer>>;
9
+ private crossFadeDuration: number; // Duration of the crossfade
10
10
 
11
- constructor() {
11
+ constructor(crossFadeDuration = 0.5) {
12
12
  super();
13
- this.mediaSource = new MediaSource();
13
+ this.audioCtx = new AudioContext();
14
+ this.nextStartTime = 0;
15
+ this.audioQueue = this.createQueue();
16
+ this.crossFadeDuration = crossFadeDuration;
17
+ }
18
+
19
+ private createQueue() {
20
+ const q = queue(
21
+ async (audioPromise: Promise<AudioBuffer>, callback: Function) => {
22
+ const audioBuffer = await audioPromise;
23
+ const source = this.audioCtx.createBufferSource();
24
+ source.buffer = audioBuffer;
25
+ source.connect(this.audioCtx.destination);
14
26
 
15
- this.audio = document.createElement("audio");
16
- this.audio.src = URL.createObjectURL(this.mediaSource);
17
- document.body.appendChild(this.audio);
27
+ // Create a gain node for crossfading
28
+ const gainNode = this.audioCtx.createGain();
29
+ source.connect(gainNode);
30
+ gainNode.connect(this.audioCtx.destination);
18
31
 
19
- this.audioQueue = async.queue((task, callback) => {
20
- this.appendNextChunk(task, callback);
21
- }, 1);
32
+ // Start the source at the next start time
33
+ source.start(this.nextStartTime);
22
34
 
23
- this.mediaSource.addEventListener("sourceopen", () => {
24
- this.sourceBuffer = this.mediaSource.addSourceBuffer(
25
- 'audio/webm; codecs="opus"'
26
- );
27
- // this.sourceBuffer.mode = "sequence";
35
+ // Schedule the crossfade
36
+ gainNode.gain.setValueAtTime(0, this.nextStartTime);
37
+ gainNode.gain.linearRampToValueAtTime(
38
+ 1,
39
+ this.nextStartTime + this.crossFadeDuration
40
+ );
41
+ gainNode.gain.setValueAtTime(
42
+ 1,
43
+ this.nextStartTime + audioBuffer.duration - this.crossFadeDuration
44
+ );
45
+ gainNode.gain.linearRampToValueAtTime(
46
+ 0,
47
+ this.nextStartTime + audioBuffer.duration
48
+ );
28
49
 
29
- this.sourceBuffer.addEventListener("updateend", () => {
30
- this.audioQueue.resume();
31
- console.log("RESUMING");
32
- });
50
+ source.onended = () => {
51
+ this.nextStartTime += audioBuffer.duration;
52
+ callback();
53
+ };
54
+ },
55
+ 1
56
+ );
57
+
58
+ q.drain(() => {
59
+ this.emit("speech-end");
60
+ });
61
+ q.saturated(() => {
62
+ this.emit("speech-start");
33
63
  });
34
- }
35
64
 
36
- start() {
37
- this.audio.play();
65
+ return q;
38
66
  }
39
67
 
40
68
  playChunk(audioData: ArrayBuffer) {
41
- this.audioQueue.push(audioData);
42
- }
43
-
44
- private appendNextChunk(
45
- chunk: ArrayBuffer,
46
- callback: async.ErrorCallback<Error>
47
- ): void {
48
- if (this.sourceBuffer && !this.sourceBuffer.updating) {
49
- console.log("PAUSE");
50
- this.audioQueue.pause();
51
- this.sourceBuffer.appendBuffer(chunk);
52
- }
53
- callback();
69
+ const decodePromise = this.audioCtx.decodeAudioData(audioData);
70
+ this.audioQueue.push(decodePromise);
54
71
  }
55
72
 
56
- clear(): void {
57
- if (this.sourceBuffer) {
58
- if (this.sourceBuffer.updating) {
59
- this.sourceBuffer.addEventListener("updateend", () => this.clear(), {
60
- once: true,
61
- });
62
- } else {
63
- this.sourceBuffer.abort();
64
- while (this.sourceBuffer.buffered.length > 0) {
65
- this.sourceBuffer.remove(0, this.sourceBuffer.buffered.end(0));
66
- }
67
- }
68
- this.sourceBuffer.timestampOffset = 0;
69
- }
73
+ async clear() {
74
+ this.audioQueue.kill();
75
+ this.audioQueue = this.createQueue();
76
+ this.nextStartTime = 0;
70
77
  }
71
78
  }
package/vapi.ts CHANGED
@@ -19,18 +19,17 @@ export default class Vapi extends EventEmitter {
19
19
  this.player.on("speech-end", () => this.emit("speech-end"));
20
20
  }
21
21
 
22
- start(assistant: CreateAssistantDTO | string): void {
22
+ start(agent: CreateAssistantDTO | string): void {
23
23
  if (this.started) {
24
24
  return;
25
25
  }
26
26
 
27
27
  this.started = true;
28
- this.player.start();
29
28
 
30
29
  client.call
31
30
  .callControllerCreateWebCall({
32
- assistant: typeof assistant === "string" ? undefined : assistant,
33
- assistantId: typeof assistant === "string" ? assistant : undefined,
31
+ assistant: typeof agent === "string" ? undefined : agent,
32
+ assistantId: typeof agent === "string" ? agent : undefined,
34
33
  })
35
34
  .then(({ data }) => {
36
35
  const { callId, url } = data;