Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Seek at a chunk level when seeking to chunk start positions #1031

Merged
merged 2 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
* Cronet Extension:
* RTMP Extension:
* HLS Extension:
* Resolve seeks to beginning of a segment more efficiently
([#1031](https://github.com/androidx/media/pull/1031)).
* DASH Extension:
* Smooth Streaming Extension:
* RTSP Extension:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ public TrackGroup getTrackGroup() {
return trackGroup;
}

/** Returns whether the chunk source has independent segments. */
public boolean hasIndependentSegments() {
return independentSegments;
}

/**
* Sets the current track selection.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,21 @@ public boolean seekToUs(long positionUs, boolean forceReset) {
return true;
}

// Detect whether the seek is to the start of a chunk that's at least partially buffered.
@Nullable HlsMediaChunk seekToMediaChunk = null;
if (chunkSource.hasIndependentSegments()) {
for (int i = 0; i < mediaChunks.size(); i++) {
HlsMediaChunk mediaChunk = mediaChunks.get(i);
long mediaChunkStartTimeUs = mediaChunk.startTimeUs;
if (mediaChunkStartTimeUs == positionUs) {
seekToMediaChunk = mediaChunk;
break;
}
}
}

// If we're not forced to reset, try and seek within the buffer.
if (sampleQueuesBuilt && !forceReset && seekInsideBufferUs(positionUs)) {
if (sampleQueuesBuilt && !forceReset && seekInsideBufferUs(positionUs, seekToMediaChunk)) {
return false;
}

Expand Down Expand Up @@ -1470,13 +1483,20 @@ private boolean isPendingReset() {
* Attempts to seek to the specified position within the sample queues.
*
* @param positionUs The seek position in microseconds.
* @param chunk The chunk to seek to, or null to seek to the exact position. {@code positionUs} is
* ignored if this is non-null.
* @return Whether the in-buffer seek was successful.
*/
private boolean seekInsideBufferUs(long positionUs) {
private boolean seekInsideBufferUs(long positionUs, @Nullable HlsMediaChunk chunk) {
int sampleQueueCount = sampleQueues.length;
for (int i = 0; i < sampleQueueCount; i++) {
SampleQueue sampleQueue = sampleQueues[i];
boolean seekInsideQueue = sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */ false);
boolean seekInsideQueue;
if (chunk != null) {
seekInsideQueue = sampleQueue.seekTo(chunk.getFirstSampleIndex(i));
} else {
seekInsideQueue = sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */ false);
}
// If we have AV tracks then an in-queue 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import androidx.media3.common.Player;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.SeekParameters;
import androidx.media3.exoplayer.hls.HlsMediaSource;
import androidx.media3.test.utils.CapturingRenderersFactory;
import androidx.media3.test.utils.DumpFileAsserts;
Expand Down Expand Up @@ -153,4 +154,37 @@ public void cea608_parseDuringExtraction() throws Exception {
DumpFileAsserts.assertOutput(
applicationContext, playbackOutput, "playbackdumps/hls/cea608.dump");
}

@Test
public void multiSegment_withSeekToPrevSyncFrame_startsRenderingAtBeginningOfSegment()
throws Exception {
Context applicationContext = ApplicationProvider.getApplicationContext();
CapturingRenderersFactory capturingRenderersFactory =
new CapturingRenderersFactory(applicationContext);
ExoPlayer player =
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
.setMediaSourceFactory(
new HlsMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
.experimentalParseSubtitlesDuringExtraction(true))
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.build();
// Prepare media fully to ensure we have all the segment data available.
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
player.setMediaItem(MediaItem.fromUri("asset:///media/hls/multi-segment/playlist.m3u8"));
player.prepare();
TestPlayerRunHelper.runUntilIsLoading(player, true);
TestPlayerRunHelper.runUntilIsLoading(player, false);

// Seek to beginning of second segment (at 500ms according to playlist)
player.setSeekParameters(SeekParameters.PREVIOUS_SYNC);
player.seekTo(600);
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();

// Output only starts at 550ms (the first sample in the second segment)
DumpFileAsserts.assertOutput(
applicationContext, playbackOutput, "playbackdumps/hls/multi-segment-with-seek.dump");
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:1
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MAP:URI="init.mp4"
#EXTINF:0.500,
playlist0.m4s
#EXTINF:0.550,
playlist1.m4s
#EXT-X-ENDLIST
Binary file not shown.
Binary file not shown.
Loading