Skip to content

Adds Remote Media Controls API#862

Draft
kul3r4 wants to merge 2 commits intomainfrom
media-bridge-api
Draft

Adds Remote Media Controls API#862
kul3r4 wants to merge 2 commits intomainfrom
media-bridge-api

Conversation

@kul3r4
Copy link
Copy Markdown
Contributor

@kul3r4 kul3r4 commented Apr 2, 2026

No description provided.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces remote media session support for Wear OS, adding a new ViewModel to manage media sessions and a skeleton activity. Several critical issues were identified in the implementation, including a type mismatch in the session flow, compilation errors caused by calling suspend functions within non-suspend lambdas, and potential memory leaks due to unreleased MediaController instances. Additionally, there are unused properties in both the ViewModel and the Activity that should be addressed.

@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
private val sessionFlow: Flow<List<MediaSession.Token>> = callbackFlow {
// [START android_wear_remote_media_session_get_sessions]
trySendBlocking(mediaSessionManager.getActiveSessionsForPackage(application.packageName))
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.

high

The initial emission of sessionFlow has a type mismatch. getActiveSessionsForPackage returns a list of platform MediaController objects, but the flow expects List<MediaSession.Token>. You should map the controllers to their session tokens and handle potential nullability of the manager and its result.

Suggested change
trySendBlocking(mediaSessionManager.getActiveSessionsForPackage(application.packageName))
trySendBlocking(mediaSessionManager?.getActiveSessionsForPackage(application.packageName)?.map { it.sessionToken } ?: emptyList())

Comment on lines +56 to +68
private val controllerFlow: Flow<MediaController?> =
sessionFlow
.distinctUntilChanged()
.map { tokens ->
tokens.map {
SessionToken.createSessionToken(application, it).await()
}
}
.map { tokens ->
tokens.map { MediaController.Builder(application, it).buildAsync().await() }
}
.mapNotNull { controllers -> controllers.firstOrNull() }
.flatMapLatest { controller -> controller.controllerEventFlow.map { controller } }
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.

high

This block contains a compilation error and a memory leak.

  1. Compilation Error: The await() function is a suspend function and cannot be called inside the non-suspend lambda of Iterable.map (lines 61 and 65).
  2. Memory Leak: MediaController instances are created for all tokens but only the first is used, leaking the others immediately. Furthermore, the used controller is never released. MediaController must be released to avoid leaking system resources.

Consider refactoring to process only the first token and manage the controller's lifecycle using callbackFlow and awaitClose.

    private val controllerFlow: Flow<MediaController?> =
        sessionFlow
            .distinctUntilChanged()
            .flatMapLatest { tokens ->
                val token = tokens.firstOrNull() ?: return@flatMapLatest kotlinx.coroutines.flow.flowOf(null)
                callbackFlow {
                    val sessionToken = SessionToken.createSessionToken(application, token).await()
                    val controller = MediaController.Builder(application, sessionToken).buildAsync().await()
                    val listener = object : Player.Listener {
                        override fun onEvents(player: Player, events: Player.Events) {
                            trySendBlocking(controller)
                        }
                    }
                    controller.addListener(listener)
                    trySendBlocking(controller)
                    awaitClose {
                        controller.removeListener(listener)
                        controller.release()
                    }
                }
            }

Comment on lines +87 to +88
// CHANGED: A private property to hold the current controller instance.
private var currentController: MediaController? = null
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.

medium

The property currentController and its associated comment are unused and should be removed to maintain code cleanliness.


class RemoteMediaSessionActivity : ComponentActivity() {

private lateinit var viewModel: RemoteMediaActivityViewModel
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.

medium

The viewModel property is initialized but never used in the activity. Consider using it to observe the media state or remove it if this is just a skeleton activity.

@kul3r4 kul3r4 force-pushed the media-bridge-api branch from f3fa00b to 4cf22fa Compare April 2, 2026 16:41

defaultConfig {
applicationId = "com.example.wear"
minSdk = 33
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.

Can you change this minSdk to reflect the right version? This will remove the SDK version check in the activity

sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
useLibrary("wear-sdk")
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.

Should this be a code snippet to be documented in DAC?

</intent-filter>
<!-- [END android_wear_remote_media_session_manifest] -->
<intent-filter>
<action android:name="com.google.wear.services.media.action.REMOTE_MEDIA_ACTIVITY" />
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.

Why not including this in the code snippet?

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