Skip to content

Commit abfd494

Browse files
marcbaechingercopybara-github
authored andcommitted
Reset live configuration to media item instance when source is released
Issue: #2606 PiperOrigin-RevId: 786665512
1 parent efc0f25 commit abfd494

File tree

5 files changed

+162
-32
lines changed

5 files changed

+162
-32
lines changed

RELEASENOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@
117117
when there are no chunks available in the buffer
118118
[#2598](https://github.com/androidx/media/issues/2598).
119119
* DASH extension:
120+
* Reset `LiveConfiguration` to the value provided by the `MediaItem` of
121+
the `DashMediaSource` when released and when the media item is updated
122+
by the user ([#2606](https://github.com/androidx/media/issues/2606)).
120123
* Smooth Streaming extension:
121124
* RTSP extension:
122125
* Decoder extensions (FFmpeg, VP9, AV1, etc.):

libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,6 @@ public DashMediaSource createMediaSource(MediaItem mediaItem) {
459459

460460
private IOException manifestFatalError;
461461
private Handler handler;
462-
463-
private MediaItem.LiveConfiguration liveConfiguration;
464462
private Uri manifestUri;
465463
private Uri initialManifestUri;
466464
private DashManifest manifest;
@@ -477,6 +475,9 @@ public DashMediaSource createMediaSource(MediaItem mediaItem) {
477475
@GuardedBy("this")
478476
private MediaItem mediaItem;
479477

478+
@GuardedBy("this")
479+
private MediaItem.LiveConfiguration liveConfiguration;
480+
480481
private DashMediaSource(
481482
MediaItem mediaItem,
482483
@Nullable DashManifest manifest,
@@ -555,13 +556,14 @@ public boolean canUpdateMediaItem(MediaItem mediaItem) {
555556
return newConfiguration != null
556557
&& newConfiguration.uri.equals(existingConfiguration.uri)
557558
&& newConfiguration.streamKeys.equals(existingConfiguration.streamKeys)
558-
&& Objects.equals(newConfiguration.drmConfiguration, existingConfiguration.drmConfiguration)
559-
&& existingMediaItem.liveConfiguration.equals(mediaItem.liveConfiguration);
559+
&& Objects.equals(
560+
newConfiguration.drmConfiguration, existingConfiguration.drmConfiguration);
560561
}
561562

562563
@Override
563564
public synchronized void updateMediaItem(MediaItem mediaItem) {
564565
this.mediaItem = mediaItem;
566+
liveConfiguration = mediaItem.liveConfiguration;
565567
}
566568

567569
@Override
@@ -631,6 +633,7 @@ protected void releaseSourceInternal() {
631633
loader.release();
632634
loader = null;
633635
}
636+
setLiveConfiguration(getMediaItem().liveConfiguration);
634637
manifestLoadStartTimestampMs = 0;
635638
manifestLoadEndTimestampMs = 0;
636639
manifestUri = initialManifestUri;
@@ -971,7 +974,7 @@ private void processManifest(boolean scheduleRefresh) {
971974
updateLiveConfiguration(nowInWindowUs, windowDurationUs);
972975
windowStartUnixTimeMs =
973976
manifest.availabilityStartTimeMs + Util.usToMs(windowStartTimeInManifestUs);
974-
windowDefaultPositionUs = nowInWindowUs - Util.msToUs(liveConfiguration.targetOffsetMs);
977+
windowDefaultPositionUs = nowInWindowUs - Util.msToUs(getLiveConfiguration().targetOffsetMs);
975978
long minimumWindowDefaultPositionUs = min(minLiveStartPositionUs, windowDurationUs / 2);
976979
if (windowDefaultPositionUs < minimumWindowDefaultPositionUs) {
977980
// The default position is too close to the start of the live window. Set it to the minimum
@@ -992,7 +995,7 @@ private void processManifest(boolean scheduleRefresh) {
992995
windowDefaultPositionUs,
993996
manifest,
994997
getMediaItem(),
995-
manifest.dynamic ? liveConfiguration : null);
998+
manifest.dynamic ? getLiveConfiguration() : null);
996999
refreshSourceInfo(timeline);
9971000

9981001
if (!sideloadedManifest) {
@@ -1067,9 +1070,10 @@ private void updateLiveConfiguration(long nowInWindowUs, long windowDurationUs)
10671070
maxLiveOffsetMs = minLiveOffsetMs;
10681071
}
10691072
long targetOffsetMs;
1070-
if (liveConfiguration.targetOffsetMs != C.TIME_UNSET) {
1073+
MediaItem.LiveConfiguration localLiveConfiguration = getLiveConfiguration();
1074+
if (localLiveConfiguration.targetOffsetMs != C.TIME_UNSET) {
10711075
// Keep existing target offset even if the media configuration changes.
1072-
targetOffsetMs = liveConfiguration.targetOffsetMs;
1076+
targetOffsetMs = localLiveConfiguration.targetOffsetMs;
10731077
} else if (manifest.serviceDescription != null
10741078
&& manifest.serviceDescription.targetOffsetMs != C.TIME_UNSET) {
10751079
targetOffsetMs = manifest.serviceDescription.targetOffsetMs;
@@ -1111,14 +1115,14 @@ private void updateLiveConfiguration(long nowInWindowUs, long windowDurationUs)
11111115
minPlaybackSpeed = 1f;
11121116
maxPlaybackSpeed = 1f;
11131117
}
1114-
liveConfiguration =
1118+
setLiveConfiguration(
11151119
new MediaItem.LiveConfiguration.Builder()
11161120
.setTargetOffsetMs(targetOffsetMs)
11171121
.setMinOffsetMs(minLiveOffsetMs)
11181122
.setMaxOffsetMs(maxLiveOffsetMs)
11191123
.setMinPlaybackSpeed(minPlaybackSpeed)
11201124
.setMaxPlaybackSpeed(maxPlaybackSpeed)
1121-
.build();
1125+
.build());
11221126
}
11231127

11241128
private void scheduleManifestRefresh(long delayUntilNextLoadMs) {
@@ -1160,6 +1164,14 @@ private long getManifestLoadRetryDelayMillis() {
11601164
return min((staleManifestReloadAttempt - 1) * 1000, 5000);
11611165
}
11621166

1167+
private synchronized MediaItem.LiveConfiguration getLiveConfiguration() {
1168+
return liveConfiguration;
1169+
}
1170+
1171+
private synchronized void setLiveConfiguration(MediaItem.LiveConfiguration liveConfiguration) {
1172+
this.liveConfiguration = liveConfiguration;
1173+
}
1174+
11631175
private <T> void startLoading(
11641176
ParsingLoadable<T> loadable,
11651177
Loader.Callback<ParsingLoadable<T>> callback,

libraries/exoplayer_dash/src/test/java/androidx/media3/exoplayer/dash/DashMediaSourceTest.java

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
import com.google.common.collect.ImmutableList;
4141
import java.io.ByteArrayInputStream;
4242
import java.io.IOException;
43+
import java.util.ArrayList;
44+
import java.util.List;
45+
import java.util.concurrent.atomic.AtomicBoolean;
4346
import java.util.concurrent.atomic.AtomicReference;
4447
import org.junit.Test;
4548
import org.junit.runner.RunWith;
@@ -57,6 +60,10 @@ public final class DashMediaSourceTest {
5760
"media/mpd/sample_mpd_live_with_complete_service_description";
5861
private static final String SAMPLE_MPD_LIVE_WITH_OFFSET_INSIDE_WINDOW =
5962
"media/mpd/sample_mpd_live_with_offset_inside_window";
63+
private static final String SAMPLE_MPD_LIVE_WITH_TIME_SHIFT_BUFFER_16_SECS =
64+
"media/mpd/sample_mpd_live_with_time_shift_buffer_16_secs";
65+
private static final String SAMPLE_MPD_LIVE_WITH_TIME_SHIFT_BUFFER_60_SECS =
66+
"media/mpd/sample_mpd_live_with_time_shift_buffer_60_secs";
6067
private static final String SAMPLE_MPD_LIVE_WITH_OFFSET_TOO_SHORT =
6168
"media/mpd/sample_mpd_live_with_offset_too_short";
6269
private static final String SAMPLE_MPD_LIVE_WITH_OFFSET_TOO_LONG =
@@ -442,6 +449,45 @@ public void prepare_targetLiveOffsetTooShort_correctedTargetOffsetAndAlignedWind
442449
assertThat(window.liveConfiguration.targetOffsetMs).isEqualTo(60_000 - 16_000);
443450
}
444451

452+
@Test
453+
public void prepare_targetLiveOffsetConstrainedByManifest_resetByRelease() throws Exception {
454+
LiveConfiguration mediaItemLiveConfiguration =
455+
new LiveConfiguration.Builder().setTargetOffsetMs(25_000L).build();
456+
AtomicBoolean hasCreatedFirstDataSource = new AtomicBoolean();
457+
DashMediaSource mediaSource =
458+
new DashMediaSource.Factory(
459+
() -> {
460+
if (!hasCreatedFirstDataSource.getAndSet(true)) {
461+
// Delivers a manifest with a window duration shorter than the declared offset.
462+
return createSampleMpdDataSource(
463+
SAMPLE_MPD_LIVE_WITH_TIME_SHIFT_BUFFER_16_SECS);
464+
}
465+
// Delivers a manifest with a window duration larger than the declared offset.
466+
return createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_TIME_SHIFT_BUFFER_60_SECS);
467+
})
468+
.createMediaSource(
469+
new MediaItem.Builder()
470+
.setUri(Uri.EMPTY)
471+
.setLiveConfiguration(mediaItemLiveConfiguration)
472+
.build());
473+
List<Window> capturedWindows = new ArrayList<>();
474+
MediaSource.MediaSourceCaller mediaSourceCaller =
475+
(source, timeline) ->
476+
capturedWindows.add(timeline.getWindow(/* windowIndex= */ 0, new Window()));
477+
478+
mediaSource.prepareSource(mediaSourceCaller, /* mediaTransferListener= */ null, PlayerId.UNSET);
479+
480+
RobolectricUtil.runMainLooperUntil(() -> capturedWindows.size() == 1);
481+
// Assert that the offset defined by the media item was overridden.
482+
assertThat(capturedWindows.get(0).liveConfiguration.targetOffsetMs).isEqualTo(9_000L);
483+
484+
mediaSource.releaseSource(mediaSourceCaller);
485+
mediaSource.prepareSource(mediaSourceCaller, /* mediaTransferListener= */ null, PlayerId.UNSET);
486+
487+
RobolectricUtil.runMainLooperUntil(() -> capturedWindows.size() == 2);
488+
assertThat(capturedWindows.get(1).liveConfiguration.targetOffsetMs).isEqualTo(25_000L);
489+
}
490+
445491
@Test
446492
public void canUpdateMediaItem_withIrrelevantFieldsChanged_returnsTrue() {
447493
MediaItem initialMediaItem =
@@ -545,28 +591,6 @@ public void canUpdateMediaItem_withChangedDrmConfiguration_returnsFalse() {
545591
assertThat(canUpdateMediaItem).isFalse();
546592
}
547593

548-
@Test
549-
public void canUpdateMediaItem_withChangedLiveConfiguration_returnsFalse() {
550-
MediaItem initialMediaItem =
551-
new MediaItem.Builder()
552-
.setUri("http://test.test")
553-
.setLiveConfiguration(new LiveConfiguration.Builder().setTargetOffsetMs(2000).build())
554-
.build();
555-
MediaItem updatedMediaItem =
556-
new MediaItem.Builder()
557-
.setUri("http://test.test")
558-
.setLiveConfiguration(new LiveConfiguration.Builder().setTargetOffsetMs(5000).build())
559-
.build();
560-
MediaSource mediaSource =
561-
new DashMediaSource.Factory(
562-
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITHOUT_LIVE_CONFIGURATION))
563-
.createMediaSource(initialMediaItem);
564-
565-
boolean canUpdateMediaItem = mediaSource.canUpdateMediaItem(updatedMediaItem);
566-
567-
assertThat(canUpdateMediaItem).isFalse();
568-
}
569-
570594
@Test
571595
public void updateMediaItem_createsTimelineWithUpdatedItem() throws Exception {
572596
MediaItem initialMediaItem =
@@ -584,6 +608,45 @@ public void updateMediaItem_createsTimelineWithUpdatedItem() throws Exception {
584608
assertThat(window.mediaItem).isEqualTo(updatedMediaItem);
585609
}
586610

611+
@Test
612+
public void updateMediaItem_targetLiveOffsetConstrainedByManifest_resetByUpdatedMediaItem()
613+
throws Exception {
614+
// A live configuration with a target offset larger than the window duration.
615+
LiveConfiguration initialLiveConfiguration =
616+
new LiveConfiguration.Builder().setTargetOffsetMs(25_000L).build();
617+
DashMediaSource mediaSource =
618+
new DashMediaSource.Factory(
619+
() -> createSampleMpdDataSource(SAMPLE_MPD_LIVE_WITH_TIME_SHIFT_BUFFER_16_SECS))
620+
.createMediaSource(
621+
new MediaItem.Builder()
622+
.setUri(Uri.EMPTY)
623+
.setLiveConfiguration(initialLiveConfiguration)
624+
.build());
625+
List<Window> capturedWindows = new ArrayList<>();
626+
MediaSource.MediaSourceCaller mediaSourceCaller =
627+
(source, timeline) ->
628+
capturedWindows.add(timeline.getWindow(/* windowIndex= */ 0, new Window()));
629+
630+
mediaSource.prepareSource(mediaSourceCaller, /* mediaTransferListener= */ null, PlayerId.UNSET);
631+
632+
RobolectricUtil.runMainLooperUntil(() -> capturedWindows.size() == 1);
633+
// Assert that the offset defined by the media item was overridden.
634+
assertThat(capturedWindows.get(0).liveConfiguration.targetOffsetMs).isEqualTo(9_000L);
635+
636+
mediaSource.releaseSource(mediaSourceCaller);
637+
// Set a new live configuration with a target offset within the window duration.
638+
mediaSource.updateMediaItem(
639+
new MediaItem.Builder()
640+
.setUri(Uri.EMPTY)
641+
.setLiveConfiguration(new LiveConfiguration.Builder().setTargetOffsetMs(5_000L).build())
642+
.build());
643+
644+
mediaSource.prepareSource(mediaSourceCaller, /* mediaTransferListener= */ null, PlayerId.UNSET);
645+
646+
RobolectricUtil.runMainLooperUntil(() -> capturedWindows.size() == 2);
647+
assertThat(capturedWindows.get(1).liveConfiguration.targetOffsetMs).isEqualTo(5_000L);
648+
}
649+
587650
private static Window prepareAndWaitForTimelineRefresh(MediaSource mediaSource) throws Exception {
588651
AtomicReference<Timeline.Window> windowReference = new AtomicReference<>();
589652
mediaSource.prepareSource(
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<MPD
3+
type="dynamic"
4+
timeShiftBufferDepth="PT16S"
5+
minimumUpdatePeriod="PT4M"
6+
availabilityStartTime="2020-01-01T00:00:00Z">
7+
<!-- Now is 60 seconds after the start of the window. -->
8+
<UTCTiming
9+
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
10+
value="2020-01-01T01:00:00Z" />
11+
<ServiceDescription id="0">
12+
<Latency target="4000" />
13+
</ServiceDescription>
14+
<Period start="PT0.0S">
15+
<AdaptationSet contentType="video">
16+
<Representation id="0" mimeType="video/mp4">
17+
<SegmentTemplate
18+
timescale="1000000"
19+
duration="2000000"
20+
availabilityTimeOffset="2"
21+
startNumber="1"
22+
media="chunk-$Number%05d$.mp4"/>
23+
</Representation>
24+
</AdaptationSet>
25+
</Period>
26+
</MPD>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<MPD
3+
type="dynamic"
4+
timeShiftBufferDepth="PT60S"
5+
minimumUpdatePeriod="PT4M"
6+
availabilityStartTime="2020-01-01T00:00:00Z">
7+
<!-- Now is 60 seconds after the start of the window. -->
8+
<UTCTiming
9+
schemeIdUri="urn:mpeg:dash:utc:direct:2014"
10+
value="2020-01-01T01:00:00Z" />
11+
<ServiceDescription id="0">
12+
<Latency target="4000" />
13+
</ServiceDescription>
14+
<Period start="PT0.0S">
15+
<AdaptationSet contentType="video">
16+
<Representation id="0" mimeType="video/mp4">
17+
<SegmentTemplate
18+
timescale="1000000"
19+
duration="2000000"
20+
availabilityTimeOffset="2"
21+
startNumber="1"
22+
media="chunk-$Number%05d$.mp4"/>
23+
</Representation>
24+
</AdaptationSet>
25+
</Period>
26+
</MPD>

0 commit comments

Comments
 (0)