Refactor MIDI and Flac extractor loaders for deduplication

Add MIDI filetype information for use in the ExtractorsFactory

PiperOrigin-RevId: 447976272
This commit is contained in:
hmzh 2022-05-11 14:12:13 +01:00 committed by Ian Baker
parent 1d89c35f7b
commit efb49f285e
2 changed files with 72 additions and 23 deletions

View File

@ -37,13 +37,14 @@ public final class FileTypes {
/** /**
* File types. One of {@link #UNKNOWN}, {@link #AC3}, {@link #AC4}, {@link #ADTS}, {@link #AMR}, * File types. One of {@link #UNKNOWN}, {@link #AC3}, {@link #AC4}, {@link #ADTS}, {@link #AMR},
* {@link #FLAC}, {@link #FLV}, {@link #MATROSKA}, {@link #MP3}, {@link #MP4}, {@link #OGG}, * {@link #FLAC}, {@link #FLV}, {@link #MATROSKA}, {@link #MP3}, {@link #MP4}, {@link #OGG},
* {@link #PS}, {@link #TS}, {@link #WAV}, {@link #WEBVTT} and {@link #JPEG}. * {@link #PS}, {@link #TS}, {@link #WAV}, {@link #WEBVTT}, {@link #JPEG} and {@link #MIDI}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({ @IntDef({
UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG,
MIDI
}) })
public @interface Type {} public @interface Type {}
/** Unknown file type. */ /** Unknown file type. */
@ -78,6 +79,8 @@ public final class FileTypes {
public static final int WEBVTT = 13; public static final int WEBVTT = 13;
/** File type for the JPEG format. */ /** File type for the JPEG format. */
public static final int JPEG = 14; public static final int JPEG = 14;
/** File type for the MIDI format. */
public static final int MIDI = 15;
@VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type"; @VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type";
@ -89,6 +92,9 @@ public final class FileTypes {
private static final String EXTENSION_AMR = ".amr"; private static final String EXTENSION_AMR = ".amr";
private static final String EXTENSION_FLAC = ".flac"; private static final String EXTENSION_FLAC = ".flac";
private static final String EXTENSION_FLV = ".flv"; private static final String EXTENSION_FLV = ".flv";
private static final String EXTENSION_MID = ".mid";
private static final String EXTENSION_MIDI = ".midi";
private static final String EXTENSION_SMF = ".smf";
private static final String EXTENSION_PREFIX_MK = ".mk"; private static final String EXTENSION_PREFIX_MK = ".mk";
private static final String EXTENSION_WEBM = ".webm"; private static final String EXTENSION_WEBM = ".webm";
private static final String EXTENSION_PREFIX_OG = ".og"; private static final String EXTENSION_PREFIX_OG = ".og";
@ -147,6 +153,8 @@ public final class FileTypes {
return FileTypes.FLAC; return FileTypes.FLAC;
case MimeTypes.VIDEO_FLV: case MimeTypes.VIDEO_FLV:
return FileTypes.FLV; return FileTypes.FLV;
case MimeTypes.AUDIO_MIDI:
return FileTypes.MIDI;
case MimeTypes.VIDEO_MATROSKA: case MimeTypes.VIDEO_MATROSKA:
case MimeTypes.AUDIO_MATROSKA: case MimeTypes.AUDIO_MATROSKA:
case MimeTypes.VIDEO_WEBM: case MimeTypes.VIDEO_WEBM:
@ -193,6 +201,10 @@ public final class FileTypes {
return FileTypes.FLAC; return FileTypes.FLAC;
} else if (filename.endsWith(EXTENSION_FLV)) { } else if (filename.endsWith(EXTENSION_FLV)) {
return FileTypes.FLV; return FileTypes.FLV;
} else if (filename.endsWith(EXTENSION_MID)
|| filename.endsWith(EXTENSION_MIDI)
|| filename.endsWith(EXTENSION_SMF)) {
return FileTypes.MIDI;
} else if (filename.startsWith( } else if (filename.startsWith(
EXTENSION_PREFIX_MK, EXTENSION_PREFIX_MK,
/* toffset= */ filename.length() - (EXTENSION_PREFIX_MK.length() + 1)) /* toffset= */ filename.length() - (EXTENSION_PREFIX_MK.length() + 1))

View File

@ -44,6 +44,7 @@ import androidx.media3.extractor.ts.TsExtractor;
import androidx.media3.extractor.ts.TsPayloadReader; import androidx.media3.extractor.ts.TsPayloadReader;
import androidx.media3.extractor.wav.WavExtractor; import androidx.media3.extractor.wav.WavExtractor;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -76,6 +77,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
* the FLAC extension or the FFmpeg extension. * the FLAC extension or the FFmpeg extension.
* </ul> * </ul>
* <li>JPEG ({@link JpegExtractor}) * <li>JPEG ({@link JpegExtractor})
* <li>MIDI, if available, the MIDI extension's {@code androidx.media3.decoder.midi.MidiExtractor}
* is used.
* </ul> * </ul>
*/ */
@UnstableApi @UnstableApi
@ -101,9 +104,13 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
FileTypes.AC4, FileTypes.AC4,
FileTypes.MP3, FileTypes.MP3,
FileTypes.JPEG, FileTypes.JPEG,
FileTypes.MIDI,
}; };
private static final FlacExtensionLoader FLAC_EXTENSION_LOADER = new FlacExtensionLoader(); private static final ExtensionLoader FLAC_EXTENSION_LOADER =
new ExtensionLoader(DefaultExtractorsFactory::getFlacExtractorConstructor);
private static final ExtensionLoader MIDI_EXTENSION_LOADER =
new ExtensionLoader(DefaultExtractorsFactory::getMidiExtractorConstructor);
private boolean constantBitrateSeekingEnabled; private boolean constantBitrateSeekingEnabled;
private boolean constantBitrateSeekingAlwaysEnabled; private boolean constantBitrateSeekingAlwaysEnabled;
@ -399,6 +406,12 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
case FileTypes.JPEG: case FileTypes.JPEG:
extractors.add(new JpegExtractor()); extractors.add(new JpegExtractor());
break; break;
case FileTypes.MIDI:
@Nullable Extractor midiExtractor = MIDI_EXTENSION_LOADER.getExtractor();
if (midiExtractor != null) {
extractors.add(midiExtractor);
}
break;
case FileTypes.WEBVTT: case FileTypes.WEBVTT:
case FileTypes.UNKNOWN: case FileTypes.UNKNOWN:
default: default:
@ -406,28 +419,63 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
} }
} }
private static final class FlacExtensionLoader { private static Constructor<? extends Extractor> getMidiExtractorConstructor()
throws ClassNotFoundException, NoSuchMethodException {
return Class.forName("androidx.media3.decoder.midi.MidiExtractor")
.asSubclass(Extractor.class)
.getConstructor();
}
@Nullable
private static Constructor<? extends Extractor> getFlacExtractorConstructor()
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
IllegalAccessException {
@SuppressWarnings("nullness:argument")
boolean isFlacNativeLibraryAvailable =
Boolean.TRUE.equals(
Class.forName("androidx.media3.decoder.flac.FlacLibrary")
.getMethod("isAvailable")
.invoke(/* obj= */ null));
if (isFlacNativeLibraryAvailable) {
return Class.forName("androidx.media3.decoder.flac.FlacExtractor")
.asSubclass(Extractor.class)
.getConstructor(int.class);
}
return null;
}
private static final class ExtensionLoader {
public interface ConstructorSupplier {
@Nullable
Constructor<? extends Extractor> getConstructor()
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException,
ClassNotFoundException;
}
private final ConstructorSupplier constructorSupplier;
private final AtomicBoolean extensionLoaded; private final AtomicBoolean extensionLoaded;
@GuardedBy("extensionLoaded") @GuardedBy("extensionLoaded")
@Nullable @Nullable
private Constructor<? extends Extractor> extractorConstructor; private Constructor<? extends Extractor> extractorConstructor;
public FlacExtensionLoader() { public ExtensionLoader(ConstructorSupplier constructorSupplier) {
this.constructorSupplier = constructorSupplier;
extensionLoaded = new AtomicBoolean(false); extensionLoaded = new AtomicBoolean(false);
} }
@Nullable @Nullable
public Extractor getExtractor(int flags) { public Extractor getExtractor(Object... constructorParams) {
@Nullable @Nullable
Constructor<? extends Extractor> extractorConstructor = maybeLoadExtractorConstructor(); Constructor<? extends Extractor> extractorConstructor = maybeLoadExtractorConstructor();
if (extractorConstructor == null) { if (extractorConstructor == null) {
return null; return null;
} }
try { try {
return extractorConstructor.newInstance(flags); return extractorConstructor.newInstance(constructorParams);
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException("Unexpected error creating FLAC extractor", e); throw new IllegalStateException("Unexpected error creating extractor", e);
} }
} }
@ -438,23 +486,12 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
return extractorConstructor; return extractorConstructor;
} }
try { try {
@SuppressWarnings("nullness:argument") return constructorSupplier.getConstructor();
boolean isFlacNativeLibraryAvailable =
Boolean.TRUE.equals(
Class.forName("androidx.media3.decoder.flac.FlacLibrary")
.getMethod("isAvailable")
.invoke(/* obj= */ null));
if (isFlacNativeLibraryAvailable) {
extractorConstructor =
Class.forName("androidx.media3.decoder.flac.FlacExtractor")
.asSubclass(Extractor.class)
.getConstructor(int.class);
}
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// Expected if the app was built without the FLAC extension. // Expected if the app was built without the extension.
} catch (Exception e) { } catch (Exception e) {
// The FLAC extension is present, but instantiation failed. // The extension is present, but instantiation failed.
throw new RuntimeException("Error instantiating FLAC extension", e); throw new RuntimeException("Error instantiating extension", e);
} }
extensionLoaded.set(true); extensionLoaded.set(true);
return extractorConstructor; return extractorConstructor;