Fix application of styles for CEA-608

Issue: #4321

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=202660712
This commit is contained in:
olly 2018-06-29 09:43:49 -07:00 committed by Oliver Woodman
parent 6f6c72266e
commit 62b90c83df
2 changed files with 127 additions and 97 deletions

View File

@ -39,6 +39,8 @@
* Allow DrmInitData to carry a license server URL * Allow DrmInitData to carry a license server URL
([#3393](https://github.com/google/ExoPlayer/issues/3393)). ([#3393](https://github.com/google/ExoPlayer/issues/3393)).
* Add callback to `VideoListener` to notify of surface size changes. * Add callback to `VideoListener` to notify of surface size changes.
* CEA-608: Improve handling of embedded styles
([#4321](https://github.com/google/ExoPlayer/issues/4321)).
* Fix bug when reporting buffered position for multi-period windows and add * Fix bug when reporting buffered position for multi-period windows and add
two additional convenience methods `Player.getTotalBufferedDuration` and two additional convenience methods `Player.getTotalBufferedDuration` and
`Player.getContentBufferedDuration` `Player.getContentBufferedDuration`

View File

@ -21,10 +21,10 @@ import android.text.Layout.Alignment;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan; import android.text.style.UnderlineSpan;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.Subtitle;
@ -55,15 +55,13 @@ public final class Cea608Decoder extends CeaDecoder {
private static final int[] ROW_INDICES = new int[] {11, 1, 3, 12, 14, 5, 7, 9}; private static final int[] ROW_INDICES = new int[] {11, 1, 3, 12, 14, 5, 7, 9};
private static final int[] COLUMN_INDICES = new int[] {0, 4, 8, 12, 16, 20, 24, 28}; private static final int[] COLUMN_INDICES = new int[] {0, 4, 8, 12, 16, 20, 24, 28};
private static final int[] COLORS = new int[] {
Color.WHITE, private static final int[] STYLE_COLORS =
Color.GREEN, new int[] {
Color.BLUE, Color.WHITE, Color.GREEN, Color.BLUE, Color.CYAN, Color.RED, Color.YELLOW, Color.MAGENTA
Color.CYAN, };
Color.RED, private static final int STYLE_ITALICS = 0x07;
Color.YELLOW, private static final int STYLE_UNCHANGED = 0x08;
Color.MAGENTA,
};
// The default number of rows to display in roll-up captions mode. // The default number of rows to display in roll-up captions mode.
private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4; private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4;
@ -377,18 +375,10 @@ public final class Cea608Decoder extends CeaDecoder {
// A midrow control code advances the cursor. // A midrow control code advances the cursor.
currentCueBuilder.append(' '); currentCueBuilder.append(' ');
// cc2 - 0|0|1|0|ATRBT|U // cc2 - 0|0|1|0|STYLE|U
// ATRBT is the 3-byte encoded attribute, and U is the underline toggle boolean underline = (cc2 & 0x01) == 0x01;
boolean isUnderlined = (cc2 & 0x01) == 0x01; int style = (cc2 >> 1) & 0x07;
currentCueBuilder.setUnderline(isUnderlined); currentCueBuilder.setStyle(style, underline);
int attribute = (cc2 >> 1) & 0x0F;
if (attribute == 0x07) {
currentCueBuilder.setMidrowStyle(new StyleSpan(Typeface.ITALIC), 2);
currentCueBuilder.setMidrowStyle(new ForegroundColorSpan(Color.WHITE), 1);
} else {
currentCueBuilder.setMidrowStyle(new ForegroundColorSpan(COLORS[attribute]), 1);
}
} }
private void handlePreambleAddressCode(byte cc1, byte cc2) { private void handlePreambleAddressCode(byte cc1, byte cc2) {
@ -414,22 +404,18 @@ public final class Cea608Decoder extends CeaDecoder {
currentCueBuilder.setRow(row); currentCueBuilder.setRow(row);
} }
if ((cc2 & 0x01) == 0x01) {
currentCueBuilder.setPreambleStyle(new UnderlineSpan());
}
// cc2 - 0|1|N|0|STYLE|U // cc2 - 0|1|N|0|STYLE|U
// cc2 - 0|1|N|1|CURSR|U // cc2 - 0|1|N|1|CURSR|U
int attribute = cc2 >> 1 & 0x0F; boolean isCursor = (cc2 & 0x10) == 0x10;
if (attribute <= 0x07) { boolean underline = (cc2 & 0x01) == 0x01;
if (attribute == 0x07) { int cursorOrStyle = (cc2 >> 1) & 0x07;
currentCueBuilder.setPreambleStyle(new StyleSpan(Typeface.ITALIC));
currentCueBuilder.setPreambleStyle(new ForegroundColorSpan(Color.WHITE)); // We need to call setStyle even for the isCursor case, to update the underline bit.
} else { // STYLE_UNCHANGED is used for this case.
currentCueBuilder.setPreambleStyle(new ForegroundColorSpan(COLORS[attribute])); currentCueBuilder.setStyle(isCursor ? STYLE_UNCHANGED : cursorOrStyle, underline);
}
} else { if (isCursor) {
currentCueBuilder.setIndent(COLUMN_INDICES[attribute & 0x07]); currentCueBuilder.setIndent(COLUMN_INDICES[cursorOrStyle]);
} }
} }
@ -585,44 +571,37 @@ public final class Cea608Decoder extends CeaDecoder {
private static class CueBuilder { private static class CueBuilder {
private static final int POSITION_UNSET = -1;
// 608 captions define a 15 row by 32 column screen grid. These constants convert from 608 // 608 captions define a 15 row by 32 column screen grid. These constants convert from 608
// positions to normalized screen position. // positions to normalized screen position.
private static final int SCREEN_CHARWIDTH = 32; private static final int SCREEN_CHARWIDTH = 32;
private static final int BASE_ROW = 15; private static final int BASE_ROW = 15;
private final List<CharacterStyle> preambleStyles; private final List<CueStyle> cueStyles;
private final List<CueStyle> midrowStyles;
private final List<SpannableString> rolledUpCaptions; private final List<SpannableString> rolledUpCaptions;
private final SpannableStringBuilder captionStringBuilder; private final StringBuilder captionStringBuilder;
private int row; private int row;
private int indent; private int indent;
private int tabOffset; private int tabOffset;
private int captionMode; private int captionMode;
private int captionRowCount; private int captionRowCount;
private int underlineStartPosition;
public CueBuilder(int captionMode, int captionRowCount) { public CueBuilder(int captionMode, int captionRowCount) {
preambleStyles = new ArrayList<>(); cueStyles = new ArrayList<>();
midrowStyles = new ArrayList<>();
rolledUpCaptions = new ArrayList<>(); rolledUpCaptions = new ArrayList<>();
captionStringBuilder = new SpannableStringBuilder(); captionStringBuilder = new StringBuilder();
reset(captionMode); reset(captionMode);
setCaptionRowCount(captionRowCount); setCaptionRowCount(captionRowCount);
} }
public void reset(int captionMode) { public void reset(int captionMode) {
this.captionMode = captionMode; this.captionMode = captionMode;
preambleStyles.clear(); cueStyles.clear();
midrowStyles.clear();
rolledUpCaptions.clear(); rolledUpCaptions.clear();
captionStringBuilder.clear(); captionStringBuilder.setLength(0);
row = BASE_ROW; row = BASE_ROW;
indent = 0; indent = 0;
tabOffset = 0; tabOffset = 0;
underlineStartPosition = POSITION_UNSET;
} }
public void setCaptionRowCount(int captionRowCount) { public void setCaptionRowCount(int captionRowCount) {
@ -630,7 +609,8 @@ public final class Cea608Decoder extends CeaDecoder {
} }
public boolean isEmpty() { public boolean isEmpty() {
return preambleStyles.isEmpty() && midrowStyles.isEmpty() && rolledUpCaptions.isEmpty() return cueStyles.isEmpty()
&& rolledUpCaptions.isEmpty()
&& captionStringBuilder.length() == 0; && captionStringBuilder.length() == 0;
} }
@ -638,6 +618,16 @@ public final class Cea608Decoder extends CeaDecoder {
int length = captionStringBuilder.length(); int length = captionStringBuilder.length();
if (length > 0) { if (length > 0) {
captionStringBuilder.delete(length - 1, length); captionStringBuilder.delete(length - 1, length);
// Decrement style start positions if necessary.
for (int i = cueStyles.size() - 1; i >= 0; i--) {
CueStyle style = cueStyles.get(i);
if (style.start == length) {
style.start--;
} else {
// All earlier cues must have style.start < length.
break;
}
}
} }
} }
@ -651,11 +641,8 @@ public final class Cea608Decoder extends CeaDecoder {
public void rollUp() { public void rollUp() {
rolledUpCaptions.add(buildSpannableString()); rolledUpCaptions.add(buildSpannableString());
captionStringBuilder.clear(); captionStringBuilder.setLength(0);
preambleStyles.clear(); cueStyles.clear();
midrowStyles.clear();
underlineStartPosition = POSITION_UNSET;
int numRows = Math.min(captionRowCount, row); int numRows = Math.min(captionRowCount, row);
while (rolledUpCaptions.size() >= numRows) { while (rolledUpCaptions.size() >= numRows) {
rolledUpCaptions.remove(0); rolledUpCaptions.remove(0);
@ -670,23 +657,8 @@ public final class Cea608Decoder extends CeaDecoder {
tabOffset = tabs; tabOffset = tabs;
} }
public void setPreambleStyle(CharacterStyle style) { public void setStyle(int style, boolean underline) {
preambleStyles.add(style); cueStyles.add(new CueStyle(style, underline, captionStringBuilder.length()));
}
public void setMidrowStyle(CharacterStyle style, int nextStyleIncrement) {
midrowStyles.add(new CueStyle(style, captionStringBuilder.length(), nextStyleIncrement));
}
public void setUnderline(boolean enabled) {
if (enabled) {
underlineStartPosition = captionStringBuilder.length();
} else if (underlineStartPosition != POSITION_UNSET) {
// underline spans won't overlap, so it's safe to modify the builder directly with them
captionStringBuilder.setSpan(new UnderlineSpan(), underlineStartPosition,
captionStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
underlineStartPosition = POSITION_UNSET;
}
} }
public void append(char text) { public void append(char text) {
@ -694,31 +666,69 @@ public final class Cea608Decoder extends CeaDecoder {
} }
public SpannableString buildSpannableString() { public SpannableString buildSpannableString() {
int length = captionStringBuilder.length(); SpannableStringBuilder builder = new SpannableStringBuilder(captionStringBuilder);
int length = builder.length();
// preamble styles apply to the entire cue int underlineStartPosition = C.INDEX_UNSET;
for (int i = 0; i < preambleStyles.size(); i++) { int italicStartPosition = C.INDEX_UNSET;
captionStringBuilder.setSpan(preambleStyles.get(i), 0, length, int colorStartPosition = 0;
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); int color = Color.WHITE;
boolean nextItalic = false;
int nextColor = Color.WHITE;
for (int i = 0; i < cueStyles.size(); i++) {
CueStyle cueStyle = cueStyles.get(i);
boolean underline = cueStyle.underline;
int style = cueStyle.style;
if (style != STYLE_UNCHANGED) {
// If the style is a color then italic is cleared.
nextItalic = style == STYLE_ITALICS;
// If the style is italic then the color is left unchanged.
nextColor = style == STYLE_ITALICS ? nextColor : STYLE_COLORS[style];
}
int position = cueStyle.start;
int nextPosition = (i + 1) < cueStyles.size() ? cueStyles.get(i + 1).start : length;
if (position == nextPosition) {
// There are more cueStyles to process at the current position.
continue;
}
// Process changes to underline up to the current position.
if (underlineStartPosition != C.INDEX_UNSET && !underline) {
setUnderlineSpan(builder, underlineStartPosition, position);
underlineStartPosition = C.INDEX_UNSET;
} else if (underlineStartPosition == C.INDEX_UNSET && underline) {
underlineStartPosition = position;
}
// Process changes to italic up to the current position.
if (italicStartPosition != C.INDEX_UNSET && !nextItalic) {
setItalicSpan(builder, italicStartPosition, position);
italicStartPosition = C.INDEX_UNSET;
} else if (italicStartPosition == C.INDEX_UNSET && nextItalic) {
italicStartPosition = position;
}
// Process changes to color up to the current position.
if (nextColor != color) {
setColorSpan(builder, colorStartPosition, position, color);
color = nextColor;
colorStartPosition = position;
}
} }
// midrow styles only apply to part of the cue, and after preamble styles // Add any final spans.
for (int i = 0; i < midrowStyles.size(); i++) { if (underlineStartPosition != C.INDEX_UNSET && underlineStartPosition != length) {
CueStyle cueStyle = midrowStyles.get(i); setUnderlineSpan(builder, underlineStartPosition, length);
int end = (i < midrowStyles.size() - cueStyle.nextStyleIncrement) }
? midrowStyles.get(i + cueStyle.nextStyleIncrement).start if (italicStartPosition != C.INDEX_UNSET && italicStartPosition != length) {
: length; setItalicSpan(builder, italicStartPosition, length);
captionStringBuilder.setSpan(cueStyle.style, cueStyle.start, end, }
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); if (colorStartPosition != length) {
setColorSpan(builder, colorStartPosition, length, color);
} }
// special case for midrow underlines that went to the end of the cue return new SpannableString(builder);
if (underlineStartPosition != POSITION_UNSET) {
captionStringBuilder.setSpan(new UnderlineSpan(), underlineStartPosition, length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return new SpannableString(captionStringBuilder);
} }
public Cue build() { public Cue build() {
@ -788,16 +798,34 @@ public final class Cea608Decoder extends CeaDecoder {
return captionStringBuilder.toString(); return captionStringBuilder.toString();
} }
private static void setUnderlineSpan(SpannableStringBuilder builder, int start, int end) {
builder.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
private static void setItalicSpan(SpannableStringBuilder builder, int start, int end) {
builder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
private static void setColorSpan(
SpannableStringBuilder builder, int start, int end, int color) {
if (color == Color.WHITE) {
// White is treated as the default color (i.e. no span is attached).
return;
}
builder.setSpan(new ForegroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
private static class CueStyle { private static class CueStyle {
public final CharacterStyle style; public final int style;
public final int start; public final boolean underline;
public final int nextStyleIncrement;
public CueStyle(CharacterStyle style, int start, int nextStyleIncrement) { public int start;
public CueStyle(int style, boolean underline, int start) {
this.style = style; this.style = style;
this.underline = underline;
this.start = start; this.start = start;
this.nextStyleIncrement = nextStyleIncrement;
} }
} }