diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java
index 096f3d2269..a2e967503b 100644
--- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java
+++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java
@@ -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,6 +422,16 @@ public class DefaultDashChunkSource implements DashChunkSource {
long firstAvailableSegmentNum = representationHolder.getFirstAvailableSegmentNum(nowUnixTimeUs);
long lastAvailableSegmentNum = representationHolder.getLastAvailableSegmentNum(nowUnixTimeUs);
+ if (isLastPeriodInDynamicManifest) {
+ long lastAvailableSegmentEndTimeUs =
+ representationHolder.getSegmentEndTimeUs(lastAvailableSegmentNum);
+ 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(
representationHolder,
diff --git a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DefaultDashChunkSourceTest.java b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DefaultDashChunkSourceTest.java
index c19432cd38..c6387ed254 100644
--- a/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DefaultDashChunkSourceTest.java
+++ b/libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DefaultDashChunkSourceTest.java
@@ -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);
diff --git a/libraries/test_data/src/test/assets/media/mpd/sample_mpd_live_known_duration_ended b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_live_known_duration_ended
new file mode 100644
index 0000000000..cad0e8da01
--- /dev/null
+++ b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_live_known_duration_ended
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/test_data/src/test/assets/media/mpd/sample_mpd_live_known_duration_not_ended b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_live_known_duration_not_ended
new file mode 100644
index 0000000000..ddf8882e87
--- /dev/null
+++ b/libraries/test_data/src/test/assets/media/mpd/sample_mpd_live_known_duration_not_ended
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+