From 2a64f85cfe1444f8ea4639bc680d28bbed6ecf8b Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 13 Aug 2020 21:52:31 +0100 Subject: [PATCH 1/4] WIP - Seamless transfer support --- app/src/main/AndroidManifest.xml | 2 ++ .../com/example/android/uamp/MainActivity.kt | 35 +++++++++++++++++++ build.gradle | 1 + common/build.gradle | 1 + 4 files changed, 39 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 25a97cf1b..78f322dab 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -92,6 +92,8 @@ + + diff --git a/app/src/main/java/com/example/android/uamp/MainActivity.kt b/app/src/main/java/com/example/android/uamp/MainActivity.kt index 8b6b19091..62cce9756 100644 --- a/app/src/main/java/com/example/android/uamp/MainActivity.kt +++ b/app/src/main/java/com/example/android/uamp/MainActivity.kt @@ -23,6 +23,11 @@ import android.view.Menu import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.mediarouter.media.MediaControlIntent +import androidx.mediarouter.media.MediaRouteSelector +import androidx.mediarouter.media.MediaRouter +import androidx.mediarouter.media.MediaRouterParams import com.example.android.uamp.fragments.MediaItemFragment import com.example.android.uamp.media.MusicService import com.example.android.uamp.utils.Event @@ -31,12 +36,16 @@ import com.example.android.uamp.viewmodels.MainActivityViewModel import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastContext + class MainActivity : AppCompatActivity() { private val viewModel by viewModels { InjectorUtils.provideMainActivityViewModel(this) } private var castContext: CastContext? = null + private lateinit var selector: MediaRouteSelector + private lateinit var router: MediaRouter + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -47,6 +56,16 @@ class MainActivity : AppCompatActivity() { setContentView(R.layout.activity_main) + router = MediaRouter.getInstance(this) + selector = MediaRouteSelector.Builder() + .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) + .build() + router.routerParams = MediaRouterParams.Builder().setTransferToLocalEnabled(true).build() + router.addCallback( + selector, MediaRouterCallback(), + MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY + ) + // Since UAMP is a music player, the volume controls should adjust the music volume while // in the app. volumeControlStream = AudioManager.STREAM_MUSIC @@ -114,4 +133,20 @@ class MainActivity : AppCompatActivity() { private fun getBrowseFragment(mediaId: String): MediaItemFragment? { return supportFragmentManager.findFragmentByTag(mediaId) as MediaItemFragment? } + + private inner class MediaRouterCallback : MediaRouter.Callback() { + override fun onRouteSelected( + router: MediaRouter, + route: MediaRouter.RouteInfo, + reason: Int + ) { + if (reason == MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) { + Log.d(TAG, "Unselected because route changed, continue playback") + } else if (reason == MediaRouter.UNSELECT_REASON_STOPPED) { + Log.d(TAG, "Unselected because route was stopped, stop playback") + } + } + } } + +private const val TAG = "MainActivity" \ No newline at end of file diff --git a/build.gradle b/build.gradle index 2e1d2779a..722bd63b4 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ buildscript { androidx_car_version = '1.0.0-alpha7' androidx_core_ktx_version = '1.3.1' androidx_media_version = '1.0.1' + androidx_mediarouter_version = '1.2.0-rc01' androidx_preference_version = '1.1.1' androidx_test_runner_version = '1.3.0' arch_lifecycle_version = '2.2.0' diff --git a/common/build.gradle b/common/build.gradle index 35a547fe4..e498d682e 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -51,6 +51,7 @@ dependencies { api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" api "androidx.media:media:$androidx_media_version" + implementation "androidx.mediarouter:mediarouter:$androidx_mediarouter_version" api "com.google.code.gson:gson:$gson_version" From 9a237bf1658aeaf292399ae0a0eced30d56b47c5 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Fri, 14 Aug 2020 10:09:24 +0100 Subject: [PATCH 2/4] Add exported attribute --- app/src/main/AndroidManifest.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 78f322dab..7701b8c8e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -92,7 +92,9 @@ - + From 3a1eecb6c1d603a06e2f5402ae3a117f10834d7f Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 1 Oct 2020 22:26:22 +0100 Subject: [PATCH 3/4] Move MediaRouter into Service --- .../com/example/android/uamp/MainActivity.kt | 27 ------------ .../android/uamp/media/MusicService.kt | 43 ++++++++++++++++++- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/example/android/uamp/MainActivity.kt b/app/src/main/java/com/example/android/uamp/MainActivity.kt index 62cce9756..d63add70e 100644 --- a/app/src/main/java/com/example/android/uamp/MainActivity.kt +++ b/app/src/main/java/com/example/android/uamp/MainActivity.kt @@ -43,9 +43,6 @@ class MainActivity : AppCompatActivity() { InjectorUtils.provideMainActivityViewModel(this) } private var castContext: CastContext? = null - private lateinit var selector: MediaRouteSelector - private lateinit var router: MediaRouter - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -56,16 +53,6 @@ class MainActivity : AppCompatActivity() { setContentView(R.layout.activity_main) - router = MediaRouter.getInstance(this) - selector = MediaRouteSelector.Builder() - .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) - .build() - router.routerParams = MediaRouterParams.Builder().setTransferToLocalEnabled(true).build() - router.addCallback( - selector, MediaRouterCallback(), - MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY - ) - // Since UAMP is a music player, the volume controls should adjust the music volume while // in the app. volumeControlStream = AudioManager.STREAM_MUSIC @@ -133,20 +120,6 @@ class MainActivity : AppCompatActivity() { private fun getBrowseFragment(mediaId: String): MediaItemFragment? { return supportFragmentManager.findFragmentByTag(mediaId) as MediaItemFragment? } - - private inner class MediaRouterCallback : MediaRouter.Callback() { - override fun onRouteSelected( - router: MediaRouter, - route: MediaRouter.RouteInfo, - reason: Int - ) { - if (reason == MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) { - Log.d(TAG, "Unselected because route changed, continue playback") - } else if (reason == MediaRouter.UNSELECT_REASON_STOPPED) { - Log.d(TAG, "Unselected because route was stopped, stop playback") - } - } - } } private const val TAG = "MainActivity" \ No newline at end of file diff --git a/common/src/main/java/com/example/android/uamp/media/MusicService.kt b/common/src/main/java/com/example/android/uamp/media/MusicService.kt index 716c49960..abfc36523 100644 --- a/common/src/main/java/com/example/android/uamp/media/MusicService.kt +++ b/common/src/main/java/com/example/android/uamp/media/MusicService.kt @@ -33,6 +33,10 @@ import android.widget.Toast import androidx.core.content.ContextCompat import androidx.media.MediaBrowserServiceCompat import androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT +import androidx.mediarouter.media.MediaControlIntent +import androidx.mediarouter.media.MediaRouteSelector +import androidx.mediarouter.media.MediaRouter +import androidx.mediarouter.media.MediaRouterParams import com.example.android.uamp.media.extensions.album import com.example.android.uamp.media.extensions.flag import com.example.android.uamp.media.extensions.id @@ -104,6 +108,10 @@ open class MusicService : MediaBrowserServiceCompat() { private lateinit var storage: PersistentStorage + private lateinit var mediaRouteSelector: MediaRouteSelector + private lateinit var mediaRouter: MediaRouter + private val mediaRouterCallback = MediaRouterCallback() + /** * This must be `by lazy` because the source won't initially be ready. * See [MusicService.onLoadChildren] to see where it's accessed (and first @@ -216,6 +224,18 @@ open class MusicService : MediaBrowserServiceCompat() { packageValidator = PackageValidator(this, R.xml.allowed_media_browser_callers) storage = PersistentStorage.getInstance(applicationContext) + + mediaRouter = MediaRouter.getInstance(this) + mediaRouter.setMediaSessionCompat(mediaSession) + mediaRouteSelector = MediaRouteSelector.Builder() + .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) + .build() + mediaRouter.routerParams = + MediaRouterParams.Builder().setTransferToLocalEnabled(true).build() + mediaRouter.addCallback( + mediaRouteSelector, mediaRouterCallback, + MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY + ) } /** @@ -250,6 +270,9 @@ open class MusicService : MediaBrowserServiceCompat() { // Free ExoPlayer resources. exoPlayer.removeListener(playerListener) exoPlayer.release() + + // Stop listening for route changes. + mediaRouter.removeCallback(mediaRouterCallback) } /** @@ -500,7 +523,10 @@ open class MusicService : MediaBrowserServiceCompat() { } else { val playbackStartPositionMs = - extras?.getLong(MEDIA_DESCRIPTION_EXTRAS_START_PLAYBACK_POSITION_MS, C.TIME_UNSET) + extras?.getLong( + MEDIA_DESCRIPTION_EXTRAS_START_PLAYBACK_POSITION_MS, + C.TIME_UNSET + ) ?: C.TIME_UNSET preparePlaylist( @@ -650,6 +676,21 @@ open class MusicService : MediaBrowserServiceCompat() { ).show() } } + + inner class MediaRouterCallback : MediaRouter.Callback() { + override fun onRouteSelected( + router: MediaRouter, + route: MediaRouter.RouteInfo, + reason: Int + ) { + if (reason == MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) { + Log.d(TAG, "Unselected because route changed, continue playback") + } else if (reason == MediaRouter.UNSELECT_REASON_STOPPED) { + Log.d(TAG, "Unselected because route was stopped, stop playback") + currentPlayer.stop() + } + } + } } /* From 0c7566ac4a85baa8ca73e1754cc2eeb98a67b4c5 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 1 Oct 2020 22:33:54 +0100 Subject: [PATCH 4/4] Remove unecessary edits --- .../main/java/com/example/android/uamp/MainActivity.kt | 9 --------- .../java/com/example/android/uamp/media/MusicService.kt | 5 +---- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/app/src/main/java/com/example/android/uamp/MainActivity.kt b/app/src/main/java/com/example/android/uamp/MainActivity.kt index d63add70e..9106ed838 100644 --- a/app/src/main/java/com/example/android/uamp/MainActivity.kt +++ b/app/src/main/java/com/example/android/uamp/MainActivity.kt @@ -18,16 +18,10 @@ package com.example.android.uamp import android.media.AudioManager import android.os.Bundle -import android.util.Log import android.view.Menu import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders -import androidx.mediarouter.media.MediaControlIntent -import androidx.mediarouter.media.MediaRouteSelector -import androidx.mediarouter.media.MediaRouter -import androidx.mediarouter.media.MediaRouterParams import com.example.android.uamp.fragments.MediaItemFragment import com.example.android.uamp.media.MusicService import com.example.android.uamp.utils.Event @@ -36,7 +30,6 @@ import com.example.android.uamp.viewmodels.MainActivityViewModel import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastContext - class MainActivity : AppCompatActivity() { private val viewModel by viewModels { @@ -121,5 +114,3 @@ class MainActivity : AppCompatActivity() { return supportFragmentManager.findFragmentByTag(mediaId) as MediaItemFragment? } } - -private const val TAG = "MainActivity" \ No newline at end of file diff --git a/common/src/main/java/com/example/android/uamp/media/MusicService.kt b/common/src/main/java/com/example/android/uamp/media/MusicService.kt index abfc36523..b51eaca7d 100644 --- a/common/src/main/java/com/example/android/uamp/media/MusicService.kt +++ b/common/src/main/java/com/example/android/uamp/media/MusicService.kt @@ -523,10 +523,7 @@ open class MusicService : MediaBrowserServiceCompat() { } else { val playbackStartPositionMs = - extras?.getLong( - MEDIA_DESCRIPTION_EXTRAS_START_PLAYBACK_POSITION_MS, - C.TIME_UNSET - ) + extras?.getLong(MEDIA_DESCRIPTION_EXTRAS_START_PLAYBACK_POSITION_MS, C.TIME_UNSET) ?: C.TIME_UNSET preparePlaylist(