Media3 with Google Assistant integration

94 views Asked by At

We wanted to integrate Google Assistant functionalities within our app with the new Media3 Library.

The goal is so that the user is able to say:

"Play Energy Basel" or "Play Energy Basel on/in Energy" or something similar to that.

Energy is the name of the app, in which there are about 20 radio channels. Sadly, the Google Assistant always tries to open another app called TuneIn, even though it's not even installed, and my MediaLibrarySession.Callback functions never get invoked by it.

The simplified code where I left out unnecessary code and reduced the available channels to only one possible MediaItem:

class RadioService: MediaLibraryService() {

    override fun onCreate() {
        super.onCreate()

        val sessionActivityPendingIntent =
            PendingIntent.getActivity(
                this,
                0,
                Intent(this, MainActivity::class.java),
                PendingIntent.FLAG_IMMUTABLE
            )

        mediaSession = MediaLibrarySession.Builder(
            this,
            exoPlayer,
            MediaLibrarySessionCallback()
        )
            .setSessionActivity(sessionActivityPendingIntent)
            .setBitmapLoader(CacheBitmapLoader(DataSourceBitmapLoader(this)))
            .build()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? {
        return mediaSession
    }

    private inner class MediaLibrarySessionCallback: MediaLibrarySession.Callback {
        //All clients can connect
        override fun onConnect(
            session: MediaSession,
            controller: MediaSession.ControllerInfo
        ): MediaSession.ConnectionResult {
            return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
                .setAvailablePlayerCommands(MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS)
                .build()
        }

        override fun onGetLibraryRoot(
            session: MediaLibrarySession,
            browser: MediaSession.ControllerInfo,
            params: LibraryParams?
        ): ListenableFuture<LibraryResult<MediaItem>> {
            return Futures.immediateFuture(
                LibraryResult.ofItem(
                    MediaItem.Builder()
                        .setUri("*****") <- Censored url for StackOverflow
                        .setMediaId("energy")
                        .setMediaMetadata(
                            MediaMetadata
                                .Builder()
                                .setIsBrowsable(false)
                                .setIsPlayable(false)
                                .build()
                        )
                        .build(),
                    params
                )
            )
        }

        override fun onGetItem(
            session: MediaLibrarySession,
            browser: MediaSession.ControllerInfo,
            mediaId: String
        ): ListenableFuture<LibraryResult<MediaItem>> {
            return Futures.immediateFuture(
                LibraryResult.ofItem(
                    allMediaItems[0],
                    null
                )
            )
        }

        override fun onGetChildren(
            session: MediaLibrarySession,
            browser: MediaSession.ControllerInfo,
            parentId: String,
            page: Int,
            pageSize: Int,
            params: LibraryParams?
        ): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
            return Futures.immediateFuture(
                LibraryResult.ofItemList(
                    allMediaItems,
                    null
                )
            )
        }

        override fun onSearch(
            session: MediaLibrarySession,
            browser: MediaSession.ControllerInfo,
            query: String,
            params: LibraryParams?
        ): ListenableFuture<LibraryResult<Void>> {
            return super.onSearch(session, browser, query, params)
        }

        override fun onGetSearchResult(
            session: MediaLibrarySession,
            browser: MediaSession.ControllerInfo,
            query: String,
            page: Int,
            pageSize: Int,
            params: LibraryParams?
        ): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
            return super.onGetSearchResult(session, browser, query, page, pageSize, params)
        }
    }

    val allMediaItems = listOf(
        MediaItem.Builder()
            .setMediaId("energy-basel")
            .setUri("****") <- Censored url for StackOverflow
            .setMimeType(MimeTypes.AUDIO_MPEG)
            .setLiveConfiguration(MediaItem.LiveConfiguration.Builder().build())
            .setMediaMetadata(
                MediaMetadata.Builder()
                    .setTitle("Energy Basel")
                    .setArtworkUri(Uri.parse("****")) <- Censored url for StackOverflow
                    .setIsPlayable(true)
                    .setIsBrowsable(false)
                    .build()
            )
            .build()
    )
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Energy"
        tools:targetApi="34">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.EnergyTest">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <service
            android:name=".player.MusicService"
            android:enabled="true"
            android:exported="true"
            android:foregroundServiceType="mediaPlayback"
            tools:ignore="ExportedService">

            <intent-filter>
                <action android:name="androidx.media3.session.MediaLibraryService"/>
                <action android:name="android.media.browse.MediaBrowserService" />
            </intent-filter>
        </service>
    </application>

</manifest>

I have tried it with Media3 library version 1.1.1 and 1.2.0-beta01, but with the same behavior. Neither onSearch, onGetSearchResult, onGetChildren, onGetItem, onGetLibraryRoot or onConnect are called. So I think, there might be a problem with advertising my app to the Google Assistant.

Are some additional steps required to make this work?

I have read about BII's but there seems to be no BII that is related to media and according to the documentation, this should somehow work magically out of the box with Media3.

0

There are 0 answers