@absolutejs/voice 0.0.22-beta.643 → 0.0.22-beta.644

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/index.js CHANGED
@@ -4574,6 +4574,8 @@ var createVoiceSession = (options) => {
4574
4574
  user: 0
4575
4575
  };
4576
4576
  const recordingFormats = {};
4577
+ const recordingStartedAt = Date.now();
4578
+ let assistantRecordingEndsAtMs = 0;
4577
4579
  let recordingPersisted = false;
4578
4580
  const captureRecordingChunk = (channel, bytes, format) => {
4579
4581
  if (!recordingConfig || recordingPersisted) {
@@ -4589,11 +4591,33 @@ var createVoiceSession = (options) => {
4589
4591
  if (currentTotal >= recordingMaxBytes) {
4590
4592
  return;
4591
4593
  }
4592
- const remaining = recordingMaxBytes - currentTotal;
4594
+ const bytesPerSample = 2;
4595
+ const bytesPerSecond = format.sampleRateHz * format.channels * bytesPerSample;
4596
+ let chunkStartMs = currentTotal / bytesPerSecond * 1000;
4597
+ if (channel === "assistant" || currentTotal === 0) {
4598
+ const wallClockOffsetMs = Math.max(0, Date.now() - recordingStartedAt);
4599
+ chunkStartMs = channel === "assistant" ? Math.max(wallClockOffsetMs, assistantRecordingEndsAtMs) : wallClockOffsetMs;
4600
+ const targetByteOffset = Math.floor(chunkStartMs * bytesPerSecond / 1000 / bytesPerSample) * bytesPerSample;
4601
+ const silenceByteLength = Math.min(Math.max(0, targetByteOffset - currentTotal), recordingMaxBytes - currentTotal);
4602
+ if (silenceByteLength > 0) {
4603
+ recordingBuffers[channel].push(new Uint8Array(silenceByteLength));
4604
+ recordingByteTotals[channel] += silenceByteLength;
4605
+ }
4606
+ if (channel === "assistant") {
4607
+ assistantRecordingEndsAtMs = chunkStartMs + bytes.byteLength / bytesPerSecond * 1000;
4608
+ }
4609
+ }
4610
+ const updatedTotal = recordingByteTotals[channel];
4611
+ if (updatedTotal >= recordingMaxBytes) {
4612
+ recordingFormats[channel] = format;
4613
+ return;
4614
+ }
4615
+ const remaining = recordingMaxBytes - updatedTotal;
4593
4616
  const slice = bytes.byteLength <= remaining ? bytes : bytes.subarray(0, remaining);
4594
4617
  recordingBuffers[channel].push(new Uint8Array(slice));
4595
4618
  recordingByteTotals[channel] += slice.byteLength;
4596
4619
  recordingFormats[channel] = format;
4620
+ return { byteLength: slice.byteLength, startMs: chunkStartMs };
4597
4621
  };
4598
4622
  const pruneTurnAudio = () => {
4599
4623
  const replayWindowMs = sttFallback?.replayWindowMs ?? DEFAULT_FALLBACK_REPLAY_MS;
@@ -4921,7 +4945,7 @@ var createVoiceSession = (options) => {
4921
4945
  };
4922
4946
  const sendAssistantAudio = async (chunk, input) => {
4923
4947
  const normalizedChunk = chunk instanceof Uint8Array ? new Uint8Array(chunk) : chunk instanceof ArrayBuffer ? new Uint8Array(chunk.slice(0)) : new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
4924
- captureRecordingChunk("assistant", normalizedChunk, input.format);
4948
+ const recordingChunk = captureRecordingChunk("assistant", normalizedChunk, input.format);
4925
4949
  kickCallSilenceWatchdog();
4926
4950
  await send({
4927
4951
  chunkBase64: encodeBase64(normalizedChunk),
@@ -4940,6 +4964,10 @@ var createVoiceSession = (options) => {
4940
4964
  if (activeTTSTurnId) {
4941
4965
  await appendTurnLatencyStage({
4942
4966
  at: input.receivedAt,
4967
+ metadata: recordingChunk ? {
4968
+ recordingByteLength: recordingChunk.byteLength,
4969
+ recordingStartMs: recordingChunk.startMs
4970
+ } : undefined,
4943
4971
  stage: "assistant_audio_received",
4944
4972
  turnId: activeTTSTurnId
4945
4973
  });
@@ -7211,6 +7211,8 @@ var createVoiceSession = (options) => {
7211
7211
  user: 0
7212
7212
  };
7213
7213
  const recordingFormats = {};
7214
+ const recordingStartedAt = Date.now();
7215
+ let assistantRecordingEndsAtMs = 0;
7214
7216
  let recordingPersisted = false;
7215
7217
  const captureRecordingChunk = (channel, bytes2, format) => {
7216
7218
  if (!recordingConfig || recordingPersisted) {
@@ -7226,11 +7228,33 @@ var createVoiceSession = (options) => {
7226
7228
  if (currentTotal >= recordingMaxBytes) {
7227
7229
  return;
7228
7230
  }
7229
- const remaining = recordingMaxBytes - currentTotal;
7231
+ const bytesPerSample = 2;
7232
+ const bytesPerSecond = format.sampleRateHz * format.channels * bytesPerSample;
7233
+ let chunkStartMs = currentTotal / bytesPerSecond * 1000;
7234
+ if (channel === "assistant" || currentTotal === 0) {
7235
+ const wallClockOffsetMs = Math.max(0, Date.now() - recordingStartedAt);
7236
+ chunkStartMs = channel === "assistant" ? Math.max(wallClockOffsetMs, assistantRecordingEndsAtMs) : wallClockOffsetMs;
7237
+ const targetByteOffset = Math.floor(chunkStartMs * bytesPerSecond / 1000 / bytesPerSample) * bytesPerSample;
7238
+ const silenceByteLength = Math.min(Math.max(0, targetByteOffset - currentTotal), recordingMaxBytes - currentTotal);
7239
+ if (silenceByteLength > 0) {
7240
+ recordingBuffers[channel].push(new Uint8Array(silenceByteLength));
7241
+ recordingByteTotals[channel] += silenceByteLength;
7242
+ }
7243
+ if (channel === "assistant") {
7244
+ assistantRecordingEndsAtMs = chunkStartMs + bytes2.byteLength / bytesPerSecond * 1000;
7245
+ }
7246
+ }
7247
+ const updatedTotal = recordingByteTotals[channel];
7248
+ if (updatedTotal >= recordingMaxBytes) {
7249
+ recordingFormats[channel] = format;
7250
+ return;
7251
+ }
7252
+ const remaining = recordingMaxBytes - updatedTotal;
7230
7253
  const slice = bytes2.byteLength <= remaining ? bytes2 : bytes2.subarray(0, remaining);
7231
7254
  recordingBuffers[channel].push(new Uint8Array(slice));
7232
7255
  recordingByteTotals[channel] += slice.byteLength;
7233
7256
  recordingFormats[channel] = format;
7257
+ return { byteLength: slice.byteLength, startMs: chunkStartMs };
7234
7258
  };
7235
7259
  const pruneTurnAudio = () => {
7236
7260
  const replayWindowMs = sttFallback?.replayWindowMs ?? DEFAULT_FALLBACK_REPLAY_MS;
@@ -7558,7 +7582,7 @@ var createVoiceSession = (options) => {
7558
7582
  };
7559
7583
  const sendAssistantAudio = async (chunk, input) => {
7560
7584
  const normalizedChunk = chunk instanceof Uint8Array ? new Uint8Array(chunk) : chunk instanceof ArrayBuffer ? new Uint8Array(chunk.slice(0)) : new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
7561
- captureRecordingChunk("assistant", normalizedChunk, input.format);
7585
+ const recordingChunk = captureRecordingChunk("assistant", normalizedChunk, input.format);
7562
7586
  kickCallSilenceWatchdog();
7563
7587
  await send({
7564
7588
  chunkBase64: encodeBase64(normalizedChunk),
@@ -7577,6 +7601,10 @@ var createVoiceSession = (options) => {
7577
7601
  if (activeTTSTurnId) {
7578
7602
  await appendTurnLatencyStage({
7579
7603
  at: input.receivedAt,
7604
+ metadata: recordingChunk ? {
7605
+ recordingByteLength: recordingChunk.byteLength,
7606
+ recordingStartMs: recordingChunk.startMs
7607
+ } : undefined,
7580
7608
  stage: "assistant_audio_received",
7581
7609
  turnId: activeTTSTurnId
7582
7610
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.643",
3
+ "version": "0.0.22-beta.644",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",