使用 media3 在后台播放音乐

pri*_*imo 3 android exoplayer android-jetpack

我一直在尝试在后台使用 exoplayer media3 lib 播放音频。到目前为止,我能够播放音频并且它也在后台播放,但我将 url 保留为静态的测试 url。现在,我想将 url 传递给我的服务类,但我无法理解该怎么做。

在这里附上我的代码文件..

class PlaybackService : MediaSessionService() {
    // Create your Player and MediaSession in the onCreate lifecycle event
    lateinit var player: Player
    private var mediaSession: MediaSession? = null
    override fun onCreate() {
        super.onCreate()
        player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
        player.prepare()
        player.playWhenReady = true
        player.seekTo(0, 0)
        val mediaItem =
            MediaItem.fromUri("https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4")
        player.setMediaItem(mediaItem)
    }

    // Return a MediaSession to link with the MediaController that is making
    // this request.
    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
        mediaSession

    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的片段

class MusicPlayerFragment : BaseFragment() {


private lateinit var controllerFuture: ListenableFuture<MediaController>
private val controller: MediaController?
    get() = if (controllerFuture.isDone) controllerFuture.get() else null

private val currentWindow = 0
var playerStopped = true
private val playbackPosition: Long = 0


override fun onStart() {
    super.onStart()
    playMedia()
}

private fun playMedia() {
    val sessionToken = SessionToken(
        requireContext(),
        ComponentName(requireContext(), PlaybackService::class.java)
    )
    controllerFuture =
        MediaController.Builder(requireContext(), sessionToken)
            .buildAsync()
    controllerFuture.addListener({
        fragmentMusicPlayerBinding.playerView.player = controllerFuture.get()
        fragmentMusicPlayerBinding.controls.player = controllerFuture.get()
    }, MoreExecutors.directExecutor())
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
   
    fragmentMusicPlayerBinding.imgPlay.setOnClickListener {
        if (playerStopped) {
            playerStopped = false
            playMedia()
        }
    }
    fragmentMusicPlayerBinding.imgStop.setOnClickListener {
        stopMediaPlayer()
    }

    fragmentMusicPlayerBinding.imgPause.setOnClickListener {
        fragmentMusicPlayerBinding.playerView.player?.pause()
    }

    fragmentMusicPlayerBinding.imgRewind.setOnClickListener {
        fragmentMusicPlayerBinding.playerView.player?.seekTo(0)
    }
    
}

private fun stopMediaPlayer() {
    controllerFuture.get().stop()
    controllerFuture.get().playWhenReady = false
    MediaController.releaseFuture(controllerFuture)
    playerStopped = true
    fragmentMusicPlayerBinding.playerView.player = null
}
Run Code Online (Sandbox Code Playgroud)

}

我面临的问题

  1. 我想动态设置从 api 获取的媒体 uri。那么,该怎么做呢?

  2. 使用此代码,我在通知中心收到 mediapplayer 通知,当我从那里播放或暂停时,音乐会停止或播放,但同时当我停止通知并从片段播放时,音乐不会播放。

  3. 每当我停止片段中的音乐时,音乐也会在后台继续播放,并且通知仍然存在

  4. 当我从通知中心清除音乐通知时,音乐也会继续播放。

如果有人遇到过任何问题,请帮助我解决我所面临的问题。TIA

pri*_*imo 6

经过多次尝试和大量研究,我找到了自己问题的答案。发布答案可以帮助社区中的其他人找到相同的答案。没有什么不好的感觉,但三天后我发布了我自己问题的答案,但这里没有人来帮助我。

注意:我的代码中使用的 Exoplayer(Mediaplayer) 是处于 beta 模式的 media3 lib。代码在 android 11 和 android 12 中进行了测试,运行良好。所以,继续使用它吧。

这个YouTube 视频对我的回答帮助很大。

我按照视频中的说明稍微修改了我的代码,这是我的最终代码。

    class MusicPlayerActivity : BaseActivity() {

    lateinit var musicPlayerBinding: ActivityMusicPlayerBinding
    var player: Player? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        musicPlayerBinding = ActivityMusicPlayerBinding.inflate(layoutInflater)
        setContentView(musicPlayerBinding.root)
        bindMusicService()
    }

    override fun onBackPressed() {
        unbindService(playerServiceConnection)
        if (player?.isPlaying!!) {
            player?.stop()
        }
        player?.release()
        player = null
        super.onBackPressed()
    }
    
    private fun bindMusicService() {
        val intent = Intent(this, PlayerService::class.java)
        bindService(intent, playerServiceConnection, Context.BIND_AUTO_CREATE)
    }

    private val playerServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val binder = service as PlayerService.ServiceBinder
            player = binder.getPlayerService().player!!
            player?.prepare()
            player?.playWhenReady = false
            player?.seekTo(0, 0)
            val mediaMetaData = MediaMetadata.Builder()
                .setAlbumTitle(MusicPlayerData.audioTitle)
                .build()
            val mediaItem =
                MediaItem.Builder().setMediaMetadata(mediaMetaData)
                    .setUri(MusicPlayerData.audioUri)
                    .build()
            player?.setMediaItem(mediaItem)
            setPlayerControls()
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            TODO("Not yet implemented")
        }
    }

    private fun setPlayerControls() {
        musicPlayerBinding.playerView.player = player
        musicPlayerBinding.controls.player = player
        player?.addListener(object : Player.Listener {
            override fun onPlaybackStateChanged(playbackState: Int) {
                super.onPlaybackStateChanged(playbackState)
                when (playbackState) {
                    Player.STATE_BUFFERING -> show(musicPlayerBinding.progressCircular)
                    Player.STATE_READY -> {
                        player?.playWhenReady = true
                        hide(musicPlayerBinding.progressCircular)
                    }
                }
            }
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的服务等级

class PlayerService : Service() {

private val iBinder = ServiceBinder()
var player: Player? = null
private var mediaSession: MediaSession? = null
lateinit var notificationManager: PlayerNotificationManager

inner class ServiceBinder : Binder() {
    fun getPlayerService(): PlayerService = this@PlayerService
}


override fun onBind(intent: Intent?): IBinder {
    return iBinder
}

override fun onCreate() {
    super.onCreate()
    player = ExoPlayer.Builder(this).build()
    mediaSession =
        MediaSession.Builder(this, player!!).setSessionActivity(pendingIntent()!!).setId(Random(5).toString())
            .build()
    notificationManager = PlayerNotificationManager.Builder(this, 111, "Music Channel")
        .setChannelImportance(IMPORTANCE_HIGH)
        .setSmallIconResourceId(R.drawable.music)
        .setChannelDescriptionResourceId(R.string.app_name)
        .setChannelNameResourceId(R.string.app_name)
        .setMediaDescriptionAdapter(audioDescriptor)
        .setNotificationListener(notificationListener)
        .build()


    notificationManager.setPlayer(player)
    notificationManager.setPriority(PRIORITY_MAX)
    notificationManager.setUseRewindAction(true)
    notificationManager.setUseFastForwardAction(false)
    notificationManager.setUsePreviousAction(false)
    notificationManager.setUsePlayPauseActions(true)
}


override fun onDestroy() {
    if (player?.isPlaying!!) {
        player?.stop()
    }
    notificationManager.setPlayer(null)
    player?.release()
    player = null
    stopForeground(true)
    stopSelf()
    super.onDestroy()
}

private val notificationListener = object : PlayerNotificationManager.NotificationListener {
    override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
        super.onNotificationCancelled(notificationId, dismissedByUser)
        stopForeground(true)
        if (player?.isPlaying!!) {
            player?.stop()
            player?.release()
        }
    }

    override fun onNotificationPosted(
        notificationId: Int,
        notification: Notification,
        ongoing: Boolean
    ) {
        super.onNotificationPosted(notificationId, notification, ongoing)
        startForeground(notificationId, notification)
    }
}

private val audioDescriptor = object : PlayerNotificationManager.MediaDescriptionAdapter {
    override fun getCurrentContentTitle(player: Player): CharSequence {
        return player.currentMediaItem?.mediaMetadata?.albumTitle!!
    }

    override fun createCurrentContentIntent(player: Player): PendingIntent? {
        return pendingIntent()
    }

    override fun getCurrentContentText(player: Player): CharSequence? {
        return ""
    }

    override fun getCurrentLargeIcon(
        player: Player,
        callback: PlayerNotificationManager.BitmapCallback
    ): Bitmap? {
        val bitmapDrawable: BitmapDrawable =
            ContextCompat.getDrawable(
                applicationContext,
                R.drawable.cma_logo_render
            ) as BitmapDrawable
        return bitmapDrawable.bitmap
    }
}

private fun pendingIntent(): PendingIntent? {
    val intent = Intent(applicationContext, MusicPlayerActivity::class.java)
    return PendingIntent.getActivity(
        applicationContext,
        0,
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )
}
Run Code Online (Sandbox Code Playgroud)

我面临的问题

  1. 我想动态设置从 api 获取的媒体 uri。那么,该怎么做呢? -----> 无需在服务类中设置媒体 uri,我们可以从活动中的服务类获取播放器的实例,并仅执行活动类中的所有功能

  2. 使用此代码,我在通知中心收到 mediapplayer 通知,当我从那里播放或暂停时,音乐会停止或播放,但同时当我停止通知并从片段播放时,音乐不会播放。 ----->修改我的服务类后问题解决

  3. 每当我停止片段中的音乐时,音乐也会在后台继续播放,并且通知仍然存在 -----> 修改我的服务类后问题得到解决

  4. 当我从通知中心清除音乐通知时,音乐也会继续播放。
    -----> 为此,我已将会话活动设置为从当前播放状态打开活动

执行完上面的事情后,

  1. 活动返回到当前状态
  2. 从通知和活动中播放音乐是同步的
  3. 背景音乐也在播放
  4. 当活动回到后台后恢复状态后,无论是从 UI 还是通知中心,播放暂停都可以完美地工作。
  5. 可以播放、暂停、停止和快退
  6. 如果需要,请在音频描述符中设置音乐标题和图标,否则可以留空。
  7. 播放歌曲时通知不会清除
  8. 如果应用程序被杀死,那么歌曲就会停止播放,通知也会消失