diff --git a/libraries/common/src/main/java/androidx/media3/common/text/CustomSpanBundler.java b/libraries/common/src/main/java/androidx/media3/common/text/CustomSpanBundler.java
index edcda586d2d..0734e684993 100644
--- a/libraries/common/src/main/java/androidx/media3/common/text/CustomSpanBundler.java
+++ b/libraries/common/src/main/java/androidx/media3/common/text/CustomSpanBundler.java
@@ -53,12 +53,13 @@
*
{@link #RUBY}
* {@link #TEXT_EMPHASIS}
* {@link #HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT}
+ * {@link #OUTLINE_WIDTH}
*
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({TYPE_USE})
- @IntDef({UNKNOWN, RUBY, TEXT_EMPHASIS, HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT})
+ @IntDef({UNKNOWN, RUBY, TEXT_EMPHASIS, HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT, OUTLINE_WIDTH})
private @interface CustomSpanType {}
private static final int UNKNOWN = -1;
@@ -69,6 +70,8 @@
private static final int HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT = 3;
+ private static final int OUTLINE_WIDTH = 4;
+
private static final String FIELD_START_INDEX = Util.intToStringMaxRadix(0);
private static final String FIELD_END_INDEX = Util.intToStringMaxRadix(1);
private static final String FIELD_FLAGS = Util.intToStringMaxRadix(2);
@@ -94,6 +97,11 @@ public static ArrayList bundleCustomSpans(Spanned text) {
text, span, /* spanType= */ HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT, /* params= */ null);
bundledCustomSpans.add(bundle);
}
+ for (OutlineSpan span : text.getSpans(0, text.length(), OutlineSpan.class)) {
+ Bundle bundle =
+ spanToBundle(text, span, /* spanType= */ OUTLINE_WIDTH, /* params= */ span.toBundle());
+ bundledCustomSpans.add(bundle);
+ }
return bundledCustomSpans;
}
@@ -113,6 +121,8 @@ public static void unbundleAndApplyCustomSpan(Bundle customSpanBundle, Spannable
case HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT:
text.setSpan(new HorizontalTextInVerticalContextSpan(), start, end, flags);
break;
+ case OUTLINE_WIDTH:
+ text.setSpan(OutlineSpan.fromBundle(checkNotNull(span)),start,end,flags);
default:
break;
}
diff --git a/libraries/common/src/main/java/androidx/media3/common/text/OutlineSpan.java b/libraries/common/src/main/java/androidx/media3/common/text/OutlineSpan.java
new file mode 100644
index 00000000000..a9afd4ffd8f
--- /dev/null
+++ b/libraries/common/src/main/java/androidx/media3/common/text/OutlineSpan.java
@@ -0,0 +1,41 @@
+package androidx.media3.common.text;
+
+import android.os.Bundle;
+import android.text.TextPaint;
+import android.text.style.CharacterStyle;
+import androidx.annotation.Px;
+import androidx.media3.common.util.Util;
+
+/**
+ * A styling span for outline width on subtitle text.
+ */
+public class OutlineSpan extends CharacterStyle {
+
+ /**
+ * The outline size in pixels.
+ */
+ @Px
+ public final float outlineWidth;
+ private static final String FIELD_OUTLINE_WIDTH = Util.intToStringMaxRadix(0);
+
+ public OutlineSpan(@Px float outlineWidth) {
+ this.outlineWidth = outlineWidth;
+ }
+
+ public Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putFloat(FIELD_OUTLINE_WIDTH, outlineWidth);
+ return bundle;
+ }
+
+ public static OutlineSpan fromBundle(Bundle bundle) {
+ return new OutlineSpan(
+ /* outlineWidth= */ bundle.getFloat(FIELD_OUTLINE_WIDTH)
+ );
+ }
+
+ @Override
+ public void updateDrawState(TextPaint tp) {
+ tp.setStrokeWidth(outlineWidth);
+ }
+}
diff --git a/libraries/ui/src/main/java/androidx/media3/ui/SpannedToHtmlConverter.java b/libraries/ui/src/main/java/androidx/media3/ui/SpannedToHtmlConverter.java
index 930579f78c0..de56631b9b1 100644
--- a/libraries/ui/src/main/java/androidx/media3/ui/SpannedToHtmlConverter.java
+++ b/libraries/ui/src/main/java/androidx/media3/ui/SpannedToHtmlConverter.java
@@ -30,6 +30,7 @@
import android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.media3.common.text.HorizontalTextInVerticalContextSpan;
+import androidx.media3.common.text.OutlineSpan;
import androidx.media3.common.text.RubySpan;
import androidx.media3.common.text.TextAnnotation;
import androidx.media3.common.text.TextEmphasisSpan;
@@ -207,6 +208,12 @@ private static String getOpeningTag(Object span, float displayDensity) {
+ "-webkit-text-emphasis-position:%2$s;text-emphasis-position:%2$s;"
+ "display:inline-block;'>",
style, position);
+ } else if (span instanceof OutlineSpan) {
+ OutlineSpan outline = ((OutlineSpan) span);
+ return Util.formatInvariant(
+ "",
+ outline.outlineWidth
+ );
} else {
return null;
}
@@ -220,7 +227,8 @@ private static String getClosingTag(Object span) {
|| span instanceof HorizontalTextInVerticalContextSpan
|| span instanceof AbsoluteSizeSpan
|| span instanceof RelativeSizeSpan
- || span instanceof TextEmphasisSpan) {
+ || span instanceof TextEmphasisSpan
+ || span instanceof OutlineSpan) {
return "";
} else if (span instanceof TypefaceSpan) {
@Nullable String fontFamily = ((TypefaceSpan) span).getFamily();
diff --git a/libraries/ui/src/main/java/androidx/media3/ui/WebViewSubtitleOutput.java b/libraries/ui/src/main/java/androidx/media3/ui/WebViewSubtitleOutput.java
index d1ee761e47e..e589cc18237 100644
--- a/libraries/ui/src/main/java/androidx/media3/ui/WebViewSubtitleOutput.java
+++ b/libraries/ui/src/main/java/androidx/media3/ui/WebViewSubtitleOutput.java
@@ -177,11 +177,13 @@ private void updateWebView() {
+ "font-size:%s;"
+ "line-height:%.2f;"
+ "text-shadow:%s;"
+ + "-webkit-text-stroke-color: %s;"
+ "'>",
HtmlUtils.toCssRgba(style.foregroundColor),
convertTextSizeToCss(defaultTextSizeType, defaultTextSize),
CSS_LINE_HEIGHT,
- convertCaptionStyleToCssTextShadow(style)));
+ convertCaptionStyleToCssTextShadow(style),
+ HtmlUtils.toCssRgba(style.edgeColor)));
Map cssRuleSets = new HashMap<>();
cssRuleSets.put(