android - Google Assistant 上的媒体播放命令无法启动我的应用
问题描述
我无法让 Google 助理为我的媒体应用播放媒体。
我已经使用Media Controller Tester应用程序验证了播放操作是否正常。我可以通过 Assistant 使用 Open Feature Actions。
但每次我尝试使用类似Play AppName
or的短语时Play Station on AppName
,Assistant 都会尝试启动 TuneIn。如果我尝试使用Play music on AppName
Assistant 启动 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)。添加这个没有帮助。
解决方案
推荐阅读
- java - 非解析 POM。.m2 文件夹中的 POM 已损坏
- entity-framework - 有没有办法找出 Entity Framework 正在创建的 SQL?
- javascript - 向数据库发出请求时加载程序未运行
- android - Spinner 下拉边框底部移除
- reactjs - React Router如何动态更改url的搜索部分
- dto - 在带有 RestController 的 SpringBoot 应用程序中使用 Mapstruct 和 lombok 将域类映射到 DTO 类
- c++ - c++20 views with parameter pack or initialiser list
- javascript - 将对象推送到来自 2D 数组的数组
- python - 来自多个包层次结构的 Python 日志记录(不使用根记录器)
- docker - Google Cloud Run 中的 docker-compose.yml