VideoView to match parent height and keep aspect r

2019-01-06 16:18发布

I have a VideoView which is set up like this:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:id="@+id/player" 
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">

    <VideoView
        android:id="@+id/video"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:layout_centerVertical="true" />

    <ProgressBar
        android:id="@+id/loader"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />

</RelativeLayout>

But the VideoView matches the width of the parent container, and then the height is set according to the aspect ratio of the loaded movie.
I would like to do just the opposite, I want the VideoView to match the height of the parent while keeping the aspect ratio intact, the video will be clipped on the sides.

I managed to stretch the VideoView to fill the parent but then the aspect ratio is not kept.

Another thing is, I'm adding MediaController to the VideoView like this:

MediaController controllers = new MediaController(this) {
    @Override
    public void hide() {
        if (state != State.Hidden) {
            this.show();
        }
        else {
            super.hide();
        }
    }
};
controllers.setAnchorView(videoView);
videoView.setMediaController(controllers);

videoView.setOnPreparedListener(new OnPreparedListener() {
    @Override
    public void onPrepared(MediaPlayer mediaPlayer) {
        controllers.show();
    }
});

This works great, and the controllers always stay on, but the height of the controllers is not being taken into account when calculating where to place the video (since it's vertically centered).

My two questions then are:

  1. How do I make the VideoView match the height of the parent yet keep the aspect ratio?
  2. How do I make the VideoView take into account the height of it's controllers?

Thanks.

8条回答
我欲成王,谁敢阻挡
2楼-- · 2019-01-06 16:55
public class MyVideoView extends VideoView {
    private int mVideoWidth;
    private int mVideoHeight;

    public MyVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public MyVideoView(Context context) {
        super(context);
    }


    @Override
    public void setVideoURI(Uri uri) {
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        retriever.setDataSource(this.getContext(), uri);
        mVideoWidth = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
        mVideoHeight = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
        super.setVideoURI(uri);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Log.i("@@@", "onMeasure");
        int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
        int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
        if (mVideoWidth > 0 && mVideoHeight > 0) {
            if (mVideoWidth * height > width * mVideoHeight) {
                // Log.i("@@@", "image too tall, correcting");
                height = width * mVideoHeight / mVideoWidth;
            } else if (mVideoWidth * height < width * mVideoHeight) {
                // Log.i("@@@", "image too wide, correcting");
                width = height * mVideoWidth / mVideoHeight;
            } else {
                // Log.i("@@@", "aspect ratio is correct: " +
                // width+"/"+height+"="+
                // mVideoWidth+"/"+mVideoHeight);
            }
        }
        // Log.i("@@@", "setting size: " + width + 'x' + height);
        setMeasuredDimension(width, height);
    }
}
查看更多
beautiful°
3楼-- · 2019-01-06 16:57

Regarding question 1, I am surprised no one has mentioned the possible use of the MediaPlayer's scaling mode. https://developer.android.com/reference/android/media/MediaPlayer.html#setVideoScalingMode(int)

It has 2 modes. Both of them always fill the view area. To get it to fill the space while preserving the aspect ratio, thus cropping the long side, you need to switch to the second mode, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING. That solves one part of the problem. The other part is to change VideoView's measuring behavior, just as some of the other answers demonstrate. This is the way I did it, mostly out of laziness and not familiar with the metadata API's that the others use, you are welcome to use this method or one of the other methods to fix the size of the view. The blanket catch ensures safety when this is called before mMediaPlayer exists, as it may be called many times, and also falls back to old behavior should the field name ever change.

class FixedSizeVideoView : VideoView {
    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)

    // rather than shrink down to fit, stay at the size requested by layout params. Let the scaling mode
    // of the media player shine through. If the scaling mode on the media player is set to the one
    // with cropping, you can make a player similar to AVLayerVideoGravityResizeAspectFill on iOS
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        try {
            val mpField = VideoView::class.java.getDeclaredField("mMediaPlayer")
            mpField.isAccessible = true
            val mediaPlayer: MediaPlayer = mpField.get(this) as MediaPlayer

            val width = View.getDefaultSize(mediaPlayer.videoWidth, widthMeasureSpec)
            val height = View.getDefaultSize(mediaPlayer.videoHeight, heightMeasureSpec)
            setMeasuredDimension(width, height)
        }
        catch (ex: Exception) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        }
    }
}

So using this class in the layout, you just change the scaling mode on the media Player wherever you have a chance. Such as:

        video.setOnPreparedListener { mp: MediaPlayer ->
            mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)
            mp.isLooping = true
            mp.setScreenOnWhilePlaying(false)
        }
        video.start()
查看更多
唯我独甜
4楼-- · 2019-01-06 17:00

Quick and efficient fix:

No need to create a custom view extending from VideoView. Just set a value big enough to android:layout_width. This will set the widthSpecMode of the video view to View.MeasureSpec.AT_MOST and then the onMeasure() method of VideoView will auto-adjust its width keeping the ratio.

<VideoView
     android:id="@+id/video"
     android:layout_width="2000dp"
     android:layout_height="match_parent" />
查看更多
Juvenile、少年°
5楼-- · 2019-01-06 17:10

You should extends from the built-in video view.

Call setVideoSize before video view is shown, you can get video size from thumbnail extracted from video.

So that, when video view's onMeasure is called, both mVideoWidth & mVideoHeight are > 0.

If you want to account the height of controllers, you can do it yourself in the onMeasure method.

Hope will help.

public class MyVideoView extends VideoView {

        private int mVideoWidth;
        private int mVideoHeight;

        public MyVideoView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

        public MyVideoView(Context context) {
            super(context);
        }

        public void setVideoSize(int width, int height) {
            mVideoWidth = width;
            mVideoHeight = height;
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // Log.i("@@@", "onMeasure");
            int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
            int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
            if (mVideoWidth > 0 && mVideoHeight > 0) {
                if (mVideoWidth * height > width * mVideoHeight) {
                    // Log.i("@@@", "image too tall, correcting");
                    height = width * mVideoHeight / mVideoWidth;
                } else if (mVideoWidth * height < width * mVideoHeight) {
                    // Log.i("@@@", "image too wide, correcting");
                    width = height * mVideoWidth / mVideoHeight;
                } else {
                    // Log.i("@@@", "aspect ratio is correct: " +
                    // width+"/"+height+"="+
                    // mVideoWidth+"/"+mVideoHeight);
                }
            }
            // Log.i("@@@", "setting size: " + width + 'x' + height);
            setMeasuredDimension(width, height);
        }
}
查看更多
啃猪蹄的小仙女
6楼-- · 2019-01-06 17:14

I spend time on searching this on the internet I didn't find any better solution that can apply to the default video view. So I searched for libraries which will solve my requirement finally I found one 'FullscreenVideoView' (https://github.com/rtoshiro/FullscreenVideoView) this solved my requirement

查看更多
对你真心纯属浪费
7楼-- · 2019-01-06 17:15

Jobbert's answer in Kotlin, in case anyone needs it:

val max = if (videoView.height > videoView.width) videoView.height else videoView.width
videoView.layoutParams = ConstraintLayout.LayoutParams(max, max)
查看更多
登录 后发表回答