web-dev-qa-db-ja.com

親の高さに合わせてアスペクト比を維持するVideoView

次のように設定されたVideoViewがあります。

<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>

しかし、VideoViewは親コンテナの幅と一致し、ロードされたムービーのアスペクト比に従って高さが設定されます。
反対のことをしたいのですが、アスペクト比をそのままに保ちながらVideoViewを親の高さに合わせたいので、ビデオは両側でクリップされます。

VideoViewを拡大して親を満たすようにしましたが、アスペクト比は維持されません。

もう1つのことは、MediaControllerをVideoViewに次のように追加することです。

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();
    }
});

これはうまく機能し、コントローラーは常にオンになりますが、ビデオの配置場所を計算するときにコントローラーの高さは考慮されません(垂直方向に中央に配置されるため)。

私の2つの質問は次のとおりです。

  1. VideoViewを親の高さに合わせながら、アスペクト比を維持するにはどうすればよいですか?
  2. VideoViewにコントローラーの高さを考慮させるにはどうすればよいですか?

ありがとう。

27
Nitzan Tomer

組み込みのビデオビューから拡張する必要があります。

ビデオビューが表示される前にsetVideoSizeを呼び出すと、ビデオから抽出されたサムネイルからビデオサイズを取得できます。

そのため、ビデオビューのonMeasureが呼び出されると、mVideoWidthmVideoHeightの両方が> 0になります。

コントローラの高さを考慮したい場合は、onMeasureメソッドで自分で行うことができます。

希望が助けます。

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);
        }
}
22
ax003d

この問題はレイアウトで解決しました。コーナーに固定されている場合は正常に機能したようですが、ビデオが歪んでいます。テストするために、相対的なレイアウトの背景を#990000に変更して、赤い突き抜けを確認しました。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:id="@+id/relative_parent"
    Android:background="#000000">
    <VideoView
        Android:layout_alignParentLeft="true"
        Android:layout_alignParentRight="true"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:layout_centerInParent="true"
        Android:focusable="false"
        Android:focusableInTouchMode="false"
        Android:id="@+id/videoView" />
</RelativeLayout>
10
Mevdev

質問1に関して、MediaPlayerのスケーリングモードの使用の可能性について誰も言及していないことに驚いています。 https://developer.Android.com/reference/Android/media/MediaPlayer.html#setVideoScalingMode(int)

2つのモードがあります。両方とも常に表示領域を埋めます。縦横比を維持しながらスペースを埋めて長辺をトリミングするには、2番目のモードVIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPINGに切り替える必要があります。これで問題の一部が解決します。他の部分は、他の回答のいくつかが示すように、VideoViewの測定動作を変更することです。これは私がやった方法であり、ほとんどが怠ofであり、他の人が使用するメタデータAPIに精通していないため、このメソッドまたはビューのサイズを修正する他のメソッドのいずれかを使用することを歓迎します。ブランケットキャッチは、mMediaPlayerが存在する前に呼び出されるときに安全性を確保します。何回も呼び出される可能性があり、フィールド名が変更された場合に古い動作にフォールバックするためです。

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)
        }
    }
}

そのため、レイアウトでこのクラスを使用すると、いつでもメディアプレーヤーのスケーリングモードを変更できます。といった:

        video.setOnPreparedListener { mp: MediaPlayer ->
            mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)
            mp.isLooping = true
            mp.setScreenOnWhilePlaying(false)
        }
        video.start()
7
androidguy
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);
    }
}
5
Ronny Shibley

迅速かつ効率的な修正:

VideoViewから拡張するカスタムビューを作成する必要はありません。 _Android:layout_width_に十分な大きさの値を設定するだけです。これにより、ビデオビューのwidthSpecModeが_View.MeasureSpec.AT_MOST_に設定され、VideoViewのonMeasure()メソッドが比率を維持しながら幅を自動調整します。

_<VideoView
     Android:id="@+id/video"
     Android:layout_width="2000dp"
     Android:layout_height="match_parent" />
_
2
Julian Paul

質問が答えではなく私の問題に初めて答えました!!私の問題は、フルスクリーンのビデオの下に空白があることでした。 layout_heightmatch_parentに設定していました。解決策は、wrap_contentに設定し、親に黒い背景を与えることでした。それは、VideoViewを親の垂直方向の中央に配置することです。

私はこれをコメントとして書いたが、誰かが私と同じ問題を抱えているかもしれないと思ったので、ここでも答えとして。

1

私のビデオは常に1000 * 1000形式でしたが、アスペクト比を知っている人のための簡単なソリューションを作成しましたが、多くのソリューションを試しました。まず、次のようなRelativeLayoutでVideoViewを作成します。

<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
      Android:id="@+id/video_holder"
      Android:layout_width="wrap_content"
      Android:layout_height="fill_parent"
      Android:clipToPadding="false">

      <VideoView  
          Android:id="@+id/videoView"
          Android:layout_width="match_parent"
          Android:layout_height="match_parent"
          Android:layout_alignParentBottom="true"
          Android:layout_alignParentEnd="true"
          Android:layout_alignParentStart="true"
          Android:layout_alignParentTop="true" />
</RelativeLayout>

次に、ビデオを読み込む前に、プログラムで次のように高さを変更します。

    int i = videoView.getHeight() > videoView.getWidth() ? videoView.getHeight() : videoView.getWidth();
    video_holder.setLayoutParams(new ConstraintLayout.LayoutParams(i, i));

もちろんこれは1:1のアスペクト比でのみ動作しますが、アスペクト比を使用して高さまたは幅を変更することができます。

0
jobbert

誰かがそれを必要とする場合のために、KotlinでのJobbertの答え:

val max = if (videoView.height > videoView.width) videoView.height else videoView.width
videoView.layoutParams = ConstraintLayout.LayoutParams(max, max)
0