尽管网上有不少Android下视频播放的例子代码,好似很简单;但试着写一个时,遇到了不少的麻烦。
我的目标是正在播放时可以通过另一个Activity来选择另一个文件来播放,而在选择过程中原文件并不停播(可以想象的是由于存在Activity切换,原文件的播放会出现停顿,只要时间不长就行)。
发现的问题是在选文件时仍能听到原文件的声音(图像自然看不到了),但文件选择后返回到原Activity时,原文件仍在播放,但图像区空白。经查,发现SurfaceHolder.Callback.surfaceChanged()并未触发,或者其中的width/height参数为0。在返回到原Activity时主动刷新(update)SurfaceView也无济于事。
查了一下SurfaceView的源码,仍然没有发现让SurfaceView主动调用surfaceChanged()的方法(update,resize,setFormat,hide/reshow等都试过,未果。)
后来的解决方案是:在MediaPlayer的onPrepared(MediaPlayer
mp)中检查MediaPlayer的width/height,如果其值为0(因为SurfaceHolder.Callback.surfaceChanged()并未触发),就释放当前的MediaPlayer对象,重新一次播放(需先保留一些参数,如文件名,当前位置,当前屏幕方向,...)。这里的重新播放并不是就地执行,而是向Activity发送一个消息,在主线程里执行重放。其中的原因是在执行MediaPlayer.setDisplay(surfaceHolder)时会调用刷新surfaceHolder:
public
void MediaPlayer.setDisplay(SurfaceHolder sh) {
mSurfaceHolder = sh;
if (sh != null) {
mSurface = sh.getSurface();
} else {
mSurface = null;
}
//以下两行用于刷新:
_setVideoSurface();
updateSurfaceScreenOn();
}
部分代码如下,其中monitorThread只是获得MediaPlayer的当前位置,并相应更新SeekBar的位置。
boolean mSurfaceExists =
true; //调试用!
private SurfaceHolder.Callback surfaceHolderCallback = new
SurfaceHolder.Callback() {
@Override
public void
surfaceChanged(SurfaceHolder holder, int format, int width, int
height) {
// Do
nothing
Log.e(LOG_TAG,
"surfaceChanged: width="+width+", height="+height);
if(width!=0
&& height!=0) {
holder.setFixedSize(width,
height);
}
}
@Override
public void
surfaceCreated(SurfaceHolder holder) {
// Do
nothing
Log.d(LOG_TAG,
"surfaceCreated.");
mSurfaceExists
= true;
}
@Override
public void
surfaceDestroyed(SurfaceHolder holder) {
// Do
nothing
Log.d(LOG_TAG,
"surfaceDestroyed.");
mSurfaceExists
= false;
}
};
private MediaPlayer getPlayer() {
// we need
get a new Player in case the old one comes to END or ERROR
state(reset() won't help).
surfaceHolder =
surfaceView.getHolder();
surfaceHolder.addCallback(surfaceHolderCallback);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
MediaPlayer
player = new MediaPlayer();
player.reset(); // new() and
reset() lead to different mPlayer status.
player.setDisplay(surfaceHolder);
player.setOnErrorListener(new
MediaPlayer.OnErrorListener(){
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.e(LOG_TAG, "MplayerError: what="+what+", extra="+extra);
if(state!=VideoPlayerState.PREPARING) {
btnPlay.setEnabled(true);
}
return false;
}
});
player.setOnBufferingUpdateListener(new
MediaPlayer.OnBufferingUpdateListener(){
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
Log.e(LOG_TAG, "MplayerError: percent="+percent);
}
});
player.setOnPreparedListener(new
MediaPlayer.OnPreparedListener(){
@Override
public void onPrepared(MediaPlayer mp) {
// Only now we can invoke mPlayer.isPlaying(),
mPlayer.getPosition(), ...
int width = mp.getVideoWidth();
int height = mp.getVideoHeight();
Log.e(LOG_TAG, "onPrepared(): width="+width+",
height="+height);
if(width<=0 || height<=0) {
// restart playing:
saved_old_pos = CurrentPosition;
stopPlayer();
postMessage(MSG_VIDEO_PLAY);
return;
}
surfaceHolder.setFixedSize(width, height);
doRealPlay();
}
});
player.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
stop();
if(arg_once==true) { //如果只需播放一个文件,播完就退出。
finish();
}
}
});
return player;
}
@Override
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {
switch(requestCode){
case SharedConstants.REQ_FILECHOOSER:
toLoadNewFile = false;
File videoFile = null;
if(resultCode == RESULT_OK) {
String retPath =
data.getStringExtra(SharedConstants.CHOSENFILE_KEY);
videoFile = new
File(retPath);
}
if(videoFile!=null &&
!videoFile.getAbsolutePath().equals(currFile_Long)
&& videoFile.canRead()) {
Log.e(LOG_TAG, "For new file: pos="+CurrentPosition + ",
mSurfaceExists="+mSurfaceExists);
// For a new and readable file:
currFile_Long = videoFile.getAbsolutePath();
currFile_Short = videoFile.getName();
saved_old_pos = 0; // start from beginning!
stop(); // new Pos will become
0.
postMessage(MSG_VIDEO_PLAY);
} else {
Log.e(LOG_TAG, "For no-new file: pos="+CurrentPosition + ",
mSurfaceExists="+mSurfaceExists);
// Weird: SurfaceView will NOT show up!
// to restore SurfaceView:
if(mPlayer!=null &&
state==VideoPlayerState.PLAYING) {
saved_old_pos = CurrentPosition;
stop(); // new Pos will become
0.
postMessage(MSG_VIDEO_PLAY);
}
}
Log.e(LOG_TAG,
" onActivityResult() done.");
break;
default:
break;
}
}
private void stopPlayer() {
if(monitorThread!=null) {
monitorThread.interrupt();
boolean retry = true;
while(retry==true) {
try {
monitorThread.join();
retry = false;
} catch(InterruptedException e) {
// Do nothing.
}
}
monitorThread=null;
}
if(mPlayer!=null
&& state!=VideoPlayerState.STOPPED)
{
mPlayer.release();
mPlayer = null; // to avoid
IllegalStateException
state =
VideoPlayerState.STOPPED;
}
CurrentPosition=0;
}
private void stop() {
stopPlayer();
btnPlay.setEnabled(true);
btnStop.setEnabled(false);
btnPlay.setText(lblPlay);
showCurrentPosition();
showPlayingInfoViews(View.VISIBLE);
lbl_video_playing_file.setText(currFile_Short);
rotateScreen(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}