Normalize MIME types when accepting user or media input
MIME types are case-insensitive, but none of the many existing comparisons across our code base take this into account. The code can be made more robust by normalizing all MIME types at the moment they are first set into a class/builder and adding toLowerCase as part of the normalization. Most concretely, this fixes an issue with playing HLS streams via the IMA SDK where the stream MIME type is indicated with all lower case "application/x-mpegurl", which failed the MIME type comparison in DefaultMediaSourceFactory. PiperOrigin-RevId: 582317261
This commit is contained in:
parent
8d83d491f1
commit
b570c72588
@ -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,
|
||||
|
@ -296,7 +296,7 @@ public final class DrmInitData implements Comparator<SchemeData>, 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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user