realtime-avatar 0.9.0 → 0.9.1

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.
@@ -43,7 +43,7 @@ function isTransient(cause) {
43
43
 
44
44
  // ../http-client/src/client.ts
45
45
  var DEFAULT_BASE_URL = "https://realtimeavatar.ai/api/v1";
46
- var SDK_VERSION = "0.9.0";
46
+ var SDK_VERSION = "0.9.1";
47
47
  var RealtimeAvatar = class {
48
48
  #apiKey;
49
49
  #baseUrl;
@@ -1,4 +1,4 @@
1
- import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-IQUJLPYF.js';
1
+ import { RealtimeAvatar, isQueued, RealtimeAvatarHttpError } from './chunk-F7E4HMBW.js';
2
2
 
3
3
  // ../proxy/src/config.ts
4
4
  var json = (body, status = 200) => new Response(JSON.stringify(body), {
package/dist/express.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-A2ATKWJT.js';
2
- import './chunk-IQUJLPYF.js';
1
+ import { createProxyHandler } from './chunk-FTAD423K.js';
2
+ import './chunk-F7E4HMBW.js';
3
3
 
4
4
  // ../proxy/src/express.ts
5
5
  function realtimeAvatarExpress(config) {
package/dist/hono.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-A2ATKWJT.js';
2
- import './chunk-IQUJLPYF.js';
1
+ import { createProxyHandler } from './chunk-FTAD423K.js';
2
+ import './chunk-F7E4HMBW.js';
3
3
 
4
4
  // ../proxy/src/hono.ts
5
5
  function realtimeAvatarHono(config) {
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-IQUJLPYF.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-F7E4HMBW.js';
package/dist/nextjs.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-A2ATKWJT.js';
2
- import './chunk-IQUJLPYF.js';
1
+ import { createProxyHandler } from './chunk-FTAD423K.js';
2
+ import './chunk-F7E4HMBW.js';
3
3
 
4
4
  // ../proxy/src/nextjs.ts
5
5
  function createRealtimeAvatarRoute(config) {
@@ -470,8 +470,8 @@ type AvatarVideoSurfaceProps = {
470
470
  * Debounce (ms) before dropping BACK to the idle clip once the live layer stops
471
471
  * being shown (turn end), so back-to-back turns don't flash the idle clip for a
472
472
  * frame between them. Default 700. A disconnect bypasses this and reverts to idle
473
- * immediately (a dead room is never held). Showing the live layer is always
474
- * immediate — only the hide is debounced.
473
+ * immediately (a dead room is never held). A new track shows its first frame
474
+ * immediately; after a frame stall, sustained progress is required to return.
475
475
  */
476
476
  idleReturnDelayMs?: number;
477
477
  /**
@@ -520,9 +520,8 @@ type AvatarVideoSurfaceProps = {
520
520
  * end, network drop, agent gone, turn-end mute).
521
521
  *
522
522
  * ONE BODY, ONE POSE. Only one media layer ever runs: the idle clip is paused while
523
- * the live layer covers it, and re-enters at its anchor (frame 0) when it takes over.
524
- * That is what lets the handover be a swap with no blending — see the front-layer and
525
- * `useIdleWhileResting` notes for the measurements behind it.
523
+ * the live layer covers it. A temporary interruption resumes the idle clip where
524
+ * it paused; a deliberate rest or disconnect returns it to its anchor (frame 0).
526
525
  *
527
526
  * This is the SSOT for "is the avatar live right now": it reads the LiveKit
528
527
  * connection state ({@link useConnectionState}) and the bound avatar track
@@ -1,8 +1,8 @@
1
1
  import { LiveKitRoomProps } from '@livekit/react-native';
2
2
  export { AudioSession, VideoTrack, VideoTrackProps, registerGlobals } from '@livekit/react-native';
3
3
  import { ReactNode, ReactElement } from 'react';
4
- import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-DDqYRz-_.js';
5
- export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarSessionClient, B as BehaviorSnapshot, C as CallTranscript, h as CallTranscriptSegment, i as CartesiaTtsModel, j as ClipResult, k as ClosingTurnResult, l as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, m as DEFAULT_CREDITS_LOW_LEAD_SECONDS, n as DEFAULT_GOVERNOR_CONFIG, o as DEFAULT_GRACE_CEILING_SECONDS, p as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, q as DEFAULT_IDLE_SECONDS, r as DEFAULT_IDLE_WARN_LEAD_SECONDS, s as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, t as EndedEvent, u as ExtendResult, F as FishTtsModel, v as FreezeReadingFn, G as Governor, w as GovernorAction, x as GovernorConfig, y as GovernorSignal, z as GovernorState, H as GraceWindowClosedEvent, I as GraceWindowOpenEvent, J as GraceWindowState, K as IdleWarningEvent, M as InboundRtpCursor, N as InboundRtpReading, O as KnownBehaviorState, P as LLMProvider, Q as LLMSelection, R as LiveKitAvatarGrantState, S as LiveKitAvatarGrantStatus, T as LiveKitCapacityState, U as LiveKitConnectionStatus, V as LiveKitSessionRequest, W as LiveKitSessionStartResult, X as MAX_RECONNECT_ATTEMPTS, Y as MAX_SESSION_INSTRUCTIONS_CHARS, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, a0 as RealtimeAvatarRequestOptions, a1 as RealtimeSessionApi, a2 as RealtimeSessionMedia, a3 as RealtimeSessionRoomSinks, a4 as ReconnectPolicy, a5 as ReconnectingEvent, a6 as RecoveryState, a7 as RetryStep, a8 as SessionBehavior, a9 as SessionClip, aa as SessionClocks, ab as SessionEndReason, ac as SessionLifecycleApi, ad as SessionLifecyclePhase, ae as SessionLifecyclePhaseKind, af as SessionLifecycleRoomBridge, ag as SessionLifecycleRoomBridgeProps, ah as SurfaceLayers, ai as TurnState, aj as TurnTimeoutEvent, ak as UseAvatarQualityGovernorInput, al as UseLiveKitAvatarGrantInput, am as UseRealtimeSessionInput, an as UseSessionLifecycleInput, ao as VoiceSpec, ap as VoiceSpecInput, aq as capacityErrorFromBusy, ar as capacityStateFromGrant, as as createProxyClient, at as isNativeLiveTrackSubscribed, au as knownBehaviorStates, av as mapTurnState, aw as readInboundRtp, ax as sessionBehaviorSchema, ay as sessionClipSchema, az as splitCallTranscript, aA as useAvatarAdaptivePlayoutDelay, aB as useAvatarPlayoutDelay, aC as useAvatarQualityGovernor, aD as useCallTranscript, aE as useLiveKitAvatarGrant, aF as useLiveTrackProducing, aG as useMicLease, aH as useRealtimeSession, aI as useReleaseMicLeaseOnTrackEnded, aJ as useSessionLifecycle } from './proxy-client-DDqYRz-_.js';
4
+ import { L as LiveKitSessionGrant, A as AvatarVideoFit } from './proxy-client-DlYBC6cp.js';
5
+ export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, g as AvatarSessionClient, B as BehaviorSnapshot, C as CallTranscript, h as CallTranscriptSegment, i as CartesiaTtsModel, j as ClipResult, k as ClosingTurnResult, l as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, m as DEFAULT_CREDITS_LOW_LEAD_SECONDS, n as DEFAULT_GOVERNOR_CONFIG, o as DEFAULT_GRACE_CEILING_SECONDS, p as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, q as DEFAULT_IDLE_SECONDS, r as DEFAULT_IDLE_WARN_LEAD_SECONDS, s as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, t as EndedEvent, u as ExtendResult, F as FishTtsModel, v as FreezeReadingFn, G as Governor, w as GovernorAction, x as GovernorConfig, y as GovernorSignal, z as GovernorState, H as GraceWindowClosedEvent, I as GraceWindowOpenEvent, J as GraceWindowState, K as IdleWarningEvent, M as InboundRtpCursor, N as InboundRtpReading, O as KnownBehaviorState, P as LLMProvider, Q as LLMSelection, R as LiveKitAvatarGrantState, S as LiveKitAvatarGrantStatus, T as LiveKitCapacityState, U as LiveKitConnectionStatus, V as LiveKitSessionRequest, W as LiveKitSessionStartResult, X as MAX_RECONNECT_ATTEMPTS, Y as MAX_SESSION_INSTRUCTIONS_CHARS, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, a0 as RealtimeAvatarRequestOptions, a1 as RealtimeSessionApi, a2 as RealtimeSessionMedia, a3 as RealtimeSessionRoomSinks, a4 as ReconnectPolicy, a5 as ReconnectingEvent, a6 as RecoveryState, a7 as RetryStep, a8 as SessionBehavior, a9 as SessionClip, aa as SessionClocks, ab as SessionEndReason, ac as SessionLifecycleApi, ad as SessionLifecyclePhase, ae as SessionLifecyclePhaseKind, af as SessionLifecycleRoomBridge, ag as SessionLifecycleRoomBridgeProps, ah as SurfaceLayers, ai as TurnState, aj as TurnTimeoutEvent, ak as UseAvatarQualityGovernorInput, al as UseLiveKitAvatarGrantInput, am as UseRealtimeSessionInput, an as UseSessionLifecycleInput, ao as VoiceSpec, ap as VoiceSpecInput, aq as capacityErrorFromBusy, ar as capacityStateFromGrant, as as createProxyClient, at as isNativeLiveTrackSubscribed, au as knownBehaviorStates, av as mapTurnState, aw as readInboundRtp, ax as sessionBehaviorSchema, ay as sessionClipSchema, az as splitCallTranscript, aA as useAvatarAdaptivePlayoutDelay, aB as useAvatarPlayoutDelay, aC as useAvatarQualityGovernor, aD as useCallTranscript, aE as useLiveKitAvatarGrant, aF as useLiveTrackProducing, aG as useMicLease, aH as useRealtimeSession, aI as useReleaseMicLeaseOnTrackEnded, aJ as useSessionLifecycle } from './proxy-client-DlYBC6cp.js';
6
6
  import { StyleProp, ViewStyle } from 'react-native';
7
7
  export { useChat, useConnectionState, useLocalParticipant, useRoomContext, useTrackToggle, useTranscriptions, useVoiceAssistant } from '@livekit/components-react';
8
8
  export { D as DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS } from './playout-delay-DWOc8iCl.js';
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
- import { g as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-DDqYRz-_.js';
3
- export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, aK as AvatarVideoSurface, aL as AvatarVideoSurfaceProps, B as BehaviorSnapshot, C as CallTranscript, h as CallTranscriptSegment, i as CartesiaTtsModel, j as ClipResult, k as ClosingTurnResult, l as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, m as DEFAULT_CREDITS_LOW_LEAD_SECONDS, n as DEFAULT_GOVERNOR_CONFIG, o as DEFAULT_GRACE_CEILING_SECONDS, p as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, q as DEFAULT_IDLE_SECONDS, r as DEFAULT_IDLE_WARN_LEAD_SECONDS, s as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, t as EndedEvent, u as ExtendResult, F as FishTtsModel, v as FreezeReadingFn, G as Governor, w as GovernorAction, x as GovernorConfig, y as GovernorSignal, z as GovernorState, H as GraceWindowClosedEvent, I as GraceWindowOpenEvent, J as GraceWindowState, K as IdleWarningEvent, M as InboundRtpCursor, N as InboundRtpReading, O as KnownBehaviorState, P as LLMProvider, Q as LLMSelection, R as LiveKitAvatarGrantState, S as LiveKitAvatarGrantStatus, T as LiveKitCapacityState, U as LiveKitConnectionStatus, V as LiveKitSessionRequest, W as LiveKitSessionStartResult, aM as LivePlaybackKeeper, X as MAX_RECONNECT_ATTEMPTS, Y as MAX_SESSION_INSTRUCTIONS_CHARS, aN as MIC_LEASE_ENDED_TIMEOUT_MS, aO as MicLease, aP as PlayableVideoElement, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, aQ as RealtimeAvatarCapacityError, aR as RealtimeAvatarLiveKitRoom, aS as RealtimeAvatarLiveKitRoomProps, a0 as RealtimeAvatarRequestOptions, a1 as RealtimeSessionApi, a2 as RealtimeSessionMedia, a3 as RealtimeSessionRoomSinks, a4 as ReconnectPolicy, a5 as ReconnectingEvent, a6 as RecoveryState, a7 as RetryStep, a8 as SessionBehavior, a9 as SessionClip, aa as SessionClocks, ab as SessionEndReason, ac as SessionLifecycleApi, ad as SessionLifecyclePhase, ae as SessionLifecyclePhaseKind, af as SessionLifecycleRoomBridge, ag as SessionLifecycleRoomBridgeProps, ah as SurfaceLayers, ai as TurnState, aj as TurnTimeoutEvent, ak as UseAvatarQualityGovernorInput, al as UseLiveKitAvatarGrantInput, am as UseRealtimeSessionInput, an as UseSessionLifecycleInput, ao as VoiceSpec, ap as VoiceSpecInput, aq as capacityErrorFromBusy, ar as capacityStateFromGrant, as as createProxyClient, au as knownBehaviorStates, av as mapTurnState, aw as readInboundRtp, ax as sessionBehaviorSchema, ay as sessionClipSchema, aT as shouldReplayPendingTurn, az as splitCallTranscript, aA as useAvatarAdaptivePlayoutDelay, aB as useAvatarPlayoutDelay, aC as useAvatarQualityGovernor, aD as useCallTranscript, aE as useLiveKitAvatarGrant, aG as useMicLease, aH as useRealtimeSession, aI as useReleaseMicLeaseOnTrackEnded, aJ as useSessionLifecycle } from './proxy-client-DDqYRz-_.js';
2
+ import { g as AvatarSessionClient, A as AvatarVideoFit } from './proxy-client-DlYBC6cp.js';
3
+ export { a as AdaptivePlayoutController, b as AdaptivePlayoutDecision, c as AdaptivePlayoutOptions, d as AdaptivePlayoutSample, e as ApproachingEndEvent, f as ApproachingEndReason, aK as AvatarVideoSurface, aL as AvatarVideoSurfaceProps, B as BehaviorSnapshot, C as CallTranscript, h as CallTranscriptSegment, i as CartesiaTtsModel, j as ClipResult, k as ClosingTurnResult, l as CreditsLowEvent, D as DEFAULT_APPROACHING_END_LEAD_SECONDS, m as DEFAULT_CREDITS_LOW_LEAD_SECONDS, n as DEFAULT_GOVERNOR_CONFIG, o as DEFAULT_GRACE_CEILING_SECONDS, p as DEFAULT_GRACE_WINDOW_LEAD_SECONDS, q as DEFAULT_IDLE_SECONDS, r as DEFAULT_IDLE_WARN_LEAD_SECONDS, s as DEFAULT_TURN_TIMEOUT_SECONDS, E as EndReason, t as EndedEvent, u as ExtendResult, F as FishTtsModel, v as FreezeReadingFn, G as Governor, w as GovernorAction, x as GovernorConfig, y as GovernorSignal, z as GovernorState, H as GraceWindowClosedEvent, I as GraceWindowOpenEvent, J as GraceWindowState, K as IdleWarningEvent, M as InboundRtpCursor, N as InboundRtpReading, O as KnownBehaviorState, P as LLMProvider, Q as LLMSelection, R as LiveKitAvatarGrantState, S as LiveKitAvatarGrantStatus, T as LiveKitCapacityState, U as LiveKitConnectionStatus, V as LiveKitSessionRequest, W as LiveKitSessionStartResult, aM as LivePlaybackKeeper, X as MAX_RECONNECT_ATTEMPTS, Y as MAX_SESSION_INSTRUCTIONS_CHARS, aN as MIC_LEASE_ENDED_TIMEOUT_MS, aO as MicLease, aP as PlayableVideoElement, Z as ProxyClientOptions, _ as QualityCap, $ as RECONNECT_BACKOFF_MS, aQ as RealtimeAvatarCapacityError, aR as RealtimeAvatarLiveKitRoom, aS as RealtimeAvatarLiveKitRoomProps, a0 as RealtimeAvatarRequestOptions, a1 as RealtimeSessionApi, a2 as RealtimeSessionMedia, a3 as RealtimeSessionRoomSinks, a4 as ReconnectPolicy, a5 as ReconnectingEvent, a6 as RecoveryState, a7 as RetryStep, a8 as SessionBehavior, a9 as SessionClip, aa as SessionClocks, ab as SessionEndReason, ac as SessionLifecycleApi, ad as SessionLifecyclePhase, ae as SessionLifecyclePhaseKind, af as SessionLifecycleRoomBridge, ag as SessionLifecycleRoomBridgeProps, ah as SurfaceLayers, ai as TurnState, aj as TurnTimeoutEvent, ak as UseAvatarQualityGovernorInput, al as UseLiveKitAvatarGrantInput, am as UseRealtimeSessionInput, an as UseSessionLifecycleInput, ao as VoiceSpec, ap as VoiceSpecInput, aq as capacityErrorFromBusy, ar as capacityStateFromGrant, as as createProxyClient, au as knownBehaviorStates, av as mapTurnState, aw as readInboundRtp, ax as sessionBehaviorSchema, ay as sessionClipSchema, aT as shouldReplayPendingTurn, az as splitCallTranscript, aA as useAvatarAdaptivePlayoutDelay, aB as useAvatarPlayoutDelay, aC as useAvatarQualityGovernor, aD as useCallTranscript, aE as useLiveKitAvatarGrant, aG as useMicLease, aH as useRealtimeSession, aI as useReleaseMicLeaseOnTrackEnded, aJ as useSessionLifecycle } from './proxy-client-DlYBC6cp.js';
4
4
  export { D as DEFAULT_AVATAR_PLAYOUT_DELAY_SECONDS } from './playout-delay-DWOc8iCl.js';
5
5
  import '@livekit/components-react';
6
6
  import 'livekit-client';
package/dist/react.js CHANGED
@@ -978,6 +978,33 @@ function useAvatarQualityGovernor(input) {
978
978
  }, [enabled, room, videoTrack, freezeReading, config, tickMs]);
979
979
  }
980
980
 
981
+ // ../client/src/react/frame-recovery.ts
982
+ var FrameRecovery = class {
983
+ constructor(stallAfterMs) {
984
+ this.stallAfterMs = stallAfterMs;
985
+ }
986
+ stallAfterMs;
987
+ lastFrameAtMs = null;
988
+ stableSinceMs = null;
989
+ ready = true;
990
+ stall() {
991
+ this.ready = false;
992
+ this.stableSinceMs = null;
993
+ }
994
+ frame(nowMs) {
995
+ const gapMs = this.lastFrameAtMs === null ? 0 : nowMs - this.lastFrameAtMs;
996
+ if (this.lastFrameAtMs !== null && gapMs > this.stallAfterMs) this.stall();
997
+ if (!this.ready) {
998
+ if (this.stableSinceMs === null || gapMs > Math.min(350, this.stallAfterMs)) {
999
+ this.stableSinceMs = nowMs;
1000
+ }
1001
+ this.ready = nowMs - this.stableSinceMs >= 500;
1002
+ }
1003
+ this.lastFrameAtMs = nowMs;
1004
+ return this.ready;
1005
+ }
1006
+ };
1007
+
981
1008
  // ../client/src/react/avatar-video-surface.ts
982
1009
  var DEFAULT_AVATAR_FRAME_STALL_MS = 800;
983
1010
  var AVATAR_FRAME_GAP_FREEZE_FLOOR_MS = 100;
@@ -1045,7 +1072,12 @@ function AvatarVideoSurface(props) {
1045
1072
  const liveDims = videoTrack?.publication?.dimensions ?? null;
1046
1073
  const idleVideoRef = useRef(null);
1047
1074
  const boxAspect = aspectRatio ?? null;
1048
- useIdleWhileResting(idleVideoRef, idleVideoUrl, showLive);
1075
+ useIdleWhileResting(
1076
+ idleVideoRef,
1077
+ idleVideoUrl,
1078
+ showLive,
1079
+ !live || connectionState === ConnectionState.Disconnected
1080
+ );
1049
1081
  useLiveResumeOnProducing(liveWrapRef, trackProducing);
1050
1082
  const fitClass = fit === "cover" ? AVATAR_VIDEO_FIT_COVER : AVATAR_VIDEO_FIT_CONTAIN;
1051
1083
  const liveLabel = showLive && liveDims && liveDims.width > 0 && liveDims.height > 0 ? `${liveDims.width}\xD7${liveDims.height}` : null;
@@ -1177,6 +1209,7 @@ function isFrameFreezeInhibited(snapshot) {
1177
1209
  }
1178
1210
  function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs) {
1179
1211
  const boundedStallMs = normalizeFrameStallMs(stallAfterMs);
1212
+ const recovery = useMemo(() => new FrameRecovery(boundedStallMs), [trackIdentity, boundedStallMs]);
1180
1213
  const [flowing, setFlowing] = useState(false);
1181
1214
  const [seenFrame, setSeenFrame] = useState(false);
1182
1215
  const flowingRef = useRef(false);
@@ -1206,6 +1239,7 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
1206
1239
  const markFrame = () => {
1207
1240
  const previous = sampleRef.current;
1208
1241
  const now = Date.now();
1242
+ lastCurrentTime = video?.currentTime ?? lastCurrentTime;
1209
1243
  const firstFrame = !previous.seenFrame;
1210
1244
  sampleRef.current = {
1211
1245
  seenFrame: true,
@@ -1219,9 +1253,10 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
1219
1253
  resumePending: false
1220
1254
  };
1221
1255
  if (firstFrame) setSeenFrame(true);
1222
- if (!flowingRef.current) {
1223
- flowingRef.current = true;
1224
- setFlowing(true);
1256
+ const nextFlowing = recovery.frame(now);
1257
+ if (flowingRef.current !== nextFlowing) {
1258
+ flowingRef.current = nextFlowing;
1259
+ setFlowing(nextFlowing);
1225
1260
  }
1226
1261
  };
1227
1262
  const cancelFrameCallback = () => {
@@ -1263,12 +1298,14 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
1263
1298
  lastCurrentTime = video.currentTime;
1264
1299
  markFrame();
1265
1300
  }
1266
- const nextFlowing = isFrameFlowingAt({
1301
+ const frameFresh = isFrameFlowingAt({
1267
1302
  seenFrame: sampleRef.current.seenFrame,
1268
1303
  lastFrameAtMs: sampleRef.current.lastFrameAtMs,
1269
1304
  nowMs: Date.now(),
1270
1305
  stallAfterMs: boundedStallMs
1271
1306
  });
1307
+ if (!frameFresh && sampleRef.current.seenFrame) recovery.stall();
1308
+ const nextFlowing = frameFresh && recovery.ready;
1272
1309
  if (flowingRef.current !== nextFlowing) {
1273
1310
  flowingRef.current = nextFlowing;
1274
1311
  setFlowing(nextFlowing);
@@ -1281,7 +1318,7 @@ function useLiveFrameFlow(wrapRef, trackProducing, trackIdentity, stallAfterMs)
1281
1318
  stopObserving();
1282
1319
  cancelFrameCallback();
1283
1320
  };
1284
- }, [wrapRef, trackProducing, trackIdentity, boundedStallMs]);
1321
+ }, [wrapRef, trackProducing, trackIdentity, boundedStallMs, recovery]);
1285
1322
  const freezeReading = useCallback(() => {
1286
1323
  const sample = sampleRef.current;
1287
1324
  const lastFrameAtMs = sample.lastFrameAtMs;
@@ -1320,19 +1357,19 @@ function useDebouncedHide(wanted, delayMs) {
1320
1357
  }, [wanted, delayMs]);
1321
1358
  return shown;
1322
1359
  }
1323
- function useIdleWhileResting(videoRef, idleVideoUrl, showLive) {
1360
+ function useIdleWhileResting(videoRef, idleVideoUrl, showLive, resetToAnchor) {
1324
1361
  useEffect(() => {
1325
1362
  const video = videoRef.current;
1326
1363
  if (!video || !idleVideoUrl) return;
1327
- applyIdleRest(video, showLive);
1328
- }, [videoRef, idleVideoUrl, showLive]);
1364
+ applyIdleRest(video, showLive, resetToAnchor);
1365
+ }, [videoRef, idleVideoUrl, showLive, resetToAnchor]);
1329
1366
  }
1330
- function applyIdleRest(video, showLive) {
1367
+ function applyIdleRest(video, showLive, resetToAnchor = true) {
1331
1368
  if (showLive) {
1332
1369
  video.pause();
1333
1370
  return;
1334
1371
  }
1335
- if (video.currentTime !== 0) video.currentTime = 0;
1372
+ if (resetToAnchor && video.currentTime !== 0) video.currentTime = 0;
1336
1373
  if (video.paused) void Promise.resolve(video.play()).catch(() => {
1337
1374
  });
1338
1375
  }
package/dist/server.js CHANGED
@@ -1 +1 @@
1
- export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-IQUJLPYF.js';
1
+ export { RealtimeAvatar, RealtimeAvatarError, RealtimeAvatarHttpError, isQueued, verifyTranscript } from './chunk-F7E4HMBW.js';
@@ -1,5 +1,5 @@
1
- import { createProxyHandler } from './chunk-A2ATKWJT.js';
2
- import './chunk-IQUJLPYF.js';
1
+ import { createProxyHandler } from './chunk-FTAD423K.js';
2
+ import './chunk-F7E4HMBW.js';
3
3
 
4
4
  // src/tanstack-start.ts
5
5
  function realtimeAvatarServerRoute(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realtime-avatar",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Realtime Avatar SDK. `realtime-avatar` and the route adapters hold your API key and are server-only; `realtime-avatar/react`, `/browser` and `/tools` never can — importing a server entry into a browser build throws at bundle time.",
5
5
  "license": "MIT",
6
6
  "author": "The Influence Company",