Commit c0bf4319 authored by Geoffrey Métais's avatar Geoffrey Métais

Integrate DisplayManagaer to LibVLC

parent 600c3ace
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
compileSdkVersion rootProject.ext.compileSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
minSdkVersion rootProject.ext.minSdkVersion
}
sourceSets {
main {
......
......@@ -2,7 +2,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/black"
android:background="@android:color/black"
android:keepScreenOn="true" >
<!--
......
<?xml version="1.0" encoding="utf-8"?>
<SurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</SurfaceView>
<?xml version="1.0" encoding="utf-8"?>
<TextureView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</TextureView>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/player_surface_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:foregroundGravity="clip_horizontal|clip_vertical"
android:fitsSystemWindows="false"
tools:ignore="true">
<ViewStub
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/surface_view"
android:id="@+id/surface_stub" />
<ViewStub
android:layout_width="1dp"
android:layout_height="1dp"
android:layout="@layout/surface_view"
android:id="@+id/subtitles_surface_stub" />
<ViewStub
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/texture_view"
android:id="@+id/texture_stub" />
</FrameLayout>
package org.videolan.libvlc.util;
import android.app.Activity;
import android.app.Presentation;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.PixelFormat;
import android.media.MediaRouter;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.FrameLayout;
import org.videolan.BuildConfig;
import org.videolan.R;
import org.videolan.libvlc.RendererItem;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
public class DisplayManager {
private static final String TAG = "VLC/DisplayManager";
private Activity mActivity;
private LiveData<RendererItem> mSelectedRenderer;
private RendererItem mRendererItem;
private boolean mTextureView;
private MediaRouter mMediaRouter;
private MediaRouter.SimpleCallback mMediaRouterCallback;
private SecondaryDisplay mPresentation;
private DisplayType mDisplayType;
private int mPresentationId = -1;
public DisplayManager(@NonNull Activity activity, @Nullable LiveData<RendererItem> selectedRender, boolean textureView, boolean cloneMode, boolean benchmark) {
mActivity = activity;
mSelectedRenderer = selectedRender;
mMediaRouter = (MediaRouter) activity.getApplicationContext().getSystemService(Context.MEDIA_ROUTER_SERVICE);
mTextureView = textureView;
mPresentation = !(cloneMode || benchmark) ? createPresentation() : null;
mDisplayType = benchmark ? DisplayType.PRIMARY : getCurrentType();
if (mSelectedRenderer != null) {
mSelectedRenderer.observeForever(mRendererObs);
}
}
public boolean isPrimary() {
return mDisplayType == DisplayType.PRIMARY;
}
public boolean isSecondary() {
return mDisplayType == DisplayType.PRESENTATION;
}
public boolean isOnRenderer() {
return mDisplayType == DisplayType.RENDERER;
}
private Observer<RendererItem> mRendererObs = new Observer<RendererItem>() {
@Override
public void onChanged(RendererItem rendererItem) {
mRendererItem = rendererItem;
updateDisplayType();
}
};
private DialogInterface.OnDismissListener mOnDismissListener = new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (dialog == mPresentation) {
if (BuildConfig.DEBUG) Log.i(TAG, "Presentation was dismissed.");
mPresentation = null;
mPresentationId = -1;
}
}
};
public void release() {
if (mPresentation != null) {
mPresentation.dismiss();
mPresentation = null;
}
if (mSelectedRenderer != null) mSelectedRenderer.removeObserver(mRendererObs);
}
private void updateDisplayType() {
if (mDisplayType != getCurrentType()) new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mActivity.recreate();
}
}, 100L);
}
private DisplayType getCurrentType() {
if (mPresentationId != -1) return DisplayType.PRESENTATION;
if (mRendererItem != null) return DisplayType.RENDERER;
return DisplayType.PRIMARY;
}
@Nullable
public SecondaryDisplay getPresentation() {
return mPresentation;
}
@Nullable
public DisplayType getDisplayType() {
return mDisplayType;
}
private SecondaryDisplay createPresentation() {
if (mMediaRouter == null) return null;
final MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO);
final Display presentationDisplay = route != null ? route.getPresentationDisplay() : null;
if (presentationDisplay != null) {
if (BuildConfig.DEBUG) Log.i(TAG, "Showing presentation on display: "+presentationDisplay);
final SecondaryDisplay presentation = new SecondaryDisplay(mActivity, presentationDisplay);
presentation.setOnDismissListener(mOnDismissListener);
try {
presentation.show();
mPresentationId = presentationDisplay.getDisplayId();
return presentation;
} catch (WindowManager.InvalidDisplayException ex) {
if (BuildConfig.DEBUG) Log.w(TAG, "Couldn't show presentation! Display was removed in " + "the meantime.", ex);
mPresentationId = -1;
}
} else if (BuildConfig.DEBUG) Log.i(TAG, "No secondary display detected");
return null;
}
public boolean setMediaRouterCallback() {
if (mMediaRouter == null || mMediaRouterCallback != null) return false;
mMediaRouterCallback = new MediaRouter.SimpleCallback() {
@Override
public void onRoutePresentationDisplayChanged(MediaRouter router, MediaRouter.RouteInfo info) {
if (BuildConfig.DEBUG) Log.d(TAG, "onRoutePresentationDisplayChanged: info="+info);
final int newDisplayId = (info.getPresentationDisplay() != null) ? info.getPresentationDisplay().getDisplayId() : -1;
if (newDisplayId == mPresentationId) return;
mPresentationId = newDisplayId;
if (newDisplayId == -1) removePresentation();
else updateDisplayType();
}
};
mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mMediaRouterCallback);
return true;
}
public void removeMediaRouterCallback() {
if (mMediaRouter != null) mMediaRouter.removeCallback(mMediaRouterCallback);
mMediaRouterCallback = null;
}
private void removePresentation() {
if (mMediaRouter == null) return;
// Dismiss the current presentation if the display has changed.
if (BuildConfig.DEBUG) Log.i(TAG, "Dismissing presentation because the current route no longer " + "has a presentation display.");
if (mPresentation != null) {
mPresentation.dismiss();
mPresentation = null;
}
updateDisplayType();
}
public class SecondaryDisplay extends Presentation {
public static final String TAG = "VLC/SecondaryDisplay";
private FrameLayout mSurfaceFrame;
private SurfaceView mSurfaceView;
private SurfaceView mSubtitlesSurfaceView;
public SecondaryDisplay(Context outerContext, Display display) {
super(outerContext, display);
}
public SecondaryDisplay(Context outerContext, Display display, int theme) {
super(outerContext, display, theme);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.player_remote);
mSurfaceFrame = findViewById(R.id.remote_player_surface_frame);
mSurfaceView = mSurfaceFrame.findViewById(R.id.remote_player_surface);
mSubtitlesSurfaceView = mSurfaceFrame.findViewById(R.id.remote_subtitles_surface);
mSubtitlesSurfaceView.setZOrderMediaOverlay(true);
mSubtitlesSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
if (BuildConfig.DEBUG) Log.i(TAG, "Secondary display created");
}
public FrameLayout getSurfaceFrame() {
return mSurfaceFrame;
}
public SurfaceView getSurfaceView() {
return mSurfaceView;
}
public SurfaceView getSubtitlesSurfaceView() {
return mSubtitlesSurfaceView;
}
}
public enum DisplayType { PRIMARY, PRESENTATION, RENDERER }
}
package org.videolan.libvlc.util;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.videolan.R;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
public class VLCVideoLayout extends FrameLayout {
public VLCVideoLayout(@NonNull Context context) {
super(context);
setupLayout(context);
}
public VLCVideoLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setupLayout(context);
}
public VLCVideoLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setupLayout(context);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public VLCVideoLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setupLayout(context);
}
private void setupLayout(@NonNull Context context) {
inflate(context, R.layout.vlc_video_layout, this);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setBackgroundColor(getResources().getColor(android.R.color.black));
final ViewGroup.LayoutParams lp = getLayoutParams();
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
setLayoutParams(lp);
}
}
package org.videolan.vlc.gui.video
import android.annotation.TargetApi
import android.app.Activity
import android.app.Presentation
import androidx.lifecycle.Observer
import android.content.Context
import android.content.DialogInterface
import android.graphics.PixelFormat
import android.media.MediaRouter
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.Display
import android.view.SurfaceView
import android.view.WindowManager
import android.widget.FrameLayout
import org.videolan.libvlc.RendererItem
import org.videolan.vlc.BuildConfig
import org.videolan.vlc.R
import org.videolan.vlc.RendererDelegate
import org.videolan.vlc.util.AndroidDevices
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
class DisplayManager(private val activity: Activity, cloneMode: Boolean, benchmark: Boolean) {
enum class DisplayType { PRIMARY, PRESENTATION, RENDERER }
val displayType: DisplayType
// Presentation
private val mediaRouter: MediaRouter? by lazy { activity.applicationContext.getSystemService(Context.MEDIA_ROUTER_SERVICE) as MediaRouter }
private var mediaRouterCallback: MediaRouter.SimpleCallback? = null
var presentation: SecondaryDisplay? = null
private var presentationDisplayId = -1
///Renderers
private var rendererItem: RendererItem? = RendererDelegate.selectedRenderer.value
val isPrimary: Boolean
get() = displayType == DisplayType.PRIMARY
val isOnRenderer: Boolean
get() = displayType == DisplayType.RENDERER
/**
* Listens for when presentations are dismissed.
*/
private val mOnDismissListener = DialogInterface.OnDismissListener { dialog ->
if (dialog == presentation) {
if (BuildConfig.DEBUG) Log.i(TAG, "Presentation was dismissed.")
presentation = null
presentationDisplayId = -1
}
}
private val rendererObs by lazy(LazyThreadSafetyMode.NONE) { Observer<RendererItem> {
rendererItem = it
updateDisplayType()
}}
init {
presentation = if (!(cloneMode || benchmark)) createPresentation() else null
displayType = if (benchmark) DisplayType.PRIMARY else getCurrentType()
if (!AndroidDevices.isChromeBook) RendererDelegate.selectedRenderer.observeForever(rendererObs)
}
companion object {
private const val TAG = "VLC/DisplayManager"
}
fun release() {
if (displayType == DisplayType.PRESENTATION) {
presentation?.dismiss()
presentation = null
}
if (!AndroidDevices.isChromeBook) RendererDelegate.selectedRenderer.removeObserver(rendererObs)
}
private fun updateDisplayType() {
if (getCurrentType() != displayType && activity is VideoPlayerActivity) activity.recreate()
}
private fun getCurrentType() = when {
presentationDisplayId != -1 -> DisplayType.PRESENTATION
rendererItem !== null -> DisplayType.RENDERER
else -> DisplayType.PRIMARY
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private fun createPresentation(): SecondaryDisplay? {
if (mediaRouter === null) return null
// Get the current route and its presentation display.
val route = mediaRouter?.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO)
val presentationDisplay = route?.presentationDisplay
if (presentationDisplay !== null) {
// Show a new presentation if possible.
if (BuildConfig.DEBUG) Log.i(TAG, "Showing presentation on display: $presentationDisplay")
val presentation = SecondaryDisplay(activity, presentationDisplay)
presentation.setOnDismissListener(mOnDismissListener)
try {
presentation.show()
presentationDisplayId = presentationDisplay.displayId
return presentation
} catch (ex: WindowManager.InvalidDisplayException) {
if (BuildConfig.DEBUG) Log.w(TAG, "Couldn't show presentation! Display was removed in " + "the meantime.", ex)
presentationDisplayId = -1
}
} else if (BuildConfig.DEBUG) Log.i(TAG, "No secondary display detected")
return null
}
/**
* Add or remove MediaRouter callbacks. This is provided for version targeting.
*
* @param add true to add, false to remove
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
fun mediaRouterAddCallback(add: Boolean) {
if (mediaRouter === null || add == (mediaRouterCallback !== null)) return
if (add) {
mediaRouterCallback = object : MediaRouter.SimpleCallback() {
override fun onRoutePresentationDisplayChanged(
router: MediaRouter, info: MediaRouter.RouteInfo) {
if (BuildConfig.DEBUG) Log.d(TAG, "onRoutePresentationDisplayChanged: info=$info")
val newDisplayId = info.presentationDisplay?.displayId ?: -1
if (newDisplayId == presentationDisplayId) return
presentationDisplayId = newDisplayId
if (newDisplayId == -1) removePresentation() else updateDisplayType()
}
}
mediaRouter?.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mediaRouterCallback)
} else {
mediaRouter?.removeCallback(mediaRouterCallback)
mediaRouterCallback = null
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private fun removePresentation() {
if (mediaRouter === null) return
// Dismiss the current presentation if the display has changed.
if (BuildConfig.DEBUG) Log.i(TAG, "Dismissing presentation because the current route no longer " + "has a presentation display.")
presentation?.dismiss()
presentation = null
updateDisplayType()
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
class SecondaryDisplay(context: Context, display: Display) : Presentation(context, display) {
lateinit var surfaceView: SurfaceView
lateinit var subtitlesSurfaceView: SurfaceView
lateinit var surfaceFrame: FrameLayout
companion object {
const val TAG = "VLC/SecondaryDisplay"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.player_remote)
surfaceView = findViewById(R.id.remote_player_surface)
subtitlesSurfaceView = findViewById(R.id.remote_subtitles_surface)
surfaceFrame = findViewById(R.id.remote_player_surface_frame)
subtitlesSurfaceView.setZOrderMediaOverlay(true)
subtitlesSurfaceView.holder.setFormat(PixelFormat.TRANSLUCENT)
if (BuildConfig.DEBUG) Log.i(TAG, "Secondary display created")
}
}
}
......@@ -25,11 +25,6 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.KeyguardManager;
import android.app.PictureInPictureParams;
import androidx.annotation.StringRes;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothHeadset;
import android.content.BroadcastReceiver;
......@@ -40,8 +35,6 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import androidx.databinding.BindingAdapter;
import androidx.databinding.DataBindingUtil;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.media.AudioManager;
......@@ -52,20 +45,6 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import androidx.annotation.NonNull;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.FragmentManager;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.appcompat.widget.PopupMenu;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.ViewStubCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import android.os.RemoteException;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.DisplayMetrics;
......@@ -80,8 +59,8 @@ import android.view.Surface;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.View.OnLayoutChangeListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
......@@ -99,12 +78,15 @@ import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.snackbar.Snackbar;
import org.jetbrains.annotations.Nullable;
import org.videolan.libvlc.IVLCVout;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
import org.videolan.libvlc.RendererItem;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.libvlc.util.DisplayManager;
import org.videolan.medialibrary.Medialibrary;
import org.videolan.medialibrary.Tools;
import org.videolan.medialibrary.media.MediaWrapper;
......@@ -147,6 +129,24 @@ import java.util.Date;
import java.util.List;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.ViewStubCompat;
import androidx.databinding.BindingAdapter;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class VideoPlayerActivity extends AppCompatActivity implements IVLCVout.Callback,
IVLCVout.OnNewVideoLayoutListener, IPlaybackSettingsController,
PlaybackService.Client.Callback, PlaybackService.Callback,PlaylistAdapter.IPlayer,
......@@ -327,7 +327,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVLCVout.C
audioBoostEnabled = mSettings.getBoolean("audio_boost", false);
mEnableCloneMode = mSettings.getBoolean("enable_clone_mode", false);
mDisplayManager = new DisplayManager(this, mEnableCloneMode, mIsBenchmark);
mDisplayManager = new DisplayManager(this, AndroidDevices.isChromeBook ? RendererDelegate.INSTANCE.getSelectedRenderer() : null, false, mEnableCloneMode, mIsBenchmark);
setContentView(mDisplayManager.isPrimary() ? R.layout.player : R.layout.player_remote_control);
/** initialize Views an their Events */
......@@ -766,7 +766,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVLCVout.C
surfaceFrameAddLayoutListener(true);
/* Listen for changes to media routes. */
if (!mIsBenchmark) mDisplayManager.mediaRouterAddCallback(true);
if (!mIsBenchmark) mDisplayManager.setMediaRouterCallback();
if (mRootView != null) mRootView.setKeepScreenOn(true);
}
......@@ -834,7 +834,7 @@ public class VideoPlayerActivity extends AppCompatActivity implements IVLCVout.C
if (mRootView != null) mRootView.setKeepScreenOn(false);
/* Stop listening for changes to media routes. */
if (!mIsBenchmark) mDisplayManager.mediaRouterAddCallback(false);
if (!mIsBenchmark) mDisplayManager.removeMediaRouterCallback();
surfaceFrameAddLayoutListener(false);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment