fix: camera remains active after closing video recording modal#38491
fix: camera remains active after closing video recording modal#38491abhinavkrin wants to merge 4 commits intodevelopfrom
Conversation
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
🦋 Changeset detectedLatest commit: 05f756f The changes in this PR will be included in the next version bump. This PR includes changesets to release 40 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughAdds a changeset, unit tests, and modifies VideoRecorder to use a sessionId guard and a stopStreamTracks utility so pending getUserMedia results are stopped if stop() is called before getUserMedia resolves. Also updates Jest to discover client-side spec files. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant VideoRecorder
participant BrowserAPI as "navigator.mediaDevices"
User->>VideoRecorder: start()
Note over VideoRecorder: capture currentSessionId = 1
VideoRecorder->>BrowserAPI: getUserMedia()
User->>VideoRecorder: stop()
Note over VideoRecorder: increment sessionId = 2\nstop active stream if any
BrowserAPI-->>VideoRecorder: getUserMedia resolves (stream)
Note over VideoRecorder: captured 1 != sessionId 2\ncall stopStreamTracks(stream)
Note over BrowserAPI: Media tracks stopped (camera released)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/meteor/app/ui/client/lib/recorderjs/videoRecorder.ts`:
- Line 127: The code currently references the video pause method without
invoking it (this.videoel.pause), so update the call in videoRecorder.ts to
invoke the method (this.videoel.pause()); also guard the call by checking the
element and the method exist (e.g., if (this.videoel && typeof
this.videoel.pause === 'function') this.videoel.pause()) to avoid runtime errors
when videoel is null/undefined.
🧹 Nitpick comments (1)
apps/meteor/app/ui/client/lib/recorderjs/videoRecorder.spec.ts (1)
52-54: Consider resetting VideoRecorder state between tests.Since
VideoRecorderis a singleton and tests directly mutate internal state (e.g., lines 167-169), there's potential for state leakage between tests. Consider adding a reset mechanism or callingVideoRecorder.stop()inafterEachto ensure clean state.♻️ Suggested improvement
afterEach(() => { + // Reset VideoRecorder state to ensure test isolation + VideoRecorder.stop(); jest.clearAllMocks(); });
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #38491 +/- ##
=========================================
Coverage 70.44% 70.44%
=========================================
Files 3162 3161 -1
Lines 110340 110740 +400
Branches 19841 19911 +70
=========================================
+ Hits 77726 78008 +282
- Misses 30581 30705 +124
+ Partials 2033 2027 -6
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/meteor/app/ui/client/lib/recorderjs/videoRecorder.ts`:
- Around line 20-22: Remove inline implementation comments that describe how the
code works; specifically delete the implementation comments around the
session-guard logic for the private field sessionId and the other noted inline
comments (near the block containing sessionId, the block at lines referenced as
36-42, and the block at 117-118). Keep the code and any necessary JSDoc/type
comments that describe public intent or API, but remove explanatory
implementation comments that repeat what the code already expresses (e.g.,
comments immediately above or beside the sessionId field and the internal guard
logic in start/stop or initialization functions).
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| describe('Race condition fix', () => { |
There was a problem hiding this comment.
Although this is a test suite aimed at the race condition fix, describing it like that seems very specific imo. I recommend changing to something along the lines of start and stop edge case handling or Asynchronous start and stop handling, etc.
Something that describes what we are testing instead of why the tests were added.
| const streamPromise = new Promise<MediaStream>((resolve) => { | ||
| setTimeout(() => resolve(mockStream), 100); | ||
| }); |
There was a problem hiding this comment.
You could reduce the use of setTimeout in this file by using the util createDeferredPromise. In this this instance the implementation could change to something like:
const streamDeferred = createDeferredPromise<MediaStream>();
getUserMediaMock.mockReturnValue(streamDeferred.promise);
// [...]
streamDeferred.resolve(mockStream);
// [...]| VideoRecorder.stop(); | ||
|
|
||
| await streamPromise; | ||
| await new Promise((resolve) => setTimeout(resolve, 150)); |
There was a problem hiding this comment.
Could we take advantage of jest fake timers and jest.runAllTicks to eliminate some of these arbitrary timeouts?
| const stream1 = { | ||
| getVideoTracks: jest.fn(() => [{ stop: jest.fn() } as unknown as MediaStreamTrack]), | ||
| getAudioTracks: jest.fn(() => [{ stop: jest.fn() } as unknown as MediaStreamTrack]), | ||
| } as unknown as MediaStream; | ||
|
|
||
| const stream2 = { | ||
| getVideoTracks: jest.fn(() => [mockVideoTrack]), | ||
| getAudioTracks: jest.fn(() => [mockAudioTrack]), | ||
| } as unknown as MediaStream; |
There was a problem hiding this comment.
Nitpick: You could create a small utility function to facilitate creating these mock streams. Would improve code readability as well to some extent,
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
| stop: jest.fn(), | ||
| } as unknown as MediaStreamTrack; | ||
|
|
||
| mockStream = { |
There was a problem hiding this comment.
mockStream = createMockStream(...)
| await Promise.resolve(); | ||
| jest.runAllTimers(); | ||
| await Promise.resolve(); |
There was a problem hiding this comment.
await Promise.resolve() is doing all the heavy lifting here, rendering the fake timers unnecessary. This applies to all other tests in this file.
| await Promise.resolve(); | |
| jest.runAllTimers(); | |
| await Promise.resolve(); | |
| await jest.runAllTimersAsync(); |
| getAudioTracks: jest.fn(() => [mockAudioTrack]), | ||
| } as unknown as MediaStream; | ||
|
|
||
| mockVideoElement = document.createElement('video'); |
There was a problem hiding this comment.
jsdom doesn't seem to support any loading or playback media operations. When running these tests it ends up generating a lot of console.error's which makes it difficult to read the logs. Could you mock the necessary functions as well?
Proposed changes (including videos or screenshots)
When users close the video recording modal quickly before the camera has fully initialized, the camera light remains on until the page is refreshed. This occurs due to a race condition where stop() is called before the asynchronous getUserMedia() callback completes, causing the stream to initialize after cleanup has been attempted
Why Session ID Mechanism is Needed
Without a way to invalidate pending async callbacks, the original code had no mechanism to prevent stale getUserMedia success handlers from executing after stop() was called. Even though stop() cleared the stream reference, the pending callback would still execute and reinitialize the camera, leaving it active indefinitely.
Screen.Recording.2026-02-04.at.6.51.09.PM.mov
Issue(s)
Steps to test or reproduce
Further comments
CORE-1301
Summary by CodeRabbit