展会信息港展会大全

Android开发通过重构VideoView类来实现更加个性化的播放器
来源:互联网   发布日期:2016-01-19 12:02:32   浏览:2699次  

导读:之前介绍了SurfaceView类的应用实例,本文来讲讲Android游戏开发中的VideoView类实例。本节使用系统的示例类VideoView继续SurfaceView类相关内容的讲解,以让大家能更深入理解Android系统中图形绘制基础类的 实 ...

之前介绍了SurfaceView类的应用实例,本文来讲讲Android游戏开发中的VideoView类实例。

本节使用系统的示例类VideoView继续SurfaceView类相关内容的讲解,以让大家能更深入理解Android系统中图形绘制基础类的 实现原理。也许你会发现无法改变VideoView类的控制方面,我们可以通过重构VideoView类来实现更加个性化的播放器。

下面是VideoView类的相关代码。

public class VideoView extends SurfaceView implements MediaPlayerControl {

private String TAG = "VideoView";

// settable by the client

private UrimUri;

private intmDuration;

// all possible internal states

private static final int STATE_ERROR= -1;

private static final int STATE_IDLE= 0;

private static final int STATE_PREPARING= 1;

private static final int STATE_PREPARED= 2;

private static final int STATE_PLAYING= 3;

private static final int STATE_PAUSED= 4;

private static final int STATE_PLAYBACK_COMPLETED = 5;

// mCurrentState is a VideoView object's current state.

// mTargetState is the state that a method caller intends to reach.

// For instance, regardless the VideoView object's current state,

// calling pause() intends to bring the object to a target state

// of STATE_PAUSED.

private int mCurrentState = STATE_IDLE;

private int mTargetState= STATE_IDLE;

// All the stuff we need for playing and showing a video

private SurfaceHolder mSurfaceHolder = null;

private MediaPlayer mMediaPlayer = null;

private intmVideoWidth;

private intmVideoHeight;

private intmSurfaceWidth;

private intmSurfaceHeight;

private MediaController mMediaController;

private OnCompletionListener mOnCompletionListener;

private MediaPlayer.OnPreparedListener mOnPreparedListener;

private intmCurrentBufferPercentage;

private OnErrorListener mOnErrorListener;

private intmSeekWhenPrepared;// recording the seek position while preparing

private booleanmCanPause;

private booleanmCanSeekBack;

private booleanmCanSeekForward;

public VideoView(Context context) {

super(context);

initVideoView();

}

public VideoView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

initVideoView();

}

public VideoView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

initVideoView();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//Log.i("@@@@", "onMeasure");

int width = getDefaultSize(mVideoWidth, widthMeasureSpec);

int height = getDefaultSize(mVideoHeight, heightMeasureSpec);

if (mVideoWidth > 0 && mVideoHeight > 0) {

if ( mVideoWidth * height> width * mVideoHeight ) {

//Log.i("@@@", "image too tall, correcting");

height = width * mVideoHeight / mVideoWidth;

} else if ( mVideoWidth * height< width * mVideoHeight ) {

//Log.i("@@@", "image too wide, correcting");

width = height * mVideoWidth / mVideoHeight;

} else {

//Log.i("@@@", "aspect ratio is correct: " +

//width+"/"+height+"="+

//mVideoWidth+"/"+mVideoHeight);

}

}

//Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);

setMeasuredDimension(width, height);

}

public int resolveAdjustedSize(int desiredSize, int measureSpec) {

int result = desiredSize;

int specMode = MeasureSpec.getMode(measureSpec);

int specSize =MeasureSpec.getSize(measureSpec);

switch (specMode) {

case MeasureSpec.UNSPECIFIED:

/* Parent says we can be as big as we want. Just don't be larger

* than max size imposed on ourselves.

*/

result = desiredSize;

break;

case MeasureSpec.AT_MOST:

/* Parent says we can be as big as we want, up to specSize.

* Don't be larger than specSize, and don't be larger than

* the max size imposed on ourselves.

*/

result = Math.min(desiredSize, specSize);

break;

case MeasureSpec.EXACTLY:

// No choice. Do what we are told.

result = specSize;

break;

}

return result;

}

private void initVideoView() {

mVideoWidth = 0;

mVideoHeight = 0;

getHolder().addCallback(mSHCallback);

getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

setFocusable(true);

setFocusableInTouchMode(true);

requestFocus();

mCurrentState = STATE_IDLE;

mTargetState= STATE_IDLE;

}

public void setVideoPath(String path) {

setVideoURI(Uri.parse(path));

}

public void setVideoURI(Uri uri) {

mUri = uri;

mSeekWhenPrepared = 0;

openVideo();

requestLayout();

invalidate();

}

public void stopPlayback() {

if (mMediaPlayer != null) {

mMediaPlayer.stop();

mMediaPlayer.release();

mMediaPlayer = null;

mCurrentState = STATE_IDLE;

mTargetState= STATE_IDLE;

}

}

private void openVideo() {

if (mUri == null || mSurfaceHolder == null) {

// not ready for playback just yet, will try again later

return;

}

// Tell the music playback service to pause

// TODO: these constants need to be published somewhere in the framework.

Intent i = new Intent("com.android.music.musicservicecommand");

i.putExtra("command", "pause");

mContext.sendBroadcast(i);

// we shouldn't clear the target state, because somebody might have

// called start() previously

release(false);

try {

mMediaPlayer = new MediaPlayer();

mMediaPlayer.setOnPreparedListener(mPreparedListener);

mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);

mDuration = -1;

mMediaPlayer.setOnCompletionListener(mCompletionListener);

mMediaPlayer.setOnErrorListener(mErrorListener);

mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);

mCurrentBufferPercentage = 0;

mMediaPlayer.setDataSource(mContext, mUri);

mMediaPlayer.setDisplay(mSurfaceHolder);mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

mMediaPlayer.setScreenOnWhilePlaying(true);

mMediaPlayer.prepareAsync();

// we don't set the target state here either, but preserve the

// target state that was there before.

mCurrentState = STATE_PREPARING;

attachMediaController();

} catch (IOException ex) {

Log.w(TAG, "Unable to open content: " + mUri, ex);

mCurrentState = STATE_ERROR;

mTargetState = STATE_ERROR;

mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);

return;

} catch (IllegalArgumentException ex) {

Log.w(TAG, "Unable to open content: " + mUri, ex);

mCurrentState = STATE_ERROR;

mTargetState = STATE_ERROR;

mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);

return;

}

}

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());

}

}

MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =

new MediaPlayer.OnVideoSizeChangedListener() {

public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {

mVideoWidth = mp.getVideoWidth();

mVideoHeight = mp.getVideoHeight();

if (mVideoWidth != 0 && mVideoHeight != 0) {

getHolder().setFixedSize(mVideoWidth, mVideoHeight);

}

}

};

MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {

public void onPrepared(MediaPlayer mp) {

mCurrentState = STATE_PREPARED;

// Get the capabilities of the player for this stream

Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,

MediaPlayer.BYPASS_METADATA_FILTER);

if (data != null) {

mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)

|| data.getBoolean(Metadata.PAUSE_AVAILABLE);

mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)

|| data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);

mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)

|| data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);

} else {

mCanPause = mCanSeekForward = mCanSeekForward = true;

}

if (mOnPreparedListener != null) {

mOnPreparedListener.onPrepared(mMediaPlayer);

}

if (mMediaController != null) {

mMediaController.setEnabled(true);

}

mVideoWidth = mp.getVideoWidth();

mVideoHeight = mp.getVideoHeight();

int seekToPosition = mSeekWhenPrepared;// mSeekWhenPrepared may be changed after seekTo() call

if (seekToPosition != 0) {

seekTo(seekToPosition);

}

if (mVideoWidth != 0 && mVideoHeight != 0) {

//Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);

getHolder().setFixedSize(mVideoWidth, mVideoHeight);

if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {

// We didn't actually change the size (it was already at the size

// we need), so we won't get a "surface changed" callback, so

// start the video here instead of in the callback.

if (mTargetState == STATE_PLAYING) {

start();

if (mMediaController != null) {

mMediaController.show();

}

} else if (!isPlaying() &&

(seekToPosition != 0 || getCurrentPosition() > 0)) {

if (mMediaController != null) {

// Show the media controls when we're paused into a video and make 'em stick.

mMediaController.show(0);

}

}

}

} else {

// We don't know the video size yet, but should start anyway.

// The video size might be reported to us later.

if (mTargetState == STATE_PLAYING) {

start();

}

}

}

};

private MediaPlayer.OnCompletionListener mCompletionListener =

new MediaPlayer.OnCompletionListener() {

public void onCompletion(MediaPlayer mp) {

mCurrentState = STATE_PLAYBACK_COMPLETED;

mTargetState = STATE_PLAYBACK_COMPLETED;

if (mMediaController != null) {

mMediaController.hide();

}

if (mOnCompletionListener != null) {mOnCompletionListener.onCompletion(mMediaPlayer);

}

}

};

private MediaPlayer.OnErrorListener mErrorListener =

new MediaPlayer.OnErrorListener() {

public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {

Log.d(TAG, "Error: " + framework_err + "," + impl_err);

mCurrentState = STATE_ERROR;

mTargetState = STATE_ERROR;

if (mMediaController != null) {

mMediaController.hide();

}

/* If an error handler has been supplied, use it and finish. */

if (mOnErrorListener != null) {

if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {

return true;

}

}

/* Otherwise, pop up an error dialog so the user knows that

* something bad has happened. Only try and pop up the dialog

* if we're attached to a window. When we're going away and no

* longer have a window, don't bother showing the user an error.

*/

if (getWindowToken() != null) {

Resources r = mContext.getResources();

int messageId;

if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {

messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;

} else {

messageId = com.android.internal.R.string.VideoView_error_text_unknown;

}

new AlertDialog.Builder(mContext)

.setTitle(com.android.internal.R.string.VideoView_error_title)

.setMessage(messageId)

.setPositiveButton(com.android.internal.R.string.VideoView_error_button,

new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int whichButton) {

/* If we get here, there is no onError listener, so

* at least inform them that the video is over.

*/

if (mOnCompletionListener != null) {

mOnCompletionListener.onCompletion(mMediaPlayer);

}

}

})

.setCancelable(false)

.show();

}

return true;

}

};

private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =

new MediaPlayer.OnBufferingUpdateListener() {

public void onBufferingUpdate(MediaPlayer mp, int percent) {

mCurrentBufferPercentage = percent;

}

};

/**

* Register a callback to be invoked when the media file

* is loaded and ready to go.

*

* @param l The callback that will be run

*/

public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)

{

mOnPreparedListener = l;

}

/**

* Register a callback to be invoked when the end of a media file

* has been reached during playback.

*

* @param l The callback that will be run

*/

public void setOnCompletionListener(OnCompletionListener l)

{

mOnCompletionListener = l;

}

/**

* Register a callback to be invoked when an error occurs

* during playback or setup.If no listener is specified,

* or if the listener returned false, VideoView will inform

* the user of any errors.

*

* @param l The callback that will be run

*/

public void setOnErrorListener(OnErrorListener l)

{

mOnErrorListener = l;

}

SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()

{

public void surfaceChanged(SurfaceHolder holder, int format,

int w, int h)

{

mSurfaceWidth = w;

mSurfaceHeight = h;

boolean isValidState =(mTargetState == STATE_PLAYING);

boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);

if (mMediaPlayer != null && isValidState && hasValidSize) {

if (mSeekWhenPrepared != 0) {

seekTo(mSeekWhenPrepared);

}

start();

if (mMediaController != null) {

mMediaController.show();

}

}

}

public void surfaceCreated(SurfaceHolder holder)

{

mSurfaceHolder = holder;

openVideo();

}

public void surfaceDestroyed(SurfaceHolder holder)

{

// after we return from this we can't use the surface any more

mSurfaceHolder = null;

if (mMediaController != null) mMediaController.hide();

release(true);

}

};

/*

* release the media player in any state

*/

private void release(boolean cleartargetstate) {

if (mMediaPlayer != null) {

mMediaPlayer.reset();

mMediaPlayer.release();

mMediaPlayer = null;

mCurrentState = STATE_IDLE;

if (cleartargetstate) {

mTargetState= STATE_IDLE;

}

}

}

@Override

public boolean onTouchEvent(MotionEvent ev) {

if (isInPlaybackState() && mMediaController != null) {

toggleMediaControlsVisiblity();

}

return false;

}

@Override

public boolean onTrackballEvent(MotionEvent ev) {

if (isInPlaybackState() && mMediaController != null) {

toggleMediaControlsVisiblity();

}

return false;

}

@Override

public boolean onKeyDown(int keyCode, KeyEvent event)

{

boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&

keyCode != KeyEvent.KEYCODE_VOLUME_UP &&

keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&

keyCode != KeyEvent.KEYCODE_MENU &&

keyCode != KeyEvent.KEYCODE_CALL &&

keyCode != KeyEvent.KEYCODE_ENDCALL;

if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {

if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||

keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {

if (mMediaPlayer.isPlaying()) {

pause();

mMediaController.show();

} else {

start();

mMediaController.hide();

}

return true;

} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP

&& mMediaPlayer.isPlaying()) {

pause();

mMediaController.show();

} else {

toggleMediaControlsVisiblity();

}

}

return super.onKeyDown(keyCode, event);

}

private void toggleMediaControlsVisiblity() {

if (mMediaController.isShowing()) {

mMediaController.hide();

} else {

mMediaController.show();

}

}

public void start() {

if (isInPlaybackState()) {

mMediaPlayer.start();

mCurrentState = STATE_PLAYING;

}

mTargetState = STATE_PLAYING;

}

public void pause() {

if (isInPlaybackState()) {

if (mMediaPlayer.isPlaying()) {

mMediaPlayer.pause();

mCurrentState = STATE_PAUSED;

}

}

mTargetState = STATE_PAUSED;

}

// cache duration as mDuration for faster access

public int getDuration() {

if (isInPlaybackState()) {

if (mDuration > 0) {

return mDuration;

}

mDuration = mMediaPlayer.getDuration();

return mDuration;

}

mDuration = -1;

return mDuration;

}

public int getCurrentPosition() {

if (isInPlaybackState()) {

return mMediaPlayer.getCurrentPosition();

}

return 0;

}

public void seekTo(int msec) {

if (isInPlaybackState()) {

mMediaPlayer.seekTo(msec);

mSeekWhenPrepared = 0;

} else {

mSeekWhenPrepared = msec;

}

}

public boolean isPlaying() {

return isInPlaybackState() && mMediaPlayer.isPlaying();

}

public int getBufferPercentage() {

if (mMediaPlayer != null) {

return mCurrentBufferPercentage;

}

return 0;

}

private boolean isInPlaybackState() {

return (mMediaPlayer != null &&

mCurrentState != STATE_ERROR &&

mCurrentState != STATE_IDLE &&

mCurrentState != STATE_PREPARING);

}

public boolean canPause() {

return mCanPause;

}

public boolean canSeekBackward() {

return mCanSeekBack;

}

public boolean canSeekForward() {

return mCanSeekForward;

}

}

赞助本站

人工智能实验室

相关热词: VideoView 重构 播放器

相关内容
AiLab云推荐
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港