无需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
相关产品推荐
相关产品推荐

