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

Android MediaPlayer+SurfaceView切后台恢复后视频重启问题求助

问题:SurfaceView播放网络视频切后台返回无法续播(使用seekTo会黑屏)

使用SurfaceView结合MediaPlayer播放网络视频,切后台再回到前台时视频会重启,而非从暂停位置续播。尝试过mp.seekTo(currentPosition, SEEK_CLOSEST)方法,虽然能续播但屏幕会短暂黑屏,且因需求限制无法使用TextureView。

原代码

import androidx.appcompat.app.AppCompatActivity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    MediaPlayer mp;
    SurfaceView sv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mp = new MediaPlayer();
        
        sv = (SurfaceView) findViewById(R.id.surfaceView);
        SurfaceHolder holder = sv.getHolder();
        holder.addCallback(new SurfaceHolder.Callback(){
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }

            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                try {
                    mp.setSurface(holder.getSurface());
                    mp.setDataSource("https://v2.chartboost.com/videoads/636e2334a790a80850ecd48b_720-1668162356.mp4");
                    mp.prepare();
                    mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                        @Override
                        public void onPrepared(MediaPlayer mediaPlayer) {
                            mp.start();
                        }
                    });
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) { }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        if(mp!= null) {
             mp.start();
        }
    }

    @Override
    protected void onPause(){
        super.onPause();
        mp.pause();
        mp.setSurface(null);
    }
}

问题分析

  1. 原代码在onPause中调用mp.setSurface(null),导致MediaPlayer彻底失去渲染表面;切回前台时surfaceCreated会重新执行setDataSource和prepare,相当于重新加载视频,所以会重启。
  2. 使用seekTo黑屏是因为恢复Surface后直接跳转位置,MediaPlayer需要重新渲染帧,中间出现空白等待期。

解决方案

核心思路:复用已初始化的MediaPlayer,仅在首次创建Surface时加载视频,后续Surface重建时直接恢复Surface并从暂停位置续播,避免重复初始化和不必要的seek操作。

修改后的代码

import androidx.appcompat.app.AppCompatActivity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private MediaPlayer mp;
    private SurfaceView sv;
    private int currentPosition = 0;
    private boolean isVideoLoaded = false; // 标记视频是否已完成加载初始化

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mp = new MediaPlayer();
        
        sv = findViewById(R.id.surfaceView);
        SurfaceHolder holder = sv.getHolder();
        holder.addCallback(new SurfaceHolder.Callback(){
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { }

            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                mp.setSurface(holder.getSurface());
                if (!isVideoLoaded) {
                    // 首次加载视频
                    try {
                        mp.setDataSource("https://v2.chartboost.com/videoads/636e2334a790a80850ecd48b_720-1668162356.mp4");
                        mp.prepare();
                        mp.setOnPreparedListener(mediaPlayer -> {
                            if (currentPosition > 0) {
                                mp.seekTo(currentPosition);
                            }
                            mp.start();
                            isVideoLoaded = true;
                        });
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    // 恢复播放,无需重新加载
                    if (currentPosition > 0) {
                        mp.seekTo(currentPosition);
                    }
                    mp.start();
                }
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                // Surface销毁时保存当前播放位置
                if (mp != null && mp.isPlaying()) {
                    currentPosition = mp.getCurrentPosition();
                    mp.pause();
                }
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 无需在这里强制start,交给surfaceCreated处理
    }

    @Override
    protected void onPause(){
        super.onPause();
        // 移除setSurface(null),避免MediaPlayer与Surface彻底解绑
        if (mp != null && mp.isPlaying()) {
            currentPosition = mp.getCurrentPosition();
            mp.pause();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 释放资源
        if (mp != null) {
            mp.release();
            mp = null;
        }
    }
}

关键改动点

  • 新增isVideoLoaded标记,避免每次Surface重建都重新加载视频数据源。
  • 在surfaceDestroyed中保存播放位置,替代onPause中的单一保存,确保Surface销毁时能准确记录位置。
  • 移除onPause中的mp.setSurface(null),保留MediaPlayer的内部状态,仅暂停播放。
  • 区分首次Surface创建和重建的逻辑:首次加载视频,重建时直接恢复Surface并从保存位置续播,减少不必要的IO和渲染等待。
  • 新增onDestroy方法释放MediaPlayer资源,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 18:05:59