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

Jetpack Media3结合Compose:Service与ViewModel状态不同步求助

解决Media3播放状态与UI不同步的问题

问题根源

你当前的ViewModel自行维护_isPlaying状态,仅在调用内部play()/pause()方法时更新,但通知栏操作直接通过MediaSession控制Player,ViewModel未监听这些外部操作带来的状态变更,导致UI显示与实际播放状态脱节。

解决方案

核心思路是让ViewModel监听MediaController的播放状态回调,实时同步真实状态到UI,而非手动维护状态。

步骤1:在ViewModel中注册MediaController状态监听器

修改RadioViewModel,添加对MediaController的播放状态监听,自动同步状态到LiveData:

class RadioViewModel(application: Application) : AndroidViewModel(application) {
    private val _isPlaying = MutableLiveData(false)
    val isPlaying: LiveData<Boolean> = _isPlaying
    private val playerReady = MutableStateFlow(false)
    private var _player: MediaController? = null
    private val _playbackState = MutableLiveData(0)
    val playbackState: LiveData<Int> = _playbackState

    // 播放状态监听器,实时同步状态
    private val playbackStateListener = object : MediaController.PlaybackStateListener {
        override fun onPlaybackStateChanged(state: PlaybackState) {
            _isPlaying.postValue(state.isPlaying)
            _playbackState.postValue(state.state)
        }
    }

    init {
        val sessionToken = SessionToken(application, ComponentName(application, PlaybackService::class.java))
        val controllerFuture = MediaController.Builder(application, sessionToken).buildAsync()
        controllerFuture.addListener(
            {
                _player = controllerFuture.get().apply {
                    // 注册监听器
                    addPlaybackStateListener(playbackStateListener)
                    // 初始化时同步一次当前状态
                    val currentState = playbackState
                    _isPlaying.postValue(currentState.isPlaying)
                    _playbackState.postValue(currentState.state)
                }
                playerReady.value = true
            },
            MoreExecutors.directExecutor()
        )
    }

    fun setUri(uri: Uri) = viewModelScope.launch {
        playerReady.first { it }
        val mediaItem = MediaItem.fromUri(uri)
        _player?.let { player ->
            player.setMediaItem(mediaItem)
            player.prepare()
        }
        _player?.play()
    }

    fun togglePlay() = _player?.let { player ->
        if (player.playbackState.isPlaying) player.pause() else player.play()
    }

    override fun onCleared() {
        super.onCleared()
        _player?.apply {
            removePlaybackStateListener(playbackStateListener)
            release()
        }
        val intent = Intent(getApplication(), PlaybackService::class.java)
        getApplication<Application>().stopService(intent)
    }
}

步骤2:移除手动维护状态的逻辑

删除原ViewModel中play()/pause()方法里手动设置_isPlaying.value的代码,所有状态更新完全依赖MediaController的监听器回调,避免手动状态与真实状态冲突。

步骤3:完善PlaybackService的通知栏支持(可选但必要)

你的PlaybackService目前未配置通知栏,Media3需要通过MediaNotificationProvider才能让通知栏控制生效,否则通知栏操作可能无法触发状态回调。在Service的onCreate中补充配置:

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

    // 配置通知栏
    val notificationProvider = DefaultMediaNotificationProvider(this)
    val notificationManager = getSystemService(NotificationManager::class.java)
    mediaSession.setNotificationProvider(
        notificationProvider,
        notificationManager,
        R.drawable.ic_notification // 替换为你的通知图标资源ID
    )
    // 设置点击通知跳转的Activity
    mediaSession.setSessionActivity(PendingIntent.getActivity(
        this,
        0,
        Intent(this, MainActivity::class.java), // 替换为你的主Activity
        PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
    ))
}

关键原理

MediaController是ViewModel与MediaSessionService的通信桥梁,所有播放操作都应通过MediaController执行,状态变化通过其监听器同步。放弃手动维护状态,完全依赖MediaController提供的真实状态,即可保证UI与通知栏、Service的状态一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 11:07:23