Fix bug where enabling CMCD for HLS live streams causes error
Determine `nextMediaSequence` and `nextPartIndex` based on the last `SegmentBaseHolder` instance, as it can update `mediaSequence` and `partIndex` depending on whether the HLS playlist has trailing parts or not. Issue: androidx/media#1395 PiperOrigin-RevId: 642961141
This commit is contained in:
parent
4f691a7c02
commit
d4802a429b
@ -133,6 +133,9 @@ This release includes the following changes since the
|
||||
schedule its work loop as renderers can make progress.
|
||||
* Use data class for `LoadControl` methods instead of individual
|
||||
parameters.
|
||||
* Fix bug where enabling CMCD for HLS live streams causes
|
||||
`ArrayIndexOutOfBoundsException`
|
||||
([#1395](https://github.com/androidx/media/issues/1395)).
|
||||
* Transformer:
|
||||
* Work around a decoder bug where the number of audio channels was capped
|
||||
at stereo when handling PCM input.
|
||||
|
@ -521,13 +521,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
? CmcdData.Factory.OBJECT_TYPE_MUXED_AUDIO_AND_VIDEO
|
||||
: CmcdData.Factory.getObjectType(trackSelection));
|
||||
|
||||
long nextChunkMediaSequence =
|
||||
partIndex == C.INDEX_UNSET
|
||||
? (chunkMediaSequence == C.INDEX_UNSET ? C.INDEX_UNSET : chunkMediaSequence + 1)
|
||||
: chunkMediaSequence;
|
||||
int nextPartIndex = partIndex == C.INDEX_UNSET ? C.INDEX_UNSET : partIndex + 1;
|
||||
long nextMediaSequence =
|
||||
segmentBaseHolder.partIndex == C.INDEX_UNSET
|
||||
? segmentBaseHolder.mediaSequence + 1
|
||||
: segmentBaseHolder.mediaSequence;
|
||||
int nextPartIndex =
|
||||
segmentBaseHolder.partIndex == C.INDEX_UNSET
|
||||
? C.INDEX_UNSET
|
||||
: segmentBaseHolder.partIndex + 1;
|
||||
SegmentBaseHolder nextSegmentBaseHolder =
|
||||
getNextSegmentHolder(playlist, nextChunkMediaSequence, nextPartIndex);
|
||||
getNextSegmentHolder(playlist, nextMediaSequence, nextPartIndex);
|
||||
if (nextSegmentBaseHolder != null) {
|
||||
Uri uri = UriUtil.resolveToUri(playlist.baseUri, segmentBaseHolder.segmentBase.url);
|
||||
Uri nextUri = UriUtil.resolveToUri(playlist.baseUri, nextSegmentBaseHolder.segmentBase.url);
|
||||
|
@ -58,6 +58,10 @@ public class HlsChunkSourceTest {
|
||||
private static final String PLAYLIST = "media/m3u8/media_playlist";
|
||||
private static final String PLAYLIST_INDEPENDENT_SEGMENTS =
|
||||
"media/m3u8/media_playlist_independent_segments";
|
||||
private static final String PLAYLIST_LIVE_LOW_LATENCY_SEGEMENTS_ONLY =
|
||||
"media/m3u8/live_low_latency_segments_only";
|
||||
private static final String PLAYLIST_LIVE_LOW_LATENCY_SEGEMENTS_AND_PARTS =
|
||||
"media/m3u8/live_low_latency_segments_and_parts";
|
||||
private static final String PLAYLIST_EMPTY = "media/m3u8/media_playlist_empty";
|
||||
private static final Uri PLAYLIST_URI = Uri.parse("http://example.com/");
|
||||
private static final long PLAYLIST_START_PERIOD_OFFSET_US = 8_000_000L;
|
||||
@ -305,6 +309,96 @@ public class HlsChunkSourceTest {
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders).doesNotContainKey("CMCD-Status");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNextChunk_forLivePlaylistWithSegmentsOnly_setsCorrectNextObjectRequest()
|
||||
throws IOException {
|
||||
// The live playlist contains 6 segments, each 4 seconds long. With a playlist start offset of 8
|
||||
// seconds, the total media time is 8 + 6*4 = 32 seconds.
|
||||
InputStream inputStream =
|
||||
TestUtil.getInputStream(
|
||||
ApplicationProvider.getApplicationContext(), PLAYLIST_LIVE_LOW_LATENCY_SEGEMENTS_ONLY);
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(PLAYLIST_URI, inputStream);
|
||||
when(mockPlaylistTracker.getPlaylistSnapshot(eq(PLAYLIST_URI), anyBoolean()))
|
||||
.thenReturn(playlist);
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory = CmcdConfiguration.Factory.DEFAULT;
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
HlsChunkSource testChunkSource = createHlsChunkSource(cmcdConfiguration);
|
||||
HlsChunkSource.HlsChunkHolder output = new HlsChunkSource.HlsChunkHolder();
|
||||
|
||||
// A request to fetch the chunk at 27 seconds should retrieve the second-to-last segment.
|
||||
testChunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(27_000_000).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 27_000_000,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
/* allowEndOfStream= */ true,
|
||||
output);
|
||||
|
||||
// The `nor` key should point to the last segment, which is `FileSequence15.ts`.
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsEntry("CMCD-Request", "bl=0,dl=0,nor=\"..%2FfileSequence15.ts\",nrr=\"0-\",su");
|
||||
|
||||
// A request to fetch the chunk at 31 seconds should retrieve the last segment.
|
||||
testChunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(31_000_000).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 31_000_000,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
/* allowEndOfStream= */ true,
|
||||
output);
|
||||
|
||||
// Since there are no next segments left, the `nor` key should be absent.
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsEntry("CMCD-Request", "bl=0,dl=0,su");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNextChunk_forLivePlaylistWithSegmentsAndParts_setsCorrectNextObjectRequest()
|
||||
throws IOException {
|
||||
// The live playlist contains 6 segments, each 4 seconds long, and two trailing parts of 1
|
||||
// second each. With a playlist start offset of 8 seconds, the total media time is 8 + 6*4 + 2*1
|
||||
// = 34 seconds.
|
||||
InputStream inputStream =
|
||||
TestUtil.getInputStream(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
PLAYLIST_LIVE_LOW_LATENCY_SEGEMENTS_AND_PARTS);
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(PLAYLIST_URI, inputStream);
|
||||
when(mockPlaylistTracker.getPlaylistSnapshot(eq(PLAYLIST_URI), anyBoolean()))
|
||||
.thenReturn(playlist);
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory = CmcdConfiguration.Factory.DEFAULT;
|
||||
MediaItem mediaItem = new MediaItem.Builder().setMediaId("mediaId").build();
|
||||
CmcdConfiguration cmcdConfiguration =
|
||||
cmcdConfigurationFactory.createCmcdConfiguration(mediaItem);
|
||||
HlsChunkSource testChunkSource = createHlsChunkSource(cmcdConfiguration);
|
||||
HlsChunkSource.HlsChunkHolder output = new HlsChunkSource.HlsChunkHolder();
|
||||
|
||||
// A request to fetch the chunk at 31 seconds should retrieve the last segment.
|
||||
testChunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(31_000_000).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 31_000_000,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
/* allowEndOfStream= */ true,
|
||||
output);
|
||||
|
||||
// The `nor` key should point to the first trailing part, which is `FileSequence16.0.ts`.
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsEntry("CMCD-Request", "bl=0,dl=0,nor=\"..%2FfileSequence16.0.ts\",nrr=\"0-\",su");
|
||||
|
||||
// A request to fetch the chunk at 34 seconds should retrieve the first trailing part.
|
||||
testChunkSource.getNextChunk(
|
||||
new LoadingInfo.Builder().setPlaybackPositionUs(34_000_000).setPlaybackSpeed(1.0f).build(),
|
||||
/* loadPositionUs= */ 34_000_000,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
/* allowEndOfStream= */ true,
|
||||
output);
|
||||
|
||||
// The `nor` key should point to the second trailing part, which is `FileSequence16.1.ts`.
|
||||
assertThat(output.chunk.dataSpec.httpRequestHeaders)
|
||||
.containsEntry("CMCD-Request", "bl=0,dl=0,nor=\"..%2FfileSequence16.1.ts\",nrr=\"0-\",su");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNextChunk_chunkSourceWithCustomCmcdConfiguration_setsCmcdHttpRequestHeaders() {
|
||||
CmcdConfiguration.Factory cmcdConfigurationFactory =
|
||||
|
Loading…
x
Reference in New Issue
Block a user