whio-api-sdk 1.0.198-beta-staging → 1.0.200-beta-staging
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/WEBSOCKET_AUDIO_STREAMING.md +14 -14
- package/dist/src/sdk/modules/websocket.module.d.ts +3 -3
- package/dist/src/sdk/modules/websocket.module.js +34 -24
- package/dist/src/sdk/sdk.d.ts +1 -1
- package/dist/src/sdk/types/websocket.types.d.ts +1 -1
- package/examples/websocket-audio-streaming.ts +15 -16
- package/package.json +1 -1
- package/src/sdk/modules/websocket.module.ts +32 -24
- package/src/sdk/types/websocket.types.ts +1 -1
|
@@ -5,7 +5,7 @@ The SDK now supports real-time audio streaming via WebSocket connections, provid
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- 🔄 **Auto-reconnection** with exponential backoff
|
|
8
|
-
- 📡 **Real-time audio streaming** with
|
|
8
|
+
- 📡 **Real-time audio streaming** with base64 audio support
|
|
9
9
|
- 🎯 **Event-driven architecture** for connection and audio events
|
|
10
10
|
- 🛡️ **Automatic authentication** using existing SDK tokens
|
|
11
11
|
- 📊 **Connection statistics** and monitoring
|
|
@@ -85,7 +85,7 @@ sdk.onWebSocketEvent('connection-error', (error) => {
|
|
|
85
85
|
|
|
86
86
|
```typescript
|
|
87
87
|
// Stream individual audio chunks
|
|
88
|
-
const audioChunk =
|
|
88
|
+
const audioChunk = "SGVsbG8gV29ybGQ="; // Base64 encoded audio data
|
|
89
89
|
sdk.streamAudioChunk(session.id, audioChunk, false);
|
|
90
90
|
|
|
91
91
|
// Stream final chunk to trigger transcription
|
|
@@ -97,9 +97,9 @@ sdk.streamAudioChunk(session.id, finalChunk, true);
|
|
|
97
97
|
```typescript
|
|
98
98
|
// Stream multiple chunks with automatic pacing
|
|
99
99
|
const audioChunks = [
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
"SGVsbG8gV29ybGQ=", // Base64 chunk 1
|
|
101
|
+
"QmFzZTY0IGNodW5r", // Base64 chunk 2
|
|
102
|
+
"QXVkaW8gZGF0YQ==" // Base64 chunk 3
|
|
103
103
|
];
|
|
104
104
|
|
|
105
105
|
await sdk.streamAudioChunks(session.id, audioChunks, {
|
|
@@ -120,9 +120,9 @@ processor.onaudioprocess = (event) => {
|
|
|
120
120
|
const inputBuffer = event.inputBuffer;
|
|
121
121
|
const inputData = inputBuffer.getChannelData(0);
|
|
122
122
|
|
|
123
|
-
// Convert to
|
|
124
|
-
const
|
|
125
|
-
sdk.streamAudioChunk(session.id,
|
|
123
|
+
// Convert float data to base64 (you'll need your own conversion logic)
|
|
124
|
+
const base64Chunk = convertFloatArrayToBase64(inputData);
|
|
125
|
+
sdk.streamAudioChunk(session.id, base64Chunk, false);
|
|
126
126
|
};
|
|
127
127
|
|
|
128
128
|
source.connect(processor);
|
|
@@ -130,7 +130,7 @@ processor.connect(audioContext.destination);
|
|
|
130
130
|
|
|
131
131
|
// Stop recording and trigger transcription
|
|
132
132
|
setTimeout(() => {
|
|
133
|
-
sdk.streamAudioChunk(session.id,
|
|
133
|
+
sdk.streamAudioChunk(session.id, "", true); // Empty string for end
|
|
134
134
|
// Cleanup...
|
|
135
135
|
}, 10000);
|
|
136
136
|
```
|
|
@@ -187,11 +187,11 @@ The server automatically buffers audio chunks and flushes them when:
|
|
|
187
187
|
|
|
188
188
|
```typescript
|
|
189
189
|
// The server handles buffering automatically
|
|
190
|
-
sdk.streamAudioChunk(sessionId,
|
|
191
|
-
sdk.streamAudioChunk(sessionId,
|
|
190
|
+
sdk.streamAudioChunk(sessionId, base64Chunk1, false); // Buffered
|
|
191
|
+
sdk.streamAudioChunk(sessionId, base64Chunk2, false); // Buffered
|
|
192
192
|
// ... (8 more chunks buffered)
|
|
193
|
-
sdk.streamAudioChunk(sessionId,
|
|
194
|
-
sdk.streamAudioChunk(sessionId,
|
|
193
|
+
sdk.streamAudioChunk(sessionId, base64Chunk10, false); // Auto-flush triggered
|
|
194
|
+
sdk.streamAudioChunk(sessionId, base64Chunk11, true); // End flag triggers transcription
|
|
195
195
|
```
|
|
196
196
|
|
|
197
197
|
## Error Handling
|
|
@@ -253,6 +253,6 @@ for (const chunk of audioChunks) {
|
|
|
253
253
|
## Troubleshooting
|
|
254
254
|
|
|
255
255
|
- **Connection Issues**: Check network connectivity and authentication
|
|
256
|
-
- **Audio Not Processing**: Ensure chunks are valid
|
|
256
|
+
- **Audio Not Processing**: Ensure chunks are valid base64 strings
|
|
257
257
|
- **Transcription Not Starting**: Verify end flag is sent
|
|
258
258
|
- **Reconnection Problems**: Check server availability and token validity
|
|
@@ -17,7 +17,7 @@ export declare class WebSocketModule extends BaseClient {
|
|
|
17
17
|
/**
|
|
18
18
|
* Connect to WebSocket server
|
|
19
19
|
*/
|
|
20
|
-
connect(): void
|
|
20
|
+
connect(): Promise<void>;
|
|
21
21
|
/**
|
|
22
22
|
* Disconnect from WebSocket server
|
|
23
23
|
*/
|
|
@@ -33,11 +33,11 @@ export declare class WebSocketModule extends BaseClient {
|
|
|
33
33
|
/**
|
|
34
34
|
* Stream audio chunk to server
|
|
35
35
|
*/
|
|
36
|
-
streamAudioChunk(sessionId: string, audioChunk:
|
|
36
|
+
streamAudioChunk(sessionId: string, audioChunk: string, isEnd?: boolean): void;
|
|
37
37
|
/**
|
|
38
38
|
* Stream multiple audio chunks with automatic end detection
|
|
39
39
|
*/
|
|
40
|
-
streamAudioChunks(sessionId: string, audioChunks:
|
|
40
|
+
streamAudioChunks(sessionId: string, audioChunks: string[], options?: AudioStreamingOptions): Promise<void>;
|
|
41
41
|
/**
|
|
42
42
|
* Add event listener
|
|
43
43
|
*/
|
|
@@ -29,7 +29,9 @@ export class WebSocketModule extends BaseClient {
|
|
|
29
29
|
reconnectAttempts: 0
|
|
30
30
|
};
|
|
31
31
|
if (this.config.autoConnect) {
|
|
32
|
-
this.connect()
|
|
32
|
+
this.connect().catch(error => {
|
|
33
|
+
console.error('Failed to auto-connect WebSocket:', error);
|
|
34
|
+
});
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
@@ -37,23 +39,32 @@ export class WebSocketModule extends BaseClient {
|
|
|
37
39
|
*/
|
|
38
40
|
connect() {
|
|
39
41
|
var _a;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
if ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.connected) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const wsUrl = this.getWebSocketUrl();
|
|
47
|
+
try {
|
|
48
|
+
// Ensure we have a fresh token before connecting
|
|
49
|
+
yield this.getToken();
|
|
50
|
+
const token = this.getAccessToken();
|
|
51
|
+
if (!token) {
|
|
52
|
+
this.emit('connection-error', new Error('No access token available for WebSocket connection'));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.socket = io(wsUrl, {
|
|
56
|
+
auth: {
|
|
57
|
+
token: token
|
|
58
|
+
},
|
|
59
|
+
forceNew: true,
|
|
60
|
+
timeout: 10000
|
|
61
|
+
});
|
|
62
|
+
this.setupEventHandlers();
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
this.emit('connection-error', error);
|
|
66
|
+
}
|
|
55
67
|
});
|
|
56
|
-
this.setupEventHandlers();
|
|
57
68
|
}
|
|
58
69
|
/**
|
|
59
70
|
* Disconnect from WebSocket server
|
|
@@ -107,11 +118,10 @@ export class WebSocketModule extends BaseClient {
|
|
|
107
118
|
if (!((_a = this.socket) === null || _a === void 0 ? void 0 : _a.connected)) {
|
|
108
119
|
throw new Error('WebSocket not connected. Cannot stream audio chunks.');
|
|
109
120
|
}
|
|
110
|
-
const chunks = audioChunks;
|
|
111
121
|
const delay = options.bufferSize || 100; // ms between chunks
|
|
112
|
-
for (let i = 0; i <
|
|
113
|
-
const isLastChunk = i ===
|
|
114
|
-
this.streamAudioChunk(sessionId,
|
|
122
|
+
for (let i = 0; i < audioChunks.length; i++) {
|
|
123
|
+
const isLastChunk = i === audioChunks.length - 1;
|
|
124
|
+
this.streamAudioChunk(sessionId, audioChunks[i], isLastChunk);
|
|
115
125
|
// Small delay between chunks to prevent overwhelming the server
|
|
116
126
|
if (!isLastChunk) {
|
|
117
127
|
yield new Promise(resolve => setTimeout(resolve, delay));
|
|
@@ -231,9 +241,9 @@ export class WebSocketModule extends BaseClient {
|
|
|
231
241
|
// Calculate exponential backoff delay
|
|
232
242
|
const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
|
|
233
243
|
this.emit('reconnecting', this.reconnectAttempts);
|
|
234
|
-
this.reconnectTimeout = setTimeout(() => {
|
|
235
|
-
this.connect();
|
|
236
|
-
}, delay);
|
|
244
|
+
this.reconnectTimeout = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
245
|
+
yield this.connect();
|
|
246
|
+
}), delay);
|
|
237
247
|
}
|
|
238
248
|
/**
|
|
239
249
|
* Get WebSocket URL
|
package/dist/src/sdk/sdk.d.ts
CHANGED
|
@@ -183,7 +183,7 @@ export declare class ApiSDK extends BaseClient {
|
|
|
183
183
|
getExternalIntegration(...args: Parameters<ExternalIntegrationModule['getExternalIntegration']>): Promise<import("./types").ExternalIntegration>;
|
|
184
184
|
updateExternalIntegration(...args: Parameters<ExternalIntegrationModule['updateExternalIntegration']>): Promise<import("./types").ExternalIntegration>;
|
|
185
185
|
deleteExternalIntegration(...args: Parameters<ExternalIntegrationModule['deleteExternalIntegration']>): Promise<void>;
|
|
186
|
-
connectWebSocket(): void
|
|
186
|
+
connectWebSocket(): Promise<void>;
|
|
187
187
|
disconnectWebSocket(): void;
|
|
188
188
|
isWebSocketConnected(): boolean;
|
|
189
189
|
streamAudioChunk(...args: Parameters<WebSocketModule['streamAudioChunk']>): void;
|
|
@@ -72,21 +72,19 @@ async function exampleWebSocketAudioStreaming() {
|
|
|
72
72
|
|
|
73
73
|
console.log('WebSocket connected, starting audio streaming...');
|
|
74
74
|
|
|
75
|
-
// Simulate audio data (in a real app, this would come from
|
|
76
|
-
const
|
|
77
|
-
const chunks:
|
|
75
|
+
// Simulate base64 audio data (in a real app, this would come from your audio processing)
|
|
76
|
+
const simulateBase64AudioChunks = (): string[] => {
|
|
77
|
+
const chunks: string[] = [];
|
|
78
78
|
for (let i = 0; i < 15; i++) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
chunks.push(chunk);
|
|
79
|
+
// Simulate different sized base64 chunks (this would be your actual audio data)
|
|
80
|
+
const mockData = `mock-audio-chunk-${i}-${Date.now()}`;
|
|
81
|
+
const base64Chunk = btoa(mockData);
|
|
82
|
+
chunks.push(base64Chunk);
|
|
85
83
|
}
|
|
86
84
|
return chunks;
|
|
87
85
|
};
|
|
88
86
|
|
|
89
|
-
const audioChunks =
|
|
87
|
+
const audioChunks = simulateBase64AudioChunks();
|
|
90
88
|
|
|
91
89
|
// Stream audio chunks one by one
|
|
92
90
|
for (let i = 0; i < audioChunks.length; i++) {
|
|
@@ -159,11 +157,12 @@ async function exampleMicrophoneStreaming() {
|
|
|
159
157
|
const inputBuffer = event.inputBuffer;
|
|
160
158
|
const inputData = inputBuffer.getChannelData(0);
|
|
161
159
|
|
|
162
|
-
// Convert Float32Array to
|
|
163
|
-
|
|
160
|
+
// Convert Float32Array to base64 (you'll need your own conversion logic)
|
|
161
|
+
// This is just a placeholder - replace with your actual conversion
|
|
162
|
+
const mockBase64 = btoa(String.fromCharCode(...Array.from(inputData.slice(0, 100))));
|
|
164
163
|
|
|
165
|
-
// Stream the audio chunk
|
|
166
|
-
sdk.streamAudioChunk(session.id,
|
|
164
|
+
// Stream the base64 audio chunk
|
|
165
|
+
sdk.streamAudioChunk(session.id, mockBase64, false);
|
|
167
166
|
};
|
|
168
167
|
|
|
169
168
|
source.connect(processor);
|
|
@@ -173,8 +172,8 @@ async function exampleMicrophoneStreaming() {
|
|
|
173
172
|
|
|
174
173
|
// Stop after 10 seconds and trigger transcription
|
|
175
174
|
setTimeout(() => {
|
|
176
|
-
// Send empty
|
|
177
|
-
sdk.streamAudioChunk(session.id,
|
|
175
|
+
// Send empty base64 string with end flag to trigger transcription
|
|
176
|
+
sdk.streamAudioChunk(session.id, "", true);
|
|
178
177
|
|
|
179
178
|
// Clean up
|
|
180
179
|
processor.disconnect();
|
package/package.json
CHANGED
|
@@ -45,35 +45,44 @@ export class WebSocketModule extends BaseClient {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
if (this.config.autoConnect) {
|
|
48
|
-
this.connect()
|
|
48
|
+
this.connect().catch(error => {
|
|
49
|
+
console.error('Failed to auto-connect WebSocket:', error);
|
|
50
|
+
});
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
53
55
|
* Connect to WebSocket server
|
|
54
56
|
*/
|
|
55
|
-
public connect(): void {
|
|
57
|
+
public async connect(): Promise<void> {
|
|
56
58
|
if (this.socket?.connected) {
|
|
57
59
|
return;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
const wsUrl = this.getWebSocketUrl();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Ensure we have a fresh token before connecting
|
|
66
|
+
await this.getToken();
|
|
67
|
+
const token = this.getAccessToken();
|
|
68
|
+
|
|
69
|
+
if (!token) {
|
|
70
|
+
this.emit('connection-error', new Error('No access token available for WebSocket connection'));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
this.socket = io(wsUrl, {
|
|
75
|
+
auth: {
|
|
76
|
+
token: token
|
|
77
|
+
},
|
|
78
|
+
forceNew: true,
|
|
79
|
+
timeout: 10000
|
|
80
|
+
});
|
|
75
81
|
|
|
76
|
-
|
|
82
|
+
this.setupEventHandlers();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.emit('connection-error', error as Error);
|
|
85
|
+
}
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
/**
|
|
@@ -113,7 +122,7 @@ export class WebSocketModule extends BaseClient {
|
|
|
113
122
|
*/
|
|
114
123
|
public streamAudioChunk(
|
|
115
124
|
sessionId: string,
|
|
116
|
-
audioChunk:
|
|
125
|
+
audioChunk: string,
|
|
117
126
|
isEnd: boolean = false
|
|
118
127
|
): void {
|
|
119
128
|
if (!this.socket?.connected) {
|
|
@@ -134,20 +143,19 @@ export class WebSocketModule extends BaseClient {
|
|
|
134
143
|
*/
|
|
135
144
|
public async streamAudioChunks(
|
|
136
145
|
sessionId: string,
|
|
137
|
-
audioChunks:
|
|
146
|
+
audioChunks: string[],
|
|
138
147
|
options: AudioStreamingOptions = {}
|
|
139
148
|
): Promise<void> {
|
|
140
149
|
if (!this.socket?.connected) {
|
|
141
150
|
throw new Error('WebSocket not connected. Cannot stream audio chunks.');
|
|
142
151
|
}
|
|
143
152
|
|
|
144
|
-
const chunks = audioChunks;
|
|
145
153
|
const delay = options.bufferSize || 100; // ms between chunks
|
|
146
154
|
|
|
147
|
-
for (let i = 0; i <
|
|
148
|
-
const isLastChunk = i ===
|
|
155
|
+
for (let i = 0; i < audioChunks.length; i++) {
|
|
156
|
+
const isLastChunk = i === audioChunks.length - 1;
|
|
149
157
|
|
|
150
|
-
this.streamAudioChunk(sessionId,
|
|
158
|
+
this.streamAudioChunk(sessionId, audioChunks[i], isLastChunk);
|
|
151
159
|
|
|
152
160
|
// Small delay between chunks to prevent overwhelming the server
|
|
153
161
|
if (!isLastChunk) {
|
|
@@ -301,8 +309,8 @@ export class WebSocketModule extends BaseClient {
|
|
|
301
309
|
|
|
302
310
|
this.emit('reconnecting', this.reconnectAttempts);
|
|
303
311
|
|
|
304
|
-
this.reconnectTimeout = setTimeout(() => {
|
|
305
|
-
this.connect();
|
|
312
|
+
this.reconnectTimeout = setTimeout(async () => {
|
|
313
|
+
await this.connect();
|
|
306
314
|
}, delay);
|
|
307
315
|
}
|
|
308
316
|
|