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

无需android:configChanges,修复ExoPlayer旋转后视频重启问题

解决ExoPlayer设备旋转时视频重启的问题(不用android:configChanges)

我太懂这种头疼了!你已经尝试了保存状态但没生效,核心问题大概率是ExoPlayer实例的生命周期没管好,或者恢复状态的时机/方式不对。咱们一步步来修复:

1. 用ViewModel托管ExoPlayer实例(关键!)

Fragment旋转时会重建,但ViewModel在配置变化时会保留下来,把ExoPlayer实例放在ViewModel里,就能避免旋转时重新创建player导致的重启。

先写一个ViewModel类:

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player

class PlayerViewModel(application: Application) : AndroidViewModel(application) {
    val exoPlayer: ExoPlayer by lazy {
        ExoPlayer.Builder(application).build().apply {
            // 可选:添加全局播放状态监听
            addListener(object : Player.Listener {
                override fun onPlaybackStateChanged(playbackState: Int) {
                    // 这里可以处理播放状态变化逻辑
                }
            })
        }
    }

    // 用于缓存播放状态的变量
    var playerPosition: Long = 0L
    var currentWindow: Int = 0
    var playWhenReady: Boolean = true

    fun setMediaItem(url: String) {
        val mediaItem = MediaItem.fromUri(url)
        exoPlayer.setMediaItem(mediaItem)
        exoPlayer.prepare()
    }

    override fun onCleared() {
        super.onCleared()
        // ViewModel销毁时释放player资源,避免内存泄漏
        exoPlayer.release()
    }
}

2. 在Fragment中正确使用ViewModel并处理状态保存/恢复

接下来修改你的Fragment,从ViewModel获取player,同时完善onSaveInstanceState和恢复逻辑:

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.google.android.exoplayer2.ui.PlayerView

class VideoPlayerFragment : Fragment() {
    private lateinit var playerView: PlayerView
    private lateinit var viewModel: PlayerViewModel
    // 替换成你的视频流URL
    private val VIDEO_STREAM_URL = "https://example.com/your-video-stream.mp4"

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_video_player, container, false)
        playerView = view.findViewById(R.id.player_view)
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 获取ViewModel实例
        viewModel = ViewModelProvider(this)[PlayerViewModel::class.java]

        // 从savedInstanceState恢复之前的状态(如果存在)
        savedInstanceState?.let {
            viewModel.playerPosition = it.getLong("player_position")
            viewModel.currentWindow = it.getInt("current_window")
            viewModel.playWhenReady = it.getBoolean("play_when_ready")
        }

        // 绑定PlayerView与ViewModel中的player实例
        playerView.player = viewModel.exoPlayer

        // 仅在首次加载时设置媒体资源,避免重复加载
        if (viewModel.exoPlayer.mediaItemCount == 0) {
            viewModel.setMediaItem(VIDEO_STREAM_URL)
        }

        // 等待player准备就绪后再恢复播放位置,避免无效操作
        viewModel.exoPlayer.addListener(object : com.google.android.exoplayer2.Player.Listener {
            override fun onPlaybackStateChanged(playbackState: Int) {
                if (playbackState == com.google.android.exoplayer2.Player.STATE_READY) {
                    viewModel.exoPlayer.seekTo(viewModel.currentWindow, viewModel.playerPosition)
                    viewModel.exoPlayer.playWhenReady = viewModel.playWhenReady
                    // 恢复完成后移除监听,防止重复触发
                    viewModel.exoPlayer.removeListener(this)
                }
            }
        })
    }

    override fun onStart() {
        super.onStart()
        // 页面可见时恢复播放状态
        viewModel.exoPlayer.playWhenReady = viewModel.playWhenReady
    }

    override fun onPause() {
        super.onPause()
        // 页面不可见时保存当前状态并暂停播放
        viewModel.playerPosition = viewModel.exoPlayer.currentPosition
        viewModel.currentWindow = viewModel.exoPlayer.currentMediaItemIndex
        viewModel.playWhenReady = viewModel.exoPlayer.playWhenReady
        viewModel.exoPlayer.playWhenReady = false
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        // 将状态存入Bundle,应对进程被系统杀死的情况
        outState.putLong("player_position", viewModel.playerPosition)
        outState.putInt("current_window", viewModel.currentWindow)
        outState.putBoolean("play_when_ready", viewModel.playWhenReady)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        // 解除PlayerView与player的绑定,避免内存泄漏
        playerView.player = null
    }
}

3. 关键注意事项

  • 绝对不要在Fragment中直接创建ExoPlayer:必须通过ViewModel托管,否则旋转时Fragment重建会重新初始化player,导致视频重启。
  • 恢复状态要等player准备好:如果在player尚未加载完媒体资源时调用seekTo,会导致状态恢复无效,所以一定要在STATE_READY状态下执行恢复操作。
  • 严格管理player生命周期:在onPause时保存状态并暂停播放,onStart时恢复播放,onDestroyView时解除绑定,避免内存泄漏和异常。
  • 兼顾进程被杀场景:ViewModel只能处理配置变化的情况,如果进程被系统杀死,需要通过onSaveInstanceState保存的Bundle来恢复状态。

这样修改后,旋转设备时ExoPlayer实例不会被销毁,播放状态也能无缝衔接,视频就不会重启了!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:32:16