Полноэкранный Экзоплеер
Я пытаюсь показать шоу видео (.mp4) с exoplayer в RecyclerView и ViewPager. Я показываю видео контроллер с пользовательским макетом. Все идет нормально.
Теперь попробуйте использовать полноэкранный режим видео, как это было раньше, но не можете найти хороший путь в документе exoplayer.
Может кто-нибудь мне помочь?
11 ответов
Библиотека ExoPlayer в настоящее время не предоставляет встроенного способа включения / отключения полноэкранного режима. Вам нужно реализовать это самостоятельно или найти какой-то сторонний код для этого, я боюсь.
По сути, требуется два шага:
a) Установите свойства окна и действия в полноэкранный и / или иммерсивный режим и (при желании) перейдите в ландшафтный режим. Это не сложно. Смотрите эту страницу на Android разработчиков.
б) Переход от рендеринга к SimpleExoPlayerView
(на самом деле речь идет о поверхности), которая охватывает весь видовой экран в режиме погружения. Это более сложная задача для достижения оптимального взаимодействия с пользователем на всех уровнях API.
Для оптимального взаимодействия с пользователем мы хотим, чтобы проигрыватель воспроизводился во время перехода к полноэкранному режиму и обратно, чтобы воспроизведение продолжалось без проблем. В RecyclerView это довольно сложно решить для всех уровней API.
Подход 1
Самым простым способом, вероятно, является наличие отдельного экземпляра SimpleExoPlayerView, который вы помещаете поверх макета, как только вы входите в режим погружения (некоторые люди открывают для этого диалоговое окно со вторым видом, у некоторых второй макет отображается просто сверху в макете). как-нибудь и показать / скрыть это по требованию).
Затем вы отсоединяете экземпляр проигрывателя от SimpleExoPlayerView, встроенного в RV, и присоединяете его к полноэкранному представлению с помощью вызова статического вспомогательного метода:
SimpleExoPlayerView.switchTargetView(simpleExoPlayer, oldPlayerView, newPlayerView);
Этот подход очень хорошо работает на API >=23. С API 23 был добавлен метод MediaCodec.setOutputSurface, позволяющий динамически менять поверхности. Статический метод выше гарантирует, что этот метод применяется. В результате аудио и видео продолжают воспроизводиться, и пользовательский опыт от входа и выхода из полноэкранного режима очень плавный. Для API <=22 необходимо создать новый экземпляр кодека, чтобы перейти на другую поверхность. Это прерывает воспроизведение, и пользовательский опыт для этого подхода ухудшается.
Подход 2
Чтобы избежать перехода на другую поверхность на более низком уровне API, необходимо использовать одну поверхность и каким-то образом перевести ее в полноэкранный режим. Вы можете либо просто скрыть все остальное, чем SimpleExoPlayerView и установить ширину и высоту макета, чтобы они соответствовали его родительскому элементу, либо вы можете заменить вид видео заполнителем и поместить его сверху и сзади.
Это может работать довольно хорошо для простых макетов, но со сложными макетами, возможно, включающими фрагменты, пейджеры, просмотры реселлера, это может быть довольно навязчивой операцией, вызывающей мерцание или кратковременное прерывание воспроизведения (на некоторых уровнях API, например, при удалении вида проигрывателя из иерархии представления).). Я видел это работает хорошо для различных макетов, хотя.
Дальнейшие подходы / проблемы
Могут быть другие и, возможно, лучшие подходы, когда вы копаете глубже и / или если вы вообще не используете SimpleExoPlayerView.
Вы можете легко сделать это из XML-файла ExoPlayer
. Установите следующий атрибут:
app:resize_mode="fill"
Чтобы показать полное полноэкранное видео на expo player, используйте эту строку в XML-файле app:resize_mode="fill"
Вы можете использовать эту библиотеку, если хотите FullScreen Exoplayer:
https://github.com/Norulab/android-exoplayer-fullscreen
Эта библиотека содержит некоторые функции расширения для ExoPlayer:
val player = SimpleExoPlayer.Builder(context).build()
player.preparePlayer(playerView)
player.setSource(applicationContext, "http://html5videoformatconverter.com/data/images/happyfit2.mp4")
Вам нужно просто добавить прослушиватель в ваш exoplayer, если вы сделаете это в Create, а затем добавить эту полосу =binding.exoplayerView.setControllerOnFullScreenModeChangedListener {}
при этом появится кнопка exoplayer, не забудьте указать версию exoplayer Version или выше = '2.16.1'
Exoplayer не поддерживает полноэкранный режим, поэтому вот обходной путь, который сработал для меня. Здесь я ограничил поворот экрана и вручную изменил ориентацию, программно изменив ширину и высоту player_view, а также убрал видимость панели инструментов. Используется Data Binding и Kotlin.
Следует за блоком кода для XML
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/video_player_container"
android:layout_width="match_parent"
android:layout_height="250dp"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:keepScreenOn="true"
android:padding="0dp"
app:controller_layout_id="@layout/exo_playback_control_view"
app:layout_constraintTop_toTopOf="parent"
app:resize_mode="fill"
app:use_controller="true" />
<LinearLayout
android:id="@+id/nextVideoContainer"
android:layout_width="wrap_content"
android:layout_height="@dimen/spacing_32"
android:background="#90000000"
android:onClick="@{() -> vm.onNextVideo()}"
android:orientation="horizontal"
android:paddingLeft="@dimen/spacing_16"
android:paddingRight="@dimen/spacing_16"
android:visibility="@{vm.shouldShowNextBtn}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.sharedcode.widgets.CustomTextView
android:id="@+id/next_video_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/label_next"
android:textColor="@android:color/white" />
<com.sharedcode.widgets.CustomImageView
android:id="@+id/next_video_image"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:paddingTop="2dp"
android:src="@drawable/ic_play_next" />
</LinearLayout>
<RelativeLayout
android:id="@+id/retry_container"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#90000000"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="@{() -> vm.onRetry()}">
<com.sharedcode.widgets.CustomTextView
android:id="@+id/txt_no_internet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:layout_centerInParent="true"
android:text="@string/txt_no_internet_connection"
android:textColor="@android:color/white"
android:textSize="@dimen/font_16" />
<com.sharedcode.widgets.CustomTextView
android:layout_width="`wrap_content`"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_no_internet"
android:layout_centerInParent="true"
android:layout_marginTop="@dimen/spacing_16"
android:maxHeight="@dimen/spacing_32"
android:text="@string/txt_tap_to_retry"
android:textColor="@android:color/white"
android:textSize="@dimen/font_16" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Изменения в манифесте Android
<activity android:name=".yourPackage.ClassName"
android:screenOrientation="portrait"
android:configChanges="orientation|screenSize|layoutDirection"/>
Проверьте ориентацию и поверните ее с помощью следующего кода
mBinding.playerView.exo_fullscreen_btn.setOnClickListener {
if ((activity as TrainingVideoActivity).checkLandscapeOrientation()) {
(activity as TrainingVideoActivity).changeOrientationToLandscape(false)
} else {
(activity as TrainingVideoActivity).changeOrientationToLandscape(true)
}
}
Методы подписи следующие
/**
* Changes the Orientation
* @param shouldLandscape
*/
fun changeOrientationToLandscape(shouldLandscape: Boolean) {
requestedOrientation = if (shouldLandscape) {
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
} else {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}
/**
* Checks the Orientation
* And returns true if Landscape else false
*/
fun checkLandscapeOrientation() : Boolean {
val orientation = resources.configuration.orientation
return orientation == Configuration.ORIENTATION_LANDSCAPE
}
Теперь просто переопределите метод onConfigurationChanged в своем фрагменте / действии, поскольку здесь я использовал фрагмент. Итак, здесь я изменил ширину / высоту родительского контейнера, в котором находится ExoplayerView.
/**
* Used for Showing Video on Full Screen
* @param newConfig
* Used EXO_PLAYER_VIEW_HEIGHT as 250
*/
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
hideToolbarAndShowFullScreen()
mBinding.playerView.exo_fullscreen_btn.setImageDrawable(ContextCompat.getDrawable(activity!!, R.drawable.ic_shrink))
val params = mBinding.videoPlayerContainer.layoutParams as ConstraintLayout.LayoutParams
params.width = ViewGroup.LayoutParams.MATCH_PARENT
params.height = ViewGroup.LayoutParams.MATCH_PARENT
mBinding.videoPlayerContainer.layoutParams = params
} else {
showToolbarAndClearFullScreen()
mBinding.playerView.exo_fullscreen_btn.setImageDrawable(ContextCompat.getDrawable(activity!!, R.drawable.ic_fullscreen))
val params = mBinding.videoPlayerContainer.layoutParams as ConstraintLayout.LayoutParams
val factor = mBinding.playerView.context.resources.displayMetrics.density
params.width = ViewGroup.LayoutParams.MATCH_PARENT
params.height = (EXO_PLAYER_VIEW_HEIGHT * factor).toInt()
mBinding.videoPlayerContainer.layoutParams = params
}
}
/**
* Show the Toolbar and reset to original in the Portrait Mode
*/
private fun showToolbarAndClearFullScreen() {
(activity as TrainingVideoActivity).supportActionBar!!.show()
(activity as TrainingVideoActivity).window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
Наконец, XML для player_controller
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#99000000">
<LinearLayout
android:id="@+id/container_play_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="horizontal">
<ImageButton
android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"
android:src="@drawable/ic_play_exoplayer"
/>
<ImageButton
android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause"
android:src="@drawable/ic_pause_exoplayer"/>
</LinearLayout>
<LinearLayout
android:id="@+id/seekbar_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom"
android:background="#CC000000"
android:clickable="false"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UselessParent">
<com.sharedcode.widgets.CustomTextView
android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="#FFBEBEBE"
android:textSize="14sp"
android:textStyle="bold" />
<com.bnb.paynearby.utils.exoplayer.ExtendedTimebar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
app:buffered_color="@color/white"
app:played_color="@color/color_red"
app:scrubber_color="@color/color_red"
app:scrubber_disabled_size="10dp"
app:unplayed_color="#484848" />
<com.sharedcode.widgets.CustomTextView
android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:includeFontPadding="false"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="#FFBEBEBE"
android:textSize="14sp"
android:textStyle="bold" />
<com.sharedcode.widgets.CustomImageView
android:id="@+id/exo_fullscreen_btn"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="8dp"
android:src="@drawable/ic_fullscreen"
/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Сообщите мне, работает ли это.
добавьте следующую строку в приложение StyledPlayerView: resize_mode="fill"
после добавления строки ваш xml должен выглядеть как код ниже
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/player"
android:layout_width="match_parent"
android:layout_height="160"
app:resize_mode="fill"
app:controller_layout_id="@layout/custom_controller_exoplayer"
app:show_buffering="when_playing"
app:show_shuffle_button="true" />
Обязательно установите длину и ширину вида игрока, чтобы он соответствовал его родителю.
Используйте это, чтобы скрыть строку состояния:
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
Используйте это, чтобы скрыть панель навигации:
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
Вы можете настроить проигрыватель на полный экран, установив параметры проигрывателя.
Params params = (LinearLayout.LayoutParams)
exoPlayerView.getLayoutParams();
params.width=params.MATCH_PARENT;
params.height=params.MATCH_PARENT;
exoPlayerView.setLayoutParams(params);
}
И скрыть панель действий:
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
getActionBar().hide();
надеюсь, это поможет
Вы можете применить магию с помощью ExoPlayerView
playerView.setControllerVisibilityListener(new
PlaybackControlView.VisibilityListener() {
@Override
public void onVisibilityChange(int i) {
// Using Activity
if (getActionBar() != null)
if (i == 0) {
// code for show
} else{
// code for hide
}
}
});
Это может быть слишком поздно, но это может помочь другим разработчикам.
Exoplayer не предоставляет полноэкранный режим по умолчанию. Поэтому нам нужен обходной путь для этого. Мы можем реализовать эту полноэкранную функциональность, используя диалог. Нам просто нужно удалить и добавить Playerview из нашей деятельности в диалог.
mFullScreenDialog = new Dialog(mContext, android.R.style.Theme_Black_NoTitleBar_Fullscreen) {
public void onBackPressed() {
if (mExoPlayerFullscreen) { //mExoPlayerFullscreen is a boolean that we need to maintain to know whether screen is fullscreen or not.
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
closeFullscreenDialog();
}
super.onBackPressed();
}
};
Здесь мы инициализировали диалог.
mFullScreenButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mExoPlayerFullscreen) {
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
openFullscreenDialog();
} else {
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
closeFullscreenDialog();
}
}
});
Здесь мы инициализировали полноэкранную кнопку нашего exoplayer.
private void openFullscreenDialog() {
((ViewGroup) playerView.getParent()).removeView(playerView);
mFullScreenDialog.addContentView(playerView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mFullScreenIcon.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.exoplayer_shrink));
mExoPlayerFullscreen = true;
mFullScreenDialog.show();
}
private void closeFullscreenDialog() {
((ViewGroup) playerView.getParent()).removeView(playerView);
((FrameLayout) findViewById(R.id.main_media_frame)).addView(playerView);
mExoPlayerFullscreen = false;
mFullScreenDialog.dismiss();
mFullScreenIcon.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.exoplayer_expand));
}
playerview - это Playerview, который вы инициализируете для
<FrameLayout
android:id="@+id/main_media_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"></com.google.android.exoplayer2.ui.PlayerView>
</FrameLayout>
и в Яве,
playerView = (PlayerView) findViewById(R.id.player_view);