Android VideoView изменение ориентации с буферизованным видео
Я пытаюсь повторить функциональность последнего приложения YouTube на Android Marketplace. При просмотре видео есть две отдельные компоновки: одна в портретной ориентации, которая предоставляет дополнительную информацию, и другая в альбомной ориентации, которая обеспечивает полноэкранный просмотр видео.
Приложение YouTupe в портретном режиме
Приложение 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"
пейзаж
Хотя ответ 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, вам, вероятно, понадобится кнопка для установки проигрывателя в альбомную или книжную ориентацию, это полезно, если у пользователя отключено автоматическое вращение телефона.
Во-первых, вам нужно будет выполнить следующие шаги:
- Создайте кнопку, которая изменит ориентацию;
- Когда пользователь нажимает кнопку, проверьте текущую ориентацию;
- Установите противоположную ориентацию, например, если она является текущей в 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();
Таким образом, размер видео будет всегда правильным (в моем случае).