Skip to content

chore: migrate from expo-av to expo-audio and expo-video#6719

Open
Rohit3523 wants to merge 233 commits into
developfrom
expo-audio
Open

chore: migrate from expo-av to expo-audio and expo-video#6719
Rohit3523 wants to merge 233 commits into
developfrom
expo-audio

Conversation

@Rohit3523

@Rohit3523 Rohit3523 commented Oct 12, 2025

Copy link
Copy Markdown
Member

Proposed changes

Depends on: #6875

Issue(s)

https://rocketchat.atlassian.net/browse/CORE-2035

How to test or reproduce

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • Refactor

    • Migrated audio playback, recording, and VOIP playback to a new audio stack.
    • Updated ringer audio and audio seeking to use the new player/recorder flows.
    • Switched attachment and share preview video rendering to a new video player.
  • Bug Fixes

    • Improved seek gesture handling with accurate, snapshot-based rollback and synchronous time updates.
    • Strengthened end-of-playback detection, cleanup, and listener release behavior.
    • Enhanced video loading/error handling and pause-on-navigation behavior.
  • Chores

    • Updated Jest mocks and media API tests; removed old audio test stubs.

@coderabbitai

coderabbitai Bot commented Oct 12, 2025

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Migrates audio and video usage from expo-av to expo-audio and expo-video across recording, playback, VoIP, ringer, previews, tests, and package dependencies, and updates seek and status handling to the new APIs.

Changes

Expo media migration

Layer / File(s) Summary
Seek and playback status
app/containers/AudioPlayer/Seek.tsx, app/containers/AudioPlayer/index.tsx
Seek callbacks become synchronous, gesture finalize uses saved seek snapshots, and playback status handling maps expo-audio fields to player state and end detection.
Recorder permissions and lifecycle
app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx, app/containers/MessageComposer/components/RecordAudio/Duration.tsx, app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
Recording permissions move to expo-audio APIs, recorder state types change to RecorderState, and recording now uses recorder hooks, stop-based cleanup, and recorder URI/state for upload and review paths.
VoIP end-of-call sound and tests
app/lib/services/voip/playCallEndedSound.ts, app/lib/services/voip/playCallEndedSound.test.ts
VoIP call-ended playback uses a synchronous expo-audio player with watchdog cleanup, and the tests validate creation, completion, coalescing, timeout cleanup, and error logging.
AudioManager factory and caches
app/lib/methods/AudioManager.ts
AudioManager becomes a factory-backed expo-audio manager with per-key player caches, listener management, seek and rate restoration, end handling, sequence lookup, and release-based unload logic.
Video rendering and preview
app/views/AttachmentView.tsx, app/views/ShareView/Preview.tsx, app/views/AttachmentView.test.tsx
Attachment and share preview video playback use expo-video players and views, with loading, error, blur, and sizing behavior moved into local video components and the test mock removed.
Ringer playback
app/containers/Ringer/index.tsx
The ringer component switches to expo-audio player hooks, maps sound names to assets, and starts looping playback from the created player.
Package dependencies
package.json
expo-audio replaces expo-av in dependencies, and expo-video is added.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • diegolmello
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 matches the main change: migrating media handling from expo-av to expo-audio and expo-video.
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.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • CORE-2035: Request failed with status code 401

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.

@Rohit3523

Copy link
Copy Markdown
Member Author

Recording is not working... idk why...

@Rohit3523 Rohit3523 had a problem deploying to approve_e2e_testing May 11, 2026 17:04 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_android_build May 11, 2026 17:08 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to official_android_build May 11, 2026 17:08 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_ios_build May 11, 2026 17:08 — with GitHub Actions Error

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/lib/services/voip/playCallEndedSound.test.ts (1)

9-14: ⚡ Quick win

Consider adding a comment clarifying the mock instance strategy.

The mock setup creates a shared mockPlayer (line 14), but individual tests use actualPlayer from mockCreateAudioPlayer.mock.results[0].value to verify behavior. While this is the correct approach, a comment explaining that mockPlayer is the template instance (provided by jest.setup.js) and tests should use actualPlayer from mock results would improve clarity for future maintainers.

📝 Suggested clarification comment
 // Access the global mock from jest.setup.js - it provides a consistent mock player
+// Note: Individual tests should use `mockCreateAudioPlayer.mock.results[n].value`
+// to access the actual player instance returned by each call, not this shared template.
 // eslint-disable-next-line `@typescript-eslint/no-var-requires`
 const expoAudio = require('expo-audio');
🤖 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 `@app/lib/services/voip/playCallEndedSound.test.ts` around lines 9 - 14, Add a
short clarifying comment above the mock setup explaining that
expoAudio.createAudioPlayer (mockCreateAudioPlayer) returns a template mock
instance (mockPlayer) provided by jest.setup.js and that tests should reference
the concrete instance created during the test run via
mockCreateAudioPlayer.mock.results[0].value (often named actualPlayer) when
asserting behavior; mention both symbols mockPlayer and
mockCreateAudioPlayer.mock.results to guide future maintainers.
🤖 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.

Inline comments:
In `@app/lib/services/voip/playCallEndedSound.ts`:
- Line 1: The code calls player?.release() which is not part of expo-audio
v0.4.9; update the cleanup to call player?.remove() instead (replace both
occurrences of player?.release() in the playCallEndedSound implementation), and
ensure any playbackStatusUpdate listeners are removed if you added them (refer
to createAudioPlayer and the local player variable and its addListener usage) so
resources are cleaned with player.remove().

---

Nitpick comments:
In `@app/lib/services/voip/playCallEndedSound.test.ts`:
- Around line 9-14: Add a short clarifying comment above the mock setup
explaining that expoAudio.createAudioPlayer (mockCreateAudioPlayer) returns a
template mock instance (mockPlayer) provided by jest.setup.js and that tests
should reference the concrete instance created during the test run via
mockCreateAudioPlayer.mock.results[0].value (often named actualPlayer) when
asserting behavior; mention both symbols mockPlayer and
mockCreateAudioPlayer.mock.results to guide future maintainers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 82d96c33-6c4b-4cea-9a28-7da554bbd6f9

📥 Commits

Reviewing files that changed from the base of the PR and between a819401 and b7f988d.

📒 Files selected for processing (4)
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/lib/services/voip/playCallEndedSound.test.ts
  • app/lib/services/voip/playCallEndedSound.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
📜 Review details
⏰ 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: ESLint and Test / run-eslint-and-test
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/services/voip/playCallEndedSound.ts
  • app/lib/services/voip/playCallEndedSound.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Files:

  • app/lib/services/voip/playCallEndedSound.ts
  • app/lib/services/voip/playCallEndedSound.test.ts
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character width, no trailing commas, avoid arrow parens, and bracket same line

Files:

  • app/lib/services/voip/playCallEndedSound.ts
  • app/lib/services/voip/playCallEndedSound.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint with @rocket.chat/eslint-config base including React, React Native, TypeScript, and Jest plugins

Files:

  • app/lib/services/voip/playCallEndedSound.ts
  • app/lib/services/voip/playCallEndedSound.test.ts
app/lib/services/voip/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Implement VoIP WebRTC peer-to-peer audio calls with Zustand stores (not Redux) in 'app/lib/services/voip/' with native CallKit (iOS) and Telecom (Android) integration

Files:

  • app/lib/services/voip/playCallEndedSound.ts
  • app/lib/services/voip/playCallEndedSound.test.ts
🧠 Learnings (1)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/lib/services/voip/playCallEndedSound.ts
  • app/lib/services/voip/playCallEndedSound.test.ts
🔇 Additional comments (6)
app/lib/services/voip/playCallEndedSound.ts (3)

53-57: Watchdog captures currentPlayer correctly before releasing.

The watchdog pattern is implemented safely: it captures the player reference (line 54) before clearing the module state (line 55), then releases the captured instance (line 56). This ensures that even if currentPlayer is set to null by releaseLock(), the original player is still released.

However, this comment is contingent on resolving the listener closure issue flagged separately. If the listener uses value-capture (as suggested), the watchdog should use the same pattern for consistency.


14-21: releaseLock() correctly resets all module state.

The function now clears currentPlayer (line 16) in addition to the flag and timer, ensuring the module is fully reset for the next invocation.


43-49: 🏗️ Heavy lift

Race condition analysis: This claim is incorrect. The isPlaying guard at lines 34-36 prevents concurrent players. When a listener fires, releaseLock() at line 46 atomically sets both isPlaying = false and currentPlayer = null before exiting. This blocks any new playCallEndedSound() invocation from proceeding past the guard until the current player fully completes. The scenario described (Player A's listener firing while Player B exists) cannot occur because Player B cannot be created while isPlaying is true. Additionally, line 45 already captures the player into a local variable, which is a reference to the player instance at that moment—even if currentPlayer changed later (which it cannot due to the guard), the local variable would remain unchanged. The code is safe as written.

			> Likely an incorrect or invalid review comment.
app/lib/services/voip/playCallEndedSound.test.ts (3)

37-50: Well-structured test for didJustFinish → release flow.

The test correctly:

  1. Creates a player via the function under test
  2. Retrieves the actual player instance from mockCreateAudioPlayer.mock.results[0].value
  3. Extracts the registered listener callback
  4. Invokes the callback with the expected status
  5. Asserts release was called

This pattern (using actualPlayer from mock results rather than the shared mockPlayer) ensures the test verifies the exact instance created by the code, which is the right approach.


95-118: Watchdog test correctly uses fake timers and verifies lock release.

The test properly:

  • Wraps fake timers in try/finally to ensure cleanup
  • Verifies the player is not released before the timeout
  • Advances timers by exactly WATCHDOG_MS (5000ms)
  • Confirms the watchdog releases the player
  • Validates that a subsequent invocation is allowed (lock was cleared)

120-137: Excellent test verifying watchdog is cleared by natural completion.

This test ensures that when the listener fires didJustFinish, the watchdog timer is properly cleared and does not cause a double-release. The assertion that actualPlayer.release is called exactly once (line 133) after advancing timers well past the watchdog threshold confirms the timer was successfully cancelled.

@@ -1,17 +1,19 @@
import { Audio } from 'expo-av';
import { createAudioPlayer } from 'expo-audio';

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

expo-audio 0.4.9 createAudioPlayer API documentation and AudioPlayer methods

💡 Result:

expo-audio 0.4.9: createAudioPlayer and AudioPlayer API/methods 1) createAudioPlayer API - Import: import { createAudioPlayer } from 'expo-audio'; [1][2] - Signature (from Expo package source): - createAudioPlayer(source?: AudioSource | string | number | null, options?: AudioPlayerOptions): AudioPlayer [3] - Purpose: creates an AudioPlayer instance that “doesn't release automatically” (so you must manage lifecycle yourself). [1][2] - Lifecycle note: Expo’s docs say that when using createAudioPlayer directly (instead of the useAudioPlayer hook), it’s your responsibility to call release() when done, otherwise you risk memory leaks. [1][2] - Options fields used by the implementation: - updateInterval (default 500) - downloadFirst (default false) - keepAudioSessionActive (default false) - preferredForwardBufferDuration (default 0) [3] - If downloadFirst is true and source is provided, Expo resolves/downloads the source and then calls player.replace(resolved); on failure it logs a warning and falls back to player.replace(fallback). [3] 2) AudioPlayer methods (imperative API) Expo’s docs list the following AudioPlayer methods: [1][2] - clearLockScreenControls() [1][2] - pause() [1][2] - play() [1][2] - remove() [1][2] - replace(source) [1][2] - seekTo(seconds, toleranceMillisBefore, toleranceMillisAfter) [1][2] - setActiveForLockScreen(active, metadata, options) [1][2] - setPlaybackRate(rate, pitchCorrectionQuality) [1][2] - updateLockScreenMetadata(metadata) [1][2] Note: Expo also has useAudioPlayer / useAudioPlayerStatus in the same docs page; those manage lifecycle automatically. [1][2] Sources: [1] https://docs.expo.dev/versions/v54.0.0/sdk/audio/ [2] https://docs.expo.dev/versions/latest/sdk/audio/ [3] https://github.com/expo/expo/blob/073ace8d/packages/expo-audio/src/ExpoAudio.ts

Citations:


🏁 Script executed:

find . -name "package.json" -type f | head -20

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 1746


🏁 Script executed:

cat app/lib/services/voip/playCallEndedSound.ts

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 2062


🏁 Script executed:

grep -A 5 '"expo-audio"' ./package.json

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 261


🏁 Script executed:

npm info expo-audio@0.4.9 | head -50

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 1168


🏁 Script executed:

# Extract and examine the expo-audio 0.4.9 tarball
cd /tmp && npm pack expo-audio@0.4.9 2>/dev/null && tar -tzf expo-audio-0.4.9.tgz | grep -E '(index|Audio)' | head -20

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 930


🏁 Script executed:

cd /tmp && tar -xzf expo-audio-0.4.9.tgz && cat package/build/ExpoAudio.js | head -200

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 4474


🏁 Script executed:

cat /tmp/package/build/index.js

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 300


🏁 Script executed:

cat /tmp/package/build/AudioModule.types.d.ts | head -150

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 4615


🏁 Script executed:

cat /tmp/package/build/Audio.types.d.ts | head -200

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 5135


🏁 Script executed:

grep -i "release\|remove" /tmp/package/build/AudioModule.types.d.ts

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 157


Use player.remove() instead of player.release().

The expo-audio v0.4.9 AudioPlayer API provides remove(): void to clean up resources, not release(). The code calls player?.release() on lines 49 and 61, which will fail at runtime. The verified API methods are:

  • createAudioPlayer(require(...))
  • player.addListener('playbackStatusUpdate', callback)
  • status.isLoaded and status.didJustFinish
  • player.play()
  • player.remove() (not release())
🤖 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 `@app/lib/services/voip/playCallEndedSound.ts` at line 1, The code calls
player?.release() which is not part of expo-audio v0.4.9; update the cleanup to
call player?.remove() instead (replace both occurrences of player?.release() in
the playCallEndedSound implementation), and ensure any playbackStatusUpdate
listeners are removed if you added them (refer to createAudioPlayer and the
local player variable and its addListener usage) so resources are cleaned with
player.remove().

@Rohit3523 Rohit3523 had a problem deploying to approve_e2e_testing May 11, 2026 17:18 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to official_android_build May 11, 2026 17:22 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_android_build May 11, 2026 17:22 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to experimental_ios_build May 11, 2026 17:22 — with GitHub Actions Error
@Rohit3523 Rohit3523 had a problem deploying to approve_e2e_testing May 26, 2026 10:32 — with GitHub Actions Error

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/views/AttachmentView.tsx (1)

187-187: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Floating promise: FileSystem.deleteAsync result is not handled.

This async call returns a promise that is neither awaited nor has error handling. Per the codebase's no-void: error ESLint rule, floating promises must be handled explicitly with .catch().

🔧 Proposed fix
-				FileSystem.deleteAsync(file, { idempotent: true });
+				FileSystem.deleteAsync(file, { idempotent: true }).catch(() => {
+					// cleanup failure is non-critical; file will be cleaned up by OS eventually
+				});

Based on learnings: "In this Rocket.Chat React Native codebase, the ESLint rule no-void: error is enforced. When you see a promise returned from an async call that is not awaited, handle the promise explicitly by attaching .catch(...) so unhandled-rejection risks are addressed."

🤖 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 `@app/views/AttachmentView.tsx` at line 187, The call to
FileSystem.deleteAsync(file, { idempotent: true }) is a floating promise and
must be handled; update the code in AttachmentView to either await the call if
inside an async function or (per project lint rule) attach a .catch(...) to the
returned promise to handle errors, e.g., call
FileSystem.deleteAsync(...).catch(err => {/* log via processLogger/console or
swallow with a comment */}); ensure you reference the FileSystem.deleteAsync
invocation in AttachmentView and use the existing logging utility (or
console.error) inside the catch handler.
🤖 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.

Outside diff comments:
In `@app/views/AttachmentView.tsx`:
- Line 187: The call to FileSystem.deleteAsync(file, { idempotent: true }) is a
floating promise and must be handled; update the code in AttachmentView to
either await the call if inside an async function or (per project lint rule)
attach a .catch(...) to the returned promise to handle errors, e.g., call
FileSystem.deleteAsync(...).catch(err => {/* log via processLogger/console or
swallow with a comment */}); ensure you reference the FileSystem.deleteAsync
invocation in AttachmentView and use the existing logging utility (or
console.error) inside the catch handler.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f6e8f60-7e9e-423d-93a6-ab5937194e3f

📥 Commits

Reviewing files that changed from the base of the PR and between f761897 and 1f3a18d.

📒 Files selected for processing (3)
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/views/AttachmentView.tsx
  • app/views/ShareView/Preview.tsx
📜 Review details
⏰ 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). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/views/ShareView/Preview.tsx
  • app/views/AttachmentView.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode and baseUrl set to app/ for import resolution

Files:

  • app/views/ShareView/Preview.tsx
  • app/views/AttachmentView.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use Prettier with tabs, single quotes, 130 char width, no trailing commas, arrow parens avoid, bracket same line
Use @rocket.chat/eslint-config base with React, React Native, TypeScript, Jest plugins

Files:

  • app/views/ShareView/Preview.tsx
  • app/views/AttachmentView.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
app/views/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

View components (70+ screen components) should be placed in app/views/ directory

Files:

  • app/views/ShareView/Preview.tsx
  • app/views/AttachmentView.tsx
app/containers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Reusable UI components should be placed in app/containers/ directory

Files:

  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
🧠 Learnings (1)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/views/ShareView/Preview.tsx
  • app/views/AttachmentView.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
🔇 Additional comments (8)
app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx (1)

11-11: LGTM!

Also applies to: 20-20, 55-55

app/views/AttachmentView.tsx (5)

49-58: Guard against repeated error events firing duplicate alerts and goBack calls.

The statusChange event can fire multiple times with error status (e.g., transient → terminal error). Without a guard, this handler will stack multiple Alert.alert dialogs and call navigation.goBack() repeatedly.

♻️ Suggested guard using a ref
+	const hasHandledErrorRef = React.useRef(false);
+
 	useEventListener(player, 'statusChange', ({ status }) => {
 		if (status === 'readyToPlay') {
 			setLoading(false);
-		} else if (status === 'error') {
+		} else if (status === 'error' && !hasHandledErrorRef.current) {
+			hasHandledErrorRef.current = true;
 			setLoading(false);
 			Alert.alert(I18n.t('Error'), I18n.t('There_was_an_error_while_action', { action: I18n.t('playing_video') }));
 			navigation.goBack();
 		}
 	});

55-55: The playing_video translation key does not exist.

The key is missing from all translation JSON files. Users will see the literal string playing_video in the error alert. Use an existing key like Error_play_video or add playing_video to all locale files.


97-106: LGTM!


120-123: LGTM!

Also applies to: 134-150, 152-194, 196-213


217-223: LGTM!

app/views/ShareView/Preview.tsx (2)

19-25: LGTM!


83-97: LGTM!

Also applies to: 101-110

@diegolmello diegolmello left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Besides this, make sure to resolve code rabbit

Comment on lines +68 to +70
const durationSeconds = data.duration ?? 0;
duration.value = durationSeconds > 0 ? durationSeconds : 0;
const currentSecond = data.currentTime ?? 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why ?? instead of ||?

const permissionToUpload = useCanUploadFile(rid);
useKeepAwake();

async function doRecording() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Any reason to rename and move away from the effect?

const player = useAudioPlayer(RINGER_SOUND_FILES[ringer]);

useEffect(() => {
const loadAndPlay = async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Any reason to remove the conditional loading?

bitRate: Audio.RecordingOptionsPresets.LOW_QUALITY.android.bitRate
outputFormat: 'aac_adts',
audioEncoder: 'aac',
sampleRate: 44100

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This magic number is wrong. They probably have defaults, no?

audiosRendered.delete(audioKey);
}

class AudioManagerClass {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why did you refactor this? What's the benefit?
You also refactored, but didn't add any tests.

import { useTheme } from '../theme';
import { LOCAL_DOCUMENT_DIRECTORY, getFilename } from '../lib/methods/handleMediaDownload';

const VideoContent = ({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Different file with stories and tests

@Rohit3523 Rohit3523 requested a deployment to approve_e2e_testing June 25, 2026 21:12 — with GitHub Actions Waiting

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx (1)

92-105: 🎯 Functional Correctness | 🟠 Major

Capture a non-null recording URI before switching to review.

audioRecorder.uri and recorderState.url are nullable here, but sendAudio still force-casts audioRecorder.uri and the review UI falls back to '' for AudioPlayer. Store a confirmed URI after stop() and reuse that value for playback and upload, or bail out if no file path is available.

🤖 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 `@app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx` around
lines 92 - 105, The recording URI handling in RecordAudio is unsafe because
goReview and sendAudio rely on nullable audioRecorder.uri/recorderState.url.
Update the RecordAudio component to capture and store a confirmed non-null URI
immediately after audioRecorder.stop() succeeds, then reuse that stored URI for
both AudioPlayer playback and getInfoAsync/upload in sendAudio; if no URI is
available, exit early instead of force-casting or falling back to an empty
string.
🧹 Nitpick comments (1)
app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx (1)

47-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit return types to the async handlers.

These local async functions should declare Promise<void> to match the TypeScript guideline for explicit return types.

Proposed fix
-	async function doRecording() {
+	async function doRecording(): Promise<void> {
...
-	const cancelRecording = async () => {
+	const cancelRecording = async (): Promise<void> => {
...
-	const goReview = async () => {
+	const goReview = async (): Promise<void> => {
...
-	const sendAudio = async () => {
+	const sendAudio = async (): Promise<void> => {

Also applies to: 82-82, 92-92, 101-101

🤖 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 `@app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx` at
line 47, Add explicit Promise<void> return types to the local async handlers in
RecordAudio, including doRecording and the other async functions referenced in
this diff, so they match the TypeScript guideline for explicit return types.
Update each async function signature to declare its return type directly on the
function/method definition, keeping the existing behavior unchanged.

Source: Coding guidelines

🤖 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.

Inline comments:
In `@app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx`:
- Line 38: The RecordAudio component has an unused retry ref that triggers
ESLint. Remove numberOfTriesRef from RecordAudio.tsx and clean up any related
dead code or imports so the component only keeps refs and state that are
actually read.

---

Outside diff comments:
In `@app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx`:
- Around line 92-105: The recording URI handling in RecordAudio is unsafe
because goReview and sendAudio rely on nullable
audioRecorder.uri/recorderState.url. Update the RecordAudio component to capture
and store a confirmed non-null URI immediately after audioRecorder.stop()
succeeds, then reuse that stored URI for both AudioPlayer playback and
getInfoAsync/upload in sendAudio; if no URI is available, exit early instead of
force-casting or falling back to an empty string.

---

Nitpick comments:
In `@app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx`:
- Line 47: Add explicit Promise<void> return types to the local async handlers
in RecordAudio, including doRecording and the other async functions referenced
in this diff, so they match the TypeScript guideline for explicit return types.
Update each async function signature to declare its return type directly on the
function/method definition, keeping the existing behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5aa6be69-04d9-44d5-9796-e2957fc68bfc

📥 Commits

Reviewing files that changed from the base of the PR and between 56be8c6 and 01d3838.

📒 Files selected for processing (6)
  • app/containers/AudioPlayer/Seek.tsx
  • app/containers/AudioPlayer/index.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/containers/MessageComposer/components/RecordAudio/Duration.tsx
  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
  • app/containers/Ringer/index.tsx
💤 Files with no reviewable changes (1)
  • app/containers/AudioPlayer/Seek.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/containers/MessageComposer/components/RecordAudio/Duration.tsx
  • app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx
  • app/containers/Ringer/index.tsx
  • app/containers/AudioPlayer/index.tsx
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode enabled

Files:

  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses

Files:

  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce ESLint rules from @rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins

Files:

  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
app/containers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place reusable UI components in 'app/containers/' directory

Files:

  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
🧠 Learnings (2)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
📚 Learning: 2026-06-25T18:37:44.793Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7434
File: app/views/ScreenLockConfigView.tsx:101-141
Timestamp: 2026-06-25T18:37:44.793Z
Learning: In the Rocket.Chat React Native codebase, do not treat passing an `async` function directly to an event prop in React/React Native UI components (e.g., `onPress={async () => ...}` in TSX) as a “floating promises” CI-blocking lint issue—this repo does not enable the ESLint `no-floating-promises` rule (while `no-void` is enforced). Only raise robustness follow-ups when there are genuinely unhandled promise paths (e.g., fire-and-forget calls like `save()` that return a Promise that is neither awaited nor handled), and prefer making sure failure paths are explicitly handled/reported rather than blocking on lint-style floating-promise concerns.

Applied to files:

  • app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx
🪛 ESLint
app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx

[error] 38-38: 'numberOfTriesRef' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

🔇 Additional comments (1)
app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx (1)

2-2: LGTM!

Also applies to: 34-36, 39-39, 63-80

const recorderState = useAudioRecorderState(audioRecorder);

const durationRef = useRef<IDurationRef>({} as IDurationRef);
const numberOfTriesRef = useRef(0);

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.

📐 Maintainability & Code Quality | 🔴 Critical | ⚡ Quick win

Remove the unused retry ref to unblock ESLint.

numberOfTriesRef is never read, and static analysis flags this as @typescript-eslint/no-unused-vars.

Proposed fix
-	const numberOfTriesRef = useRef(0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const numberOfTriesRef = useRef(0);
🧰 Tools
🪛 ESLint

[error] 38-38: 'numberOfTriesRef' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

🤖 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 `@app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx` at
line 38, The RecordAudio component has an unused retry ref that triggers ESLint.
Remove numberOfTriesRef from RecordAudio.tsx and clean up any related dead code
or imports so the component only keeps refs and state that are actually read.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants