VideoView类剖析

来源:互联网 发布:网络最流行的话语 编辑:程序博客网 时间:2024/06/03 07:24
有关SurfaceView相关的内容今天Android123继续延用系统的示例类VideoView来让大家深入了解Android平台的图形绘制基础类的实现原理。大家可能会发现VideoView类的控制方面无法改变,我们可以通过重构VideoView类来实现更个性化的播放器。
  public class VideoView extends SurfaceView implements MediaPlayerControl {
  private String TAG = "VideoView";
  // settable by the client
  private Uri         mUri;
  private int         mDuration;
  // 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 int         mVideoWidth;
  private int         mVideoHeight;
  private int         mSurfaceWidth;
  private int         mSurfaceHeight;
  private MediaController mMediaController;
  private OnCompletionListener mOnCompletionListener;
  private MediaPlayer.OnPreparedListener mOnPreparedListener;
  private int         mCurrentBufferPercentage;
  private OnErrorListener mOnErrorListener;
  private int         mSeekWhenPrepared;  // recording the seek position while preparing
  private boolean     mCanPause;
  private boolean     mCanSeekBack;
  private boolean     mCanSeekForward;
  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;
原创粉丝点击