Use ProgressiveMediaSource and period for images

PiperOrigin-RevId: 555424664
This commit is contained in:
tofunmi 2023-08-10 09:49:17 +00:00 committed by Tianyi Feng
parent fd5784455c
commit d4b452d475
5 changed files with 99 additions and 23 deletions

View file

@ -55,6 +55,7 @@ import androidx.media3.exoplayer.upstream.Loader.LoadErrorAction;
import androidx.media3.exoplayer.upstream.Loader.Loadable;
import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.ExtractorOutput;
import androidx.media3.extractor.ForwardingSeekMap;
import androidx.media3.extractor.PositionHolder;
import androidx.media3.extractor.SeekMap;
import androidx.media3.extractor.SeekMap.SeekPoints;
@ -119,6 +120,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final Runnable maybeFinishPrepareRunnable;
private final Runnable onContinueLoadingRequestedRunnable;
private final Handler handler;
private final boolean isSingleSample;
@Nullable private Callback callback;
@Nullable private IcyHeaders icyHeaders;
@ -163,6 +165,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* indexing. May be null.
* @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between each
* invocation of {@link Callback#onContinueLoadingRequested(SequenceableLoader)}.
* @param singleSampleDurationUs The duration of media with a single sample in microseconds.
*/
// maybeFinishPrepare is not posted to the handler until initialization completes.
@SuppressWarnings({"nullness:argument", "nullness:methodref.receiver.bound"})
@ -177,7 +180,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Listener listener,
Allocator allocator,
@Nullable String customCacheKey,
int continueLoadingCheckIntervalBytes) {
int continueLoadingCheckIntervalBytes,
long singleSampleDurationUs) {
this.uri = uri;
this.dataSource = dataSource;
this.drmSessionManager = drmSessionManager;
@ -190,6 +194,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes;
loader = new Loader("ProgressiveMediaPeriod");
this.progressiveMediaExtractor = progressiveMediaExtractor;
this.durationUs = singleSampleDurationUs;
isSingleSample = singleSampleDurationUs != C.TIME_UNSET;
loadCondition = new ConditionVariable();
maybeFinishPrepareRunnable = this::maybeFinishPrepare;
onContinueLoadingRequestedRunnable =
@ -202,7 +208,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
sampleQueueTrackIds = new TrackId[0];
sampleQueues = new SampleQueue[0];
pendingResetPositionUs = C.TIME_UNSET;
durationUs = C.TIME_UNSET;
dataType = C.DATA_TYPE_MEDIA;
}
@ -272,8 +277,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
}
// We'll always need to seek if this is a first selection to a non-zero position, or if we're
// making a selection having previously disabled all tracks.
boolean seekRequired = seenFirstTrackSelection ? oldEnabledTrackCount == 0 : positionUs != 0;
// making a selection having previously disabled all tracks, except for when we have a single
// sample.
boolean seekRequired =
!isSingleSample && (seenFirstTrackSelection ? oldEnabledTrackCount == 0 : positionUs != 0);
// Select new tracks.
for (int i = 0; i < selections.length; i++) {
if (streams[i] == null && selections[i] != null) {
@ -327,6 +334,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public void discardBuffer(long positionUs, boolean toKeyframe) {
if (isSingleSample) {
// Optimize by not discarding buffers.
return;
}
assertPrepared();
if (isPendingReset()) {
return;
@ -734,7 +745,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private void setSeekMap(SeekMap seekMap) {
this.seekMap = icyHeaders == null ? seekMap : new Unseekable(/* durationUs= */ C.TIME_UNSET);
durationUs = seekMap.getDurationUs();
if (seekMap.getDurationUs() == C.TIME_UNSET && durationUs == C.TIME_UNSET) {
this.seekMap =
new ForwardingSeekMap(this.seekMap) {
@Override
public long getDurationUs() {
return durationUs;
}
};
}
durationUs = this.seekMap.getDurationUs();
isLive = !isLengthKnown && seekMap.getDurationUs() == C.TIME_UNSET;
dataType = isLive ? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE : C.DATA_TYPE_MEDIA;
listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable(), isLive);
@ -880,7 +900,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) {
SampleQueue sampleQueue = sampleQueues[i];
boolean seekInsideQueue = sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */ false);
boolean seekInsideQueue =
isSingleSample
? sampleQueue.seekTo(sampleQueue.getFirstIndex())
: sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */ false);
// If we have AV tracks then an in-buffer seek is successful if the seek into every AV queue
// is successful. We ignore whether seeks within non-AV queues are successful in this case, as
// they may be sparse or poorly interleaved. If we only have non-AV tracks then a seek is

View file

@ -233,7 +233,6 @@ public final class ProgressiveMediaSource extends BaseMediaSource
private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy;
private final int continueLoadingCheckIntervalBytes;
private boolean timelineIsPlaceholder;
private long timelineDurationUs;
private boolean timelineIsSeekable;
@ -271,6 +270,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
@Nullable MediaItem.LocalConfiguration newConfiguration = mediaItem.localConfiguration;
return newConfiguration != null
&& newConfiguration.uri.equals(existingConfiguration.uri)
&& newConfiguration.imageDurationMs == existingConfiguration.imageDurationMs
&& Util.areEqual(newConfiguration.customCacheKey, existingConfiguration.customCacheKey);
}
@ -311,7 +311,8 @@ public final class ProgressiveMediaSource extends BaseMediaSource
this,
allocator,
localConfiguration.customCacheKey,
continueLoadingCheckIntervalBytes);
continueLoadingCheckIntervalBytes,
Util.msToUs(localConfiguration.imageDurationMs));
}
@Override

View file

@ -28,7 +28,10 @@ import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.upstream.DefaultAllocator;
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.ExtractorsFactory;
import androidx.media3.extractor.mp4.Mp4Extractor;
import androidx.media3.extractor.png.PngExtractor;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.concurrent.TimeoutException;
@ -44,18 +47,27 @@ public final class ProgressiveMediaPeriodTest {
public void prepareUsingBundledExtractors_updatesSourceInfoBeforeOnPreparedCallback()
throws TimeoutException {
testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback(
new BundledExtractorsAdapter(Mp4Extractor.FACTORY));
new BundledExtractorsAdapter(Mp4Extractor.FACTORY), C.TIME_UNSET);
}
@Test
public void
prepareUsingBundledExtractor_withImageExtractor_updatesSourceInfoBeforeOnPreparedCallback()
throws TimeoutException {
ExtractorsFactory pngExtractorFactory = () -> new Extractor[] {new PngExtractor()};
testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback(
new BundledExtractorsAdapter(pngExtractorFactory), 5 * C.MICROS_PER_SECOND);
}
@Test
public void prepareUsingMediaParser_updatesSourceInfoBeforeOnPreparedCallback()
throws TimeoutException {
testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback(
new MediaParserExtractorAdapter(PlayerId.UNSET));
new MediaParserExtractorAdapter(PlayerId.UNSET), C.TIME_UNSET);
}
private static void testExtractorsUpdatesSourceInfoBeforeOnPreparedCallback(
ProgressiveMediaExtractor extractor) throws TimeoutException {
ProgressiveMediaExtractor extractor, long imageDurationUs) throws TimeoutException {
AtomicBoolean sourceInfoRefreshCalled = new AtomicBoolean(false);
ProgressiveMediaPeriod.Listener sourceInfoRefreshListener =
(durationUs, isSeekable, isLive) -> sourceInfoRefreshCalled.set(true);
@ -74,7 +86,8 @@ public final class ProgressiveMediaPeriodTest {
sourceInfoRefreshListener,
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
/* customCacheKey= */ null,
ProgressiveMediaSource.DEFAULT_LOADING_CHECK_INTERVAL_BYTES);
ProgressiveMediaSource.DEFAULT_LOADING_CHECK_INTERVAL_BYTES,
imageDurationUs);
AtomicBoolean prepareCallbackCalled = new AtomicBoolean(false);
AtomicBoolean sourceInfoRefreshCalledBeforeOnPrepared = new AtomicBoolean(false);

View file

@ -0,0 +1,48 @@
/*
* Copyright 2023 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 androidx.media3.common.util.UnstableApi;
/** A forwarding class for {@link SeekMap} */
@UnstableApi
public class ForwardingSeekMap implements SeekMap {
private final SeekMap seekMap;
/**
* Creates a instance.
*
* @param seekMap The original {@link SeekMap}.
*/
public ForwardingSeekMap(SeekMap seekMap) {
this.seekMap = seekMap;
}
@Override
public boolean isSeekable() {
return seekMap.isSeekable();
}
@Override
public long getDurationUs() {
return seekMap.getDurationUs();
}
@Override
public SeekPoints getSeekPoints(long timeUs) {
return seekMap.getSeekPoints(timeUs);
}
}

View file

@ -17,6 +17,7 @@ package androidx.media3.extractor.jpeg;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.extractor.ExtractorOutput;
import androidx.media3.extractor.ForwardingSeekMap;
import androidx.media3.extractor.SeekMap;
import androidx.media3.extractor.SeekPoint;
import androidx.media3.extractor.TrackOutput;
@ -54,17 +55,7 @@ public final class StartOffsetExtractorOutput implements ExtractorOutput {
@Override
public void seekMap(SeekMap seekMap) {
extractorOutput.seekMap(
new SeekMap() {
@Override
public boolean isSeekable() {
return seekMap.isSeekable();
}
@Override
public long getDurationUs() {
return seekMap.getDurationUs();
}
new ForwardingSeekMap(seekMap) {
@Override
public SeekPoints getSeekPoints(long timeUs) {
SeekPoints seekPoints = seekMap.getSeekPoints(timeUs);