diff --git a/README.md b/README.md index ceafd25..8b8db7e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ VideoListPlayer实现了在列表控件(ListView, RecyclerView)中加载并 #Changelogs +**v.14** + 1.支持更多类型的scaleType,详见 [Android-ScalableVideoView](https://github.com/yqritc/Android-ScalableVideoView) + 2.加入 `getCurrentPosition()` 和 `getDuration()` 接口 + **v1.3** fix在多类型列表元素中出现视频无法正常播放的bug diff --git a/app/src/main/java/com/waynell/videolist/demo/holder/VideoViewHolder.java b/app/src/main/java/com/waynell/videolist/demo/holder/VideoViewHolder.java index aeb9fff..3bf287e 100644 --- a/app/src/main/java/com/waynell/videolist/demo/holder/VideoViewHolder.java +++ b/app/src/main/java/com/waynell/videolist/demo/holder/VideoViewHolder.java @@ -82,6 +82,8 @@ void cliclVideoView() { videoView.mute(); Toast.makeText(itemView.getContext(), "turn off video sound", Toast.LENGTH_SHORT).show(); } + + Toast.makeText(itemView.getContext(), "durration: " + videoView.getDuration() + " pos: " + videoView.getCurrentPosition(), Toast.LENGTH_LONG).show(); } private void reset() { diff --git a/app/src/main/res/layout/video_list_item.xml b/app/src/main/res/layout/video_list_item.xml index 9d5b0a8..f187a6b 100644 --- a/app/src/main/res/layout/video_list_item.xml +++ b/app/src/main/res/layout/video_list_item.xml @@ -4,6 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="300dp" + android:background="#000000" android:orientation="vertical"> + android:layout_height="300dp" + app:scaleType="fitCenter"/> viewHeight) { // device in landscape - scaleX = (viewHeight * contentWidth) / (viewWidth * contentHeight); - } else { - scaleY = (viewWidth * contentHeight) / (viewHeight * contentWidth); - } - break; - case BOTTOM: - case CENTER_CROP: - case TOP: - if (contentWidth > viewWidth && contentHeight > viewHeight) { - scaleX = contentWidth / viewWidth; - scaleY = contentHeight / viewHeight; - } else if (contentWidth < viewWidth && contentHeight < viewHeight) { - scaleY = viewWidth / contentWidth; - scaleX = viewHeight / contentHeight; - } else if (viewWidth > contentWidth) { - scaleY = (viewWidth / contentWidth) / (viewHeight / contentHeight); - } else if (viewHeight > contentHeight) { - scaleX = (viewHeight / contentHeight) / (viewWidth / contentWidth); - } - break; - } - - // Calculate pivot points, in our case crop from center - float pivotPointX; - float pivotPointY; - - switch (mScaleType) { - case TOP: - pivotPointX = 0; - pivotPointY = 0; - break; - case BOTTOM: - pivotPointX = viewWidth; - pivotPointY = viewHeight; - break; - case CENTER_CROP: - pivotPointX = viewWidth / 2; - pivotPointY = viewHeight / 2; - break; - case FILL: - pivotPointX = mPivotPointX; - pivotPointY = mPivotPointY; - break; - default: - throw new IllegalStateException("pivotPointX, pivotPointY for ScaleType " + mScaleType + " are not defined"); - } - - float fitCoef = 1; - switch (mScaleType) { - case FILL: - break; - case BOTTOM: - case CENTER_CROP: - case TOP: - if (mContentHeight > mContentWidth) { //Portrait video - fitCoef = viewWidth / (viewWidth * scaleX); - } else { //Landscape video - fitCoef = viewHeight / (viewHeight * scaleY); - } - break; - } - - mContentScaleX = fitCoef * scaleX; - mContentScaleY = fitCoef * scaleY; - - mPivotPointX = pivotPointX; - mPivotPointY = pivotPointY; - - updateMatrixScaleRotate(); - } - - private void updateMatrixScaleRotate() { - mTransformMatrix.reset(); - mTransformMatrix.setScale(mContentScaleX * mContentScaleMultiplier, mContentScaleY * mContentScaleMultiplier, mPivotPointX, mPivotPointY); - mTransformMatrix.postRotate(mContentRotation, mPivotPointX, mPivotPointY); - setTransform(mTransformMatrix); - } - - private void updateMatrixTranslate() { - float scaleX = mContentScaleX * mContentScaleMultiplier; - float scaleY = mContentScaleY * mContentScaleMultiplier; - - mTransformMatrix.reset(); - mTransformMatrix.setScale(scaleX, scaleY, mPivotPointX, mPivotPointY); - mTransformMatrix.postTranslate(mContentX, mContentY); - setTransform(mTransformMatrix); - } - - @Override - public void setRotation(float degrees) { - mContentRotation = degrees; - - updateMatrixScaleRotate(); - } - - @Override - public float getRotation() { - return mContentRotation; - } - - @Override - public void setPivotX(float pivotX) { - mPivotPointX = pivotX; - } - - @Override - public void setPivotY(float pivotY) { - mPivotPointY = pivotY; - } - - @Override - public float getPivotX() { - return mPivotPointX; - } - - @Override - public float getPivotY() { - return mPivotPointY; - } - - public float getContentAspectRatio() { - return mContentWidth != null && mContentHeight != null - ? (float) mContentWidth / (float) mContentHeight - : 0; - } - - /** - * Use it to animate TextureView content x position - * @param x - */ - public final void setContentX(float x) { - mContentX = (int) x - (getMeasuredWidth() - getScaledContentWidth()) / 2; - updateMatrixTranslate(); - } - - /** - * Use it to animate TextureView content x position - * @param y - */ - public final void setContentY(float y) { - mContentY = (int) y - (getMeasuredHeight() - getScaledContentHeight()) / 2; - updateMatrixTranslate(); - } - - protected final float getContentX() { - return mContentX; - } - - protected final float getContentY() { - return mContentY; - } - - /** - * Use it to set content of a TextureView in the center of TextureView - */ - public void centralizeContent() { - int measuredWidth = getMeasuredWidth(); - int measuredHeight = getMeasuredHeight(); - int scaledContentWidth = getScaledContentWidth(); - int scaledContentHeight = getScaledContentHeight(); - - mContentX = 0; - mContentY = 0; - - updateMatrixScaleRotate(); - } - - public Integer getScaledContentWidth() { - return (int) (mContentScaleX * mContentScaleMultiplier * getMeasuredWidth()); - } - - public Integer getScaledContentHeight() { - return (int) (mContentScaleY * mContentScaleMultiplier * getMeasuredHeight()); - } - - public float getContentScale() { - return mContentScaleMultiplier; - } - - public void setContentScale(float contentScale) { - mContentScaleMultiplier = contentScale; - updateMatrixScaleRotate(); - } - - protected final void setContentHeight(int height) { - mContentHeight = height; - } - - protected final Integer getContentHeight() { - return mContentHeight; - } - - protected final void setContentWidth(int width) { - mContentWidth = width; - } - - protected final Integer getContentWidth() { - return mContentWidth; - } - -} diff --git a/video-list-player/src/main/java/com/waynell/videolist/widget/ScaleManager.java b/video-list-player/src/main/java/com/waynell/videolist/widget/ScaleManager.java new file mode 100644 index 0000000..c665a4f --- /dev/null +++ b/video-list-player/src/main/java/com/waynell/videolist/widget/ScaleManager.java @@ -0,0 +1,192 @@ +package com.waynell.videolist.widget; + +import android.graphics.Matrix; + +/** + * Create On 05/01/2017 + * + * @author Wayne + */ +class ScaleManager { + private Size mViewSize; + private Size mVideoSize; + + ScaleManager(Size viewSize, Size videoSize) { + mViewSize = viewSize; + mVideoSize = videoSize; + } + + Matrix getScaleMatrix(ScaleType scaleType) { + switch (scaleType) { + case NONE: + return getNoScale(); + + case FIT_XY: + return fitXY(); + case FIT_CENTER: + return fitCenter(); + case FIT_START: + return fitStart(); + case FIT_END: + return fitEnd(); + + case LEFT_TOP: + return getOriginalScale(PivotPoint.LEFT_TOP); + case LEFT_CENTER: + return getOriginalScale(PivotPoint.LEFT_CENTER); + case LEFT_BOTTOM: + return getOriginalScale(PivotPoint.LEFT_BOTTOM); + case CENTER_TOP: + return getOriginalScale(PivotPoint.CENTER_TOP); + case CENTER: + return getOriginalScale(PivotPoint.CENTER); + case CENTER_BOTTOM: + return getOriginalScale(PivotPoint.CENTER_BOTTOM); + case RIGHT_TOP: + return getOriginalScale(PivotPoint.RIGHT_TOP); + case RIGHT_CENTER: + return getOriginalScale(PivotPoint.RIGHT_CENTER); + case RIGHT_BOTTOM: + return getOriginalScale(PivotPoint.RIGHT_BOTTOM); + + case LEFT_TOP_CROP: + return getCropScale(PivotPoint.LEFT_TOP); + case LEFT_CENTER_CROP: + return getCropScale(PivotPoint.LEFT_CENTER); + case LEFT_BOTTOM_CROP: + return getCropScale(PivotPoint.LEFT_BOTTOM); + case CENTER_TOP_CROP: + return getCropScale(PivotPoint.CENTER_TOP); + case CENTER_CROP: + return getCropScale(PivotPoint.CENTER); + case CENTER_BOTTOM_CROP: + return getCropScale(PivotPoint.CENTER_BOTTOM); + case RIGHT_TOP_CROP: + return getCropScale(PivotPoint.RIGHT_TOP); + case RIGHT_CENTER_CROP: + return getCropScale(PivotPoint.RIGHT_CENTER); + case RIGHT_BOTTOM_CROP: + return getCropScale(PivotPoint.RIGHT_BOTTOM); + + case START_INSIDE: + return startInside(); + case CENTER_INSIDE: + return centerInside(); + case END_INSIDE: + return endInside(); + + default: + return null; + } + } + + private Matrix getMatrix(float sx, float sy, float px, float py) { + Matrix matrix = new Matrix(); + matrix.setScale(sx, sy, px, py); + return matrix; + } + + private Matrix getMatrix(float sx, float sy, PivotPoint pivotPoint) { + switch (pivotPoint) { + case LEFT_TOP: + return getMatrix(sx, sy, 0, 0); + case LEFT_CENTER: + return getMatrix(sx, sy, 0, mViewSize.getHeight() / 2f); + case LEFT_BOTTOM: + return getMatrix(sx, sy, 0, mViewSize.getHeight()); + case CENTER_TOP: + return getMatrix(sx, sy, mViewSize.getWidth() / 2f, 0); + case CENTER: + return getMatrix(sx, sy, mViewSize.getWidth() / 2f, mViewSize.getHeight() / 2f); + case CENTER_BOTTOM: + return getMatrix(sx, sy, mViewSize.getWidth() / 2f, mViewSize.getHeight()); + case RIGHT_TOP: + return getMatrix(sx, sy, mViewSize.getWidth(), 0); + case RIGHT_CENTER: + return getMatrix(sx, sy, mViewSize.getWidth(), mViewSize.getHeight() / 2f); + case RIGHT_BOTTOM: + return getMatrix(sx, sy, mViewSize.getWidth(), mViewSize.getHeight()); + default: + throw new IllegalArgumentException("Illegal PivotPoint"); + } + } + + private Matrix getNoScale() { + float sx = mVideoSize.getWidth() / (float) mViewSize.getWidth(); + float sy = mVideoSize.getHeight() / (float) mViewSize.getHeight(); + return getMatrix(sx, sy, PivotPoint.LEFT_TOP); + } + + private Matrix getFitScale(PivotPoint pivotPoint) { + float sx = (float) mViewSize.getWidth() / mVideoSize.getWidth(); + float sy = (float) mViewSize.getHeight() / mVideoSize.getHeight(); + float minScale = Math.min(sx, sy); + sx = minScale / sx; + sy = minScale / sy; + return getMatrix(sx, sy, pivotPoint); + } + + private Matrix fitXY() { + return getMatrix(1, 1, PivotPoint.LEFT_TOP); + } + + private Matrix fitStart() { + return getFitScale(PivotPoint.LEFT_TOP); + } + + private Matrix fitCenter() { + return getFitScale(PivotPoint.CENTER); + } + + private Matrix fitEnd() { + return getFitScale(PivotPoint.RIGHT_BOTTOM); + } + + private Matrix getOriginalScale(PivotPoint pivotPoint) { + float sx = mVideoSize.getWidth() / (float) mViewSize.getWidth(); + float sy = mVideoSize.getHeight() / (float) mViewSize.getHeight(); + return getMatrix(sx, sy, pivotPoint); + } + + private Matrix getCropScale(PivotPoint pivotPoint) { + float sx = (float) mViewSize.getWidth() / mVideoSize.getWidth(); + float sy = (float) mViewSize.getHeight() / mVideoSize.getHeight(); + float maxScale = Math.max(sx, sy); + sx = maxScale / sx; + sy = maxScale / sy; + return getMatrix(sx, sy, pivotPoint); + } + + private Matrix startInside() { + if (mVideoSize.getHeight() <= mViewSize.getWidth() + && mVideoSize.getHeight() <= mViewSize.getHeight()) { + // video is smaller than view size + return getOriginalScale(PivotPoint.LEFT_TOP); + } else { + // either of width or height of the video is larger than view size + return fitStart(); + } + } + + private Matrix centerInside() { + if (mVideoSize.getHeight() <= mViewSize.getWidth() + && mVideoSize.getHeight() <= mViewSize.getHeight()) { + // video is smaller than view size + return getOriginalScale(PivotPoint.CENTER); + } else { + // either of width or height of the video is larger than view size + return fitCenter(); + } + } + + private Matrix endInside() { + if (mVideoSize.getHeight() <= mViewSize.getWidth() + && mVideoSize.getHeight() <= mViewSize.getHeight()) { + // video is smaller than view size + return getOriginalScale(PivotPoint.RIGHT_BOTTOM); + } else { + // either of width or height of the video is larger than view size + return fitEnd(); + } + } +} diff --git a/video-list-player/src/main/java/com/waynell/videolist/widget/ScaleType.java b/video-list-player/src/main/java/com/waynell/videolist/widget/ScaleType.java new file mode 100644 index 0000000..65c0ca2 --- /dev/null +++ b/video-list-player/src/main/java/com/waynell/videolist/widget/ScaleType.java @@ -0,0 +1,38 @@ +package com.waynell.videolist.widget; + +/** + * Create On 05/01/2017 + * @author Wayne + */ +public enum ScaleType { + NONE, + + FIT_XY, + FIT_START, + FIT_CENTER, + FIT_END, + + LEFT_TOP, + LEFT_CENTER, + LEFT_BOTTOM, + CENTER_TOP, + CENTER, + CENTER_BOTTOM, + RIGHT_TOP, + RIGHT_CENTER, + RIGHT_BOTTOM, + + LEFT_TOP_CROP, + LEFT_CENTER_CROP, + LEFT_BOTTOM_CROP, + CENTER_TOP_CROP, + CENTER_CROP, + CENTER_BOTTOM_CROP, + RIGHT_TOP_CROP, + RIGHT_CENTER_CROP, + RIGHT_BOTTOM_CROP, + + START_INSIDE, + CENTER_INSIDE, + END_INSIDE +} diff --git a/video-list-player/src/main/java/com/waynell/videolist/widget/Size.java b/video-list-player/src/main/java/com/waynell/videolist/widget/Size.java new file mode 100644 index 0000000..2de7c3a --- /dev/null +++ b/video-list-player/src/main/java/com/waynell/videolist/widget/Size.java @@ -0,0 +1,23 @@ +package com.waynell.videolist.widget; + +/** + * Create On 05/01/2017 + * @author Wayne + */ +public class Size { + private int mWidth; + private int mHeight; + + public Size(int width, int height) { + mWidth = width; + mHeight = height; + } + + public int getWidth() { + return mWidth; + } + + public int getHeight() { + return mHeight; + } +} diff --git a/video-list-player/src/main/java/com/waynell/videolist/widget/TextureVideoView.java b/video-list-player/src/main/java/com/waynell/videolist/widget/TextureVideoView.java index 166faaa..44f74db 100644 --- a/video-list-player/src/main/java/com/waynell/videolist/widget/TextureVideoView.java +++ b/video-list-player/src/main/java/com/waynell/videolist/widget/TextureVideoView.java @@ -3,6 +3,8 @@ import android.annotation.TargetApi; import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Matrix; import android.graphics.SurfaceTexture; import android.media.AudioManager; import android.media.MediaExtractor; @@ -12,6 +14,7 @@ import android.os.Build; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; import android.os.Message; import android.util.AttributeSet; import android.util.Log; @@ -19,6 +22,7 @@ import android.view.TextureView; import com.waynell.videolist.BuildConfig; +import com.waynell.videolist.R; import java.io.IOException; @@ -29,7 +33,7 @@ * @author Wayne */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) -public class TextureVideoView extends ScalableTextureView +public class TextureVideoView extends TextureView implements TextureView.SurfaceTextureListener, Handler.Callback, MediaPlayer.OnPreparedListener, @@ -70,6 +74,8 @@ public class TextureVideoView extends ScalableTextureView private boolean mSoundMute; private boolean mHasAudio; + private ScaleType mScaleType = ScaleType.NONE; + private static final HandlerThread sThread = new HandlerThread("VideoPlayThread"); static { sThread.start(); @@ -86,17 +92,27 @@ public interface MediaPlayerCallback { } public TextureVideoView(Context context) { - super(context); - init(); + this(context, null); } public TextureVideoView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); + this(context, attrs, 0); } public TextureVideoView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + if (attrs == null) { + return; + } + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.scaleStyle, 0, 0); + if (a == null) { + return; + } + + int scaleType = a.getInt(R.styleable.scaleStyle_scaleType, ScaleType.NONE.ordinal()); + a.recycle(); + mScaleType = ScaleType.values()[scaleType]; init(); } @@ -107,6 +123,70 @@ public void setMediaPlayerCallback(MediaPlayerCallback mediaPlayerCallback) { } } + public int getCurrentPosition() { + if (isInPlaybackState()) { + return mMediaPlayer.getCurrentPosition(); + } + return 0; + } + + public int getDuration() { + if (isInPlaybackState()) { + return mMediaPlayer.getDuration(); + } + + return -1; + } + + public int getVideoHeight() { + if (mMediaPlayer != null) { + return mMediaPlayer.getVideoHeight(); + } + return 0; + } + + public int getVideoWidth() { + if (mMediaPlayer != null) { + return mMediaPlayer.getVideoWidth(); + } + return 0; + } + + public void setScaleType(ScaleType scaleType) { + mScaleType = scaleType; + scaleVideoSize(getVideoWidth(), getVideoHeight()); + } + + public ScaleType getScaleType() { + return mScaleType; + } + + private void scaleVideoSize(int videoWidth, int videoHeight) { + if (videoWidth == 0 || videoHeight == 0) { + return; + } + + Size viewSize = new Size(getWidth(), getHeight()); + Size videoSize = new Size(videoWidth, videoHeight); + ScaleManager scaleManager = new ScaleManager(viewSize, videoSize); + final Matrix matrix = scaleManager.getScaleMatrix(mScaleType); + if (matrix == null) { + return; + } + + if(Looper.myLooper() == Looper.getMainLooper()) { + setTransform(matrix); + } + else { + mHandler.postAtFrontOfQueue(new Runnable() { + @Override + public void run() { + setTransform(matrix); + } + }); + } + } + @Override public boolean handleMessage(Message msg) { synchronized (TextureVideoView.class) { @@ -140,12 +220,14 @@ public boolean handleMessage(Message msg) { } private void init() { - mContext = getContext(); - mCurrentState = STATE_IDLE; - mTargetState = STATE_IDLE; - mHandler = new Handler(); - mVideoHandler = new Handler(sThread.getLooper(), this); - setSurfaceTextureListener(this); + if(!isInEditMode()) { + mContext = getContext(); + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + mHandler = new Handler(); + mVideoHandler = new Handler(sThread.getLooper(), this); + setSurfaceTextureListener(this); + } } @@ -215,21 +297,7 @@ private void openVideo() { } } - } catch (IOException ex) { - if(SHOW_LOGS) Log.w(TAG, "Unable to open content: " + mUri, ex); - mCurrentState = STATE_ERROR; - mTargetState = STATE_ERROR; - if (mMediaPlayerCallback != null) { - mHandler.post(new Runnable() { - @Override - public void run() { - if(mMediaPlayerCallback != null) { - mMediaPlayerCallback.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); - } - } - }); - } - } catch (IllegalArgumentException ex) { + } catch (IOException | IllegalArgumentException ex) { if(SHOW_LOGS) Log.w(TAG, "Unable to open content: " + mUri, ex); mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; @@ -417,6 +485,7 @@ public void run() { @Override public void onVideoSizeChanged(final MediaPlayer mp, final int width, final int height) { + scaleVideoSize(width, height); if (mMediaPlayerCallback != null) { mHandler.post(new Runnable() { @Override diff --git a/video-list-player/src/main/res/values/attrs.xml b/video-list-player/src/main/res/values/attrs.xml new file mode 100644 index 0000000..c2cee7f --- /dev/null +++ b/video-list-player/src/main/res/values/attrs.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file