Android VideoView изменение ориентации с буферизованным видео

Я пытаюсь повторить функциональность последнего приложения YouTube на Android Marketplace. При просмотре видео есть две отдельные компоновки: одна в портретной ориентации, которая предоставляет дополнительную информацию, и другая в альбомной ориентации, которая обеспечивает полноэкранный просмотр видео.

Макет портрета приложения YouTube
Приложение YouTupe в портретном режиме

Ландшафтный макет приложения YouTube
Приложение YouTube в ландшафтном режиме

(Извините за случайность фотографий, но они были первыми фотографиями, которые я мог найти о фактическом расположении)

Обычно это довольно легко сделать - просто укажите альтернативный макет в layout-land, и все будет хорошо. То, что приложение YouTube делает очень хорошо (и то, что я пытаюсь воспроизвести), заключается в том, что при изменении ориентации видео продолжает воспроизводиться и не требует повторной буферизации с самого начала.

Я выяснил, что переопределение onConfigurationChange() и установка новых LayoutParameters позволит мне изменить размер видео без принудительного создания буферов - однако видео будет случайным образом масштабироваться до различной ширины / высоты при повороте экрана несколько раз. Я пытался делать всевозможные вызовы invalidate() в VideoView, пытался вызвать RequestLayout() в родительском контейнере RelativeLayout и просто пробовал как можно больше разных вещей, но я не могу заставить его работать должным образом. Любой совет будет принята с благодарностью!

Вот мой код:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        questionText.setVisibility(View.GONE);
        respond.setVisibility(View.GONE);
        questionVideo.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
    } else {
        questionText.setVisibility(View.VISIBLE);
        respond.setVisibility(View.VISIBLE);
        Resources r = getResources();
        int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 150.0f, r.getDisplayMetrics());
        questionVideo.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, height));
    }
}

РЕДАКТИРОВАТЬ: я обнаружил в logcat какой-то интересный вывод, который появляется, когда мое видео вращается, что кажется виновником - хотя я понятия не имею, как это исправить:

Logcat выводится при правильном изменении размера (занимает все окно)

обратите внимание на h = 726

12-13 15:37:35.468  1262  1270 I ActivityManager: Config changed: { scale=1.0 imsi=310/4 loc=en_US touch=3 keys=1/1/2 nav=1/1 orien=2 layout=34 uiMode=17 seq=210}
12-13 15:37:35.561  1262  1268 I TIOverlay: Position/X0/Y76/W480/H225
12-13 15:37:35.561  1262  1268 I TIOverlay: Adjusted Position/X1/Y0/W403/H225
12-13 15:37:35.561  1262  1268 I TIOverlay: Rotation/90
12-13 15:37:35.561  1262  1268 I Overlay : v4l2_overlay_set_position:: w=480 h=224
12-13 15:37:35.561  1262  1268 I Overlay : v4l2_overlay_set_position:: w=402 h=726
12-13 15:37:35.561  1262  1268 I Overlay : dumping driver state:
12-13 15:37:35.561  1262  1268 I Overlay : output pixfmt:
12-13 15:37:35.561  1262  1268 I Overlay : w: 432
12-13 15:37:35.561  1262  1268 I Overlay : h: 240
12-13 15:37:35.561  1262  1268 I Overlay : color: 7
12-13 15:37:35.561  1262  1268 I Overlay : UYVY
12-13 15:37:35.561  1262  1268 I Overlay : v4l2_overlay window:
12-13 15:37:35.561  1262  1268 I Overlay : window l: 1 
12-13 15:37:35.561  1262  1268 I Overlay : window t: 0 
12-13 15:37:35.561  1262  1268 I Overlay : window w: 402 
12-13 15:37:35.561  1262  1268 I Overlay : window h: 726

Logcat выводится при неправильном изменении размера (занимает крошечную часть полного экрана)

обратите внимание на h=480

12-13 15:43:00.085  1262  1270 I ActivityManager: Config changed: { scale=1.0 imsi=310/4 loc=en_US touch=3 keys=1/1/2 nav=1/1 orien=2 layout=34 uiMode=17 seq=216}
12-13 15:43:00.171  1262  1268 I TIOverlay: Position/X0/Y76/W480/H225
12-13 15:43:00.171  1262  1268 I TIOverlay: Adjusted Position/X138/Y0/W266/H225
12-13 15:43:00.171  1262  1268 I TIOverlay: Rotation/90
12-13 15:43:00.179  1262  1268 I Overlay : v4l2_overlay_set_position:: w=480 h=224
12-13 15:43:00.179  1262  1268 I Overlay : v4l2_overlay_set_position:: w=266 h=480
12-13 15:43:00.179  1262  1268 I Overlay : dumping driver state:
12-13 15:43:00.179  1262  1268 I Overlay : output pixfmt:
12-13 15:43:00.179  1262  1268 I Overlay : w: 432
12-13 15:43:00.179  1262  1268 I Overlay : h: 240
12-13 15:43:00.179  1262  1268 I Overlay : color: 7
12-13 15:43:00.179  1262  1268 I Overlay : UYVY
12-13 15:43:00.179  1262  1268 I Overlay : v4l2_overlay window:
12-13 15:43:00.179  1262  1268 I Overlay : window l: 138 
12-13 15:43:00.179  1262  1268 I Overlay : window t: 0 
12-13 15:43:00.179  1262  1268 I Overlay : window w: 266 
12-13 15:43:00.179  1262  1268 I Overlay : window h: 480

Может быть, кто-то знает, что такое "Наложение" и почему он не получает правильное значение высоты?

11 ответов

Решение

РЕДАКТИРОВАТЬ: (июнь 2016 г.)

Этот ответ очень старый (я думаю, Android 2.2/2.3) и, вероятно, не так актуален, как другие ответы ниже! Сначала посмотрите на них, если вы не разрабатываете устаревший Android:)


Я смог сузить проблему до функции onMeasure в классе VideoView. Создав дочерний класс и переопределив функцию onMeasure, я смог получить желаемую функциональность.

public class VideoViewCustom extends VideoView {

    private int mForceHeight = 0;
    private int mForceWidth = 0;
    public VideoViewCustom(Context context) {
        super(context);
    }

    public VideoViewCustom(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    public void setDimensions(int w, int h) {
        this.mForceHeight = h;
        this.mForceWidth = w;

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.i("@@@@", "onMeasure");

        setMeasuredDimension(mForceWidth, mForceHeight);
    }
}

Затем внутри своей деятельности я просто сделал следующее:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        questionVideo.setDimensions(displayHeight, displayWidth);
        questionVideo.getHolder().setFixedSize(displayHeight, displayWidth);

    } else {
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);

        questionVideo.setDimensions(displayWidth, smallHeight);
        questionVideo.getHolder().setFixedSize(displayWidth, smallHeight);

    }
}

Линия:

questionVideo.getHolder().setFixedSize(displayWidth, smallHeight);

является ключевым для того, чтобы сделать эту работу. Если вы выполните вызов setDimensions без этого парня, размер видео все равно не изменится.

Единственное, что вам нужно сделать, это убедиться, что вы вызываете setDimensions() внутри метода onCreate(), иначе ваше видео не начнет буферизоваться, так как видео не будет настроено для рисования на поверхности любого размера.

// onCreate()
questionVideo.setDimensions(initialWidth, initialHeight); 

И последняя ключевая часть: если вы когда-нибудь задумывались, почему VideoView не изменяет размеры при вращении, вам необходимо убедиться, что размеры, к которым вы изменяете размер, либо точно равны видимой области, либо меньше ее. У меня была действительно большая проблема, когда я устанавливал ширину / высоту VideoView на весь размер дисплея, когда у меня все еще были панель уведомлений / строка заголовка на экране, и это не меняло размеры VideoView вообще. Простое удаление панели уведомлений и строки заголовка устранило проблему.

Надеюсь, это поможет кому-то в будущем!

Вот действительно простой способ выполнить то, что вы хотите с минимальным кодом:

AndroidManifest.xml:

android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout|uiMode"

Примечание. Отредактируйте, если необходимо, для вашего API, это относится к 10+, но более низкие API требуют удаления части "screenSize|screenLayout|uiMode" этой строки

Внутри метода "OnCreate", обычно в разделе "super.onCreate", добавьте:

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);

И затем где-нибудь, обычно внизу, добавьте:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);
}

Это позволит изменить размер видео в полноэкранный режим всякий раз, когда ориентация изменилась без прерывания воспроизведения и требует только переопределения метода конфигурации.

Прежде всего, большое спасибо за ваш обширный ответ.

У меня была та же проблема, видео в большинстве случаев было бы меньше, больше или искажено внутри VideoView после поворота.

Я попробовал ваше решение, но я также попробовал случайные вещи, и случайно заметил, что, если мой VideoView центрирован на его родительском объекте, он волшебным образом работает сам по себе (никакой пользовательский VideoView не требуется или что-либо еще).

Чтобы быть более конкретным, с помощью этого макета я воспроизвожу проблему в большинстве случаев:

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

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

С этим одним макетом у меня никогда не возникает проблем (плюс, видео центрировано, как и должно быть в любом случае;):

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

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

</RelativeLayout>

Это также работает с wrap_content вместо match_parent (видео по-прежнему занимает все пространство), что не имеет особого смысла для меня.

Во всяком случае, у меня нет никакого объяснения этому - это похоже на ошибку VideoView для меня.

VideoView использует так называемый оверлей, который представляет собой область, в которой воспроизводится видео. Это наложение находится за окном, в котором хранится VideoView. VideoView пробивает отверстие в своем окне, чтобы наложение было видимым. Затем он синхронизирует его с макетом (например, если вы переместите или измените размер VideoView, наложение также должно быть перемещено и изменено в размере).

Где-то на этапе макета есть ошибка, из-за которой наложение использует предыдущий размер, установленный VideoView.

Чтобы исправить это, создайте подкласс VideoView и переопределите onLayout:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    getHolder().setSizeFromLayout();
}

и оверлей будет иметь правильный размер из размеров макета VideoView.

Репликация приложения YouTube

Мне удалось построить пример проекта, который не требует android:configChanges="orientation" или обычай VideoView, Получающийся опыт идентичен тому, как приложение YouTube обрабатывает вращение во время воспроизведения видео. Другими словами, видео не нужно приостанавливать, повторно буферизовать или перезагружать, и оно не пропускает и не пропускает какие-либо аудиокадры при изменении ориентации устройства.

Оптимальный метод

Этот метод использует TextureView и сопровождающий его SurfaceTexture в качестве раковины для MediaPlayerтекущий видеокадр. Поскольку SurfaceTexture использует объект текстуры GL (на который просто ссылается целое число из контекста GL), можно полагать, что можно сохранить ссылку на SurfaceTexture посредством изменений конфигурации. Сам текстурный вид уничтожается и воссоздается во время изменения конфигурации (вместе с задним действием), а вновь созданный текстурный вид просто обновляется с помощью ссылки на SurfaceTexture перед его присоединением.

Я создал полный рабочий пример, показывающий, как и когда инициализировать ваш MediaPlayer и возможный MediaController, и я выделю интересные части, относящиеся к этому вопросу, ниже:

public class VideoFragment {

    TextureView mDisplay;
    SurfaceTexture mTexture;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        final View rootView = inflater.inflate(R.layout.fragment_main, container, false);
        mDisplay = (TextureView) rootView.findViewById(R.id.texture_view);
        if (mTexture != null) {
            mDisplay.setSurfaceTexture(mTexture);
        }
        mDisplay.setSurfaceTextureListener(mTextureListener);

        return rootView;
    }

    TextureView.SurfaceTextureListener mTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            mTexture = surface;
            // Initialize your media now or flag that the SurfaceTexture is available..
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
            mTexture = surface;
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            mTexture = surface;
            return false; // this says you are still using the SurfaceTexture..
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
            mTexture = surface;
        }
    };

    @Override
    public void onDestroyView() {
        mDisplay = null;
        super.onDestroyView();
    }

    // ...

}

Поскольку в решении используется сохраненный фрагмент, а не операция, которая обрабатывает изменения конфигурации вручную, вы можете полностью использовать систему распределения ресурсов для конкретной конфигурации, как это было бы естественно. К сожалению, если ваш минимальный SDK ниже API 16, вам, по сути, потребуется бэкпорт TextureView (чего я не сделал).

Наконец, если вам интересно, ознакомьтесь с моим начальным вопросом: мой оригинальный подход, медиа-стек Android, почему он не работает и альтернативный обходной путь.

Использовать этот:

@Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {

            getActivity().getWindow().addFlags(
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
            getActivity().getWindow().clearFlags(
                    WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);

        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {

            getActivity().getWindow().addFlags(
                    WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
            getActivity().getWindow().clearFlags(
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);

        }
    }

Также не забудьте добавить строку ниже к вашей активности в манифесте:

android:configChanges="orientation|keyboard|keyboardHidden|screenSize"

Я взял примеры из некоторых ответов здесь и попытался сделать это по-своему. Похоже, простое решение.

videoLayout = (RelativeLayout) videoView.findViewById(R.id.videoFrame);

onConfigurationChange внутри фрагмента. Где видео отображается в полноэкранном режиме в альбомной ориентации. Обратите внимание, что я скрываю панель действий.

@Override
public void onConfigurationChanged(Configuration newConfig) {

    int          height     = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200.0f,getResources().getDisplayMetrics());
    ActionBar    actionBar  = weatherActivity.getSupportActionBar();
    LayoutParams params     = videoLayout.getLayoutParams();

    if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
    {
        if(actionBar.isShowing())
            actionBar.hide();


        params.width  = ViewGroup.LayoutParams.MATCH_PARENT;
        params.height = ViewGroup.LayoutParams.MATCH_PARENT;

        videoLayout.requestLayout();
    }
    else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
    {
        if(!actionBar.isShowing())
            actionBar.show();

        params.width  = ViewGroup.LayoutParams.MATCH_PARENT;
        params.height = height;

        videoLayout.requestLayout();
    }

    super.onConfigurationChanged(newConfig);
}   

и вот мой файл макета

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

<RelativeLayout 
    android:id="@+id/videoFrame"
    android:layout_height="200dp"
    android:layout_width="match_parent">

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

</RelativeLayout>

У меня есть другое относительное расположение ниже этого, которого нет в этом макете. Но это не имеет никакого значения.

Посмотрите мой пример кода, он работает для меня

public class CustomVideoView extends android.widget.VideoView {
private int width;
private int height;
private Context context;
private VideoSizeChangeListener listener;
private boolean isFullscreen;

public CustomVideoView(Context context) {
    super(context);
    init(context);
}

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

/**
 * get video screen width and height for calculate size
 *
 * @param context Context
 */
private void init(Context context) {
    this.context = context;
    setScreenSize();
}

/**
 * calculate real screen size
 */
private void setScreenSize() {
    Display display = ((Activity) context).getWindowManager().getDefaultDisplay();

    if (Build.VERSION.SDK_INT >= 17) {
        //new pleasant way to get real metrics
        DisplayMetrics realMetrics = new DisplayMetrics();
        display.getRealMetrics(realMetrics);
        width = realMetrics.widthPixels;
        height = realMetrics.heightPixels;

    } else if (Build.VERSION.SDK_INT >= 14) {
        //reflection for this weird in-between time
        try {
            Method mGetRawH = Display.class.getMethod("getRawHeight");
            Method mGetRawW = Display.class.getMethod("getRawWidth");
            width = (Integer) mGetRawW.invoke(display);
            height = (Integer) mGetRawH.invoke(display);
        } catch (Exception e) {
            //this may not be 100% accurate, but it's all we've got
            width = display.getWidth();
            height = display.getHeight();
        }

    } else {
        //This should be close, as lower API devices should not have window navigation bars
        width = display.getWidth();
        height = display.getHeight();
    }

    // when landscape w > h, swap it
    if (width > height) {
        int temp = width;
        width = height;
        height = temp;
    }
}

/**
 * set video size change listener
 *
 */
public void setVideoSizeChangeListener(VideoSizeChangeListener listener) {
    this.listener = listener;
}

public interface VideoSizeChangeListener {
    /**
     * when landscape
     */
    void onFullScreen();

    /**
     * when portrait
     */
    void onNormalSize();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        // full screen when landscape
        setSize(height, width);
        if (listener != null) listener.onFullScreen();
        isFullscreen = true;
    } else {
        // height = width * 9/16
        setSize(width, width * 9 / 16);
        if (listener != null) listener.onNormalSize();
        isFullscreen = false;
    }
}

/**
 * @return true: fullscreen
 */
public boolean isFullscreen() {
    return isFullscreen;
}

/**
 * set video sie
 *
 * @param w Width
 * @param h Height
 */
private void setSize(int w, int h) {
    setMeasuredDimension(w, h);
    getHolder().setFixedSize(w, h);
}
}

и не пересоздавать вид при смене ориентации

// AndroidManifest.xml
android:configChanges="screenSize|orientation|keyboardHidden"

portrain

пейзажпейзаж

мой исходный код здесь

Хотя ответ Mark37 (очень полезный) работает, он требует установки размеров вручную (используя setDimensions). Это может быть хорошо в приложении, где желаемые размеры известны заранее, но если вы хотите, чтобы виды автоматически определяли размер на основе параметров видео (например, чтобы убедиться, что сохраняется исходное соотношение сторон), необходим другой подход,

К счастью, оказывается, что часть setDimensions на самом деле не нужна. OnMeasure VideoView уже включает в себя всю необходимую логику, поэтому вместо того, чтобы полагаться на кого-то для вызова setDimensions, можно просто вызвать super.onMeasure, а затем использовать getMeasuredWidth / getMeasuredHeight представления, чтобы получить фиксированный размер.

Итак, класс VideoViewCustom становится просто:

public class VideoViewCustom extends VideoView {

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

    public VideoViewCustom(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        getHolder().setFixedSize(getMeasuredWidth(), getMeasuredHeight());
    }
}

Эта версия не требует никакого дополнительного кода в вызывающей программе и использует все преимущества существующей реализации VideoView onMeasure.

На самом деле это довольно похоже на подход, предложенный Zapek, который делает в основном то же самое, только с onLayout вместо onMeasure.

ПРОСТОЙ ответ и до 2018 года.

Положительные моменты за этот ответ: код ниже

1) Неважно, если вы установите FULL_SCREEN или нет. Поскольку это соответствует родителю, это всегда будет работать. В старом принятом ответе, если вы не установили FULL_SCREEN, android возьмет на себя весь размер телефона, и тогда VideoView будет за пределами экрана;

2) Вам не нужно создавать пользовательский VideoView или PlayerView;

3) Он не вызывает метод несколько раз. В старом принятом ответе он несколько раз вызывает ненужные измерения;

4) Существует также кнопка для изменения ориентации, как приложение YouTube;

5) Установка автоматического поворота телефона на OFF также блокирует изменение ориентации приложения.

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    ...
    setPlayerViewLayoutParams();
    ...
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        setPlayerViewLayoutParamsForLandScape();
    } else {
        setPlayerViewLayoutParamsForPortrait();
    }
}

private void setPlayerViewLayoutParamsForLandScape() {
    ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
            ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.MATCH_PARENT);
    mPlayerView.setLayoutParams(lp);
}

private void setPlayerViewLayoutParamsForPortrait() {
    Display display = myContext.getWindowManager().getDefaultDisplay();
    Point size = new Point();
    display.getSize(size);
    int width = size.x;
    Double doubleHeight = width / 1.5;
    Integer height = doubleHeight.intValue();

    ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
            ConstraintLayout.LayoutParams.MATCH_PARENT, height);
    mPlayerView.setLayoutParams(lp);
}

private void setPlayerViewLayoutParams() {
    if (myContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        setPlayerViewLayoutParamsForLandScape();
    } else {
        setPlayerViewLayoutParamsForPortrait();
    }
}

OBS: я использую PlayerView внутри ConstraintLayout. Потому что я работаю с библиотекой ExoPlayer Media.

fragment_player.xml

<android.support.constraint.ConstraintLayout   
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<com.google.android.exoplayer2.ui.PlayerView
    android:id="@+id/player_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorBlack"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintVertical_bias="0.0"/>

</android.support.constraint.ConstraintLayout>

ДОПОЛНИТЕЛЬНЫЙ

Я также хочу кнопку, чтобы изменить ориентацию

Чтобы полностью воспроизвести приложение youtube, вам, вероятно, понадобится кнопка для установки проигрывателя в альбомную или книжную ориентацию, это полезно, если у пользователя отключено автоматическое вращение телефона.

Во-первых, вам нужно будет выполнить следующие шаги:

  1. Создайте кнопку, которая изменит ориентацию;
  2. Когда пользователь нажимает кнопку, проверьте текущую ориентацию;
  3. Установите противоположную ориентацию, например, если она является текущей в LandScape, выберите "Портрет".

PlayerFragment.java

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.image_button_full_screen:
            if (myContext.getResources().getConfiguration().orientation 
                == Configuration.ORIENTATION_LANDSCAPE) {
                myContext.setRequestedOrientation(
                        ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
            } else {
                myContext.setRequestedOrientation(
                        ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
            }
            break;
    }
}

ПРОБЛЕМА 1: вращение устройства больше не работает

Вы заметите, что если вы нажмете на кнопку, чтобы изменить ориентацию, вращение устройства больше не будет работать, чтобы изменить размер проигрывателя. Это происходит потому, что вы устанавливаете ориентацию на LandScape или Portrait программно, и она останется такой же навсегда, поэтому вам нужен способ установить ориентацию на SENSOR, поэтому при повороте устройства он будет проверять, какова реальная текущая ориентация. Метод ниже сделает это за вас.

ПРОБЛЕМА 2: Автоповорот телефона выключен, и он все еще меняет ориентацию

Внутри метода, описанного ниже, вам нужно проверить, включено или выключено автоповорот телефона. Если он включен, вы можете установить ОРИЕНТАЦИЮ на ДАТЧИК, чтобы телефон мог менять ориентацию, когда пользователь поворачивает устройство. В случае, если автоповорот выключен, вы ничего не делаете, поэтому ориентация будет заблокирована в книжной или горизонтальной ориентации, в зависимости от того, нажал ли пользователь кнопку полноэкранного режима или нет.

private void setOrientationListener() {
    OrientationEventListener orientationEventListener = new OrientationEventListener(this) {
        @Override
        public void onOrientationChanged(int orientation) {
            // 94 > 90 - 10 and 94 < 90 + 10     //   80 < orientation < 100.  Rotating to the LEFT.
            // 278 > 270 - 10 and 278 < 270 + 10 //   260 < orientation < 280. Rotating to the RIGHT.
            int epsilon = 10;
            int leftLandscape = 90;
            int rightLandscape = 270;
            if (epsilonCheck(orientation, leftLandscape, epsilon) ||
                    epsilonCheck(orientation, rightLandscape, epsilon)) {
                if (android.provider.Settings.System.getInt(getContentResolver(),
                        Settings.System.ACCELEROMETER_ROTATION, 0) == 1) {
                    // Phone's auto rotation is ON
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
                }
            }
        }

        private boolean epsilonCheck(int a, int b, int epsilon) {
            return a > b - epsilon && a < b + epsilon;
        }
    };
    orientationEventListener.enable();
}

Если вы работаете с мультимедийным приложением, у меня есть пример, который поможет вам, как эффективно использовать библиотеку Exoplayer.

Я пытался использовать код других ответов, но это не решило мою проблему, потому что, если я быстро поворачивал экран, размер моего видео был увеличен. Поэтому я частично использую их код, который помещаю в поток, который очень быстро обновляет окно. Итак, вот мой код:

  new Thread(new Runnable()
    {
        @Override
        public void run()
        {
            while(true)
            {
                try
                {
                    Thread.sleep(1);
                }
                catch(InterruptedException e){}
                handler.post(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                                WindowManager.LayoutParams.FLAG_FULLSCREEN);
                    }
                });
            }
        }
    }).start();

Таким образом, размер видео будет всегда правильным (в моем случае).

Другие вопросы по тегам