diff --git a/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/scenario/scene/road/V2XRoadVideoWindow.java b/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/scenario/scene/road/V2XRoadVideoWindow.java index da6d1b592b..268fa90b4b 100644 --- a/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/scenario/scene/road/V2XRoadVideoWindow.java +++ b/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/scenario/scene/road/V2XRoadVideoWindow.java @@ -53,13 +53,21 @@ public class V2XRoadVideoWindow extends RelativeLayout implements IV2XWindow, ID private void initView(Context context) { LayoutInflater.from(context).inflate(R.layout.window_road_video, this); - mVideoView = findViewById(R.id.roadVideoView); + mVideoView = findViewById(R.id.roadVideoView);/*播放器*/ + windowPalyImageView = findViewById(R.id.window_video_play);/*播放键*/ + mThumbnailImageView = findViewById(R.id.thumbnail_image);/*第一帧图片*/ closeImage = findViewById(R.id.roadVideoClose); - mThumbnailImageView = findViewById(R.id.thumbnail_image); - windowPalyImageView = findViewById(R.id.window_video_play); closeImage.setOnClickListener(v -> { close(); }); + mVideoView.setOnClickListener(v -> { + mThumbnailImageView.setVisibility(View.GONE); + if (mVideoView.isPlaying()) { + videoPause(); + } else { + videoResume(); + } + }); } @Override @@ -93,7 +101,6 @@ public class V2XRoadVideoWindow extends RelativeLayout implements IV2XWindow, ID mVideoView.setVisibility(VISIBLE); mVideoView.setVideoPath(path); mVideoView.setOnPreparedListener(mediaPlayer -> { - Logger.w(MODULE_NAME, "全屏准备。。。。。"); mThumbnailImageView.setVisibility(View.GONE); windowPalyImageView.setVisibility(View.GONE); }); @@ -101,9 +108,28 @@ public class V2XRoadVideoWindow extends RelativeLayout implements IV2XWindow, ID } /* - * 视频播放结束 - * */ - private void videoPlayEnd(String path){ + * 视频暂停播放 + * */ + private void videoPause() { + mVideoView.pause(); + windowPalyImageView.setVisibility(View.VISIBLE); + windowPalyImageView.setOnClickListener(v -> { + videoResume(); + }); + } + + /* + * 视频暂停后继续播放 + * */ + private void videoResume() { + mThumbnailImageView.setVisibility(View.INVISIBLE); + mVideoView.resume(); + } + + /* + * 视频播放结束 + * */ + private void videoPlayEnd(String path) { Bitmap bitmap = BitmapHelper.getVideoThumbnail(path); mThumbnailImageView.setVisibility(View.VISIBLE); mThumbnailImageView.setImageBitmap(bitmap); diff --git a/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/view/TextureVideoView.java b/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/view/TextureVideoView.java index b628bcd25f..25d6690a3c 100644 --- a/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/view/TextureVideoView.java +++ b/modules/mogo-module-v2x/src/main/java/com/mogo/module/v2x/view/TextureVideoView.java @@ -1,5 +1,3 @@ -package com.mogo.module.v2x.view; - /* * Copyright (C) 2006 The Android Open Source Project * @@ -16,129 +14,245 @@ package com.mogo.module.v2x.view; * limitations under the License. */ -import android.annotation.SuppressLint; +package com.mogo.module.v2x.view; + +import android.app.AlertDialog; import android.content.Context; -import android.graphics.Color; +import android.content.DialogInterface; +import android.content.res.Resources; 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.OnSeekCompleteListener; +import android.media.MediaPlayer.OnInfoListener; import android.net.Uri; +import android.opengl.GLES20; +import android.os.Build; 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.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.MediaController; import android.widget.MediaController.MediaPlayerControl; -import com.mogo.module.v2x.R; -import com.mogo.module.v2x.V2XConst; -import com.mogo.utils.logger.Logger; - import java.io.IOException; import java.util.Map; -import static com.mogo.module.v2x.V2XConst.MODULE_NAME; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; /** - * 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. + * Displays a video file. The TextureVideoView 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.

+ * + * Note: VideoView does not retain its full state when going into the + * background. In particular, it does not restore the current play state, + * play position or selected tracks. Applications should + * save and restore these on their own in + * {@link android.app.Activity#onSaveInstanceState} and + * {@link android.app.Activity#onRestoreInstanceState}.

+ * Also note that the audio session id (from {@link #getAudioSessionId}) may + * change from its previously returned value when the VideoView is restored.

+ * + * This code is based on the official Android sources for 7.1.1_r13 with the following differences: + *

    + *
  1. extends {@link TextureView} instead of a {@link android.view.SurfaceView} + * allowing proper view animations
  2. + *
  3. removes code that uses hidden APIs and thus is not available (e.g. subtitle support)
  4. + *
  5. various small fixes and improvements
  6. + *
*/ -public class TextureVideoView extends TextureView implements MediaPlayerControl { - private String TAG = "V2XModuleProvider"; +public class TextureVideoView extends TextureView + implements MediaPlayerControl { + private static final String TAG = "TextureVideoView"; + + // 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; + // settable by the client private Uri mUri; private Map mHeaders; - private int mDuration; - // all possible internal states - private int mCurrentState = V2XConst.STATE_IDLE; - private int mTargetState = mCurrentState, mTagetStateBackup = mCurrentState; - private int mPrepareState = V2XConst.STATE_PREPARED; + // mCurrentState is a TextureVideoView object's current state. + // mTargetState is the state that a method caller intends to reach. + // For instance, regardless the TextureVideoView 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 Surface mSurface = null; private MediaPlayer mMediaPlayer = null; - private int mVideoWidth, mVideoHeight; - + private int mAudioSession; + private int mVideoWidth; + private int mVideoHeight; + private MediaController mMediaController; private OnCompletionListener mOnCompletionListener; private MediaPlayer.OnPreparedListener mOnPreparedListener; private int mCurrentBufferPercentage; private OnErrorListener mOnErrorListener; - private MediaPlayer.OnInfoListener mOnInfoListener; - private int mSeekWhenPrepared; - private boolean isFocusLoss = true; - private boolean isSuspendFromActivity = false; - private int progressWhileSuspend = 0; - public static final int MEDIA_INFO_VIDEO_NOT_SUPPORTED = 860; - public static final int MEDIA_INFO_AUDIO_NOT_SUPPORTED = 862; + private OnInfoListener mOnInfoListener; + private int mSeekWhenPrepared; // recording the seek position while preparing + private boolean mCanPause; + private boolean mCanSeekBack; + private boolean mCanSeekForward; + private boolean mShouldRequestAudioFocus = true; public TextureVideoView(Context context) { - super(context); - initVideoView(); + this(context, null); } public TextureVideoView(Context context, AttributeSet attrs) { - super(context, attrs, 0); - initVideoView(); + this(context, attrs, 0); } public TextureVideoView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - initVideoView(); + + mVideoWidth = 0; + mVideoHeight = 0; + + setSurfaceTextureListener(mSurfaceTextureListener); + + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", " + // + MeasureSpec.toString(heightMeasureSpec) + ")"); + + int width = getDefaultSize(mVideoWidth, widthMeasureSpec); + int height = getDefaultSize(mVideoHeight, heightMeasureSpec); + if (mVideoWidth > 0 && mVideoHeight > 0) { + + int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + + if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { + // the size is fixed + width = widthSpecSize; + height = heightSpecSize; + + // for compatibility, we adjust size based on aspect ratio + if ( mVideoWidth * height < width * mVideoHeight ) { + //Log.i("@@@", "image too wide, correcting"); + width = height * mVideoWidth / mVideoHeight; + } else if ( mVideoWidth * height > width * mVideoHeight ) { + //Log.i("@@@", "image too tall, correcting"); + height = width * mVideoHeight / mVideoWidth; + } + } else if (widthSpecMode == MeasureSpec.EXACTLY) { + // only the width is fixed, adjust the height to match aspect ratio if possible + width = widthSpecSize; + height = width * mVideoHeight / mVideoWidth; + if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { + // couldn't match aspect ratio within the constraints + height = heightSpecSize; + } + } else if (heightSpecMode == MeasureSpec.EXACTLY) { + // only the height is fixed, adjust the width to match aspect ratio if possible + height = heightSpecSize; + width = height * mVideoWidth / mVideoHeight; + if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { + // couldn't match aspect ratio within the constraints + width = widthSpecSize; + } + } else { + // neither the width nor the height are fixed, try to use actual video size + width = mVideoWidth; + height = mVideoHeight; + if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { + // too tall, decrease both width and height + height = heightSpecSize; + width = height * mVideoWidth / mVideoHeight; + } + if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { + // too wide, decrease both width and height + width = widthSpecSize; + height = width * mVideoHeight / mVideoWidth; + } + } + } else { + // no size yet, just adopt the given spec sizes + } + setMeasuredDimension(width, height); } - @SuppressLint("NewApi") @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(TextureVideoView.class.getName()); } - @SuppressLint("NewApi") @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(TextureVideoView.class.getName()); } - private void initVideoView() { - mVideoWidth = 0; - mVideoHeight = 0; - setSurfaceTextureListener(mSurfaceTextureListener); - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - mCurrentState = V2XConst.STATE_IDLE; - mTargetState = V2XConst.STATE_IDLE; - } - - public boolean requestAudioFocus() { - return true; + public int resolveAdjustedSize(int desiredSize, int measureSpec) { + return getDefaultSize(desiredSize, measureSpec); } + /** + * Sets video path. + * + * @param path the path of the video. + */ public void setVideoPath(String path) { setVideoURI(Uri.parse(path)); } + /** + * Sets video URI. + * + * @param uri the URI of the video. + */ public void setVideoURI(Uri uri) { setVideoURI(uri, null); } + /** + * Sets video URI using specific headers. + * + * @param uri the URI of the video. + * @param headers the headers for the URI request. + * Note that the cross domain redirection is allowed by default, but that can be + * changed with key/value pairs through the headers parameter with + * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value + * to disallow or allow cross domain redirection. + */ public void setVideoURI(Uri uri, Map headers) { - if (uri == null) { - Logger.i(MODULE_NAME, "setVideoURI--- uri = null"); - } else { - Logger.i(MODULE_NAME, "setVideoURI--- uri = " + uri.getPath()); - } mUri = uri; mHeaders = headers; mSeekWhenPrepared = 0; - isFocusLoss = !requestAudioFocus(); openVideo(); requestLayout(); invalidate(); @@ -149,232 +263,354 @@ public class TextureVideoView extends TextureView implements MediaPlayerControl mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; - mTargetState = mCurrentState = V2XConst.STATE_IDLE; + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + if (mShouldRequestAudioFocus) { + AudioManager am = (AudioManager) getContext().getApplicationContext().getSystemService(Context.AUDIO_SERVICE); + am.abandonAudioFocus(null); + } } + clearSurface(); } - @SuppressLint("NewApi") - private void openVideo() { - Logger.i(MODULE_NAME, "openVideo"); - if (mUri == null) { - Logger.i(MODULE_NAME, "mUri == null "); - } - if (mSurface == null) { - Logger.i(MODULE_NAME, "mSurface == null "); - } - if (mUri == null || mSurface == null || isSuspendFromActivity) { - Logger.i(MODULE_NAME, "isSuspendFromActivity = " + isSuspendFromActivity); + /** + * Clears the surface texture by attaching a GL context and clearing it. + * Code taken from Hugo Gresse's answer on stackoverflow.com. + */ + private void clearSurface() { + if (mSurface == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { return; } + + EGL10 egl = (EGL10) EGLContext.getEGL(); + EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + egl.eglInitialize(display, null); + + int[] attribList = { + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 8, + EGL10.EGL_RENDERABLE_TYPE, EGL10.EGL_WINDOW_BIT, + EGL10.EGL_NONE, 0, // placeholder for recordable [@-3] + EGL10.EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + egl.eglChooseConfig(display, attribList, configs, configs.length, numConfigs); + EGLConfig config = configs[0]; + EGLContext context = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, new int[]{ + 12440, 2, EGL10.EGL_NONE + }); + EGLSurface eglSurface = egl.eglCreateWindowSurface(display, config, mSurface, new int[]{ + EGL10.EGL_NONE + }); + + egl.eglMakeCurrent(display, eglSurface, eglSurface, context); + GLES20.glClearColor(0, 0, 0, 1); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + egl.eglSwapBuffers(display, eglSurface); + egl.eglDestroySurface(display, eglSurface); + egl.eglMakeCurrent(display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + egl.eglDestroyContext(display, context); + egl.eglTerminate(display); + } + + private void openVideo() { + if (mUri == null || mSurface == 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 (mShouldRequestAudioFocus) { + AudioManager am = (AudioManager) getContext().getApplicationContext().getSystemService(Context.AUDIO_SERVICE); + am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); + } + try { mMediaPlayer = new MediaPlayer(); + + if (mAudioSession != 0) { + mMediaPlayer.setAudioSessionId(mAudioSession); + } else { + mAudioSession = mMediaPlayer.getAudioSessionId(); + } mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); - mDuration = -1; mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); - mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); - mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener); mMediaPlayer.setOnInfoListener(mInfoListener); + mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); mCurrentBufferPercentage = 0; - mMediaPlayer.setDataSource(getContext(), mUri, mHeaders); - if (mSurface != null) { - /* - Canvas mCanvas = mSurface.lockCanvas(new Rect()); - mCanvas.drawColor(Color.BLACK); - mSurface.unlockCanvasAndPost(mCanvas); - invalidate(); - */ - mMediaPlayer.setSurface(mSurface); - } + mMediaPlayer.setDataSource(getContext().getApplicationContext(), mUri, mHeaders); + mMediaPlayer.setSurface(mSurface); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setScreenOnWhilePlaying(true); - mMediaPlayer.setVolume(0, 0); - mMediaPlayer.prepare(); - mPrepareState = mCurrentState = V2XConst.STATE_PREPARING; - } catch (IOException | SecurityException | IllegalStateException | IllegalArgumentException ex) { - ex.printStackTrace(); - onError(); + 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; } } - private void onError() { - mTargetState = mCurrentState = V2XConst.STATE_ERROR; - if (mErrorListener != null) - mErrorListener.onError(mMediaPlayer, - MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + public void setMediaController(MediaController controller) { + if (mMediaController != null) { + mMediaController.hide(); + } + mMediaController = controller; + attachMediaController(); } - 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) { - Logger.d(MODULE_NAME, "OnVideoSizeChangedListener mVideoWidth:" + " mVideoWidth:" + mVideoWidth + " mVideoHeight:" + mVideoHeight); - } + 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) { + /* + * 播放器大小等于获取的视频尺寸!!!!!!!!!!!!!!!!! + * */ +// getSurfaceTexture().setDefaultBufferSize(mVideoWidth, mVideoHeight); +// requestLayout(); + } + } + }; MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { public void onPrepared(MediaPlayer mp) { - Logger.i(MODULE_NAME, "MediaPlayer.OnPreparedListener"); - mPrepareState = mCurrentState = V2XConst.STATE_PREPARED; - mVideoWidth = mp.getVideoWidth(); - mVideoHeight = mp.getVideoHeight(); + mCurrentState = STATE_PREPARED; + + mCanPause = mCanSeekBack = mCanSeekForward = true; - int seekToPosition = mSeekWhenPrepared; -// Logger.i(MODULE_NAME, "seekToPosition = " + seekToPosition); - if (seekToPosition != 0) { - seekTo(seekToPosition); - } - if (mTargetState == V2XConst.STATE_PLAYING) { - start(); - } if (mOnPreparedListener != null) { mOnPreparedListener.onPrepared(mMediaPlayer); } - mp.setOnInfoListener((mp1, what, extra) -> { - if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) - setBackgroundColor(Color.TRANSPARENT); - return true; - }); - } - }; - - private OnCompletionListener mCompletionListener = new OnCompletionListener() { - public void onCompletion(MediaPlayer mp) { - Logger.i(MODULE_NAME, "MediaPlayer.OnCompletionListener"); - mTargetState = mCurrentState = V2XConst.STATE_PLAYBACK_COMPLETED; - if (mOnCompletionListener != null) { - mOnCompletionListener.onCompletion(mMediaPlayer); + if (mMediaController != null) { + mMediaController.setEnabled(true); } - } - }; + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); - private OnSeekCompleteListener mSeekCompleteListener = mp -> { - Logger.i(MODULE_NAME, "MediaPlayer.OnSeekCompleteListener"); - try { - mCurrentState = mMediaPlayer.isPlaying() ? V2XConst.STATE_PLAYING : V2XConst.STATE_PAUSED; - } catch (Exception e) { - mCurrentState = V2XConst.STATE_PLAYING; - e.printStackTrace(); - } - }; - - private OnErrorListener mErrorListener = new OnErrorListener() { - public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { - Logger.i(MODULE_NAME, "MediaPlayer.onError"); - mTargetState = mPrepareState = mCurrentState = V2XConst.STATE_ERROR; - if (mOnErrorListener != null) { - mOnErrorListener.onError(mMediaPlayer, framework_err, - impl_err); + int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call + if (seekToPosition != 0) { + seekTo(seekToPosition); } - release(false); - return true; - } - }; - - private MediaPlayer.OnInfoListener mInfoListener = new MediaPlayer.OnInfoListener() { - - @Override - public boolean onInfo(MediaPlayer mp, int what, int extra) { - Logger.i(MODULE_NAME, "MediaPlayer.OnInfoListener---what = " + what + ";extra = " + extra); - int messageId = 0; - if (what == MEDIA_INFO_VIDEO_NOT_SUPPORTED) { - messageId = R.string.VideoView_info_text_video_not_supported; - } else if (what == MEDIA_INFO_AUDIO_NOT_SUPPORTED) { - messageId = R.string.file_not_support; - } - if (messageId != 0) { - if (mOnInfoListener != null) { - mOnInfoListener.onInfo(mp, what, extra); + if (mVideoWidth != 0 && mVideoHeight != 0) { + //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight); +// getSurfaceTexture().setDefaultBufferSize(mVideoWidth, mVideoHeight);/*播放器大小等于获取的视频尺寸!!!!!!!!!!!!!!!!!*/ + // We won't get a "surface changed" callback if the surface is already the right size, 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(); } - return true; } - return false; } }; - private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { - public void onBufferingUpdate(MediaPlayer mp, int percent) { - Logger.i(MODULE_NAME, "MediaPlayer.OnBufferingUpdateListener"); - mCurrentBufferPercentage = percent; - } - }; + private OnCompletionListener mCompletionListener = + new 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 OnInfoListener mInfoListener = + new OnInfoListener() { + public boolean onInfo(MediaPlayer mp, int arg1, int arg2) { + if (mOnInfoListener != null) { + mOnInfoListener.onInfo(mp, arg1, arg2); + } + return true; + } + }; + + private OnErrorListener mErrorListener = + new 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 = getContext().getResources(); + int messageId; + + if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { + messageId = android.R.string.VideoView_error_text_invalid_progressive_playback; + } else { + messageId = android.R.string.VideoView_error_text_unknown; + } + + new AlertDialog.Builder(getContext()) + .setMessage(messageId) + .setPositiveButton(android.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. + * 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) { + 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. + * 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) { + 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. + * 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, TextureVideoView will inform + * the user of any errors. * * @param l The callback that will be run */ - public void setOnErrorListener(OnErrorListener l) { + public void setOnErrorListener(OnErrorListener l) + { mOnErrorListener = l; } - public void setOnInfoListener(MediaPlayer.OnInfoListener 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; } - SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() { + SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() + { @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - Logger.i(MODULE_NAME, "onSurfaceTextureAvailable"); + public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) { + boolean isValidState = (mTargetState == STATE_PLAYING); + boolean hasValidSize = (width > 0 && height > 0); + if (mMediaPlayer != null && isValidState && hasValidSize) { + if (mSeekWhenPrepared != 0) { + seekTo(mSeekWhenPrepared); + } + start(); + } + } + + @Override + public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) { mSurface = new Surface(surface); openVideo(); } @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - Logger.d(MODULE_NAME, "onSurfaceTextureSizeChanged mVideoWidth:" + " mVideoWidth:" + mVideoWidth + " mVideoHeight:" + mVideoHeight); - boolean isValidState = (mTargetState == V2XConst.STATE_PLAYING); - boolean hasValidSize = (mVideoWidth == width && mVideoHeight == height); - if (mMediaPlayer != null && isValidState && hasValidSize) { - if (mSeekWhenPrepared != 0) { - seekTo(mSeekWhenPrepared); - } - try { - Thread.sleep(100); - } catch (Exception e) { - e.printStackTrace(); - } - if (!isFocusLoss) start(); + public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) { + // after we return from this we can't use the surface any more + if (mSurface != null) { + mSurface.release(); + mSurface = null; } + if (mMediaController != null) mMediaController.hide(); + release(true); + return true; } - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - Logger.i(MODULE_NAME, "onSurfaceTextureDestroyed"); - mSurface = null; - release(false); - return false; - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - + public void onSurfaceTextureUpdated(final SurfaceTexture surface) { + // do nothing } }; @@ -382,131 +618,125 @@ public class TextureVideoView extends TextureView implements MediaPlayerControl * release the media player in any state */ private void release(boolean cleartargetstate) { - Logger.i(MODULE_NAME, "release ---cleartargetstate=" + cleartargetstate); - mCurrentState = V2XConst.STATE_IDLE; - - if (cleartargetstate) { - mTargetState = V2XConst.STATE_IDLE; - - new Thread(new Runnable() { - @Override - public void run() { - try { - if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { - try { - mMediaPlayer.stop(); - } catch (IllegalStateException e) { - try { - this.wait(1000); - mMediaPlayer.stop(); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } - } - - if (mMediaPlayer != null) { - mMediaPlayer.release();// release会做reset - } - mMediaPlayer = null; - } catch (IllegalStateException e) { - e.printStackTrace(); - } - } - }).start(); - } else { - if (mMediaPlayer != null) { - try { - mMediaPlayer.stop(); - mMediaPlayer.reset(); - mMediaPlayer.release(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } - } + if (mMediaPlayer != null) { + mMediaPlayer.reset(); + mMediaPlayer.release(); mMediaPlayer = null; + mCurrentState = STATE_IDLE; + if (cleartargetstate) { + mTargetState = STATE_IDLE; + } + if (mShouldRequestAudioFocus) { + AudioManager am = (AudioManager) getContext().getApplicationContext().getSystemService(Context.AUDIO_SERVICE); + am.abandonAudioFocus(null); + } } } - public boolean isPrepared() { - return mPrepareState == V2XConst.STATE_PREPARED - || isSuspendFromActivity; + @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(); + } + } + + @Override public void start() { - Logger.i(MODULE_NAME, "TextureVideoView---start"); - if (isFocusLoss) { - mTagetStateBackup = V2XConst.STATE_PLAYING; - return; - } - //Logger.i(MODULE_NAME, "mCurrentState = " + mCurrentState); if (isInPlaybackState()) { - Logger.i(MODULE_NAME, "MediaPlayer.start"); mMediaPlayer.start(); - mCurrentState = V2XConst.STATE_PLAYING; + mCurrentState = STATE_PLAYING; } - mTargetState = V2XConst.STATE_PLAYING; + mTargetState = STATE_PLAYING; } + @Override public void pause() { - Logger.i(MODULE_NAME, "TextureVideoView---pause"); - Logger.i(MODULE_NAME, "mCurrentState = " + mCurrentState); if (isInPlaybackState()) { if (mMediaPlayer.isPlaying()) { - Logger.i(MODULE_NAME, " MediaPlayer.pause"); mMediaPlayer.pause(); - mCurrentState = V2XConst.STATE_PAUSED; + mCurrentState = STATE_PAUSED; } } - mTagetStateBackup = mTargetState = V2XConst.STATE_PAUSED; + mTargetState = STATE_PAUSED; } public void suspend() { - Logger.i(MODULE_NAME, "TextureVideoView---suspend"); - if (!isSuspendFromActivity) { - isSuspendFromActivity = true; - if (mMediaPlayer != null) { - if (isInPlaybackState()) { - progressWhileSuspend = mMediaPlayer.getCurrentPosition(); - } - } - release(false); - } + release(false); } public void resume() { - Logger.i(MODULE_NAME, "TextureVideoView---resume"); - isSuspendFromActivity = false; openVideo(); - seekTo(progressWhileSuspend); - - if (mTagetStateBackup == V2XConst.STATE_IDLE) - mTagetStateBackup = mTargetState; - Logger.i(MODULE_NAME, "isFocusLoss = " + isFocusLoss); - Logger.i(MODULE_NAME, "isInPlaybackState() = " + isInPlaybackState()); - if (isFocusLoss) { - if (isInPlaybackState()) { - if (mMediaPlayer.isPlaying()) { - mMediaPlayer.pause(); - mCurrentState = V2XConst.STATE_PAUSED; - } - } - } } - // cache duration as mDuration for faster access + @Override public int getDuration() { if (isInPlaybackState()) { - if (mDuration > 0) { - return mDuration; - } - mDuration = mMediaPlayer.getDuration(); - return mDuration; + return mMediaPlayer.getDuration(); } - mDuration = -1; - return mDuration; + + return -1; } + @Override public int getCurrentPosition() { if (isInPlaybackState()) { return mMediaPlayer.getCurrentPosition(); @@ -514,25 +744,22 @@ public class TextureVideoView extends TextureView implements MediaPlayerControl return 0; } + @Override public void seekTo(int msec) { - Logger.i(MODULE_NAME, "TextureVideoView---seekTo---msec = " + msec); - if (isInPlaybackState() && mCurrentState != V2XConst.STATE_SEEKING - && msec > 0 && msec < getDuration()) { - mCurrentState = V2XConst.STATE_SEEKING; - - Logger.i(MODULE_NAME, "MediaPlayer.seekTo(msec) = " + msec); + if (isInPlaybackState()) { mMediaPlayer.seekTo(msec); mSeekWhenPrepared = 0; } else { mSeekWhenPrepared = msec; - progressWhileSuspend = msec; } } + @Override public boolean isPlaying() { return isInPlaybackState() && mMediaPlayer.isPlaying(); } + @Override public int getBufferPercentage() { if (mMediaPlayer != null) { return mCurrentBufferPercentage; @@ -541,50 +768,58 @@ public class TextureVideoView extends TextureView implements MediaPlayerControl } private boolean isInPlaybackState() { - return (mMediaPlayer != null && mCurrentState != V2XConst.STATE_ERROR - && mCurrentState != V2XConst.STATE_IDLE && mCurrentState != V2XConst.STATE_PREPARING); + return (mMediaPlayer != null && + mCurrentState != STATE_ERROR && + mCurrentState != STATE_IDLE && + mCurrentState != STATE_PREPARING); } @Override public boolean canPause() { - return false; + return mCanPause; } @Override public boolean canSeekBackward() { - return false; + return mCanSeekBack; } @Override public boolean canSeekForward() { - return false; + return mCanSeekForward; } - public String getVideoPath() { - if (mUri != null) { - return mUri.getPath(); - } - return null; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = getDefaultSize(mVideoWidth, widthMeasureSpec); - int height = getDefaultSize(mVideoHeight, heightMeasureSpec); - setMeasuredDimension(width, height); - } - - public boolean isPause() { - return mTargetState == V2XConst.STATE_PAUSED; - } - - @Override public int getAudioSessionId() { - return 0; + if (mAudioSession == 0) { + MediaPlayer foo = new MediaPlayer(); + mAudioSession = foo.getAudioSessionId(); + foo.release(); + } + return mAudioSession; } - public int getCurrentState() { - Logger.i(MODULE_NAME, "mCurrentState == " + mCurrentState); - return mCurrentState; + /** + * Sets the request audio focus flag. If enabled, {@link TextureVideoView} will request + * audio focus when opening a video by calling {@link AudioManager}. This flag + * should be set before calling {@link TextureVideoView#setVideoPath(String)} or + * {@link TextureVideoView#setVideoURI(Uri)}. By default, {@link TextureVideoView} will + * request audio focus. + * + * @param shouldRequestAudioFocus If {@code true}, {@link TextureVideoView} will request + * audio focus before opening a video, else audio focus is not requested + */ + public void setShouldRequestAudioFocus(boolean shouldRequestAudioFocus) { + mShouldRequestAudioFocus = shouldRequestAudioFocus; } + + /** + * Returns the current state of the audio focus request flag. + * + * @return {@code true}, if {@link TextureVideoView} will request + * audio focus before opening a video, else {@code false} + */ + public boolean shouldRequestAudioFocus() { + return mShouldRequestAudioFocus; + } + }