@absolutejs/voice 0.0.22-beta.673 → 0.0.22-beta.674

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.
@@ -2135,6 +2135,49 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2135
2135
  let maxGapMs = 0;
2136
2136
  let errorCount = 0;
2137
2137
  let hasScheduledAnyChunk = false;
2138
+ let buffersScheduled = 0;
2139
+ let lastScheduledTurnId;
2140
+ const audioFormats = new Set;
2141
+ let lastAudioFormat = null;
2142
+ let formatChangeCount = 0;
2143
+ let incompleteFrameChunkCount = 0;
2144
+ let discardedPartialFrameBytes = 0;
2145
+ let pendingFrameBytes = new Uint8Array;
2146
+ let pendingFrameFormat = null;
2147
+ let pendingFrameTurnId;
2148
+ const formatKey = (format) => `${format.container}/${format.encoding}/${String(format.channels)}ch/${String(format.sampleRateHz)}hz`;
2149
+ const frameAlignedChunk = (chunk) => {
2150
+ const format = formatKey(chunk.format);
2151
+ audioFormats.add(format);
2152
+ if (lastAudioFormat !== null && lastAudioFormat !== format) {
2153
+ formatChangeCount += 1;
2154
+ }
2155
+ lastAudioFormat = format;
2156
+ if (pendingFrameBytes.byteLength > 0 && (pendingFrameFormat !== format || pendingFrameTurnId !== chunk.turnId)) {
2157
+ discardedPartialFrameBytes += pendingFrameBytes.byteLength;
2158
+ pendingFrameBytes = new Uint8Array;
2159
+ }
2160
+ const joined = pendingFrameBytes.byteLength === 0 ? chunk.chunk : (() => {
2161
+ const bytes = new Uint8Array(pendingFrameBytes.byteLength + chunk.chunk.byteLength);
2162
+ bytes.set(pendingFrameBytes);
2163
+ bytes.set(chunk.chunk, pendingFrameBytes.byteLength);
2164
+ return bytes;
2165
+ })();
2166
+ const frameBytes = Math.max(1, chunk.format.channels) * 2;
2167
+ const completeBytes = joined.byteLength - joined.byteLength % frameBytes;
2168
+ const remainderBytes = joined.byteLength - completeBytes;
2169
+ if (remainderBytes > 0) {
2170
+ incompleteFrameChunkCount += 1;
2171
+ pendingFrameBytes = joined.slice(completeBytes);
2172
+ pendingFrameFormat = format;
2173
+ pendingFrameTurnId = chunk.turnId;
2174
+ } else {
2175
+ pendingFrameBytes = new Uint8Array;
2176
+ pendingFrameFormat = null;
2177
+ pendingFrameTurnId = undefined;
2178
+ }
2179
+ return completeBytes > 0 ? { ...chunk, chunk: joined.subarray(0, completeBytes) } : null;
2180
+ };
2138
2181
  const sampleConcurrency = () => {
2139
2182
  if (activeAudioPlayers.size > observedPeakConcurrency) {
2140
2183
  observedPeakConcurrency = activeAudioPlayers.size;
@@ -2254,7 +2297,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2254
2297
  queueEndTime = audioContext.currentTime;
2255
2298
  return audioContext;
2256
2299
  };
2257
- const scheduleBuffer = (context, buffer, rate) => {
2300
+ const scheduleBuffer = (context, buffer, rate, turnId) => {
2258
2301
  const node = context.createBufferSource();
2259
2302
  node.buffer = buffer;
2260
2303
  if (node.playbackRate) {
@@ -2272,13 +2315,15 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2272
2315
  };
2273
2316
  const earliestStart = context.currentTime + lookaheadSeconds;
2274
2317
  const startAt = Math.max(earliestStart, queueEndTime);
2275
- if (hasScheduledAnyChunk && earliestStart > queueEndTime + GAP_EPSILON_S) {
2318
+ if (hasScheduledAnyChunk && turnId !== undefined && turnId === lastScheduledTurnId && earliestStart > queueEndTime + GAP_EPSILON_S) {
2276
2319
  const gapMs = (earliestStart - queueEndTime) * 1000;
2277
2320
  gapCount += 1;
2278
2321
  totalGapMs += gapMs;
2279
2322
  maxGapMs = Math.max(maxGapMs, gapMs);
2280
2323
  }
2281
2324
  hasScheduledAnyChunk = true;
2325
+ lastScheduledTurnId = turnId;
2326
+ buffersScheduled += 1;
2282
2327
  scheduledDurationMs += buffer.duration / rate * 1000;
2283
2328
  sampleConcurrency();
2284
2329
  queueEndTime = startAt + buffer.duration / rate;
@@ -2291,10 +2336,13 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2291
2336
  };
2292
2337
  const scheduleChunk = async (chunk) => {
2293
2338
  const context = await ensureAudioContext();
2294
- const buffer = decodePCM16LEChunk(context, chunk);
2339
+ const alignedChunk = frameAlignedChunk(chunk);
2340
+ if (!alignedChunk)
2341
+ return;
2342
+ const buffer = decodePCM16LEChunk(context, alignedChunk);
2295
2343
  if (Math.abs(playbackRate - 1) <= STRETCH_BYPASS_EPSILON) {
2296
2344
  stretcher?.reset();
2297
- scheduleBuffer(context, buffer, playbackRate);
2345
+ scheduleBuffer(context, buffer, playbackRate, chunk.turnId);
2298
2346
  return;
2299
2347
  }
2300
2348
  const channels = Math.max(1, chunk.format.channels);
@@ -2315,13 +2363,14 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2315
2363
  continue;
2316
2364
  outBuffer.getChannelData(channelIndex).set(channelOut);
2317
2365
  }
2318
- scheduleBuffer(context, outBuffer, 1);
2366
+ scheduleBuffer(context, outBuffer, 1, chunk.turnId);
2319
2367
  };
2320
2368
  const stopQueuedPlayback = (options2) => {
2321
2369
  for (const node of [...sourceNodes]) {
2322
2370
  node.stop?.();
2323
2371
  }
2324
2372
  queueEndTime = audioContext ? audioContext.currentTime : 0;
2373
+ lastScheduledTurnId = undefined;
2325
2374
  if (options2?.forceClear) {
2326
2375
  for (const node of sourceNodes) {
2327
2376
  node.disconnect?.();
@@ -2416,13 +2465,19 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2416
2465
  const chunksReceived = source.assistantAudio.length;
2417
2466
  const chunksScheduled = state.processedChunkCount;
2418
2467
  return {
2468
+ audioFormatCount: audioFormats.size,
2469
+ buffersScheduled,
2419
2470
  chunksReceived,
2420
2471
  chunksScheduled,
2472
+ discardedPartialFrameBytes,
2421
2473
  errorCount,
2474
+ formatChangeCount,
2422
2475
  gapCount,
2476
+ incompleteFrameChunkCount,
2423
2477
  maxConcurrentPlayers: observedPeakConcurrency,
2424
2478
  maxGapMs: Math.round(maxGapMs),
2425
- ok: observedPeakConcurrency <= 1 && errorCount === 0 && chunksScheduled >= chunksReceived,
2479
+ pendingPartialFrameBytes: pendingFrameBytes.byteLength,
2480
+ ok: observedPeakConcurrency <= 1 && errorCount === 0 && chunksScheduled >= chunksReceived && discardedPartialFrameBytes === 0 && pendingFrameBytes.byteLength === 0,
2426
2481
  scheduledDurationMs: Math.round(scheduledDurationMs),
2427
2482
  totalGapMs: Math.round(totalGapMs)
2428
2483
  };
@@ -886,6 +886,49 @@ var createVoiceAudioPlayer = (source, options = {}) => {
886
886
  let maxGapMs = 0;
887
887
  let errorCount = 0;
888
888
  let hasScheduledAnyChunk = false;
889
+ let buffersScheduled = 0;
890
+ let lastScheduledTurnId;
891
+ const audioFormats = new Set;
892
+ let lastAudioFormat = null;
893
+ let formatChangeCount = 0;
894
+ let incompleteFrameChunkCount = 0;
895
+ let discardedPartialFrameBytes = 0;
896
+ let pendingFrameBytes = new Uint8Array;
897
+ let pendingFrameFormat = null;
898
+ let pendingFrameTurnId;
899
+ const formatKey = (format) => `${format.container}/${format.encoding}/${String(format.channels)}ch/${String(format.sampleRateHz)}hz`;
900
+ const frameAlignedChunk = (chunk) => {
901
+ const format = formatKey(chunk.format);
902
+ audioFormats.add(format);
903
+ if (lastAudioFormat !== null && lastAudioFormat !== format) {
904
+ formatChangeCount += 1;
905
+ }
906
+ lastAudioFormat = format;
907
+ if (pendingFrameBytes.byteLength > 0 && (pendingFrameFormat !== format || pendingFrameTurnId !== chunk.turnId)) {
908
+ discardedPartialFrameBytes += pendingFrameBytes.byteLength;
909
+ pendingFrameBytes = new Uint8Array;
910
+ }
911
+ const joined = pendingFrameBytes.byteLength === 0 ? chunk.chunk : (() => {
912
+ const bytes = new Uint8Array(pendingFrameBytes.byteLength + chunk.chunk.byteLength);
913
+ bytes.set(pendingFrameBytes);
914
+ bytes.set(chunk.chunk, pendingFrameBytes.byteLength);
915
+ return bytes;
916
+ })();
917
+ const frameBytes = Math.max(1, chunk.format.channels) * 2;
918
+ const completeBytes = joined.byteLength - joined.byteLength % frameBytes;
919
+ const remainderBytes = joined.byteLength - completeBytes;
920
+ if (remainderBytes > 0) {
921
+ incompleteFrameChunkCount += 1;
922
+ pendingFrameBytes = joined.slice(completeBytes);
923
+ pendingFrameFormat = format;
924
+ pendingFrameTurnId = chunk.turnId;
925
+ } else {
926
+ pendingFrameBytes = new Uint8Array;
927
+ pendingFrameFormat = null;
928
+ pendingFrameTurnId = undefined;
929
+ }
930
+ return completeBytes > 0 ? { ...chunk, chunk: joined.subarray(0, completeBytes) } : null;
931
+ };
889
932
  const sampleConcurrency = () => {
890
933
  if (activeAudioPlayers.size > observedPeakConcurrency) {
891
934
  observedPeakConcurrency = activeAudioPlayers.size;
@@ -1005,7 +1048,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
1005
1048
  queueEndTime = audioContext.currentTime;
1006
1049
  return audioContext;
1007
1050
  };
1008
- const scheduleBuffer = (context, buffer, rate) => {
1051
+ const scheduleBuffer = (context, buffer, rate, turnId) => {
1009
1052
  const node = context.createBufferSource();
1010
1053
  node.buffer = buffer;
1011
1054
  if (node.playbackRate) {
@@ -1023,13 +1066,15 @@ var createVoiceAudioPlayer = (source, options = {}) => {
1023
1066
  };
1024
1067
  const earliestStart = context.currentTime + lookaheadSeconds;
1025
1068
  const startAt = Math.max(earliestStart, queueEndTime);
1026
- if (hasScheduledAnyChunk && earliestStart > queueEndTime + GAP_EPSILON_S) {
1069
+ if (hasScheduledAnyChunk && turnId !== undefined && turnId === lastScheduledTurnId && earliestStart > queueEndTime + GAP_EPSILON_S) {
1027
1070
  const gapMs = (earliestStart - queueEndTime) * 1000;
1028
1071
  gapCount += 1;
1029
1072
  totalGapMs += gapMs;
1030
1073
  maxGapMs = Math.max(maxGapMs, gapMs);
1031
1074
  }
1032
1075
  hasScheduledAnyChunk = true;
1076
+ lastScheduledTurnId = turnId;
1077
+ buffersScheduled += 1;
1033
1078
  scheduledDurationMs += buffer.duration / rate * 1000;
1034
1079
  sampleConcurrency();
1035
1080
  queueEndTime = startAt + buffer.duration / rate;
@@ -1042,10 +1087,13 @@ var createVoiceAudioPlayer = (source, options = {}) => {
1042
1087
  };
1043
1088
  const scheduleChunk = async (chunk) => {
1044
1089
  const context = await ensureAudioContext();
1045
- const buffer = decodePCM16LEChunk(context, chunk);
1090
+ const alignedChunk = frameAlignedChunk(chunk);
1091
+ if (!alignedChunk)
1092
+ return;
1093
+ const buffer = decodePCM16LEChunk(context, alignedChunk);
1046
1094
  if (Math.abs(playbackRate - 1) <= STRETCH_BYPASS_EPSILON) {
1047
1095
  stretcher?.reset();
1048
- scheduleBuffer(context, buffer, playbackRate);
1096
+ scheduleBuffer(context, buffer, playbackRate, chunk.turnId);
1049
1097
  return;
1050
1098
  }
1051
1099
  const channels = Math.max(1, chunk.format.channels);
@@ -1066,13 +1114,14 @@ var createVoiceAudioPlayer = (source, options = {}) => {
1066
1114
  continue;
1067
1115
  outBuffer.getChannelData(channelIndex).set(channelOut);
1068
1116
  }
1069
- scheduleBuffer(context, outBuffer, 1);
1117
+ scheduleBuffer(context, outBuffer, 1, chunk.turnId);
1070
1118
  };
1071
1119
  const stopQueuedPlayback = (options2) => {
1072
1120
  for (const node of [...sourceNodes]) {
1073
1121
  node.stop?.();
1074
1122
  }
1075
1123
  queueEndTime = audioContext ? audioContext.currentTime : 0;
1124
+ lastScheduledTurnId = undefined;
1076
1125
  if (options2?.forceClear) {
1077
1126
  for (const node of sourceNodes) {
1078
1127
  node.disconnect?.();
@@ -1167,13 +1216,19 @@ var createVoiceAudioPlayer = (source, options = {}) => {
1167
1216
  const chunksReceived = source.assistantAudio.length;
1168
1217
  const chunksScheduled = state.processedChunkCount;
1169
1218
  return {
1219
+ audioFormatCount: audioFormats.size,
1220
+ buffersScheduled,
1170
1221
  chunksReceived,
1171
1222
  chunksScheduled,
1223
+ discardedPartialFrameBytes,
1172
1224
  errorCount,
1225
+ formatChangeCount,
1173
1226
  gapCount,
1227
+ incompleteFrameChunkCount,
1174
1228
  maxConcurrentPlayers: observedPeakConcurrency,
1175
1229
  maxGapMs: Math.round(maxGapMs),
1176
- ok: observedPeakConcurrency <= 1 && errorCount === 0 && chunksScheduled >= chunksReceived,
1230
+ pendingPartialFrameBytes: pendingFrameBytes.byteLength,
1231
+ ok: observedPeakConcurrency <= 1 && errorCount === 0 && chunksScheduled >= chunksReceived && discardedPartialFrameBytes === 0 && pendingFrameBytes.byteLength === 0,
1177
1232
  scheduledDurationMs: Math.round(scheduledDurationMs),
1178
1233
  totalGapMs: Math.round(totalGapMs)
1179
1234
  };
@@ -1624,17 +1624,23 @@ export type VoiceAudioPlayerSource = {
1624
1624
  assistantAudio: VoiceStreamState["assistantAudio"];
1625
1625
  subscribe: (subscriber: () => void) => () => void;
1626
1626
  };
1627
- /** Post-call playback integrity, for verifying the assistant audio actually
1628
- * played cleanly (no overlap, no dropped/stalled packets) — emit it as a trace
1629
- * so a garbled call is provable, not guessed. `ok` is the all-clear roll-up. */
1627
+ /** Post-call playback-pipeline integrity. This proves what the browser player
1628
+ * received and scheduled; it does not claim that the resulting waveform
1629
+ * sounded clean. A browser-side replay is required for that stronger claim. */
1630
1630
  export type VoiceAudioIntegrity = {
1631
1631
  ok: boolean;
1632
1632
  chunksReceived: number;
1633
1633
  chunksScheduled: number;
1634
+ buffersScheduled: number;
1634
1635
  scheduledDurationMs: number;
1635
1636
  gapCount: number;
1636
1637
  totalGapMs: number;
1637
1638
  maxGapMs: number;
1639
+ audioFormatCount: number;
1640
+ formatChangeCount: number;
1641
+ incompleteFrameChunkCount: number;
1642
+ discardedPartialFrameBytes: number;
1643
+ pendingPartialFrameBytes: number;
1638
1644
  maxConcurrentPlayers: number;
1639
1645
  errorCount: number;
1640
1646
  };
@@ -2228,6 +2228,49 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2228
2228
  let maxGapMs = 0;
2229
2229
  let errorCount = 0;
2230
2230
  let hasScheduledAnyChunk = false;
2231
+ let buffersScheduled = 0;
2232
+ let lastScheduledTurnId;
2233
+ const audioFormats = new Set;
2234
+ let lastAudioFormat = null;
2235
+ let formatChangeCount = 0;
2236
+ let incompleteFrameChunkCount = 0;
2237
+ let discardedPartialFrameBytes = 0;
2238
+ let pendingFrameBytes = new Uint8Array;
2239
+ let pendingFrameFormat = null;
2240
+ let pendingFrameTurnId;
2241
+ const formatKey = (format) => `${format.container}/${format.encoding}/${String(format.channels)}ch/${String(format.sampleRateHz)}hz`;
2242
+ const frameAlignedChunk = (chunk) => {
2243
+ const format = formatKey(chunk.format);
2244
+ audioFormats.add(format);
2245
+ if (lastAudioFormat !== null && lastAudioFormat !== format) {
2246
+ formatChangeCount += 1;
2247
+ }
2248
+ lastAudioFormat = format;
2249
+ if (pendingFrameBytes.byteLength > 0 && (pendingFrameFormat !== format || pendingFrameTurnId !== chunk.turnId)) {
2250
+ discardedPartialFrameBytes += pendingFrameBytes.byteLength;
2251
+ pendingFrameBytes = new Uint8Array;
2252
+ }
2253
+ const joined = pendingFrameBytes.byteLength === 0 ? chunk.chunk : (() => {
2254
+ const bytes2 = new Uint8Array(pendingFrameBytes.byteLength + chunk.chunk.byteLength);
2255
+ bytes2.set(pendingFrameBytes);
2256
+ bytes2.set(chunk.chunk, pendingFrameBytes.byteLength);
2257
+ return bytes2;
2258
+ })();
2259
+ const frameBytes = Math.max(1, chunk.format.channels) * 2;
2260
+ const completeBytes = joined.byteLength - joined.byteLength % frameBytes;
2261
+ const remainderBytes = joined.byteLength - completeBytes;
2262
+ if (remainderBytes > 0) {
2263
+ incompleteFrameChunkCount += 1;
2264
+ pendingFrameBytes = joined.slice(completeBytes);
2265
+ pendingFrameFormat = format;
2266
+ pendingFrameTurnId = chunk.turnId;
2267
+ } else {
2268
+ pendingFrameBytes = new Uint8Array;
2269
+ pendingFrameFormat = null;
2270
+ pendingFrameTurnId = undefined;
2271
+ }
2272
+ return completeBytes > 0 ? { ...chunk, chunk: joined.subarray(0, completeBytes) } : null;
2273
+ };
2231
2274
  const sampleConcurrency = () => {
2232
2275
  if (activeAudioPlayers.size > observedPeakConcurrency) {
2233
2276
  observedPeakConcurrency = activeAudioPlayers.size;
@@ -2347,7 +2390,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2347
2390
  queueEndTime = audioContext.currentTime;
2348
2391
  return audioContext;
2349
2392
  };
2350
- const scheduleBuffer = (context, buffer, rate) => {
2393
+ const scheduleBuffer = (context, buffer, rate, turnId) => {
2351
2394
  const node = context.createBufferSource();
2352
2395
  node.buffer = buffer;
2353
2396
  if (node.playbackRate) {
@@ -2365,13 +2408,15 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2365
2408
  };
2366
2409
  const earliestStart = context.currentTime + lookaheadSeconds;
2367
2410
  const startAt = Math.max(earliestStart, queueEndTime);
2368
- if (hasScheduledAnyChunk && earliestStart > queueEndTime + GAP_EPSILON_S) {
2411
+ if (hasScheduledAnyChunk && turnId !== undefined && turnId === lastScheduledTurnId && earliestStart > queueEndTime + GAP_EPSILON_S) {
2369
2412
  const gapMs = (earliestStart - queueEndTime) * 1000;
2370
2413
  gapCount += 1;
2371
2414
  totalGapMs += gapMs;
2372
2415
  maxGapMs = Math.max(maxGapMs, gapMs);
2373
2416
  }
2374
2417
  hasScheduledAnyChunk = true;
2418
+ lastScheduledTurnId = turnId;
2419
+ buffersScheduled += 1;
2375
2420
  scheduledDurationMs += buffer.duration / rate * 1000;
2376
2421
  sampleConcurrency();
2377
2422
  queueEndTime = startAt + buffer.duration / rate;
@@ -2384,10 +2429,13 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2384
2429
  };
2385
2430
  const scheduleChunk = async (chunk) => {
2386
2431
  const context = await ensureAudioContext();
2387
- const buffer = decodePCM16LEChunk(context, chunk);
2432
+ const alignedChunk = frameAlignedChunk(chunk);
2433
+ if (!alignedChunk)
2434
+ return;
2435
+ const buffer = decodePCM16LEChunk(context, alignedChunk);
2388
2436
  if (Math.abs(playbackRate - 1) <= STRETCH_BYPASS_EPSILON) {
2389
2437
  stretcher?.reset();
2390
- scheduleBuffer(context, buffer, playbackRate);
2438
+ scheduleBuffer(context, buffer, playbackRate, chunk.turnId);
2391
2439
  return;
2392
2440
  }
2393
2441
  const channels = Math.max(1, chunk.format.channels);
@@ -2408,13 +2456,14 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2408
2456
  continue;
2409
2457
  outBuffer.getChannelData(channelIndex).set(channelOut);
2410
2458
  }
2411
- scheduleBuffer(context, outBuffer, 1);
2459
+ scheduleBuffer(context, outBuffer, 1, chunk.turnId);
2412
2460
  };
2413
2461
  const stopQueuedPlayback = (options2) => {
2414
2462
  for (const node of [...sourceNodes]) {
2415
2463
  node.stop?.();
2416
2464
  }
2417
2465
  queueEndTime = audioContext ? audioContext.currentTime : 0;
2466
+ lastScheduledTurnId = undefined;
2418
2467
  if (options2?.forceClear) {
2419
2468
  for (const node of sourceNodes) {
2420
2469
  node.disconnect?.();
@@ -2509,13 +2558,19 @@ var createVoiceAudioPlayer = (source, options = {}) => {
2509
2558
  const chunksReceived = source.assistantAudio.length;
2510
2559
  const chunksScheduled = state.processedChunkCount;
2511
2560
  return {
2561
+ audioFormatCount: audioFormats.size,
2562
+ buffersScheduled,
2512
2563
  chunksReceived,
2513
2564
  chunksScheduled,
2565
+ discardedPartialFrameBytes,
2514
2566
  errorCount,
2567
+ formatChangeCount,
2515
2568
  gapCount,
2569
+ incompleteFrameChunkCount,
2516
2570
  maxConcurrentPlayers: observedPeakConcurrency,
2517
2571
  maxGapMs: Math.round(maxGapMs),
2518
- ok: observedPeakConcurrency <= 1 && errorCount === 0 && chunksScheduled >= chunksReceived,
2572
+ pendingPartialFrameBytes: pendingFrameBytes.byteLength,
2573
+ ok: observedPeakConcurrency <= 1 && errorCount === 0 && chunksScheduled >= chunksReceived && discardedPartialFrameBytes === 0 && pendingFrameBytes.byteLength === 0,
2519
2574
  scheduledDurationMs: Math.round(scheduledDurationMs),
2520
2575
  totalGapMs: Math.round(totalGapMs)
2521
2576
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.673",
3
+ "version": "0.0.22-beta.674",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",