diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d7f0901bd2..1ed740f23a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,8 @@ resource URIs where `package` is different to the package of the current application. This has always been documented to work, but wasn't correctly implemented until now. + * Normalize MIME types set by app code or read from media to be fully + lower-case. * ExoPlayer: * Add `PreloadMediaSource` and `PreloadMediaPeriod` that allows apps to preload the media source at a specific start position before playback, diff --git a/libraries/common/src/main/java/androidx/media3/common/DrmInitData.java b/libraries/common/src/main/java/androidx/media3/common/DrmInitData.java index 4f942fa739..32a5d234f7 100644 --- a/libraries/common/src/main/java/androidx/media3/common/DrmInitData.java +++ b/libraries/common/src/main/java/androidx/media3/common/DrmInitData.java @@ -296,7 +296,7 @@ public final class DrmInitData implements Comparator, Parcelable { UUID uuid, @Nullable String licenseServerUrl, String mimeType, @Nullable byte[] data) { this.uuid = Assertions.checkNotNull(uuid); this.licenseServerUrl = licenseServerUrl; - this.mimeType = Assertions.checkNotNull(mimeType); + this.mimeType = MimeTypes.normalizeMimeType(Assertions.checkNotNull(mimeType)); this.data = data; } 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 5795a1437b..e5dba212e6 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Format.java +++ b/libraries/common/src/main/java/androidx/media3/common/Format.java @@ -396,7 +396,7 @@ public final class Format implements Bundleable { */ @CanIgnoreReturnValue public Builder setContainerMimeType(@Nullable String containerMimeType) { - this.containerMimeType = containerMimeType; + this.containerMimeType = MimeTypes.normalizeMimeType(containerMimeType); return this; } @@ -410,7 +410,7 @@ public final class Format implements Bundleable { */ @CanIgnoreReturnValue public Builder setSampleMimeType(@Nullable String sampleMimeType) { - this.sampleMimeType = sampleMimeType; + this.sampleMimeType = MimeTypes.normalizeMimeType(sampleMimeType); return this; } diff --git a/libraries/common/src/main/java/androidx/media3/common/MediaItem.java b/libraries/common/src/main/java/androidx/media3/common/MediaItem.java index d03c683d68..bef87eb64e 100644 --- a/libraries/common/src/main/java/androidx/media3/common/MediaItem.java +++ b/libraries/common/src/main/java/androidx/media3/common/MediaItem.java @@ -1179,7 +1179,7 @@ public final class MediaItem implements Bundleable { @Nullable Object tag, long imageDurationMs) { this.uri = uri; - this.mimeType = mimeType; + this.mimeType = MimeTypes.normalizeMimeType(mimeType); this.drmConfiguration = drmConfiguration; this.adsConfiguration = adsConfiguration; this.streamKeys = streamKeys; @@ -1612,7 +1612,7 @@ public final class MediaItem implements Bundleable { /** Sets the MIME type. */ @CanIgnoreReturnValue public Builder setMimeType(@Nullable String mimeType) { - this.mimeType = mimeType; + this.mimeType = MimeTypes.normalizeMimeType(mimeType); return this; } @@ -1694,7 +1694,7 @@ public final class MediaItem implements Bundleable { @Nullable String label, @Nullable String id) { this.uri = uri; - this.mimeType = mimeType; + this.mimeType = MimeTypes.normalizeMimeType(mimeType); this.language = language; this.selectionFlags = selectionFlags; this.roleFlags = roleFlags; diff --git a/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java b/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java index fdfb10327f..54a08b6f94 100644 --- a/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java +++ b/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java @@ -25,6 +25,7 @@ import com.google.common.base.Ascii; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.checkerframework.checker.nullness.qual.PolyNull; import org.checkerframework.dataflow.qual.Pure; /** Defines common MIME types and helper methods. */ @@ -635,18 +636,30 @@ public final class MimeTypes { /** * Normalizes the MIME type provided so that equivalent MIME types are uniquely represented. * - * @param mimeType A MIME type to normalize. + * @param mimeType A MIME type to normalize, or null. * @return The normalized MIME type, or the argument MIME type if its normalized form is unknown. */ @UnstableApi - public static String normalizeMimeType(String mimeType) { + public static @PolyNull String normalizeMimeType(@PolyNull String mimeType) { + if (mimeType == null) { + return null; + } + mimeType = Ascii.toLowerCase(mimeType); switch (mimeType) { + // Normalize uncommon versions of some audio MIME types to their standard equivalent. case BASE_TYPE_AUDIO + "/x-flac": return AUDIO_FLAC; case BASE_TYPE_AUDIO + "/mp3": return AUDIO_MPEG; case BASE_TYPE_AUDIO + "/x-wav": return AUDIO_WAV; + // Normalize MIME types that are often written with upper-case letters to their common form. + case "application/x-mpegurl": + return APPLICATION_M3U8; + case "audio/mpeg-l1": + return AUDIO_MPEG_L1; + case "audio/mpeg-l2": + return AUDIO_MPEG_L2; default: return mimeType; } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadRequest.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadRequest.java index 2f69102775..b3bbd130f0 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadRequest.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadRequest.java @@ -23,6 +23,7 @@ import android.os.Parcelable; import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.MediaItem; +import androidx.media3.common.MimeTypes; import androidx.media3.common.StreamKey; import androidx.media3.common.util.Assertions; import androidx.media3.common.util.UnstableApi; @@ -61,7 +62,7 @@ public final class DownloadRequest implements Parcelable { /** Sets the {@link DownloadRequest#mimeType}. */ @CanIgnoreReturnValue public Builder setMimeType(@Nullable String mimeType) { - this.mimeType = mimeType; + this.mimeType = MimeTypes.normalizeMimeType(mimeType); return this; } diff --git a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/DefaultMediaSourceFactoryTest.java b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/DefaultMediaSourceFactoryTest.java index 8062cff051..1db89cce18 100644 --- a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/DefaultMediaSourceFactoryTest.java +++ b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/DefaultMediaSourceFactoryTest.java @@ -51,6 +51,18 @@ public class DefaultMediaSourceFactoryTest { assertThat(mediaSource).isInstanceOf(HlsMediaSource.class); } + @Test + public void createMediaSource_withMimeTypeLowerCaseLetters_hlsSource() { + DefaultMediaSourceFactory defaultMediaSourceFactory = + new DefaultMediaSourceFactory((Context) ApplicationProvider.getApplicationContext()); + MediaItem mediaItem = + new MediaItem.Builder().setUri(URI_MEDIA).setMimeType("application/x-mpegurl").build(); + + MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem); + + assertThat(mediaSource).isInstanceOf(HlsMediaSource.class); + } + @Test public void createMediaSource_withTag_tagInSource() { Object tag = new Object(); diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/metadata/flac/PictureFrame.java b/libraries/extractor/src/main/java/androidx/media3/extractor/metadata/flac/PictureFrame.java index 6189212518..36b6d4207c 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/metadata/flac/PictureFrame.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/metadata/flac/PictureFrame.java @@ -22,6 +22,7 @@ import android.os.Parcelable; import androidx.annotation.Nullable; import androidx.media3.common.MediaMetadata; import androidx.media3.common.Metadata; +import androidx.media3.common.MimeTypes; import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.UnstableApi; import com.google.common.base.Charsets; @@ -159,7 +160,8 @@ public final class PictureFrame implements Metadata.Entry { public static PictureFrame fromPictureBlock(ParsableByteArray pictureBlock) { int pictureType = pictureBlock.readInt(); int mimeTypeLength = pictureBlock.readInt(); - String mimeType = pictureBlock.readString(mimeTypeLength, Charsets.US_ASCII); + String mimeType = + MimeTypes.normalizeMimeType(pictureBlock.readString(mimeTypeLength, Charsets.US_ASCII)); int descriptionLength = pictureBlock.readInt(); String description = pictureBlock.readString(descriptionLength); int width = pictureBlock.readInt(); diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/metadata/id3/Id3Decoder.java b/libraries/extractor/src/main/java/androidx/media3/extractor/metadata/id3/Id3Decoder.java index 3358fe0daf..b05717e947 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/metadata/id3/Id3Decoder.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/metadata/id3/Id3Decoder.java @@ -18,6 +18,7 @@ package androidx.media3.extractor.metadata.id3; import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.Metadata; +import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Log; import androidx.media3.common.util.ParsableBitArray; import androidx.media3.common.util.ParsableByteArray; @@ -557,7 +558,8 @@ public final class Id3Decoder extends SimpleMetadataDecoder { id3Data.readBytes(data, 0, frameSize - 1); int mimeTypeEndIndex = indexOfZeroByte(data, 0); - String mimeType = new String(data, 0, mimeTypeEndIndex, Charsets.ISO_8859_1); + String mimeType = + MimeTypes.normalizeMimeType(new String(data, 0, mimeTypeEndIndex, Charsets.ISO_8859_1)); int filenameStartIndex = mimeTypeEndIndex + 1; int filenameEndIndex = indexOfTerminator(data, filenameStartIndex, encoding); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java index a91c5178b0..eae549e010 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java @@ -74,6 +74,7 @@ public final class TransformationRequest { */ @CanIgnoreReturnValue public Builder setVideoMimeType(@Nullable String videoMimeType) { + videoMimeType = MimeTypes.normalizeMimeType(videoMimeType); checkArgument( videoMimeType == null || MimeTypes.isVideo(videoMimeType), "Not a video MIME type: " + videoMimeType); @@ -100,6 +101,7 @@ public final class TransformationRequest { */ @CanIgnoreReturnValue public Builder setAudioMimeType(@Nullable String audioMimeType) { + audioMimeType = MimeTypes.normalizeMimeType(audioMimeType); checkArgument( audioMimeType == null || MimeTypes.isAudio(audioMimeType), "Not an audio MIME type: " + audioMimeType); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java index 65f1cbf635..df04e1f85d 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java @@ -174,6 +174,7 @@ public final class Transformer { */ @CanIgnoreReturnValue public Builder setAudioMimeType(String audioMimeType) { + audioMimeType = MimeTypes.normalizeMimeType(audioMimeType); checkArgument(MimeTypes.isAudio(audioMimeType), "Not an audio MIME type: " + audioMimeType); this.audioMimeType = audioMimeType; return this; @@ -205,6 +206,7 @@ public final class Transformer { */ @CanIgnoreReturnValue public Builder setVideoMimeType(String videoMimeType) { + videoMimeType = MimeTypes.normalizeMimeType(videoMimeType); checkArgument(MimeTypes.isVideo(videoMimeType), "Not a video MIME type: " + videoMimeType); this.videoMimeType = videoMimeType; return this;