assemblyai 4.12.2 → 4.13.0-beta.1
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/assemblyai.umd.js +210 -4
- package/dist/assemblyai.umd.min.js +1 -1
- package/dist/browser.mjs +207 -5
- package/dist/bun.mjs +207 -5
- package/dist/deno.mjs +207 -5
- package/dist/index.cjs +210 -4
- package/dist/index.mjs +210 -5
- package/dist/node.cjs +207 -4
- package/dist/node.mjs +207 -5
- package/dist/services/index.d.ts +6 -1
- package/dist/services/streaming/factory.d.ts +10 -0
- package/dist/services/streaming/index.d.ts +2 -0
- package/dist/services/streaming/service.d.ts +20 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/services/index.d.ts +1 -0
- package/dist/types/streaming/index.d.ts +71 -0
- package/dist/utils/errors/index.d.ts +1 -0
- package/dist/utils/errors/streaming.d.ts +23 -0
- package/dist/workerd.mjs +207 -5
- package/package.json +1 -1
- package/src/services/index.ts +14 -0
- package/src/services/streaming/factory.ts +39 -0
- package/src/services/streaming/index.ts +2 -0
- package/src/services/streaming/service.ts +219 -0
- package/src/types/index.ts +2 -1
- package/src/types/services/index.ts +1 -0
- package/src/types/streaming/index.ts +94 -0
- package/src/utils/errors/index.ts +6 -0
- package/src/utils/errors/streaming.ts +51 -0
package/dist/workerd.mjs
CHANGED
|
@@ -15,7 +15,7 @@ if (typeof navigator !== "undefined" && navigator.userAgent) {
|
|
|
15
15
|
defaultUserAgentString += navigator.userAgent;
|
|
16
16
|
}
|
|
17
17
|
const defaultUserAgent = {
|
|
18
|
-
sdk: { name: "JavaScript", version: "4.
|
|
18
|
+
sdk: { name: "JavaScript", version: "4.13.0-beta.1" },
|
|
19
19
|
};
|
|
20
20
|
if (typeof process !== "undefined") {
|
|
21
21
|
if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
|
|
@@ -197,9 +197,48 @@ const RealtimeErrorMessages = {
|
|
|
197
197
|
class RealtimeError extends Error {
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
const StreamingErrorType = {
|
|
201
|
+
BadSampleRate: 4000,
|
|
202
|
+
AuthFailed: 4001,
|
|
203
|
+
InsufficientFunds: 4002,
|
|
204
|
+
FreeTierUser: 4003,
|
|
205
|
+
NonexistentSessionId: 4004,
|
|
206
|
+
SessionExpired: 4008,
|
|
207
|
+
ClosedSession: 4010,
|
|
208
|
+
RateLimited: 4029,
|
|
209
|
+
UniqueSessionViolation: 4030,
|
|
210
|
+
SessionTimeout: 4031,
|
|
211
|
+
AudioTooShort: 4032,
|
|
212
|
+
AudioTooLong: 4033,
|
|
213
|
+
AudioTooSmallToTranscode: 4034,
|
|
214
|
+
BadSchema: 4101,
|
|
215
|
+
TooManyStreams: 4102,
|
|
216
|
+
Reconnected: 4103,
|
|
217
|
+
};
|
|
218
|
+
const StreamingErrorMessages = {
|
|
219
|
+
[StreamingErrorType.BadSampleRate]: "Sample rate must be a positive integer",
|
|
220
|
+
[StreamingErrorType.AuthFailed]: "Not Authorized",
|
|
221
|
+
[StreamingErrorType.InsufficientFunds]: "Insufficient funds",
|
|
222
|
+
[StreamingErrorType.FreeTierUser]: "This feature is paid-only and requires you to add a credit card. Please visit https://app.assemblyai.com/ to add a credit card to your account.",
|
|
223
|
+
[StreamingErrorType.NonexistentSessionId]: "Session ID does not exist",
|
|
224
|
+
[StreamingErrorType.SessionExpired]: "Session has expired",
|
|
225
|
+
[StreamingErrorType.ClosedSession]: "Session is closed",
|
|
226
|
+
[StreamingErrorType.RateLimited]: "Rate limited",
|
|
227
|
+
[StreamingErrorType.UniqueSessionViolation]: "Unique session violation",
|
|
228
|
+
[StreamingErrorType.SessionTimeout]: "Session Timeout",
|
|
229
|
+
[StreamingErrorType.AudioTooShort]: "Audio too short",
|
|
230
|
+
[StreamingErrorType.AudioTooLong]: "Audio too long",
|
|
231
|
+
[StreamingErrorType.AudioTooSmallToTranscode]: "Audio too small to transcode",
|
|
232
|
+
[StreamingErrorType.BadSchema]: "Bad schema",
|
|
233
|
+
[StreamingErrorType.TooManyStreams]: "Too many streams",
|
|
234
|
+
[StreamingErrorType.Reconnected]: "This session has been reconnected. This WebSocket is no longer valid.",
|
|
235
|
+
};
|
|
236
|
+
class StreamingError extends Error {
|
|
237
|
+
}
|
|
238
|
+
|
|
200
239
|
const defaultRealtimeUrl = "wss://api.assemblyai.com/v2/realtime/ws";
|
|
201
240
|
const forceEndOfUtteranceMessage = `{"force_end_utterance":true}`;
|
|
202
|
-
const terminateSessionMessage = `{"terminate_session":true}`;
|
|
241
|
+
const terminateSessionMessage$1 = `{"terminate_session":true}`;
|
|
203
242
|
/**
|
|
204
243
|
* RealtimeTranscriber connects to the Streaming Speech-to-Text API and lets you transcribe audio in real-time.
|
|
205
244
|
*/
|
|
@@ -388,11 +427,11 @@ class RealtimeTranscriber {
|
|
|
388
427
|
const sessionTerminatedPromise = new Promise((resolve) => {
|
|
389
428
|
this.sessionTerminatedResolve = resolve;
|
|
390
429
|
});
|
|
391
|
-
this.socket.send(terminateSessionMessage);
|
|
430
|
+
this.socket.send(terminateSessionMessage$1);
|
|
392
431
|
await sessionTerminatedPromise;
|
|
393
432
|
}
|
|
394
433
|
else {
|
|
395
|
-
this.socket.send(terminateSessionMessage);
|
|
434
|
+
this.socket.send(terminateSessionMessage$1);
|
|
396
435
|
}
|
|
397
436
|
}
|
|
398
437
|
if (this.socket?.removeAllListeners)
|
|
@@ -729,7 +768,166 @@ function dataUrlToBlob(dataUrl) {
|
|
|
729
768
|
return new Blob([u8arr], { type: mime });
|
|
730
769
|
}
|
|
731
770
|
|
|
771
|
+
const defaultStreamingUrl$1 = "wss://streaming.assemblyai.com/v3/ws";
|
|
772
|
+
const terminateSessionMessage = `{"type":"Terminate"}`;
|
|
773
|
+
class StreamingTranscriber {
|
|
774
|
+
constructor(params) {
|
|
775
|
+
this.listeners = {};
|
|
776
|
+
this.params = {
|
|
777
|
+
...params,
|
|
778
|
+
websocketBaseUrl: params.websocketBaseUrl || defaultStreamingUrl$1,
|
|
779
|
+
};
|
|
780
|
+
if ("token" in params && params.token)
|
|
781
|
+
this.token = params.token;
|
|
782
|
+
if ("apiKey" in params && params.apiKey)
|
|
783
|
+
this.apiKey = params.apiKey;
|
|
784
|
+
if (!(this.token || this.apiKey)) {
|
|
785
|
+
throw new Error("API key or temporary token is required.");
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
connectionUrl() {
|
|
789
|
+
const url = new URL(this.params.websocketBaseUrl ?? "");
|
|
790
|
+
if (url.protocol !== "wss:") {
|
|
791
|
+
throw new Error("Invalid protocol, must be wss");
|
|
792
|
+
}
|
|
793
|
+
const searchParams = new URLSearchParams();
|
|
794
|
+
searchParams.set("sample_rate", this.params.sampleRate.toString());
|
|
795
|
+
if (this.params.wordFinalizationMaxWaitTime) {
|
|
796
|
+
searchParams.set("word_finalization_max_wait_time", this.params.wordFinalizationMaxWaitTime.toString());
|
|
797
|
+
}
|
|
798
|
+
if (this.params.endOfTurnConfidenceThreshold) {
|
|
799
|
+
searchParams.set("end_of_turn_confidence_threshold", this.params.endOfTurnConfidenceThreshold.toString());
|
|
800
|
+
}
|
|
801
|
+
if (this.params.minEndOfTurnSilenceWhenConfident) {
|
|
802
|
+
searchParams.set("min_end_of_turn_silence_when_confident", this.params.minEndOfTurnSilenceWhenConfident.toString());
|
|
803
|
+
}
|
|
804
|
+
if (this.params.maxTurnSilence) {
|
|
805
|
+
searchParams.set("max_turn_silence", this.params.maxTurnSilence.toString());
|
|
806
|
+
}
|
|
807
|
+
if (this.params.formattedFinals) {
|
|
808
|
+
searchParams.set("formatted_finals", this.params.formattedFinals.toString());
|
|
809
|
+
}
|
|
810
|
+
url.search = searchParams.toString();
|
|
811
|
+
return url;
|
|
812
|
+
}
|
|
813
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
814
|
+
on(event, listener) {
|
|
815
|
+
this.listeners[event] = listener;
|
|
816
|
+
}
|
|
817
|
+
connect() {
|
|
818
|
+
return new Promise((resolve) => {
|
|
819
|
+
if (this.socket) {
|
|
820
|
+
throw new Error("Already connected");
|
|
821
|
+
}
|
|
822
|
+
const url = this.connectionUrl();
|
|
823
|
+
this.socket = factory(url.toString(), {
|
|
824
|
+
headers: { Authorization: this.token || this.apiKey },
|
|
825
|
+
});
|
|
826
|
+
this.socket.binaryType = "arraybuffer";
|
|
827
|
+
this.socket.onopen = () => { };
|
|
828
|
+
this.socket.onclose = ({ code, reason }) => {
|
|
829
|
+
if (!reason) {
|
|
830
|
+
if (code in StreamingErrorMessages) {
|
|
831
|
+
reason = StreamingErrorMessages[code];
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
this.listeners.close?.(code, reason);
|
|
835
|
+
};
|
|
836
|
+
this.socket.onerror = (event) => {
|
|
837
|
+
if (event.error)
|
|
838
|
+
this.listeners.error?.(event.error);
|
|
839
|
+
else
|
|
840
|
+
this.listeners.error?.(new Error(event.message));
|
|
841
|
+
};
|
|
842
|
+
this.socket.onmessage = ({ data }) => {
|
|
843
|
+
const message = JSON.parse(data.toString());
|
|
844
|
+
if ("error" in message) {
|
|
845
|
+
this.listeners.error?.(new StreamingError(message.error));
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
switch (message.type) {
|
|
849
|
+
case "Begin": {
|
|
850
|
+
resolve(message);
|
|
851
|
+
this.listeners.open?.(message);
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
case "Turn": {
|
|
855
|
+
this.listeners.turn?.(message);
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
case "Termination": {
|
|
859
|
+
this.sessionTerminatedResolve?.();
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
stream() {
|
|
867
|
+
return new WritableStream({
|
|
868
|
+
write: (chunk) => {
|
|
869
|
+
this.sendAudio(chunk);
|
|
870
|
+
},
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
sendAudio(audio) {
|
|
874
|
+
this.send(audio);
|
|
875
|
+
}
|
|
876
|
+
send(data) {
|
|
877
|
+
if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
|
|
878
|
+
throw new Error("Socket is not open for communication");
|
|
879
|
+
}
|
|
880
|
+
this.socket.send(data);
|
|
881
|
+
}
|
|
882
|
+
async close(waitForSessionTermination = true) {
|
|
883
|
+
if (this.socket) {
|
|
884
|
+
if (this.socket.readyState === this.socket.OPEN) {
|
|
885
|
+
if (waitForSessionTermination) {
|
|
886
|
+
const sessionTerminatedPromise = new Promise((resolve) => {
|
|
887
|
+
this.sessionTerminatedResolve = resolve;
|
|
888
|
+
});
|
|
889
|
+
this.socket.send(terminateSessionMessage);
|
|
890
|
+
await sessionTerminatedPromise;
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
this.socket.send(terminateSessionMessage);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (this.socket?.removeAllListeners)
|
|
897
|
+
this.socket.removeAllListeners();
|
|
898
|
+
this.socket.close();
|
|
899
|
+
}
|
|
900
|
+
this.listeners = {};
|
|
901
|
+
this.socket = undefined;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
class StreamingTranscriberFactory extends BaseService {
|
|
906
|
+
constructor(params) {
|
|
907
|
+
super(params);
|
|
908
|
+
}
|
|
909
|
+
transcriber(params) {
|
|
910
|
+
return new StreamingTranscriber(params);
|
|
911
|
+
}
|
|
912
|
+
async createTemporaryToken(params) {
|
|
913
|
+
const searchParams = new URLSearchParams();
|
|
914
|
+
// Add each param to the search params
|
|
915
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
916
|
+
if (value !== undefined && value !== null) {
|
|
917
|
+
searchParams.append(key, String(value));
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
const queryString = searchParams.toString();
|
|
921
|
+
const url = queryString ? `/v3/token?${queryString}` : "/v3/token";
|
|
922
|
+
const data = await this.fetchJson(url, {
|
|
923
|
+
method: "GET",
|
|
924
|
+
});
|
|
925
|
+
return data.token;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
732
929
|
const defaultBaseUrl = "https://api.assemblyai.com";
|
|
930
|
+
const defaultStreamingUrl = "https://streaming.assemblyai.com";
|
|
733
931
|
class AssemblyAI {
|
|
734
932
|
/**
|
|
735
933
|
* Create a new AssemblyAI client.
|
|
@@ -744,7 +942,11 @@ class AssemblyAI {
|
|
|
744
942
|
this.transcripts = new TranscriptService(params, this.files);
|
|
745
943
|
this.lemur = new LemurService(params);
|
|
746
944
|
this.realtime = new RealtimeTranscriberFactory(params);
|
|
945
|
+
this.streaming = new StreamingTranscriberFactory({
|
|
946
|
+
...params,
|
|
947
|
+
baseUrl: params.streamingBaseUrl || defaultStreamingUrl,
|
|
948
|
+
});
|
|
747
949
|
}
|
|
748
950
|
}
|
|
749
951
|
|
|
750
|
-
export { AssemblyAI, FileService, LemurService, RealtimeService, RealtimeServiceFactory, RealtimeTranscriber, RealtimeTranscriberFactory, TranscriptService };
|
|
952
|
+
export { AssemblyAI, FileService, LemurService, RealtimeService, RealtimeServiceFactory, RealtimeTranscriber, RealtimeTranscriberFactory, StreamingTranscriber, TranscriptService };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assemblyai",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.0-beta.1",
|
|
4
4
|
"description": "The AssemblyAI JavaScript SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, as well as the latest LeMUR models.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18"
|
package/src/services/index.ts
CHANGED
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
} from "./realtime";
|
|
9
9
|
import { TranscriptService } from "./transcripts";
|
|
10
10
|
import { FileService } from "./files";
|
|
11
|
+
import { StreamingTranscriber, StreamingTranscriberFactory } from "./streaming";
|
|
11
12
|
|
|
12
13
|
const defaultBaseUrl = "https://api.assemblyai.com";
|
|
14
|
+
const defaultStreamingUrl = "https://streaming.assemblyai.com";
|
|
13
15
|
|
|
14
16
|
class AssemblyAI {
|
|
15
17
|
/**
|
|
@@ -32,6 +34,11 @@ class AssemblyAI {
|
|
|
32
34
|
*/
|
|
33
35
|
public realtime: RealtimeTranscriberFactory;
|
|
34
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The streaming service.
|
|
39
|
+
*/
|
|
40
|
+
public streaming: StreamingTranscriberFactory;
|
|
41
|
+
|
|
35
42
|
/**
|
|
36
43
|
* Create a new AssemblyAI client.
|
|
37
44
|
* @param params - The parameters for the service, including the API key and base URL, if any.
|
|
@@ -41,10 +48,16 @@ class AssemblyAI {
|
|
|
41
48
|
if (params.baseUrl && params.baseUrl.endsWith("/")) {
|
|
42
49
|
params.baseUrl = params.baseUrl.slice(0, -1);
|
|
43
50
|
}
|
|
51
|
+
|
|
44
52
|
this.files = new FileService(params);
|
|
45
53
|
this.transcripts = new TranscriptService(params, this.files);
|
|
46
54
|
this.lemur = new LemurService(params);
|
|
47
55
|
this.realtime = new RealtimeTranscriberFactory(params);
|
|
56
|
+
|
|
57
|
+
this.streaming = new StreamingTranscriberFactory({
|
|
58
|
+
...params,
|
|
59
|
+
baseUrl: params.streamingBaseUrl || defaultStreamingUrl,
|
|
60
|
+
});
|
|
48
61
|
}
|
|
49
62
|
}
|
|
50
63
|
|
|
@@ -57,4 +70,5 @@ export {
|
|
|
57
70
|
RealtimeService,
|
|
58
71
|
TranscriptService,
|
|
59
72
|
FileService,
|
|
73
|
+
StreamingTranscriber,
|
|
60
74
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseServiceParams,
|
|
3
|
+
StreamingTokenParams,
|
|
4
|
+
StreamingTranscriberParams,
|
|
5
|
+
StreamingTemporaryTokenResponse,
|
|
6
|
+
} from "../..";
|
|
7
|
+
import { StreamingTranscriber } from "./service";
|
|
8
|
+
import { BaseService } from "../base";
|
|
9
|
+
|
|
10
|
+
export class StreamingTranscriberFactory extends BaseService {
|
|
11
|
+
constructor(params: BaseServiceParams) {
|
|
12
|
+
super(params);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
transcriber(params: StreamingTranscriberParams): StreamingTranscriber {
|
|
16
|
+
return new StreamingTranscriber(params);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async createTemporaryToken(params: StreamingTokenParams) {
|
|
20
|
+
const searchParams = new URLSearchParams();
|
|
21
|
+
|
|
22
|
+
// Add each param to the search params
|
|
23
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
24
|
+
if (value !== undefined && value !== null) {
|
|
25
|
+
searchParams.append(key, String(value));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const queryString = searchParams.toString();
|
|
30
|
+
const url = queryString ? `/v3/token?${queryString}` : "/v3/token";
|
|
31
|
+
|
|
32
|
+
const data = await this.fetchJson<StreamingTemporaryTokenResponse>(url, {
|
|
33
|
+
method: "GET",
|
|
34
|
+
});
|
|
35
|
+
return data.token;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class StreamingServiceFactory extends StreamingTranscriberFactory {}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { WritableStream } from "#streams";
|
|
2
|
+
import {
|
|
3
|
+
PolyfillWebSocket,
|
|
4
|
+
factory as polyfillWebSocketFactory,
|
|
5
|
+
} from "#websocket";
|
|
6
|
+
import { ErrorEvent, MessageEvent, CloseEvent } from "ws";
|
|
7
|
+
import {
|
|
8
|
+
StreamingEvents,
|
|
9
|
+
StreamingListeners,
|
|
10
|
+
StreamingTranscriberParams,
|
|
11
|
+
AudioData,
|
|
12
|
+
BeginEvent,
|
|
13
|
+
StreamingEventMessage,
|
|
14
|
+
TurnEvent,
|
|
15
|
+
} from "../..";
|
|
16
|
+
import { StreamingError, StreamingErrorMessages } from "../../utils/errors";
|
|
17
|
+
import { StreamingErrorTypeCodes } from "../../utils/errors/streaming";
|
|
18
|
+
|
|
19
|
+
const defaultStreamingUrl = "wss://streaming.assemblyai.com/v3/ws";
|
|
20
|
+
const terminateSessionMessage = `{"type":"Terminate"}`;
|
|
21
|
+
|
|
22
|
+
type BufferLike =
|
|
23
|
+
| string
|
|
24
|
+
| Buffer
|
|
25
|
+
| DataView
|
|
26
|
+
| number
|
|
27
|
+
| ArrayBufferView
|
|
28
|
+
| Uint8Array
|
|
29
|
+
| ArrayBuffer
|
|
30
|
+
| SharedArrayBuffer
|
|
31
|
+
| ReadonlyArray<unknown>
|
|
32
|
+
| ReadonlyArray<number>
|
|
33
|
+
| { valueOf(): ArrayBuffer }
|
|
34
|
+
| { valueOf(): SharedArrayBuffer }
|
|
35
|
+
| { valueOf(): Uint8Array }
|
|
36
|
+
| { valueOf(): ReadonlyArray<number> }
|
|
37
|
+
| { valueOf(): string }
|
|
38
|
+
| { [Symbol.toPrimitive](hint: string): string };
|
|
39
|
+
|
|
40
|
+
export class StreamingTranscriber {
|
|
41
|
+
private apiKey?: string;
|
|
42
|
+
private token?: string;
|
|
43
|
+
private params: StreamingTranscriberParams;
|
|
44
|
+
|
|
45
|
+
private socket?: PolyfillWebSocket;
|
|
46
|
+
private listeners: StreamingListeners = {};
|
|
47
|
+
private sessionTerminatedResolve?: () => void;
|
|
48
|
+
|
|
49
|
+
constructor(params: StreamingTranscriberParams) {
|
|
50
|
+
this.params = {
|
|
51
|
+
...params,
|
|
52
|
+
websocketBaseUrl: params.websocketBaseUrl || defaultStreamingUrl,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if ("token" in params && params.token) this.token = params.token;
|
|
56
|
+
if ("apiKey" in params && params.apiKey) this.apiKey = params.apiKey;
|
|
57
|
+
|
|
58
|
+
if (!(this.token || this.apiKey)) {
|
|
59
|
+
throw new Error("API key or temporary token is required.");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private connectionUrl(): URL {
|
|
64
|
+
const url = new URL(this.params.websocketBaseUrl ?? "");
|
|
65
|
+
|
|
66
|
+
if (url.protocol !== "wss:") {
|
|
67
|
+
throw new Error("Invalid protocol, must be wss");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const searchParams = new URLSearchParams();
|
|
71
|
+
|
|
72
|
+
searchParams.set("sample_rate", this.params.sampleRate.toString());
|
|
73
|
+
|
|
74
|
+
if (this.params.wordFinalizationMaxWaitTime) {
|
|
75
|
+
searchParams.set(
|
|
76
|
+
"word_finalization_max_wait_time",
|
|
77
|
+
this.params.wordFinalizationMaxWaitTime.toString(),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (this.params.endOfTurnConfidenceThreshold) {
|
|
82
|
+
searchParams.set(
|
|
83
|
+
"end_of_turn_confidence_threshold",
|
|
84
|
+
this.params.endOfTurnConfidenceThreshold.toString(),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this.params.minEndOfTurnSilenceWhenConfident) {
|
|
89
|
+
searchParams.set(
|
|
90
|
+
"min_end_of_turn_silence_when_confident",
|
|
91
|
+
this.params.minEndOfTurnSilenceWhenConfident.toString(),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (this.params.maxTurnSilence) {
|
|
96
|
+
searchParams.set(
|
|
97
|
+
"max_turn_silence",
|
|
98
|
+
this.params.maxTurnSilence.toString(),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (this.params.formattedFinals) {
|
|
103
|
+
searchParams.set(
|
|
104
|
+
"formatted_finals",
|
|
105
|
+
this.params.formattedFinals.toString(),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
url.search = searchParams.toString();
|
|
110
|
+
|
|
111
|
+
return url;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
on(event: "open", listener: (event: BeginEvent) => void): void;
|
|
115
|
+
on(event: "turn", listener: (event: TurnEvent) => void): void;
|
|
116
|
+
on(event: "error", listener: (error: Error) => void): void;
|
|
117
|
+
on(event: "close", listener: (code: number, reason: string) => void): void;
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
+
on(event: StreamingEvents, listener: (...args: any[]) => void) {
|
|
120
|
+
this.listeners[event] = listener;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
connect() {
|
|
124
|
+
return new Promise<BeginEvent>((resolve) => {
|
|
125
|
+
if (this.socket) {
|
|
126
|
+
throw new Error("Already connected");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const url = this.connectionUrl();
|
|
130
|
+
|
|
131
|
+
this.socket = polyfillWebSocketFactory(url.toString(), {
|
|
132
|
+
headers: { Authorization: this.token || this.apiKey },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
this.socket.binaryType = "arraybuffer";
|
|
136
|
+
|
|
137
|
+
this.socket.onopen = () => {};
|
|
138
|
+
|
|
139
|
+
this.socket.onclose = ({ code, reason }: CloseEvent) => {
|
|
140
|
+
if (!reason) {
|
|
141
|
+
if (code in StreamingErrorMessages) {
|
|
142
|
+
reason = StreamingErrorMessages[code as StreamingErrorTypeCodes];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
this.listeners.close?.(code, reason);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
this.socket.onerror = (event: ErrorEvent) => {
|
|
149
|
+
if (event.error) this.listeners.error?.(event.error as Error);
|
|
150
|
+
else this.listeners.error?.(new Error(event.message));
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
this.socket.onmessage = ({ data }: MessageEvent) => {
|
|
154
|
+
const message = JSON.parse(data.toString()) as StreamingEventMessage;
|
|
155
|
+
|
|
156
|
+
if ("error" in message) {
|
|
157
|
+
this.listeners.error?.(new StreamingError(message.error));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
switch (message.type) {
|
|
162
|
+
case "Begin": {
|
|
163
|
+
resolve(message);
|
|
164
|
+
this.listeners.open?.(message);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "Turn": {
|
|
168
|
+
this.listeners.turn?.(message);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case "Termination": {
|
|
172
|
+
this.sessionTerminatedResolve?.();
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
stream(): WritableStream<AudioData> {
|
|
181
|
+
return new WritableStream<AudioData>({
|
|
182
|
+
write: (chunk: AudioData) => {
|
|
183
|
+
this.sendAudio(chunk);
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
sendAudio(audio: AudioData) {
|
|
189
|
+
this.send(audio);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private send(data: BufferLike) {
|
|
193
|
+
if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
|
|
194
|
+
throw new Error("Socket is not open for communication");
|
|
195
|
+
}
|
|
196
|
+
this.socket.send(data);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async close(waitForSessionTermination = true) {
|
|
200
|
+
if (this.socket) {
|
|
201
|
+
if (this.socket.readyState === this.socket.OPEN) {
|
|
202
|
+
if (waitForSessionTermination) {
|
|
203
|
+
const sessionTerminatedPromise = new Promise<void>((resolve) => {
|
|
204
|
+
this.sessionTerminatedResolve = resolve;
|
|
205
|
+
});
|
|
206
|
+
this.socket.send(terminateSessionMessage);
|
|
207
|
+
await sessionTerminatedPromise;
|
|
208
|
+
} else {
|
|
209
|
+
this.socket.send(terminateSessionMessage);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (this.socket?.removeAllListeners) this.socket.removeAllListeners();
|
|
213
|
+
this.socket.close();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this.listeners = {};
|
|
217
|
+
this.socket = undefined;
|
|
218
|
+
}
|
|
219
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export * from "./files";
|
|
2
2
|
export * from "./transcripts";
|
|
3
3
|
export * from "./realtime";
|
|
4
|
-
export * from "./services";
|
|
5
4
|
export * from "./asyncapi.generated";
|
|
5
|
+
export * from "./streaming";
|
|
6
|
+
export * from "./services";
|
|
6
7
|
export * from "./openapi.generated";
|
|
7
8
|
export * from "./deprecated";
|
|
8
9
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export type StreamingTranscriberParams = {
|
|
2
|
+
websocketBaseUrl?: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
token?: string;
|
|
5
|
+
sampleRate: number;
|
|
6
|
+
|
|
7
|
+
wordFinalizationMaxWaitTime?: number;
|
|
8
|
+
endOfTurnConfidenceThreshold?: number;
|
|
9
|
+
minEndOfTurnSilenceWhenConfident?: number;
|
|
10
|
+
maxTurnSilence?: number;
|
|
11
|
+
formattedFinals?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type StreamingEvents = "open" | "close" | "turn" | "error";
|
|
15
|
+
|
|
16
|
+
export type StreamingListeners = {
|
|
17
|
+
open?: (event: BeginEvent) => void;
|
|
18
|
+
close?: (code: number, reason: string) => void;
|
|
19
|
+
turn?: (event: TurnEvent) => void;
|
|
20
|
+
error?: (error: Error) => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type StreamingTokenParams = {
|
|
24
|
+
expires_in_seconds: number;
|
|
25
|
+
max_session_duration_seconds: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type StreamingTemporaryTokenResponse = {
|
|
29
|
+
token: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type StreamingAudioData = ArrayBufferLike;
|
|
33
|
+
|
|
34
|
+
export type BeginEvent = {
|
|
35
|
+
type: "Begin";
|
|
36
|
+
id: string;
|
|
37
|
+
expires_at: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type TurnEvent = {
|
|
41
|
+
type: "Turn";
|
|
42
|
+
turn_order: number;
|
|
43
|
+
turn_is_formatted: boolean;
|
|
44
|
+
end_of_turn: boolean;
|
|
45
|
+
transcript: string;
|
|
46
|
+
end_of_turn_confidence: number;
|
|
47
|
+
words: StreamingWord[];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type StreamingWord = {
|
|
51
|
+
start: number;
|
|
52
|
+
end: number;
|
|
53
|
+
confidence: number;
|
|
54
|
+
text: string;
|
|
55
|
+
word_is_final: boolean;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type TerminationEvent = {
|
|
59
|
+
type: "Termination";
|
|
60
|
+
audio_duration_seconds: number;
|
|
61
|
+
session_duration_seconds: number;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type StreamingTerminateSession = {
|
|
65
|
+
type: "Terminate";
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type StreamingUpdateConfiguration = {
|
|
69
|
+
type: "UpdateConfiguration";
|
|
70
|
+
word_finalization_max_wait_time?: number;
|
|
71
|
+
end_of_turn_confidence_threshold?: number;
|
|
72
|
+
min_end_of_turn_silence_when_confident?: number;
|
|
73
|
+
max_turn_silence?: number;
|
|
74
|
+
formatted_finals?: boolean;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type StreamingForceEndpoint = {
|
|
78
|
+
type: "ForceEndpoint";
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type ErrorEvent = {
|
|
82
|
+
error: string;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type StreamingEventMessage =
|
|
86
|
+
| BeginEvent
|
|
87
|
+
| TurnEvent
|
|
88
|
+
| TerminationEvent
|
|
89
|
+
| ErrorEvent;
|
|
90
|
+
|
|
91
|
+
export type StreamingOperationMessage =
|
|
92
|
+
| StreamingUpdateConfiguration
|
|
93
|
+
| StreamingForceEndpoint
|
|
94
|
+
| StreamingTerminateSession;
|