Back to project page Android-VideoView.
The source code is released under:
GNU General Public License
If you think the Android project Android-VideoView listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package com.ai.videoview.widget; /* w w w . j a v a 2s . c o m*/ import java.io.IOException; import android.content.Context; import android.content.Intent; import android.graphics.SurfaceTexture; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnInfoListener; import android.net.Uri; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.MediaController; import android.widget.MediaController.MediaPlayerControl; /** * VideoView is used to play video, just like * {@link android.widget.VideoView VideoView}. We define a custom view, because * we could not use {@link android.widget.VideoView VideoView} in ListView. <br/> * VideoViews inside ScrollViews do not scroll properly. Even if you use the * workaround to set the background color, the MediaController does not scroll * along with the VideoView. Also, the scrolling video looks horrendous with the * workaround, lots of flickering. * * @author leo * */ public class VideoView extends TextureView implements MediaPlayerControl { private static final String TAG = "VideoView"; // 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; // currentState is a VideoView object's current state. // targetState 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; // Stuff we need for playing and showing a video private MediaPlayer mMediaPlayer; private int mVideoWidth; private int mVideoHeight; private int mSurfaceWidth; private int mSurfaceHeight; private SurfaceTexture mSurfaceTexture; private Surface mSurface; private MediaController mMediaController; private MediaPlayer.OnCompletionListener mOnCompletionListener; private MediaPlayer.OnPreparedListener mOnPreparedListener; private MediaPlayer.OnErrorListener mOnErrorListener; private MediaPlayer.OnInfoListener mOnInfoListener; private int mSeekWhenPrepared; // recording the seek position while // preparing private int mCurrentBufferPercentage; private int mAudioSession; private Uri mUri; private Context mContext; public VideoView(final Context context) { super(context); mContext = context; initVideoView(); } public VideoView(final Context context, final AttributeSet attrs) { super(context, attrs); mContext = context; initVideoView(); } public VideoView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; initVideoView(); } public void initVideoView() { mVideoHeight = 0; mVideoWidth = 0; setBackgroundColor(getResources().getColor(android.R.color.transparent)); setFocusable(false); setSurfaceTextureListener(mSurfaceTextureListener); } 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; } public void setVideoPath(String path) { Log.d(TAG, "Setting video path to: " + path); setVideoURI(Uri.parse(path)); } public void setVideoURI(Uri _videoURI) { mUri = _videoURI; mSeekWhenPrepared = 0; requestLayout(); invalidate(); openVideo(); } public Uri getUri() { return mUri; } public void setSurfaceTexture(SurfaceTexture _surfaceTexture) { mSurfaceTexture = _surfaceTexture; } public void openVideo() { if ((mUri == null) || (mSurfaceTexture == null)) { Log.d(TAG, "Cannot open video, uri or surface texture is null."); 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); release(false); try { mSurface = new Surface(mSurfaceTexture); mMediaPlayer = new MediaPlayer(); if (mAudioSession != 0) { mMediaPlayer.setAudioSessionId(mAudioSession); } else { mAudioSession = mMediaPlayer.getAudioSessionId(); } mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); mMediaPlayer.setOnCompletionListener(mCompleteListener); mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnInfoListener(mOnInfoListener); mMediaPlayer.setOnVideoSizeChangedListener(mVideoSizeChangedListener); mMediaPlayer.setSurface(mSurface); mCurrentBufferPercentage = 0; mMediaPlayer.setDataSource(mContext, mUri); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setScreenOnWhilePlaying(true); mMediaPlayer.prepareAsync(); mCurrentState = STATE_PREPARING; } catch (IllegalStateException e) { mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; Log.d(TAG, e.getMessage()); // TODO auto-generated catch block } catch (IOException e) { mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; Log.d(TAG, e.getMessage()); // TODO auto-generated catch block } } public void stopPlayback() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; if (null != mMediaControllListener) { mMediaControllListener.onStop(); } } } 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()); } } private void release(boolean cleartargetstate) { Log.d(TAG, "Releasing media player."); if (mMediaPlayer != null) { mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; mCurrentState = STATE_IDLE; if (cleartargetstate) { mTargetState = STATE_IDLE; } } else { Log.d(TAG, "Media player was null, did not release."); } } @Override protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { // Will resize the view if the video dimensions have been found. // video dimensions are found after onPrepared has been called by // MediaPlayer int width = getDefaultSize(mVideoWidth, widthMeasureSpec); int height = getDefaultSize(mVideoHeight, heightMeasureSpec); if ((mVideoWidth > 0) && (mVideoHeight > 0)) { if ((mVideoWidth * height) > (width * mVideoHeight)) { Log.d(TAG, "Video too tall, change size."); height = (width * mVideoHeight) / mVideoWidth; } else if ((mVideoWidth * height) < (width * mVideoHeight)) { Log.d(TAG, "Video too wide, change size."); width = (height * mVideoWidth) / mVideoHeight; } else { Log.d(TAG, "Aspect ratio is correct."); } } setMeasuredDimension(width, height); } @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_VOLUME_MUTE && 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_PLAY) { if (!mMediaPlayer.isPlaying()) { start(); mMediaController.hide(); } return true; } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { if (mMediaPlayer.isPlaying()) { pause(); mMediaController.show(); } return true; } else { toggleMediaControlsVisiblity(); } } return super.onKeyDown(keyCode, event); } private void toggleMediaControlsVisiblity() { if (mMediaController.isShowing()) { mMediaController.hide(); } else { mMediaController.show(); } } public void start() { // This can potentially be called at several points, it will go through // when all conditions are ready // 1. When setting the video URI // 2. When the surface becomes available // 3. From the activity if (isInPlaybackState()) { mMediaPlayer.start(); mCurrentState = STATE_PLAYING; if (null != mMediaControllListener) { mMediaControllListener.onStart(); } } else { Log.d(TAG, "Could not start. Current state " + mCurrentState); } mTargetState = STATE_PLAYING; } public void pause() { if (isInPlaybackState()) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); mCurrentState = STATE_PAUSED; if (null != mMediaControllListener) { mMediaControllListener.onPause(); } } } mTargetState = STATE_PAUSED; } public void suspend() { release(false); } public void resume() { openVideo(); } @Override 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; } } @Override public boolean isPlaying() { return isInPlaybackState() && mMediaPlayer.isPlaying(); } @Override 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)); } @Override public boolean canPause() { return false; } @Override public boolean canSeekBackward() { return false; } @Override public boolean canSeekForward() { return false; } @Override public int getAudioSessionId() { if (mAudioSession == 0) { MediaPlayer foo = new MediaPlayer(); mAudioSession = foo.getAudioSessionId(); foo.release(); } return mAudioSession; } // Listeners private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { @Override public void onBufferingUpdate(final MediaPlayer mp, final int percent) { mCurrentBufferPercentage = percent; } }; private MediaPlayer.OnCompletionListener mCompleteListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(final MediaPlayer mp) { mCurrentState = STATE_PLAYBACK_COMPLETED; mTargetState = STATE_PLAYBACK_COMPLETED; mSurface.release(); if (mMediaController != null) { mMediaController.hide(); } if (mOnCompletionListener != null) { mOnCompletionListener.onCompletion(mp); } if (mMediaControllListener != null) { mMediaControllListener.onComplete(); } } }; private MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(final MediaPlayer mp) { mCurrentState = STATE_PREPARED; 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); } requestLayout(); invalidate(); if ((mVideoWidth != 0) && (mVideoHeight != 0)) { if (mTargetState == STATE_PLAYING) { mMediaPlayer.start(); if (null != mMediaControllListener) { mMediaControllListener.onStart(); } } } else { if (mTargetState == STATE_PLAYING) { mMediaPlayer.start(); if (null != mMediaControllListener) { mMediaControllListener.onStart(); } } } } }; private MediaPlayer.OnVideoSizeChangedListener mVideoSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() { @Override public void onVideoSizeChanged(final MediaPlayer mp, final int width, final int height) { mVideoWidth = mp.getVideoWidth(); mVideoHeight = mp.getVideoHeight(); if (mVideoWidth != 0 && mVideoHeight != 0) { requestLayout(); } } }; private MediaPlayer.OnErrorListener mErrorListener = new MediaPlayer.OnErrorListener() { @Override public boolean onError(final MediaPlayer mp, final int what, final int extra) { Log.d(TAG, "Error: " + what + "," + extra); 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, what, extra)) { 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) { // new AlertDialog.Builder(mContext).setMessage("Error: " + what + "," + extra).setPositiveButton("OK", 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; } }; SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) { Log.d(TAG, "onSurfaceTextureAvailable."); mSurfaceTexture = surface; openVideo(); } @Override public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) { Log.d(TAG, "onSurfaceTextureSizeChanged: " + width + '/' + height); mSurfaceWidth = width; mSurfaceHeight = height; boolean isValidState = (mTargetState == STATE_PLAYING); boolean hasValidSize = (mVideoWidth == width && mVideoHeight == height); if (mMediaPlayer != null && isValidState && hasValidSize) { if (mSeekWhenPrepared != 0) { seekTo(mSeekWhenPrepared); } start(); } } @Override public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) { mSurface = null; if (mMediaController != null) mMediaController.hide(); release(true); return true; } @Override public void onSurfaceTextureUpdated(final SurfaceTexture surface) { } }; /** * 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; } /** * Register a callback to be invoked when an informational event occurs * during playback or setup. * * @param l * The callback that will be run */ public void setOnInfoListener(OnInfoListener l) { mOnInfoListener = l; } public static interface MediaControllListener { public void onStart(); public void onPause(); public void onStop(); public void onComplete(); } MediaControllListener mMediaControllListener; public void setMediaControllListener(MediaControllListener mediaControllListener) { mMediaControllListener = mediaControllListener; } @Override public void setVisibility(int visibility) { System.out.println("setVisibility: "+ visibility); super.setVisibility(visibility); } }