diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 3b34c286c7..9921db756e 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -11,6 +11,9 @@
* Add a method to `AdPlaybackState` to allow resetting an ad group so that
it can be played again
([#9615](https://github.com/google/ExoPlayer/issues/9615)).
+* DASH:
+ * Support the `forced-subtitle` track role
+ ([#9727](https://github.com/google/ExoPlayer/issues/9727)).
* HLS:
* Support key-frame accurate seeking in HLS
([#2882](https://github.com/google/ExoPlayer/issues/2882)).
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
index ebafbee31d..4ade8ce361 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
@@ -1475,6 +1475,8 @@ public class DashManifestParser extends DefaultHandler
case "main":
return C.SELECTION_FLAG_DEFAULT;
case "forced_subtitle":
+ // Support both hyphen and underscore (https://github.com/google/ExoPlayer/issues/9727).
+ case "forced-subtitle":
return C.SELECTION_FLAG_FORCED;
default:
return 0;
@@ -1545,6 +1547,8 @@ public class DashManifestParser extends DefaultHandler
case "caption":
return C.ROLE_FLAG_CAPTION;
case "forced_subtitle":
+ // Support both hyphen and underscore (https://github.com/google/ExoPlayer/issues/9727).
+ case "forced-subtitle":
case "subtitle":
return C.ROLE_FLAG_SUBTITLE;
case "sign":
diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java
index 6926a05859..cb2d216c32 100644
--- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java
+++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java
@@ -254,7 +254,13 @@ public class DashManifestParserTest {
assertThat(format.selectionFlags).isEqualTo(C.SELECTION_FLAG_FORCED);
assertThat(adaptationSets.get(1).type).isEqualTo(C.TRACK_TYPE_TEXT);
+ // Ensure that forced-subtitle and forced_subtitle are both parsed as a 'forced' text track.
+ // https://github.com/google/ExoPlayer/issues/9727
format = adaptationSets.get(2).representations.get(0).format;
+ assertThat(format.roleFlags).isEqualTo(C.ROLE_FLAG_SUBTITLE);
+ assertThat(format.selectionFlags).isEqualTo(C.SELECTION_FLAG_FORCED);
+
+ format = adaptationSets.get(3).representations.get(0).format;
assertThat(format.containerMimeType).isEqualTo(MimeTypes.APPLICATION_TTML);
assertThat(format.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_TTML);
assertThat(format.codecs).isNull();
@@ -586,10 +592,11 @@ public class DashManifestParserTest {
assertThat(manifest.getPeriodCount()).isEqualTo(1);
List adaptationSets = manifest.getPeriod(0).adaptationSets;
- assertThat(adaptationSets).hasSize(3);
+ assertThat(adaptationSets).hasSize(4);
assertThat(getAvailabilityTimeOffsetUs(adaptationSets.get(0))).isEqualTo(C.TIME_UNSET);
assertThat(getAvailabilityTimeOffsetUs(adaptationSets.get(1))).isEqualTo(C.TIME_UNSET);
assertThat(getAvailabilityTimeOffsetUs(adaptationSets.get(2))).isEqualTo(C.TIME_UNSET);
+ assertThat(getAvailabilityTimeOffsetUs(adaptationSets.get(3))).isEqualTo(C.TIME_UNSET);
}
@Test
diff --git a/testdata/src/test/assets/media/mpd/sample_mpd_text b/testdata/src/test/assets/media/mpd/sample_mpd_text
index f940527037..4220f5b2f2 100644
--- a/testdata/src/test/assets/media/mpd/sample_mpd_text
+++ b/testdata/src/test/assets/media/mpd/sample_mpd_text
@@ -13,11 +13,17 @@
-
+
https://test.com/0
+
+
+
+ https://test.com/0
+
+
https://test.com/0