diff --git a/library/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/src/main/java/com/google/android/exoplayer2/text/Cue.java index c4c5a7e4ca..84b67928bb 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.text; import android.graphics.Color; +import android.graphics.Bitmap; import android.support.annotation.IntDef; import android.text.Layout.Alignment; import java.lang.annotation.Retention; @@ -78,7 +79,8 @@ public class Cue { public static final int LINE_TYPE_NUMBER = 1; /** - * The cue text. Note the {@link CharSequence} may be decorated with styling spans. + * The cue text, or null if this is an image cue. Note the {@link CharSequence} may be decorated + * with styling spans. */ public final CharSequence text; @@ -87,6 +89,11 @@ public class Cue { */ public final Alignment textAlignment; + /** + * The cue image, or null if this is a text cue. + */ + public final Bitmap bitmap; + /** * The position of the {@link #lineAnchor} of the cue box within the viewport in the direction * orthogonal to the writing direction, or {@link #DIMEN_UNSET}. When set, the interpretation of @@ -95,8 +102,8 @@ public class Cue { * For horizontal text and {@link #lineType} equal to {@link #LINE_TYPE_FRACTION}, this is the * fractional vertical position relative to the top of the viewport. */ - public final float line; + /** * The type of the {@link #line} value. *

@@ -122,9 +129,8 @@ public class Cue { * {@code (line == -2 && lineAnchor == ANCHOR_TYPE_START)} position a cue so that only its first * line is visible at the bottom of the viewport. */ + @LineType public final int lineType; - @LineType - public final int lineType; /** * The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START}, * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. @@ -133,9 +139,8 @@ public class Cue { * and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box * respectively. */ + @AnchorType public final int lineAnchor; - @AnchorType - public final int lineAnchor; /** * The fractional position of the {@link #positionAnchor} of the cue box within the viewport in * the direction orthogonal to {@link #line}, or {@link #DIMEN_UNSET}. @@ -154,8 +159,7 @@ public class Cue { * and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box * respectively. */ - @AnchorType - public final int positionAnchor; + @AnchorType public final int positionAnchor; /** * The size of the cue box in the writing direction specified as a fraction of the viewport size @@ -174,7 +178,36 @@ public class Cue { public final int windowColor; /** - * Constructs a cue whose {@link #textAlignment} is null, whose type parameters are set to + * Constructs an image cue whose type parameters are set to {@link #TYPE_UNSET} and whose + * dimension parameters are set to {@link #DIMEN_UNSET}. + * + * @param bitmap See {@link #bitmap}. + */ + public Cue(Bitmap bitmap) { + this(bitmap, DIMEN_UNSET, TYPE_UNSET, DIMEN_UNSET, TYPE_UNSET, DIMEN_UNSET); + } + + /** + * Creates an image cue. + * + * @param horizontalPosition The position of the horizontal anchor within the viewport, expressed + * as a fraction of the viewport width. + * @param horizontalPositionAnchor The horizontal anchor. One of {@link #ANCHOR_TYPE_START}, + * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. + * @param verticalPosition The position of the vertical anchor within the viewport, expressed as a + * fraction of the viewport height. + * @param verticalPositionAnchor The vertical anchor. One of {@link #ANCHOR_TYPE_START}, + * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. + * @param width The width of the cue, expressed as a fraction of the viewport width. + */ + public Cue(Bitmap bitmap, float horizontalPosition, @AnchorType int horizontalPositionAnchor, + float verticalPosition, @AnchorType int verticalPositionAnchor, float width) { + this(null, null, bitmap, verticalPosition, LINE_TYPE_FRACTION, verticalPositionAnchor, + horizontalPosition, horizontalPositionAnchor, width, false, Color.BLACK); + } + + /** + * Constructs a text cue whose {@link #textAlignment} is null, whose type parameters are set to * {@link #TYPE_UNSET} and whose dimension parameters are set to {@link #DIMEN_UNSET}. * * @param text See {@link #text}. @@ -184,6 +217,8 @@ public class Cue { } /** + * Creates a text cue. + * * @param text See {@link #text}. * @param textAlignment See {@link #textAlignment}. * @param line See {@link #line}. @@ -200,6 +235,8 @@ public class Cue { } /** + * Creates a text cue. + * * @param text See {@link #text}. * @param textAlignment See {@link #textAlignment}. * @param line See {@link #line}. @@ -214,8 +251,16 @@ public class Cue { public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType, @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size, boolean windowColorSet, int windowColor) { + this(text, textAlignment, null, line, lineType, lineAnchor, position, positionAnchor, size, + windowColorSet, windowColor); + } + + private Cue(CharSequence text, Alignment textAlignment, Bitmap bitmap, float line, + @LineType int lineType, @AnchorType int lineAnchor, float position, + @AnchorType int positionAnchor, float size, boolean windowColorSet, int windowColor) { this.text = text; this.textAlignment = textAlignment; + this.bitmap = bitmap; this.line = line; this.lineType = lineType; this.lineAnchor = lineAnchor; diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index 04f3b986bd..3e9fb3e68b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -18,11 +18,13 @@ package com.google.android.exoplayer2.ui; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Bitmap; 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.Rect; import android.graphics.RectF; import android.text.Layout.Alignment; import android.text.StaticLayout; @@ -63,6 +65,7 @@ import com.google.android.exoplayer2.util.Util; private final Paint paint; // Previous input variables. + private Bitmap cueBitmap; private CharSequence cueText; private Alignment cueTextAlignment; private float cueLine; @@ -93,6 +96,7 @@ import com.google.android.exoplayer2.util.Util; private int textLeft; private int textTop; private int textPaddingX; + private Rect bitmapRect; @SuppressWarnings("ResourceType") public SubtitlePainter(Context context) { @@ -141,21 +145,25 @@ import com.google.android.exoplayer2.util.Util; public void draw(Cue cue, boolean applyEmbeddedStyles, CaptionStyleCompat style, float textSizePx, float bottomPaddingFraction, Canvas canvas, int cueBoxLeft, int cueBoxTop, int cueBoxRight, int cueBoxBottom) { - CharSequence cueText = cue.text; - if (TextUtils.isEmpty(cueText)) { - // Nothing to draw. - return; - } - - int windowColor = cue.windowColorSet ? cue.windowColor : style.windowColor; - - if (!applyEmbeddedStyles) { - // Strip out any embedded styling. - cueText = cueText.toString(); - windowColor = style.windowColor; + boolean isTextCue = cue.bitmap == null; + CharSequence cueText = null; + Bitmap cueBitmap = null; + if (isTextCue) { + cueText = cue.text; + if (TextUtils.isEmpty(cueText)) { + // Nothing to draw. + return; + } + if (!applyEmbeddedStyles) { + // Strip out any embedded styling. + cueText = cueText.toString(); + } + } else { + cueBitmap = cue.bitmap; } if (areCharSequencesEqual(this.cueText, cueText) && Util.areEqual(this.cueTextAlignment, cue.textAlignment) + && this.cueBitmap == cueBitmap && this.cueLine == cue.line && this.cueLineType == cue.lineType && Util.areEqual(this.cueLineAnchor, cue.lineAnchor) @@ -165,7 +173,7 @@ import com.google.android.exoplayer2.util.Util; && this.applyEmbeddedStyles == applyEmbeddedStyles && this.foregroundColor == style.foregroundColor && this.backgroundColor == style.backgroundColor - && this.windowColor == windowColor + && this.windowColor == style.windowColor && this.edgeType == style.edgeType && this.edgeColor == style.edgeColor && Util.areEqual(this.textPaint.getTypeface(), style.typeface) @@ -176,12 +184,13 @@ import com.google.android.exoplayer2.util.Util; && this.parentRight == cueBoxRight && this.parentBottom == cueBoxBottom) { // We can use the cached layout. - drawLayout(canvas); + drawLayout(canvas, isTextCue); return; } this.cueText = cueText; this.cueTextAlignment = cue.textAlignment; + this.cueBitmap = cue.bitmap; this.cueLine = cue.line; this.cueLineType = cue.lineType; this.cueLineAnchor = cue.lineAnchor; @@ -191,7 +200,7 @@ import com.google.android.exoplayer2.util.Util; this.applyEmbeddedStyles = applyEmbeddedStyles; this.foregroundColor = style.foregroundColor; this.backgroundColor = style.backgroundColor; - this.windowColor = windowColor; + this.windowColor = style.windowColor; this.edgeType = style.edgeType; this.edgeColor = style.edgeColor; this.textPaint.setTypeface(style.typeface); @@ -202,6 +211,15 @@ import com.google.android.exoplayer2.util.Util; this.parentRight = cueBoxRight; this.parentBottom = cueBoxBottom; + if (isTextCue) { + setupTextLayout(); + } else { + setupBitmapLayout(); + } + drawLayout(canvas, isTextCue); + } + + private void setupTextLayout() { int parentWidth = parentRight - parentLeft; int parentHeight = parentBottom - parentTop; @@ -237,7 +255,7 @@ import com.google.android.exoplayer2.util.Util; int anchorPosition = Math.round(parentWidth * cuePosition) + parentLeft; textLeft = cuePositionAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textWidth : cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textWidth) / 2 - : anchorPosition; + : anchorPosition; textLeft = Math.max(textLeft, parentLeft); textRight = Math.min(textLeft + textWidth, parentRight); } else { @@ -256,12 +274,12 @@ import com.google.android.exoplayer2.util.Util; if (cueLine >= 0) { anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop; } else { - anchorPosition = Math.round((cueLine + 1) * firstLineHeight) + parentBottom; + anchorPosition = Math.round(cueLine * firstLineHeight) + parentBottom; } } textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textHeight) / 2 - : anchorPosition; + : anchorPosition; if (textTop + textHeight > parentBottom) { textTop = parentBottom - textHeight; } else if (textTop < parentTop) { @@ -279,16 +297,31 @@ import com.google.android.exoplayer2.util.Util; this.textLeft = textLeft; this.textTop = textTop; this.textPaddingX = textPaddingX; - - drawLayout(canvas); } - /** - * Draws {@link #textLayout} into the provided canvas. - * - * @param canvas The canvas into which to draw. - */ - private void drawLayout(Canvas canvas) { + private void setupBitmapLayout() { + int parentWidth = parentRight - parentLeft; + int parentHeight = parentBottom - parentTop; + float anchorX = parentLeft + (parentWidth * cuePosition); + float anchorY = parentTop + (parentHeight * cueLine); + int width = (int) (parentWidth * cueSize); + int height = (int) (width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth())); + int x = (int) (cueLineAnchor == Cue.ANCHOR_TYPE_END ? (anchorX - width) + : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorX - (width / 2)) : anchorX); + int y = (int) (cuePositionAnchor == Cue.ANCHOR_TYPE_END ? (anchorY - width) + : cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorY - (width / 2)) : anchorY); + bitmapRect = new Rect(x, y, x + width, y + height); + } + + private void drawLayout(Canvas canvas, boolean isTextCue) { + if (isTextCue) { + drawTextLayout(canvas); + } else { + drawBitmapLayout(canvas); + } + } + + private void drawTextLayout(Canvas canvas) { final StaticLayout layout = textLayout; if (layout == null) { // Nothing to draw. @@ -347,6 +380,10 @@ import com.google.android.exoplayer2.util.Util; canvas.restoreToCount(saveCount); } + private void drawBitmapLayout(Canvas canvas) { + canvas.drawBitmap(cueBitmap, null, bitmapRect, null); + } + /** * This method is used instead of {@link TextUtils#equals(CharSequence, CharSequence)} because the * latter only checks the text of each sequence, and does not check for equality of styling that