@volley/recognition-client-sdk-node22 0.1.424

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.
Files changed (58) hide show
  1. package/README.md +344 -0
  2. package/dist/browser.bundled.d.ts +1280 -0
  3. package/dist/browser.d.ts +10 -0
  4. package/dist/browser.d.ts.map +1 -0
  5. package/dist/config-builder.d.ts +134 -0
  6. package/dist/config-builder.d.ts.map +1 -0
  7. package/dist/errors.d.ts +41 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/factory.d.ts +36 -0
  10. package/dist/factory.d.ts.map +1 -0
  11. package/dist/index.bundled.d.ts +2572 -0
  12. package/dist/index.d.ts +16 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +10199 -0
  15. package/dist/index.js.map +7 -0
  16. package/dist/recog-client-sdk.browser.d.ts +10 -0
  17. package/dist/recog-client-sdk.browser.d.ts.map +1 -0
  18. package/dist/recog-client-sdk.browser.js +5746 -0
  19. package/dist/recog-client-sdk.browser.js.map +7 -0
  20. package/dist/recognition-client.d.ts +128 -0
  21. package/dist/recognition-client.d.ts.map +1 -0
  22. package/dist/recognition-client.types.d.ts +271 -0
  23. package/dist/recognition-client.types.d.ts.map +1 -0
  24. package/dist/simplified-vgf-recognition-client.d.ts +178 -0
  25. package/dist/simplified-vgf-recognition-client.d.ts.map +1 -0
  26. package/dist/utils/audio-ring-buffer.d.ts +69 -0
  27. package/dist/utils/audio-ring-buffer.d.ts.map +1 -0
  28. package/dist/utils/message-handler.d.ts +45 -0
  29. package/dist/utils/message-handler.d.ts.map +1 -0
  30. package/dist/utils/url-builder.d.ts +28 -0
  31. package/dist/utils/url-builder.d.ts.map +1 -0
  32. package/dist/vgf-recognition-mapper.d.ts +66 -0
  33. package/dist/vgf-recognition-mapper.d.ts.map +1 -0
  34. package/dist/vgf-recognition-state.d.ts +91 -0
  35. package/dist/vgf-recognition-state.d.ts.map +1 -0
  36. package/package.json +74 -0
  37. package/src/browser.ts +24 -0
  38. package/src/config-builder.spec.ts +265 -0
  39. package/src/config-builder.ts +240 -0
  40. package/src/errors.ts +84 -0
  41. package/src/factory.spec.ts +215 -0
  42. package/src/factory.ts +47 -0
  43. package/src/index.ts +127 -0
  44. package/src/recognition-client.spec.ts +889 -0
  45. package/src/recognition-client.ts +844 -0
  46. package/src/recognition-client.types.ts +338 -0
  47. package/src/simplified-vgf-recognition-client.integration.spec.ts +718 -0
  48. package/src/simplified-vgf-recognition-client.spec.ts +1525 -0
  49. package/src/simplified-vgf-recognition-client.ts +524 -0
  50. package/src/utils/audio-ring-buffer.spec.ts +335 -0
  51. package/src/utils/audio-ring-buffer.ts +170 -0
  52. package/src/utils/message-handler.spec.ts +311 -0
  53. package/src/utils/message-handler.ts +131 -0
  54. package/src/utils/url-builder.spec.ts +252 -0
  55. package/src/utils/url-builder.ts +92 -0
  56. package/src/vgf-recognition-mapper.spec.ts +78 -0
  57. package/src/vgf-recognition-mapper.ts +232 -0
  58. package/src/vgf-recognition-state.ts +102 -0
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Audio Ring Buffer Tests
3
+ */
4
+
5
+ import { AudioRingBuffer } from './audio-ring-buffer.js';
6
+
7
+ describe('AudioRingBuffer', () => {
8
+ describe('constructor', () => {
9
+ it('should initialize with correct buffer size', () => {
10
+ const buffer = new AudioRingBuffer({
11
+ maxBufferDurationSec: 10,
12
+ chunksPerSecond: 100
13
+ });
14
+
15
+ expect(buffer.isEmpty()).toBe(true);
16
+ expect(buffer.getBufferedCount()).toBe(0);
17
+ expect(buffer.isOverflowing()).toBe(false);
18
+ });
19
+
20
+ it('should accept logger function', () => {
21
+ const logger = jest.fn();
22
+ const buffer = new AudioRingBuffer({
23
+ maxBufferDurationSec: 1,
24
+ chunksPerSecond: 10,
25
+ logger
26
+ });
27
+
28
+ expect(buffer).toBeDefined();
29
+ });
30
+ });
31
+
32
+ describe('write', () => {
33
+ it('should write ArrayBuffer data', () => {
34
+ const buffer = new AudioRingBuffer({
35
+ maxBufferDurationSec: 1,
36
+ chunksPerSecond: 10
37
+ });
38
+
39
+ const data = new ArrayBuffer(100);
40
+ buffer.write(data);
41
+
42
+ expect(buffer.isEmpty()).toBe(false);
43
+ expect(buffer.getBufferedCount()).toBe(1);
44
+ });
45
+
46
+ it('should write ArrayBufferView data', () => {
47
+ const buffer = new AudioRingBuffer({
48
+ maxBufferDurationSec: 1,
49
+ chunksPerSecond: 10
50
+ });
51
+
52
+ const data = new Uint8Array(100);
53
+ buffer.write(data);
54
+
55
+ expect(buffer.isEmpty()).toBe(false);
56
+ expect(buffer.getBufferedCount()).toBe(1);
57
+ });
58
+
59
+ it('should track total buffered bytes', () => {
60
+ const buffer = new AudioRingBuffer({
61
+ maxBufferDurationSec: 1,
62
+ chunksPerSecond: 10
63
+ });
64
+
65
+ buffer.write(new ArrayBuffer(100));
66
+ buffer.write(new ArrayBuffer(200));
67
+
68
+ const stats = buffer.getStats();
69
+ expect(stats.totalBufferedBytes).toBe(300);
70
+ expect(stats.chunksBuffered).toBe(2);
71
+ });
72
+
73
+ it('should handle multiple writes', () => {
74
+ const buffer = new AudioRingBuffer({
75
+ maxBufferDurationSec: 1,
76
+ chunksPerSecond: 100
77
+ });
78
+
79
+ for (let i = 0; i < 50; i++) {
80
+ buffer.write(new ArrayBuffer(10));
81
+ }
82
+
83
+ expect(buffer.getBufferedCount()).toBe(50);
84
+ expect(buffer.isOverflowing()).toBe(false);
85
+ });
86
+ });
87
+
88
+ describe('overflow detection', () => {
89
+ it('should detect overflow when buffer is full', () => {
90
+ const buffer = new AudioRingBuffer({
91
+ maxBufferDurationSec: 1,
92
+ chunksPerSecond: 5 // Small buffer of 5 chunks
93
+ });
94
+
95
+ // Write more than buffer can hold
96
+ for (let i = 0; i < 10; i++) {
97
+ buffer.write(new ArrayBuffer(10));
98
+ }
99
+
100
+ expect(buffer.isOverflowing()).toBe(true);
101
+ const stats = buffer.getStats();
102
+ expect(stats.hasWrapped).toBe(true);
103
+ expect(stats.overflowCount).toBeGreaterThan(0);
104
+ });
105
+
106
+ it('should call logger on overflow', () => {
107
+ const logger = jest.fn();
108
+ const buffer = new AudioRingBuffer({
109
+ maxBufferDurationSec: 1,
110
+ chunksPerSecond: 3, // Small buffer
111
+ logger
112
+ });
113
+
114
+ // Fill buffer to overflow
115
+ for (let i = 0; i < 10; i++) {
116
+ buffer.write(new ArrayBuffer(10));
117
+ }
118
+
119
+ // Logger should have been called with overflow message
120
+ expect(logger).toHaveBeenCalledWith(
121
+ 'debug',
122
+ expect.stringContaining('overflow'),
123
+ expect.any(Object)
124
+ );
125
+ });
126
+ });
127
+
128
+ describe('read', () => {
129
+ it('should return null when buffer is empty', () => {
130
+ const buffer = new AudioRingBuffer({
131
+ maxBufferDurationSec: 1,
132
+ chunksPerSecond: 10
133
+ });
134
+
135
+ expect(buffer.read()).toBeNull();
136
+ });
137
+
138
+ it('should return data in FIFO order', () => {
139
+ const buffer = new AudioRingBuffer({
140
+ maxBufferDurationSec: 1,
141
+ chunksPerSecond: 10
142
+ });
143
+
144
+ const data1 = new Uint8Array([1, 2, 3]);
145
+ const data2 = new Uint8Array([4, 5, 6]);
146
+
147
+ buffer.write(data1);
148
+ buffer.write(data2);
149
+
150
+ const chunk1 = buffer.read();
151
+ expect(chunk1).not.toBeNull();
152
+ expect(chunk1!.data).toBe(data1);
153
+
154
+ const chunk2 = buffer.read();
155
+ expect(chunk2).not.toBeNull();
156
+ expect(chunk2!.data).toBe(data2);
157
+ });
158
+
159
+ it('should decrement buffered count after read', () => {
160
+ const buffer = new AudioRingBuffer({
161
+ maxBufferDurationSec: 1,
162
+ chunksPerSecond: 10
163
+ });
164
+
165
+ buffer.write(new ArrayBuffer(10));
166
+ buffer.write(new ArrayBuffer(10));
167
+
168
+ expect(buffer.getBufferedCount()).toBe(2);
169
+
170
+ buffer.read();
171
+ expect(buffer.getBufferedCount()).toBe(1);
172
+
173
+ buffer.read();
174
+ expect(buffer.getBufferedCount()).toBe(0);
175
+ expect(buffer.isEmpty()).toBe(true);
176
+ });
177
+ });
178
+
179
+ describe('readAll', () => {
180
+ it('should return empty array when buffer is empty', () => {
181
+ const buffer = new AudioRingBuffer({
182
+ maxBufferDurationSec: 1,
183
+ chunksPerSecond: 10
184
+ });
185
+
186
+ expect(buffer.readAll()).toEqual([]);
187
+ });
188
+
189
+ it('should return all chunks without removing them', () => {
190
+ const buffer = new AudioRingBuffer({
191
+ maxBufferDurationSec: 1,
192
+ chunksPerSecond: 10
193
+ });
194
+
195
+ buffer.write(new ArrayBuffer(10));
196
+ buffer.write(new ArrayBuffer(20));
197
+ buffer.write(new ArrayBuffer(30));
198
+
199
+ const chunks = buffer.readAll();
200
+ expect(chunks).toHaveLength(3);
201
+
202
+ // Should still have all chunks
203
+ expect(buffer.getBufferedCount()).toBe(3);
204
+ });
205
+ });
206
+
207
+ describe('flush', () => {
208
+ it('should return all chunks and clear buffer', () => {
209
+ const buffer = new AudioRingBuffer({
210
+ maxBufferDurationSec: 1,
211
+ chunksPerSecond: 10
212
+ });
213
+
214
+ buffer.write(new ArrayBuffer(10));
215
+ buffer.write(new ArrayBuffer(20));
216
+
217
+ const chunks = buffer.flush();
218
+ expect(chunks).toHaveLength(2);
219
+ expect(buffer.isEmpty()).toBe(true);
220
+ expect(buffer.getBufferedCount()).toBe(0);
221
+ });
222
+ });
223
+
224
+ describe('clear', () => {
225
+ it('should reset all state', () => {
226
+ const buffer = new AudioRingBuffer({
227
+ maxBufferDurationSec: 1,
228
+ chunksPerSecond: 5
229
+ });
230
+
231
+ // Fill and overflow
232
+ for (let i = 0; i < 10; i++) {
233
+ buffer.write(new ArrayBuffer(100));
234
+ }
235
+
236
+ expect(buffer.isOverflowing()).toBe(true);
237
+
238
+ buffer.clear();
239
+
240
+ expect(buffer.isEmpty()).toBe(true);
241
+ expect(buffer.getBufferedCount()).toBe(0);
242
+ expect(buffer.isOverflowing()).toBe(false);
243
+
244
+ const stats = buffer.getStats();
245
+ expect(stats.chunksBuffered).toBe(0);
246
+ expect(stats.totalBufferedBytes).toBe(0);
247
+ expect(stats.overflowCount).toBe(0);
248
+ expect(stats.hasWrapped).toBe(false);
249
+ });
250
+
251
+ it('should call logger when clearing', () => {
252
+ const logger = jest.fn();
253
+ const buffer = new AudioRingBuffer({
254
+ maxBufferDurationSec: 1,
255
+ chunksPerSecond: 10,
256
+ logger
257
+ });
258
+
259
+ buffer.write(new ArrayBuffer(10));
260
+ buffer.clear();
261
+
262
+ expect(logger).toHaveBeenCalledWith(
263
+ 'debug',
264
+ expect.stringContaining('cleared')
265
+ );
266
+ });
267
+ });
268
+
269
+ describe('getStats', () => {
270
+ it('should return correct statistics', () => {
271
+ const buffer = new AudioRingBuffer({
272
+ maxBufferDurationSec: 1,
273
+ chunksPerSecond: 10
274
+ });
275
+
276
+ buffer.write(new ArrayBuffer(100));
277
+ buffer.write(new ArrayBuffer(200));
278
+
279
+ const stats = buffer.getStats();
280
+
281
+ expect(stats.chunksBuffered).toBe(2);
282
+ expect(stats.currentBufferedChunks).toBe(2);
283
+ expect(stats.totalBufferedBytes).toBe(300);
284
+ expect(stats.hasWrapped).toBe(false);
285
+ expect(stats.overflowCount).toBe(0);
286
+ });
287
+ });
288
+
289
+ describe('getBufferedCount with wraparound', () => {
290
+ it('should correctly count after partial read and more writes', () => {
291
+ const buffer = new AudioRingBuffer({
292
+ maxBufferDurationSec: 1,
293
+ chunksPerSecond: 10 // Buffer size of 10
294
+ });
295
+
296
+ // Write 5 chunks
297
+ for (let i = 0; i < 5; i++) {
298
+ buffer.write(new ArrayBuffer(10));
299
+ }
300
+ expect(buffer.getBufferedCount()).toBe(5);
301
+
302
+ // Read 3 chunks
303
+ buffer.read();
304
+ buffer.read();
305
+ buffer.read();
306
+ expect(buffer.getBufferedCount()).toBe(2);
307
+
308
+ // Write 4 more (wraps around in buffer)
309
+ for (let i = 0; i < 4; i++) {
310
+ buffer.write(new ArrayBuffer(10));
311
+ }
312
+
313
+ // Should have 2 + 4 = 6 chunks
314
+ expect(buffer.getBufferedCount()).toBe(6);
315
+ expect(buffer.isOverflowing()).toBe(false);
316
+ });
317
+
318
+ it('should handle buffer overflow correctly', () => {
319
+ const buffer = new AudioRingBuffer({
320
+ maxBufferDurationSec: 1,
321
+ chunksPerSecond: 3 // Buffer size of 3
322
+ });
323
+
324
+ // Write 5 chunks (more than buffer can hold)
325
+ for (let i = 0; i < 5; i++) {
326
+ buffer.write(new ArrayBuffer(10));
327
+ }
328
+
329
+ // Buffer should be full but not exceed capacity
330
+ // Ring buffer drops oldest when full
331
+ expect(buffer.getBufferedCount()).toBeLessThanOrEqual(3);
332
+ expect(buffer.isOverflowing()).toBe(true);
333
+ });
334
+ });
335
+ });
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Audio Ring Buffer
3
+ * Manages circular buffer for audio data with overflow detection
4
+ */
5
+
6
+ export interface BufferedAudio {
7
+ data: ArrayBuffer | ArrayBufferView;
8
+ timestamp: number;
9
+ }
10
+
11
+ export interface AudioRingBufferConfig {
12
+ maxBufferDurationSec: number;
13
+ chunksPerSecond: number;
14
+ logger?: (level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: any) => void;
15
+ }
16
+
17
+ export class AudioRingBuffer {
18
+ private buffer: BufferedAudio[] = [];
19
+ private bufferSize: number;
20
+ private writeIndex = 0;
21
+ private readIndex = 0;
22
+ private hasWrapped = false;
23
+ private totalBufferedBytes = 0;
24
+ private overflowCount = 0;
25
+ private chunksBuffered = 0;
26
+ private logger?: (level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: any) => void;
27
+
28
+ constructor(config: AudioRingBufferConfig) {
29
+ this.bufferSize = config.maxBufferDurationSec * config.chunksPerSecond;
30
+ this.buffer = new Array(this.bufferSize);
31
+ if (config.logger) {
32
+ this.logger = config.logger;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Write audio chunk to ring buffer with overflow detection
38
+ */
39
+ write(audioData: ArrayBuffer | ArrayBufferView): void {
40
+ const bytes = ArrayBuffer.isView(audioData) ? audioData.byteLength : audioData.byteLength;
41
+
42
+ // Write to current position
43
+ this.buffer[this.writeIndex] = {
44
+ data: audioData,
45
+ timestamp: Date.now()
46
+ };
47
+
48
+ // Advance write pointer
49
+ const nextWriteIndex = (this.writeIndex + 1) % this.bufferSize;
50
+
51
+ // Detect overflow: write caught up to read
52
+ if (nextWriteIndex === this.readIndex && this.writeIndex !== this.readIndex) {
53
+ this.hasWrapped = true;
54
+ this.overflowCount++;
55
+
56
+ // Log buffer overflow event
57
+ if (this.logger) {
58
+ this.logger('debug', '[RecogSDK] Buffer overflow detected', {
59
+ bufferSize: this.bufferSize,
60
+ totalOverflows: this.overflowCount,
61
+ droppedChunk: this.buffer[this.readIndex]?.timestamp
62
+ });
63
+ }
64
+
65
+ // Move read pointer forward to make room (drop oldest)
66
+ this.readIndex = (this.readIndex + 1) % this.bufferSize;
67
+ }
68
+
69
+ this.writeIndex = nextWriteIndex;
70
+ this.chunksBuffered++;
71
+ this.totalBufferedBytes += bytes;
72
+ }
73
+
74
+ /**
75
+ * Read next chunk from buffer
76
+ */
77
+ read(): BufferedAudio | null {
78
+ if (this.isEmpty()) {
79
+ return null;
80
+ }
81
+
82
+ const chunk = this.buffer[this.readIndex];
83
+ this.readIndex = (this.readIndex + 1) % this.bufferSize;
84
+ return chunk || null;
85
+ }
86
+
87
+ /**
88
+ * Read all buffered chunks without removing them
89
+ */
90
+ readAll(): BufferedAudio[] {
91
+ const chunks: BufferedAudio[] = [];
92
+ let index = this.readIndex;
93
+
94
+ while (index !== this.writeIndex) {
95
+ const chunk = this.buffer[index];
96
+ if (chunk) {
97
+ chunks.push(chunk);
98
+ }
99
+ index = (index + 1) % this.bufferSize;
100
+ }
101
+
102
+ return chunks;
103
+ }
104
+
105
+ /**
106
+ * Flush all buffered data and advance read pointer
107
+ */
108
+ flush(): BufferedAudio[] {
109
+ const chunks = this.readAll();
110
+ this.readIndex = this.writeIndex;
111
+ return chunks;
112
+ }
113
+
114
+ /**
115
+ * Get count of buffered chunks
116
+ */
117
+ getBufferedCount(): number {
118
+ if (this.writeIndex >= this.readIndex) {
119
+ return this.writeIndex - this.readIndex;
120
+ } else {
121
+ // Wrapped around
122
+ return this.bufferSize - this.readIndex + this.writeIndex;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Check if buffer is empty
128
+ */
129
+ isEmpty(): boolean {
130
+ return this.readIndex === this.writeIndex;
131
+ }
132
+
133
+ /**
134
+ * Check if buffer has overflowed
135
+ */
136
+ isOverflowing(): boolean {
137
+ return this.hasWrapped;
138
+ }
139
+
140
+ /**
141
+ * Clear the buffer and reset all counters
142
+ * Frees memory by releasing all stored audio chunks
143
+ */
144
+ clear(): void {
145
+ this.buffer = [];
146
+ this.writeIndex = 0;
147
+ this.readIndex = 0;
148
+ this.hasWrapped = false;
149
+ this.overflowCount = 0;
150
+ this.chunksBuffered = 0;
151
+ this.totalBufferedBytes = 0;
152
+
153
+ if (this.logger) {
154
+ this.logger('debug', '[RecogSDK] Audio buffer cleared');
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Get buffer statistics
160
+ */
161
+ getStats() {
162
+ return {
163
+ chunksBuffered: this.chunksBuffered,
164
+ currentBufferedChunks: this.getBufferedCount(),
165
+ overflowCount: this.overflowCount,
166
+ hasWrapped: this.hasWrapped,
167
+ totalBufferedBytes: this.totalBufferedBytes
168
+ };
169
+ }
170
+ }