mirror of
https://github.com/androidx/media.git
synced 2025-05-16 12:09:50 +08:00
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:
parent
6f6c72266e
commit
62b90c83df
@ -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`
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user