@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 +2 -2
- package/api.ts +95 -32
- package/dist/api.d.ts +3 -3
- package/dist/api.js +16 -4
- package/dist/player.d.ts +6 -7
- package/dist/player.js +51 -43
- package/dist/vapi.d.ts +1 -1
- package/dist/vapi.js +3 -4
- package/package.json +1 -1
- package/player.ts +59 -52
- package/vapi.ts +3 -4
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 `
|
|
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-
|
|
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
|
|
231
|
-
twilioAuthToken
|
|
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<
|
|
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?: (
|
|
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>
|
|
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>) =>
|
|
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(
|
|
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(
|
|
339
|
+
const keys = Object.keys(query).filter(
|
|
340
|
+
(key) => "undefined" !== typeof query[key]
|
|
341
|
+
);
|
|
331
342
|
return keys
|
|
332
|
-
.map((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")
|
|
344
|
-
|
|
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(
|
|
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 = (
|
|
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(
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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<
|
|
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: (
|
|
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: (
|
|
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: (
|
|
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: (
|
|
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: (
|
|
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: (
|
|
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: (
|
|
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
|
|
158
|
-
twilioAuthToken
|
|
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")
|
|
58
|
-
|
|
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
|
|
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) =>
|
|
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
|
|
5
|
-
private
|
|
6
|
-
private audio;
|
|
4
|
+
private audioCtx;
|
|
5
|
+
private nextStartTime;
|
|
7
6
|
private audioQueue;
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
private crossFadeDuration;
|
|
8
|
+
constructor(crossFadeDuration?: number);
|
|
9
|
+
private createQueue;
|
|
10
10
|
playChunk(audioData: ArrayBuffer): void;
|
|
11
|
-
|
|
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 =
|
|
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.
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
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
|
-
|
|
30
|
-
|
|
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.
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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(
|
|
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(
|
|
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
|
|
30
|
-
assistantId: typeof
|
|
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
package/player.ts
CHANGED
|
@@ -1,71 +1,78 @@
|
|
|
1
|
-
import
|
|
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
|
|
7
|
-
private
|
|
8
|
-
private
|
|
9
|
-
private
|
|
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.
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
}, 1);
|
|
32
|
+
// Start the source at the next start time
|
|
33
|
+
source.start(this.nextStartTime);
|
|
22
34
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
37
|
-
this.audio.play();
|
|
65
|
+
return q;
|
|
38
66
|
}
|
|
39
67
|
|
|
40
68
|
playChunk(audioData: ArrayBuffer) {
|
|
41
|
-
this.
|
|
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()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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(
|
|
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
|
|
33
|
-
assistantId: typeof
|
|
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;
|