Команды воспроизведения мультимедиа в Google Assistant не запускают мое приложение
Мне не удается заставить Google Assistant воспроизводить мультимедиа для моего мультимедийного приложения.
Я проверил с помощью приложения Media Controller Tester , что действия воспроизведения работают. Я могу использовать действия с открытыми функциями с помощником.
Но каждый раз я пытаюсь использовать такие фразы, как
Play AppName
или же
Play Station on AppName
, Ассистент пытается запустить TuneIn. Если я попробую
Play music on AppName
Ассистент запускает 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). Добавление этого не помогает.