Commit 04c56c44 by aquilescanta Committed by Oliver Woodman

Publish components that depend on MediaParser

This change will be followed up by:
- Changes adding APIs to enable the use of MediaParser in each of the supported
  media sources.
- Changes removing TODOs related to the change of the stable SDK to API 30.

PiperOrigin-RevId: 339556777
parent d5170688
/*
* 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 com.google.android.exoplayer2.source;
import static com.google.android.exoplayer2.source.mediaparser.MediaParserUtil.PARAMETER_EAGERLY_EXPOSE_TRACK_TYPE;
import static com.google.android.exoplayer2.source.mediaparser.MediaParserUtil.PARAMETER_INCLUDE_SUPPLEMENTAL_DATA;
import static com.google.android.exoplayer2.source.mediaparser.MediaParserUtil.PARAMETER_IN_BAND_CRYPTO_INFO;
import android.annotation.SuppressLint;
import android.media.MediaParser;
import android.media.MediaParser.SeekPoint;
import android.net.Uri;
import android.util.Pair;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.source.mediaparser.InputReaderAdapterV30;
import com.google.android.exoplayer2.source.mediaparser.OutputConsumerAdapterV30;
import com.google.android.exoplayer2.upstream.DataReader;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/** {@link ProgressiveMediaExtractor} implemented on top of the platform's {@link MediaParser}. */
@RequiresApi(30)
/* package */ final class MediaParserExtractorAdapter implements ProgressiveMediaExtractor {
private final OutputConsumerAdapterV30 outputConsumerAdapter;
private final InputReaderAdapterV30 inputReaderAdapter;
private final MediaParser mediaParser;
private String parserName;
@SuppressLint("WrongConstant")
public MediaParserExtractorAdapter() {
// TODO: Add support for injecting the desired extractor list.
outputConsumerAdapter = new OutputConsumerAdapterV30();
inputReaderAdapter = new InputReaderAdapterV30();
mediaParser = MediaParser.create(outputConsumerAdapter);
mediaParser.setParameter(PARAMETER_EAGERLY_EXPOSE_TRACK_TYPE, true);
mediaParser.setParameter(PARAMETER_IN_BAND_CRYPTO_INFO, true);
mediaParser.setParameter(PARAMETER_INCLUDE_SUPPLEMENTAL_DATA, true);
parserName = MediaParser.PARSER_NAME_UNKNOWN;
}
@Override
public void init(
DataReader dataReader,
Uri uri,
Map<String, List<String>> responseHeaders,
long position,
long length,
ExtractorOutput output)
throws IOException {
outputConsumerAdapter.setExtractorOutput(output);
inputReaderAdapter.setDataReader(dataReader, length);
inputReaderAdapter.setCurrentPosition(position);
String currentParserName = mediaParser.getParserName();
if (MediaParser.PARSER_NAME_UNKNOWN.equals(currentParserName)) {
// We need to sniff.
mediaParser.advance(inputReaderAdapter);
parserName = mediaParser.getParserName();
outputConsumerAdapter.setSelectedParserName(parserName);
} else if (!currentParserName.equals(parserName)) {
// The parser was created by name.
parserName = mediaParser.getParserName();
outputConsumerAdapter.setSelectedParserName(parserName);
} else {
// The parser implementation has already been selected. Do nothing.
}
}
@Override
public void release() {
mediaParser.release();
}
@Override
public void disableSeekingOnMp3Streams() {
if (MediaParser.PARSER_NAME_MP3.equals(parserName)) {
outputConsumerAdapter.disableSeeking();
}
}
@Override
public long getCurrentInputPosition() {
return inputReaderAdapter.getPosition();
}
@Override
public void seek(long position, long seekTimeUs) {
inputReaderAdapter.setCurrentPosition(position);
Pair<SeekPoint, SeekPoint> seekPoints = outputConsumerAdapter.getSeekPoints(seekTimeUs);
mediaParser.seek(seekPoints.second.position == position ? seekPoints.second : seekPoints.first);
}
@Override
public int read(PositionHolder positionHolder) throws IOException {
boolean shouldContinue = mediaParser.advance(inputReaderAdapter);
positionHolder.position = inputReaderAdapter.getAndResetSeekPosition();
return !shouldContinue
? Extractor.RESULT_END_OF_INPUT
: positionHolder.position != C.POSITION_UNSET
? Extractor.RESULT_SEEK
: Extractor.RESULT_CONTINUE;
}
}
...@@ -188,9 +188,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -188,9 +188,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.customCacheKey = customCacheKey; this.customCacheKey = customCacheKey;
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes;
loader = new Loader("Loader:ProgressiveMediaPeriod"); loader = new Loader("Loader:ProgressiveMediaPeriod");
ProgressiveMediaExtractor progressiveMediaExtractor = this.progressiveMediaExtractor = new BundledExtractorsAdapter(extractorsFactory);
new BundledExtractorsAdapter(extractorsFactory);
this.progressiveMediaExtractor = progressiveMediaExtractor;
loadCondition = new ConditionVariable(); loadCondition = new ConditionVariable();
maybeFinishPrepareRunnable = this::maybeFinishPrepare; maybeFinishPrepareRunnable = this::maybeFinishPrepare;
onContinueLoadingRequestedRunnable = onContinueLoadingRequestedRunnable =
......
/*
* 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 com.google.android.exoplayer2.source.chunk;
import static com.google.android.exoplayer2.source.mediaparser.MediaParserUtil.PARAMETER_EAGERLY_EXPOSE_TRACK_TYPE;
import static com.google.android.exoplayer2.source.mediaparser.MediaParserUtil.PARAMETER_EXPOSE_CAPTION_FORMATS;
import static com.google.android.exoplayer2.source.mediaparser.MediaParserUtil.PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT;
import static com.google.android.exoplayer2.source.mediaparser.MediaParserUtil.PARAMETER_EXPOSE_DUMMY_SEEK_MAP;
import static com.google.android.exoplayer2.source.mediaparser.MediaParserUtil.PARAMETER_INCLUDE_SUPPLEMENTAL_DATA;
import static com.google.android.exoplayer2.source.mediaparser.MediaParserUtil.PARAMETER_IN_BAND_CRYPTO_INFO;
import static com.google.android.exoplayer2.source.mediaparser.MediaParserUtil.PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS;
import android.annotation.SuppressLint;
import android.media.MediaFormat;
import android.media.MediaParser;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ChunkIndex;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.source.mediaparser.InputReaderAdapterV30;
import com.google.android.exoplayer2.source.mediaparser.MediaParserUtil;
import com.google.android.exoplayer2.source.mediaparser.OutputConsumerAdapterV30;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/** {@link ChunkExtractor} implemented on top of the platform's {@link MediaParser}. */
@RequiresApi(30)
public final class MediaParserChunkExtractor implements ChunkExtractor {
private final OutputConsumerAdapterV30 outputConsumerAdapter;
private final InputReaderAdapterV30 inputReaderAdapter;
private final MediaParser mediaParser;
private final TrackOutputProviderAdapter trackOutputProviderAdapter;
private final DummyTrackOutput dummyTrackOutput;
private long pendingSeekUs;
@Nullable private TrackOutputProvider trackOutputProvider;
@Nullable private Format[] sampleFormats;
/**
* Creates a new instance.
*
* @param primaryTrackType The type of the primary track, or {@link C#TRACK_TYPE_NONE} if there is
* no primary track. Must be one of the {@link C C.TRACK_TYPE_*} constants.
* @param manifestFormat The chunks {@link Format} as obtained from the manifest.
* @param closedCaptionFormats A list containing the {@link Format Formats} of the closed-caption
* tracks in the chunks.
*/
@SuppressLint("WrongConstant")
public MediaParserChunkExtractor(
int primaryTrackType, Format manifestFormat, List<Format> closedCaptionFormats) {
outputConsumerAdapter =
new OutputConsumerAdapterV30(
manifestFormat, primaryTrackType, /* expectDummySeekMap= */ true);
inputReaderAdapter = new InputReaderAdapterV30();
String mimeType = Assertions.checkNotNull(manifestFormat.containerMimeType);
String parserName =
MimeTypes.isMatroska(mimeType)
? MediaParser.PARSER_NAME_MATROSKA
: MediaParser.PARSER_NAME_FMP4;
outputConsumerAdapter.setSelectedParserName(parserName);
mediaParser = MediaParser.createByName(parserName, outputConsumerAdapter);
mediaParser.setParameter(MediaParser.PARAMETER_MATROSKA_DISABLE_CUES_SEEKING, true);
mediaParser.setParameter(PARAMETER_IN_BAND_CRYPTO_INFO, true);
mediaParser.setParameter(PARAMETER_INCLUDE_SUPPLEMENTAL_DATA, true);
mediaParser.setParameter(PARAMETER_EAGERLY_EXPOSE_TRACK_TYPE, true);
mediaParser.setParameter(PARAMETER_EXPOSE_DUMMY_SEEK_MAP, true);
mediaParser.setParameter(PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT, true);
mediaParser.setParameter(PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS, true);
ArrayList<MediaFormat> closedCaptionMediaFormats = new ArrayList<>();
for (int i = 0; i < closedCaptionFormats.size(); i++) {
closedCaptionMediaFormats.add(
MediaParserUtil.toCaptionsMediaFormat(closedCaptionFormats.get(i)));
}
mediaParser.setParameter(PARAMETER_EXPOSE_CAPTION_FORMATS, closedCaptionMediaFormats);
outputConsumerAdapter.setMuxedCaptionFormats(closedCaptionFormats);
trackOutputProviderAdapter = new TrackOutputProviderAdapter();
dummyTrackOutput = new DummyTrackOutput();
pendingSeekUs = C.TIME_UNSET;
}
// ChunkExtractor implementation.
@Override
public void init(
@Nullable TrackOutputProvider trackOutputProvider, long startTimeUs, long endTimeUs) {
this.trackOutputProvider = trackOutputProvider;
outputConsumerAdapter.setSampleTimestampUpperLimitFilterUs(endTimeUs);
outputConsumerAdapter.setExtractorOutput(trackOutputProviderAdapter);
pendingSeekUs = startTimeUs;
}
@Override
public void release() {
mediaParser.release();
}
@Override
public boolean read(ExtractorInput extractorInput) throws IOException {
maybeExecutePendingSeek();
inputReaderAdapter.setDataReader(extractorInput, extractorInput.getLength());
return mediaParser.advance(inputReaderAdapter);
}
@Nullable
@Override
public ChunkIndex getChunkIndex() {
return outputConsumerAdapter.getChunkIndex();
}
@Nullable
@Override
public Format[] getSampleFormats() {
return sampleFormats;
}
// Internal methods.
private void maybeExecutePendingSeek() {
@Nullable MediaParser.SeekMap dummySeekMap = outputConsumerAdapter.getDummySeekMap();
if (pendingSeekUs != C.TIME_UNSET && dummySeekMap != null) {
mediaParser.seek(dummySeekMap.getSeekPoints(pendingSeekUs).first);
pendingSeekUs = C.TIME_UNSET;
}
}
// Internal classes.
private class TrackOutputProviderAdapter implements ExtractorOutput {
@Override
public TrackOutput track(int id, int type) {
return trackOutputProvider != null ? trackOutputProvider.track(id, type) : dummyTrackOutput;
}
@Override
public void endTracks() {
// Imitate BundledChunkExtractor behavior, which captures a sample format snapshot when
// endTracks is called.
sampleFormats = outputConsumerAdapter.getSampleFormats();
}
@Override
public void seekMap(SeekMap seekMap) {
// Do nothing.
}
}
}
/*
* 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 com.google.android.exoplayer2.source.mediaparser;
import android.annotation.SuppressLint;
import android.media.MediaParser;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataReader;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/** {@link MediaParser.SeekableInputReader} implementation wrapping a {@link DataReader}. */
@RequiresApi(30)
@SuppressLint("Override") // TODO: Remove once the SDK becomes stable.
public final class InputReaderAdapterV30 implements MediaParser.SeekableInputReader {
@Nullable private DataReader dataReader;
private long resourceLength;
private long currentPosition;
private long lastSeekPosition;
/**
* Sets the wrapped {@link DataReader}.
*
* @param dataReader The {@link DataReader} to wrap.
* @param length The length of the resource from which {@code dataReader} reads.
*/
public void setDataReader(DataReader dataReader, long length) {
this.dataReader = dataReader;
resourceLength = length;
lastSeekPosition = C.POSITION_UNSET;
}
/** Sets the absolute position in the resource from which the wrapped {@link DataReader} reads. */
public void setCurrentPosition(long position) {
currentPosition = position;
}
/**
* Returns the last value passed to {@link #seekToPosition(long)} and sets the stored value to
* {@link C#POSITION_UNSET}.
*/
public long getAndResetSeekPosition() {
long lastSeekPosition = this.lastSeekPosition;
this.lastSeekPosition = C.POSITION_UNSET;
return lastSeekPosition;
}
// SeekableInputReader implementation.
@Override
public void seekToPosition(long position) {
lastSeekPosition = position;
}
@Override
public int read(byte[] bytes, int offset, int readLength) throws IOException {
int bytesRead = Util.castNonNull(dataReader).read(bytes, offset, readLength);
currentPosition += bytesRead;
return bytesRead;
}
@Override
public long getPosition() {
return currentPosition;
}
@Override
public long getLength() {
return resourceLength;
}
}
/*
* 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 com.google.android.exoplayer2.source.mediaparser;
import android.media.MediaFormat;
import android.media.MediaParser;
import com.google.android.exoplayer2.Format;
/**
* Miscellaneous constants and utility methods related to the {@link MediaParser} integration.
*
* <p>For documentation on constants, please see the {@link MediaParser} documentation.
*/
public final class MediaParserUtil {
public static final String PARAMETER_IN_BAND_CRYPTO_INFO =
"android.media.mediaparser.inBandCryptoInfo";
public static final String PARAMETER_INCLUDE_SUPPLEMENTAL_DATA =
"android.media.mediaparser.includeSupplementalData";
public static final String PARAMETER_EAGERLY_EXPOSE_TRACK_TYPE =
"android.media.mediaparser.eagerlyExposeTrackType";
public static final String PARAMETER_EXPOSE_DUMMY_SEEK_MAP =
"android.media.mediaparser.exposeDummySeekMap";
public static final String PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT =
"android.media.mediaParser.exposeChunkIndexAsMediaFormat";
public static final String PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS =
"android.media.mediaParser.overrideInBandCaptionDeclarations";
public static final String PARAMETER_EXPOSE_CAPTION_FORMATS =
"android.media.mediaParser.exposeCaptionFormats";
public static final String PARAMETER_IGNORE_TIMESTAMP_OFFSET =
"android.media.mediaparser.ignoreTimestampOffset";
private MediaParserUtil() {}
/**
* Returns a {@link MediaFormat} with equivalent {@link MediaFormat#KEY_MIME} and {@link
* MediaFormat#KEY_CAPTION_SERVICE_NUMBER} to the given {@link Format}.
*/
public static MediaFormat toCaptionsMediaFormat(Format format) {
MediaFormat mediaFormat = new MediaFormat();
mediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
if (format.accessibilityChannel != Format.NO_VALUE) {
mediaFormat.setInteger(MediaFormat.KEY_CAPTION_SERVICE_NUMBER, format.accessibilityChannel);
}
return mediaFormat;
}
}
/*
* Copyright (C) 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.
*/
@NonNullApi
package com.google.android.exoplayer2.source.mediaparser;
import com.google.android.exoplayer2.util.NonNullApi;
...@@ -804,7 +804,6 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -804,7 +804,6 @@ public class DefaultDashChunkSource implements DashChunkSource {
List<Format> closedCaptionFormats, List<Format> closedCaptionFormats,
@Nullable TrackOutput playerEmsgTrackOutput) { @Nullable TrackOutput playerEmsgTrackOutput) {
String containerMimeType = representation.format.containerMimeType; String containerMimeType = representation.format.containerMimeType;
Extractor extractor; Extractor extractor;
if (MimeTypes.isText(containerMimeType)) { if (MimeTypes.isText(containerMimeType)) {
if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
......
...@@ -52,7 +52,6 @@ import com.google.android.exoplayer2.upstream.HttpDataSource; ...@@ -52,7 +52,6 @@ import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -546,5 +545,4 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -546,5 +545,4 @@ public final class HlsMediaSource extends BaseMediaSource
} }
refreshSourceInfo(timeline); refreshSourceInfo(timeline);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment