From d717a0c5d50174b672ad0c2a42251b0eddf26ed6 Mon Sep 17 00:00:00 2001 From: Gilles Khouzam Date: Fri, 31 May 2024 12:19:31 -0700 Subject: [PATCH 1/2] Add a CustomData field to the Format class Summary: This change aims to add a generic `CustomData` field to the `Format` class. The intent is to allow ExoPlayer customers to add extra data to the Format class without forcing specific data to be included, impacting customers that do not need it and would allow for the data to be changed without requiring changes to the `Media3` codebase. --- .../java/androidx/media3/common/Format.java | 35 +++++++++++++++- .../androidx/media3/common/FormatTest.java | 42 ++++++++++++++++++- .../exoplayer/audio/DecoderAudioRenderer.java | 1 + .../audio/MediaCodecAudioRenderer.java | 1 + 4 files changed, 77 insertions(+), 2 deletions(-) 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..046a86fe51 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Format.java +++ b/libraries/common/src/main/java/androidx/media3/common/Format.java @@ -196,6 +196,9 @@ public final class Format { private @C.CryptoType int cryptoType; + // Extra custom data added to the class. + @Nullable private Object customData; + /** Creates a new instance with default values. */ public Builder() { labels = ImmutableList.of(); @@ -273,6 +276,8 @@ public final class Format { this.tileCountVertical = format.tileCountVertical; // Provided by the source. this.cryptoType = format.cryptoType; + // Extra custom data added to the class. + this.customData = format.customData; } /** @@ -729,6 +734,20 @@ public final class Format { return this; } + // Extra custom data added to the class. + + /** + * Sets the opaque object {@link Format#customData}. The default value is null. + * + * @param customData The {@link Format#customData}. + * @return The builder. + */ + @CanIgnoreReturnValue + public Builder setCustomData(@Nullable Object customData) { + this.customData = customData; + return this; + } + // Build. public Format build() { @@ -985,6 +1004,12 @@ public final class Format { */ @UnstableApi public final @C.CryptoType int cryptoType; + /** + * An extra opaque object that can be added to the {@link Format} to provide additional information + * that can be passed through the player. + */ + @UnstableApi @Nullable public final Object customData; + // Lazily initialized hashcode. private int hashCode; @@ -1060,6 +1085,8 @@ public final class Format { } else { cryptoType = builder.cryptoType; } + // Extra custom data added to the class. + customData = builder.customData; } /** Returns a {@link Format.Builder} initialized with the values of this instance. */ @@ -1234,6 +1261,8 @@ public final class Format { result = 31 * result + tileCountVertical; // Provided by the source. result = 31 * result + cryptoType; + // Extra custom data added to the class. + result = 31 * result + (customData == null ? 0 : customData.hashCode()); hashCode = result; } return hashCode; @@ -1284,7 +1313,8 @@ public final class Format { && Util.areEqual(metadata, other.metadata) && Util.areEqual(colorInfo, other.colorInfo) && Util.areEqual(drmInitData, other.drmInitData) - && initializationDataEquals(other); + && initializationDataEquals(other) + && Util.areEqual(customData, other.customData); } /** @@ -1382,6 +1412,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..a216276b71 100644 --- a/libraries/common/src/test/java/androidx/media3/common/FormatTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/FormatTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import android.os.Bundle; +import androidx.media3.common.util.Util; import androidx.media3.test.utils.FakeMetadataEntry; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; @@ -35,6 +36,36 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public final class FormatTest { + public static class ExoCustomData { + public final String extraMetadata; + public final int customInt; + + public ExoCustomData(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(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ExoCustomData other = (ExoCustomData) obj; + return Util.areEqual(extraMetadata, other.extraMetadata) && customInt == other.customInt; + } + } + @Test public void buildUponFormat_createsEqualFormat() { Format testFormat = createTestFormat(); @@ -116,7 +147,16 @@ public final class FormatTest { .build()); } - private static Format createTestFormat() { + @Test + public void copyFormat_copiesCustomData() { + Format format = createTestFormat().buildUpon().setCustomData(new ExoCustomData("CustomData", 100)).build(); + + Format copy = format.buildUpon().build(); + assertThat(format.customData).isEqualTo(copy.customData); + assertThat(format.customData).isEqualTo(new ExoCustomData("CustomData", 100)); + } + +private static Format createTestFormat() { byte[] initData1 = new byte[] {1, 2, 3}; byte[] initData2 = new byte[] {4, 5, 6}; List initializationData = new ArrayList<>(); 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) From b6070a529939d2826a337248eb05bd5ef9112617 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 24 Jun 2024 09:55:41 +0100 Subject: [PATCH 2/2] Formatting fixes --- RELEASENOTES.md | 2 + .../java/androidx/media3/common/Format.java | 79 ++++++++-------- .../androidx/media3/common/FormatTest.java | 93 +++++++++---------- 3 files changed, 87 insertions(+), 87 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 39184f0d7f..37733a15ea 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 046a86fe51..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. @@ -196,9 +198,6 @@ public final class Format { private @C.CryptoType int cryptoType; - // Extra custom data added to the class. - @Nullable private Object customData; - /** Creates a new instance with default values. */ public Builder() { labels = ImmutableList.of(); @@ -244,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. @@ -276,8 +276,6 @@ public final class Format { this.tileCountVertical = format.tileCountVertical; // Provided by the source. this.cryptoType = format.cryptoType; - // Extra custom data added to the class. - this.customData = format.customData; } /** @@ -419,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. /** @@ -734,20 +748,6 @@ public final class Format { return this; } - // Extra custom data added to the class. - - /** - * Sets the opaque object {@link Format#customData}. The default value is null. - * - * @param customData The {@link Format#customData}. - * @return The builder. - */ - @CanIgnoreReturnValue - public Builder setCustomData(@Nullable Object customData) { - this.customData = customData; - return this; - } - // Build. public Format build() { @@ -880,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. */ @@ -1004,12 +1013,6 @@ public final class Format { */ @UnstableApi public final @C.CryptoType int cryptoType; - /** - * An extra opaque object that can be added to the {@link Format} to provide additional information - * that can be passed through the player. - */ - @UnstableApi @Nullable public final Object customData; - // Lazily initialized hashcode. private int hashCode; @@ -1046,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. @@ -1085,8 +1089,6 @@ public final class Format { } else { cryptoType = builder.cryptoType; } - // Extra custom data added to the class. - customData = builder.customData; } /** Returns a {@link Format.Builder} initialized with the values of this instance. */ @@ -1231,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. @@ -1261,8 +1264,6 @@ public final class Format { result = 31 * result + tileCountVertical; // Provided by the source. result = 31 * result + cryptoType; - // Extra custom data added to the class. - result = 31 * result + (customData == null ? 0 : customData.hashCode()); hashCode = result; } return hashCode; @@ -1302,19 +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) + && Objects.equals(metadata, other.metadata) + && Objects.equals(colorInfo, other.colorInfo) + && Objects.equals(drmInitData, other.drmInitData) && initializationDataEquals(other) - && Util.areEqual(customData, other.customData); + && Objects.equals(customData, other.customData); } /** 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 a216276b71..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,13 +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.media3.common.util.Util; +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; @@ -36,36 +36,6 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public final class FormatTest { - public static class ExoCustomData { - public final String extraMetadata; - public final int customInt; - - public ExoCustomData(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(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ExoCustomData other = (ExoCustomData) obj; - return Util.areEqual(extraMetadata, other.extraMetadata) && customInt == other.customInt; - } - } - @Test public void buildUponFormat_createsEqualFormat() { Format testFormat = createTestFormat(); @@ -73,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 @@ -147,16 +122,7 @@ public final class FormatTest { .build()); } - @Test - public void copyFormat_copiesCustomData() { - Format format = createTestFormat().buildUpon().setCustomData(new ExoCustomData("CustomData", 100)).build(); - - Format copy = format.buildUpon().build(); - assertThat(format.customData).isEqualTo(copy.customData); - assertThat(format.customData).isEqualTo(new ExoCustomData("CustomData", 100)); - } - -private static Format createTestFormat() { + private static Format createTestFormat() { byte[] initData1 = new byte[] {1, 2, 3}; byte[] initData2 = new byte[] {4, 5, 6}; List initializationData = new ArrayList<>(); @@ -193,6 +159,7 @@ private static Format createTestFormat() { .setPeakBitrate(2048) .setCodecs("codec") .setMetadata(metadata) + .setCustomData(new TestCustomData("CustomData", 100)) .setContainerMimeType(VIDEO_MP4) .setSampleMimeType(MimeTypes.VIDEO_H264) .setMaxInputSize(5000) @@ -218,4 +185,34 @@ private static Format createTestFormat() { .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; + } + } }