首页 > 解决方案 > Google Assistant 上的媒体播放命令无法启动我的应用

问题描述

我无法让 Google 助理为我的媒体应用播放媒体。

我已经使用Media Controller Tester应用程序验证了播放操作是否正常。我可以通过 Assistant 使用 Open Feature Actions。

但每次我尝试使用类似Play AppNameor的短语时Play Station on AppName,Assistant 都会尝试启动 TuneIn。如果我尝试使用Play music on AppNameAssistant 启动 YouTube Music 。

我在这里尝试了文档中的所有内容,并使用 UAMP 作为基础(我也看到了类似的行为)

这是我的音频服务的精简版:

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()
    }
}

模块清单(注意 - 该服务不在我的主应用程序模块中)

<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>

在主应用清单中:

<?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>

我也尝试过设置我的播放状态:

        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
        }

但我的理解是我不MediaSessionConnector应该需要照顾它(因为我使用的是 ExoPlayer)。添加这个没有帮助。

标签: androidmedia-playerexoplayerapp-actionsgoogle-assistant

解决方案


推荐阅读