diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 024eab8267..02f888ed06 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,6 +3,8 @@ ### Unreleased changes * Common Library: + * Add `Format.customData` to store app-provided custom information about + `Format` instances. * ExoPlayer: * Fix some audio focus inconsistencies, e.g. not reporting full or transient focus loss while the player is paused 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 188a7f25d8..7c2b4193f4 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Format.java +++ b/libraries/common/src/main/java/androidx/media3/common/Format.java @@ -37,6 +37,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -149,6 +150,7 @@ public final class Format { private int peakBitrate; @Nullable private String codecs; @Nullable private Metadata metadata; + @Nullable private Object customData; // Container specific. @@ -241,6 +243,7 @@ public final class Format { this.peakBitrate = format.peakBitrate; this.codecs = format.codecs; this.metadata = format.metadata; + this.customData = format.customData; // Container specific. this.containerMimeType = format.containerMimeType; // Sample specific. @@ -414,6 +417,22 @@ public final class Format { return this; } + /** + * Sets the opaque object {@link Format#customData}. The default value is null. + * + *
This value is not included in serialized {@link Bundle} instances of this class that are + * used to transfer data to other processes. + * + * @param customData The {@link Format#customData}. + * @return The builder. + */ + @UnstableApi + @CanIgnoreReturnValue + public Builder setCustomData(@Nullable Object customData) { + this.customData = customData; + return this; + } + // Container specific. /** @@ -861,6 +880,15 @@ public final class Format { /** Metadata, or null if unknown or not applicable. */ @UnstableApi @Nullable public final Metadata metadata; + /** + * An extra opaque object that can be added to the {@link Format} to provide additional + * information that can be passed through the player. + * + *
This value is not included in serialized {@link Bundle} instances of this class that are + * used to transfer data to other processes. + */ + @UnstableApi @Nullable public final Object customData; + // Container specific. /** The MIME type of the container, or null if unknown or not applicable. */ @@ -1021,6 +1049,7 @@ public final class Format { bitrate = peakBitrate != NO_VALUE ? peakBitrate : averageBitrate; codecs = builder.codecs; metadata = builder.metadata; + customData = builder.customData; // Container specific. containerMimeType = builder.containerMimeType; // Sample specific. @@ -1204,6 +1233,7 @@ public final class Format { result = 31 * result + peakBitrate; result = 31 * result + (codecs == null ? 0 : codecs.hashCode()); result = 31 * result + (metadata == null ? 0 : metadata.hashCode()); + result = 31 * result + (customData == null ? 0 : customData.hashCode()); // Container specific. result = 31 * result + (containerMimeType == null ? 0 : containerMimeType.hashCode()); // Sample specific. @@ -1273,18 +1303,19 @@ public final class Format { && cryptoType == other.cryptoType && Float.compare(frameRate, other.frameRate) == 0 && Float.compare(pixelWidthHeightRatio, other.pixelWidthHeightRatio) == 0 - && Util.areEqual(id, other.id) - && Util.areEqual(label, other.label) + && Objects.equals(id, other.id) + && Objects.equals(label, other.label) && labels.equals(other.labels) - && Util.areEqual(codecs, other.codecs) - && Util.areEqual(containerMimeType, other.containerMimeType) - && Util.areEqual(sampleMimeType, other.sampleMimeType) - && Util.areEqual(language, other.language) + && Objects.equals(codecs, other.codecs) + && Objects.equals(containerMimeType, other.containerMimeType) + && Objects.equals(sampleMimeType, other.sampleMimeType) + && Objects.equals(language, other.language) && Arrays.equals(projectionData, other.projectionData) - && Util.areEqual(metadata, other.metadata) - && Util.areEqual(colorInfo, other.colorInfo) - && Util.areEqual(drmInitData, other.drmInitData) - && initializationDataEquals(other); + && Objects.equals(metadata, other.metadata) + && Objects.equals(colorInfo, other.colorInfo) + && Objects.equals(drmInitData, other.drmInitData) + && initializationDataEquals(other) + && Objects.equals(customData, other.customData); } /** @@ -1382,6 +1413,9 @@ public final class Format { Joiner.on(',').appendTo(builder, Util.getRoleFlagStrings(format.roleFlags)); builder.append("]"); } + if (format.customData != null) { + builder.append(", customData=").append(format.customData); + } return builder.toString(); } diff --git a/libraries/common/src/test/java/androidx/media3/common/FormatTest.java b/libraries/common/src/test/java/androidx/media3/common/FormatTest.java index e95ee68631..8f1a618f3d 100644 --- a/libraries/common/src/test/java/androidx/media3/common/FormatTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/FormatTest.java @@ -22,12 +22,13 @@ import static androidx.media3.test.utils.TestUtil.buildTestData; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; -import android.os.Bundle; +import androidx.annotation.Nullable; import androidx.media3.test.utils.FakeMetadataEntry; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,22 +43,27 @@ public final class FormatTest { } @Test - public void roundTripViaBundle_ofParameters_yieldsEqualInstance() { + public void roundTripViaBundle_includeMetadata_includesAllBundledFields() { Format formatToBundle = createTestFormat(); + Format formatFromBundle = Format.fromBundle(formatToBundle.toBundle(/* excludeMetadata= */ false)); - assertThat(formatFromBundle).isEqualTo(formatToBundle); + // Expect all data to be bundled except the custom data. + Format expectedRoundTripFormat = formatToBundle.buildUpon().setCustomData(null).build(); + assertThat(formatFromBundle).isEqualTo(expectedRoundTripFormat); } @Test - public void roundTripViaBundle_excludeMetadata_hasMetadataExcluded() { + public void roundTripViaBundle_excludeMetadata_includesAllBundledFieldsExceptMetadata() { Format format = createTestFormat(); - Bundle bundleWithMetadataExcluded = format.toBundle(/* excludeMetadata= */ true); + Format formatFromBundle = Format.fromBundle(format.toBundle(/* excludeMetadata= */ true)); - Format formatWithMetadataExcluded = Format.fromBundle(bundleWithMetadataExcluded); - assertThat(formatWithMetadataExcluded).isEqualTo(format.buildUpon().setMetadata(null).build()); + // Expect all data to be bundled except the custom data and metadata. + Format expectedRoundTripFormat = + format.buildUpon().setCustomData(null).setMetadata(null).build(); + assertThat(formatFromBundle).isEqualTo(expectedRoundTripFormat); } @Test @@ -153,6 +159,7 @@ public final class FormatTest { .setPeakBitrate(2048) .setCodecs("codec") .setMetadata(metadata) + .setCustomData(new TestCustomData("CustomData", 100)) .setContainerMimeType(VIDEO_MP4) .setSampleMimeType(MimeTypes.VIDEO_H264) .setMaxInputSize(5000) @@ -178,4 +185,34 @@ public final class FormatTest { .setTileCountVertical(40) .build(); } + + private static final class TestCustomData { + public final String extraMetadata; + public final int customInt; + + public TestCustomData(String extraMetadata, int customInt) { + this.extraMetadata = extraMetadata; + this.customInt = customInt; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (extraMetadata == null ? 0 : extraMetadata.hashCode()); + result = 31 * result + customInt; + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TestCustomData other = (TestCustomData) obj; + return Objects.equals(extraMetadata, other.extraMetadata) && customInt == other.customInt; + } + } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DecoderAudioRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DecoderAudioRenderer.java index 12b4860c13..5a40f90214 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DecoderAudioRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DecoderAudioRenderer.java @@ -449,6 +449,7 @@ public abstract class DecoderAudioRenderer< .setEncoderDelay(encoderDelay) .setEncoderPadding(encoderPadding) .setMetadata(inputFormat.metadata) + .setCustomData(inputFormat.customData) .setId(inputFormat.id) .setLabel(inputFormat.label) .setLabels(inputFormat.labels) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java index 24d4cf307b..e3329d2739 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java @@ -573,6 +573,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media .setEncoderDelay(format.encoderDelay) .setEncoderPadding(format.encoderPadding) .setMetadata(format.metadata) + .setCustomData(format.customData) .setId(format.id) .setLabel(format.label) .setLabels(format.labels)