public final class

DefaultExtractorsFactory

extends java.lang.Object

implements ExtractorsFactory

 java.lang.Object

↳androidx.media3.extractor.DefaultExtractorsFactory

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-extractor', version: '1.0.0-alpha03'

  • groupId: androidx.media3
  • artifactId: media3-extractor
  • version: 1.0.0-alpha03

Artifact androidx.media3:media3-extractor:1.0.0-alpha03 it located at Google repository (https://maven.google.com/)

Overview

An ExtractorsFactory that provides an array of extractors for the following formats:

Summary

Constructors
publicDefaultExtractorsFactory()

Methods
public synchronized ExtractorcreateExtractors()

public synchronized ExtractorcreateExtractors(Uri uri, java.util.Map<java.lang.String, java.util.List> responseHeaders)

public synchronized DefaultExtractorsFactorysetAdtsExtractorFlags(int flags)

Sets flags for AdtsExtractor instances created by the factory.

public synchronized DefaultExtractorsFactorysetAmrExtractorFlags(int flags)

Sets flags for AmrExtractor instances created by the factory.

public synchronized DefaultExtractorsFactorysetConstantBitrateSeekingAlwaysEnabled(boolean constantBitrateSeekingAlwaysEnabled)

Convenience method to set whether approximate seeking using constant bitrate assumptions should be enabled for all extractors that support it, and if it should be enabled even if the content length (and hence the duration of the media) is unknown.

public synchronized DefaultExtractorsFactorysetConstantBitrateSeekingEnabled(boolean constantBitrateSeekingEnabled)

Convenience method to set whether approximate seeking using constant bitrate assumptions should be enabled for all extractors that support it.

public synchronized DefaultExtractorsFactorysetFlacExtractorFlags(int flags)

Sets flags for FlacExtractor instances created by the factory.

public synchronized DefaultExtractorsFactorysetFragmentedMp4ExtractorFlags(int flags)

Sets flags for FragmentedMp4Extractor instances created by the factory.

public synchronized DefaultExtractorsFactorysetMatroskaExtractorFlags(int flags)

Sets flags for MatroskaExtractor instances created by the factory.

public synchronized DefaultExtractorsFactorysetMp3ExtractorFlags(int flags)

Sets flags for Mp3Extractor instances created by the factory.

public synchronized DefaultExtractorsFactorysetMp4ExtractorFlags(int flags)

Sets flags for Mp4Extractor instances created by the factory.

public synchronized DefaultExtractorsFactorysetTsExtractorFlags(int flags)

Sets flags for DefaultTsPayloadReaderFactorys used by TsExtractor instances created by the factory.

public synchronized DefaultExtractorsFactorysetTsExtractorMode(int mode)

Sets the mode for TsExtractor instances created by the factory.

public synchronized DefaultExtractorsFactorysetTsExtractorTimestampSearchBytes(int timestampSearchBytes)

Sets the number of bytes searched to find a timestamp for TsExtractor instances created by the factory.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public DefaultExtractorsFactory()

Methods

public synchronized DefaultExtractorsFactory setConstantBitrateSeekingEnabled(boolean constantBitrateSeekingEnabled)

Convenience method to set whether approximate seeking using constant bitrate assumptions should be enabled for all extractors that support it. If set to true, the flags required to enable this functionality will be OR'd with those passed to the setters when creating extractor instances. If set to false then the flags passed to the setters will be used without modification.

Parameters:

constantBitrateSeekingEnabled: Whether approximate seeking using a constant bitrate assumption should be enabled for all extractors that support it.

Returns:

The factory, for convenience.

public synchronized DefaultExtractorsFactory setConstantBitrateSeekingAlwaysEnabled(boolean constantBitrateSeekingAlwaysEnabled)

Convenience method to set whether approximate seeking using constant bitrate assumptions should be enabled for all extractors that support it, and if it should be enabled even if the content length (and hence the duration of the media) is unknown. If set to true, the flags required to enable this functionality will be OR'd with those passed to the setters when creating extractor instances. If set to false then the flags passed to the setters will be used without modification.

When seeking into content where the length is unknown, application code should ensure that requested seek positions are valid, or should be ready to handle playback failures reported through with PlaybackException.errorCode set to PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE.

Parameters:

constantBitrateSeekingAlwaysEnabled: Whether approximate seeking using a constant bitrate assumption should be enabled for all extractors that support it, including when the content duration is unknown.

Returns:

The factory, for convenience.

public synchronized DefaultExtractorsFactory setAdtsExtractorFlags(int flags)

Sets flags for AdtsExtractor instances created by the factory.

Parameters:

flags: The flags to use.

Returns:

The factory, for convenience.

See also: AdtsExtractor.AdtsExtractor(int)

public synchronized DefaultExtractorsFactory setAmrExtractorFlags(int flags)

Sets flags for AmrExtractor instances created by the factory.

Parameters:

flags: The flags to use.

Returns:

The factory, for convenience.

See also: AmrExtractor.AmrExtractor(int)

public synchronized DefaultExtractorsFactory setFlacExtractorFlags(int flags)

Sets flags for FlacExtractor instances created by the factory. The flags are also used by androidx.media3.decoder.flac.FlacExtractor instances if the FLAC extension is being used.

Parameters:

flags: The flags to use.

Returns:

The factory, for convenience.

See also: FlacExtractor.FlacExtractor(int)

public synchronized DefaultExtractorsFactory setMatroskaExtractorFlags(int flags)

Sets flags for MatroskaExtractor instances created by the factory.

Parameters:

flags: The flags to use.

Returns:

The factory, for convenience.

See also: MatroskaExtractor.MatroskaExtractor(int)

public synchronized DefaultExtractorsFactory setMp4ExtractorFlags(int flags)

Sets flags for Mp4Extractor instances created by the factory.

Parameters:

flags: The flags to use.

Returns:

The factory, for convenience.

See also: Mp4Extractor.Mp4Extractor(int)

public synchronized DefaultExtractorsFactory setFragmentedMp4ExtractorFlags(int flags)

Sets flags for FragmentedMp4Extractor instances created by the factory.

Parameters:

flags: The flags to use.

Returns:

The factory, for convenience.

See also: FragmentedMp4Extractor.FragmentedMp4Extractor(int)

public synchronized DefaultExtractorsFactory setMp3ExtractorFlags(int flags)

Sets flags for Mp3Extractor instances created by the factory.

Parameters:

flags: The flags to use.

Returns:

The factory, for convenience.

See also: Mp3Extractor.Mp3Extractor(int)

public synchronized DefaultExtractorsFactory setTsExtractorMode(int mode)

Sets the mode for TsExtractor instances created by the factory.

Parameters:

mode: The mode to use.

Returns:

The factory, for convenience.

See also: TsExtractor.TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory, int)

public synchronized DefaultExtractorsFactory setTsExtractorFlags(int flags)

Sets flags for DefaultTsPayloadReaderFactorys used by TsExtractor instances created by the factory.

Parameters:

flags: The flags to use.

Returns:

The factory, for convenience.

See also: TsExtractor.TsExtractor(int)

public synchronized DefaultExtractorsFactory setTsExtractorTimestampSearchBytes(int timestampSearchBytes)

Sets the number of bytes searched to find a timestamp for TsExtractor instances created by the factory.

Parameters:

timestampSearchBytes: The number of search bytes to use.

Returns:

The factory, for convenience.

See also: TsExtractor.TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory, int)

public synchronized Extractor createExtractors()

public synchronized Extractor createExtractors(Uri uri, java.util.Map<java.lang.String, java.util.List> responseHeaders)

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.media3.extractor;

import static androidx.media3.common.FileTypes.inferFileTypeFromResponseHeaders;
import static androidx.media3.common.FileTypes.inferFileTypeFromUri;

import android.net.Uri;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.media3.common.FileTypes;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.util.TimestampAdjuster;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.extractor.amr.AmrExtractor;
import androidx.media3.extractor.flac.FlacExtractor;
import androidx.media3.extractor.flv.FlvExtractor;
import androidx.media3.extractor.jpeg.JpegExtractor;
import androidx.media3.extractor.mkv.MatroskaExtractor;
import androidx.media3.extractor.mp3.Mp3Extractor;
import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
import androidx.media3.extractor.mp4.Mp4Extractor;
import androidx.media3.extractor.ogg.OggExtractor;
import androidx.media3.extractor.ts.Ac3Extractor;
import androidx.media3.extractor.ts.Ac4Extractor;
import androidx.media3.extractor.ts.AdtsExtractor;
import androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory;
import androidx.media3.extractor.ts.PsExtractor;
import androidx.media3.extractor.ts.TsExtractor;
import androidx.media3.extractor.ts.TsPayloadReader;
import androidx.media3.extractor.wav.WavExtractor;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * An {@link ExtractorsFactory} that provides an array of extractors for the following formats:
 *
 * <ul>
 *   <li>MP4, including M4A ({@link Mp4Extractor})
 *   <li>fMP4 ({@link FragmentedMp4Extractor})
 *   <li>Matroska and WebM ({@link MatroskaExtractor})
 *   <li>Ogg Vorbis/FLAC ({@link OggExtractor}
 *   <li>MP3 ({@link Mp3Extractor})
 *   <li>AAC ({@link AdtsExtractor})
 *   <li>MPEG TS ({@link TsExtractor})
 *   <li>MPEG PS ({@link PsExtractor})
 *   <li>FLV ({@link FlvExtractor})
 *   <li>WAV ({@link WavExtractor})
 *   <li>AC3 ({@link Ac3Extractor})
 *   <li>AC4 ({@link Ac4Extractor})
 *   <li>AMR ({@link AmrExtractor})
 *   <li>FLAC
 *       <ul>
 *         <li>If available, the FLAC extension's {@code androidx.media3.decoder.flac.FlacExtractor}
 *             is used.
 *         <li>Otherwise, the core {@link FlacExtractor} is used. Note that Android devices do not
 *             generally include a FLAC decoder before API 27. This can be worked around by using
 *             the FLAC extension or the FFmpeg extension.
 *       </ul>
 *   <li>JPEG ({@link JpegExtractor})
 * </ul>
 */
@UnstableApi
public final class DefaultExtractorsFactory implements ExtractorsFactory {

  // Extractors order is optimized according to
  // https://docs.google.com/document/d/1w2mKaWMxfz2Ei8-LdxqbPs1VLe_oudB-eryXXw9OvQQ.
  // The JPEG extractor appears after audio/video extractors because we expect audio/video input to
  // be more common.
  private static final int[] DEFAULT_EXTRACTOR_ORDER =
      new int[] {
        FileTypes.FLV,
        FileTypes.FLAC,
        FileTypes.WAV,
        FileTypes.MP4,
        FileTypes.AMR,
        FileTypes.PS,
        FileTypes.OGG,
        FileTypes.TS,
        FileTypes.MATROSKA,
        FileTypes.ADTS,
        FileTypes.AC3,
        FileTypes.AC4,
        FileTypes.MP3,
        FileTypes.JPEG,
      };

  private static final FlacExtensionLoader FLAC_EXTENSION_LOADER = new FlacExtensionLoader();

  private boolean constantBitrateSeekingEnabled;
  private boolean constantBitrateSeekingAlwaysEnabled;
  private @AdtsExtractor.Flags int adtsFlags;
  private @AmrExtractor.Flags int amrFlags;
  private @FlacExtractor.Flags int flacFlags;
  private @MatroskaExtractor.Flags int matroskaFlags;
  private @Mp4Extractor.Flags int mp4Flags;
  private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags;
  private @Mp3Extractor.Flags int mp3Flags;
  private @TsExtractor.Mode int tsMode;
  private @DefaultTsPayloadReaderFactory.Flags int tsFlags;
  private int tsTimestampSearchBytes;

  public DefaultExtractorsFactory() {
    tsMode = TsExtractor.MODE_SINGLE_PMT;
    tsTimestampSearchBytes = TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES;
  }

  /**
   * Convenience method to set whether approximate seeking using constant bitrate assumptions should
   * be enabled for all extractors that support it. If set to true, the flags required to enable
   * this functionality will be OR'd with those passed to the setters when creating extractor
   * instances. If set to false then the flags passed to the setters will be used without
   * modification.
   *
   * @param constantBitrateSeekingEnabled Whether approximate seeking using a constant bitrate
   *     assumption should be enabled for all extractors that support it.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setConstantBitrateSeekingEnabled(
      boolean constantBitrateSeekingEnabled) {
    this.constantBitrateSeekingEnabled = constantBitrateSeekingEnabled;
    return this;
  }

  /**
   * Convenience method to set whether approximate seeking using constant bitrate assumptions should
   * be enabled for all extractors that support it, and if it should be enabled even if the content
   * length (and hence the duration of the media) is unknown. If set to true, the flags required to
   * enable this functionality will be OR'd with those passed to the setters when creating extractor
   * instances. If set to false then the flags passed to the setters will be used without
   * modification.
   *
   * <p>When seeking into content where the length is unknown, application code should ensure that
   * requested seek positions are valid, or should be ready to handle playback failures reported
   * through {@link Player.Listener#onPlayerError} with {@link PlaybackException#errorCode} set to
   * {@link PlaybackException#ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE}.
   *
   * @param constantBitrateSeekingAlwaysEnabled Whether approximate seeking using a constant bitrate
   *     assumption should be enabled for all extractors that support it, including when the content
   *     duration is unknown.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setConstantBitrateSeekingAlwaysEnabled(
      boolean constantBitrateSeekingAlwaysEnabled) {
    this.constantBitrateSeekingAlwaysEnabled = constantBitrateSeekingAlwaysEnabled;
    return this;
  }

  /**
   * Sets flags for {@link AdtsExtractor} instances created by the factory.
   *
   * @see AdtsExtractor#AdtsExtractor(int)
   * @param flags The flags to use.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setAdtsExtractorFlags(
      @AdtsExtractor.Flags int flags) {
    this.adtsFlags = flags;
    return this;
  }

  /**
   * Sets flags for {@link AmrExtractor} instances created by the factory.
   *
   * @see AmrExtractor#AmrExtractor(int)
   * @param flags The flags to use.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setAmrExtractorFlags(@AmrExtractor.Flags int flags) {
    this.amrFlags = flags;
    return this;
  }

  /**
   * Sets flags for {@link FlacExtractor} instances created by the factory. The flags are also used
   * by {@code androidx.media3.decoder.flac.FlacExtractor} instances if the FLAC extension is being
   * used.
   *
   * @see FlacExtractor#FlacExtractor(int)
   * @param flags The flags to use.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setFlacExtractorFlags(
      @FlacExtractor.Flags int flags) {
    this.flacFlags = flags;
    return this;
  }

  /**
   * Sets flags for {@link MatroskaExtractor} instances created by the factory.
   *
   * @see MatroskaExtractor#MatroskaExtractor(int)
   * @param flags The flags to use.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setMatroskaExtractorFlags(
      @MatroskaExtractor.Flags int flags) {
    this.matroskaFlags = flags;
    return this;
  }

  /**
   * Sets flags for {@link Mp4Extractor} instances created by the factory.
   *
   * @see Mp4Extractor#Mp4Extractor(int)
   * @param flags The flags to use.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setMp4ExtractorFlags(@Mp4Extractor.Flags int flags) {
    this.mp4Flags = flags;
    return this;
  }

  /**
   * Sets flags for {@link FragmentedMp4Extractor} instances created by the factory.
   *
   * @see FragmentedMp4Extractor#FragmentedMp4Extractor(int)
   * @param flags The flags to use.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setFragmentedMp4ExtractorFlags(
      @FragmentedMp4Extractor.Flags int flags) {
    this.fragmentedMp4Flags = flags;
    return this;
  }

  /**
   * Sets flags for {@link Mp3Extractor} instances created by the factory.
   *
   * @see Mp3Extractor#Mp3Extractor(int)
   * @param flags The flags to use.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setMp3ExtractorFlags(@Mp3Extractor.Flags int flags) {
    mp3Flags = flags;
    return this;
  }

  /**
   * Sets the mode for {@link TsExtractor} instances created by the factory.
   *
   * @see TsExtractor#TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory, int)
   * @param mode The mode to use.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setTsExtractorMode(@TsExtractor.Mode int mode) {
    tsMode = mode;
    return this;
  }

  /**
   * Sets flags for {@link DefaultTsPayloadReaderFactory}s used by {@link TsExtractor} instances
   * created by the factory.
   *
   * @see TsExtractor#TsExtractor(int)
   * @param flags The flags to use.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setTsExtractorFlags(
      @DefaultTsPayloadReaderFactory.Flags int flags) {
    tsFlags = flags;
    return this;
  }

  /**
   * Sets the number of bytes searched to find a timestamp for {@link TsExtractor} instances created
   * by the factory.
   *
   * @see TsExtractor#TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory, int)
   * @param timestampSearchBytes The number of search bytes to use.
   * @return The factory, for convenience.
   */
  public synchronized DefaultExtractorsFactory setTsExtractorTimestampSearchBytes(
      int timestampSearchBytes) {
    tsTimestampSearchBytes = timestampSearchBytes;
    return this;
  }

  @Override
  public synchronized Extractor[] createExtractors() {
    return createExtractors(Uri.EMPTY, new HashMap<>());
  }

  @Override
  public synchronized Extractor[] createExtractors(
      Uri uri, Map<String, List<String>> responseHeaders) {
    List<Extractor> extractors = new ArrayList<>(/* initialCapacity= */ 14);

    @FileTypes.Type
    int responseHeadersInferredFileType = inferFileTypeFromResponseHeaders(responseHeaders);
    if (responseHeadersInferredFileType != FileTypes.UNKNOWN) {
      addExtractorsForFileType(responseHeadersInferredFileType, extractors);
    }

    @FileTypes.Type int uriInferredFileType = inferFileTypeFromUri(uri);
    if (uriInferredFileType != FileTypes.UNKNOWN
        && uriInferredFileType != responseHeadersInferredFileType) {
      addExtractorsForFileType(uriInferredFileType, extractors);
    }

    for (int fileType : DEFAULT_EXTRACTOR_ORDER) {
      if (fileType != responseHeadersInferredFileType && fileType != uriInferredFileType) {
        addExtractorsForFileType(fileType, extractors);
      }
    }

    return extractors.toArray(new Extractor[extractors.size()]);
  }

  private void addExtractorsForFileType(@FileTypes.Type int fileType, List<Extractor> extractors) {
    switch (fileType) {
      case FileTypes.AC3:
        extractors.add(new Ac3Extractor());
        break;
      case FileTypes.AC4:
        extractors.add(new Ac4Extractor());
        break;
      case FileTypes.ADTS:
        extractors.add(
            new AdtsExtractor(
                adtsFlags
                    | (constantBitrateSeekingEnabled
                        ? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
                        : 0)
                    | (constantBitrateSeekingAlwaysEnabled
                        ? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS
                        : 0)));
        break;
      case FileTypes.AMR:
        extractors.add(
            new AmrExtractor(
                amrFlags
                    | (constantBitrateSeekingEnabled
                        ? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
                        : 0)
                    | (constantBitrateSeekingAlwaysEnabled
                        ? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS
                        : 0)));
        break;
      case FileTypes.FLAC:
        @Nullable Extractor flacExtractor = FLAC_EXTENSION_LOADER.getExtractor(flacFlags);
        if (flacExtractor != null) {
          extractors.add(flacExtractor);
        } else {
          extractors.add(new FlacExtractor(flacFlags));
        }
        break;
      case FileTypes.FLV:
        extractors.add(new FlvExtractor());
        break;
      case FileTypes.MATROSKA:
        extractors.add(new MatroskaExtractor(matroskaFlags));
        break;
      case FileTypes.MP3:
        extractors.add(
            new Mp3Extractor(
                mp3Flags
                    | (constantBitrateSeekingEnabled
                        ? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
                        : 0)
                    | (constantBitrateSeekingAlwaysEnabled
                        ? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS
                        : 0)));
        break;
      case FileTypes.MP4:
        extractors.add(new FragmentedMp4Extractor(fragmentedMp4Flags));
        extractors.add(new Mp4Extractor(mp4Flags));
        break;
      case FileTypes.OGG:
        extractors.add(new OggExtractor());
        break;
      case FileTypes.PS:
        extractors.add(new PsExtractor());
        break;
      case FileTypes.TS:
        extractors.add(new TsExtractor(tsMode, tsFlags, tsTimestampSearchBytes));
        break;
      case FileTypes.WAV:
        extractors.add(new WavExtractor());
        break;
      case FileTypes.JPEG:
        extractors.add(new JpegExtractor());
        break;
      case FileTypes.WEBVTT:
      case FileTypes.UNKNOWN:
      default:
        break;
    }
  }

  private static final class FlacExtensionLoader {
    private final AtomicBoolean extensionLoaded;

    @GuardedBy("extensionLoaded")
    @Nullable
    private Constructor<? extends Extractor> extractorConstructor;

    public FlacExtensionLoader() {
      extensionLoaded = new AtomicBoolean(false);
    }

    @Nullable
    public Extractor getExtractor(int flags) {
      @Nullable
      Constructor<? extends Extractor> extractorConstructor = maybeLoadExtractorConstructor();
      if (extractorConstructor == null) {
        return null;
      }
      try {
        return extractorConstructor.newInstance(flags);
      } catch (Exception e) {
        throw new IllegalStateException("Unexpected error creating FLAC extractor", e);
      }
    }

    @Nullable
    private Constructor<? extends Extractor> maybeLoadExtractorConstructor() {
      synchronized (extensionLoaded) {
        if (extensionLoaded.get()) {
          return extractorConstructor;
        }
        try {
          @SuppressWarnings("nullness:argument")
          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) {
          // Expected if the app was built without the FLAC extension.
        } catch (Exception e) {
          // The FLAC extension is present, but instantiation failed.
          throw new RuntimeException("Error instantiating FLAC extension", e);
        }
        extensionLoaded.set(true);
        return extractorConstructor;
      }
    }
  }
}