diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/SubtitleDecoderFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/SubtitleDecoderFactory.java index 24d7cab070..9c93a01f3b 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/SubtitleDecoderFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/SubtitleDecoderFactory.java @@ -23,9 +23,7 @@ import androidx.media3.extractor.text.DefaultSubtitleParserFactory; import androidx.media3.extractor.text.SubtitleDecoder; import androidx.media3.extractor.text.SubtitleParser; import androidx.media3.extractor.text.cea.Cea608Decoder; -import androidx.media3.extractor.text.cea.Cea608Parser; import androidx.media3.extractor.text.cea.Cea708Decoder; -import androidx.media3.extractor.text.cea.Cea708Parser; import java.util.Objects; /** A factory for {@link SubtitleDecoder} instances. */ @@ -82,13 +80,11 @@ public interface SubtitleDecoderFactory { case MimeTypes.APPLICATION_CEA608: case MimeTypes.APPLICATION_MP4CEA608: return new Cea608Decoder( - new Cea608Parser( - mimeType, - format.accessibilityChannel, - Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS)); + mimeType, + format.accessibilityChannel, + Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS); case MimeTypes.APPLICATION_CEA708: - return new Cea708Decoder( - new Cea708Parser(format.accessibilityChannel, format.initializationData)); + return new Cea708Decoder(format.accessibilityChannel, format.initializationData); default: break; } diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea608Decoder.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea608Decoder.java index 0885c949e2..69ae611928 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea608Decoder.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea608Decoder.java @@ -36,6 +36,12 @@ import java.nio.ByteBuffer; @UnstableApi public final class Cea608Decoder extends CeaDecoder { + /** + * The minimum value for the {@code validDataChannelTimeoutMs} constructor parameter permitted by + * ANSI/CTA-608-E R-2014 Annex C.9. + */ + public static final long MIN_DATA_CHANNEL_TIMEOUT_MS = Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS; + private static final CuesWithTiming EMPTY_CUES = new CuesWithTiming( ImmutableList.of(), /* startTimeUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET); @@ -49,10 +55,15 @@ public final class Cea608Decoder extends CeaDecoder { /** * Constructs an instance. * - * @param parser A {@link Cea608Parser} to parse the subtitle data. + * @param mimeType The MIME type of the CEA-608 data. + * @param accessibilityChannel The Accessibility channel, or {@link Format#NO_VALUE} if unknown. + * @param validDataChannelTimeoutMs The timeout (in milliseconds) permitted by ANSI/CTA-608-E + * R-2014 Annex C.9 to clear "stuck" captions where no removal control code is received. The + * timeout should be at least {@link #MIN_DATA_CHANNEL_TIMEOUT_MS} or {@link C#TIME_UNSET} for + * no timeout. This applies an upper-bound on the duration of a single caption. */ - public Cea608Decoder(Cea608Parser parser) { - this.cea608Parser = parser; + public Cea608Decoder(String mimeType, int accessibilityChannel, long validDataChannelTimeoutMs) { + this.cea608Parser = new Cea608Parser(mimeType, accessibilityChannel, validDataChannelTimeoutMs); lastCueUpdateUs = C.TIME_UNSET; } diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea608Parser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea608Parser.java index 6f0a45fad2..921618f6aa 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea608Parser.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea608Parser.java @@ -51,7 +51,7 @@ import java.util.List; // format must happen during rendering), or re-add it to DefaultSubtitleParserFactory (if we're // able to solve the re-ordering issue during extraction). @UnstableApi -public final class Cea608Parser implements SubtitleParser { +/* package */ final class Cea608Parser implements SubtitleParser { /** * The {@link CueReplacementBehavior} for consecutive {@link CuesWithTiming} emitted by this diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Decoder.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Decoder.java index f0619c7034..4a33430ba3 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Decoder.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Decoder.java @@ -19,6 +19,8 @@ import static androidx.media3.common.util.Assertions.checkNotNull; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.Format; +import androidx.media3.common.util.CodecSpecificDataUtil; import androidx.media3.common.util.UnstableApi; import androidx.media3.extractor.text.CuesWithTiming; import androidx.media3.extractor.text.CuesWithTimingSubtitle; @@ -28,6 +30,7 @@ import androidx.media3.extractor.text.SubtitleInputBuffer; import androidx.media3.extractor.text.SubtitleParser.OutputOptions; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; +import java.util.List; /** A {@link SubtitleDecoder} for CEA-708 (also known as "EIA-708"). */ @UnstableApi @@ -40,10 +43,13 @@ public final class Cea708Decoder extends CeaDecoder { /** * Constructs an instance. * - * @param parser A {@link Cea708Parser} to parse the subtitle data. + * @param accessibilityChannel The accessibility channel, or {@link Format#NO_VALUE} if unknown. + * @param initializationData Optional initialization data for the decoder. If present, it must + * conform to the structure created by {@link + * CodecSpecificDataUtil#buildCea708InitializationData}. */ - public Cea708Decoder(Cea708Parser parser) { - this.cea708Parser = parser; + public Cea708Decoder(int accessibilityChannel, @Nullable List initializationData) { + this.cea708Parser = new Cea708Parser(accessibilityChannel, initializationData); } @Override diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Parser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Parser.java index dc7b43b670..1a0a23ca84 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Parser.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/cea/Cea708Parser.java @@ -52,7 +52,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; // format must happen during rendering), or re-add it to DefaultSubtitleParserFactory (if we're // able to solve the re-ordering issue during extraction). @UnstableApi -public final class Cea708Parser implements SubtitleParser { +/* package */ final class Cea708Parser implements SubtitleParser { /** * The {@link CueReplacementBehavior} for consecutive {@link CuesWithTiming} emitted by this diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea608DecoderTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea608DecoderTest.java index b8416bb9f5..3bbc813fb8 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea608DecoderTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea608DecoderTest.java @@ -45,10 +45,9 @@ public class Cea608DecoderTest { public void paintOnEmitsSubtitlesImmediately() throws Exception { Cea608Decoder decoder = new Cea608Decoder( - new Cea608Parser( - MimeTypes.APPLICATION_CEA608, - /* accessibilityChannel= */ 1, - Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS)); + MimeTypes.APPLICATION_CEA608, + /* accessibilityChannel= */ 1, + Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS); byte[] sample1 = Bytes.concat( // 'paint on' control character @@ -85,10 +84,9 @@ public class Cea608DecoderTest { public void paintOnEmitsSubtitlesImmediately_reordersOutOfOrderSamples() throws Exception { Cea608Decoder decoder = new Cea608Decoder( - new Cea608Parser( - MimeTypes.APPLICATION_CEA608, - /* accessibilityChannel= */ 1, - Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS)); + MimeTypes.APPLICATION_CEA608, + /* accessibilityChannel= */ 1, + Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS); byte[] sample1 = Bytes.concat( // 'paint on' control character @@ -127,10 +125,9 @@ public class Cea608DecoderTest { public void rollUpEmitsSubtitlesImmediately() throws Exception { Cea608Decoder decoder = new Cea608Decoder( - new Cea608Parser( - MimeTypes.APPLICATION_CEA608, - /* accessibilityChannel= */ 1, // field 1, channel 1 - Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS)); + MimeTypes.APPLICATION_CEA608, + /* accessibilityChannel= */ 1, // field 1, channel 1 + Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS); byte[] sample1 = Bytes.concat( // 'roll up 2 rows' control character @@ -182,10 +179,9 @@ public class Cea608DecoderTest { public void onlySelectedFieldIsUsed() throws Exception { Cea608Decoder decoder = new Cea608Decoder( - new Cea608Parser( - MimeTypes.APPLICATION_CEA608, - /* accessibilityChannel= */ 1, // field 1, channel 1 - Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS)); + MimeTypes.APPLICATION_CEA608, + /* accessibilityChannel= */ 1, // field 1, channel 1 + Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS); // field 1 (0xFC header): 'test subtitle' // field 2 (0xFD header): 'wrong field!' byte[] sample1 = @@ -223,10 +219,9 @@ public class Cea608DecoderTest { public void onlySelectedChannelIsUsed() throws Exception { Cea608Decoder decoder = new Cea608Decoder( - new Cea608Parser( - MimeTypes.APPLICATION_CEA608, - /* accessibilityChannel= */ 2, // field 1, channel 2 - Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS)); + MimeTypes.APPLICATION_CEA608, + /* accessibilityChannel= */ 2, // field 1, channel 2 + Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS); // field 1 (0xFC header), channel 1: 'wrong channel' // field 1 (0xFC header), channel 2: 'test subtitle' // field 2 (0xFD header), channel 1: 'wrong field!' @@ -317,10 +312,9 @@ public class Cea608DecoderTest { public void serviceSwitchOnField1Handled() throws Exception { Cea608Decoder decoder = new Cea608Decoder( - new Cea608Parser( - MimeTypes.APPLICATION_CEA608, - /* accessibilityChannel= */ 1, // field 1, channel 1 - Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS)); + MimeTypes.APPLICATION_CEA608, + /* accessibilityChannel= */ 1, // field 1, channel 1 + Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS); // field 1 (0xFC header): 'test' then service switch // field 2 (0xFD header): 'wrong!' byte[] sample1 = @@ -349,10 +343,9 @@ public class Cea608DecoderTest { public void serviceSwitchOnField2Handled() throws Exception { Cea608Decoder decoder = new Cea608Decoder( - new Cea608Parser( - MimeTypes.APPLICATION_CEA608, - /* accessibilityChannel= */ 3, // field 2, channel 1 - Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS)); + MimeTypes.APPLICATION_CEA608, + /* accessibilityChannel= */ 3, // field 2, channel 1 + Cea608Parser.MIN_DATA_CHANNEL_TIMEOUT_MS); // field 1 (0xFC header): 'wrong!' // field 2 (0xFD header): 'test' then service switch byte[] sample1 = diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea708DecoderTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea708DecoderTest.java index 2fa1c5f8c1..2653eae16e 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea708DecoderTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/cea/Cea708DecoderTest.java @@ -60,8 +60,8 @@ public class Cea708DecoderTest { public void singleServiceAndWindowDefinition() throws Exception { Cea708Decoder cea708Decoder = new Cea708Decoder( - new Cea708Parser( - /* accessibilityChannel= */ Format.NO_VALUE, /* initializationData= */ null)); + + /* accessibilityChannel= */ Format.NO_VALUE, /* initializationData= */ null); byte[] windowDefinition = TestUtil.createByteArray( 0x98, // DF0 command (define window 0)