realtime-avatar 0.27.1 → 0.27.3

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/react.js CHANGED
@@ -2249,13 +2249,17 @@ var AVATAR_VIDEO_BADGE = {
2249
2249
  var AVATAR_VIDEO_BADGE_DOT = { width: 6, height: 6, borderRadius: 9999, background: "#34d399" };
2250
2250
  var AVATAR_VIDEO_OVERLAY = { position: "absolute", inset: 0, zIndex: 40 };
2251
2251
  var RECORDED_MEDIA_MODES = ["audio", "video", "audio_video"];
2252
- var recordingModeSchema = z.enum(["off", ...RECORDED_MEDIA_MODES, "participants"]).describe("Server-owned recording policy; omitted means off. Legacy audio includes user and avatar audio. participants saves one continuous audio/video file per participant, without mixing their voices.");
2252
+ var recordingModeSchema = z.enum(["off", ...RECORDED_MEDIA_MODES, "participants", "tracks"]).describe("Server-owned recording policy; omitted means off. Legacy audio includes user and avatar audio. participants saves one continuous audio/video file per participant, without mixing their voices. tracks saves each published participant audio/video track separately; list recordings by session after capture.");
2253
2253
  var RECORDING_STATUSES = ["pending", "recording", "processing", "ready", "failed", "expired"];
2254
2254
  z.enum(RECORDING_STATUSES);
2255
2255
  var recordingParticipantSchema = z.object({
2256
2256
  participantIdentity: z.string().min(1).max(160),
2257
2257
  role: z.enum(["user", "avatar"])
2258
2258
  }).strict();
2259
+ var recordingMutedIntervalSchema = z.object({
2260
+ startMs: z.number().int().min(0),
2261
+ endMs: z.number().int().positive()
2262
+ }).strict().refine((interval) => interval.endMs > interval.startMs, "Muted interval must end after it starts");
2259
2263
  var recordingMetadataSchema = z.object({
2260
2264
  sessionId: z.string().min(1),
2261
2265
  recordingId: z.string().min(1),
@@ -2264,13 +2268,15 @@ var recordingMetadataSchema = z.object({
2264
2268
  retainedUntil: z.string().datetime({ offset: true }),
2265
2269
  participant: recordingParticipantSchema.optional(),
2266
2270
  mediaStartedAt: z.string().datetime({ offset: true }).optional(),
2267
- mediaEndedAt: z.string().datetime({ offset: true }).optional()
2271
+ mediaEndedAt: z.string().datetime({ offset: true }).optional(),
2272
+ mediaStartOffsetMs: z.number().int().min(0).optional().describe("First playable native media timestamp relative to mediaStartedAt. Playback retains this offset; it does not reset the file clock to zero."),
2273
+ mediaMutedIntervalsMs: z.array(recordingMutedIntervalSchema).optional().describe("Intervals of hidden camera video relative to mediaStartedAt, using native media timestamps. Audio playback continues through these intervals.")
2268
2274
  });
2269
2275
  var recordingArtifactSchema = z.discriminatedUnion("status", [
2270
2276
  recordingMetadataSchema.extend({ status: z.enum(["pending", "recording", "processing", "expired"]) }).strict(),
2271
2277
  recordingMetadataSchema.extend({
2272
2278
  status: z.literal("ready"),
2273
- mediaType: z.enum(["audio/mp4", "video/mp4"]),
2279
+ mediaType: z.enum(["audio/mp4", "video/mp4", "audio/ogg", "video/webm"]),
2274
2280
  sizeBytes: z.number().int().positive(),
2275
2281
  durationMs: z.number().int().nonnegative().nullable()
2276
2282
  }).strict(),
@@ -2333,6 +2339,8 @@ var transcriptionOptionsSchema = z.object({
2333
2339
  languageCodes: transcriptionWireSchema.shape.language_codes,
2334
2340
  customVocabulary: transcriptionWireSchema.shape.custom_vocabulary
2335
2341
  }).strict();
2342
+ var schema02 = z.enum(["minor-sexual-content-v1"]);
2343
+ var contentPolicySchema = schema02;
2336
2344
 
2337
2345
  // ../client/src/wire.ts
2338
2346
  var DEFAULT_AVATAR_ID = "maria";
@@ -2499,7 +2507,8 @@ z.object({
2499
2507
  support_edits: sessionSupportEditsSchema.optional(),
2500
2508
  transcript_webhook: transcriptWebhookSchema.optional(),
2501
2509
  client_metadata: clientMetadataSchema.optional(),
2502
- connection_history: z.boolean().optional()
2510
+ connection_history: z.boolean().optional(),
2511
+ content_policy: contentPolicySchema.optional()
2503
2512
  }).strict().superRefine((value, ctx) => {
2504
2513
  if (value.support_edits && value.render_backend === "generative") {
2505
2514
  ctx.addIssue({
@@ -2567,7 +2576,8 @@ z.object({
2567
2576
  supportEdits: sessionSupportEditsSchema.optional(),
2568
2577
  transcriptWebhook: transcriptWebhookSchema.optional(),
2569
2578
  clientMetadata: clientMetadataSchema.optional(),
2570
- connectionHistory: z.boolean().optional()
2579
+ connectionHistory: z.boolean().optional(),
2580
+ contentPolicy: contentPolicySchema.optional()
2571
2581
  }).strict();
2572
2582
  z.object({
2573
2583
  recording: recordingArtifactSchema.optional(),
@@ -4588,43 +4598,57 @@ function recordingTimeline(recordings) {
4588
4598
  if (new Set(recordings.map((recording) => recording.sessionId)).size > 1) throw new Error("Recordings belong to different sessions");
4589
4599
  if (new Set(recordings.map((recording) => recording.recordingId)).size !== recordings.length) throw new Error("Duplicate recording assets");
4590
4600
  const participants = recordings.filter((recording) => recording.participant && recording.status === "ready");
4591
- if (new Set(participants.map((recording) => recording.participant?.role)).size !== participants.length) throw new Error("Duplicate participant recordings");
4592
4601
  const entries = participants.map((recording) => {
4593
4602
  if (recording.status !== "ready" || !recording.mediaStartedAt || !recording.mediaEndedAt || recording.durationMs === null) {
4594
4603
  throw new Error("Recording media timing is unavailable");
4595
4604
  }
4596
4605
  const start = Date.parse(recording.mediaStartedAt);
4597
4606
  const end = Math.min(Date.parse(recording.mediaEndedAt), start + recording.durationMs);
4598
- if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) throw new Error("Recording media timing is invalid");
4599
- return { recording, start, end };
4607
+ const activeStart = start + (recording.mediaStartOffsetMs ?? 0);
4608
+ if (!Number.isFinite(start) || !Number.isFinite(end) || !Number.isFinite(activeStart) || end <= activeStart) throw new Error("Recording media timing is invalid");
4609
+ return { recording, start, activeStart, end };
4600
4610
  });
4601
4611
  if (!entries.length) return null;
4612
+ const laneEnds = /* @__PURE__ */ new Map();
4613
+ for (const { recording, activeStart, end } of [...entries].sort((a, b) => a.activeStart - b.activeStart)) {
4614
+ const kinds = recording.mode === "audio_video" ? ["audio", "video"] : [recording.mode];
4615
+ for (const kind of kinds) {
4616
+ const lane = `${recording.participant?.role}:${kind}`;
4617
+ if (activeStart < (laneEnds.get(lane) ?? -Infinity)) throw new Error(`Duplicate participant ${kind} recordings overlap`);
4618
+ laneEnds.set(lane, end);
4619
+ }
4620
+ }
4602
4621
  const originMs = Math.min(...entries.map((entry) => entry.start));
4603
4622
  return {
4604
4623
  originMs,
4605
4624
  durationMs: Math.max(...entries.map((entry) => entry.end)) - originMs,
4606
- entries: entries.map(({ recording, start, end }) => ({ recording, startMs: start - originMs, endMs: end - originMs }))
4625
+ entries: entries.map(({ recording, start, activeStart, end }) => ({ recording, startMs: start - originMs, activeStartMs: activeStart - originMs, endMs: end - originMs }))
4607
4626
  };
4608
4627
  }
4609
4628
  function recordingPositions(timeline, positionMs) {
4610
4629
  return timeline.entries.map((entry) => ({
4611
4630
  recordingId: entry.recording.recordingId,
4612
- active: positionMs >= entry.startMs && positionMs < entry.endMs,
4631
+ active: positionMs >= entry.activeStartMs && positionMs < entry.endMs,
4613
4632
  timeSeconds: Math.max(0, Math.min(positionMs, entry.endMs) - entry.startMs) / 1e3
4614
4633
  }));
4615
4634
  }
4616
- function exhausted(element, seconds) {
4617
- return Number.isFinite(element.duration) && seconds >= element.duration || element.ended && seconds >= element.currentTime;
4635
+ function recordingVideoVisible(recording, timeSeconds) {
4636
+ const mediaMs = timeSeconds * 1e3;
4637
+ return recording.mode !== "audio" && !(recording.mediaMutedIntervalsMs ?? []).some(
4638
+ (interval) => mediaMs >= interval.startMs && mediaMs < interval.endMs
4639
+ );
4618
4640
  }
4619
4641
  function RecordingPlayer({ assets, className }) {
4642
+ const timelineKey = JSON.stringify(assets.map((asset) => asset.recording));
4620
4643
  const timeline = useMemo(() => {
4621
4644
  try {
4622
4645
  return recordingTimeline(assets.map((asset) => asset.recording));
4623
4646
  } catch {
4624
4647
  return null;
4625
4648
  }
4626
- }, [assets]);
4649
+ }, [timelineKey]);
4627
4650
  const media = useRef(/* @__PURE__ */ new Map());
4651
+ const seeks = useRef(/* @__PURE__ */ new Map());
4628
4652
  const position = useRef(0);
4629
4653
  const anchor = useRef(null);
4630
4654
  const [displayPosition, setDisplayPosition] = useState(0);
@@ -4632,6 +4656,11 @@ function RecordingPlayer({ assets, className }) {
4632
4656
  const [buffering, setBuffering] = useState(false);
4633
4657
  const [error, setError] = useState("");
4634
4658
  const sourceKey = assets.map((asset) => `${asset.recording.recordingId}:${asset.url}`).join("|");
4659
+ const seekMedia = (recordingId, element, seconds) => {
4660
+ if (element.readyState < 1 || Math.abs(element.currentTime - seconds) <= 0.02) return;
4661
+ seeks.current.set(recordingId, { seconds, retries: 0 });
4662
+ element.currentTime = seconds;
4663
+ };
4635
4664
  useEffect(() => {
4636
4665
  if (!timeline) return;
4637
4666
  const sessionId = timeline.entries[0].recording.sessionId;
@@ -4648,6 +4677,7 @@ function RecordingPlayer({ assets, className }) {
4648
4677
  let last = performance.now();
4649
4678
  let painted = last;
4650
4679
  const starting = /* @__PURE__ */ new Set();
4680
+ const active = /* @__PURE__ */ new Set();
4651
4681
  const stopMedia = () => {
4652
4682
  for (const element of media.current.values()) element.pause();
4653
4683
  };
@@ -4664,19 +4694,23 @@ function RecordingPlayer({ assets, className }) {
4664
4694
  const states = recordingPositions(timeline, proposed);
4665
4695
  const waiting = playing && states.some((state) => {
4666
4696
  const element = media.current.get(state.recordingId);
4667
- return state.active && (!element || !exhausted(element, state.timeSeconds) && (element.readyState < 2 || element.seeking));
4697
+ return state.active && (!element || (!element.ended || seeks.current.has(state.recordingId)) && (element.readyState < 2 || element.seeking));
4668
4698
  });
4669
4699
  setBuffering(waiting);
4670
4700
  if (!waiting) position.current = proposed;
4671
4701
  for (const state of recordingPositions(timeline, position.current)) {
4672
4702
  const element = media.current.get(state.recordingId);
4673
4703
  if (!element) continue;
4674
- if (!state.active || exhausted(element, state.timeSeconds)) {
4704
+ if (!state.active) {
4675
4705
  element.pause();
4706
+ active.delete(state.recordingId);
4676
4707
  continue;
4677
4708
  }
4678
- if (element.readyState >= 1 && Math.abs(element.currentTime - state.timeSeconds) > 0.08) {
4679
- element.currentTime = state.timeSeconds;
4709
+ if (!active.has(state.recordingId)) seekMedia(state.recordingId, element, state.timeSeconds);
4710
+ if (element.readyState >= 1) active.add(state.recordingId);
4711
+ if (element.ended && !seeks.current.has(state.recordingId)) {
4712
+ element.pause();
4713
+ continue;
4680
4714
  }
4681
4715
  if (!playing || waiting) {
4682
4716
  element.pause();
@@ -4712,14 +4746,14 @@ function RecordingPlayer({ assets, className }) {
4712
4746
  stopMedia();
4713
4747
  };
4714
4748
  }, [timeline, playing, sourceKey]);
4715
- const activeIds = new Set(timeline ? recordingPositions(timeline, displayPosition).filter((state) => state.active).map((state) => state.recordingId) : []);
4749
+ const positions = new Map(timeline ? recordingPositions(timeline, displayPosition).map((state) => [state.recordingId, state]) : []);
4716
4750
  const seek = (value) => {
4717
4751
  position.current = value;
4718
4752
  setDisplayPosition(value);
4719
4753
  if (timeline) for (const state of recordingPositions(timeline, value)) {
4720
4754
  const element = media.current.get(state.recordingId);
4721
4755
  if (!element) continue;
4722
- if (element.readyState >= 1) element.currentTime = state.timeSeconds;
4756
+ seekMedia(state.recordingId, element, state.timeSeconds);
4723
4757
  if (!state.active) element.pause();
4724
4758
  }
4725
4759
  };
@@ -4733,8 +4767,8 @@ function RecordingPlayer({ assets, className }) {
4733
4767
  if (position.current >= timeline.durationMs) seek(0);
4734
4768
  for (const state of recordingPositions(timeline, position.current)) {
4735
4769
  const element = media.current.get(state.recordingId);
4736
- if (state.active && element && !exhausted(element, state.timeSeconds)) {
4737
- if (element.readyState >= 1) element.currentTime = state.timeSeconds;
4770
+ if (state.active && element) {
4771
+ seekMedia(state.recordingId, element, state.timeSeconds);
4738
4772
  void element.play().catch((cause) => {
4739
4773
  if (cause instanceof Error && cause.name === "AbortError") return;
4740
4774
  for (const current of media.current.values()) current.pause();
@@ -4748,6 +4782,8 @@ function RecordingPlayer({ assets, className }) {
4748
4782
  return /* @__PURE__ */ jsxs("div", { className, children: [
4749
4783
  /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 240px), 1fr))", gap: 12 }, children: assets.map(({ recording, url }) => {
4750
4784
  if (!recording.participant) return null;
4785
+ const state = positions.get(recording.recordingId);
4786
+ const visible = Boolean(state?.active && recordingVideoVisible(recording, state.timeSeconds));
4751
4787
  const label = recording.participant.role === "user" ? "User" : "Character";
4752
4788
  const ref = (element) => {
4753
4789
  if (element) media.current.set(recording.recordingId, element);
@@ -4758,22 +4794,36 @@ function RecordingPlayer({ assets, className }) {
4758
4794
  setPlaying(false);
4759
4795
  setError("A recording could not load. Refresh access and try again.");
4760
4796
  };
4761
- return recording.mode !== "audio" ? /* @__PURE__ */ jsxs("figure", { hidden: !activeIds.has(recording.recordingId), style: { margin: 0 }, children: [
4797
+ const onLoadedMetadata = (event) => {
4798
+ const state2 = timeline && recordingPositions(timeline, position.current).find((item) => item.recordingId === recording.recordingId);
4799
+ if (state2) seekMedia(recording.recordingId, event.currentTarget, state2.timeSeconds);
4800
+ };
4801
+ const onSeeked = (event) => {
4802
+ const pending = seeks.current.get(recording.recordingId);
4803
+ if (!pending) return;
4804
+ if (Math.abs(event.currentTarget.currentTime - pending.seconds) > 0.08 && pending.retries < 2) {
4805
+ pending.retries++;
4806
+ event.currentTarget.currentTime = pending.seconds;
4807
+ } else seeks.current.delete(recording.recordingId);
4808
+ };
4809
+ return recording.mode !== "audio" ? /* @__PURE__ */ jsxs("figure", { hidden: !visible, style: { margin: 0 }, children: [
4762
4810
  /* @__PURE__ */ jsx("figcaption", { children: label }),
4763
4811
  /* @__PURE__ */ jsx(
4764
4812
  "video",
4765
4813
  {
4766
4814
  ref,
4767
4815
  src: url,
4816
+ muted: recording.mode === "video",
4768
4817
  playsInline: true,
4769
4818
  preload: "auto",
4770
4819
  onError,
4771
- hidden: !activeIds.has(recording.recordingId),
4820
+ onLoadedMetadata,
4821
+ onSeeked,
4822
+ hidden: !visible,
4772
4823
  style: { width: "100%", maxHeight: 360, objectFit: "contain" }
4773
4824
  }
4774
- ),
4775
- !activeIds.has(recording.recordingId) ? /* @__PURE__ */ jsx("p", { children: "No recording at this time." }) : null
4776
- ] }, recording.recordingId) : /* @__PURE__ */ jsx("audio", { ref, src: url, preload: "auto", onError, "aria-label": `${label} audio` }, recording.recordingId);
4825
+ )
4826
+ ] }, recording.recordingId) : /* @__PURE__ */ jsx("audio", { ref, src: url, preload: "auto", onError, onLoadedMetadata, onSeeked, "aria-label": `${label} audio` }, recording.recordingId);
4777
4827
  }) }),
4778
4828
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
4779
4829
  /* @__PURE__ */ jsx("button", { type: "button", onClick: toggle, disabled: !timeline, style: { minHeight: 44, minWidth: 44 }, children: playing ? "Pause" : "Play" }),
@@ -6,6 +6,7 @@ declare const recordingModeSchema: z.ZodEnum<{
6
6
  audio_video: "audio_video";
7
7
  off: "off";
8
8
  participants: "participants";
9
+ tracks: "tracks";
9
10
  }>;
10
11
  type RecordingMode = z.infer<typeof recordingModeSchema>;
11
12
  declare const recordingArtifactStatusSchema: z.ZodEnum<{
@@ -44,6 +45,11 @@ declare const recordingArtifactSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
44
45
  }, z.core.$strict>>;
45
46
  mediaStartedAt: z.ZodOptional<z.ZodString>;
46
47
  mediaEndedAt: z.ZodOptional<z.ZodString>;
48
+ mediaStartOffsetMs: z.ZodOptional<z.ZodNumber>;
49
+ mediaMutedIntervalsMs: z.ZodOptional<z.ZodArray<z.ZodObject<{
50
+ startMs: z.ZodNumber;
51
+ endMs: z.ZodNumber;
52
+ }, z.core.$strict>>>;
47
53
  status: z.ZodEnum<{
48
54
  pending: "pending";
49
55
  recording: "recording";
@@ -69,10 +75,17 @@ declare const recordingArtifactSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
69
75
  }, z.core.$strict>>;
70
76
  mediaStartedAt: z.ZodOptional<z.ZodString>;
71
77
  mediaEndedAt: z.ZodOptional<z.ZodString>;
78
+ mediaStartOffsetMs: z.ZodOptional<z.ZodNumber>;
79
+ mediaMutedIntervalsMs: z.ZodOptional<z.ZodArray<z.ZodObject<{
80
+ startMs: z.ZodNumber;
81
+ endMs: z.ZodNumber;
82
+ }, z.core.$strict>>>;
72
83
  status: z.ZodLiteral<"ready">;
73
84
  mediaType: z.ZodEnum<{
74
85
  "audio/mp4": "audio/mp4";
75
86
  "video/mp4": "video/mp4";
87
+ "audio/ogg": "audio/ogg";
88
+ "video/webm": "video/webm";
76
89
  }>;
77
90
  sizeBytes: z.ZodNumber;
78
91
  durationMs: z.ZodNullable<z.ZodNumber>;
@@ -95,6 +108,11 @@ declare const recordingArtifactSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
95
108
  }, z.core.$strict>>;
96
109
  mediaStartedAt: z.ZodOptional<z.ZodString>;
97
110
  mediaEndedAt: z.ZodOptional<z.ZodString>;
111
+ mediaStartOffsetMs: z.ZodOptional<z.ZodNumber>;
112
+ mediaMutedIntervalsMs: z.ZodOptional<z.ZodArray<z.ZodObject<{
113
+ startMs: z.ZodNumber;
114
+ endMs: z.ZodNumber;
115
+ }, z.core.$strict>>>;
98
116
  status: z.ZodLiteral<"failed">;
99
117
  errorCode: z.ZodEnum<{
100
118
  recording_failed: "recording_failed";
@@ -128,6 +146,11 @@ declare const listRecordingsResponseSchema: z.ZodObject<{
128
146
  }, z.core.$strict>>;
129
147
  mediaStartedAt: z.ZodOptional<z.ZodString>;
130
148
  mediaEndedAt: z.ZodOptional<z.ZodString>;
149
+ mediaStartOffsetMs: z.ZodOptional<z.ZodNumber>;
150
+ mediaMutedIntervalsMs: z.ZodOptional<z.ZodArray<z.ZodObject<{
151
+ startMs: z.ZodNumber;
152
+ endMs: z.ZodNumber;
153
+ }, z.core.$strict>>>;
131
154
  status: z.ZodEnum<{
132
155
  pending: "pending";
133
156
  recording: "recording";
@@ -153,10 +176,17 @@ declare const listRecordingsResponseSchema: z.ZodObject<{
153
176
  }, z.core.$strict>>;
154
177
  mediaStartedAt: z.ZodOptional<z.ZodString>;
155
178
  mediaEndedAt: z.ZodOptional<z.ZodString>;
179
+ mediaStartOffsetMs: z.ZodOptional<z.ZodNumber>;
180
+ mediaMutedIntervalsMs: z.ZodOptional<z.ZodArray<z.ZodObject<{
181
+ startMs: z.ZodNumber;
182
+ endMs: z.ZodNumber;
183
+ }, z.core.$strict>>>;
156
184
  status: z.ZodLiteral<"ready">;
157
185
  mediaType: z.ZodEnum<{
158
186
  "audio/mp4": "audio/mp4";
159
187
  "video/mp4": "video/mp4";
188
+ "audio/ogg": "audio/ogg";
189
+ "video/webm": "video/webm";
160
190
  }>;
161
191
  sizeBytes: z.ZodNumber;
162
192
  durationMs: z.ZodNullable<z.ZodNumber>;
@@ -179,6 +209,11 @@ declare const listRecordingsResponseSchema: z.ZodObject<{
179
209
  }, z.core.$strict>>;
180
210
  mediaStartedAt: z.ZodOptional<z.ZodString>;
181
211
  mediaEndedAt: z.ZodOptional<z.ZodString>;
212
+ mediaStartOffsetMs: z.ZodOptional<z.ZodNumber>;
213
+ mediaMutedIntervalsMs: z.ZodOptional<z.ZodArray<z.ZodObject<{
214
+ startMs: z.ZodNumber;
215
+ endMs: z.ZodNumber;
216
+ }, z.core.$strict>>>;
182
217
  status: z.ZodLiteral<"failed">;
183
218
  errorCode: z.ZodEnum<{
184
219
  recording_failed: "recording_failed";
@@ -1,5 +1,5 @@
1
- import { R as RecordingArtifact } from './recording-DjswTx5B.js';
2
- export { L as ListRecordingsQuery, a as ListRecordingsResponse, b as RecordingAccessResponse, c as RecordingArtifactStatus, d as RecordingMode, e as RecordingParticipant, l as listRecordingsQuerySchema, f as listRecordingsResponseSchema, r as recordingAccessResponseSchema, g as recordingArtifactSchema, h as recordingArtifactStatusSchema, i as recordingModeSchema, j as recordingParticipantSchema } from './recording-DjswTx5B.js';
1
+ import { R as RecordingArtifact } from './recording-ydFVySSd.js';
2
+ export { L as ListRecordingsQuery, a as ListRecordingsResponse, b as RecordingAccessResponse, c as RecordingArtifactStatus, d as RecordingMode, e as RecordingParticipant, l as listRecordingsQuerySchema, f as listRecordingsResponseSchema, r as recordingAccessResponseSchema, g as recordingArtifactSchema, h as recordingArtifactStatusSchema, i as recordingModeSchema, j as recordingParticipantSchema } from './recording-ydFVySSd.js';
3
3
  import 'zod';
4
4
 
5
5
  declare function recordingTimeline(recordings: readonly RecordingArtifact[]): {
@@ -13,7 +13,7 @@ declare function recordingTimeline(recordings: readonly RecordingArtifact[]): {
13
13
  createdAt: string;
14
14
  retainedUntil: string;
15
15
  status: "ready";
16
- mediaType: "audio/mp4" | "video/mp4";
16
+ mediaType: "audio/mp4" | "video/mp4" | "audio/ogg" | "video/webm";
17
17
  sizeBytes: number;
18
18
  durationMs: number | null;
19
19
  participant?: {
@@ -22,8 +22,14 @@ declare function recordingTimeline(recordings: readonly RecordingArtifact[]): {
22
22
  } | undefined;
23
23
  mediaStartedAt?: string | undefined;
24
24
  mediaEndedAt?: string | undefined;
25
+ mediaStartOffsetMs?: number | undefined;
26
+ mediaMutedIntervalsMs?: {
27
+ startMs: number;
28
+ endMs: number;
29
+ }[] | undefined;
25
30
  };
26
31
  startMs: number;
32
+ activeStartMs: number;
27
33
  endMs: number;
28
34
  }[];
29
35
  } | null;
package/dist/recording.js CHANGED
@@ -2,13 +2,17 @@ import { z } from 'zod';
2
2
 
3
3
  // ../http-client/src/generated/recording.ts
4
4
  var RECORDED_MEDIA_MODES = ["audio", "video", "audio_video"];
5
- var recordingModeSchema = z.enum(["off", ...RECORDED_MEDIA_MODES, "participants"]).describe("Server-owned recording policy; omitted means off. Legacy audio includes user and avatar audio. participants saves one continuous audio/video file per participant, without mixing their voices.");
5
+ var recordingModeSchema = z.enum(["off", ...RECORDED_MEDIA_MODES, "participants", "tracks"]).describe("Server-owned recording policy; omitted means off. Legacy audio includes user and avatar audio. participants saves one continuous audio/video file per participant, without mixing their voices. tracks saves each published participant audio/video track separately; list recordings by session after capture.");
6
6
  var RECORDING_STATUSES = ["pending", "recording", "processing", "ready", "failed", "expired"];
7
7
  var recordingArtifactStatusSchema = z.enum(RECORDING_STATUSES);
8
8
  var recordingParticipantSchema = z.object({
9
9
  participantIdentity: z.string().min(1).max(160),
10
10
  role: z.enum(["user", "avatar"])
11
11
  }).strict();
12
+ var recordingMutedIntervalSchema = z.object({
13
+ startMs: z.number().int().min(0),
14
+ endMs: z.number().int().positive()
15
+ }).strict().refine((interval) => interval.endMs > interval.startMs, "Muted interval must end after it starts");
12
16
  var recordingMetadataSchema = z.object({
13
17
  sessionId: z.string().min(1),
14
18
  recordingId: z.string().min(1),
@@ -17,13 +21,15 @@ var recordingMetadataSchema = z.object({
17
21
  retainedUntil: z.string().datetime({ offset: true }),
18
22
  participant: recordingParticipantSchema.optional(),
19
23
  mediaStartedAt: z.string().datetime({ offset: true }).optional(),
20
- mediaEndedAt: z.string().datetime({ offset: true }).optional()
24
+ mediaEndedAt: z.string().datetime({ offset: true }).optional(),
25
+ mediaStartOffsetMs: z.number().int().min(0).optional().describe("First playable native media timestamp relative to mediaStartedAt. Playback retains this offset; it does not reset the file clock to zero."),
26
+ mediaMutedIntervalsMs: z.array(recordingMutedIntervalSchema).optional().describe("Intervals of hidden camera video relative to mediaStartedAt, using native media timestamps. Audio playback continues through these intervals.")
21
27
  });
22
28
  var recordingArtifactSchema = z.discriminatedUnion("status", [
23
29
  recordingMetadataSchema.extend({ status: z.enum(["pending", "recording", "processing", "expired"]) }).strict(),
24
30
  recordingMetadataSchema.extend({
25
31
  status: z.literal("ready"),
26
- mediaType: z.enum(["audio/mp4", "video/mp4"]),
32
+ mediaType: z.enum(["audio/mp4", "video/mp4", "audio/ogg", "video/webm"]),
27
33
  sizeBytes: z.number().int().positive(),
28
34
  durationMs: z.number().int().nonnegative().nullable()
29
35
  }).strict(),
@@ -52,28 +58,37 @@ function recordingTimeline(recordings) {
52
58
  if (new Set(recordings.map((recording) => recording.sessionId)).size > 1) throw new Error("Recordings belong to different sessions");
53
59
  if (new Set(recordings.map((recording) => recording.recordingId)).size !== recordings.length) throw new Error("Duplicate recording assets");
54
60
  const participants = recordings.filter((recording) => recording.participant && recording.status === "ready");
55
- if (new Set(participants.map((recording) => recording.participant?.role)).size !== participants.length) throw new Error("Duplicate participant recordings");
56
61
  const entries = participants.map((recording) => {
57
62
  if (recording.status !== "ready" || !recording.mediaStartedAt || !recording.mediaEndedAt || recording.durationMs === null) {
58
63
  throw new Error("Recording media timing is unavailable");
59
64
  }
60
65
  const start = Date.parse(recording.mediaStartedAt);
61
66
  const end = Math.min(Date.parse(recording.mediaEndedAt), start + recording.durationMs);
62
- if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) throw new Error("Recording media timing is invalid");
63
- return { recording, start, end };
67
+ const activeStart = start + (recording.mediaStartOffsetMs ?? 0);
68
+ if (!Number.isFinite(start) || !Number.isFinite(end) || !Number.isFinite(activeStart) || end <= activeStart) throw new Error("Recording media timing is invalid");
69
+ return { recording, start, activeStart, end };
64
70
  });
65
71
  if (!entries.length) return null;
72
+ const laneEnds = /* @__PURE__ */ new Map();
73
+ for (const { recording, activeStart, end } of [...entries].sort((a, b) => a.activeStart - b.activeStart)) {
74
+ const kinds = recording.mode === "audio_video" ? ["audio", "video"] : [recording.mode];
75
+ for (const kind of kinds) {
76
+ const lane = `${recording.participant?.role}:${kind}`;
77
+ if (activeStart < (laneEnds.get(lane) ?? -Infinity)) throw new Error(`Duplicate participant ${kind} recordings overlap`);
78
+ laneEnds.set(lane, end);
79
+ }
80
+ }
66
81
  const originMs = Math.min(...entries.map((entry) => entry.start));
67
82
  return {
68
83
  originMs,
69
84
  durationMs: Math.max(...entries.map((entry) => entry.end)) - originMs,
70
- entries: entries.map(({ recording, start, end }) => ({ recording, startMs: start - originMs, endMs: end - originMs }))
85
+ entries: entries.map(({ recording, start, activeStart, end }) => ({ recording, startMs: start - originMs, activeStartMs: activeStart - originMs, endMs: end - originMs }))
71
86
  };
72
87
  }
73
88
  function recordingPositions(timeline, positionMs) {
74
89
  return timeline.entries.map((entry) => ({
75
90
  recordingId: entry.recording.recordingId,
76
- active: positionMs >= entry.startMs && positionMs < entry.endMs,
91
+ active: positionMs >= entry.activeStartMs && positionMs < entry.endMs,
77
92
  timeSeconds: Math.max(0, Math.min(positionMs, entry.endMs) - entry.startMs) / 1e3
78
93
  }));
79
94
  }
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, RealtimeAvatarOptions, StartCallOptions, clipLibraryDeclarationSchema, verifyTranscript } from './index.js';
2
- export { h as Asset, g as AssetKind, A as Avatar, o as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, p as CallConnection, C as CallMode, a as CallPolicy, q as CallQueued, r as ClipDeclaration, f as ClipLibrary, d as ClipLibraryDeclaration, s as ClipLibraryPlan, e as ClipLibraryUpdate, t as ClipSource, u as ContextMessage, n as CreditBalance, E as EndCallOptions, v as EndCallReason, w as ExternalLlm, k as ListRecordingsQuery, l as ListRecordingsResponse, i as ListSessionsOptions, L as LoopRedirect, m as RecordingAccessResponse, R as RecordingArtifact, x as RecordingArtifactStatus, y as RecordingMode, S as StartCallResult, T as TranscriptPayload, j as UsageSession, U as UsageSessionPage, V as VideoPolicy, z as isQueued, B as listRecordingsResponseSchema, D as recordingAccessResponseSchema, F as recordingArtifactSchema, G as recordingModeSchema } from './types-6MUqx800.js';
2
+ export { h as Asset, g as AssetKind, A as Avatar, o as AvatarClip, c as AvatarSourceSwap, b as AvatarUpdate, p as CallConnection, C as CallMode, a as CallPolicy, q as CallQueued, r as ClipDeclaration, f as ClipLibrary, d as ClipLibraryDeclaration, s as ClipLibraryPlan, e as ClipLibraryUpdate, t as ClipSource, u as ContextMessage, n as CreditBalance, E as EndCallOptions, v as EndCallReason, w as ExternalLlm, k as ListRecordingsQuery, l as ListRecordingsResponse, i as ListSessionsOptions, L as LoopRedirect, m as RecordingAccessResponse, R as RecordingArtifact, x as RecordingArtifactStatus, y as RecordingMode, S as StartCallResult, T as TranscriptPayload, j as UsageSession, U as UsageSessionPage, V as VideoPolicy, z as isQueued, B as listRecordingsResponseSchema, D as recordingAccessResponseSchema, F as recordingArtifactSchema, G as recordingModeSchema } from './types-kgmS7ECd.js';
3
3
  import './connection-history.js';
4
4
  import 'zod';
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-4IEGR75H.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, clipLibraryDeclarationSchema, isQueued, listRecordingsResponseSchema, recordingAccessResponseSchema, recordingArtifactSchema, recordingModeSchema, verifyTranscript } from './chunk-W4DGBG4Z.js';
2
2
  import './chunk-MCIXU6XP.js';
@@ -1,5 +1,5 @@
1
- import { P as ProxyConfig } from './types-JtCwa0Ip.js';
2
- import './types-6MUqx800.js';
1
+ import { P as ProxyConfig } from './types-lgS4Z9rd.js';
2
+ import './types-kgmS7ECd.js';
3
3
  import 'zod';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-DOHZ365B.js';
2
- import './chunk-4IEGR75H.js';
1
+ import { createProxyHandler } from './chunk-QBOIOZ3O.js';
2
+ import './chunk-W4DGBG4Z.js';
3
3
  import './chunk-MCIXU6XP.js';
4
4
 
5
5
  // src/tanstack-start.ts