Skip to content

Commit

Permalink
Merge pull request #1031 from garethfenn:hlschunkseek
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 603008793
  • Loading branch information
Copybara-Service committed Jan 31, 2024
2 parents fd2ea22 + b768ac7 commit 45bd5c6
Show file tree
Hide file tree
Showing 9 changed files with 617 additions and 3 deletions.
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

0 comments on commit 45bd5c6

Please sign in to comment.