Skip to content

ECHO-588 Better mechanism to keep a device awake (during recording)#389

Merged
spashii merged 1 commit intomainfrom
testing
Dec 8, 2025
Merged

ECHO-588 Better mechanism to keep a device awake (during recording)#389
spashii merged 1 commit intomainfrom
testing

Conversation

@ussaama
Copy link
Copy Markdown
Contributor

@ussaama ussaama commented Dec 8, 2025

Summary by CodeRabbit

  • New Features
    • Enhanced wake-lock management during recording sessions with automatic fallback support for improved stability and reliability
    • Better device-wake maintenance on iOS devices experiencing low-battery scenarios through alternative mechanisms when native wake-lock is unavailable
    • More robust recording functionality with comprehensive device activity management across varying power states

✏️ Tip: You can customize this high-level summary in your review settings.

@linear
Copy link
Copy Markdown

linear bot commented Dec 8, 2025

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 8, 2025

Walkthrough

Introduces a video-based fallback mechanism for device wake-lock persistence during recording. When native wake lock fails on iOS low-battery scenarios, a silent 1-pixel video plays instead. Enhances wake lock hook to expose lifecycle states and improves obtain/release handling with proper cleanup.

Changes

Cohort / File(s) Summary
Wake Lock Hook Enhancement
echo/frontend/src/hooks/useWakeLock.ts
Added isActive and isSupported state flags to track wake lock lifecycle. Enhanced obtainWakeLock with release listener cleanup and proper error handling. Exposed both flags in return object for downstream consumption.
Video Fallback Hook (New)
echo/frontend/src/hooks/useVideoWakeLockFallback.ts
New hook providing iOS low-battery fallback via hidden 1px video playback. Manages video element lifecycle, playback verification via periodic interval, and conditional activation based on recording + wake lock states. Returns isActive and videoElement references.
Component Integration
echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
Refactored to use named wakeLock instance from useWakeLock hook and integrated useVideoWakeLockFallback with wakeLock.isActive and isRecording props.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • useVideoWakeLockFallback.ts: New hook with DOM manipulation, ref management, and interval logic—verify cleanup paths and edge cases around video lifecycle
  • useWakeLock.ts: Verify event listener attachment/cleanup and state synchronization during obtain/release transitions
  • ParticipantConversationAudio.tsx: Confirm prop plumbing and interaction between hooks is correct

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements the primary coding requirement from ECHO-588: playing a background video as a fallback mechanism to keep devices awake during recording. However, audio worker and mediabunny integration objectives are not addressed. Implement the audio worker for splitting/saving raw audio files and integrate with mediabunny for download functionality to fully satisfy ECHO-588 requirements.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: a better mechanism to keep devices awake during recording, which directly matches the implementation of wake-lock handling and video fallback functionality.
Out of Scope Changes check ✅ Passed All code changes are scoped to wake-lock and video fallback mechanisms directly supporting the device wake functionality objective. No out-of-scope modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch testing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ussaama ussaama requested a review from spashii December 8, 2025 12:53
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 77c2fb8 and 78c410b.

📒 Files selected for processing (3)
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx (3 hunks)
  • echo/frontend/src/hooks/useVideoWakeLockFallback.ts (1 hunks)
  • echo/frontend/src/hooks/useWakeLock.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
echo/frontend/**/src/**/*.{css,tsx,ts}

📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)

Use CSS variables var(--app-background) and var(--app-text) instead of hardcoded colors like #F6F4F1 or #2D2D2C to ensure theme changes propagate

Files:

  • echo/frontend/src/hooks/useVideoWakeLockFallback.ts
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
  • echo/frontend/src/hooks/useWakeLock.ts
echo/frontend/**/src/{routes,components}/**/*.tsx

📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)

echo/frontend/**/src/{routes,components}/**/*.tsx: Compose Mantine primitives (Stack, Group, ActionIcon, etc.) while layering Tailwind utility classes via className, alongside toast feedback via @/components/common/Toaster
For Tailwind classes that need dynamic values, replace with inline style props using CSS variables instead of hardcoded hex values

Files:

  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
echo/frontend/**/src/{theme.tsx,components,routes}/**/*.tsx

📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)

When adding new Mantine components, reference the global Mantine theme in src/theme.tsx with custom primary color palette and component defaults, preferring CSS variables in styles over hardcoded hex values

Files:

  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
echo/frontend/**/src/{components,routes}/**/*.tsx

📄 CodeRabbit inference engine (echo/frontend/AGENTS.md)

UI mutations should surface inline feedback: pair toasts with contextual Mantine Alert components inside modals/forms for errors or warnings

Files:

  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
🧠 Learnings (3)
📓 Common learnings
Learnt from: ussaama
Repo: Dembrane/echo PR: 205
File: echo/frontend/src/lib/query.ts:1444-1506
Timestamp: 2025-07-10T12:48:20.683Z
Learning: ussaama prefers string concatenation over template literals for simple cases where readability is clearer, even when linting tools suggest template literals. Human readability takes precedence over strict linting rules in straightforward concatenation scenarios.
📚 Learning: 2025-08-06T13:38:30.769Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 256
File: echo/frontend/src/components/participant/MicrophoneTest.tsx:54-54
Timestamp: 2025-08-06T13:38:30.769Z
Learning: In echo/frontend/src/components/participant/MicrophoneTest.tsx, the useDisclosure hook is used where only the `close` function is needed locally (called in handleConfirmMicChange and handleCancelMicChange), while `opened` and `open` are unused because the modal state is managed by a parent component. The `showSecondModal` state variable is used separately to control content switching within the modal between the main UI and confirmation step.

Applied to files:

  • echo/frontend/src/hooks/useVideoWakeLockFallback.ts
  • echo/frontend/src/components/participant/ParticipantConversationAudio.tsx
📚 Learning: 2025-12-05T00:33:33.409Z
Learnt from: CR
Repo: Dembrane/echo PR: 0
File: echo/frontend/AGENTS.md:0-0
Timestamp: 2025-12-05T00:33:33.409Z
Learning: Applies to echo/frontend/**/src/routes/auth/**/*.tsx : Auth hero uses `/public/video/auth-hero.mp4` with `/public/video/auth-hero-poster.jpg` as poster; keep the bright blur overlay consistent when iterating on onboarding screens

Applied to files:

  • echo/frontend/src/hooks/useVideoWakeLockFallback.ts
🧬 Code graph analysis (1)
echo/frontend/src/components/participant/ParticipantConversationAudio.tsx (2)
echo/frontend/src/hooks/useWakeLock.ts (1)
  • useWakeLock (3-91)
echo/frontend/src/hooks/useVideoWakeLockFallback.ts (1)
  • useVideoWakeLockFallback (6-126)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-check-server
🔇 Additional comments (7)
echo/frontend/src/components/participant/ParticipantConversationAudio.tsx (1)

28-29: LGTM - clean integration of the video wake lock fallback.

The hook wiring is correct. Passing wakeLock.isActive to determine when to engage the fallback is the right call. The side-effect-only usage pattern (not destructuring the return) is totally valid here since you just need the hook to manage the DOM video element lifecycle.

Ship it. 🚀

Also applies to: 104-104, 118-122

echo/frontend/src/hooks/useWakeLock.ts (3)

9-22: Solid cleanup pattern - listener removal before release.

The releaseHandlerRef pattern here is 10x. Prevents the classic "stale listener on a released sentinel" footgun. One minor note: wakeLock.current.release() on line 18 returns a Promise that's not awaited. In this sync cleanup context that's acceptable, but if you ever need guaranteed ordering, you'd want to make this async.


24-61: LGTM - proper wakelock lifecycle management.

Releasing the old wakelock before requesting a new one (lines 29-40) is the correct approach. Avoids resource leaks and race conditions with multiple concurrent sentinels. The try-catch with setIsActive(false) on failure is defensive and correct.


63-82: Empty dep array is intentional but verify obtainWakeLockOnMount behavior.

The lint suppression is fine for "run once on mount" semantics. Just be aware: if a consumer ever passes a dynamic obtainWakeLockOnMount that changes after mount, it won't re-evaluate. Given current usage in ParticipantConversationAudio.tsx where it's hardcoded to true, this is totally fine.

echo/frontend/src/hooks/useVideoWakeLockFallback.ts (3)

3-4: Based inline video data - zero network round trips.

Embedding the minimal H.264 video as base64 is the big brain move here. No CDN dependency, no network latency, instant playback. This will work even in offline scenarios which is clutch for recording use cases.


122-125: videoElement return value won't update after creation.

Refs don't trigger re-renders, so videoRef.current will be null on initial render and consumers won't see the updated value unless they re-render for unrelated reasons. If you need reactive access to the element, you'd need to track it with state instead.

That said, looking at the consumer in ParticipantConversationAudio.tsx, the return values aren't being used - the hook is purely for side effects. So this is fine as-is, but be aware if future consumers rely on videoElement being accurate.


90-99: 5s interval for iOS pause detection is reasonable.

iOS can be aggressive about pausing background media. The periodic check + restart pattern is the right workaround. If you ever see issues in the wild, could consider reducing to 2-3s, but 5s should be fine for most cases.

Comment on lines +20 to +35
if (!shouldActivateFallback) {
// Clean up video element if it exists
if (videoRef.current) {
videoRef.current.pause();
videoRef.current.src = "";
videoRef.current.remove();
videoRef.current = null;
console.log(
"[VideoWakeLockFallback] Cleaned up video - wakelock is working",
);
}
if (checkIntervalRef.current) {
clearInterval(checkIntervalRef.current);
checkIntervalRef.current = null;
}
return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider extracting cleanup logic to avoid duplication.

The cleanup code at lines 22-34 and lines 109-118 is nearly identical. Not a blocker, but could DRY this up with a helper:

+const cleanupVideo = (
+  videoRef: React.MutableRefObject<HTMLVideoElement | null>,
+  checkIntervalRef: React.MutableRefObject<NodeJS.Timeout | null>,
+) => {
+  if (videoRef.current) {
+    videoRef.current.pause();
+    videoRef.current.src = "";
+    videoRef.current.remove();
+    videoRef.current = null;
+  }
+  if (checkIntervalRef.current) {
+    clearInterval(checkIntervalRef.current);
+    checkIntervalRef.current = null;
+  }
+};

Then call cleanupVideo(videoRef, checkIntervalRef) in both places. Optional refactor.

Also applies to: 107-119

🤖 Prompt for AI Agents
In echo/frontend/src/hooks/useVideoWakeLockFallback.ts around lines 20-35 and
107-119, the video cleanup logic is duplicated; extract a small helper function
(e.g., cleanupVideo) that accepts the videoRef and checkIntervalRef, performs
null checks, pauses the video, clears src, removes the element, nulls the
videoRef, clears and nulls the interval ref, and emits the same console log;
replace both duplicated blocks with a single call to cleanupVideo(videoRef,
checkIntervalRef) to keep behavior identical and preserve types and null-safety.

@spashii spashii added this pull request to the merge queue Dec 8, 2025
Merged via the queue into main with commit 176aaf3 Dec 8, 2025
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants