mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
Use ProgressiveMediaSource and period for images
PiperOrigin-RevId: 555424664
This commit is contained in:
parent
fd5784455c
commit
d4b452d475
5 changed files with 99 additions and 23 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue