Add SubtitleView and CaptionStyleCompat to ExoPlayer.

This commit is contained in:
Oliver Woodman 2014-09-11 16:30:39 +01:00
parent e4b35e884a
commit 6c3ae7f175
6 changed files with 519 additions and 15 deletions

View File

@ -24,13 +24,20 @@ import com.google.android.exoplayer.demo.full.player.DefaultRendererBuilder;
import com.google.android.exoplayer.demo.full.player.DemoPlayer;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.full.player.SmoothStreamingRendererBuilder;
import com.google.android.exoplayer.text.CaptionStyleCompat;
import com.google.android.exoplayer.text.SubtitleView;
import com.google.android.exoplayer.util.Util;
import com.google.android.exoplayer.util.VerboseLogUtil;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
@ -38,6 +45,8 @@ import android.view.SurfaceHolder;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.view.accessibility.CaptioningManager;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.PopupMenu;
@ -50,6 +59,7 @@ import android.widget.TextView;
public class FullPlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
DemoPlayer.Listener, DemoPlayer.TextListener {
private static final float CAPTION_LINE_HEIGHT_RATIO = 0.0533f;
private static final int MENU_GROUP_TRACKS = 1;
private static final int ID_OFFSET = 2;
@ -60,7 +70,7 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
private VideoSurfaceView surfaceView;
private TextView debugTextView;
private TextView playerStateTextView;
private TextView subtitlesTextView;
private SubtitleView subtitleView;
private Button videoButton;
private Button audioButton;
private Button textButton;
@ -108,7 +118,7 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
debugTextView = (TextView) findViewById(R.id.debug_text_view);
playerStateTextView = (TextView) findViewById(R.id.player_state_view);
subtitlesTextView = (TextView) findViewById(R.id.subtitles);
subtitleView = (SubtitleView) findViewById(R.id.subtitles);
mediaController = new MediaController(this);
mediaController.setAnchorView(root);
@ -122,6 +132,7 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
@Override
public void onResume() {
super.onResume();
configureSubtitleView();
preparePlayer();
}
@ -380,10 +391,10 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
@Override
public void onText(String text) {
if (TextUtils.isEmpty(text)) {
subtitlesTextView.setVisibility(View.INVISIBLE);
subtitleView.setVisibility(View.INVISIBLE);
} else {
subtitlesTextView.setVisibility(View.VISIBLE);
subtitlesTextView.setText(text);
subtitleView.setVisibility(View.VISIBLE);
subtitleView.setText(text);
}
}
@ -409,4 +420,40 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
}
}
private void configureSubtitleView() {
CaptionStyleCompat captionStyle;
float captionTextSize = getCaptionFontSize();
if (Util.SDK_INT >= 19) {
captionStyle = getUserCaptionStyleV19();
captionTextSize *= getUserCaptionFontScaleV19();
} else {
captionStyle = CaptionStyleCompat.DEFAULT;
}
subtitleView.setStyle(captionStyle);
subtitleView.setTextSize(captionTextSize);
}
private float getCaptionFontSize() {
Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
Point displaySize = new Point();
display.getSize(displaySize);
return Math.max(getResources().getDimension(R.dimen.subtitle_minimum_font_size),
CAPTION_LINE_HEIGHT_RATIO * Math.min(displaySize.x, displaySize.y));
}
@TargetApi(19)
private float getUserCaptionFontScaleV19() {
CaptioningManager captioningManager =
(CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
return captioningManager.getFontScale();
}
@TargetApi(19)
private CaptionStyleCompat getUserCaptionStyleV19() {
CaptioningManager captioningManager =
(CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
}
}

View File

@ -24,15 +24,13 @@
android:layout_height="match_parent"
android:layout_gravity="center"/>
<TextView android:id="@+id/subtitles"
android:layout_width="match_parent"
<com.google.android.exoplayer.text.SubtitleView android:id="@+id/subtitles"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom="32dp"
android:gravity="center"
android:textSize="20sp"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="32dp"
android:visibility="invisible"/>
<View android:id="@+id/shutter"

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The minimum subtitle font size. -->
<dimen name="subtitle_minimum_font_size">13sp</dimen>
</resources>

View File

@ -95,8 +95,8 @@ import com.google.android.exoplayer.upstream.NonBlockingInputStream;
// likely. The choice of 25% is relatively arbitrary.
int tableSize = (sampleCount * 125) / 100;
sampleSizeTable = new int[tableSize];
sampleDecodingTimeTable = new int[tableSize];
sampleCompositionTimeOffsetTable = new int[tableSize];
sampleDecodingTimeTable = new long[tableSize];
sampleIsSyncFrameTable = new boolean[tableSize];
sampleHasSubsampleEncryptionTable = new boolean[tableSize];
}
@ -147,7 +147,7 @@ import com.google.android.exoplayer.upstream.NonBlockingInputStream;
return true;
}
public int getSamplePresentationTime(int index) {
public long getSamplePresentationTime(int index) {
return sampleDecodingTimeTable[index] + sampleCompositionTimeOffsetTable[index];
}

View File

@ -0,0 +1,142 @@
/*
* 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.text;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.graphics.Color;
import android.graphics.Typeface;
import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle;
/**
* A compatibility wrapper for {@link CaptionStyle}.
*/
public final class CaptionStyleCompat {
/**
* Edge type value specifying no character edges.
*/
public static final int EDGE_TYPE_NONE = 0;
/**
* Edge type value specifying uniformly outlined character edges.
*/
public static final int EDGE_TYPE_OUTLINE = 1;
/**
* Edge type value specifying drop-shadowed character edges.
*/
public static final int EDGE_TYPE_DROP_SHADOW = 2;
/**
* Edge type value specifying raised bevel character edges.
*/
public static final int EDGE_TYPE_RAISED = 3;
/**
* Edge type value specifying depressed bevel character edges.
*/
public static final int EDGE_TYPE_DEPRESSED = 4;
/**
* Use color setting specified by the track and fallback to default caption style.
*/
public static final int USE_TRACK_COLOR_SETTINGS = 1;
/**
* Default caption style.
*/
public static final CaptionStyleCompat DEFAULT = new CaptionStyleCompat(
Color.WHITE, Color.BLACK, Color.TRANSPARENT, EDGE_TYPE_NONE, Color.WHITE, null);
/**
* The preferred foreground color.
*/
public final int foregroundColor;
/**
* The preferred background color.
*/
public final int backgroundColor;
/**
* The preferred window color.
*/
public final int windowColor;
/**
* The preferred edge type. One of:
* <ul>
* <li>{@link #EDGE_TYPE_NONE}
* <li>{@link #EDGE_TYPE_OUTLINE}
* <li>{@link #EDGE_TYPE_DROP_SHADOW}
* <li>{@link #EDGE_TYPE_RAISED}
* <li>{@link #EDGE_TYPE_DEPRESSED}
* </ul>
*/
public final int edgeType;
/**
* The preferred edge color, if using an edge type other than {@link #EDGE_TYPE_NONE}.
*/
public final int edgeColor;
/**
* The preferred typeface.
*/
public final Typeface typeface;
/**
* Creates a {@link CaptionStyleCompat} equivalent to a provided {@link CaptionStyle}.
*
* @param style A {@link CaptionStyle}.
* @return The equivalent {@link CaptionStyleCompat}.
*/
@TargetApi(19)
public static CaptionStyleCompat createFromCaptionStyle(CaptionStyle style) {
int windowColor = Util.SDK_INT >= 21 ? getWindowColorV21(style) : Color.TRANSPARENT;
return new CaptionStyleCompat(style.foregroundColor, style.backgroundColor, windowColor,
style.edgeType, style.edgeColor, style.getTypeface());
}
/**
* @param foregroundColor See {@link #foregroundColor}.
* @param backgroundColor See {@link #backgroundColor}.
* @param windowColor See {@link #windowColor}.
* @param edgeType See {@link #edgeType}.
* @param edgeColor See {@link #edgeColor}.
* @param typeface See {@link #typeface}.
*/
public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, int edgeType,
int edgeColor, Typeface typeface) {
this.foregroundColor = foregroundColor;
this.backgroundColor = backgroundColor;
this.windowColor = windowColor;
this.edgeType = edgeType;
this.edgeColor = edgeColor;
this.typeface = typeface;
}
@SuppressWarnings("unused")
@TargetApi(21)
private static int getWindowColorV21(CaptioningManager.CaptionStyle captionStyle) {
// TODO: Uncomment when building against API level 21.
return Color.TRANSPARENT; //captionStyle.windowColor;
}
}

View File

@ -0,0 +1,295 @@
/*
* 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.text;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Join;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
/**
* A view for rendering captions.
* <p>
* The caption style and text size can be configured using {@link #setStyle(CaptionStyleCompat)} and
* {@link #setTextSize(float)} respectively.
*/
public class SubtitleView extends View {
/**
* Ratio of inner padding to font size.
*/
private static final float INNER_PADDING_RATIO = 0.125f;
/**
* Temporary rectangle used for computing line bounds.
*/
private final RectF lineBounds = new RectF();
/**
* Reusable string builder used for holding text.
*/
private final StringBuilder textBuilder = new StringBuilder();
// Styled dimensions.
private final float cornerRadius;
private final float outlineWidth;
private final float shadowRadius;
private final float shadowOffset;
private TextPaint textPaint;
private Paint paint;
private int foregroundColor;
private int backgroundColor;
private int edgeColor;
private int edgeType;
private boolean hasMeasurements;
private int lastMeasuredWidth;
private StaticLayout layout;
private float spacingMult;
private float spacingAdd;
private int innerPaddingX;
public SubtitleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
int[] viewAttr = {android.R.attr.text, android.R.attr.textSize,
android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier};
TypedArray a = context.obtainStyledAttributes(attrs, viewAttr, defStyleAttr, 0);
CharSequence text = a.getText(0);
int textSize = a.getDimensionPixelSize(1, 15);
spacingAdd = a.getDimensionPixelSize(2, 0);
spacingMult = a.getFloat(3, 1);
a.recycle();
Resources resources = getContext().getResources();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
int twoDpInPx = Math.round((2 * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
cornerRadius = twoDpInPx;
outlineWidth = twoDpInPx;
shadowRadius = twoDpInPx;
shadowOffset = twoDpInPx;
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setSubpixelText(true);
paint = new Paint();
paint.setAntiAlias(true);
innerPaddingX = 0;
setText(text);
setTextSize(textSize);
setStyle(CaptionStyleCompat.DEFAULT);
}
public SubtitleView(Context context) {
this(context, null);
}
@Override
public void setBackgroundColor(int color) {
backgroundColor = color;
invalidate();
}
/**
* Sets the text to be displayed by the view.
*
* @param text The text to display.
*/
public void setText(CharSequence text) {
textBuilder.setLength(0);
textBuilder.append(text);
hasMeasurements = false;
requestLayout();
}
/**
* Sets the text size in pixels.
*
* @param size The text size in pixels.
*/
public void setTextSize(float size) {
if (textPaint.getTextSize() != size) {
textPaint.setTextSize(size);
innerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
hasMeasurements = false;
requestLayout();
invalidate();
}
}
/**
* Configures the view according to the given style.
*
* @param style A style for the view.
*/
public void setStyle(CaptionStyleCompat style) {
foregroundColor = style.foregroundColor;
backgroundColor = style.backgroundColor;
edgeType = style.edgeType;
edgeColor = style.edgeColor;
setTypeface(style.typeface);
super.setBackgroundColor(style.windowColor);
hasMeasurements = false;
requestLayout();
}
private void setTypeface(Typeface typeface) {
if (textPaint.getTypeface() != typeface) {
textPaint.setTypeface(typeface);
hasMeasurements = false;
requestLayout();
invalidate();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthSpec = MeasureSpec.getSize(widthMeasureSpec);
if (computeMeasurements(widthSpec)) {
final StaticLayout layout = this.layout;
final int paddingX = getPaddingLeft() + getPaddingRight() + innerPaddingX * 2;
final int height = layout.getHeight() + getPaddingTop() + getPaddingBottom();
int width = 0;
int lineCount = layout.getLineCount();
for (int i = 0; i < lineCount; i++) {
width = Math.max((int) Math.ceil(layout.getLineWidth(i)), width);
}
width += paddingX;
setMeasuredDimension(width, height);
} else if (Util.SDK_INT >= 11) {
setTooSmallMeasureDimensionV11();
} else {
setMeasuredDimension(0, 0);
}
}
@TargetApi(11)
private void setTooSmallMeasureDimensionV11() {
setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
}
@Override
public void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
computeMeasurements(width);
}
private boolean computeMeasurements(int maxWidth) {
if (hasMeasurements && maxWidth == lastMeasuredWidth) {
return true;
}
// Account for padding.
final int paddingX = getPaddingLeft() + getPaddingRight() + innerPaddingX * 2;
maxWidth -= paddingX;
if (maxWidth <= 0) {
return false;
}
hasMeasurements = true;
lastMeasuredWidth = maxWidth;
layout = new StaticLayout(textBuilder, textPaint, maxWidth, null, spacingMult, spacingAdd,
true);
return true;
}
@Override
protected void onDraw(Canvas c) {
final StaticLayout layout = this.layout;
if (layout == null) {
return;
}
final int saveCount = c.save();
final int innerPaddingX = this.innerPaddingX;
c.translate(getPaddingLeft() + innerPaddingX, getPaddingTop());
final int lineCount = layout.getLineCount();
final Paint textPaint = this.textPaint;
final Paint paint = this.paint;
final RectF bounds = lineBounds;
if (Color.alpha(backgroundColor) > 0) {
final float cornerRadius = this.cornerRadius;
float previousBottom = layout.getLineTop(0);
paint.setColor(backgroundColor);
paint.setStyle(Style.FILL);
for (int i = 0; i < lineCount; i++) {
bounds.left = layout.getLineLeft(i) - innerPaddingX;
bounds.right = layout.getLineRight(i) + innerPaddingX;
bounds.top = previousBottom;
bounds.bottom = layout.getLineBottom(i);
previousBottom = bounds.bottom;
c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
}
}
if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) {
textPaint.setStrokeJoin(Join.ROUND);
textPaint.setStrokeWidth(outlineWidth);
textPaint.setColor(edgeColor);
textPaint.setStyle(Style.FILL_AND_STROKE);
layout.draw(c);
} else if (edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) {
textPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, edgeColor);
} else if (edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED
|| edgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) {
boolean raised = edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED;
int colorUp = raised ? Color.WHITE : edgeColor;
int colorDown = raised ? edgeColor : Color.WHITE;
float offset = shadowRadius / 2f;
textPaint.setColor(foregroundColor);
textPaint.setStyle(Style.FILL);
textPaint.setShadowLayer(shadowRadius, -offset, -offset, colorUp);
layout.draw(c);
textPaint.setShadowLayer(shadowRadius, offset, offset, colorDown);
}
textPaint.setColor(foregroundColor);
textPaint.setStyle(Style.FILL);
layout.draw(c);
textPaint.setShadowLayer(0, 0, 0, 0);
c.restoreToCount(saveCount);
}
}