Commit e8fa9a55 by ibaker Committed by Ian Baker

Make Cue final and change 2 subclasses to delegate-pattern

This avoids needing to jump through type paremeter hoops to create a
sub-classable Cue.Builder.

Cue should have a standard set of fields, because it will be
consumed by renderers that don't know what type it is. The
existing subclass fields are only used inside their respective
packages, so can be part of a wrapper object instead.

This lays the groundwork for converting Cue's multiple constructors
into a Builder pattern.

PiperOrigin-RevId: 284992135
parent 5bbabbc1
......@@ -24,10 +24,11 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Contains information about a specific cue, including textual content and formatting data.
*/
public class Cue {
/** Contains information about a specific cue, including textual content and formatting data. */
// This class shouldn't be sub-classed. If a subtitle format needs additional fields, either they
// should be generic enough to be added here, or the format-specific decoder should pass the
// information around in a sidecar object.
public final class Cue {
/** The empty cue. */
public static final Cue EMPTY = new Cue("");
......
/*
* Copyright (C) 2016 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.exoplayer2.text.cea;
import android.text.Layout.Alignment;
import androidx.annotation.NonNull;
import com.google.android.exoplayer2.text.Cue;
/**
* A {@link Cue} for CEA-708.
*/
/* package */ final class Cea708Cue extends Cue implements Comparable<Cea708Cue> {
/**
* The priority of the cue box.
*/
public final int priority;
/**
* @param text See {@link #text}.
* @param textAlignment See {@link #textAlignment}.
* @param line See {@link #line}.
* @param lineType See {@link #lineType}.
* @param lineAnchor See {@link #lineAnchor}.
* @param position See {@link #position}.
* @param positionAnchor See {@link #positionAnchor}.
* @param size See {@link #size}.
* @param windowColorSet See {@link #windowColorSet}.
* @param windowColor See {@link #windowColor}.
* @param priority See (@link #priority}.
*/
public Cea708Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size,
boolean windowColorSet, int windowColor, int priority) {
super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, size,
windowColorSet, windowColor);
this.priority = priority;
}
@Override
public int compareTo(@NonNull Cea708Cue other) {
if (other.priority < priority) {
return -1;
} else if (other.priority > priority) {
return 1;
}
return 0;
}
}
......@@ -41,12 +41,12 @@ public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder {
private static final int TYPE_vttc = 0x76747463;
private final ParsableByteArray sampleData;
private final WebvttCue.Builder builder;
private final WebvttCueInfo.Builder builder;
public Mp4WebvttDecoder() {
super("Mp4WebvttDecoder");
sampleData = new ParsableByteArray();
builder = new WebvttCue.Builder();
builder = new WebvttCueInfo.Builder();
}
@Override
......@@ -72,8 +72,9 @@ public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder {
return new Mp4WebvttSubtitle(resultingCueList);
}
private static Cue parseVttCueBox(ParsableByteArray sampleData, WebvttCue.Builder builder,
int remainingCueBoxBytes) throws SubtitleDecoderException {
private static Cue parseVttCueBox(
ParsableByteArray sampleData, WebvttCueInfo.Builder builder, int remainingCueBoxBytes)
throws SubtitleDecoderException {
builder.reset();
while (remainingCueBoxBytes > 0) {
if (remainingCueBoxBytes < BOX_HEADER_SIZE) {
......@@ -95,7 +96,7 @@ public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder {
// Other VTTCueBox children are still not supported and are ignored.
}
}
return builder.build();
return builder.build().cue;
}
}
......@@ -27,14 +27,15 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
/** A representation of a WebVTT cue. */
public final class WebvttCue extends Cue {
public final class WebvttCueInfo {
private static final float DEFAULT_POSITION = 0.5f;
/* package */ static final float DEFAULT_POSITION = 0.5f;
public final Cue cue;
public final long startTime;
public final long endTime;
private WebvttCue(
private WebvttCueInfo(
long startTime,
long endTime,
CharSequence text,
......@@ -45,21 +46,12 @@ public final class WebvttCue extends Cue {
float position,
@Cue.AnchorType int positionAnchor,
float width) {
super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width);
this.cue =
new Cue(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width);
this.startTime = startTime;
this.endTime = endTime;
}
/**
* Returns whether or not this cue should be placed in the default position and rolled-up with
* the other "normal" cues.
*
* @return Whether this cue should be placed in the default position.
*/
public boolean isNormalCue() {
return (line == DIMEN_UNSET && position == DEFAULT_POSITION);
}
/** Builder for WebVTT cues. */
@SuppressWarnings("hiding")
public static class Builder {
......@@ -120,10 +112,10 @@ public final class WebvttCue extends Cue {
private float line;
// Equivalent to WebVTT's snap-to-lines flag:
// https://www.w3.org/TR/webvtt1/#webvtt-cue-snap-to-lines-flag
@LineType private int lineType;
@AnchorType private int lineAnchor;
@Cue.LineType private int lineType;
@Cue.AnchorType private int lineAnchor;
private float position;
@AnchorType private int positionAnchor;
@Cue.AnchorType private int positionAnchor;
private float width;
// Initialization methods
......@@ -154,7 +146,7 @@ public final class WebvttCue extends Cue {
// Construction methods.
public WebvttCue build() {
public WebvttCueInfo build() {
line = computeLine(line, lineType);
if (position == Cue.DIMEN_UNSET) {
......@@ -167,7 +159,7 @@ public final class WebvttCue extends Cue {
width = Math.min(width, deriveMaxSize(positionAnchor, position));
return new WebvttCue(
return new WebvttCueInfo(
startTime,
endTime,
Assertions.checkNotNull(text),
......@@ -205,12 +197,12 @@ public final class WebvttCue extends Cue {
return this;
}
public Builder setLineType(@LineType int lineType) {
public Builder setLineType(@Cue.LineType int lineType) {
this.lineType = lineType;
return this;
}
public Builder setLineAnchor(@AnchorType int lineAnchor) {
public Builder setLineAnchor(@Cue.AnchorType int lineAnchor) {
this.lineAnchor = lineAnchor;
return this;
}
......@@ -220,7 +212,7 @@ public final class WebvttCue extends Cue {
return this;
}
public Builder setPositionAnchor(@AnchorType int positionAnchor) {
public Builder setPositionAnchor(@Cue.AnchorType int positionAnchor) {
this.positionAnchor = positionAnchor;
return this;
}
......@@ -231,7 +223,7 @@ public final class WebvttCue extends Cue {
}
// https://www.w3.org/TR/webvtt1/#webvtt-cue-line
private static float computeLine(float line, @LineType int lineType) {
private static float computeLine(float line, @Cue.LineType int lineType) {
if (line != Cue.DIMEN_UNSET
&& lineType == Cue.LINE_TYPE_FRACTION
&& (line < 0.0f || line > 1.0f)) {
......@@ -242,9 +234,9 @@ public final class WebvttCue extends Cue {
} else if (lineType == Cue.LINE_TYPE_FRACTION) {
return 1.0f; // Step 3
} else {
// Steps 4 - 10 (stacking multiple simultaneous cues) are handled by WebvttSubtitle#getCues
// and WebvttCue#isNormalCue.
return DIMEN_UNSET;
// Steps 4 - 10 (stacking multiple simultaneous cues) are handled by
// WebvttSubtitle.getCues(long) and WebvttSubtitle.isNormal(Cue).
return Cue.DIMEN_UNSET;
}
}
......@@ -264,7 +256,7 @@ public final class WebvttCue extends Cue {
}
// https://www.w3.org/TR/webvtt1/#webvtt-cue-position-alignment
@AnchorType
@Cue.AnchorType
private static int derivePositionAnchor(@TextAlignment int textAlignment) {
switch (textAlignment) {
case TextAlignment.LEFT:
......@@ -297,7 +289,7 @@ public final class WebvttCue extends Cue {
}
// Step 2 here: https://www.w3.org/TR/webvtt1/#processing-cue-settings
private static float deriveMaxSize(@AnchorType int positionAnchor, float position) {
private static float deriveMaxSize(@Cue.AnchorType int positionAnchor, float position) {
switch (positionAnchor) {
case Cue.ANCHOR_TYPE_START:
return 1.0f - position;
......
......@@ -91,7 +91,7 @@ public final class WebvttCueParser {
* @return Whether a valid Cue was found.
*/
public boolean parseCue(
ParsableByteArray webvttData, WebvttCue.Builder builder, List<WebvttCssStyle> styles) {
ParsableByteArray webvttData, WebvttCueInfo.Builder builder, List<WebvttCssStyle> styles) {
@Nullable String firstLine = webvttData.readLine();
if (firstLine == null) {
return false;
......@@ -119,10 +119,10 @@ public final class WebvttCueParser {
* Parses a string containing a list of cue settings.
*
* @param cueSettingsList String containing the settings for a given cue.
* @param builder The {@link WebvttCue.Builder} where incremental construction takes place.
* @param builder The {@link WebvttCueInfo.Builder} where incremental construction takes place.
*/
/* package */ static void parseCueSettingsList(String cueSettingsList,
WebvttCue.Builder builder) {
/* package */ static void parseCueSettingsList(
String cueSettingsList, WebvttCueInfo.Builder builder) {
// Parse the cue settings list.
Matcher cueSettingMatcher = CUE_SETTING_PATTERN.matcher(cueSettingsList);
while (cueSettingMatcher.find()) {
......@@ -147,7 +147,8 @@ public final class WebvttCueParser {
}
/**
* Parses the text payload of a WebVTT Cue and applies modifications on {@link WebvttCue.Builder}.
* Parses the text payload of a WebVTT Cue and applies modifications on {@link
* WebvttCueInfo.Builder}.
*
* @param id Id of the cue, {@code null} if it is not present.
* @param markup The markup text to be parsed.
......@@ -155,7 +156,10 @@ public final class WebvttCueParser {
* @param builder Output builder.
*/
/* package */ static void parseCueText(
@Nullable String id, String markup, WebvttCue.Builder builder, List<WebvttCssStyle> styles) {
@Nullable String id,
String markup,
WebvttCueInfo.Builder builder,
List<WebvttCssStyle> styles) {
SpannableStringBuilder spannedText = new SpannableStringBuilder();
ArrayDeque<StartTag> startTagStack = new ArrayDeque<>();
List<StyleMatch> scratchStyleMatches = new ArrayList<>();
......@@ -230,7 +234,7 @@ public final class WebvttCueParser {
@Nullable String id,
Matcher cueHeaderMatcher,
ParsableByteArray webvttData,
WebvttCue.Builder builder,
WebvttCueInfo.Builder builder,
StringBuilder textBuilder,
List<WebvttCssStyle> styles) {
try {
......@@ -260,7 +264,7 @@ public final class WebvttCueParser {
// Internal methods
private static void parseLineAttribute(String s, WebvttCue.Builder builder) {
private static void parseLineAttribute(String s, WebvttCueInfo.Builder builder) {
int commaIndex = s.indexOf(',');
if (commaIndex != -1) {
builder.setLineAnchor(parsePositionAnchor(s.substring(commaIndex + 1)));
......@@ -279,7 +283,7 @@ public final class WebvttCueParser {
}
}
private static void parsePositionAttribute(String s, WebvttCue.Builder builder) {
private static void parsePositionAttribute(String s, WebvttCueInfo.Builder builder) {
int commaIndex = s.indexOf(',');
if (commaIndex != -1) {
builder.setPositionAnchor(parsePositionAnchor(s.substring(commaIndex + 1)));
......@@ -304,24 +308,24 @@ public final class WebvttCueParser {
}
}
@WebvttCue.Builder.TextAlignment
@WebvttCueInfo.Builder.TextAlignment
private static int parseTextAlignment(String s) {
switch (s) {
case "start":
return WebvttCue.Builder.TextAlignment.START;
return WebvttCueInfo.Builder.TextAlignment.START;
case "left":
return WebvttCue.Builder.TextAlignment.LEFT;
return WebvttCueInfo.Builder.TextAlignment.LEFT;
case "center":
case "middle":
return WebvttCue.Builder.TextAlignment.CENTER;
return WebvttCueInfo.Builder.TextAlignment.CENTER;
case "end":
return WebvttCue.Builder.TextAlignment.END;
return WebvttCueInfo.Builder.TextAlignment.END;
case "right":
return WebvttCue.Builder.TextAlignment.RIGHT;
return WebvttCueInfo.Builder.TextAlignment.RIGHT;
default:
Log.w(TAG, "Invalid alignment value: " + s);
// Default value: https://www.w3.org/TR/webvtt1/#webvtt-cue-text-alignment
return WebvttCue.Builder.TextAlignment.CENTER;
return WebvttCueInfo.Builder.TextAlignment.CENTER;
}
}
......
......@@ -42,7 +42,7 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder {
private final WebvttCueParser cueParser;
private final ParsableByteArray parsableWebvttData;
private final WebvttCue.Builder webvttCueBuilder;
private final WebvttCueInfo.Builder webvttCueBuilder;
private final CssParser cssParser;
private final List<WebvttCssStyle> definedStyles;
......@@ -50,7 +50,7 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder {
super("WebvttDecoder");
cueParser = new WebvttCueParser();
parsableWebvttData = new ParsableByteArray();
webvttCueBuilder = new WebvttCue.Builder();
webvttCueBuilder = new WebvttCueInfo.Builder();
cssParser = new CssParser();
definedStyles = new ArrayList<>();
}
......@@ -72,24 +72,24 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder {
while (!TextUtils.isEmpty(parsableWebvttData.readLine())) {}
int event;
ArrayList<WebvttCue> subtitles = new ArrayList<>();
List<WebvttCueInfo> cueInfos = new ArrayList<>();
while ((event = getNextEvent(parsableWebvttData)) != EVENT_END_OF_FILE) {
if (event == EVENT_COMMENT) {
skipComment(parsableWebvttData);
} else if (event == EVENT_STYLE_BLOCK) {
if (!subtitles.isEmpty()) {
if (!cueInfos.isEmpty()) {
throw new SubtitleDecoderException("A style block was found after the first cue.");
}
parsableWebvttData.readLine(); // Consume the "STYLE" header.
definedStyles.addAll(cssParser.parseBlock(parsableWebvttData));
} else if (event == EVENT_CUE) {
if (cueParser.parseCue(parsableWebvttData, webvttCueBuilder, definedStyles)) {
subtitles.add(webvttCueBuilder.build());
cueInfos.add(webvttCueBuilder.build());
webvttCueBuilder.reset();
}
}
}
return new WebvttSubtitle(subtitles);
return new WebvttSubtitle(cueInfos);
}
/**
......
......@@ -30,23 +30,20 @@ import java.util.List;
*/
/* package */ final class WebvttSubtitle implements Subtitle {
private final List<WebvttCue> cues;
private final int numCues;
private final List<Cue> cues;
private final long[] cueTimesUs;
private final long[] sortedCueTimesUs;
/**
* @param cues A list of the cues in this subtitle.
*/
public WebvttSubtitle(List<WebvttCue> cues) {
this.cues = cues;
numCues = cues.size();
cueTimesUs = new long[2 * numCues];
for (int cueIndex = 0; cueIndex < numCues; cueIndex++) {
WebvttCue cue = cues.get(cueIndex);
/** Constructs a new WebvttSubtitle from a list of {@link WebvttCueInfo}s. */
public WebvttSubtitle(List<WebvttCueInfo> cueInfos) {
this.cues = new ArrayList<>(cueInfos.size());
cueTimesUs = new long[2 * cueInfos.size()];
for (int cueIndex = 0; cueIndex < cueInfos.size(); cueIndex++) {
WebvttCueInfo cueInfo = cueInfos.get(cueIndex);
this.cues.add(cueInfo.cue);
int arrayIndex = cueIndex * 2;
cueTimesUs[arrayIndex] = cue.startTime;
cueTimesUs[arrayIndex + 1] = cue.endTime;
cueTimesUs[arrayIndex] = cueInfo.startTime;
cueTimesUs[arrayIndex + 1] = cueInfo.endTime;
}
sortedCueTimesUs = Arrays.copyOf(cueTimesUs, cueTimesUs.length);
Arrays.sort(sortedCueTimesUs);
......@@ -73,16 +70,16 @@ import java.util.List;
@Override
public List<Cue> getCues(long timeUs) {
List<Cue> list = new ArrayList<>();
WebvttCue firstNormalCue = null;
Cue firstNormalCue = null;
SpannableStringBuilder normalCueTextBuilder = null;
for (int i = 0; i < numCues; i++) {
for (int i = 0; i < cues.size(); i++) {
if ((cueTimesUs[i * 2] <= timeUs) && (timeUs < cueTimesUs[i * 2 + 1])) {
WebvttCue cue = cues.get(i);
Cue cue = cues.get(i);
// TODO(ibaker): Replace this with a closer implementation of the WebVTT spec (keeping
// individual cues, but tweaking their `line` value):
// https://www.w3.org/TR/webvtt1/#cue-computed-line
if (cue.isNormalCue()) {
if (isNormal(cue)) {
// we want to merge all of the normal cues into a single cue to ensure they are drawn
// correctly (i.e. don't overlap) and to emulate roll-up, but only if there are multiple
// normal cues, otherwise we can just append the single normal cue
......@@ -104,7 +101,7 @@ import java.util.List;
}
if (normalCueTextBuilder != null) {
// there were multiple normal cues, so create a new cue with all of the text
list.add(new WebvttCue.Builder().setText(normalCueTextBuilder).build());
list.add(new WebvttCueInfo.Builder().setText(normalCueTextBuilder).build().cue);
} else if (firstNormalCue != null) {
// there was only a single normal cue, so just add it to the list
list.add(firstNormalCue);
......@@ -112,4 +109,13 @@ import java.util.List;
return list;
}
/**
* Returns whether or not this cue should be placed in the default position and rolled-up with the
* other "normal" cues.
*
* @return Whether this cue should be placed in the default position.
*/
private static boolean isNormal(Cue cue) {
return (cue.line == Cue.DIMEN_UNSET && cue.position == WebvttCueInfo.DEFAULT_POSITION);
}
}
......@@ -92,7 +92,7 @@ public final class Mp4WebvttDecoderTest {
Mp4WebvttDecoder decoder = new Mp4WebvttDecoder();
Subtitle result = decoder.decode(SINGLE_CUE_SAMPLE, SINGLE_CUE_SAMPLE.length, false);
// Line feed must be trimmed by the decoder
Cue expectedCue = new WebvttCue.Builder().setText("Hello World").build();
Cue expectedCue = new WebvttCueInfo.Builder().setText("Hello World").build().cue;
assertMp4WebvttSubtitleEquals(result, expectedCue);
}
......@@ -100,8 +100,8 @@ public final class Mp4WebvttDecoderTest {
public void testTwoCuesSample() throws SubtitleDecoderException {
Mp4WebvttDecoder decoder = new Mp4WebvttDecoder();
Subtitle result = decoder.decode(DOUBLE_CUE_SAMPLE, DOUBLE_CUE_SAMPLE.length, false);
Cue firstExpectedCue = new WebvttCue.Builder().setText("Hello World").build();
Cue secondExpectedCue = new WebvttCue.Builder().setText("Bye Bye").build();
Cue firstExpectedCue = new WebvttCueInfo.Builder().setText("Hello World").build().cue;
Cue secondExpectedCue = new WebvttCueInfo.Builder().setText("Bye Bye").build().cue;
assertMp4WebvttSubtitleEquals(result, firstExpectedCue, secondExpectedCue);
}
......
......@@ -243,9 +243,9 @@ public final class WebvttCueParserTest {
}
private static Spanned parseCueText(String string) {
WebvttCue.Builder builder = new WebvttCue.Builder();
WebvttCueInfo.Builder builder = new WebvttCueInfo.Builder();
WebvttCueParser.parseCueText(null, string, builder, Collections.emptyList());
return (Spanned) builder.build().text;
return (Spanned) builder.build().cue.text;
}
private static <T> T[] getSpans(Spanned text, Class<T> spanType) {
......
......@@ -41,16 +41,16 @@ public class WebvttSubtitleTest {
private static final WebvttSubtitle simpleSubtitle;
static {
ArrayList<WebvttCue> simpleSubtitleCues = new ArrayList<>();
WebvttCue firstCue =
new WebvttCue.Builder()
ArrayList<WebvttCueInfo> simpleSubtitleCues = new ArrayList<>();
WebvttCueInfo firstCue =
new WebvttCueInfo.Builder()
.setStartTime(1000000)
.setEndTime(2000000)
.setText(FIRST_SUBTITLE_STRING)
.build();
simpleSubtitleCues.add(firstCue);
WebvttCue secondCue =
new WebvttCue.Builder()
WebvttCueInfo secondCue =
new WebvttCueInfo.Builder()
.setStartTime(3000000)
.setEndTime(4000000)
.setText(SECOND_SUBTITLE_STRING)
......@@ -62,16 +62,16 @@ public class WebvttSubtitleTest {
private static final WebvttSubtitle overlappingSubtitle;
static {
ArrayList<WebvttCue> overlappingSubtitleCues = new ArrayList<>();
WebvttCue firstCue =
new WebvttCue.Builder()
ArrayList<WebvttCueInfo> overlappingSubtitleCues = new ArrayList<>();
WebvttCueInfo firstCue =
new WebvttCueInfo.Builder()
.setStartTime(1000000)
.setEndTime(3000000)
.setText(FIRST_SUBTITLE_STRING)
.build();
overlappingSubtitleCues.add(firstCue);
WebvttCue secondCue =
new WebvttCue.Builder()
WebvttCueInfo secondCue =
new WebvttCueInfo.Builder()
.setStartTime(2000000)
.setEndTime(4000000)
.setText(SECOND_SUBTITLE_STRING)
......@@ -83,16 +83,16 @@ public class WebvttSubtitleTest {
private static final WebvttSubtitle nestedSubtitle;
static {
ArrayList<WebvttCue> nestedSubtitleCues = new ArrayList<>();
WebvttCue firstCue =
new WebvttCue.Builder()
ArrayList<WebvttCueInfo> nestedSubtitleCues = new ArrayList<>();
WebvttCueInfo firstCue =
new WebvttCueInfo.Builder()
.setStartTime(1000000)
.setEndTime(4000000)
.setText(FIRST_SUBTITLE_STRING)
.build();
nestedSubtitleCues.add(firstCue);
WebvttCue secondCue =
new WebvttCue.Builder()
WebvttCueInfo secondCue =
new WebvttCueInfo.Builder()
.setStartTime(2000000)
.setEndTime(3000000)
.setText(SECOND_SUBTITLE_STRING)
......
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 sign in to comment