You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从MediaSessionService示例获取Player变量并在Compose中使用?

在Compose中简单获取Media3 MediaController并实现网络媒体播放与通知

一、修复PlaybackService的核心问题

你当前的Service代码存在两个关键错误:

  • 未初始化mediaSession,导致外部无法连接会话
  • Service内部错误使用MediaController,正确逻辑是Service持有Player实例,通过MediaSession对外暴露控制能力

修改后的PlaybackService代码:

import android.content.Intent
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
import androidx.media3.session.MediaStyleNotificationHelper

class PlaybackService : MediaSessionService() {
    private lateinit var player: ExoPlayer
    private var mediaSession: MediaSession? = null

    override fun onCreate() {
        super.onCreate()
        // 初始化ExoPlayer(适配网络媒体播放)
        player = ExoPlayer.Builder(this).build()
        // 关联Player与MediaSession,对外暴露控制入口
        mediaSession = MediaSession.Builder(this, player).build()
        // 启动前台服务并显示媒体通知(Android 8.0+必须步骤)
        startForegroundWithNotification()
    }

    private fun startForegroundWithNotification() {
        val notification = MediaStyleNotificationHelper.createNotification(
            this,
            mediaSession!!.sessionToken,
            "媒体播放",
            "正在播放网络媒体",
            null // 可传入点击通知跳转的Activity类,如MainActivity::class.java
        )
        startForeground(1, notification)
    }

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

    override fun onTaskRemoved(rootIntent: Intent?) {
        if (!player.playWhenReady || player.mediaItemCount == 0) {
            stopSelf()
        }
    }

    override fun onDestroy() {
        player.stop()
        player.release()
        mediaSession?.release()
        super.onDestroy()
    }
}

二、在Compose中获取MediaController

无需复杂MVVM架构,以下两种简单方式实现:

方式1:在Activity中初始化并传递给Composable

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors

class MainActivity : ComponentActivity() {
    private lateinit var controllerFuture: ListenableFuture<MediaController>
    private var mediaController: MediaController? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 启动PlaybackService
        startService(Intent(this, PlaybackService::class.java))
        // 异步构建MediaController
        val sessionToken = SessionToken(this, PlaybackService::class.java)
        controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
        controllerFuture.addListener({
            mediaController = controllerFuture.get()
            // 控制器就绪后更新Composable
            setContent {
                mediaController?.let { HomeScreen(it) } ?: Text("加载中...")
            }
        }, MoreExecutors.directExecutor())

        // 初始显示加载状态
        setContent {
            Text("加载中...")
        }
    }

    override fun onDestroy() {
        mediaController?.release()
        super.onDestroy()
    }
}

方式2:直接在Composable中管理连接状态

import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.media3.common.MediaItem
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors

@Composable
fun HomeScreen() {
    val context = LocalContext.current
    val mediaController = remember { mutableStateOf<MediaController?>(null) }

    // 异步连接MediaSession
    LaunchedEffect(Unit) {
        if (mediaController.value == null) {
            context.startService(Intent(context, PlaybackService::class.java))
            val sessionToken = SessionToken(context, PlaybackService::class.java)
            val future: ListenableFuture<MediaController> = MediaController.Builder(context, sessionToken).buildAsync()
            future.addListener({
                mediaController.value = future.get()
            }, MoreExecutors.directExecutor())
        }
    }

    Column {
        when (mediaController.value) {
            null -> Text("加载中...")
            else -> {
                Button(onClick = {
                    // 播放网络媒体
                    val mediaItem = MediaItem.fromUri("https://example.com/your-media-file.mp3")
                    mediaController.value?.apply {
                        setMediaItem(mediaItem)
                        prepare()
                        play()
                    }
                }) {
                    Text("播放")
                }
                Button(onClick = { mediaController.value?.stop() }) {
                    Text("停止")
                }
            }
        }
    }
}

三、关键注意事项

  • 权限配置:确保Manifest中已添加INTERNET和FOREGROUND_SERVICE权限,且Service的foregroundServiceType设为mediaPlayback
  • 通知显示:必须调用startForeground将服务设为前台,否则Android 8.0+系统不会显示媒体通知
  • 资源释放:Activity销毁时释放MediaController,Service销毁时释放Player和MediaSession,避免内存泄漏

内容的提问来源于stack exchange,提问作者Amr Essam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 08:35:59