summaryrefslogtreecommitdiffstats
path: root/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java')
-rwxr-xr-xsrc/org/codeaurora/gallery3d/video/CodeauroraVideoView.java1049
1 files changed, 1049 insertions, 0 deletions
diff --git a/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java b/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java
new file mode 100755
index 000000000..c637c295a
--- /dev/null
+++ b/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java
@@ -0,0 +1,1049 @@
+package org.codeaurora.gallery3d.video;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnBufferingUpdateListener;
+import android.media.MediaPlayer.OnVideoSizeChangedListener;
+import android.media.Metadata;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnInfoListener;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.MediaController;
+import android.widget.MediaController.MediaPlayerControl;
+
+import org.codeaurora.gallery3d.video.ScreenModeManager.ScreenModeListener;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Displays a video file. The VideoView class
+ * can load images from various sources (such as resources or content
+ * providers), takes care of computing its measurement from the video so that
+ * it can be used in any layout manager, and provides various display options
+ * such as scaling and tinting.
+ */
+public class CodeauroraVideoView extends SurfaceView implements MediaPlayerControl, ScreenModeListener{
+ private static final boolean LOG = false;
+ private String TAG = "CodeauroraVideoView";
+ // settable by the client
+ private Uri mUri;
+ private Map<String, String> mHeaders;
+
+ // 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;
+ private static final int STATE_SUSPENDED = 6;
+ private static final int MSG_LAYOUT_READY = 1;
+
+ // 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 mAudioSession;
+ private int mVideoWidth;
+ private int mVideoHeight;
+ private int mSurfaceWidth;
+ private int mSurfaceHeight;
+ private int mDuration;
+ private MediaController mMediaController;
+ private OnCompletionListener mOnCompletionListener;
+ private MediaPlayer.OnPreparedListener mOnPreparedListener;
+ private MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener;
+ private MediaPlayer.OnVideoSizeChangedListener mVideoSizeListener;
+ private MediaPlayer.OnPreparedListener mPreparedListener;
+ private ScreenModeManager mScreenManager;
+ private int mCurrentBufferPercentage;
+ private OnErrorListener mOnErrorListener;
+ private OnInfoListener mOnInfoListener;
+ private int mSeekWhenPrepared; // recording the seek position while preparing
+ private boolean mCanPause;
+ private boolean mCanSeekBack;
+ private boolean mCanSeekForward;
+ private boolean mCanSeek;
+ private boolean mHasGotPreparedCallBack = false;
+ private boolean mNeedWaitLayout = false;
+ private boolean mHasGotMetaData = false;
+ private boolean mOnResumed;
+ private boolean mIsShowDialog = false;
+
+ private final Handler mHandler = new Handler() {
+ public void handleMessage(final Message msg) {
+ if (LOG) {
+ Log.v(TAG, "handleMessage() to do prepare. msg=" + msg);
+ }
+ switch (msg.what) {
+ case MSG_LAYOUT_READY:
+ if (mMediaPlayer == null || mUri == null) {
+ Log.w(TAG, "Cannot prepare play! mMediaPlayer=" + mMediaPlayer
+ + ", mUri=" + mUri);
+ return;
+ }
+ doPreparedIfReady(mMediaPlayer);
+ break;
+ default:
+ Log.w(TAG, "Unhandled message " + msg);
+ break;
+ }
+ }
+ };
+
+ public CodeauroraVideoView(Context context) {
+ super(context);
+ initVideoView();
+ initialize();
+ }
+
+ public CodeauroraVideoView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ initVideoView();
+ initialize();
+ }
+
+ public CodeauroraVideoView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initVideoView();
+ initialize();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = 0;
+ int height = 0;
+ int screenMode = ScreenModeManager.SCREENMODE_BIGSCREEN;
+ if (mScreenManager != null) {
+ screenMode = mScreenManager.getScreenMode();
+ }
+ switch (screenMode) {
+ case ScreenModeManager.SCREENMODE_BIGSCREEN:
+ width = getDefaultSize(mVideoWidth, widthMeasureSpec);
+ height = getDefaultSize(mVideoHeight, heightMeasureSpec);
+ if (mVideoWidth > 0 && mVideoHeight > 0) {
+ if (mVideoWidth * height > width * mVideoHeight) {
+ height = width * mVideoHeight / mVideoWidth;
+ } else if (mVideoWidth * height < width * mVideoHeight) {
+ width = height * mVideoWidth / mVideoHeight;
+ }
+ }
+ break;
+ case ScreenModeManager.SCREENMODE_FULLSCREEN:
+ width = getDefaultSize(mVideoWidth, widthMeasureSpec);
+ height = getDefaultSize(mVideoHeight, heightMeasureSpec);
+ break;
+ case ScreenModeManager.SCREENMODE_CROPSCREEN:
+ width = getDefaultSize(mVideoWidth, widthMeasureSpec);
+ height = getDefaultSize(mVideoHeight, heightMeasureSpec);
+ if (mVideoWidth > 0 && mVideoHeight > 0) {
+ if (mVideoWidth * height > width * mVideoHeight) {
+ width = height * mVideoWidth / mVideoHeight;
+ } else if (mVideoWidth * height < width * mVideoHeight) {
+ height = width * mVideoHeight / mVideoWidth;
+ }
+ }
+ break;
+ default:
+ Log.w(TAG, "wrong screen mode : " + screenMode);
+ break;
+ }
+ if (LOG) {
+ Log.v(TAG, "onMeasure() set size: " + width + 'x' + height);
+ Log.v(TAG, "onMeasure() video size: " + mVideoWidth + 'x' + mVideoHeight);
+ Log.v(TAG, "onMeasure() mNeedWaitLayout=" + mNeedWaitLayout);
+ }
+ setMeasuredDimension(width, height);
+ if (mNeedWaitLayout) { // when OnMeasure ok, start video.
+ mNeedWaitLayout = false;
+ mHandler.sendEmptyMessage(MSG_LAYOUT_READY);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(CodeauroraVideoView.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(CodeauroraVideoView.class.getName());
+ }
+
+ public int resolveAdjustedSize(int desiredSize, int measureSpec) {
+ return getDefaultSize(desiredSize, measureSpec);
+ }
+
+ 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;
+ }
+
+ private void initialize() {
+ mPreparedListener = new MediaPlayer.OnPreparedListener() {
+ public void onPrepared(final MediaPlayer mp) {
+ if (LOG) {
+ Log.v(TAG, "mPreparedListener.onPrepared(" + mp + ")");
+ }
+ //Here we can get meta data from mediaplayer.
+ // Get the capabilities of the player for this stream
+ final 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);
+ mCanSeek = !data.has(Metadata.SEEK_AVAILABLE)
+ || data.getBoolean(Metadata.SEEK_AVAILABLE);
+ } else {
+ mCanPause = true;
+ mCanSeekBack = true;
+ mCanSeekForward = true;
+ mCanSeek = true;
+ Log.w(TAG, "Metadata is null!");
+ }
+ if (LOG) {
+ Log.v(TAG, "mPreparedListener.onPrepared() mCanPause=" + mCanPause);
+ }
+ mHasGotPreparedCallBack = true;
+ doPreparedIfReady(mMediaPlayer);
+ }
+ };
+
+ mErrorListener = new MediaPlayer.OnErrorListener() {
+ public boolean onError(final MediaPlayer mp, final int frameworkErr, final int implErr) {
+ Log.d(TAG, "Error: " + frameworkErr + "," + implErr);
+ //record error position and duration
+ //here disturb the original logic
+ mSeekWhenPrepared = getCurrentPosition();
+ if (LOG) {
+ Log.v(TAG, "onError() mSeekWhenPrepared=" + mSeekWhenPrepared + ", mDuration=" + mDuration);
+ }
+ //for old version Streaming server, getduration is not valid.
+ mDuration = Math.abs(mDuration);
+ 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, frameworkErr, implErr)) {
+ 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) {
+ final Resources r = mContext.getResources();
+ int messageId;
+
+ if (frameworkErr == 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)
+ .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;
+ }
+ };
+
+ mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() {
+ public void onBufferingUpdate(final MediaPlayer mp, final int percent) {
+ mCurrentBufferPercentage = percent;
+ if (mOnBufferingUpdateListener != null) {
+ mOnBufferingUpdateListener.onBufferingUpdate(mp, percent);
+ }
+ if (LOG) {
+ Log.v(TAG, "onBufferingUpdate() Buffering percent: " + percent);
+ Log.v(TAG, "onBufferingUpdate() mTargetState=" + mTargetState);
+ Log.v(TAG, "onBufferingUpdate() mCurrentState=" + mCurrentState);
+ }
+ }
+ };
+
+ mSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() {
+ public void onVideoSizeChanged(final MediaPlayer mp, final int width, final int height) {
+ mVideoWidth = mp.getVideoWidth();
+ mVideoHeight = mp.getVideoHeight();
+ if (LOG) {
+ Log.v(TAG, "OnVideoSizeChagned(" + width + "," + height + ")");
+ Log.v(TAG, "OnVideoSizeChagned(" + mVideoWidth + "," + mVideoHeight + ")");
+ Log.v(TAG, "OnVideoSizeChagned() mCurrentState=" + mCurrentState);
+ }
+ if (mVideoWidth != 0 && mVideoHeight != 0) {
+ getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+ if (mCurrentState == STATE_PREPARING) {
+ mNeedWaitLayout = true;
+ }
+ }
+ if (mVideoSizeListener != null) {
+ mVideoSizeListener.onVideoSizeChanged(mp, width, height);
+ }
+ CodeauroraVideoView.this.requestLayout();
+ }
+ };
+
+ getHolder().removeCallback(mSHCallback);
+ mSHCallback = new SurfaceHolder.Callback() {
+ public void surfaceChanged(final SurfaceHolder holder, final int format,
+ final int w, final int h) {
+ if (LOG) {
+ Log.v(TAG, "surfaceChanged(" + holder + ", " + format
+ + ", " + w + ", " + h + ")");
+ Log.v(TAG, "surfaceChanged() mMediaPlayer=" + mMediaPlayer
+ + ", mTargetState=" + mTargetState
+ + ", mVideoWidth=" + mVideoWidth
+ + ", mVideoHeight=" + mVideoHeight);
+ }
+ mSurfaceWidth = w;
+ mSurfaceHeight = h;
+ final boolean isValidState = (mTargetState == STATE_PLAYING);
+ final boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h);
+ if (mMediaPlayer != null && isValidState && hasValidSize) {
+ if (mSeekWhenPrepared != 0) {
+ seekTo(mSeekWhenPrepared);
+ }
+ Log.v(TAG, "surfaceChanged() start()");
+ start();
+ }
+ }
+
+ public void surfaceCreated(final SurfaceHolder holder) {
+ if (LOG) {
+ Log.v(TAG, "surfaceCreated(" + holder + ")");
+ }
+ if (mCurrentState == STATE_SUSPENDED) {
+ mSurfaceHolder = holder;
+ mMediaPlayer.setDisplay(mSurfaceHolder);
+ if (mMediaPlayer.resume()) {
+ mCurrentState = STATE_PREPARED;
+ if (mSeekWhenPrepared != 0) {
+ seekTo(mSeekWhenPrepared);
+ }
+ if (mTargetState == STATE_PLAYING) {
+ start();
+ }
+ return;
+ } else {
+ release(false);
+ }
+ }
+ mSurfaceHolder = holder;
+ openVideo();
+ }
+
+ public void surfaceDestroyed(final SurfaceHolder holder) {
+ // after we return from this we can't use the surface any more
+ if (LOG) {
+ Log.v(TAG, "surfaceDestroyed(" + holder + ")");
+ }
+ mSurfaceHolder = null;
+ if (mMediaController != null) {
+ mMediaController.hide();
+ }
+ if (isHTTPStreaming(mUri) && mCurrentState == STATE_SUSPENDED) {
+ // Don't call release() while run suspend operation
+ return;
+ }
+ release(true);
+ }
+ };
+ getHolder().addCallback(mSHCallback);
+ }
+
+ public void setVideoPath(String path) {
+ setVideoURI(Uri.parse(path));
+ }
+
+ public void setVideoURI(Uri uri) {
+ setVideoURI(uri, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void setVideoURI(Uri uri, Map<String, String> headers) {
+ Log.d(TAG,"setVideoURI uri = " + uri);
+ mDuration = -1;
+ setResumed(true);
+ mUri = uri;
+ mHeaders = headers;
+ 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() {
+ clearVideoInfo();
+ if (mUri == null || mSurfaceHolder == null) {
+ // not ready for playback just yet, will try again later
+ return;
+ }
+
+ // we shouldn't clear the target state, because somebody might have
+ // called start() previously
+ release(false);
+ if ("".equalsIgnoreCase(String.valueOf(mUri))) {
+ Log.w(TAG, "Unable to open content: " + mUri);
+ mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ return;
+ }
+ try {
+ mMediaPlayer = new MediaPlayer();
+ if (mAudioSession != 0) {
+ mMediaPlayer.setAudioSessionId(mAudioSession);
+ } else {
+ mAudioSession = mMediaPlayer.getAudioSessionId();
+ }
+ mMediaPlayer.setOnPreparedListener(mPreparedListener);
+ mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
+ mMediaPlayer.setOnCompletionListener(mCompletionListener);
+ mMediaPlayer.setOnErrorListener(mErrorListener);
+ mMediaPlayer.setOnInfoListener(mOnInfoListener);
+ mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
+ mCurrentBufferPercentage = 0;
+ mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
+ 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());
+ }
+ }
+
+ private boolean isHTTPStreaming(Uri mUri) {
+ if (mUri != null){
+ String scheme = mUri.toString();
+ if (scheme.startsWith("http://") || scheme.startsWith("https://")) {
+ if (scheme.endsWith(".m3u8") || scheme.endsWith(".m3u")
+ || scheme.contains("m3u8") || scheme.endsWith(".mpd")) {
+ // HLS or DASH streaming source
+ return false;
+ }
+ // HTTP streaming
+ return true;
+ }
+ }
+ return false;
+ }
+
+ 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);
+ requestLayout();
+ }
+ }
+ };
+
+ 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;
+
+ 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ 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();
+ }
+ }
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ if (LOG) {
+ Log.v(TAG, "surfaceCreated(" + holder + ")");
+ }
+ if (mCurrentState == STATE_SUSPENDED) {
+ mSurfaceHolder = holder;
+ mMediaPlayer.setDisplay(mSurfaceHolder);
+ if (mMediaPlayer.resume()) {
+ mCurrentState = STATE_PREPARED;
+ if (mSeekWhenPrepared != 0) {
+ seekTo(mSeekWhenPrepared);
+ }
+ if (mTargetState == STATE_PLAYING) {
+ start();
+ }
+ return;
+ } else {
+ release(false);
+ }
+ }
+ 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();
+ if (isHTTPStreaming(mUri) && mCurrentState == STATE_SUSPENDED) {
+ // Don't call release() while run suspend operation
+ return;
+ }
+ 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) {
+ final 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 &&
+ keyCode != KeyEvent.KEYCODE_CAMERA;
+ if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
+ if (event.getRepeatCount() == 0 && (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 if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD ||
+ keyCode == KeyEvent.KEYCODE_MEDIA_NEXT ||
+ keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS ||
+ keyCode == KeyEvent.KEYCODE_MEDIA_REWIND ||
+ keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
+ keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
+ // consume media action, so if video view if front,
+ // other media player will not play any sounds.
+ return true;
+ } else {
+ toggleMediaControlsVisiblity();
+ }
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private void toggleMediaControlsVisiblity() {
+ if (mMediaController.isShowing()) {
+ mMediaController.hide();
+ } else {
+ mMediaController.show();
+ }
+ }
+
+ public void setDialogShowState(boolean isDialogShow) {
+ mIsShowDialog = isDialogShow;
+ }
+
+ @Override
+ public void start() {
+ if (mIsShowDialog) return;
+ 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() {
+ // HTTP streaming will call mMediaPlayer->suspend(), others will call release()
+ if (isHTTPStreaming(mUri) && mCurrentState != STATE_PREPARING) {
+ if (mMediaPlayer != null) {
+ if (mMediaPlayer.suspend()) {
+ mTargetState = mCurrentState;
+ mCurrentState = STATE_SUSPENDED;
+ return;
+ }
+ }
+ }
+ release(false);
+ }
+
+ public void resume() {
+ // HTTP streaming (with suspended status) will call mMediaPlayer->resume(),
+ // others will call openVideo()
+ if (mCurrentState == STATE_SUSPENDED) {
+ if (mSurfaceHolder != null) {
+ // The surface hasn't been destroyed
+ if (mMediaPlayer.resume()) {
+ mCurrentState = STATE_PREPARED;
+ if (mSeekWhenPrepared !=0) {
+ seekTo(mSeekWhenPrepared);
+ }
+ if (mTargetState == STATE_PLAYING) {
+ start();
+ }
+ return;
+ } else {
+ // resume failed, so call release() before openVideo()
+ release(false);
+ }
+ } else {
+ // The surface has been destroyed, resume operation will be done
+ // after surface created
+ return;
+ }
+ }
+ openVideo();
+ }
+
+ @Override
+ public int getDuration() {
+ final boolean inPlaybackState = isInPlaybackState();
+ if (LOG) {
+ Log.v(TAG, "getDuration() mDuration=" + mDuration + ", inPlaybackState="
+ + inPlaybackState);
+ }
+ if (inPlaybackState) {
+ if (mDuration > 0) {
+ return mDuration;
+ }
+ // in case the duration is zero or smaller than zero for streaming
+ // video
+ int tempDuration = mMediaPlayer.getDuration();
+ if (tempDuration <= 0) {
+ return mDuration;
+ } else {
+ mDuration = tempDuration;
+ }
+
+ return mDuration;
+ }
+ return mDuration;
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ int position = 0;
+ if (mSeekWhenPrepared > 0) {
+ // if connecting error before seek,
+ // we should remember this position for retry
+ position = mSeekWhenPrepared;
+ // /M: if player not started, getCurrentPosition() will lead to NE.
+ } else if (isInPlaybackState()) {
+ position = mMediaPlayer.getCurrentPosition();
+ }
+ if (LOG) {
+ Log.v(TAG, "getCurrentPosition() return " + position
+ + ", mSeekWhenPrepared=" + mSeekWhenPrepared);
+ }
+ return position;
+ }
+
+ @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 &&
+ mCurrentState != STATE_SUSPENDED);
+ }
+
+ @Override
+ public boolean canPause() {
+ return mCanPause;
+ }
+
+ @Override
+ public boolean canSeekBackward() {
+ return mCanSeekBack;
+ }
+
+ @Override
+ public boolean canSeekForward() {
+ return mCanSeekForward;
+ }
+
+ public boolean canSeek() {
+ return mCanSeek;
+ }
+
+ @Override
+ public int getAudioSessionId() {
+ if (mAudioSession == 0) {
+ MediaPlayer foo = new MediaPlayer();
+ mAudioSession = foo.getAudioSessionId();
+ foo.release();
+ }
+ return mAudioSession;
+ }
+
+ // for duration displayed
+ public void setDuration(final int duration) {
+ if (LOG) {
+ Log.v(TAG, "setDuration(" + duration + ")");
+ }
+ mDuration = (duration > 0 ? -duration : duration);
+ }
+
+ public void setVideoURI(final Uri uri, final Map<String, String> headers,
+ final boolean hasGotMetaData) {
+ if (LOG) {
+ Log.v(TAG, "setVideoURI(" + uri + ", " + headers + ")");
+ }
+ // clear the flags
+ mHasGotMetaData = hasGotMetaData;
+ setVideoURI(uri, headers);
+ }
+
+ private void doPreparedIfReady(final MediaPlayer mp) {
+ if (LOG) {
+ Log.v(TAG, "doPreparedIfReady() mHasGotPreparedCallBack=" + mHasGotPreparedCallBack
+ + ", mHasGotMetaData=" + mHasGotMetaData + ", mNeedWaitLayout="
+ + mNeedWaitLayout
+ + ", mCurrentState=" + mCurrentState);
+ }
+ if (mHasGotPreparedCallBack && mHasGotMetaData && !mNeedWaitLayout) {
+ doPrepared(mp);
+ }
+ }
+
+ private void doPrepared(final MediaPlayer mp) {
+ if (LOG) {
+ Log.v(TAG, "doPrepared(" + mp + ") start");
+ }
+ mCurrentState = STATE_PREPARED;
+ if (mOnPreparedListener != null) {
+ mOnPreparedListener.onPrepared(mMediaPlayer);
+ }
+ mVideoWidth = mp.getVideoWidth();
+ mVideoHeight = mp.getVideoHeight();
+
+ // mSeekWhenPrepared may be changed after seekTo()
+ final int seekToPosition = mSeekWhenPrepared;
+ if (seekToPosition != 0) {
+ seekTo(seekToPosition);
+ }
+ if (mVideoWidth != 0 && mVideoHeight != 0) {
+ getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+ }
+
+ if (mTargetState == STATE_PLAYING) {
+ start();
+ }
+ if (LOG) {
+ Log.v(TAG, "doPrepared() end video size: " + mVideoWidth + "," + mVideoHeight
+ + ", mTargetState=" + mTargetState + ", mCurrentState=" + mCurrentState);
+ }
+ }
+
+ /**
+ * surfaceCreate will invoke openVideo after the activity stoped. Here set
+ * this flag to avoid openVideo after the activity stoped.
+ *
+ * @param resume
+ */
+ public void setResumed(final boolean resume) {
+ if (LOG) {
+ Log.v(TAG, "setResumed(" + resume + ") mUri=" + mUri + ", mOnResumed=" + mOnResumed);
+ }
+ mOnResumed = resume;
+ }
+
+ private void clearVideoInfo() {
+ if (LOG) {
+ Log.v(TAG, "clearVideoInfo()");
+ }
+ mHasGotPreparedCallBack = false;
+ mNeedWaitLayout = false;
+ }
+
+ public void clearSeek() {
+ if (LOG) {
+ Log.v(TAG, "clearSeek() mSeekWhenPrepared=" + mSeekWhenPrepared);
+ }
+ mSeekWhenPrepared = 0;
+ }
+
+ public void clearDuration() {
+ if (LOG) {
+ Log.v(TAG, "clearDuration() mDuration=" + mDuration);
+ }
+ mDuration = -1;
+ }
+
+ public boolean isTargetPlaying() {
+ if (LOG) {
+ Log.v(TAG, "isTargetPlaying() mTargetState=" + mTargetState);
+ }
+ return mTargetState == STATE_PLAYING;
+ }
+
+ public void setScreenModeManager(final ScreenModeManager manager) {
+ mScreenManager = manager;
+ if (mScreenManager != null) {
+ mScreenManager.addListener(this);
+ }
+ if (LOG) {
+ Log.v(TAG, "setScreenModeManager(" + manager + ")");
+ }
+ }
+
+ @Override
+ public void onScreenModeChanged(final int newMode) {
+ this.requestLayout();
+ }
+
+ public void setOnVideoSizeChangedListener(final OnVideoSizeChangedListener l) {
+ mVideoSizeListener = l;
+ if (LOG) {
+ Log.i(TAG, "setOnVideoSizeChangedListener(" + l + ")");
+ }
+ }
+
+ public void setOnBufferingUpdateListener(final OnBufferingUpdateListener l) {
+ mOnBufferingUpdateListener = l;
+ if (LOG) {
+ Log.v(TAG, "setOnBufferingUpdateListener(" + l + ")");
+ }
+ }
+}