Skip to content

Conversation

@VelikovPetar
Copy link
Contributor

@VelikovPetar VelikovPetar commented Jan 9, 2026

🎯 Goal

Adds stricter ExoPlayer cleanup logic:

  1. Create the player in OnStart
  2. Pause the player in OnPause
  3. Release / cleanup the player in OnStop

This is different from previously where we did the cleanup only in OnDestroy/OnDispose. This is an optimistic fix for ExoPlayer holding the AudioMix wake lock, in cases where the screen wasn't properly cleaned-up (destroyed in background, or killed by system).

Additionally, introduces a currentTimestamp tracking, to ensure the video is restored at the timestamp when it was paused, to match the previous behaviour.

🛠 Implementation details

  • Create the ExoPlayer in OnStart
  • Pause the ExoPlayer in OnPause
  • Release / cleanup the player in OnStop
  • Add savedPlaybackPosition tracking and restoring logic

🎨 UI Changes

NA

🧪 Testing

Test playing/pausing/minimizing/resuming video attachments. There should be no visible behaviour change from previously.

Summary by CodeRabbit

  • Bug Fixes

    • Video playback position is saved and restored when switching items, resuming the app, or returning to previously viewed content.
    • Improved lifecycle handling for more stable playback across foreground/background transitions and ensured players are released reliably to prevent audio wake locks.
    • Autoplay behavior preserved when returning to media.
  • Documentation

    • CHANGELOG updated to note the stricter player cleanup.

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

@VelikovPetar VelikovPetar marked this pull request as ready for review January 9, 2026 10:16
@VelikovPetar VelikovPetar requested a review from a team as a code owner January 9, 2026 10:16
@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

Walkthrough

Updates ExoPlayer/Media3 lifecycle and playback-state handling across Compose gallery, an Activity, and a Fragment: players are created on START, paused on PAUSE, save/restore playback position on STOP/START, and are explicitly released to avoid AudioMix partial wake locks; two changelog entries added.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added two identical "⬆️ Improved" entries referencing PR #6075 documenting stricter ExoPlayer cleanup to avoid AudioMix partial wake locks.
Compose gallery
stream-chat-android-compose/.../MediaGalleryPreviewScreen.kt
Replace static Player with nullable state-held Player?; add savedPlaybackState; create player on START, pause on PAUSE, save position and release on STOP/onDispose; restore position when returning to a page; render video pages only when player exists.
Activity player
stream-chat-android-ui-components/.../AttachmentMediaActivity.kt
Add savedPlaybackPosition and autoPlay; move player creation to onStart() using setMediaItem(..., savedPlaybackPosition) and prepare(); set playWhenReady from autoPlay; save position and release in onStop().
Fragment player
stream-chat-android-ui-components/.../AttachmentGalleryVideoPageFragment.kt
Add onStart()/onStop() to initialize player with savedPlaybackPosition and to save position + release on stop; defer player setup from onViewCreated to onStart; keep onPause pausing behavior.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Lifecycle
    participant UI as UI (Compose / Activity / Fragment)
    participant Player as ExoPlayer / Media3 Player
    participant Store as Saved Playback State

    App->>UI: onStart
    UI->>Player: create player if null
    Player->>Store: request saved position for current page
    Store-->>Player: return position (default 0)
    Player->>Player: setMediaItem(uri, startPosition) & prepare()
    Player->>Player: set playWhenReady (autoPlay)

    UI->>Player: render page using Player (if available)

    App->>UI: onPause
    UI->>Player: pause()

    App->>UI: onStop
    Player->>Store: save current position
    UI->>Player: clear PlayerView reference
    UI->>Player: release() and null reference
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • gpunto

Poem

🐰 I hop through frames and mark the spot,

Positions kept — I save a lot.
Players nap when day is gone,
Wake locks fade at early dawn.
A tidy hop, and on we run ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add stricter ExoPlayer clean-up logic' accurately captures the primary objective and main change in the PR, which is implementing stricter lifecycle-based ExoPlayer cleanup.
Description check ✅ Passed The PR description covers the required sections: Goal (explains the motivation), Implementation details (lists the key changes), Testing (provides testing instructions), and references the UI changes section. However, the Contributor Checklist items are not checked, and optional sections like UI Changes, GIF, and testing patch details are incomplete or missing.

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


📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

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 c67a57d and 7db34f6.

📒 Files selected for processing (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md
⏰ 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). (6)
  • GitHub Check: Build / compose apks
  • GitHub Check: base-android-ci / Run static checks
  • GitHub Check: base-android-ci / Run unit tests
  • GitHub Check: base-android-ci / Build
  • GitHub Check: Detekt
  • GitHub Check: compare-sdk-sizes / Compare SDK sizes

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.

Copy link

@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

Caution

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

⚠️ Outside diff range comments (2)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt (1)

66-97: Fix lifecycle safety: add finish() after invalid input, null-check URL before player creation, and preserve play state on resume

Line 77-81: onCreate() returns without finish() after validation fails, allowing onStart() to proceed with invalid data. Add finish() after showPlaybackError().

Line 92: onStart() directly passes nullable url to Uri.parse(url) without null checking. This will crash if inputs were invalid but onCreate() didn't finish. Add a null guard or capture url before use.

Line 108: onStop() hard-codes autoPlay = false, losing the actual play state. When resuming, the player always resumes paused. Capture player?.playWhenReady before pausing in onPause() instead.

Proposed diff
     if ((type.isNullOrEmpty() && mimeType.isNullOrEmpty()) || url.isNullOrEmpty()) {
         logger.e { "This file can't be displayed. The TYPE or the URL are null" }
         showPlaybackError()
+        finish()
         return
     }

 override fun onStart() {
     super.onStart()
     // (Re)create player when returning to foreground.
+    val mediaUrl = url ?: return
     player = createPlayer()
         .apply {
-            setMediaItem(MediaItem.fromUri(Uri.parse(url)), savedPlaybackPosition)
+            setMediaItem(MediaItem.fromUri(Uri.parse(mediaUrl)), savedPlaybackPosition)
             prepare()
             playWhenReady = autoPlay
         }
         .also(::setupPlayerView)
 }

 override fun onPause() {
     super.onPause()
+    // Capture whether we should resume playing before pause() flips playWhenReady to false.
+    autoPlay = player?.playWhenReady == true
     player?.pause()
 }

 override fun onStop() {
     super.onStop()
     // Save playback position and release player to free wake lock.
     savedPlaybackPosition = player?.currentPosition ?: 0L
-    autoPlay = false
+    binding.playerView.player = null
+    binding.controls.player = null
     player?.release()
     player = null
 }
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewScreen.kt (1)

551-610: Add bounds check and clear stale media when assetUrl is missing

  1. Line 563 accesses attachments[currentPage] without checking bounds. Since LaunchedEffect depends only on (currentPage, player) and not attachments, if the attachments list shrinks while currentPage remains the same, this causes an IndexOutOfBoundsException.

  2. When attachment.isVideo() is true but assetUrl is null (lines 565–576), the player retains the previous media item. The video page then renders with stale content that may not match the current page.

  3. The onPlaybackError callback captured in DisposableEffect (line 29) is not wrapped with rememberUpdatedState, so if the callback changes, the lifecycle observer retains a stale reference.

Proposed fixes
+    val onPlaybackErrorState = rememberUpdatedState(onPlaybackError)

     LaunchedEffect(currentPage, player) {
         player?.let { activePlayer ->
             activePlayer.pause() // Pause the player when the page changes
+            if (currentPage !in attachments.indices) return@LaunchedEffect
             val attachment = attachments[currentPage]
             // Prepare the player with the media item if it's a video.
             if (attachment.isVideo()) {
                 attachment.assetUrl?.let { assetUrl ->
                     // Restore playback position if returning to the same page after app resume.
                     val startPosition = savedPlaybackState
                         ?.takeIf { (savedPage, _) => savedPage == currentPage }
                         ?.second
                         ?: 0L
                     savedPlaybackState = null
                     activePlayer.setMediaItem(MediaItem.fromUri(assetUrl), startPosition)
                     activePlayer.prepare()
-                }
+                } ?: run {
+                    // Prevent playing previous page's media
+                    activePlayer.stop()
+                    activePlayer.clearMediaItems()
+                }
             }
         }
     }

     DisposableEffect(lifecycleOwner, previewMode) {
         val observer = LifecycleEventObserver { _, event ->
             when (event) {
                 Lifecycle.Event.ON_START -> {
                     if (!previewMode && player == null) {
                         player = createPlayer(
                             context = context,
                             onBuffering = { isBuffering -> showBuffering = isBuffering },
-                            onPlaybackError = onPlaybackError,
+                            onPlaybackError = onPlaybackErrorState.value,
                         )
                     }
                 }
🤖 Fix all issues with AI agents
In
@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt:
- Around line 69-83: The fragment can crash when assetUrl is null and the player
lifecycle is mishandled: in onStart() use a null-safe URL (check assetUrl and
bail out or use Uri.EMPTY) before calling MediaItem.fromUri, and only
create/prepare the player when assetUrl is present (reference: onStart(),
assetUrl, createPlayer(), setMediaItem()). In onStop() clear the PlayerView's
player reference (the view set up by setupPlayerView / PlayerView) before
calling player.release() and save position into savedPlaybackPosition; then
remove the duplicate player.release() call from onDestroyView() so release
happens only once (references: onStop(), onDestroyView(), player, PlayerView,
savedPlaybackPosition, setupPlayerView).
🧹 Nitpick comments (2)
CHANGELOG.md (2)

61-63: Avoid “duplicate-looking” changelog entries; tailor wording per artifact (or centralize once).

These two bullets are identical (one under stream-chat-android-ui-components, one under stream-chat-android-compose). If that’s intentional, consider slightly tailoring each line to the concrete surface (e.g., “AttachmentMediaActivity / AttachmentGalleryVideoPageFragment” vs “MediaGalleryPreviewScreen”) so it doesn’t read like an accidental duplicate. Otherwise, consider documenting it once in a shared/common section if that matches your release-note conventions.


62-62: Minor wording polish (“prevent holding” reads better).

Current: “prevent keeping AudioMix partial wake locks” → consider “prevent holding AudioMix partial wake locks” (or “prevent AudioMix partial wake locks from being held after leaving the screen”).

Also applies to: 74-74

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

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 c71d105 and 343c6b7.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewScreen.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{kt,kts}

📄 CodeRabbit inference engine (AGENTS.md)

Format and apply Kotlin style with Spotless (4 spaces, no wildcard imports, licence headers)

Files:

  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewScreen.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use @OptIn annotations explicitly; avoid suppressions unless documented
Document public APIs with KDoc, including thread expectations and state notes

Files:

  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewScreen.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt
**/stream-chat-android-compose/**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/stream-chat-android-compose/**/*.kt: Compose components should follow noun-based naming (e.g., MessageList, ChannelListHeader)
Compose previews should use @StreamPreview helpers

Files:

  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewScreen.kt
🧠 Learnings (3)
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/stream-chat-android-compose/**/*.kt : Compose previews should use `StreamPreview` helpers

Applied to files:

  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewScreen.kt
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/stream-chat-android-compose/**/*Test.kt : Add Paparazzi snapshots for Compose UI regressions and run `verifyPaparazziDebug`

Applied to files:

  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewScreen.kt
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/stream-chat-android-ui-components/**/*Test.kt : Record Shot baselines when behaviour changes in XML kit UI tests

Applied to files:

  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt
  • CHANGELOG.md
🧬 Code graph analysis (2)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewScreen.kt (3)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt (1)
  • createPlayer (225-245)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/internal/StreamMediaPlayerContent.kt (1)
  • createPlayer (191-213)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/internal/MediaGalleryPage.kt (1)
  • MediaGalleryVideoPage (292-332)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt (1)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt (1)
  • createPlayer (225-245)
⏰ 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: compare-sdk-sizes / Compare SDK sizes
  • GitHub Check: Build / compose apks
🔇 Additional comments (1)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewScreen.kt (1)

73-75: Import change only

@github-actions
Copy link
Contributor

github-actions bot commented Jan 9, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.25 MB 0.00 MB 🟢
stream-chat-android-offline 5.48 MB 5.48 MB 0.00 MB 🟢
stream-chat-android-ui-components 10.62 MB 10.62 MB 0.00 MB 🟢
stream-chat-android-compose 12.84 MB 12.84 MB 0.00 MB 🟢

Copy link

@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: 2

🤖 Fix all issues with AI agents
In
@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt:
- Around line 87-97: The activity crashes because onStop dereferences binding
even when binding wasn't initialized and onStart can run after an onCreate early
return for invalid intent; fix by validating intent data early and bailing out
with finish() immediately if url/type are invalid, ensure binding is initialized
before any possible finish() (or move finish() to after binding.inflate), and
guard onStop/onStart to only touch player/binding when they are initialized
(e.g., check ::binding.isInitialized or that player != null) so
createPlayer()/setupPlayerView and player cleanup are only called when valid
data and binding exist.
- Around line 58-63: Capture the current playback intent in onPause() by setting
autoPlay = (player?.isPlaying == true) and save the playback position into
savedPlaybackPosition, remove the existing autoPlay = false assignment from
onStop(), and in onStop() clear both binding.playerView.player and
binding.controls.player (since setupPlayerView() assigns both) so the player is
properly released while preserving the user’s resume intent.
🧹 Nitpick comments (1)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt (1)

58-63: Persist savedPlaybackPosition across config change / process death (currently it’s in-memory only).
If the Activity is recreated (rotation) or the process is killed, position resets to 0L.

Proposed fix
 private var savedPlaybackPosition: Long = 0L
 private var autoPlay: Boolean = true

+override fun onSaveInstanceState(outState: Bundle) {
+    outState.putLong("stream:savedPlaybackPosition", savedPlaybackPosition)
+    outState.putBoolean("stream:autoPlay", autoPlay)
+    super.onSaveInstanceState(outState)
+}
+
 override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
+
+    savedInstanceState?.let {
+        savedPlaybackPosition = it.getLong("stream:savedPlaybackPosition", 0L)
+        autoPlay = it.getBoolean("stream:autoPlay", true)
+    }
     ...
 }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

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 343c6b7 and 1c3cfd0.

📒 Files selected for processing (2)
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{kt,kts}

📄 CodeRabbit inference engine (AGENTS.md)

Format and apply Kotlin style with Spotless (4 spaces, no wildcard imports, licence headers)

Files:

  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use @OptIn annotations explicitly; avoid suppressions unless documented
Document public APIs with KDoc, including thread expectations and state notes

Files:

  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
🧠 Learnings (1)
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/stream-chat-android-ui-components/**/*Test.kt : Record Shot baselines when behaviour changes in XML kit UI tests

Applied to files:

  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
🧬 Code graph analysis (1)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt (1)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt (1)
  • createPlayer (224-244)
⏰ 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). (6)
  • GitHub Check: Build / compose apks
  • GitHub Check: compare-sdk-sizes / Compare SDK sizes
  • GitHub Check: base-android-ci / Run unit tests
  • GitHub Check: base-android-ci / Run static checks
  • GitHub Check: base-android-ci / Build
  • GitHub Check: Detekt

Copy link

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt (1)

66-98: Add finish() call in onCreate() when invalid inputs detected to prevent crash in onStart().

The issue is valid. The url property is nullable (String?), and when it's null, onCreate() returns after calling showPlaybackError() without invoking finish(). Since binding is already initialized before this validation check, the safety check in onStart() (if (!::binding.isInitialized) return) won't prevent execution. When the activity resumes, onStart() will attempt to pass the null url to Uri.parse(url), risking a crash or undefined behavior.

The proposed fix is correct:

  • Add finish() in onCreate() after showPlaybackError() to terminate the activity lifecycle
  • Add defensive null check in onStart() as a safety measure

The androidx.media3 API (Player.setMediaItem() and Player.release()) is available in the configured version.

🧹 Nitpick comments (1)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt (1)

58-63: Consider persisting savedPlaybackPosition across process death / config change (optional).

Right now savedPlaybackPosition survives only while the Activity instance survives; if the process is killed you’ll restart from 0L. If you want “killed by the system” restoration, persist via onSaveInstanceState (and restore in onCreate).

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

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 1c3cfd0 and 91b0986.

📒 Files selected for processing (1)
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{kt,kts}

📄 CodeRabbit inference engine (AGENTS.md)

Format and apply Kotlin style with Spotless (4 spaces, no wildcard imports, licence headers)

Files:

  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use @OptIn annotations explicitly; avoid suppressions unless documented
Document public APIs with KDoc, including thread expectations and state notes

Files:

  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
🧠 Learnings (3)
📓 Common learnings
Learnt from: VelikovPetar
Repo: GetStream/stream-chat-android PR: 6075
File: stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt:58-63
Timestamp: 2026-01-09T11:00:38.487Z
Learning: In `AttachmentMediaActivity` (stream-chat-android-ui-components), the `autoPlay = false` in `onStop()` is intentional to maintain the original behavior where videos do not auto-resume after the activity is backgrounded.
📚 Learning: 2026-01-09T11:00:38.487Z
Learnt from: VelikovPetar
Repo: GetStream/stream-chat-android PR: 6075
File: stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt:58-63
Timestamp: 2026-01-09T11:00:38.487Z
Learning: In `AttachmentMediaActivity` (stream-chat-android-ui-components), the `autoPlay = false` in `onStop()` is intentional to maintain the original behavior where videos do not auto-resume after the activity is backgrounded.

Applied to files:

  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/stream-chat-android-ui-components/**/*Test.kt : Record Shot baselines when behaviour changes in XML kit UI tests

Applied to files:

  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt
🧬 Code graph analysis (1)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt (1)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/internal/AttachmentGalleryVideoPageFragment.kt (1)
  • createPlayer (224-244)
⏰ 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). (6)
  • GitHub Check: compare-sdk-sizes / Compare SDK sizes
  • GitHub Check: base-android-ci / Run unit tests
  • GitHub Check: base-android-ci / Build
  • GitHub Check: base-android-ci / Run static checks
  • GitHub Check: Detekt
  • GitHub Check: Build / compose apks
🔇 Additional comments (1)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt (1)

105-115: Cleanup flow looks correct; autoPlay = false matches the intended “no auto-resume after background” behavior.

Detaching PlayerView/controls before release() is good for avoiding leaked surfaces/listeners, and saving currentPosition before release enables your timestamp restore on the next onStart(). Also calling autoPlay = false here aligns with the original behavior (per retrieved learnings).

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
48.1% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@VelikovPetar VelikovPetar merged commit ce5450a into develop Jan 16, 2026
13 of 14 checks passed
@VelikovPetar VelikovPetar deleted the bug/AND-1010_improve_exoplayer_cleanup branch January 16, 2026 08:51
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.

3 participants