diff --git a/libraries/common/src/main/java/androidx/media3/common/Format.java b/libraries/common/src/main/java/androidx/media3/common/Format.java index 3d440c5bd2..ad2b579743 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Format.java +++ b/libraries/common/src/main/java/androidx/media3/common/Format.java @@ -15,13 +15,20 @@ */ package androidx.media3.common; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.os.Bundle; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.base.Joiner; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -172,6 +179,7 @@ public final class Format implements Bundleable { // Text specific. private int accessibilityChannel; + @UnstableApi private @CueReplacementBehavior int cueReplacementBehavior; // Image specific @@ -201,6 +209,7 @@ public final class Format implements Bundleable { pcmEncoding = NO_VALUE; // Text specific. accessibilityChannel = NO_VALUE; + cueReplacementBehavior = CUE_REPLACEMENT_BEHAVIOR_MERGE; // Image specific. tileCountHorizontal = NO_VALUE; tileCountVertical = NO_VALUE; @@ -248,6 +257,7 @@ public final class Format implements Bundleable { this.encoderPadding = format.encoderPadding; // Text specific. this.accessibilityChannel = format.accessibilityChannel; + this.cueReplacementBehavior = format.cueReplacementBehavior; // Image specific. this.tileCountHorizontal = format.tileCountHorizontal; this.tileCountVertical = format.tileCountVertical; @@ -626,6 +636,19 @@ public final class Format implements Bundleable { return this; } + /** + * Sets {@link Format#cueReplacementBehavior}. The default value is {@link + * #CUE_REPLACEMENT_BEHAVIOR_MERGE}. + * + * @param cueReplacementBehavior The {@link Format.CueReplacementBehavior}. + * @return The builder. + */ + @CanIgnoreReturnValue + public Builder setCueReplacementBehavior(@CueReplacementBehavior int cueReplacementBehavior) { + this.cueReplacementBehavior = cueReplacementBehavior; + return this; + } + // Image specific. /** @@ -673,6 +696,36 @@ public final class Format implements Bundleable { } } + /** + * The replacement behaviors for consecutive samples in a {@linkplain C#TRACK_TYPE_TEXT text + * track} of type {@link MimeTypes#APPLICATION_MEDIA3_CUES}. + */ + @UnstableApi + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef({ + CUE_REPLACEMENT_BEHAVIOR_MERGE, + CUE_REPLACEMENT_BEHAVIOR_REPLACE, + }) + public @interface CueReplacementBehavior {} + + /** + * Subsequent cues should be merged with any previous cues that should still be shown on screen. + * + *
Tracks with this behavior must not contain samples with an {@linkplain C#TIME_UNSET unset} + * duration. + */ + @UnstableApi public static final int CUE_REPLACEMENT_BEHAVIOR_MERGE = 1; + + /** + * Subsequent cues should replace all previous cues. + * + *
Tracks with this behavior may contain samples with an {@linkplain C#TIME_UNSET unset} + * duration (but the duration may also be set to a 'real' value). + */ + @UnstableApi public static final int CUE_REPLACEMENT_BEHAVIOR_REPLACE = 2; + /** A value for various fields to indicate that the field's value is unknown or not applicable. */ public static final int NO_VALUE = -1; @@ -847,6 +900,12 @@ public final class Format implements Bundleable { /** The Accessibility channel, or {@link #NO_VALUE} if not known or applicable. */ @UnstableApi public final int accessibilityChannel; + /** + * The replacement behavior that should be followed when handling consecutive samples in a + * {@linkplain C#TRACK_TYPE_TEXT text track} of type {@link MimeTypes#APPLICATION_MEDIA3_CUES}. + */ + @UnstableApi public final @CueReplacementBehavior int cueReplacementBehavior; + // Image specific. /** @@ -908,6 +967,7 @@ public final class Format implements Bundleable { encoderPadding = builder.encoderPadding == NO_VALUE ? 0 : builder.encoderPadding; // Text specific. accessibilityChannel = builder.accessibilityChannel; + cueReplacementBehavior = builder.cueReplacementBehavior; // Image specific. tileCountHorizontal = builder.tileCountHorizontal; tileCountVertical = builder.tileCountVertical; diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/CuesWithTiming.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/CuesWithTiming.java index f37a2baa6f..6fc1a5b9e3 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/CuesWithTiming.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/CuesWithTiming.java @@ -17,6 +17,7 @@ package androidx.media3.extractor.text; import androidx.media3.common.C; +import androidx.media3.common.Format; import androidx.media3.common.text.Cue; import androidx.media3.common.util.UnstableApi; import com.google.common.collect.ImmutableList; @@ -41,9 +42,12 @@ public class CuesWithTiming { * The duration for which {@link #cues} should be shown on screen, in microseconds, or {@link * C#TIME_UNSET} if not known. * - *
If this value is set then {@link #cues} from multiple instances may be shown on the screen - * simultaneously (if their durations overlap). If this value is {@link C#TIME_UNSET} then {@link - * #cues} should be shown on the screen until the {@link #startTimeUs} of the next instance. + *
If {@link Format#cueReplacementBehavior} is {@link Format#CUE_REPLACEMENT_BEHAVIOR_MERGE} + * then cues from multiple instances will be shown on screen simultaneously if their start times + * and durations overlap. + * + *
{@link C#TIME_UNSET} is only permitted if the {@link Format#cueReplacementBehavior} of the + * current track is {@link Format#CUE_REPLACEMENT_BEHAVIOR_REPLACE}. */ public final long durationUs; diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleParser.java index 4634c84a3f..928824514d 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleParser.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleParser.java @@ -16,9 +16,12 @@ package androidx.media3.extractor.text; +import static androidx.media3.common.Format.CUE_REPLACEMENT_BEHAVIOR_MERGE; + import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.Format; +import androidx.media3.common.Format.CueReplacementBehavior; import androidx.media3.common.util.UnstableApi; import java.util.List; @@ -95,4 +98,16 @@ public interface SubtitleParser { *
The default implementation is a no-op. */ default void reset() {} + + /** + * Returns the {@link CueReplacementBehavior} for consecutive {@link CuesWithTiming} emitted by + * this implementation. + * + *
A given instance must always return the same value from this method. + * + *
The default implementation returns {@link Format#CUE_REPLACEMENT_BEHAVIOR_MERGE}.
+ */
+ default @CueReplacementBehavior int getCueReplacementBehavior() {
+ return CUE_REPLACEMENT_BEHAVIOR_MERGE;
+ }
}
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleTranscodingTrackOutput.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleTranscodingTrackOutput.java
index 9b8e37bdac..d5c9b01c6b 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleTranscodingTrackOutput.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleTranscodingTrackOutput.java
@@ -26,6 +26,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.DataReader;
import androidx.media3.common.Format;
+import androidx.media3.common.Format.CueReplacementBehavior;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.Util;
@@ -86,6 +87,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (currentSubtitleParser == null) {
delegate.format(format);
} else {
+ @CueReplacementBehavior
+ int nextCuesBehavior = currentSubtitleParser.getCueReplacementBehavior();
delegate.format(
format
.buildUpon()
@@ -94,6 +97,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Reset this value to the default. All non-default timestamp adjustments are done
// below in sampleMetadata() and there are no 'subsamples' after transcoding.
.setSubsampleOffsetUs(Format.OFFSET_SAMPLE_RELATIVE)
+ .setCueReplacementBehavior(nextCuesBehavior)
.build());
}
}
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/dvb/DvbParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/dvb/DvbParser.java
index 400710e5eb..6cfeeca1b3 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/dvb/DvbParser.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/dvb/DvbParser.java
@@ -26,6 +26,8 @@ import android.graphics.PorterDuffXfermode;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
+import androidx.media3.common.Format;
+import androidx.media3.common.Format.CueReplacementBehavior;
import androidx.media3.common.text.Cue;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableBitArray;
@@ -128,6 +130,11 @@ public final class DvbParser implements SubtitleParser {
subtitleService.reset();
}
+ @Override
+ public @CueReplacementBehavior int getCueReplacementBehavior() {
+ return Format.CUE_REPLACEMENT_BEHAVIOR_REPLACE;
+ }
+
@Override
public ImmutableList