先上图,有图有真相。无图就是耍流氓
界面比较丑,用来测试的 ,这个就不要见怪了哈.
维护以前的项目 ,客户有一个强烈的需求,就是视频播放的无缝切换,视频资源有4K,这就直接限制了使用第三方框架的可能性,
这边测试过了,无论是Vitami还是IJKVideoPlayer 在播放4k资源的时候,都会出现卡顿,闪退的情况 ,测试下来,只有原生的API不会出现卡顿的情况.
那就自己手动封装一个,
网上看了一会,有两种方案。
1:使用多个MediaPlayer方案,全部初始化,前面播放完毕之后 ,后边直接播放 ,就减少了mediaPlayer初始化和销毁的时间
2:TextureView+imageView+mediaplayer 播放视频,在播放完毕之后 ,获取视频最后一针,使用imageView去显示最后一贞图片,等待mediaplayer生命周期完成,下一次播放 ,隐藏自己
这里讲的是第2中,第一种有一个缺陷,那就是列表循环播放 ,播放的末尾的时候 ,还是会有有一个1~2黑屏的过程,这个是无法接受的,这里贴一下代码,有需要的伙伴可以直接copy过去测试一下
package com.ryx.twoscreen.video;
import android.app.Activity;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;
import com.ryx.twoscreen.R;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
public class VideoActivity extends Activity implements SurfaceHolder.Callback {
private MediaPlayer firstPlayer, //负责播放进入视频播放界面后的第一段视频
nextMediaPlayer, //负责一段视频播放结束后,播放下一段视频
cachePlayer, //负责setNextMediaPlayer的player缓存对象
currentPlayer; //负责当前播放视频段落的player对象
private SurfaceView surface;
private SurfaceHolder surfaceHolder;
private ArrayList VideoListQueue = new ArrayList();
private HashMap playersCache = new HashMap();
private int currentVideoIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
protected void onDestroy() {
super.onDestroy();
if (firstPlayer != null) {
if (firstPlayer.isPlaying()) {
firstPlayer.stop();
}
firstPlayer.release();
}
if (nextMediaPlayer != null) {
if (nextMediaPlayer.isPlaying()) {
nextMediaPlayer.stop();
}
nextMediaPlayer.release();
}
if (currentPlayer != null) {
if (currentPlayer.isPlaying()) {
currentPlayer.stop();
}
currentPlayer.release();
}
currentPlayer = null;
}
private void initView() {
surface = (SurfaceView) findViewById(R.id.surfaceView1);
surfaceHolder = surface.getHolder();// SurfaceHolder是SurfaceView的控制接口
surfaceHolder.addCallback(this); // 因为这个类实现了SurfaceHolder.Callback接口,所以回调参数直接this
}
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
// TODO 自动生成的方法存根
}
@Override
public void surfaceCreated(SurfaceHolder arg0) {
//surfaceView创建完毕后,首先获取该直播间所有视频分段的url
getVideoUrls();
//然后初始化播放手段视频的player对象
initFirstPlayer();
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
}
private void initFirstPlayer() {
firstPlayer = new MediaPlayer();
firstPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
firstPlayer.setDisplay(surfaceHolder);
firstPlayer
.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
onVideoPlayCompleted(mp);
}
});
cachePlayer = firstPlayer;
initNexttPlayer();
startPlayFirstVideo();
}
private void startPlayFirstVideo() {
try {
firstPlayer.setDataSource(VideoListQueue.get(currentVideoIndex));
firstPlayer.prepare();
firstPlayer.start();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
private void initNexttPlayer() {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < VideoListQueue.size(); i++) {
nextMediaPlayer = new MediaPlayer();
nextMediaPlayer
.setAudioStreamType(AudioManager.STREAM_MUSIC);
nextMediaPlayer
.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
onVideoPlayCompleted(mp);
}
});
try {
nextMediaPlayer.setDataSource(VideoListQueue.get(i));
nextMediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
cachePlayer.setNextMediaPlayer(nextMediaPlayer);
cachePlayer = nextMediaPlayer;
playersCache.put(String.valueOf(i), nextMediaPlayer);
}
}
}).start();
}
private void onVideoPlayCompleted(MediaPlayer mp) {
Log.e("cdl", "============" + currentVideoIndex + "/" + VideoListQueue.size());
mp.setDisplay(null);
currentPlayer = playersCache.get(String.valueOf(++currentVideoIndex));
if (currentPlayer != null) {
currentPlayer.setDisplay(surfaceHolder);
} else {
Toast.makeText(VideoActivity.this, "视频播放完毕..", Toast.LENGTH_SHORT)
.show();
}
}
private void getVideoUrls() {
String url = "/sdcard/aaa.mp4";
VideoListQueue.add(url);
VideoListQueue.add(url);
VideoListQueue.add(url);
VideoListQueue.add(url);
VideoListQueue.add(url);
VideoListQueue.add(url);
VideoListQueue.add(url);
VideoListQueue.add(url);
VideoListQueue.add(url);
VideoListQueue.add(url);
}
}
接下来就是第二种方法,使用图片显示1000ms欺骗用户的眼睛,这个用户是不会发现的
播放状态监听Listener用来界面回调
VideoPlayListener
package com.etv.view.video;
public interface VideoPlayListener {
void playCompletion();
void playError(String errorDesc);
}
下面就是封装的View
package com.etv.view.video;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import com.etv.R;
import com.etv.util.MyLog;
import com.etv.util.SimpleDateUtil;
import com.etv.util.system.VoiceManager;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class VideoViewBitmap extends RelativeLayout implements
TextureView.SurfaceTextureListener, MediaPlayer.OnCompletionListener,
OnPreparedListener, MediaPlayer.OnErrorListener, SeekBar.OnSeekBarChangeListener {
View view;
private MediaPlayer mMediaPlayer;
private Surface surface;
private TextureView textureView;
private ImageView videoImage;
private SeekBar seekBar_progress;
VideoPlayListener listener;
List playList = new ArrayList();
TextView tv_current_play, tv_total_play;
int currentPlayIndex = 0;
Context context;
public void setPlayList(List playUrlList) {
this.playList = playUrlList;
if (playUrlList.size() < 1) {
return;
}
final String currentPlayUrl = playList.get(currentPlayIndex);
handler.postDelayed(new Runnable() {
@Override
public void run() {
startToPlay(currentPlayUrl);
}
}, 1000);
}
public void setVideoPlayListener(VideoPlayListener listener) {
this.listener = listener;
}
public VideoViewBitmap(Context context) {
this(context, null);
}
public VideoViewBitmap(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VideoViewBitmap(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.e("cdl", "==========init");
this.context = context;
view = View.inflate(context, R.layout.view_video, null);
initView(view);
addView(view);
}
Button btn_pre, btn_play_pause, btn_next;
LinearLayout lin_control;
SeekBar seekBar_voice;
private void initView(View view) {
int voiceNum = VoiceManager.getCurrentVoiceNum(context);
MyLog.media("=====当前音量====" + voiceNum);
seekBar_voice = (SeekBar) view.findViewById(R.id.seekBar_voice);
seekBar_voice.setProgress(voiceNum * 100 / 15);
lin_control = (LinearLayout) view.findViewById(R.id.lin_control);
btn_pre = (Button) view.findViewById(R.id.btn_pre);
btn_play_pause = (Button) view.findViewById(R.id.btn_play_pause);
btn_next = (Button) view.findViewById(R.id.btn_next);
btn_pre.setOnClickListener(clickListener);
btn_play_pause.setOnClickListener(clickListener);
btn_next.setOnClickListener(clickListener);
tv_current_play = (TextView) view.findViewById(R.id.tv_current_play);
tv_total_play = (TextView) view.findViewById(R.id.tv_total_play);
textureView = (TextureView) view.findViewById(R.id.textureview);
textureView.setSurfaceTextureListener(this);//设置监听函数 重写4个方法
videoImage = (ImageView) view.findViewById(R.id.video_image);
seekBar_progress = (SeekBar) view.findViewById(R.id.seekBar_progress);
seekBar_progress.setOnSeekBarChangeListener(this);
textureView.setOnClickListener(clickListener);
seekBar_voice.setOnSeekBarChangeListener(seekBarListener);
}
SeekBar.OnSeekBarChangeListener seekBarListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int progress = seekBar.getProgress();
MyLog.media("=====设置的音量只==" + progress);
VoiceManager.repairDevVoice(context, progress + "");
}
};
OnClickListener clickListener = new OnClickListener() {
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.textureview:
handler.removeMessages(SHOW_CONTROL_VIEW);
if (lin_control.getVisibility() == VISIBLE) {
lin_control.setVisibility(GONE);
} else {
lin_control.setVisibility(VISIBLE);
handler.sendEmptyMessageDelayed(SHOW_CONTROL_VIEW, 8000);
}
break;
case R.id.btn_pre:
playPreVideo();
break;
case R.id.btn_play_pause:
playOrPauseVideo();
break;
case R.id.btn_next:
playNextVideo();
break;
}
}
};
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
surface = new Surface(surfaceTexture);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
surface = null;
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
}
clearMemory();
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
public void startToPlay(String playUrl) {
try {
File file = new File(playUrl);
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
}
mMediaPlayer.reset();
mMediaPlayer.setDataSource(file.getAbsolutePath());
mMediaPlayer.setSurface(surface);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepare();
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnErrorListener(this);
} catch (Exception e) {
Log.e("cdl", "==========播放异常====" + e.toString());
e.printStackTrace();
}
}
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
Log.e("cdl", "==========开始播放==");
btn_play_pause.setBackgroundResource(R.mipmap.video_pause);
mediaPlayer.start();
startTimer();
}
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
if (listener != null) {
listener.playCompletion();
}
videoImage.setVisibility(View.VISIBLE);
Bitmap currentFrameBitmap = textureView.getBitmap();
videoImage.setImageBitmap(currentFrameBitmap);
handler.sendEmptyMessageDelayed(SHOW_IMAGE_YAG, 500);
playNextVideo();
}
/**
* 播放上一个视频
*/
private void playPreVideo() {
currentPlayIndex--;
if (currentPlayIndex < 0) {
currentPlayIndex = (playList.size() - 1);
}
String playUrl = playList.get(currentPlayIndex);
try {
File file = new File(playUrl);
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
}
mMediaPlayer.reset();
mMediaPlayer.setDataSource(file.getAbsolutePath());
mMediaPlayer.setSurface(surface);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepare();
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnErrorListener(this);
} catch (Exception e) {
Log.e("cdl", "==========播放异常====" + e.toString());
e.printStackTrace();
}
}
/***
* 播放或者暂停视频
*/
private void playOrPauseVideo() {
if (mMediaPlayer == null) {
return;
}
if (mMediaPlayer.isPlaying()) {
btn_play_pause.setBackgroundResource(R.mipmap.video_play);
mMediaPlayer.pause();
} else {
btn_play_pause.setBackgroundResource(R.mipmap.video_pause);
mMediaPlayer.start();
}
}
/***
* 播放下一个
*/
private void playNextVideo() {
currentPlayIndex++;
if (currentPlayIndex > (playList.size() - 1)) {
currentPlayIndex = 0;
}
String playUrl = playList.get(currentPlayIndex);
try {
File file = new File(playUrl);
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
}
mMediaPlayer.reset();
mMediaPlayer.setDataSource(file.getAbsolutePath());
mMediaPlayer.setSurface(surface);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepare();
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnErrorListener(this);
} catch (Exception e) {
Log.e("cdl", "==========播放异常====" + e.toString());
e.printStackTrace();
}
}
private static final int SHOW_IMAGE_YAG = 2345;
private static final int SHOW_PROGRESS_TAG = SHOW_IMAGE_YAG + 1;
private static final int SHOW_CONTROL_VIEW = SHOW_PROGRESS_TAG + 1;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SHOW_CONTROL_VIEW:
lin_control.setVisibility(GONE);
break;
case SHOW_IMAGE_YAG:
videoImage.setVisibility(View.GONE);
break;
case SHOW_PROGRESS_TAG:
if (mMediaPlayer == null) {
return;
}
int currentDuration = mMediaPlayer.getCurrentPosition();
int maxDuration = mMediaPlayer.getDuration();
int progress = currentDuration * 100 / maxDuration;
seekBar_progress.setProgress(progress);
String playCurrent = SimpleDateUtil.timeParseMedia(currentDuration);
String totalDuration = SimpleDateUtil.timeParseMedia(maxDuration);
tv_current_play.setText(playCurrent);
tv_total_play.setText(totalDuration);
break;
}
}
};
//==================================================================
Timer timer;
MyTask task;
public void startTimer() {
cacelTimer();
timer = new Timer(true);
task = new MyTask();
timer.schedule(task, 0, 500);
}
public void cacelTimer() {
if (timer != null) {
timer.cancel();
}
if (task != null) {
task.cancel();
}
}
@Override
public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
if (listener != null) {
listener.playError("播放异常");
}
return false;
}
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
Log.e("seekBar", "onProgressChanged==当前播放进度====" + i);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
Log.e("seekBar", "onStartTrackingTouch=========");
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int maxDuration = mMediaPlayer.getDuration();
int progress = seekBar.getProgress();
int curretDuration = progress * maxDuration / 100;
mMediaPlayer.seekTo(curretDuration);
Log.e("seekBar", "onStopTrackingTouch=========");
}
public void setOnPreparedListener(OnPreparedListener onPreparedListener) {
}
public void clearMemory() {
cacelTimer();
if (handler != null) {
handler.removeMessages(SHOW_CONTROL_VIEW);
handler.removeMessages(SHOW_IMAGE_YAG);
handler.removeMessages(SHOW_PROGRESS_TAG);
}
}
class MyTask extends TimerTask {
@Override
public void run() {
handler.sendEmptyMessage(SHOW_PROGRESS_TAG);
}
}
}
这个方法用来解析视频播放时间长,duration转00:00
public static String timeParseMedia(long duration) {
String time = "";
long minute = duration / 60000;
long seconds = duration % 60000;
long second = Math.round((float) seconds / 1000);
if (minute < 10) {
time += "0";
}
time += minute + ":";
if (second < 10) {
time += "0";
}
time += second;
return time;
}
xml布局文件
view_video
音量控制工具类
package com.etv.util.system; import android.app.Service; import android.content.Context; import android.media.AudioManager; import android.util.Log; import com.etv.util.MyLog; import com.etv.view.MyToastView; /** * 音量控制管理器 */ public class VoiceManager { private static final String TAG = "VoiceManager"; public static int getCurrentVoiceNum(Context context) { AudioManager mAudioManager = (AudioManager) context.getSystemService(Service.AUDIO_SERVICE); int mediacurrent = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); return mediacurrent; } /*** * 处理媒体音量 * @param b * true add * false request */ public static void dealMediaVoice(Context context, boolean b) { try { AudioManager mAudioManager = (AudioManager) context.getSystemService(Service.AUDIO_SERVICE); int mediamax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); int mediacurrent = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); Log.i(TAG, "===处理前媒体音量====" + mediacurrent + " /" + mediamax); if (b) { if (mediacurrent == mediamax) { return; } mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mediacurrent + 1, 0); int currentVoice = mediacurrent + 1; MyToastView.getInstance().Toast(context, "增加音量: " + currentVoice * 100 / 15 + "% "); } else { if (mediacurrent < 1) { return; } mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mediacurrent - 1, 0); int currentVoice = mediacurrent - 1; MyToastView.getInstance().Toast(context, "降低音量: " + currentVoice * 100 / 15 + "% "); } } catch (Exception e) { } } /** * 按照比例来修改音量 * * @param context * @param progress */ public static void repairDevVoice(Context context, String progress) { int progressNum = Integer.parseInt(progress); if (progressNum == 0) { return; } int voiceNum = (progressNum * 15) / 100; MyLog.media("====设置的音量===" + voiceNum); AudioManager mAudioManager = (AudioManager) context.getSystemService(Service.AUDIO_SERVICE); mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, voiceNum, 0); //设置媒体音量为 0 } /*** * 静音 */ public static void addMediaMaxVoice(Context context) { try { AudioManager mAudioManager = (AudioManager) context.getSystemService(Service.AUDIO_SERVICE); mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 15); //设置媒体音量为 0 } catch (Exception e) { } } /*** * 静音 */ public static void stopMediaVoice(Context context) { try { AudioManager mAudioManager = (AudioManager) context.getSystemService(Service.AUDIO_SERVICE); mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0); //设置媒体音量为 0 } catch (Exception e) { } } }
=========================================================
主界面调用方法,传递一个视频路径集合
目前这个只支持本地视频播放,
List videoLists = null;
videoView = (VideoViewBitmap) view.findViewById(R.id.video_view);
videoView.setPlayList(videoLists);