@vapi-ai/web 1.0.62 → 1.0.63

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