Skip to content

Commit

Permalink
Make ExtractorMediaSource timeline dynamic until duration is set
Browse files Browse the repository at this point in the history
We (eventually - albeit possibly infinitely far in the future)
expect a timeline update with a window of known duration. This
also stops live radio stream playbacks transitioning to ended
state when their tracks are disabled.

As part of this fix, I found an issue where getPeriodPosition
could return null even when defaultPositionProjectionUs is 0,
which is not as documented.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=176492024
  • Loading branch information
ojw28 committed Nov 21, 2017
1 parent c49ae53 commit 856c2f8
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected void setUp() throws Exception {
}

public void testNoClipping() {
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true);
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false);

Timeline clippedTimeline = getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US);

Expand All @@ -56,7 +56,7 @@ public void testNoClipping() {
}

public void testClippingUnseekableWindowThrows() {
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), false);
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), false, false);

// If the unseekable window isn't clipped, clipping succeeds.
getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US);
Expand All @@ -70,7 +70,7 @@ public void testClippingUnseekableWindowThrows() {
}

public void testClippingStart() {
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true);
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false);

Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US,
TEST_PERIOD_DURATION_US);
Expand All @@ -81,7 +81,7 @@ public void testClippingStart() {
}

public void testClippingEnd() {
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true);
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false);

Timeline clippedTimeline = getClippedTimeline(timeline, 0,
TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
Expand All @@ -92,7 +92,7 @@ public void testClippingEnd() {
}

public void testClippingStartAndEnd() {
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true);
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false);

Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US,
TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US * 2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,11 @@ public void onSourceInfoRefreshed(long durationUs, boolean isSeekable) {
private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable) {
timelineDurationUs = durationUs;
timelineIsSeekable = isSeekable;
sourceListener.onSourceInfoRefreshed(
this, new SinglePeriodTimeline(timelineDurationUs, timelineIsSeekable), null);
// If the duration is currently unset, we expect to be able to update the window when its
// duration eventually becomes known.
boolean isDynamic = timelineDurationUs == C.TIME_UNSET;
sourceListener.onSourceInfoRefreshed(this,
new SinglePeriodTimeline(timelineDurationUs, timelineIsSeekable, isDynamic), null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ public final class SinglePeriodTimeline extends Timeline {
private final boolean isDynamic;

/**
* Creates a timeline of one period of known duration, and a static window starting at zero and
* extending to that duration.
* Creates a timeline containing a single period and a window that spans it.
*
* @param durationUs The duration of the period, in microseconds.
* @param isSeekable Whether seeking is supported within the period.
* @param isDynamic Whether the window may change when the timeline is updated.
*/
public SinglePeriodTimeline(long durationUs, boolean isSeekable) {
this(durationUs, durationUs, 0, 0, isSeekable, false);
public SinglePeriodTimeline(long durationUs, boolean isSeekable, boolean isDynamic) {
this(durationUs, durationUs, 0, 0, isSeekable, isDynamic);
}

/**
Expand All @@ -63,7 +63,7 @@ public SinglePeriodTimeline(long periodDurationUs, long windowDurationUs,
long windowPositionInPeriodUs, long windowDefaultStartPositionUs, boolean isSeekable,
boolean isDynamic) {
this(C.TIME_UNSET, C.TIME_UNSET, periodDurationUs, windowDurationUs, windowPositionInPeriodUs,
windowDefaultStartPositionUs, isSeekable, isDynamic);
windowDefaultStartPositionUs, isSeekable, isDynamic);
}

/**
Expand Down Expand Up @@ -106,11 +106,16 @@ public Window getWindow(int windowIndex, Window window, boolean setIds,
Assertions.checkIndex(windowIndex, 0, 1);
Object id = setIds ? ID : null;
long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs;
if (isDynamic) {
windowDefaultStartPositionUs += defaultPositionProjectionUs;
if (windowDefaultStartPositionUs > windowDurationUs) {
// The projection takes us beyond the end of the live window.
if (isDynamic && defaultPositionProjectionUs != 0) {
if (windowDurationUs == C.TIME_UNSET) {
// Don't allow projection into a window that has an unknown duration.
windowDefaultStartPositionUs = C.TIME_UNSET;
} else {
windowDefaultStartPositionUs += defaultPositionProjectionUs;
if (windowDefaultStartPositionUs > windowDurationUs) {
// The projection takes us beyond the end of the window.
windowDefaultStartPositionUs = C.TIME_UNSET;
}
}
}
return window.set(id, presentationStartTimeMs, windowStartTimeMs, isSeekable, isDynamic,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Fo
this.eventListener = eventListener;
this.eventSourceId = eventSourceId;
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
timeline = new SinglePeriodTimeline(durationUs, true);
timeline = new SinglePeriodTimeline(durationUs, true, false);
}

// MediaSource implementation.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2017 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;

import static com.google.common.truth.Truth.assertThat;

import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.Timeline.Window;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

/**
* Unit test for {@link SinglePeriodTimeline}.
*/
@RunWith(RobolectricTestRunner.class)
@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE)
public final class SinglePeriodTimelineTest {

private Window window;
private Period period;

@Before
public void setUp() throws Exception {
window = new Window();
period = new Period();
}

@Test
public void testGetPeriodPositionDynamicWindowUnknownDuration() {
SinglePeriodTimeline timeline = new SinglePeriodTimeline(C.TIME_UNSET, false, true);
// Should return null with any positive position projection.
Pair<Integer, Long> position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, 1);
assertThat(position).isNull();
// Should return (0, 0) without a position projection.
position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, 0);
assertThat(position.first).isEqualTo(0);
assertThat(position.second).isEqualTo(0);
}

@Test
public void testGetPeriodPositionDynamicWindowKnownDuration() {
long windowDurationUs = 1000;
SinglePeriodTimeline timeline = new SinglePeriodTimeline(windowDurationUs, windowDurationUs, 0,
0, false, true);
// Should return null with a positive position projection beyond window duration.
Pair<Integer, Long> position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET,
windowDurationUs + 1);
assertThat(position).isNull();
// Should return (0, duration) with a projection equal to window duration.
position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, windowDurationUs);
assertThat(position.first).isEqualTo(0);
assertThat(position.second).isEqualTo(windowDurationUs);
// Should return (0, 0) without a position projection.
position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, 0);
assertThat(position.first).isEqualTo(0);
assertThat(position.second).isEqualTo(0);
}

}

0 comments on commit 856c2f8

Please sign in to comment.