VideoView
继承SurfaceView
,并且实现了MediaPlayerControl
接口,相当于MediaPlayer+SurfaceView
的组合。关于MediaPlayer
可参考这里。
在布局文件里添加VideoView
<VideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
在Activity
里面播放视频
var mVideoView = findViewById(R.id.video_view)
mVideoView.setVideoURI(Uri.parse(uri))
// 设置控制器
mVideoView.setMediaController(MediaController(this))
mVideoView.start()
主要方法
// 设置视频源
void setVideoPath(String path)
void setVideoURI(Uri uri)
void setVideoURI(Uri uri, Map<String, String> headers)
// 恢复,从头播放
void resume()
// 播放
void start()
// 暂停
void pause()
// 释放资源
void suspend()
// 停止播放
void stopPlayback()
// 视频时长
int getDuration()
// 当前位置
int getCurrentPosition()
// 跳转到指定时间
void seekTo(int msec)
// 是否正在播放
boolean isPlaying()
// 设置控制器
void setMediaController(MediaController controller)
VideoView
里面有mCurrentState
和mTargetState
两个状态,当前状态和目标状态,初始值都是STATE_IDLE
,播放的时候都与这两个状态有关。
VideoView
里面的setVideoXXX
和resume
方法都直接调用openVideo
,在openVideo
方法里面,调用MediaPlayer
播放视频。
mPreparedListener
在MediaPlayer
准备好播放资源后,如果mSeekWhenPrepared
被设置,跳转到指定时间。而如果mTargetState
被设置为STATE_PLAYING
,直接播放。
public void setVideoPath(String path) {
setVideoURI(Uri.parse(path));
}
public void setVideoURI(Uri uri) {
setVideoURI(uri, null);
}
public void setVideoURI(Uri uri, Map<String, String> headers) {
mUri = uri;
mHeaders = headers;
mSeekWhenPrepared = 0;
openVideo();
requestLayout();
invalidate();
}
public void resume() {
openVideo();
}
private void openVideo() {
if (mUri == null || mSurfaceHolder == null) {
// not ready for playback just yet, will try again later
return;
}
// 清空mCurrentState,但保留mTargetState
release(false);
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnPreparedListener(mPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
mMediaPlayer.setOnCompletionListener(mCompletionListener);
mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
mMediaPlayer.setDisplay(mSurfaceHolder);
mMediaPlayer.prepareAsync();
mCurrentState = STATE_PREPARING;
}
private void release(boolean cleartargetstate) {
if (mMediaPlayer != null) {
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
mPendingSubtitleTracks.clear();
mCurrentState = STATE_IDLE;
if (cleartargetstate) {
mTargetState = STATE_IDLE;
}
if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
mAudioManager.abandonAudioFocus(null);
}
}
}
MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
public void onPrepared(MediaPlayer mp) {
mCurrentState = STATE_PREPARED;
if (mOnPreparedListener != null) {
mOnPreparedListener.onPrepared(mMediaPlayer);
}
int seekToPosition = mSeekWhenPrepared;
if (seekToPosition != 0) {
seekTo(seekToPosition);
}
if (mVideoWidth != 0 && mVideoHeight != 0) {
getHolder().setFixedSize(mVideoWidth, mVideoHeight);
if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
if (mTargetState == STATE_PLAYING) {
start();
} else if (!isPlaying() &&
(seekToPosition != 0 || getCurrentPosition() > 0)) {
if (mMediaController != null) {
mMediaController.show(0);
}
}
}
} else {
if (mTargetState == STATE_PLAYING) {
start();
}
}
}
};
VideoView
里面start
、pause
方法都是直接调用MediaPlayer
的对应方法。suspend
和stopPlayback
都是停止播放并释放资源,但stopPlayback
会修改mTargetState
。
@Override
public void start() {
if (isInPlaybackState()) {
mMediaPlayer.start();
mCurrentState = STATE_PLAYING;
}
mTargetState = STATE_PLAYING;
}
@Override
public void pause() {
if (isInPlaybackState()) {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
mCurrentState = STATE_PAUSED;
}
}
mTargetState = STATE_PAUSED;
}
public void suspend() {
release(false);
}
public void stopPlayback() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
mCurrentState = STATE_IDLE;
mTargetState = STATE_IDLE;
mAudioManager.abandonAudioFocus(null);
}
}
getDuration
、getCurrentPosition
和seekTo
也都是调用MediaPlayer
的对应方法,只是包装了一层状态的校验,比直接调用更合理。
public int getDuration() {
if (isInPlaybackState()) {
return mMediaPlayer.getDuration();
}
return -1;
}
@Override
public int getCurrentPosition() {
if (isInPlaybackState()) {
return mMediaPlayer.getCurrentPosition();
}
return 0;
}
@Override
public void seekTo(int msec) {
if (isInPlaybackState()) {
mMediaPlayer.seekTo(msec);
mSeekWhenPrepared = 0;
} else {
mSeekWhenPrepared = msec;
}
}
MediaController
控制器是VideoView
默认的控制器。
public void setMediaController(MediaController controller) {
if (mMediaController != null) {
mMediaController.hide();
}
mMediaController = controller;
attachMediaController();
}
private void attachMediaController() {
if (mMediaPlayer != null && mMediaController != null) {
mMediaController.setMediaPlayer(this);
View anchorView = this.getParent() instanceof View ?
(View)this.getParent() : this;
mMediaController.setAnchorView(anchorView);
mMediaController.setEnabled(isInPlaybackState());
}
}
MediaController
创建界面,一般包含3个按钮,从左往右分别是倒退、播放/暂停和快进。底部从左到右是当前时间、进度条和总播放时间。拖动进度条可以跳转到指定时间。
public void setMediaPlayer(MediaPlayerControl player) {
mPlayer = player;
updatePausePlay();
}
public void setAnchorView(View view) {
if (mAnchor != null) {
mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener);
}
mAnchor = view;
if (mAnchor != null) {
mAnchor.addOnLayoutChangeListener(mLayoutChangeListener);
}
FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
removeAllViews();
View v = makeControllerView();
addView(v, frameParams);
}
调用setPrevNextListeners
方法后,根据next
和prev
的值,在两边添加按钮。
public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev)