4

I’m having some trouble getting Google Assistant to play media for my media app.

I have verified using the Media Controller Tester app that the play actions are working. I am able to use Open Feature Actions with Assistant.

But every time I try to use phrases like Play AppName or Play Station on AppName, Assistant tries to launch TuneIn. If I try Play music on AppName Assistant launches YouTube Music .

I have tried everything in the docs here and have used UAMP as a base (of which I am also seeing similar behaviour)

Here is a cut down version of my audio service:

class AudioService : MediaBrowserServiceCompat() {

    @Inject
    lateinit var audioServiceBrowserManager: AudioServiceBrowserManager

    @Inject
    lateinit var schedulerProvider: RxSchedulerProvider

    @Inject
    lateinit var playbackPreparer: AppPlaybackPreparer

    @Inject
    lateinit var playbackControlDispatcher: AppControlDispatcher

    @Inject
    lateinit var audioProvider: AudioProvider

    @Inject
    lateinit var playbackManager: PlaybackManager

    @Inject
    lateinit var mediaSessionChangedCallback: MediaSessionChangedCallback

    private lateinit var mediaSession: MediaSessionCompat
    private lateinit var mediaSessionConnector: MediaSessionConnector
    private lateinit var mediaController: MediaControllerCompat
    private lateinit var audioNotificationManager: AudioNotificationManager

    private lateinit var packageValidator: PackageValidator

    private val disposables = CompositeDisposable()

    companion object {
        private const val SEEK_BACKWARD_INCREMENT = 15000
        private const val SEEK_FORWARD_INCREMENT = 30000

        private const val MEDIA_SESSION_TAG: String = "AudioService"

        internal const val METADATA_MEDIA_TYPE = "au.net.app.player.service.metadata.mediaType"
        internal const val METADATA_MEDIA_TYPE_ONDEMAND_VIDEO = 0L
        internal const val METADATA_MEDIA_TYPE_ONDEMAND_AUDIO = 2L
        internal const val METADATA_MEDIA_TYPE_LIVE = 1L

        val DEFAULT_PLAYBACK_STATE: PlaybackStateCompat = PlaybackStateCompat.Builder()
                .setState(PlaybackStateCompat.STATE_NONE, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f)
                .build()
    }

    override fun onCreate() {
        AndroidInjection.inject(this)
        super.onCreate()

        mediaSession = MediaSessionCompat(this, MEDIA_SESSION_TAG).apply {
            setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
            isActive = true
        }

        sessionToken = mediaSession.sessionToken

        mediaSessionConnector = MediaSessionConnector(mediaSession).apply {
            setRewindIncrementMs(SEEK_BACKWARD_INCREMENT)
            setFastForwardIncrementMs(SEEK_FORWARD_INCREMENT)
            setPlaybackPreparer(playbackPreparer)
            setQueueNavigator(AppQueueNavigator(mediaSession, audioProvider, this@AudioService, this))
            setControlDispatcher(playbackControlDispatcher)
            setPlayer(playbackManager.currentPlayback.playerImpl)
            registerCustomCommandReceiver(playbackManager.mediaSessionCommandReceiver)
        }

        disposables.add(
                playbackManager.currentPlaybackObservable.subscribe { currentPlayback ->
                    mediaSessionConnector.setPlayer(currentPlayback.playerImpl)
                }
        )

        mediaController = MediaControllerCompat(this, mediaSession)
        mediaController.registerCallback(mediaSessionChangedCallback)

        try {
            audioNotificationManager = AudioNotificationManager(this, mediaController)
        } catch (e: RemoteException) {
            throw IllegalStateException("Could not create a MediaNotificationManager", e)
        }

        packageValidator = PackageValidator(this, R.xml.allowed_media_browser_callers)
    }

    private var currentLoadChildrenDisposable: Disposable? = null
    override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
        Timber.d("""
            onLoadChildren(
                parentId = $parentId, 
                result = $result
            )
        """.trimIndent())
    }

    override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
        Timber.d("""
            onGetRoot(
                clientPackageName = $clientPackageName, 
                clientUid = $clientUid,
                rootHints = $rootHints
            )
        """.trimIndent())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)

        return START_STICKY
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)

        playbackManager.handleStop()
        stopSelf()
    }

    override fun onDestroy() {
        super.onDestroy()

        playbackManager.handleStop()

        disposables.dispose()

        mediaSession.isActive = false
        mediaSession.release()
    }
}

The module manifest (note - the service is not in my main app module)

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="au.net.app.player.service">

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

    <application>

        <service
            android:name="au.net.app.player.service.AudioService"
            android:exported="true">
            <intent-filter>
                <action android:name="android.media.browse.MediaBrowserService" />
            </intent-filter>
        </service>

        <receiver android:name="androidx.media.session.MediaButtonReceiver">
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_BUTTON" />
            </intent-filter>
        </receiver>

        <meta-data
            android:name="com.google.android.gms.car.application"
            android:resource="@xml/automotive_app_desc" />

    </application>

</manifest>

In the main app manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="au.net.app"
    android:installLocation="auto">

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:name=".AppApplication"
        android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:networkSecurityConfig="@xml/network_security_config"
        android:supportsRtl="true"
        android:hardwareAccelerated="true"
        android:theme="@style/AppTheme">

        <activity
            android:name=".mainscreen.MainActivity"
            android:launchMode="singleTask"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar"
            android:resizeableActivity="true"
            android:supportsPictureInPicture="true"
            android:windowSoftInputMode="adjustResize">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <!-- Intent filters to open Feature screens -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data
                    android:host="feature"
                    android:pathPattern="/.*"
                    android:scheme="${APP_SCHEME}" />
            </intent-filter>

            <!-- Declares that the app handles SEARCH intent for media playback -->
            <!-- This is mandatory for Android Auto support: -->
            <!-- https://stackoverflow.com/questions/31953155/android-auto-voice-cannot-perform-play-x-on-y/31976075#31976075 -->
            <intent-filter>
                <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>


        <!-- Required for Google Assistant integration -->
        <meta-data android:name="com.google.android.actions" android:resource="@xml/actions" />

    </application>

</manifest>

I have also tried setting up my playback state with:

        val DEFAULT_PLAYBACK_STATE: PlaybackStateCompat = PlaybackStateCompat.Builder()
                .setActions(getSupportedActions())
                .setState(PlaybackStateCompat.STATE_NONE, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f)
                .build()

        private fun getSupportedActions(): Long {
            return PlaybackStateCompat.ACTION_PLAY or
                    PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH or
                    PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
                    PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or
                    PlaybackStateCompat.ACTION_PLAY_PAUSE
        }

But my understanding is I shouldn't need to as MediaSessionConnector should take care of that (since I am using ExoPlayer). Adding this does not help.

Nick Felker
  • 11,536
  • 1
  • 21
  • 35
Katie B
  • 193
  • 1
  • 6
  • I'm not sure if its possible to do a voice test of `play song_title on yourAppName` as it would require Google Assistant to know `yourAppName`. Even for Google App Actions, there is some plugin/Google account required integration test but not using voice as seen in the Google IO video in [Google App Actions](https://stackoverflow.com/a/56103900/295004) answer. – Morrison Chang Feb 09 '21 at 06:31
  • I have used the plugin https://developers.google.com/assistant/app/test-tool for the testing of the App Actions, this works fine. But I don't know what the intent would be for media playback. I have also been successful in using voice to test the Actions (once the preview was set up in the test tool) – Katie B Feb 09 '21 at 22:28
  • The triggering phrase `play song_title on yourAppName` is not supported by a BII. Please take a look at the [BII reference](https://developers.google.com/assistant/app/reference/built-in-intents) and find a better match for it. As an alternative use a [Custom intent](https://developers.google.com/assistant/app/custom-intents). – Diego Feb 10 '21 at 01:42
  • Thanks Diego, I might give custom intents a try. – Katie B Feb 11 '21 at 05:21

0 Answers0