mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add ProgressiveMediaSource.Listener interface and onSeekMap event
This will allow the listeners who are interested in the `SeekMap` to get informed once the period has done the preparation. PiperOrigin-RevId: 723027718
This commit is contained in:
parent
decfb9b0a9
commit
3e56d2a6fb
@ -87,14 +87,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
interface Listener {
|
||||
|
||||
/**
|
||||
* Called when the duration, the ability to seek within the period, or the categorization as
|
||||
* live stream changes.
|
||||
* Called when the duration, the {@link SeekMap} of the period, or the categorization as live
|
||||
* stream changes.
|
||||
*
|
||||
* @param durationUs The duration of the period, or {@link C#TIME_UNSET}.
|
||||
* @param isSeekable Whether the period is seekable.
|
||||
* @param seekMap The {@link SeekMap}.
|
||||
* @param isLive Whether the period is live.
|
||||
*/
|
||||
void onSourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive);
|
||||
void onSourceInfoRefreshed(long durationUs, SeekMap seekMap, boolean isLive);
|
||||
}
|
||||
|
||||
private static final String TAG = "ProgressiveMediaPeriod";
|
||||
@ -637,14 +637,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
public void onLoadCompleted(
|
||||
ExtractingLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) {
|
||||
if (durationUs == C.TIME_UNSET && seekMap != null) {
|
||||
boolean isSeekable = seekMap.isSeekable();
|
||||
long largestQueuedTimestampUs =
|
||||
getLargestQueuedTimestampUs(/* includeDisabledTracks= */ true);
|
||||
durationUs =
|
||||
largestQueuedTimestampUs == Long.MIN_VALUE
|
||||
? 0
|
||||
: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
|
||||
listener.onSourceInfoRefreshed(durationUs, isSeekable, isLive);
|
||||
listener.onSourceInfoRefreshed(durationUs, seekMap, isLive);
|
||||
}
|
||||
StatsDataSource dataSource = loadable.dataSource;
|
||||
LoadEventInfo loadEventInfo =
|
||||
@ -829,7 +828,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
isLive = !isLengthKnown && seekMap.getDurationUs() == C.TIME_UNSET;
|
||||
dataType = isLive ? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE : C.DATA_TYPE_MEDIA;
|
||||
if (prepared) {
|
||||
listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable(), isLive);
|
||||
listener.onSourceInfoRefreshed(durationUs, seekMap, isLive);
|
||||
} else {
|
||||
maybeFinishPrepare();
|
||||
}
|
||||
@ -892,7 +891,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
};
|
||||
}
|
||||
listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable(), isLive);
|
||||
listener.onSourceInfoRefreshed(durationUs, seekMap, isLive);
|
||||
prepared = true;
|
||||
checkNotNull(callback).onPrepared(this);
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import androidx.media3.extractor.DefaultExtractorsFactory;
|
||||
import androidx.media3.extractor.Extractor;
|
||||
import androidx.media3.extractor.ExtractorOutput;
|
||||
import androidx.media3.extractor.ExtractorsFactory;
|
||||
import androidx.media3.extractor.SeekMap;
|
||||
import androidx.media3.extractor.TrackOutput;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
@ -62,6 +63,24 @@ import java.util.concurrent.Executor;
|
||||
public final class ProgressiveMediaSource extends BaseMediaSource
|
||||
implements ProgressiveMediaPeriod.Listener {
|
||||
|
||||
/**
|
||||
* A listener of {@linkplain ProgressiveMediaSource progressive media sources}, which will be
|
||||
* notified of source events.
|
||||
*/
|
||||
public interface Listener {
|
||||
|
||||
/**
|
||||
* Called when the {@link SeekMap} of the source has been extracted from the stream.
|
||||
*
|
||||
* <p>Called on the playback thread.
|
||||
*
|
||||
* @param source The {@link MediaSource} whose {@link SeekMap} has been extracted from the
|
||||
* stream.
|
||||
* @param seekMap The source's {@link SeekMap}.
|
||||
*/
|
||||
void onSeekMap(MediaSource source, SeekMap seekMap);
|
||||
}
|
||||
|
||||
/** Factory for {@link ProgressiveMediaSource}s. */
|
||||
@SuppressWarnings("deprecation") // Implement deprecated type for backwards compatibility.
|
||||
public static final class Factory implements MediaSourceFactory {
|
||||
@ -307,6 +326,8 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
||||
@GuardedBy("this")
|
||||
private MediaItem mediaItem;
|
||||
|
||||
@Nullable private Listener listener;
|
||||
|
||||
private ProgressiveMediaSource(
|
||||
MediaItem mediaItem,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
@ -399,12 +420,31 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
||||
drmSessionManager.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Listener}.
|
||||
*
|
||||
* <p>This method must be called on the playback thread.
|
||||
*/
|
||||
public void setListener(Listener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the {@link Listener}.
|
||||
*
|
||||
* <p>This method must be called on the playback thread.
|
||||
*/
|
||||
public void clearListener() {
|
||||
this.listener = null;
|
||||
}
|
||||
|
||||
// ProgressiveMediaPeriod.Listener implementation.
|
||||
|
||||
@Override
|
||||
public void onSourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) {
|
||||
public void onSourceInfoRefreshed(long durationUs, SeekMap seekMap, boolean isLive) {
|
||||
// If we already have the duration from a previous source info refresh, use it.
|
||||
durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs;
|
||||
boolean isSeekable = seekMap.isSeekable();
|
||||
if (!timelineIsPlaceholder
|
||||
&& timelineDurationUs == durationUs
|
||||
&& timelineIsSeekable == isSeekable
|
||||
@ -417,6 +457,9 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
||||
timelineIsLive = isLive;
|
||||
timelineIsPlaceholder = false;
|
||||
notifySourceInfoRefreshed();
|
||||
if (listener != null) {
|
||||
listener.onSeekMap(this, seekMap);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
@ -39,6 +39,7 @@ import androidx.media3.exoplayer.analytics.PlayerId;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.trackselection.FixedTrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
|
||||
import androidx.media3.extractor.SeekMap;
|
||||
import androidx.media3.test.utils.MediaSourceTestRunner;
|
||||
import androidx.media3.test.utils.TestUtil;
|
||||
import androidx.media3.test.utils.robolectric.RobolectricUtil;
|
||||
@ -151,7 +152,8 @@ public class ProgressiveMediaSourceTest {
|
||||
new ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||
.enableLazyLoadingWithSingleTrack(/* trackId= */ 42, format)
|
||||
.createMediaSource(MediaItem.fromUri(mediaUri));
|
||||
MediaSourceTestRunner mediaSourceTestRunner = new MediaSourceTestRunner(mediaSource);
|
||||
ProgressiveMediaSourceTestRunner mediaSourceTestRunner =
|
||||
new ProgressiveMediaSourceTestRunner(mediaSource);
|
||||
ConditionVariable loadCompleted = new ConditionVariable();
|
||||
mediaSourceTestRunner.runOnPlaybackThread(
|
||||
() ->
|
||||
@ -168,6 +170,9 @@ public class ProgressiveMediaSourceTest {
|
||||
}
|
||||
}));
|
||||
|
||||
AtomicReference<SeekMap> seekMapReference = new AtomicReference<>();
|
||||
ProgressiveMediaSource.Listener listener = (source, seekMap) -> seekMapReference.set(seekMap);
|
||||
mediaSourceTestRunner.setListener(listener);
|
||||
Timeline timeline = mediaSourceTestRunner.prepareSource();
|
||||
MediaPeriod mediaPeriod =
|
||||
mediaSourceTestRunner.createPeriod(
|
||||
@ -178,6 +183,7 @@ public class ProgressiveMediaSourceTest {
|
||||
|
||||
assertThat(preparedLatch.await(DEFAULT_TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(openedUris).isEmpty();
|
||||
assertThat(seekMapReference.get()).isNotNull();
|
||||
|
||||
ListenableFuture<Boolean> isLoading =
|
||||
mediaSourceTestRunner.asyncRunOnPlaybackThread(
|
||||
@ -205,6 +211,7 @@ public class ProgressiveMediaSourceTest {
|
||||
assertThat(openedUris).containsExactly(mediaUri);
|
||||
|
||||
mediaSourceTestRunner.releasePeriod(mediaPeriod);
|
||||
mediaSourceTestRunner.clearListener();
|
||||
mediaSourceTestRunner.releaseSource();
|
||||
mediaSourceTestRunner.release();
|
||||
}
|
||||
@ -279,4 +286,22 @@ public class ProgressiveMediaSourceTest {
|
||||
/* streamResetFlags= */ new boolean[] {false},
|
||||
/* positionUs= */ 0);
|
||||
}
|
||||
|
||||
private static final class ProgressiveMediaSourceTestRunner extends MediaSourceTestRunner {
|
||||
|
||||
private final ProgressiveMediaSource mediaSource;
|
||||
|
||||
public ProgressiveMediaSourceTestRunner(ProgressiveMediaSource mediaSource) {
|
||||
super(mediaSource);
|
||||
this.mediaSource = mediaSource;
|
||||
}
|
||||
|
||||
public void setListener(ProgressiveMediaSource.Listener listener) {
|
||||
runOnPlaybackThread(() -> mediaSource.setListener(listener));
|
||||
}
|
||||
|
||||
public void clearListener() {
|
||||
runOnPlaybackThread(mediaSource::clearListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -360,7 +360,7 @@ public class MediaSourceTestRunner {
|
||||
playbackThread.quit();
|
||||
}
|
||||
|
||||
private class MediaSourceListener implements MediaSourceCaller, MediaSourceEventListener {
|
||||
public class MediaSourceListener implements MediaSourceCaller, MediaSourceEventListener {
|
||||
|
||||
// MediaSourceCaller methods.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user