Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import androidx.media3.common.text.Cue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* A {@link SubtitleView.Output} that uses Android's native layout framework via {@link
Expand Down Expand Up @@ -104,12 +106,39 @@ public void dispatchDraw(Canvas canvas) {
return;
}

// Track cumulative offsets per line value to prevent overlapping only among cues that share
// the same line number. Cues with different line numbers are already separated by the
// line-number positioning math in SubtitlePainter, so they don't need viewport adjustment.
// We use Float as the key: DIMEN_UNSET for unpositioned cues, or the actual line value.
Map<Float, Integer> cumulativeOffsetByLine = new HashMap<>();

int cueCount = cues.size();
for (int i = 0; i < cueCount; i++) {
Cue cue = cues.get(i);
if (cue.verticalType != Cue.TYPE_UNSET) {
cue = repositionVerticalCue(cue);
}

// Determine the effective line key for grouping.
float lineKey = cue.line;
boolean isBottomStackedCue =
cue.line == Cue.DIMEN_UNSET
|| (cue.lineType == Cue.LINE_TYPE_NUMBER && cue.line < 0);
boolean isTopStackedCue =
cue.line != Cue.DIMEN_UNSET && cue.lineType == Cue.LINE_TYPE_NUMBER && cue.line >= 0;

// Get the cumulative offset for cues at this same line value.
int cumulativeOffset = cumulativeOffsetByLine.getOrDefault(lineKey, 0);

// Adjust boundaries to account for previously drawn cues at the same line.
int adjustedTop = top;
int adjustedBottom = bottom;
if (isBottomStackedCue && cumulativeOffset > 0) {
adjustedBottom = bottom - cumulativeOffset;
} else if (isTopStackedCue && cumulativeOffset > 0) {
adjustedTop = top + cumulativeOffset;
}

float cueTextSizePx =
SubtitleViewUtils.resolveTextSize(
cue.textSizeType, cue.textSize, rawViewHeight, viewHeightMinusPadding);
Expand All @@ -122,9 +151,14 @@ public void dispatchDraw(Canvas canvas) {
bottomPaddingFraction,
canvas,
left,
top,
adjustedTop,
right,
bottom);
adjustedBottom);

// Accumulate offset so subsequent cues at the same line don't overlap.
if (isBottomStackedCue || isTopStackedCue) {
cumulativeOffsetByLine.put(lineKey, cumulativeOffset + painter.getLastDrawnCueHeight());
}
}
}

Expand Down
17 changes: 17 additions & 0 deletions libraries/ui/src/main/java/androidx/media3/ui/SubtitlePainter.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
/** Ratio of inner padding to font size. */
private static final float INNER_PADDING_RATIO = 0.125f;

/** The height of the last drawn cue, or 0 if no cue was drawn. */
private int lastDrawnCueHeight;

// Styled dimensions.
private final float outlineWidth;
private final float shadowRadius;
Expand Down Expand Up @@ -218,13 +221,27 @@ public void draw(
if (isTextCue) {
checkNotNull(cueText);
setupTextLayout();
lastDrawnCueHeight = textLayout != null ? textLayout.getHeight() : 0;
} else {
checkNotNull(cueBitmap);
setupBitmapLayout();
lastDrawnCueHeight = bitmapRect != null ? bitmapRect.height() : 0;
}
drawLayout(canvas, isTextCue);
}

/**
* Returns the height of the last drawn cue.
*
* <p>This can be used to stack multiple cues without overlap by adjusting the drawing bounds for
* subsequent cues by this amount.
*
* @return The height of the last drawn cue in pixels, or 0 if no cue was drawn.
*/
public int getLastDrawnCueHeight() {
return lastDrawnCueHeight;
}

@RequiresNonNull("cueText")
private void setupTextLayout() {
SpannableStringBuilder cueText =
Expand Down
Loading