Adjust periodEnded logic further

Periods that are not the last in the manifest are always ended
and non-dynamic periods are also always ended by definition.

Also, some periods may not be able to declare their final duration
accurately and we need to account for some inaccuracy. This can be
done by testing whether no further full segment within the duration
can be expected.
This commit is contained in:
tonihei 2023-07-19 16:02:16 +01:00
parent 88e921ced1
commit 71591782f0
4 changed files with 149 additions and 3 deletions

View file

@ -410,7 +410,9 @@ public class DefaultDashChunkSource implements DashChunkSource {
}
long periodDurationUs = representationHolder.periodDurationUs;
boolean periodEnded = periodDurationUs != C.TIME_UNSET;
boolean isLastPeriodInDynamicManifest =
manifest.dynamic && periodIndex == manifest.getPeriodCount() - 1;
boolean periodEnded = !isLastPeriodInDynamicManifest || periodDurationUs != C.TIME_UNSET;
if (representationHolder.getSegmentCount() == 0) {
// The index doesn't define any segments.
@ -420,10 +422,15 @@ public class DefaultDashChunkSource implements DashChunkSource {
long firstAvailableSegmentNum = representationHolder.getFirstAvailableSegmentNum(nowUnixTimeUs);
long lastAvailableSegmentNum = representationHolder.getLastAvailableSegmentNum(nowUnixTimeUs);
if (manifest.dynamic) {
if (isLastPeriodInDynamicManifest) {
long lastAvailableSegmentEndTimeUs =
representationHolder.getSegmentEndTimeUs(lastAvailableSegmentNum);
periodEnded &= (lastAvailableSegmentEndTimeUs >= periodDurationUs);
long lastSegmentDurationUs =
lastAvailableSegmentEndTimeUs
- representationHolder.getSegmentStartTimeUs(lastAvailableSegmentNum);
// Account for some inaccuracy in the overall period duration value by assuming that the
// period is finished once no further full sample fits into the overall duration.
periodEnded &= (lastAvailableSegmentEndTimeUs + lastSegmentDurationUs >= periodDurationUs);
}
long segmentNum =
getSegmentNum(

View file

@ -38,6 +38,7 @@ import androidx.media3.exoplayer.source.MediaLoadData;
import androidx.media3.exoplayer.source.chunk.BundledChunkExtractor;
import androidx.media3.exoplayer.source.chunk.Chunk;
import androidx.media3.exoplayer.source.chunk.ChunkHolder;
import androidx.media3.exoplayer.source.chunk.MediaChunk;
import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection;
import androidx.media3.exoplayer.trackselection.FixedTrackSelection;
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
@ -414,6 +415,104 @@ public class DefaultDashChunkSourceTest {
"key4=5.0");
}
@Test
public void
getNextChunk_afterLastAvailableButBeforeEndOfLiveManifestWithKnownDuration_doesNotReturnEndOfStream()
throws Exception {
DashManifest manifest =
new DashManifestParser()
.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(),
"media/mpd/sample_mpd_live_known_duration_not_ended"));
DefaultDashChunkSource chunkSource =
new DefaultDashChunkSource(
BundledChunkExtractor.FACTORY,
new LoaderErrorThrower.Placeholder(),
manifest,
new BaseUrlExclusionList(),
/* periodIndex= */ 0,
/* adaptationSetIndices= */ new int[] {0},
new FixedTrackSelection(new TrackGroup(new Format.Builder().build()), /* track= */ 0),
C.TRACK_TYPE_VIDEO,
new FakeDataSource(),
/* elapsedRealtimeOffsetMs= */ 0,
/* maxSegmentsPerLoad= */ 1,
/* enableEventMessageTrack= */ false,
/* closedCaptionFormats= */ ImmutableList.of(),
/* playerTrackEmsgHandler= */ null,
PlayerId.UNSET,
/* cmcdConfiguration= */ null);
ChunkHolder output = new ChunkHolder();
// Populate with last available media chunk
chunkSource.getNextChunk(
/* playbackPositionUs= */ 0,
/* loadPositionUs= */ 0,
/* queue= */ ImmutableList.of(),
output);
Chunk previousChunk = output.chunk;
output.clear();
// Request another chunk
chunkSource.getNextChunk(
/* playbackPositionUs= */ 0,
/* loadPositionUs= */ 4_000_000,
/* queue= */ ImmutableList.of((MediaChunk) previousChunk),
output);
assertThat(output.endOfStream).isFalse();
assertThat(output.chunk).isNull();
}
@Test
public void getNextChunk_atEndOfLiveManifestWithKnownDuration_returnsEndOfStream()
throws Exception {
DashManifest manifest =
new DashManifestParser()
.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(
ApplicationProvider.getApplicationContext(),
"media/mpd/sample_mpd_live_known_duration_ended"));
DefaultDashChunkSource chunkSource =
new DefaultDashChunkSource(
BundledChunkExtractor.FACTORY,
new LoaderErrorThrower.Placeholder(),
manifest,
new BaseUrlExclusionList(),
/* periodIndex= */ 0,
/* adaptationSetIndices= */ new int[] {0},
new FixedTrackSelection(new TrackGroup(new Format.Builder().build()), /* track= */ 0),
C.TRACK_TYPE_VIDEO,
new FakeDataSource(),
/* elapsedRealtimeOffsetMs= */ 0,
/* maxSegmentsPerLoad= */ 1,
/* enableEventMessageTrack= */ false,
/* closedCaptionFormats= */ ImmutableList.of(),
/* playerTrackEmsgHandler= */ null,
PlayerId.UNSET,
/* cmcdConfiguration= */ null);
ChunkHolder output = new ChunkHolder();
// Populate with last media chunk
chunkSource.getNextChunk(
/* playbackPositionUs= */ 0,
/* loadPositionUs= */ 4_000_000,
/* queue= */ ImmutableList.of(),
output);
Chunk previousChunk = output.chunk;
output.clear();
// Request next chunk
chunkSource.getNextChunk(
/* playbackPositionUs= */ 0,
/* loadPositionUs= */ 8_000_000,
/* queue= */ ImmutableList.of((MediaChunk) previousChunk),
output);
assertThat(output.endOfStream).isTrue();
}
private DashChunkSource createDashChunkSource(
int numberOfTracks, @Nullable CmcdConfiguration cmcdConfiguration) throws IOException {
Assertions.checkArgument(numberOfTracks < 6);

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD
type="dynamic"
timeShiftBufferDepth="PT16S"
minimumUpdatePeriod="PT4M"
availabilityStartTime="2024-01-01T00:01:00Z">
<UTCTiming
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
value="2024-01-01T00:01:00Z" />
<Period id="1" start="PT0S" duration="PT10S">
<AdaptationSet id="0" contentType="video">
<SegmentTemplate presentationTimeOffset="0" timescale="1000" startNumber="1" media="video_$Time$.m4s">
<SegmentTimeline>
<S t="0" d="4000" r="1"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="0"/>
</AdaptationSet>
</Period>
</MPD>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD
type="dynamic"
timeShiftBufferDepth="PT16S"
minimumUpdatePeriod="PT4M"
availabilityStartTime="2024-01-01T00:01:00Z">
<UTCTiming
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
value="2024-01-01T00:01:00Z" />
<Period id="1" start="PT0S" duration="PT10S">
<AdaptationSet id="0" contentType="video">
<SegmentTemplate presentationTimeOffset="0" timescale="1000" startNumber="1" media="video_$Time$.m4s">
<SegmentTimeline>
<S t="0" d="4000"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="0"/>
</AdaptationSet>
</Period>
</MPD>