SoFunction
Updated on 2025-04-10

Android VideoView class example explanation

This section uses the system's sample class VideoView to continue to explain the relevant content of the SurfaceView class, so that everyone can have a deeper understanding of the implementation principles of the basic graphics drawing class in the Android system. Maybe you will find that the control aspect of the VideoView class cannot be changed. We can implement a more personalized player by refactoring the VideoView class.

Below is the relevant code of the VideoView class.

Java code

 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  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) { 
  //("@@@@", "onMeasure"); 
  int width = getDefaultSize(mVideoWidth, widthMeasureSpec); 
  int height = getDefaultSize(mVideoHeight, heightMeasureSpec); 
  if (mVideoWidth > 0 && mVideoHeight > 0) { 
   if ( mVideoWidth * height > width * mVideoHeight ) { 
    //("@@@", "image too tall, correcting"); 
    height = width * mVideoHeight / mVideoWidth; 
   } else if ( mVideoWidth * height < width * mVideoHeight ) { 
    //("@@@", "image too wide, correcting"); 
    width = height * mVideoWidth / mVideoHeight; 
   } else { 
    //("@@@", "aspect ratio is correct: " + 
      //width+"/"+height+"="+ 
      //mVideoWidth+"/"+mVideoHeight); 
   } 
  } 
  //("@@@@@@@@@@", "setting size: " + width + 'x' + height); 
  setMeasuredDimension(width, height); 
 } 
  
 public int resolveAdjustedSize(int desiredSize, int measureSpec) { 
  int result = desiredSize; 
  int specMode = (measureSpec); 
  int specSize = (measureSpec); 
 
  switch (specMode) { 
   case : 
        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 = (desiredSize, specSize); 
    break; 
     
   case : 
    // 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((path)); 
 } 
 
 public void setVideoURI(Uri uri) { 
  mUri = uri; 
  mSeekWhenPrepared = 0; 
  openVideo(); 
  requestLayout(); 
  invalidate(); 
 } 
  
 public void stopPlayback() { 
  if (mMediaPlayer != null) { 
   (); 
   (); 
   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(""); 
  ("command", "pause"); 
  (i); 
 
  // we shouldn't clear the target state, because somebody might have 
  // called start() previously 
  release(false); 
  try { 
   mMediaPlayer = new MediaPlayer(); 
   (mPreparedListener); 
   (mSizeChangedListener); 
   mDuration = -1; 
   (mCompletionListener); 
   (mErrorListener); 
   (mBufferingUpdateListener); 
   mCurrentBufferPercentage = 0; 
   (mContext, mUri); 
   (mSurfaceHolder); 
   (AudioManager.STREAM_MUSIC); 
   (true); 
   (); 
   // 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) { 
   (TAG, "Unable to open content: " + mUri, ex); 
   mCurrentState = STATE_ERROR; 
   mTargetState = STATE_ERROR; 
   (mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); 
   return; 
  } catch (IllegalArgumentException ex) { 
   (TAG, "Unable to open content: " + mUri, ex); 
   mCurrentState = STATE_ERROR; 
   mTargetState = STATE_ERROR; 
   (mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); 
   return; 
  } 
 } 
  
 public void setMediaController(MediaController controller) { 
  if (mMediaController != null) { 
   (); 
  } 
  mMediaController = controller; 
  attachMediaController(); 
 } 
 
 private void attachMediaController() { 
  if (mMediaPlayer != null && mMediaController != null) { 
   (this); 
   View anchorView = () instanceof View ? 
     (View)() : this; 
   (anchorView); 
   (isInPlaybackState()); 
  } 
 } 
  
  mSizeChangedListener = 
  new () { 
   public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { 
    mVideoWidth = (); 
    mVideoHeight = (); 
    if (mVideoWidth != 0 && mVideoHeight != 0) { 
     getHolder().setFixedSize(mVideoWidth, mVideoHeight); 
    } 
   } 
 }; 
  
  mPreparedListener = new () { 
  public void onPrepared(MediaPlayer mp) { 
   mCurrentState = STATE_PREPARED; 
 
   // Get the capabilities of the player for this stream 
   Metadata data = (MediaPlayer.METADATA_ALL, 
          MediaPlayer.BYPASS_METADATA_FILTER); 
 
   if (data != null) { 
    mCanPause = !(Metadata.PAUSE_AVAILABLE) 
      || (Metadata.PAUSE_AVAILABLE); 
    mCanSeekBack = !(Metadata.SEEK_BACKWARD_AVAILABLE) 
      || (Metadata.SEEK_BACKWARD_AVAILABLE); 
    mCanSeekForward = !(Metadata.SEEK_FORWARD_AVAILABLE) 
      || (Metadata.SEEK_FORWARD_AVAILABLE); 
   } else { 
    mCanPause = mCanSeekForward = mCanSeekForward = true; 
   } 
 
   if (mOnPreparedListener != null) { 
    (mMediaPlayer); 
   } 
   if (mMediaController != null) { 
    (true); 
   } 
   mVideoWidth = (); 
   mVideoHeight = (); 
 
   int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call 
   if (seekToPosition != 0) { 
    seekTo(seekToPosition); 
   } 
   if (mVideoWidth != 0 && mVideoHeight != 0) { 
    //("@@@@", "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) { 
       (); 
      } 
     } 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. 
       (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  mCompletionListener = 
  new () { 
  public void onCompletion(MediaPlayer mp) { 
   mCurrentState = STATE_PLAYBACK_COMPLETED; 
   mTargetState = STATE_PLAYBACK_COMPLETED; 
   if (mMediaController != null) { 
    (); 
   } 
   if (mOnCompletionListener != null) { 
    (mMediaPlayer); 
   } 
  } 
 }; 
 
 private  mErrorListener = 
  new () { 
  public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { 
   (TAG, "Error: " + framework_err + "," + impl_err); 
   mCurrentState = STATE_ERROR; 
   mTargetState = STATE_ERROR; 
   if (mMediaController != null) { 
    (); 
   } 
 
   /* If an error handler has been supplied, use it and finish. */ 
   if (mOnErrorListener != null) { 
    if ((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 = (); 
    int messageId; 
 
    if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { 
     messageId = .VideoView_error_text_invalid_progressive_playback; 
    } else { 
     messageId = .VideoView_error_text_unknown; 
    } 
 
    new (mContext) 
      .setTitle(.VideoView_error_title) 
      .setMessage(messageId) 
      .setPositiveButton(.VideoView_error_button, 
        new () { 
         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) { 
           (mMediaPlayer); 
          } 
         } 
        }) 
      .setCancelable(false) 
      .show(); 
   } 
   return true; 
  } 
 }; 
 
 private  mBufferingUpdateListener = 
  new () { 
  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( 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; 
 } 
 
  mSHCallback = new () 
 { 
  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) { 
     (); 
    } 
   } 
  } 
 
  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) (); 
   release(true); 
  } 
 }; 
 
  private void release(boolean cleartargetstate) { 
  if (mMediaPlayer != null) { 
   (); 
   (); 
   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 (()) { 
     pause(); 
     (); 
    } else { 
     start(); 
     (); 
    } 
    return true; 
   } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP 
     && ()) { 
    pause(); 
    (); 
   } else { 
    toggleMediaControlsVisiblity(); 
   } 
  } 
 
  return (keyCode, event); 
 } 
 
 private void toggleMediaControlsVisiblity() { 
  if (()) { 
   (); 
  } else { 
   (); 
  } 
 } 
  
 public void start() { 
  if (isInPlaybackState()) { 
   (); 
   mCurrentState = STATE_PLAYING; 
  } 
  mTargetState = STATE_PLAYING; 
 } 
  
 public void pause() { 
  if (isInPlaybackState()) { 
   if (()) { 
    (); 
    mCurrentState = STATE_PAUSED; 
   } 
  } 
  mTargetState = STATE_PAUSED; 
 } 
  
 // cache duration as mDuration for faster access 
 public int getDuration() { 
  if (isInPlaybackState()) { 
   if (mDuration > 0) { 
    return mDuration; 
   } 
   mDuration = (); 
   return mDuration; 
  } 
  mDuration = -1; 
  return mDuration; 
 } 
  
 public int getCurrentPosition() { 
  if (isInPlaybackState()) { 
   return (); 
  } 
  return 0; 
 } 
  
 public void seekTo(int msec) { 
  if (isInPlaybackState()) { 
   (msec); 
   mSeekWhenPrepared = 0; 
  } else { 
   mSeekWhenPrepared = msec; 
  } 
 }  
    
 public boolean isPlaying() { 
  return isInPlaybackState() && (); 
 } 
  
 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; 
 } 
} 

The above is a detailed introduction to the Android VideoView class. We will continue to add relevant knowledge in the future. Thank you for your support for this site!