java.lang.Object
↳androidx.media3.exoplayer.source.mediaparser.OutputConsumerAdapterV30
Gradle dependencies
compile group: 'androidx.media3', name: 'media3-exoplayer', version: '1.0.0-alpha03'
- groupId: androidx.media3
- artifactId: media3-exoplayer
- version: 1.0.0-alpha03
Artifact androidx.media3:media3-exoplayer:1.0.0-alpha03 it located at Google repository (https://maven.google.com/)
Overview
implementation that redirects output to an ExtractorOutput.
Summary
Methods |
---|
public void | disableSeeking()
Overrides future received SeekMaps with non-seekable instances. |
public ChunkIndex | getChunkIndex()
Returns the most recently output ChunkIndex, or null if none has been output. |
public MediaParser.SeekMap | getDummySeekMap()
Returns a dummy , or null if not available. |
public Format | getSampleFormats()
Returns the last output format for each track, or null if not all the tracks have been
identified. |
public <any> | getSeekPoints(long seekTimeUs)
Returns the instances corresponding to the given timestamp. |
public void | onSampleCompleted(int trackIndex, long timeUs, int flags, int size, int offset, MediaCodec.CryptoInfo cryptoInfo)
|
public void | onSampleDataFound(int trackIndex, MediaParser.InputReader sampleData)
|
public void | onSeekMapFound(MediaParser.SeekMap seekMap)
|
public void | onTrackCountFound(int numberOfTracks)
|
public void | onTrackDataFound(int trackIndex, TrackData trackData)
|
public void | setExtractorOutput(ExtractorOutput extractorOutput)
Sets the ExtractorOutput to which MediaParser's output is directed. |
public void | setMuxedCaptionFormats(java.util.List<Format> muxedCaptionFormats)
Sets Format information associated to the caption tracks multiplexed in the media. |
public void | setSampleTimestampUpperLimitFilterUs(long sampleTimestampUpperLimitFilterUs)
Sets an upper limit for sample timestamp filtering. |
public void | setSelectedParserName(java.lang.String parserName)
Defines the container mime type to propagate through TrackOutput.format(Format). |
public void | setTimestampAdjuster(TimestampAdjuster timestampAdjuster)
Sets a TimestampAdjuster for adjusting the timestamps of the output samples. |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Constructors
public
OutputConsumerAdapterV30()
Equivalent to OutputConsumerAdapterV30(primaryTrackManifestFormat= null, primaryTrackType= C.TRACK_TYPE_NONE,
expectDummySeekMap= false)
public
OutputConsumerAdapterV30(
Format primaryTrackManifestFormat, int primaryTrackType, boolean expectDummySeekMap)
Creates a new instance.
Parameters:
primaryTrackManifestFormat: The manifest-obtained format of the primary track, or null if
not applicable.
primaryTrackType: The of the primary track. C.TRACK_TYPE_NONE if there is no primary track.
expectDummySeekMap: Whether the output consumer should expect an initial dummy seek map
which should be exposed through OutputConsumerAdapterV30.getDummySeekMap().
Methods
public void
setSampleTimestampUpperLimitFilterUs(long sampleTimestampUpperLimitFilterUs)
Sets an upper limit for sample timestamp filtering.
When set, samples with timestamps greater than sampleTimestampUpperLimitFilterUs
will be discarded.
Parameters:
sampleTimestampUpperLimitFilterUs: The maximum allowed sample timestamp, or C.TIME_UNSET to remove filtering.
Sets a TimestampAdjuster for adjusting the timestamps of the output samples.
Sets the ExtractorOutput to which MediaParser's
output is directed.
public void
setMuxedCaptionFormats(java.util.List<Format> muxedCaptionFormats)
Sets Format information associated to the caption tracks multiplexed in the media.
public void
disableSeeking()
Overrides future received SeekMaps with non-seekable instances.
public MediaParser.SeekMap
getDummySeekMap()
Returns a dummy , or null if not available.
the dummy returns a single whose
matches the requested timestamp, and is 0.
Returns the most recently output ChunkIndex, or null if none has been output.
public <any>
getSeekPoints(long seekTimeUs)
Returns the instances corresponding to the given timestamp.
Parameters:
seekTimeUs: The timestamp in microseconds to retrieve
instances for.
Returns:
The instances corresponding to the given timestamp.
public void
setSelectedParserName(java.lang.String parserName)
Defines the container mime type to propagate through TrackOutput.format(Format).
Parameters:
parserName: The name of the selected parser.
public
Format getSampleFormats()
Returns the last output format for each track, or null if not all the tracks have been
identified.
public void
onTrackCountFound(int numberOfTracks)
public void
onSeekMapFound(MediaParser.SeekMap seekMap)
public void
onTrackDataFound(int trackIndex, TrackData trackData)
public void
onSampleDataFound(int trackIndex, MediaParser.InputReader sampleData)
public void
onSampleCompleted(int trackIndex, long timeUs, int flags, int size, int offset, MediaCodec.CryptoInfo cryptoInfo)
Source
/*
* Copyright 2020 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.exoplayer.source.mediaparser;
import static android.media.MediaParser.PARSER_NAME_AC3;
import static android.media.MediaParser.PARSER_NAME_AC4;
import static android.media.MediaParser.PARSER_NAME_ADTS;
import static android.media.MediaParser.PARSER_NAME_AMR;
import static android.media.MediaParser.PARSER_NAME_FLAC;
import static android.media.MediaParser.PARSER_NAME_FLV;
import static android.media.MediaParser.PARSER_NAME_FMP4;
import static android.media.MediaParser.PARSER_NAME_MATROSKA;
import static android.media.MediaParser.PARSER_NAME_MP3;
import static android.media.MediaParser.PARSER_NAME_MP4;
import static android.media.MediaParser.PARSER_NAME_OGG;
import static android.media.MediaParser.PARSER_NAME_PS;
import static android.media.MediaParser.PARSER_NAME_TS;
import static android.media.MediaParser.PARSER_NAME_WAV;
import android.annotation.SuppressLint;
import android.media.DrmInitData.SchemeInitData;
import android.media.MediaCodec;
import android.media.MediaCodec.CryptoInfo;
import android.media.MediaFormat;
import android.media.MediaParser;
import android.media.MediaParser.TrackData;
import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.C;
import androidx.media3.common.C.SelectionFlags;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.DataReader;
import androidx.media3.common.DrmInitData;
import androidx.media3.common.DrmInitData.SchemeData;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.TimestampAdjuster;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.extractor.ChunkIndex;
import androidx.media3.extractor.DummyExtractorOutput;
import androidx.media3.extractor.ExtractorOutput;
import androidx.media3.extractor.SeekMap;
import androidx.media3.extractor.SeekPoint;
import androidx.media3.extractor.TrackOutput;
import androidx.media3.extractor.TrackOutput.CryptoData;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* {@link MediaParser.OutputConsumer} implementation that redirects output to an {@link
* ExtractorOutput}.
*/
@RequiresApi(30)
@SuppressLint("Override") // TODO: Remove once the SDK becomes stable.
@UnstableApi
public final class OutputConsumerAdapterV30 implements MediaParser.OutputConsumer {
private static final String TAG = "OConsumerAdapterV30";
private static final Pair<MediaParser.SeekPoint, MediaParser.SeekPoint> SEEK_POINT_PAIR_START =
Pair.create(MediaParser.SeekPoint.START, MediaParser.SeekPoint.START);
private static final String MEDIA_FORMAT_KEY_TRACK_TYPE = "track-type-string";
private static final String MEDIA_FORMAT_KEY_CHUNK_INDEX_SIZES = "chunk-index-int-sizes";
private static final String MEDIA_FORMAT_KEY_CHUNK_INDEX_OFFSETS = "chunk-index-long-offsets";
private static final String MEDIA_FORMAT_KEY_CHUNK_INDEX_DURATIONS =
"chunk-index-long-us-durations";
private static final String MEDIA_FORMAT_KEY_CHUNK_INDEX_TIMES = "chunk-index-long-us-times";
private static final Pattern REGEX_CRYPTO_INFO_PATTERN =
Pattern.compile("pattern \\(encrypt: (\\d+), skip: (\\d+)\\)");
private final ArrayList<@NullableType TrackOutput> trackOutputs;
private final ArrayList<@NullableType Format> trackFormats;
private final ArrayList<@NullableType CryptoInfo> lastReceivedCryptoInfos;
private final ArrayList<@NullableType CryptoData> lastOutputCryptoDatas;
private final DataReaderAdapter scratchDataReaderAdapter;
private final boolean expectDummySeekMap;
private final @C.TrackType int primaryTrackType;
@Nullable private final Format primaryTrackManifestFormat;
private ExtractorOutput extractorOutput;
@Nullable private MediaParser.SeekMap dummySeekMap;
@Nullable private MediaParser.SeekMap lastSeekMap;
@Nullable private String containerMimeType;
@Nullable private ChunkIndex lastChunkIndex;
@Nullable private TimestampAdjuster timestampAdjuster;
private List<Format> muxedCaptionFormats;
private int primaryTrackIndex;
private long sampleTimestampUpperLimitFilterUs;
private boolean tracksFoundCalled;
private boolean tracksEnded;
private boolean seekingDisabled;
/**
* Equivalent to {@link #OutputConsumerAdapterV30(Format, int, boolean)
* OutputConsumerAdapterV30(primaryTrackManifestFormat= null, primaryTrackType= C.TRACK_TYPE_NONE,
* expectDummySeekMap= false)}
*/
public OutputConsumerAdapterV30() {
this(
/* primaryTrackManifestFormat= */ null,
/* primaryTrackType= */ C.TRACK_TYPE_NONE,
/* expectDummySeekMap= */ false);
}
/**
* Creates a new instance.
*
* @param primaryTrackManifestFormat The manifest-obtained format of the primary track, or null if
* not applicable.
* @param primaryTrackType The {@link C.TrackType type} of the primary track. {@link
* C#TRACK_TYPE_NONE} if there is no primary track.
* @param expectDummySeekMap Whether the output consumer should expect an initial dummy seek map
* which should be exposed through {@link #getDummySeekMap()}.
*/
public OutputConsumerAdapterV30(
@Nullable Format primaryTrackManifestFormat,
@C.TrackType int primaryTrackType,
boolean expectDummySeekMap) {
this.expectDummySeekMap = expectDummySeekMap;
this.primaryTrackManifestFormat = primaryTrackManifestFormat;
this.primaryTrackType = primaryTrackType;
trackOutputs = new ArrayList<>();
trackFormats = new ArrayList<>();
lastReceivedCryptoInfos = new ArrayList<>();
lastOutputCryptoDatas = new ArrayList<>();
scratchDataReaderAdapter = new DataReaderAdapter();
extractorOutput = new DummyExtractorOutput();
sampleTimestampUpperLimitFilterUs = C.TIME_UNSET;
muxedCaptionFormats = ImmutableList.of();
}
/**
* Sets an upper limit for sample timestamp filtering.
*
* <p>When set, samples with timestamps greater than {@code sampleTimestampUpperLimitFilterUs}
* will be discarded.
*
* @param sampleTimestampUpperLimitFilterUs The maximum allowed sample timestamp, or {@link
* C#TIME_UNSET} to remove filtering.
*/
public void setSampleTimestampUpperLimitFilterUs(long sampleTimestampUpperLimitFilterUs) {
this.sampleTimestampUpperLimitFilterUs = sampleTimestampUpperLimitFilterUs;
}
/** Sets a {@link TimestampAdjuster} for adjusting the timestamps of the output samples. */
public void setTimestampAdjuster(TimestampAdjuster timestampAdjuster) {
this.timestampAdjuster = timestampAdjuster;
}
/**
* Sets the {@link ExtractorOutput} to which {@link MediaParser MediaParser's} output is directed.
*/
public void setExtractorOutput(ExtractorOutput extractorOutput) {
this.extractorOutput = extractorOutput;
}
/** Sets {@link Format} information associated to the caption tracks multiplexed in the media. */
public void setMuxedCaptionFormats(List<Format> muxedCaptionFormats) {
this.muxedCaptionFormats = muxedCaptionFormats;
}
/** Overrides future received {@link SeekMap SeekMaps} with non-seekable instances. */
public void disableSeeking() {
seekingDisabled = true;
}
/**
* Returns a dummy {@link MediaParser.SeekMap}, or null if not available.
*
* <p>the dummy {@link MediaParser.SeekMap} returns a single {@link MediaParser.SeekPoint} whose
* {@link MediaParser.SeekPoint#timeMicros} matches the requested timestamp, and {@link
* MediaParser.SeekPoint#position} is 0.
*/
@Nullable
public MediaParser.SeekMap getDummySeekMap() {
return dummySeekMap;
}
/** Returns the most recently output {@link ChunkIndex}, or null if none has been output. */
@Nullable
public ChunkIndex getChunkIndex() {
return lastChunkIndex;
}
/**
* Returns the {@link MediaParser.SeekPoint} instances corresponding to the given timestamp.
*
* @param seekTimeUs The timestamp in microseconds to retrieve {@link MediaParser.SeekPoint}
* instances for.
* @return The {@link MediaParser.SeekPoint} instances corresponding to the given timestamp.
*/
public Pair<MediaParser.SeekPoint, MediaParser.SeekPoint> getSeekPoints(long seekTimeUs) {
return lastSeekMap != null ? lastSeekMap.getSeekPoints(seekTimeUs) : SEEK_POINT_PAIR_START;
}
/**
* Defines the container mime type to propagate through {@link TrackOutput#format}.
*
* @param parserName The name of the selected parser.
*/
public void setSelectedParserName(String parserName) {
containerMimeType = getMimeType(parserName);
}
/**
* Returns the last output format for each track, or null if not all the tracks have been
* identified.
*/
@Nullable
public Format[] getSampleFormats() {
if (!tracksFoundCalled) {
return null;
}
Format[] sampleFormats = new Format[trackFormats.size()];
for (int i = 0; i < trackFormats.size(); i++) {
sampleFormats[i] = Assertions.checkNotNull(trackFormats.get(i));
}
return sampleFormats;
}
// MediaParser.OutputConsumer implementation.
@Override
public void onTrackCountFound(int numberOfTracks) {
tracksFoundCalled = true;
maybeEndTracks();
}
@Override
public void onSeekMapFound(MediaParser.SeekMap seekMap) {
if (expectDummySeekMap && dummySeekMap == null) {
// This is a dummy seek map.
dummySeekMap = seekMap;
} else {
lastSeekMap = seekMap;
long durationUs = seekMap.getDurationMicros();
extractorOutput.seekMap(
seekingDisabled
? new SeekMap.Unseekable(
durationUs != MediaParser.SeekMap.UNKNOWN_DURATION ? durationUs : C.TIME_UNSET)
: new SeekMapAdapter(seekMap));
}
}
@Override
public void onTrackDataFound(int trackIndex, TrackData trackData) {
if (maybeObtainChunkIndex(trackData.mediaFormat)) {
// The MediaFormat contains a chunk index. It does not contain anything else.
return;
}
ensureSpaceForTrackIndex(trackIndex);
@Nullable TrackOutput trackOutput = trackOutputs.get(trackIndex);
if (trackOutput == null) {
@Nullable
String trackTypeString = trackData.mediaFormat.getString(MEDIA_FORMAT_KEY_TRACK_TYPE);
int trackType =
toTrackTypeConstant(
trackTypeString != null
? trackTypeString
: trackData.mediaFormat.getString(MediaFormat.KEY_MIME));
if (trackType == primaryTrackType) {
primaryTrackIndex = trackIndex;
}
trackOutput = extractorOutput.track(trackIndex, trackType);
trackOutputs.set(trackIndex, trackOutput);
if (trackTypeString != null) {
// The MediaFormat includes the track type string, so it cannot include any other keys, as
// per the android.media.mediaparser.eagerlyExposeTrackType parameter documentation.
return;
}
}
Format format = toExoPlayerFormat(trackData);
trackOutput.format(
primaryTrackManifestFormat != null && trackIndex == primaryTrackIndex
? format.withManifestFormatInfo(primaryTrackManifestFormat)
: format);
trackFormats.set(trackIndex, format);
maybeEndTracks();
}
@Override
public void onSampleDataFound(int trackIndex, MediaParser.InputReader sampleData)
throws IOException {
ensureSpaceForTrackIndex(trackIndex);
scratchDataReaderAdapter.input = sampleData;
TrackOutput trackOutput = trackOutputs.get(trackIndex);
if (trackOutput == null) {
trackOutput = extractorOutput.track(trackIndex, C.TRACK_TYPE_UNKNOWN);
trackOutputs.set(trackIndex, trackOutput);
}
trackOutput.sampleData(
scratchDataReaderAdapter, (int) sampleData.getLength(), /* allowEndOfInput= */ true);
}
@Override
public void onSampleCompleted(
int trackIndex,
long timeUs,
int flags,
int size,
int offset,
@Nullable MediaCodec.CryptoInfo cryptoInfo) {
if (sampleTimestampUpperLimitFilterUs != C.TIME_UNSET
&& timeUs >= sampleTimestampUpperLimitFilterUs) {
// Ignore this sample.
return;
} else if (timestampAdjuster != null) {
timeUs = timestampAdjuster.adjustSampleTimestamp(timeUs);
}
Assertions.checkNotNull(trackOutputs.get(trackIndex))
.sampleMetadata(timeUs, flags, size, offset, toExoPlayerCryptoData(trackIndex, cryptoInfo));
}
// Private methods.
private boolean maybeObtainChunkIndex(MediaFormat mediaFormat) {
@Nullable
ByteBuffer chunkIndexSizesByteBuffer =
mediaFormat.getByteBuffer(MEDIA_FORMAT_KEY_CHUNK_INDEX_SIZES);
if (chunkIndexSizesByteBuffer == null) {
return false;
}
IntBuffer chunkIndexSizes = chunkIndexSizesByteBuffer.asIntBuffer();
LongBuffer chunkIndexOffsets =
Assertions.checkNotNull(mediaFormat.getByteBuffer(MEDIA_FORMAT_KEY_CHUNK_INDEX_OFFSETS))
.asLongBuffer();
LongBuffer chunkIndexDurationsUs =
Assertions.checkNotNull(mediaFormat.getByteBuffer(MEDIA_FORMAT_KEY_CHUNK_INDEX_DURATIONS))
.asLongBuffer();
LongBuffer chunkIndexTimesUs =
Assertions.checkNotNull(mediaFormat.getByteBuffer(MEDIA_FORMAT_KEY_CHUNK_INDEX_TIMES))
.asLongBuffer();
int[] sizes = new int[chunkIndexSizes.remaining()];
long[] offsets = new long[chunkIndexOffsets.remaining()];
long[] durationsUs = new long[chunkIndexDurationsUs.remaining()];
long[] timesUs = new long[chunkIndexTimesUs.remaining()];
chunkIndexSizes.get(sizes);
chunkIndexOffsets.get(offsets);
chunkIndexDurationsUs.get(durationsUs);
chunkIndexTimesUs.get(timesUs);
lastChunkIndex = new ChunkIndex(sizes, offsets, durationsUs, timesUs);
extractorOutput.seekMap(lastChunkIndex);
return true;
}
private void ensureSpaceForTrackIndex(int trackIndex) {
for (int i = trackOutputs.size(); i <= trackIndex; i++) {
trackOutputs.add(null);
trackFormats.add(null);
lastReceivedCryptoInfos.add(null);
lastOutputCryptoDatas.add(null);
}
}
@Nullable
private CryptoData toExoPlayerCryptoData(int trackIndex, @Nullable CryptoInfo cryptoInfo) {
if (cryptoInfo == null) {
return null;
}
@Nullable CryptoInfo lastReceivedCryptoInfo = lastReceivedCryptoInfos.get(trackIndex);
CryptoData cryptoDataToOutput;
// MediaParser keeps identity and value equality aligned for efficient comparison.
if (lastReceivedCryptoInfo == cryptoInfo) {
// They match, we can reuse the last one we created.
cryptoDataToOutput = Assertions.checkNotNull(lastOutputCryptoDatas.get(trackIndex));
} else {
// They don't match, we create a new CryptoData.
// TODO: Access pattern encryption info directly once the Android SDK makes it visible.
// See [Internal ref: b/154248283].
int encryptedBlocks;
int clearBlocks;
try {
Matcher matcher = REGEX_CRYPTO_INFO_PATTERN.matcher(cryptoInfo.toString());
matcher.find();
encryptedBlocks = Integer.parseInt(Util.castNonNull(matcher.group(1)));
clearBlocks = Integer.parseInt(Util.castNonNull(matcher.group(2)));
} catch (RuntimeException e) {
// Should never happen.
Log.e(TAG, "Unexpected error while parsing CryptoInfo: " + cryptoInfo, e);
// Assume no-pattern encryption.
encryptedBlocks = 0;
clearBlocks = 0;
}
cryptoDataToOutput =
new CryptoData(cryptoInfo.mode, cryptoInfo.key, encryptedBlocks, clearBlocks);
lastReceivedCryptoInfos.set(trackIndex, cryptoInfo);
lastOutputCryptoDatas.set(trackIndex, cryptoDataToOutput);
}
return cryptoDataToOutput;
}
private void maybeEndTracks() {
if (!tracksFoundCalled || tracksEnded) {
return;
}
int size = trackOutputs.size();
for (int i = 0; i < size; i++) {
if (trackOutputs.get(i) == null) {
return;
}
}
extractorOutput.endTracks();
tracksEnded = true;
}
private static @C.TrackType int toTrackTypeConstant(@Nullable String string) {
if (string == null) {
return C.TRACK_TYPE_UNKNOWN;
}
switch (string) {
case "audio":
return C.TRACK_TYPE_AUDIO;
case "video":
return C.TRACK_TYPE_VIDEO;
case "text":
return C.TRACK_TYPE_TEXT;
case "metadata":
return C.TRACK_TYPE_METADATA;
case "unknown":
return C.TRACK_TYPE_UNKNOWN;
default:
// Must be a MIME type.
return MimeTypes.getTrackType(string);
}
}
private Format toExoPlayerFormat(TrackData trackData) {
// TODO: Consider adding support for the following:
// format.id
// format.stereoMode
// format.projectionData
MediaFormat mediaFormat = trackData.mediaFormat;
@Nullable String mediaFormatMimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
int mediaFormatAccessibilityChannel =
mediaFormat.getInteger(
MediaFormat.KEY_CAPTION_SERVICE_NUMBER, /* defaultValue= */ Format.NO_VALUE);
Format.Builder formatBuilder =
new Format.Builder()
.setDrmInitData(
toExoPlayerDrmInitData(
mediaFormat.getString("crypto-mode-fourcc"), trackData.drmInitData))
.setContainerMimeType(containerMimeType)
.setPeakBitrate(
mediaFormat.getInteger(
MediaFormat.KEY_BIT_RATE, /* defaultValue= */ Format.NO_VALUE))
.setChannelCount(
mediaFormat.getInteger(
MediaFormat.KEY_CHANNEL_COUNT, /* defaultValue= */ Format.NO_VALUE))
.setColorInfo(getColorInfo(mediaFormat))
.setSampleMimeType(mediaFormatMimeType)
.setCodecs(mediaFormat.getString(MediaFormat.KEY_CODECS_STRING))
.setFrameRate(
mediaFormat.getFloat(
MediaFormat.KEY_FRAME_RATE, /* defaultValue= */ Format.NO_VALUE))
.setWidth(
mediaFormat.getInteger(MediaFormat.KEY_WIDTH, /* defaultValue= */ Format.NO_VALUE))
.setHeight(
mediaFormat.getInteger(MediaFormat.KEY_HEIGHT, /* defaultValue= */ Format.NO_VALUE))
.setInitializationData(getInitializationData(mediaFormat))
.setLanguage(mediaFormat.getString(MediaFormat.KEY_LANGUAGE))
.setMaxInputSize(
mediaFormat.getInteger(
MediaFormat.KEY_MAX_INPUT_SIZE, /* defaultValue= */ Format.NO_VALUE))
.setPcmEncoding(
mediaFormat.getInteger("exo-pcm-encoding", /* defaultValue= */ Format.NO_VALUE))
.setRotationDegrees(
mediaFormat.getInteger(MediaFormat.KEY_ROTATION, /* defaultValue= */ 0))
.setSampleRate(
mediaFormat.getInteger(
MediaFormat.KEY_SAMPLE_RATE, /* defaultValue= */ Format.NO_VALUE))
.setSelectionFlags(getSelectionFlags(mediaFormat))
.setEncoderDelay(
mediaFormat.getInteger(MediaFormat.KEY_ENCODER_DELAY, /* defaultValue= */ 0))
.setEncoderPadding(
mediaFormat.getInteger(MediaFormat.KEY_ENCODER_PADDING, /* defaultValue= */ 0))
.setPixelWidthHeightRatio(
mediaFormat.getFloat("pixel-width-height-ratio-float", /* defaultValue= */ 1f))
.setSubsampleOffsetUs(
mediaFormat.getLong(
"subsample-offset-us-long", /* defaultValue= */ Format.OFFSET_SAMPLE_RELATIVE))
.setAccessibilityChannel(mediaFormatAccessibilityChannel);
for (int i = 0; i < muxedCaptionFormats.size(); i++) {
Format muxedCaptionFormat = muxedCaptionFormats.get(i);
if (Util.areEqual(muxedCaptionFormat.sampleMimeType, mediaFormatMimeType)
&& muxedCaptionFormat.accessibilityChannel == mediaFormatAccessibilityChannel) {
// The track's format matches this muxedCaptionFormat, so we apply the manifest format
// information to the track.
formatBuilder
.setLanguage(muxedCaptionFormat.language)
.setRoleFlags(muxedCaptionFormat.roleFlags)
.setSelectionFlags(muxedCaptionFormat.selectionFlags)
.setLabel(muxedCaptionFormat.label)
.setMetadata(muxedCaptionFormat.metadata);
break;
}
}
return formatBuilder.build();
}
@Nullable
private static DrmInitData toExoPlayerDrmInitData(
@Nullable String schemeType, @Nullable android.media.DrmInitData drmInitData) {
if (drmInitData == null) {
return null;
}
SchemeData[] schemeDatas = new SchemeData[drmInitData.getSchemeInitDataCount()];
for (int i = 0; i < schemeDatas.length; i++) {
SchemeInitData schemeInitData = drmInitData.getSchemeInitDataAt(i);
schemeDatas[i] =
new SchemeData(schemeInitData.uuid, schemeInitData.mimeType, schemeInitData.data);
}
return new DrmInitData(schemeType, schemeDatas);
}
private static @SelectionFlags int getSelectionFlags(MediaFormat mediaFormat) {
int selectionFlags = 0;
selectionFlags |=
getFlag(
mediaFormat,
/* key= */ MediaFormat.KEY_IS_AUTOSELECT,
/* returnValueIfPresent= */ C.SELECTION_FLAG_AUTOSELECT);
selectionFlags |=
getFlag(
mediaFormat,
/* key= */ MediaFormat.KEY_IS_DEFAULT,
/* returnValueIfPresent= */ C.SELECTION_FLAG_DEFAULT);
selectionFlags |=
getFlag(
mediaFormat,
/* key= */ MediaFormat.KEY_IS_FORCED_SUBTITLE,
/* returnValueIfPresent= */ C.SELECTION_FLAG_FORCED);
return selectionFlags;
}
private static int getFlag(MediaFormat mediaFormat, String key, int returnValueIfPresent) {
return mediaFormat.getInteger(key, /* defaultValue= */ 0) != 0 ? returnValueIfPresent : 0;
}
private static List<byte[]> getInitializationData(MediaFormat mediaFormat) {
ArrayList<byte[]> initData = new ArrayList<>();
int i = 0;
while (true) {
@Nullable ByteBuffer byteBuffer = mediaFormat.getByteBuffer("csd-" + i++);
if (byteBuffer == null) {
break;
}
initData.add(getArray(byteBuffer));
}
return initData;
}
@Nullable
private static ColorInfo getColorInfo(MediaFormat mediaFormat) {
@Nullable
ByteBuffer hdrStaticInfoByteBuffer = mediaFormat.getByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO);
@Nullable
byte[] hdrStaticInfo =
hdrStaticInfoByteBuffer != null ? getArray(hdrStaticInfoByteBuffer) : null;
int colorTransfer =
mediaFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER, /* defaultValue= */ Format.NO_VALUE);
int colorRange =
mediaFormat.getInteger(MediaFormat.KEY_COLOR_RANGE, /* defaultValue= */ Format.NO_VALUE);
int colorStandard =
mediaFormat.getInteger(MediaFormat.KEY_COLOR_STANDARD, /* defaultValue= */ Format.NO_VALUE);
if (hdrStaticInfo != null
|| colorTransfer != Format.NO_VALUE
|| colorRange != Format.NO_VALUE
|| colorStandard != Format.NO_VALUE) {
return new ColorInfo(colorStandard, colorRange, colorTransfer, hdrStaticInfo);
}
return null;
}
private static byte[] getArray(ByteBuffer byteBuffer) {
byte[] array = new byte[byteBuffer.remaining()];
byteBuffer.get(array);
return array;
}
private static String getMimeType(String parserName) {
switch (parserName) {
case PARSER_NAME_MATROSKA:
return MimeTypes.VIDEO_WEBM;
case PARSER_NAME_FMP4:
case PARSER_NAME_MP4:
return MimeTypes.VIDEO_MP4;
case PARSER_NAME_MP3:
return MimeTypes.AUDIO_MPEG;
case PARSER_NAME_ADTS:
return MimeTypes.AUDIO_AAC;
case PARSER_NAME_AC3:
return MimeTypes.AUDIO_AC3;
case PARSER_NAME_TS:
return MimeTypes.VIDEO_MP2T;
case PARSER_NAME_FLV:
return MimeTypes.VIDEO_FLV;
case PARSER_NAME_OGG:
return MimeTypes.AUDIO_OGG;
case PARSER_NAME_PS:
return MimeTypes.VIDEO_PS;
case PARSER_NAME_WAV:
return MimeTypes.AUDIO_RAW;
case PARSER_NAME_AMR:
return MimeTypes.AUDIO_AMR;
case PARSER_NAME_AC4:
return MimeTypes.AUDIO_AC4;
case PARSER_NAME_FLAC:
return MimeTypes.AUDIO_FLAC;
default:
throw new IllegalArgumentException("Illegal parser name: " + parserName);
}
}
private static final class SeekMapAdapter implements SeekMap {
private final MediaParser.SeekMap adaptedSeekMap;
public SeekMapAdapter(MediaParser.SeekMap adaptedSeekMap) {
this.adaptedSeekMap = adaptedSeekMap;
}
@Override
public boolean isSeekable() {
return adaptedSeekMap.isSeekable();
}
@Override
public long getDurationUs() {
long durationMicros = adaptedSeekMap.getDurationMicros();
return durationMicros != MediaParser.SeekMap.UNKNOWN_DURATION ? durationMicros : C.TIME_UNSET;
}
@Override
@SuppressWarnings("ReferenceEquality")
public SeekPoints getSeekPoints(long timeUs) {
Pair<MediaParser.SeekPoint, MediaParser.SeekPoint> seekPoints =
adaptedSeekMap.getSeekPoints(timeUs);
SeekPoints exoPlayerSeekPoints;
if (seekPoints.first == seekPoints.second) {
exoPlayerSeekPoints = new SeekPoints(asExoPlayerSeekPoint(seekPoints.first));
} else {
exoPlayerSeekPoints =
new SeekPoints(
asExoPlayerSeekPoint(seekPoints.first), asExoPlayerSeekPoint(seekPoints.second));
}
return exoPlayerSeekPoints;
}
private static SeekPoint asExoPlayerSeekPoint(MediaParser.SeekPoint seekPoint) {
return new SeekPoint(seekPoint.timeMicros, seekPoint.position);
}
}
private static final class DataReaderAdapter implements DataReader {
@Nullable public MediaParser.InputReader input;
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
return Util.castNonNull(input).read(buffer, offset, length);
}
}
}