Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,15 @@ public final class FileTypes {
* <li>{@link #BMP}
* <li>{@link #HEIF}
* <li>{@link #AVIF}
* <li>{@link #MJPEG}
* </ul>
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG,
MIDI, AVI, PNG, WEBP, BMP, HEIF, AVIF
MIDI, AVI, PNG, WEBP, BMP, HEIF, AVIF, MJPEG
})
public @interface Type {}

Expand Down Expand Up @@ -139,6 +140,9 @@ public final class FileTypes {
/** File type for the AVIF format. */
public static final int AVIF = 21;

/** File type for Motion JPEG streams. */
public static final int MJPEG = 22;

@VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type";

private static final String EXTENSION_AC3 = ".ac3";
Expand Down Expand Up @@ -173,6 +177,8 @@ public final class FileTypes {
private static final String EXTENSION_WEBVTT = ".webvtt";
private static final String EXTENSION_JPG = ".jpg";
private static final String EXTENSION_JPEG = ".jpeg";
private static final String EXTENSION_MJPG = ".mjpg";
private static final String EXTENSION_MJPEG = ".mjpeg";
private static final String EXTENSION_AVI = ".avi";
private static final String EXTENSION_PNG = ".png";
private static final String EXTENSION_WEBP = ".webp";
Expand All @@ -187,7 +193,14 @@ private FileTypes() {}
/** Returns the {@link Type} corresponding to the response headers provided. */
public static @FileTypes.Type int inferFileTypeFromResponseHeaders(
Map<String, List<String>> responseHeaders) {
@Nullable List<String> contentTypes = responseHeaders.get(HEADER_CONTENT_TYPE);
@Nullable List<String> contentTypes = null;
for (Map.Entry<String, List<String>> responseHeader : responseHeaders.entrySet()) {
if (responseHeader.getKey() != null
&& responseHeader.getKey().equalsIgnoreCase(HEADER_CONTENT_TYPE)) {
contentTypes = responseHeader.getValue();
break;
}
}
@Nullable
String mimeType = contentTypes == null || contentTypes.isEmpty() ? null : contentTypes.get(0);
return inferFileTypeFromMimeType(mimeType);
Expand All @@ -202,6 +215,10 @@ private FileTypes() {}
if (mimeType == null) {
return FileTypes.UNKNOWN;
}
int parametersStartIndex = mimeType.indexOf(';');
if (parametersStartIndex != -1) {
mimeType = mimeType.substring(0, parametersStartIndex).trim();
}
mimeType = normalizeMimeType(mimeType);
switch (mimeType) {
case MimeTypes.AUDIO_AC3:
Expand Down Expand Up @@ -257,6 +274,9 @@ private FileTypes() {}
return FileTypes.HEIF;
case MimeTypes.IMAGE_AVIF:
return FileTypes.AVIF;
case MimeTypes.MULTIPART_MJPEG:
case MimeTypes.VIDEO_MJPEG:
return FileTypes.MJPEG;
default:
return FileTypes.UNKNOWN;
}
Expand Down Expand Up @@ -320,6 +340,8 @@ private FileTypes() {}
return FileTypes.WAV;
} else if (filename.endsWith(EXTENSION_VTT) || filename.endsWith(EXTENSION_WEBVTT)) {
return FileTypes.WEBVTT;
} else if (filename.endsWith(EXTENSION_MJPG) || filename.endsWith(EXTENSION_MJPEG)) {
return FileTypes.MJPEG;
} else if (filename.endsWith(EXTENSION_JPG) || filename.endsWith(EXTENSION_JPEG)) {
return FileTypes.JPEG;
} else if (filename.endsWith(EXTENSION_AVI)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ public final class MimeTypes {
@UnstableApi public static final String IMAGE_WEBP = BASE_TYPE_IMAGE + "/webp";
@UnstableApi public static final String IMAGE_RAW = BASE_TYPE_IMAGE + "/raw";

// multipart/ MIME types

/** MIME type for multipart Motion JPEG streams. */
@UnstableApi public static final String MULTIPART_MJPEG = "multipart/x-mixed-replace";

/**
* A non-standard codec string for E-AC3-JOC. Use of this constant allows for disambiguation
* between regular E-AC3 ("ec-3") and E-AC3-JOC ("ec+3") streams from the codec string alone. The
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,28 @@ public void inferFileFormat_fromResponseHeaders_returnsExpectedFormat() {
.isEqualTo(FileTypes.MP4);
}

@Test
public void inferFileFormat_fromMjpegResponseHeaders_returnsMjpeg() {
Map<String, List<String>> responseHeaders = new HashMap<>();
responseHeaders.put(
"Content-type",
Collections.singletonList("multipart/x-mixed-replace; boundary=--myboundary"));

assertThat(FileTypes.inferFileTypeFromResponseHeaders(responseHeaders))
.isEqualTo(FileTypes.MJPEG);
}

@Test
public void inferFileFormat_fromMjpegResponseHeadersWithQuotedBoundary_returnsMjpeg() {
Map<String, List<String>> responseHeaders = new HashMap<>();
responseHeaders.put(
"Content-Type",
Collections.singletonList("multipart/x-mixed-replace; boundary=\"--totalmjpeg\""));

assertThat(FileTypes.inferFileTypeFromResponseHeaders(responseHeaders))
.isEqualTo(FileTypes.MJPEG);
}

@Test
public void inferFileFormat_fromResponseHeadersWithUnknownContentType_returnsUnknownFormat() {
Map<String, List<String>> responseHeaders = new HashMap<>();
Expand All @@ -62,6 +84,12 @@ public void inferFileFormat_fromMimeType_returnsExpectedFormat() {
assertThat(FileTypes.inferFileTypeFromMimeType("audio/x-flac")).isEqualTo(FileTypes.FLAC);
}

@Test
public void inferFileFormat_fromMjpegMimeType_returnsMjpeg() {
assertThat(FileTypes.inferFileTypeFromMimeType(MimeTypes.VIDEO_MJPEG))
.isEqualTo(FileTypes.MJPEG);
}

@Test
public void inferFileFormat_fromUnknownMimeType_returnsUnknownFormat() {
assertThat(inferFileTypeFromMimeType(/* mimeType= */ "unknown")).isEqualTo(FileTypes.UNKNOWN);
Expand All @@ -85,6 +113,12 @@ public void inferFileFormat_fromUriWithExtensionPrefix_returnsExpectedFormat() {
assertThat(inferFileTypeFromUri(Uri.parse("filename.mka"))).isEqualTo(FileTypes.MATROSKA);
}

@Test
public void inferFileFormat_fromMjpegUri_returnsMjpeg() {
assertThat(inferFileTypeFromUri(Uri.parse("filename.mjpeg"))).isEqualTo(FileTypes.MJPEG);
assertThat(inferFileTypeFromUri(Uri.parse("filename.mjpg"))).isEqualTo(FileTypes.MJPEG);
}

@Test
public void inferFileFormat_fromUriWithUnknownExtension_returnsUnknownFormat() {
assertThat(inferFileTypeFromUri(Uri.parse("filename.unknown"))).isEqualTo(FileTypes.UNKNOWN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import androidx.media3.extractor.flv.FlvExtractor;
import androidx.media3.extractor.heif.HeifExtractor;
import androidx.media3.extractor.jpeg.JpegExtractor;
import androidx.media3.extractor.jpeg.MjpegExtractor;
import androidx.media3.extractor.mkv.MatroskaExtractor;
import androidx.media3.extractor.mp3.Mp3Extractor;
import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
Expand Down Expand Up @@ -88,6 +89,7 @@
* generally include a FLAC decoder before API 27. This can be worked around by using
* the FLAC extension or the FFmpeg extension.
* </ul>
* <li>Motion JPEG ({@link MjpegExtractor})
* <li>JPEG ({@link JpegExtractor})
* <li>PNG ({@link PngExtractor})
* <li>WEBP ({@link WebpExtractor})
Expand Down Expand Up @@ -124,6 +126,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
// without further analysis.
FileTypes.AVI,
FileTypes.MIDI,
FileTypes.MJPEG,
FileTypes.JPEG,
FileTypes.PNG,
FileTypes.WEBP,
Expand Down Expand Up @@ -613,6 +616,9 @@ private void addExtractorsForFileType(@FileTypes.Type int fileType, List<Extract
case FileTypes.AVIF:
extractors.add(new AvifExtractor());
break;
case FileTypes.MJPEG:
extractors.add(new MjpegExtractor());
break;
case FileTypes.WEBVTT:
case FileTypes.UNKNOWN:
default:
Expand Down
Loading