mirror of
https://github.com/androidx/media.git
synced 2025-05-06 23:20:42 +08:00
Make ExtractorMediaSource timeline dynamic until duration is set
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
This commit is contained in:
parent
c49ae53699
commit
856c2f8d3e
@ -45,7 +45,7 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -56,7 +56,7 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -70,7 +70,7 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -81,7 +81,7 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -92,7 +92,7 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase {
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -327,8 +327,11 @@ public final class ExtractorMediaSource implements MediaSource, ExtractorMediaPe
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,13 +106,18 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
Assertions.checkIndex(windowIndex, 0, 1);
|
||||
Object id = setIds ? ID : null;
|
||||
long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs;
|
||||
if (isDynamic) {
|
||||
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 live window.
|
||||
// The projection takes us beyond the end of the window.
|
||||
windowDefaultStartPositionUs = C.TIME_UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
return window.set(id, presentationStartTimeMs, windowStartTimeMs, isSeekable, isDynamic,
|
||||
windowDefaultStartPositionUs, windowDurationUs, 0, 0, windowPositionInPeriodUs);
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ public final class SingleSampleMediaSource implements MediaSource {
|
||||
this.eventListener = eventListener;
|
||||
this.eventSourceId = eventSourceId;
|
||||
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
|
||||
timeline = new SinglePeriodTimeline(durationUs, true);
|
||||
timeline = new SinglePeriodTimeline(durationUs, true, false);
|
||||
}
|
||||
|
||||
// MediaSource implementation.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user