Skip to content

fix(MediaRecorder): guard pause/resume against inactive recorder to prevent InvalidStateError#3218

Open
MartinCupela wants to merge 2 commits into
masterfrom
fix/MediaRecorder/prevent-invalid-state-error-v14
Open

fix(MediaRecorder): guard pause/resume against inactive recorder to prevent InvalidStateError#3218
MartinCupela wants to merge 2 commits into
masterfrom
fix/MediaRecorder/prevent-invalid-state-error-v14

Conversation

@MartinCupela

@MartinCupela MartinCupela commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🎯 Goal

Port changes from #3217 to v14.

πŸ›  Implementation details

Root cause
In MediaRecorderController, pause() guards only against the controller's internal state subject, then calls the native API unconditionally:

pause = () => {
  if (this.recordingState.value !== MediaRecordingState.RECORDING) return;
  ...
  this.mediaRecorder?.pause();   // ← throws if native state is 'inactive'
  ...
};

The internal recordingState BehaviorSubject can drift out of sync with the native mediaRecorder.state. On iOS Safari the native MediaRecorder can transition to inactive on its own (interruptions, the stop button's native stop(), track ending) while the React-driven recordingState subject still reads RECORDING. When the user taps stop then immediately pause, pause() passes its internal-state guard but this.mediaRecorder.pause() hits an inactive recorder β†’ InvalidStateError: The MediaRecorder's state cannot be inactive.

Note the inconsistency: stop() already guards against the native this.mediaRecorder?.state, but pause()/resume() guard only against the internal subject. That asymmetry is the bug.

Summary by CodeRabbit

  • Bug Fixes
    • Improved stability of media recording to prevent errors during pause and resume, especially on iOS Safari when the native recorder state changes unexpectedly.
  • Tests
    • Added regression tests covering pause/resume behavior to ensure operations no longer throw or invoke failing native calls in edge-state scenarios.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. πŸŽ‰

ℹ️ Recent review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9cd4b81-4fa0-4ebf-863c-ebda2ff5b073

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 1b4ce93 and 6084810.

πŸ“’ Files selected for processing (1)
  • src/components/MediaRecorder/classes/__tests__/MediaRecorderController.test.ts
πŸ’€ Files with no reviewable changes (1)
  • src/components/MediaRecorder/classes/tests/MediaRecorderController.test.ts

πŸ“ Walkthrough

Walkthrough

Adds native-state guards to MediaRecorderController.pause() and .resume() to avoid calling native methods when the underlying MediaRecorder is not in the required state, and adds regression tests that simulate an inactive native recorder to verify no throws or native calls occur.

Changes

MediaRecorder pause/resume state guards

Layer / File(s) Summary
State guards for pause and resume
src/components/MediaRecorder/classes/MediaRecorderController.ts
pause() checks the native recorder is 'recording' before calling pause(); resume() checks the native recorder is 'paused' before calling resume(). Early returns prevent native calls when the recorder has become inactive.
Regression tests for inactive state
src/components/MediaRecorder/classes/__tests__/MediaRecorderController.test.ts
New tests simulate an inactive native MediaRecorder and assert that MediaRecorderController.pause() and resume() neither throw nor invoke the native pause()/resume() methods.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I’m a rabbit with a careful eye,
I watch the recorder puff and sigh,
If it’s inactive, I step aside,
No frantic calls, no error cried,
Tests hop inβ€”now peaceified. πŸ‡βœ¨

πŸš₯ Pre-merge checks | βœ… 5
βœ… Passed checks (5 passed)
Check name Status Explanation
Title check βœ… Passed The title clearly and concisely describes the main change: adding guards to pause/resume methods to prevent InvalidStateError when the native recorder is inactive.
Description check βœ… Passed The description covers the Goal (port changes from #3217) and detailed Implementation details explaining root cause and the fix, but lacks the UI Changes section.
Docstring Coverage βœ… Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
πŸ“ Generate docstrings
  • Create stacked PR
  • Commit on current branch
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/MediaRecorder/prevent-invalid-state-error-v14

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.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown

Size Change: +12 B (0%)

Total Size: 656 kB

πŸ“¦ View Changed
Filename Size Change
dist/cjs/index.js 255 kB +8 B (0%)
dist/es/index.mjs 252 kB +6 B (0%)
dist/es/useNotificationApi.mjs 48.6 kB -2 B (0%)
ℹ️ View Unchanged
Filename Size
dist/cjs/audioProcessing.js 1.74 kB
dist/cjs/emojis.js 2.54 kB
dist/cjs/mp3-encoder.js 814 B
dist/cjs/ReactPlayerWrapper.js 545 B
dist/cjs/useNotificationApi.js 49.8 kB
dist/css/emoji-picker.css 178 B
dist/css/emoji-replacement.css 456 B
dist/css/index.css 39.7 kB
dist/es/audioProcessing.mjs 1.65 kB
dist/es/emojis.mjs 2.47 kB
dist/es/mp3-encoder.mjs 768 B
dist/es/ReactPlayerWrapper.mjs 485 B

compressed-size-action

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/components/MediaRecorder/classes/__tests__/MediaRecorderController.test.ts (1)

421-442: πŸ’€ Low value

Minor redundancy in test setup.

Line 429 sets controller.recordingState.next(MediaRecordingState.PAUSED), but this is redundant since line 427 controller.pause() already updates the recording state to PAUSED. While not a functional issue, removing it would reduce noise.

♻️ Optional simplification
      const controller = new MediaRecorderController();
      await controller.start();
      controller.pause();
      const recorder = controller.mediaRecorder as unknown as MediaRecorderMockType;
-     controller.recordingState.next(MediaRecordingState.PAUSED);
      recorder.state = 'inactive';
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/components/MediaRecorder/classes/__tests__/MediaRecorderController.test.ts`
around lines 421 - 442, The test duplicates state setup: after calling
controller.pause() the recording state is already PAUSED, so remove the
redundant manual state mutation
controller.recordingState.next(MediaRecordingState.PAUSED) in the test 'does not
resume the native recorder when its state is inactive'; keep controller.pause(),
the recorder mock setup (recorder.state = 'inactive' and
recorder.resume.mockImplementation(...)), and the expect assertions intact to
simplify the test while preserving behavior.
πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@src/components/MediaRecorder/classes/__tests__/MediaRecorderController.test.ts`:
- Around line 421-442: The test duplicates state setup: after calling
controller.pause() the recording state is already PAUSED, so remove the
redundant manual state mutation
controller.recordingState.next(MediaRecordingState.PAUSED) in the test 'does not
resume the native recorder when its state is inactive'; keep controller.pause(),
the recorder mock setup (recorder.state = 'inactive' and
recorder.resume.mockImplementation(...)), and the expect assertions intact to
simplify the test while preserving behavior.

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4c228558-9c9c-4759-9d8a-cd4f24f64094

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 0b95202 and 1b4ce93.

πŸ“’ Files selected for processing (2)
  • src/components/MediaRecorder/classes/MediaRecorderController.ts
  • src/components/MediaRecorder/classes/__tests__/MediaRecorderController.test.ts

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 83.92%. Comparing base (0b95202) to head (6084810).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3218      +/-   ##
==========================================
- Coverage   83.95%   83.92%   -0.03%     
==========================================
  Files         439      439              
  Lines       13222    13224       +2     
  Branches     4293     4295       +2     
==========================================
- Hits        11100    11098       -2     
- Misses       2122     2126       +4     

β˜” View full report in Codecov by Harness.
πŸ“’ Have feedback on the report? Share it here.

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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