From 057771aeec61058cd6c95553cbe45766113e00ed Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 21 Oct 2020 12:01:45 +0100 Subject: [PATCH] Fix calculation for isSegmentAvailableAtFullNetworkSpeed. The current caluclation was comparing Unix time with period time. Fix it by always comparing against period time. Issue: #4904 PiperOrigin-RevId: 338235754 --- .../source/dash/DefaultDashChunkSource.java | 37 ++++---- .../dash/DefaultDashChunkSourceTest.java | 92 +++++++++++++++++++ .../sample_mpd_live_with_offset_inside_window | 3 +- 3 files changed, 115 insertions(+), 17 deletions(-) create mode 100644 library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index ebadd96d2d..12a5bf8512 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -277,6 +277,7 @@ public class DefaultDashChunkSource implements DashChunkSource { } long nowUnixTimeUs = C.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs)); + long nowPeriodTimeUs = getNowPeriodTimeUs(nowUnixTimeUs); MediaChunk previous = queue.isEmpty() ? null : queue.get(queue.size() - 1); MediaChunkIterator[] chunkIterators = new MediaChunkIterator[trackSelection.length()]; for (int i = 0; i < chunkIterators.length; i++) { @@ -300,7 +301,7 @@ public class DefaultDashChunkSource implements DashChunkSource { } else { chunkIterators[i] = new RepresentationSegmentIterator( - representationHolder, segmentNum, lastAvailableSegmentNum, nowUnixTimeUs); + representationHolder, segmentNum, lastAvailableSegmentNum, nowPeriodTimeUs); } } } @@ -391,7 +392,7 @@ public class DefaultDashChunkSource implements DashChunkSource { segmentNum, maxSegmentCount, seekTimeUs, - nowUnixTimeUs); + nowPeriodTimeUs); } @Override @@ -488,13 +489,16 @@ public class DefaultDashChunkSource implements DashChunkSource { } long lastSegmentNum = representationHolders[0].getLastAvailableSegmentNum(nowUnixTimeUs); long lastSegmentEndTimeUs = representationHolders[0].getSegmentEndTimeUs(lastSegmentNum); - long nowPeriodTimeUs = - nowUnixTimeUs - - C.msToUs(manifest.availabilityStartTimeMs + manifest.getPeriod(periodIndex).startMs); + long nowPeriodTimeUs = getNowPeriodTimeUs(nowUnixTimeUs); long availabilityEndTimeUs = min(nowPeriodTimeUs, lastSegmentEndTimeUs); return max(0, availabilityEndTimeUs - playbackPositionUs); } + private long getNowPeriodTimeUs(long nowUnixTimeUs) { + return nowUnixTimeUs + - C.msToUs(manifest.availabilityStartTimeMs + manifest.getPeriod(periodIndex).startMs); + } + protected Chunk newInitializationChunk( RepresentationHolder representationHolder, DataSource dataSource, @@ -535,7 +539,7 @@ public class DefaultDashChunkSource implements DashChunkSource { long firstSegmentNum, int maxSegmentCount, long seekTimeUs, - long nowUnixTimeUs) { + long nowPeriodTimeUs) { Representation representation = representationHolder.representation; long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum); RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum); @@ -543,7 +547,8 @@ public class DefaultDashChunkSource implements DashChunkSource { if (representationHolder.chunkExtractor == null) { long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum); int flags = - representationHolder.isSegmentAvailableAtFullNetworkSpeed(firstSegmentNum, nowUnixTimeUs) + representationHolder.isSegmentAvailableAtFullNetworkSpeed( + firstSegmentNum, nowPeriodTimeUs) ? 0 : DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED; DataSpec dataSpec = DashUtil.buildDataSpec(representation, segmentUri, flags); @@ -569,7 +574,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ? periodDurationUs : C.TIME_UNSET; int flags = - representationHolder.isSegmentAvailableAtFullNetworkSpeed(segmentNum, nowUnixTimeUs) + representationHolder.isSegmentAvailableAtFullNetworkSpeed(segmentNum, nowPeriodTimeUs) ? 0 : DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED; DataSpec dataSpec = DashUtil.buildDataSpec(representation, segmentUri, flags); @@ -597,7 +602,7 @@ public class DefaultDashChunkSource implements DashChunkSource { protected static final class RepresentationSegmentIterator extends BaseMediaChunkIterator { private final RepresentationHolder representationHolder; - private final long currentUnixTimeUs; + private final long nowPeriodTimeUs; /** * Creates iterator. @@ -605,17 +610,17 @@ public class DefaultDashChunkSource implements DashChunkSource { * @param representation The {@link RepresentationHolder} to wrap. * @param firstAvailableSegmentNum The number of the first available segment. * @param lastAvailableSegmentNum The number of the last available segment. - * @param currentUnixTimeUs The current time in microseconds since the epoch used for - * calculating if segments are available at full network speed. + * @param nowPeriodTimeUs The current time in microseconds since the start of the period used + * for calculating if segments are available at full network speed. */ public RepresentationSegmentIterator( RepresentationHolder representation, long firstAvailableSegmentNum, long lastAvailableSegmentNum, - long currentUnixTimeUs) { + long nowPeriodTimeUs) { super(/* fromIndex= */ firstAvailableSegmentNum, /* toIndex= */ lastAvailableSegmentNum); this.representationHolder = representation; - this.currentUnixTimeUs = currentUnixTimeUs; + this.nowPeriodTimeUs = nowPeriodTimeUs; } @Override @@ -624,7 +629,7 @@ public class DefaultDashChunkSource implements DashChunkSource { long currentIndex = getCurrentIndex(); RangedUri segmentUri = representationHolder.getSegmentUrl(currentIndex); int flags = - representationHolder.isSegmentAvailableAtFullNetworkSpeed(currentIndex, currentUnixTimeUs) + representationHolder.isSegmentAvailableAtFullNetworkSpeed(currentIndex, nowPeriodTimeUs) ? 0 : DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED; return DashUtil.buildDataSpec(representationHolder.representation, segmentUri, flags); @@ -787,8 +792,8 @@ public class DefaultDashChunkSource implements DashChunkSource { - 1; } - public boolean isSegmentAvailableAtFullNetworkSpeed(long segmentNum, long nowUnixTimeUs) { - return getSegmentEndTimeUs(segmentNum) <= nowUnixTimeUs; + public boolean isSegmentAvailableAtFullNetworkSpeed(long segmentNum, long nowPeriodTimeUs) { + return getSegmentEndTimeUs(segmentNum) <= nowPeriodTimeUs; } @Nullable diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java new file mode 100644 index 0000000000..71aed770b0 --- /dev/null +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source.dash; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import android.os.SystemClock; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.chunk.ChunkHolder; +import com.google.android.exoplayer2.source.dash.manifest.DashManifest; +import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; +import com.google.android.exoplayer2.testutil.FakeDataSource; +import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.trackselection.FixedTrackSelection; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.LoaderErrorThrower; +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit test for {@link DefaultDashChunkSource}. */ +@RunWith(AndroidJUnit4.class) +public class DefaultDashChunkSourceTest { + + private static final String SAMPLE_MPD_LIVE_WITH_OFFSET_INSIDE_WINDOW = + "media/mpd/sample_mpd_live_with_offset_inside_window"; + + @Test + public void getNextChunk_forLowLatencyManifest_setsCorrectMayNotLoadAtFullNetworkSpeedFlag() + throws Exception { + long nowMs = 2_000_000_000_000L; + SystemClock.setCurrentTimeMillis(nowMs); + DashManifest manifest = + new DashManifestParser() + .parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream( + ApplicationProvider.getApplicationContext(), + SAMPLE_MPD_LIVE_WITH_OFFSET_INSIDE_WINDOW)); + DefaultDashChunkSource chunkSource = + new DefaultDashChunkSource( + new LoaderErrorThrower.Dummy(), + manifest, + /* 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); + + long nowInPeriodUs = C.msToUs(nowMs - manifest.availabilityStartTimeMs); + ChunkHolder output = new ChunkHolder(); + + chunkSource.getNextChunk( + /* playbackPositionUs= */ nowInPeriodUs - 5 * C.MICROS_PER_SECOND, + /* loadPositionUs= */ nowInPeriodUs - 5 * C.MICROS_PER_SECOND, + /* queue= */ ImmutableList.of(), + output); + assertThat(output.chunk.dataSpec.flags & DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED) + .isEqualTo(0); + + chunkSource.getNextChunk( + /* playbackPositionUs= */ nowInPeriodUs, + /* loadPositionUs= */ nowInPeriodUs, + /* queue= */ ImmutableList.of(), + output); + assertThat(output.chunk.dataSpec.flags & DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED) + .isNotEqualTo(0); + } +} diff --git a/testdata/src/test/assets/media/mpd/sample_mpd_live_with_offset_inside_window b/testdata/src/test/assets/media/mpd/sample_mpd_live_with_offset_inside_window index e0d4cfdddc..bf0704b440 100644 --- a/testdata/src/test/assets/media/mpd/sample_mpd_live_with_offset_inside_window +++ b/testdata/src/test/assets/media/mpd/sample_mpd_live_with_offset_inside_window @@ -17,7 +17,8 @@ timescale="1000000" duration="2000000" availabilityTimeOffset="2" - startNumber="1"/> + startNumber="1" + media="chunk-$Number%05d$.mp4"/>