Android Media3 会话和控制器 - 播放未开始

问题描述 投票:0回答:1

我正在尝试实现 Android Media3 MediaSessionService 和 MediaController,但由于某种原因播放无法开始。我究竟做错了什么?我认为我所做的一切都完全按照在后台播放媒体中所述进行。

播放服务.kt

class PlaybackService : MediaSessionService() {

    private var mediaSession: MediaSession? = null

    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
        mediaSession

    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }
}

MainActivity.kt

class MainActivity : ComponentActivity() {

    private lateinit var controllerFuture: ListenableFuture<MediaController>
    private lateinit var controller: MediaController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        log("onCreate MainActivity")
        setContent {
            TestMediaTheme {

                Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {

                    Button(onClick = {

                        //val url = "android.resource://$packageName/${R.raw.test}"
                        val url = "https://download.samplelib.com/mp3/sample-15s.mp3"

                        play(url)

                    }) {
                        Text(text = "Play")
                    }

                }

            }
        }
    }

    override fun onStart() {
        super.onStart()
        val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
        controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
        controllerFuture.addListener(
            {
                controller = controllerFuture.get()
                initController()
            },
            MoreExecutors.directExecutor()
        )
    }

    override fun onStop() {
        MediaController.releaseFuture(controllerFuture)
        super.onStop()
    }

    private fun initController() {
        //controller.playWhenReady = true
        controller.addListener(object : Player.Listener {

            override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
                super.onMediaMetadataChanged(mediaMetadata)
                log("onMediaMetadataChanged=$mediaMetadata")
            }

            override fun onIsPlayingChanged(isPlaying: Boolean) {
                super.onIsPlayingChanged(isPlaying)
                log("onIsPlayingChanged=$isPlaying")
            }

            override fun onPlaybackStateChanged(playbackState: Int) {
                super.onPlaybackStateChanged(playbackState)
                log("onPlaybackStateChanged=${getStateName(playbackState)}")
            }

            override fun onPlayerError(error: PlaybackException) {
                super.onPlayerError(error)
                log("onPlayerError=${error.stackTraceToString()}")
            }

            override fun onPlayerErrorChanged(error: PlaybackException?) {
                super.onPlayerErrorChanged(error)
                log("onPlayerErrorChanged=${error?.stackTraceToString()}")
            }
        })
        log("start=${getStateName(controller.playbackState)}")
        log("COMMAND_PREPARE=${controller.isCommandAvailable(COMMAND_PREPARE)}")
        log("COMMAND_SET_MEDIA_ITEM=${controller.isCommandAvailable(COMMAND_SET_MEDIA_ITEM)}")
        log("COMMAND_PLAY_PAUSE=${controller.isCommandAvailable(COMMAND_PLAY_PAUSE)}")
    }

    private fun play(url: String) {
        log("play($url)")
        log("before=${getStateName(controller.playbackState)}")
        controller.setMediaItem(MediaItem.fromUri(url))
        controller.prepare()
        controller.play()
        log("after=${getStateName(controller.playbackState)}")
    }

    private fun getStateName(i: Int): String? {
        return when (i) {
            1 -> "STATE_IDLE"
            2 -> "STATE_BUFFERING"
            3 -> "STATE_READY"
            4 -> "STATE_ENDED"
            else -> null
        }
    }

    private fun log(message: String) {
        Log.e("=====[TestMedia]=====", message)
    }
}

AndroidManifest.xml

<?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.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.TestMedia"
        tools:targetApi="33">

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:theme="@style/Theme.TestMedia">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>

        <service
            android:name=".PlaybackService"
            android:exported="true"
            android:foregroundServiceType="mediaPlayback">
            <intent-filter>
                <action android:name="androidx.media3.session.MediaSessionService" />
                <action android:name="android.media.browse.MediaBrowserService" />
            </intent-filter>
        </service>

    </application>

</manifest>

这是调试日志:

01:51:22.004  E  onCreate MainActivity
01:51:22.544  E  start=STATE_IDLE
01:51:22.544  E  COMMAND_PREPARE=true
01:51:22.544  E  COMMAND_SET_MEDIA_ITEM=true
01:51:22.544  E  COMMAND_PLAY_PAUSE=true
//click 1
01:51:24.027  E  play(https://download.samplelib.com/mp3/sample-15s.mp3)
01:51:24.027  E  before=STATE_IDLE
01:51:24.029  E  onPlaybackStateChanged=STATE_BUFFERING
01:51:24.029  E  after=STATE_BUFFERING
01:51:24.053  E  onPlaybackStateChanged=STATE_ENDED
//click 2
01:51:25.715  E  play(https://download.samplelib.com/mp3/sample-15s.mp3)
01:51:25.715  E  before=STATE_ENDED
01:51:25.716  E  onPlaybackStateChanged=STATE_BUFFERING
01:51:25.716  E  after=STATE_BUFFERING
//click 3
01:51:26.749  E  play(https://download.samplelib.com/mp3/sample-15s.mp3)
01:51:26.749  E  before=STATE_BUFFERING
01:51:26.750  E  after=STATE_BUFFERING
//click 4
01:51:30.172  E  play(https://download.samplelib.com/mp3/sample-15s.mp3)
01:51:30.172  E  before=STATE_BUFFERING
01:51:30.173  E  after=STATE_BUFFERING

所以看起来好像在第一次单击后播放器缓冲然后立即结束,而在第二次单击后它只是无限期地缓冲。有人知道可能是什么问题吗?

android android-jetpack-compose android-mediaplayer exoplayer android-mediasession
1个回答
16
投票

更新:

在1.1.0版本中已修复。

向 MediaSession.Callback.onAddMediaItems 添加默认实现,以允许请求的 MediaItem 传递到 Player(如果它们具有本地配置(例如 URI))。


旧答案:

终于我找到了解决方案,感谢这个问题这个问题。 Media3 指南似乎缺少一个非常关键的部分。

来自 onAddMediaItems 文档:

Note that the requested media items don't have a MediaItem.LocalConfiguration (for example, a URI) and need to be updated to make them playable by the underlying Player.

最后我通过重写 MediaSession.Callback.onAddMediaItems 解决了这个问题

class PlaybackService : MediaSessionService(), MediaSession.Callback {

    private var mediaSession: MediaSession? = null

    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).setCallback(this).build()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
        mediaSession

    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }

    override fun onAddMediaItems(
        mediaSession: MediaSession,
        controller: MediaSession.ControllerInfo,
        mediaItems: MutableList<MediaItem>
    ): ListenableFuture<MutableList<MediaItem>> {
        val updatedMediaItems = mediaItems.map { it.buildUpon().setUri(it.mediaId).build() }.toMutableList()
        return Futures.immediateFuture(updatedMediaItems)
    }
}

然后更换

controller.setMediaItem(MediaItem.fromUri(url))

val media = MediaItem.Builder().setMediaId(url).build()
controller.setMediaItem(media)
© www.soinside.com 2019 - 2024. All rights reserved.