Change how subtitles are laid out in the demo app.

The SubtitleLayout is now properly aligned with the surface.
This means the subtitles remain on top of the video in portrait
mode, rather than being huge and below it.
This commit is contained in:
Oliver Woodman 2015-07-15 11:09:20 +01:00
parent fe52a486f4
commit c1e1497d13
8 changed files with 144 additions and 148 deletions

View File

@ -15,8 +15,8 @@
*/
package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.AspectRatioFrameLayout;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.VideoSurfaceView;
import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
import com.google.android.exoplayer.demo.player.DashRendererBuilder;
@ -55,6 +55,7 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
@ -109,7 +110,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
private MediaController mediaController;
private View debugRootView;
private View shutterView;
private VideoSurfaceView surfaceView;
private AspectRatioFrameLayout videoFrame;
private SurfaceView surfaceView;
private TextView debugTextView;
private TextView playerStateTextView;
private SubtitleLayout subtitleLayout;
@ -170,7 +172,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
shutterView = findViewById(R.id.shutter);
debugRootView = findViewById(R.id.controls_root);
surfaceView = (VideoSurfaceView) findViewById(R.id.surface_view);
videoFrame = (AspectRatioFrameLayout) findViewById(R.id.video_frame);
surfaceView = (SurfaceView) findViewById(R.id.surface_view);
surfaceView.getHolder().addCallback(this);
debugTextView = (TextView) findViewById(R.id.debug_text_view);
@ -364,7 +367,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
@Override
public void onVideoSizeChanged(int width, int height, float pixelWidthAspectRatio) {
shutterView.setVisibility(View.GONE);
surfaceView.setVideoWidthHeightRatio(
videoFrame.setAspectRatio(
height == 0 ? 1 : (width * pixelWidthAspectRatio) / height);
}

View File

@ -21,22 +21,26 @@
android:layout_height="match_parent"
android:keepScreenOn="true">
<com.google.android.exoplayer.VideoSurfaceView android:id="@+id/surface_view"
<com.google.android.exoplayer.AspectRatioFrameLayout android:id="@+id/video_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
android:layout_gravity="center">
<com.google.android.exoplayer.text.SubtitleLayout android:id="@+id/subtitles"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="32dp"/>
<SurfaceView android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
<View android:id="@+id/shutter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
<com.google.android.exoplayer.text.SubtitleLayout android:id="@+id/subtitles"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<View android:id="@+id/shutter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
</com.google.android.exoplayer.AspectRatioFrameLayout>
<LinearLayout
android:layout_width="match_parent"

View File

@ -15,10 +15,10 @@
*/
package com.google.android.exoplayer.demo.webm;
import com.google.android.exoplayer.AspectRatioFrameLayout;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.VideoSurfaceView;
import com.google.android.exoplayer.ext.opus.LibopusAudioTrackRenderer;
import com.google.android.exoplayer.ext.vp9.LibvpxVideoTrackRenderer;
import com.google.android.exoplayer.ext.vp9.VpxDecoderException;
@ -37,6 +37,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
@ -69,7 +70,8 @@ public class VideoPlayer extends Activity implements OnClickListener,
private ExoPlayer player;
private Handler handler;
private MediaController mediaController;
private VideoSurfaceView videoSurfaceView;
private AspectRatioFrameLayout videoFrame;
private SurfaceView surfaceView;
private VpxVideoSurfaceView vpxVideoSurfaceView;
private TextView debugInfoView;
private TextView playerStateView;
@ -101,7 +103,8 @@ public class VideoPlayer extends Activity implements OnClickListener,
mediaController = new MediaController(this);
mediaController.setAnchorView(root);
videoSurfaceView = (VideoSurfaceView) findViewById(R.id.surface_view);
videoFrame = (AspectRatioFrameLayout) findViewById(R.id.video_frame);
surfaceView = (SurfaceView) findViewById(R.id.surface_view);
vpxVideoSurfaceView = (VpxVideoSurfaceView) findViewById(R.id.vpx_surface_view);
debugInfoView = (TextView) findViewById(R.id.debug_info);
playerStateView = (TextView) findViewById(R.id.player_state);
@ -171,11 +174,11 @@ public class VideoPlayer extends Activity implements OnClickListener,
if (useOpenGL) {
player.sendMessage(videoRenderer, LibvpxVideoTrackRenderer.MSG_SET_VPX_SURFACE_VIEW,
vpxVideoSurfaceView);
videoSurfaceView.setVisibility(View.GONE);
surfaceView.setVisibility(View.GONE);
} else {
player.sendMessage(
videoRenderer, LibvpxVideoTrackRenderer.MSG_SET_SURFACE,
videoSurfaceView.getHolder().getSurface());
surfaceView.getHolder().getSurface());
vpxVideoSurfaceView.setVisibility(View.GONE);
}
TrackRenderer audioRenderer = new LibopusAudioTrackRenderer(sampleSource);
@ -192,7 +195,7 @@ public class VideoPlayer extends Activity implements OnClickListener,
}
public void onRenderersBuilt(TrackRenderer[] renderers) {
videoSurfaceView.setVisibility(View.GONE);
surfaceView.setVisibility(View.GONE);
player = ExoPlayer.Factory.newInstance(renderers.length);
player.addListener(this);
mediaController.setMediaPlayer(new PlayerControl(player));
@ -210,11 +213,7 @@ public class VideoPlayer extends Activity implements OnClickListener,
@Override
public void onVideoSizeChanged(int width, int height) {
if (isDash || useOpenGL) {
vpxVideoSurfaceView.setVideoWidthHeightRatio(height == 0 ? 1 : (width * 1.0f) / height);
} else {
videoSurfaceView.setVideoWidthHeightRatio(height == 0 ? 1 : (width * 1.0f) / height);
}
videoFrame.setAspectRatio(height == 0 ? 1 : (width * 1.0f) / height);
debugInfoView.setText("Video: " + width + " x " + height);
}

View File

@ -20,17 +20,24 @@
android:orientation="vertical"
android:keepScreenOn="true">
<com.google.android.exoplayer.VideoSurfaceView
android:id="@+id/surface_view"
<com.google.android.exoplayer.AspectRatioFrameLayout android:id="@+id/video_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center|center_vertical"/>
android:layout_gravity="center">
<com.google.android.exoplayer.ext.vp9.VpxVideoSurfaceView
android:id="@+id/vpx_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center|center_vertical"/>
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center|center_vertical"/>
<com.google.android.exoplayer.ext.vp9.VpxVideoSurfaceView
android:id="@+id/vpx_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center|center_vertical"/>
</com.google.android.exoplayer.AspectRatioFrameLayout>
<LinearLayout
android:layout_width="wrap_content"

View File

@ -28,12 +28,8 @@ import android.util.AttributeSet;
@TargetApi(11)
public class VpxVideoSurfaceView extends GLSurfaceView {
private static final float MAX_ASPECT_RATIO_DEFORMATION_PERCENT = 0.01f;
private final VpxRenderer renderer;
private float videoAspectRatio;
public VpxVideoSurfaceView(Context context) {
this(context, null);
}
@ -52,33 +48,4 @@ public class VpxVideoSurfaceView extends GLSurfaceView {
requestRender();
}
/**
* Set the aspect ratio that this view should satisfy.
*
* @param widthHeightRatio The width to height ratio.
*/
public void setVideoWidthHeightRatio(float widthHeightRatio) {
if (this.videoAspectRatio != widthHeightRatio) {
this.videoAspectRatio = widthHeightRatio;
requestLayout();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
if (videoAspectRatio != 0) {
float viewAspectRatio = (float) width / height;
float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
if (aspectDeformation > MAX_ASPECT_RATIO_DEFORMATION_PERCENT) {
height = (int) (width / videoAspectRatio);
} else if (aspectDeformation < -MAX_ASPECT_RATIO_DEFORMATION_PERCENT) {
width = (int) (height * videoAspectRatio);
}
}
setMeasuredDimension(width, height);
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
/**
* A {@link FrameLayout} that resizes itself to match a specified aspect ratio.
*/
public final class AspectRatioFrameLayout extends FrameLayout {
/**
* The {@link FrameLayout} will not resize itself if the fractional difference between its natural
* aspect ratio and the requested aspect ratio falls below this threshold.
* <p>
* This tolerance allows the view to occupy the whole of the screen when the requested aspect
* ratio is very close, but not exactly equal to, the aspect ratio of the screen. This may reduce
* the number of view layers that need to be composited by the underlying system, which can help
* to reduce power consumption.
*/
private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f;
private float videoAspectRatio;
public AspectRatioFrameLayout(Context context) {
super(context);
}
public AspectRatioFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Set the aspect ratio that this view should satisfy.
*
* @param widthHeightRatio The width to height ratio.
*/
public void setAspectRatio(float widthHeightRatio) {
if (this.videoAspectRatio != widthHeightRatio) {
this.videoAspectRatio = widthHeightRatio;
requestLayout();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (videoAspectRatio == 0) {
// Aspect ratio not set.
return;
}
int width = getMeasuredWidth();
int height = getMeasuredHeight();
float viewAspectRatio = (float) width / height;
float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) {
// We're within the allowed tolerance.
return;
}
if (aspectDeformation > 0) {
height = (int) (width / videoAspectRatio);
} else {
width = (int) (height * videoAspectRatio);
}
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceView;
/**
* A SurfaceView that resizes itself to match a specified aspect ratio.
*/
public class VideoSurfaceView extends SurfaceView {
/**
* The surface view will not resize itself if the fractional difference between its default
* aspect ratio and the aspect ratio of the video falls below this threshold.
* <p>
* This tolerance is useful for fullscreen playbacks, since it ensures that the surface will
* occupy the whole of the screen when playing content that has the same (or virtually the same)
* aspect ratio as the device. This typically reduces the number of view layers that need to be
* composited by the underlying system, which can help to reduce power consumption.
*/
private static final float MAX_ASPECT_RATIO_DEFORMATION_PERCENT = 0.01f;
private float videoAspectRatio;
public VideoSurfaceView(Context context) {
super(context);
}
public VideoSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Set the aspect ratio that this {@link VideoSurfaceView} should satisfy.
*
* @param widthHeightRatio The width to height ratio.
*/
public void setVideoWidthHeightRatio(float widthHeightRatio) {
if (this.videoAspectRatio != widthHeightRatio) {
this.videoAspectRatio = widthHeightRatio;
requestLayout();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
if (videoAspectRatio != 0) {
float viewAspectRatio = (float) width / height;
float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
if (aspectDeformation > MAX_ASPECT_RATIO_DEFORMATION_PERCENT) {
height = (int) (width / videoAspectRatio);
} else if (aspectDeformation < -MAX_ASPECT_RATIO_DEFORMATION_PERCENT) {
width = (int) (height * videoAspectRatio);
}
}
setMeasuredDimension(width, height);
}
}

View File

@ -32,7 +32,13 @@ public final class SubtitleLayout extends ViewGroup {
* Use the same line height ratio as WebVtt to match the display with the preview.
* WebVtt specifies line height as 5.3% of the viewport height.
*/
private static final float LINE_HEIGHT_RATIO = 0.0533f;
private static final float LINE_HEIGHT_FRACTION = 0.0533f;
/**
* The default bottom padding to apply when {@link Cue#line} is {@link Cue#UNSET_VALUE}, as a
* fraction of the viewport height.
*/
private static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f;
private final List<SubtitleView> subtitleViews;
@ -145,7 +151,8 @@ public final class SubtitleLayout extends ViewGroup {
int viewLeft = (width - subtitleView.getMeasuredWidth()) / 2;
int viewRight = viewLeft + subtitleView.getMeasuredWidth();
int viewTop = bottom - subtitleView.getMeasuredHeight();
int viewTop = bottom - subtitleView.getMeasuredHeight()
- (int) (height * DEFAULT_BOTTOM_PADDING_FRACTION);
int viewBottom = bottom;
if (subtitleCue.alignment != null) {
@ -176,7 +183,7 @@ public final class SubtitleLayout extends ViewGroup {
}
private void updateSubtitlesTextSize(int height) {
textSize = LINE_HEIGHT_RATIO * height * fontScale;
textSize = LINE_HEIGHT_FRACTION * height * fontScale;
}
private SubtitleView createSubtitleView() {