Commit 04e4a564 authored by Geoffrey Métais's avatar Geoffrey Métais

LibVLC: Implement VideoHelper

parent a9192ff3
......@@ -6,12 +6,12 @@
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:inflatedId="@+id/surface_video"
android:layout="@layout/surface_view"
android:id="@+id/surface_stub" />
......@@ -19,11 +19,13 @@
android:layout_width="1dp"
android:layout_height="1dp"
android:layout="@layout/surface_view"
android:inflatedId="@+id/surface_subtitles"
android:id="@+id/subtitles_surface_stub" />
<ViewStub
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/texture_view"
android:inflatedId="@+id/texture_video"
android:id="@+id/texture_stub" />
</FrameLayout>
......@@ -33,14 +33,17 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.RequiresApi;
import android.util.SparseArray;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.libvlc.util.DisplayManager;
import org.videolan.libvlc.util.VLCUtil;
import org.videolan.libvlc.util.VLCVideoLayout;
import java.io.File;
import androidx.annotation.RequiresApi;
@SuppressWarnings("unused, JniMissingFunction")
public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
......@@ -359,6 +362,15 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
private native boolean nativeSetAmp(int index, float amp);
}
//Video size constants
public static final int SURFACE_BEST_FIT = 0;
public static final int SURFACE_FIT_SCREEN = 1;
public static final int SURFACE_FILL = 2;
public static final int SURFACE_16_9 = 3;
public static final int SURFACE_4_3 = 4;
public static final int SURFACE_ORIGINAL = 5;
public static final int SURFACE_SIZE_COUNT = 6;
private Media mMedia = null;
private boolean mPlaying = false;
private boolean mPlayRequested = false;
......@@ -374,6 +386,9 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
private boolean mCanDoPassthrough;
// Video tools
private VideoHelper mVideoHelper = null;
interface SurfaceListener {
void onSurfaceCreated();
void onSurfaceDestroyed();
......@@ -573,6 +588,30 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
return mWindow;
}
public void attachViews(VLCVideoLayout surfaceFrame, DisplayManager dm, boolean subtitles, boolean textureView) {
mVideoHelper = new VideoHelper(this, surfaceFrame, dm, subtitles, textureView);
mVideoHelper.attachViews();
}
public void detachViews() {
if (mVideoHelper != null) {
mVideoHelper.release();
mVideoHelper = null;
}
}
public void changeSurfaceLayout() {
if (mVideoHelper != null) mVideoHelper.updateVideoSurfaces();
}
public void setVideoSurfacesize(int size) {
if (mVideoHelper != null) mVideoHelper.setVideoSurfacesize(size);
}
public int getVideoSurfacesize() {
return mVideoHelper != null ? mVideoHelper.getVideoSurfacesize() : SURFACE_BEST_FIT;
}
/**
* Set a Media
*
......
package org.videolan.libvlc;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.FrameLayout;
import org.videolan.R;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.libvlc.util.DisplayManager;
import org.videolan.libvlc.util.VLCVideoLayout;
class VideoHelper implements IVLCVout.OnNewVideoLayoutListener {
private static final String TAG = "LibVLC/VideoHelper";
private int mCurrentSize = MediaPlayer.SURFACE_BEST_FIT;
private int mVideoHeight = 0;
private int mVideoWidth = 0;
private int mVideoVisibleHeight = 0;
private int mVideoVisibleWidth = 0;
private int mVideoSarNum = 0;
private int mVideoSarDen = 0;
private FrameLayout mVideoLayout;
private FrameLayout mVideoSurfaceFrame;
private SurfaceView mVideoSurface = null;
private SurfaceView mSubtitlesSurface = null;
private TextureView mVideoTexture = null;
private final Handler mHandler = new Handler();
private View.OnLayoutChangeListener mOnLayoutChangeListener = null;
private DisplayManager mDisplayManager;
private org.videolan.libvlc.MediaPlayer mMediaPlayer;
VideoHelper(MediaPlayer player, VLCVideoLayout surfaceFrame, DisplayManager dm, boolean subtitles, boolean textureView) {
init(player, surfaceFrame, dm, subtitles, !textureView);
}
private void init(MediaPlayer player, VLCVideoLayout surfaceFrame, DisplayManager dm, boolean subtitles, boolean useSurfaceView) {
mMediaPlayer = player;
mDisplayManager = dm;
mVideoLayout = surfaceFrame;
final boolean isPrimary = mDisplayManager == null || mDisplayManager.isPrimary();
if (isPrimary) {
mVideoSurfaceFrame = mVideoLayout.findViewById(R.id.player_surface_frame);
if (useSurfaceView) {
ViewStub stub = mVideoSurfaceFrame.findViewById(R.id.surface_stub);
mVideoSurface = stub != null ? (SurfaceView) stub.inflate() : (SurfaceView) mVideoSurfaceFrame.findViewById(R.id.surface_video);
if (subtitles) {
stub = mVideoSurfaceFrame.findViewById(R.id.subtitles_surface_stub);
mSubtitlesSurface = stub != null ? (SurfaceView) stub.inflate() : (SurfaceView) mVideoSurfaceFrame.findViewById(R.id.surface_subtitles);
mSubtitlesSurface.setZOrderMediaOverlay(true);
mSubtitlesSurface.getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
} else {
final ViewStub stub = mVideoSurfaceFrame.findViewById(R.id.texture_stub);
mVideoTexture = stub != null ? (TextureView) stub.inflate() : (TextureView) mVideoSurfaceFrame.findViewById(R.id.texture_video);;
}
} else if (mDisplayManager.getPresentation() != null){
mVideoSurfaceFrame = mDisplayManager.getPresentation().getSurfaceFrame();
mVideoSurface = mDisplayManager.getPresentation().getSurfaceView();
mSubtitlesSurface = mDisplayManager.getPresentation().getSubtitlesSurfaceView();
}
}
public void release() {
if (mMediaPlayer.getVLCVout().areViewsAttached()) detachViews();
mMediaPlayer = null;
mHandler.removeCallbacks(null);
mVideoSurfaceFrame = null;
mVideoSurface = null;
mSubtitlesSurface = null;
mVideoTexture = null;
}
public void attachViews() {
final IVLCVout vlcVout = mMediaPlayer.getVLCVout();
if (mVideoSurface != null) {
vlcVout.setVideoView(mVideoSurface);
if (mSubtitlesSurface != null)
vlcVout.setSubtitlesView(mSubtitlesSurface);
} else
vlcVout.setVideoView(mVideoTexture);
vlcVout.attachViews(this);
if (mOnLayoutChangeListener == null) {
mOnLayoutChangeListener = new View.OnLayoutChangeListener() {
private final Runnable runnable = new Runnable() {
@Override
public void run() {
if (mVideoSurfaceFrame != null) updateVideoSurfaces();
}
};
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
mHandler.removeCallbacks(runnable);
mHandler.post(runnable);
}
}
};
}
mVideoSurfaceFrame.addOnLayoutChangeListener(mOnLayoutChangeListener);
mMediaPlayer.setVideoTrackEnabled(true);
}
public void detachViews() {
mMediaPlayer.setVideoTrackEnabled(false);
if (mOnLayoutChangeListener != null) {
mVideoSurfaceFrame.removeOnLayoutChangeListener(mOnLayoutChangeListener);
mOnLayoutChangeListener = null;
}
mMediaPlayer.getVLCVout().detachViews();
}
private void changeMediaPlayerLayout(int displayW, int displayH) {
/* Change the video placement using the MediaPlayer API */
switch (mCurrentSize) {
case MediaPlayer.SURFACE_BEST_FIT:
mMediaPlayer.setAspectRatio(null);
mMediaPlayer.setScale(0);
break;
case MediaPlayer.SURFACE_FIT_SCREEN:
case MediaPlayer.SURFACE_FILL: {
Media.VideoTrack vtrack = mMediaPlayer.getCurrentVideoTrack();
if (vtrack == null)
return;
final boolean videoSwapped = vtrack.orientation == Media.VideoTrack.Orientation.LeftBottom
|| vtrack.orientation == Media.VideoTrack.Orientation.RightTop;
if (mCurrentSize == MediaPlayer.SURFACE_FIT_SCREEN) {
int videoW = vtrack.width;
int videoH = vtrack.height;
if (videoSwapped) {
int swap = videoW;
videoW = videoH;
videoH = swap;
}
if (vtrack.sarNum != vtrack.sarDen)
videoW = videoW * vtrack.sarNum / vtrack.sarDen;
float ar = videoW / (float) videoH;
float dar = displayW / (float) displayH;
float scale;
if (dar >= ar)
scale = displayW / (float) videoW; /* horizontal */
else
scale = displayH / (float) videoH; /* vertical */
mMediaPlayer.setScale(scale);
mMediaPlayer.setAspectRatio(null);
} else {
mMediaPlayer.setScale(0);
mMediaPlayer.setAspectRatio(!videoSwapped ? ""+displayW+":"+displayH
: ""+displayH+":"+displayW);
}
break;
}
case MediaPlayer.SURFACE_16_9:
mMediaPlayer.setAspectRatio("16:9");
mMediaPlayer.setScale(0);
break;
case MediaPlayer.SURFACE_4_3:
mMediaPlayer.setAspectRatio("4:3");
mMediaPlayer.setScale(0);
break;
case MediaPlayer.SURFACE_ORIGINAL:
mMediaPlayer.setAspectRatio(null);
mMediaPlayer.setScale(1);
break;
}
}
@TargetApi(Build.VERSION_CODES.N)
void updateVideoSurfaces() {
final boolean isPrimary = mDisplayManager == null || mDisplayManager.isPrimary();
final Activity activity = isPrimary && mVideoSurfaceFrame.getContext() instanceof Activity ? (Activity) mVideoSurfaceFrame.getContext() : null;
int sw;
int sh;
// get screen size
if (activity != null) {
sw = activity.getWindow().getDecorView().getWidth();
sh = activity.getWindow().getDecorView().getHeight();
} else if (mDisplayManager.getPresentation() != null && mDisplayManager.getPresentation().getWindow() != null) {
sw = mDisplayManager.getPresentation().getWindow().getDecorView().getWidth();
sh = mDisplayManager.getPresentation().getWindow().getDecorView().getHeight();
} else return;
// sanity check
if (sw * sh == 0) {
Log.e(TAG, "Invalid surface size");
return;
}
mMediaPlayer.getVLCVout().setWindowSize(sw, sh);
ViewGroup.LayoutParams lp = mVideoSurface.getLayoutParams();
if (mVideoWidth * mVideoHeight == 0 || (AndroidUtil.isNougatOrLater && activity != null && activity.isInPictureInPictureMode())) {
/* Case of OpenGL vouts: handles the placement of the video using MediaPlayer API */
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
mVideoSurface.setLayoutParams(lp);
lp = mVideoSurfaceFrame.getLayoutParams();
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
mVideoSurfaceFrame.setLayoutParams(lp);
if (mVideoWidth * mVideoHeight == 0) changeMediaPlayerLayout(sw, sh);
return;
}
if (lp.width == lp.height && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
/* We handle the placement of the video using Android View LayoutParams */
mMediaPlayer.setAspectRatio(null);
mMediaPlayer.setScale(0);
}
double dw = sw, dh = sh;
final boolean isPortrait = isPrimary && mVideoSurfaceFrame.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
if (sw > sh && isPortrait || sw < sh && !isPortrait) {
dw = sh;
dh = sw;
}
// compute the aspect ratio
double ar, vw;
if (mVideoSarDen == mVideoSarNum) {
/* No indication about the density, assuming 1:1 */
vw = mVideoVisibleWidth;
ar = (double)mVideoVisibleWidth / (double)mVideoVisibleHeight;
} else {
/* Use the specified aspect ratio */
vw = mVideoVisibleWidth * (double)mVideoSarNum / mVideoSarDen;
ar = vw / mVideoVisibleHeight;
}
// compute the display aspect ratio
double dar = dw / dh;
switch (mCurrentSize) {
case MediaPlayer.SURFACE_BEST_FIT:
if (dar < ar)
dh = dw / ar;
else
dw = dh * ar;
break;
case MediaPlayer.SURFACE_FIT_SCREEN:
if (dar >= ar)
dh = dw / ar; /* horizontal */
else
dw = dh * ar; /* vertical */
break;
case MediaPlayer.SURFACE_FILL:
break;
case MediaPlayer.SURFACE_16_9:
ar = 16.0 / 9.0;
if (dar < ar)
dh = dw / ar;
else
dw = dh * ar;
break;
case MediaPlayer.SURFACE_4_3:
ar = 4.0 / 3.0;
if (dar < ar)
dh = dw / ar;
else
dw = dh * ar;
break;
case MediaPlayer.SURFACE_ORIGINAL:
dh = mVideoVisibleHeight;
dw = vw;
break;
}
// set display size
lp.width = (int) Math.ceil(dw * mVideoWidth / mVideoVisibleWidth);
lp.height = (int) Math.ceil(dh * mVideoHeight / mVideoVisibleHeight);
mVideoSurface.setLayoutParams(lp);
if (mSubtitlesSurface != null) mSubtitlesSurface.setLayoutParams(lp);
// set frame size (crop if necessary)
lp = mVideoSurfaceFrame.getLayoutParams();
lp.width = (int) Math.floor(dw);
lp.height = (int) Math.floor(dh);
mVideoSurfaceFrame.setLayoutParams(lp);
mVideoSurface.invalidate();
if (mSubtitlesSurface != null) mSubtitlesSurface.invalidate();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void onNewVideoLayout(IVLCVout vlcVout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) {
mVideoWidth = width;
mVideoHeight = height;
mVideoVisibleWidth = visibleWidth;
mVideoVisibleHeight = visibleHeight;
mVideoSarNum = sarNum;
mVideoSarDen = sarDen;
updateVideoSurfaces();
}
void setVideoSurfacesize(int size) {
mCurrentSize = size;
updateVideoSurfaces();
}
int getVideoSurfacesize() {
return mCurrentSize;
}
}
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