实现功能:
1、下次进入,从上一次播放位置开始播放;
2、来电保存视频进度;
3、视频未看部分不能快进;
4、适应视频大小,防止播放画面变形;
5、播放与暂停按钮;
6、按home键、查看最新打开应用键,back返回键后,再次回到或打开播放页面,不报错,能正常播放。
解决问题:
1、避免MediaPlayer error(-38, 0),MediaPlayer error(-19, 0);
2、避免Mediaplayer setVideoSurfaceTexture failed: -22问题;
3、避免进入onError回调函数后,Mediaplayer的start(),pause()方面无效果;
4、Mediaplayer onCompletion后 再次start后 再调用pause无暂停效果,还是会继续播放。
解决思路:
Mediaplayer和SurfaceView都重新new一个,一切重新开始。
实现代码:
import android.content.SharedPreferences;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.*;
import cn.rainfo.surfaceviewplayer.util.DisplayUtil;
import cn.rainfo.surfaceviewplayer.util.TimeUtil;
import java.io.IOException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class SurfaceViewPlayerActivity extends AppCompatActivity implements MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnVideoSizeChangedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener, SurfaceHolder.Callback,SeekBar.OnSeekBarChangeListener,View.OnClickListener {
FrameLayout fLyt;
SurfaceView surfaceView;
TextView duration_mmss_tv,position_mmss_tv;
SeekBar position_seek_bar;
ProgressBar progressBar;
private SurfaceHolder holder;
private MediaPlayer mMediaPlayer;
private String mediaPath = "http://pvqnrhdsk.bkt.clouddn.com/%E6%B8%94%E8%88%9F%E6%99%9A%E5%94%B1.mp4";
int[] resetXY;
private int currentPosition,durationPosition,maxCurrentPosition;
ScheduledExecutorService executorService;
private TelephonyManager manager;
private boolean videoCompletion;
ImageView mPlay_pause_iv;
private Handler progressHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
if(currentPosition>maxCurrentPosition){
maxCurrentPosition=currentPosition;
}else if (currentPosition<maxCurrentPosition){
if(videoCompletion){
mMediaPlayer.start();
}
}
position_mmss_tv.setText(TimeUtil.formatMmSs(currentPosition));
position_seek_bar.setProgress(currentPosition);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_surface_view_player);
manager = (TelephonyManager) this.getSystemService(TELEPHONY_SERVICE);
// 对listen_call_state状态监听
manager.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE);
fLyt = findViewById(R.id.fLyt);
position_mmss_tv = findViewById(R.id.position_mmss_tv);
duration_mmss_tv = findViewById(R.id.duration_mmss_tv);
position_seek_bar = findViewById(R.id.position_seek_bar);
progressBar = findViewById(R.id.progressBar);
mPlay_pause_iv = findViewById(R.id.play_pause_iv);
position_seek_bar.setOnSeekBarChangeListener(this);
mPlay_pause_iv.setOnClickListener(this);
}
private void getProgress() {
executorService= new ScheduledThreadPoolExecutor(1);
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
currentPosition=mMediaPlayer.getCurrentPosition();
progressHandler.sendEmptyMessage(1);
}
},1,1,TimeUnit.SECONDS);
}
@Override
public void onBufferingUpdate(MediaPlayer mediaPlayer, int i) {
}
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
videoCompletion=true;
mMediaPlayer.pause();
mPlay_pause_iv.setImageResource(R.drawable.ac_video_play);
}
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
// Log.i("SurfaceViewPlayer", "onPrepared");
mPlay_pause_iv.setImageResource(R.drawable.ac_video_pause);
mMediaPlayer.seekTo(getLastCurrentPosition());
mMediaPlayer.start();
durationPosition=mMediaPlayer.getDuration();
position_seek_bar.setMax(durationPosition);
duration_mmss_tv.setText(TimeUtil.formatMmSs(durationPosition));
getProgress();
}
@Override
public void onVideoSizeChanged(MediaPlayer mediaPlayer, int videoX, int videoY) {
// Log.i("SurfaceViewPlayer", "onVideoSizeChanged");
if (resetXY == null) {
resetXY = DisplayUtil.resetVideoXY(DisplayUtil.getDisplayX(SurfaceViewPlayerActivity.this), videoX, videoY);
}
if (surfaceView == null) {
surfaceView = new SurfaceView(SurfaceViewPlayerActivity.this);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(resetXY[0], resetXY[1]);
fLyt.addView(surfaceView,0, layoutParams);
holder = surfaceView.getHolder();
holder.addCallback(this);
}
}
/**
* 错误常数
* MEDIA_ERROR_IO
* 文件不存在或错误,或网络不可访问错误
* 值: -1004 (0xfffffc14)
* MEDIA_ERROR_MALFORMED
* 流不符合有关标准或文件的编码规范
* 值: -1007 (0xfffffc11)
* MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK
* 视频流及其容器不适用于连续播放视频的指标(例如:MOOV原子)不在文件的开始.
* 值: 200 (0x000000c8)
* MEDIA_ERROR_SERVER_DIED
* 媒体服务器挂掉了。此时,程序必须释放MediaPlayer 对象,并重新new 一个新的。
* 值: 100 (0x00000064)
* MEDIA_ERROR_TIMED_OUT
* 一些操作使用了过长的时间,也就是超时了,通常是超过了3-5秒
* 值: -110 (0xffffff92)
* MEDIA_ERROR_UNKNOWN
* 未知错误
* 值: 1 (0x00000001)
* MEDIA_ERROR_UNSUPPORTED
* 比特流符合相关编码标准或文件的规格,但媒体框架不支持此功能
* 值: -1010 (0xfffffc0e)
* what int: 标记的错误类型:
* MEDIA_ERROR_UNKNOWN
* MEDIA_ERROR_SERVER_DIED
* extra int: 标记的错误类型.
* MEDIA_ERROR_IO
* MEDIA_ERROR_MALFORMED
* MEDIA_ERROR_UNSUPPORTED
* MEDIA_ERROR_TIMED_OUT
* MEDIA_ERROR_SYSTEM (-2147483648) - low-level system error.
*
* @param mediaPlayer
* @param what
* @param extra
* @return
*/
@Override
public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
saveLastCurrentPosition();
if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
//媒体服务器挂掉了。此时,程序必须释放MediaPlayer 对象,并重新new 一个新的。
Toast.makeText(SurfaceViewPlayerActivity.this,
"网络服务错误",
Toast.LENGTH_SHORT).show();
} else if (what == MediaPlayer.MEDIA_ERROR_UNKNOWN) {
if (extra == MediaPlayer.MEDIA_ERROR_IO) {
//文件不存在或错误,或网络不可访问错误
Toast.makeText(SurfaceViewPlayerActivity.this,
"网络文件错误",
Toast.LENGTH_SHORT).show();
} else if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
//超时
Toast.makeText(SurfaceViewPlayerActivity.this,
"网络超时",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(SurfaceViewPlayerActivity.this,
"未知错误" + extra,
Toast.LENGTH_SHORT).show();
}
}
finish();
return false;
}
@Override
public boolean onInfo(MediaPlayer mediaPlayer, int what, int extra) {
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
progressBar.setVisibility(View.GONE);
return true;
}else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
progressBar.setVisibility(View.GONE);
return true;
}else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
progressBar.setVisibility(View.VISIBLE);
return true;
}
return false;
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
// Log.i("SurfaceViewPlayer", "surfaceCreated");
mMediaPlayer.setDisplay(holder);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
private void startPlayVideo() {
progressBar.setVisibility(View.VISIBLE);
// 创建一个MediaPlayer对象
mMediaPlayer = new MediaPlayer();
// 设置播放的视频数据源
try {
mMediaPlayer.setDataSource(mediaPath);
} catch (IOException e) {
Toast.makeText(this, "视频地址错误", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
mMediaPlayer.setOnBufferingUpdateListener(this);
// 设置相关的监听器
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.setOnVideoSizeChangedListener(this);
mMediaPlayer.setOnErrorListener(this);
mMediaPlayer.setOnInfoListener(this);
//播放时屏幕保持唤醒
mMediaPlayer.setScreenOnWhilePlaying(true);
// 播放准备,使用异步方式,配合OnPreparedListener
mMediaPlayer.prepareAsync();
}
@Override
protected void onPause() {
super.onPause();
saveLastCurrentPosition();
if (mMediaPlayer != null) {
releaseVideoPlayer();
}
}
@Override
protected void onResume() {
super.onResume();
startPlayVideo();
}
private void releaseVideoPlayer(){
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
fLyt.removeView(surfaceView);
holder = null;
surfaceView = null;
}
private void saveLastCurrentPosition(){
SharedPreferences.Editor editor = getSharedPreferences("SurfaceViewPlayer", MODE_PRIVATE).edit();
editor.putInt("lastCurrentPosition", currentPosition);
editor.commit();
}
private int getLastCurrentPosition(){
SharedPreferences sharedPreferences = getSharedPreferences("SurfaceViewPlayer", MODE_PRIVATE);
return sharedPreferences.getInt("lastCurrentPosition", 0);
}
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
//b代表是否TrackingTouch,i代表当前进度;判断滑到中进度小于滑动前的视频播放最大进度
if (b&&i<maxCurrentPosition){
mMediaPlayer.seekTo(i);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if(!videoCompletion&&seekBar.getProgress()>maxCurrentPosition){
mMediaPlayer.seekTo(maxCurrentPosition);
Toast.makeText(SurfaceViewPlayerActivity.this,"只能快进到此,未学习过的内容不能快进",Toast.LENGTH_SHORT).show();
}
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.play_pause_iv:
if(mMediaPlayer!=null) {
if(videoCompletion){
videoCompletion=false;
//MediaPlay onCompletion后 再次start后 再调用pause无暂停效果,还是会继续播放;所以采用先释放播放器资源,一切重新开始。
releaseVideoPlayer();
startPlayVideo();
}else {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
mPlay_pause_iv.setImageResource(R.drawable.ac_video_play);
} else {
mMediaPlayer.start();
mPlay_pause_iv.setImageResource(R.drawable.ac_video_pause);
}
}
}
break;
}
}
private class MyPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
//空闲
case TelephonyManager.CALL_STATE_IDLE:
break;
//来电
case TelephonyManager.CALL_STATE_RINGING:
saveLastCurrentPosition();
break;
//挂断
case TelephonyManager.CALL_STATE_OFFHOOK:
break;
}
super.onCallStateChanged(state, incomingNumber);
}
}
@Override
protected void onDestroy() {
executorService.shutdown();
super.onDestroy();
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" android:orientation="vertical">
<include
android:id="@+id/inHeader"
layout="@layout/include_header"/>
<FrameLayout
android:id="@+id/fLyt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="200dp">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:padding="3dp">
<ImageView android:id="@+id/play_pause_iv" android:layout_width="40dp" android:layout_height="40dp" android:src="@drawable/ac_video_pause"/>
<TextView
android:layout_toRightOf="@+id/play_pause_iv"
android:id="@+id/position_mmss_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:layout_centerVertical="true"/>
<SeekBar
android:id="@+id/position_seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/duration_mmss_tv"
android:layout_toRightOf="@+id/position_mmss_tv"
android:layout_centerVertical="true"/>
<TextView
android:id="@+id/duration_mmss_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:layout_centerVertical="true"
android:layout_marginRight="3dp"
android:layout_toLeftOf="@+id/ivFullscreen"/>
<ImageView
android:id="@+id/ivFullscreen"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="@drawable/ac_video_play_fullscreen"/>
</RelativeLayout>
</LinearLayout>
效果图: