mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Make current period a placeholder when a live stream is reset
In case the player is reset while a live stream is playing, the current period needs to be a placeholder. This makes sure that the default start position is used when the first live timeline arrives after re-preparing. #minor-release PiperOrigin-RevId: 539044360 (cherry picked from commit 71153a43a8e55380076af97450414f9142dc636b)
This commit is contained in:
parent
50f4caacd6
commit
56c62d1ab1
@ -1492,9 +1492,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
queue.clear();
|
queue.clear();
|
||||||
shouldContinueLoading = false;
|
shouldContinueLoading = false;
|
||||||
|
|
||||||
|
Timeline timeline = playbackInfo.timeline;
|
||||||
|
if (releaseMediaSourceList && timeline instanceof PlaylistTimeline) {
|
||||||
|
// Wrap the current live timeline to make sure the current period is marked as a placeholder
|
||||||
|
// to force resolving the default start position with the next timeline refresh.
|
||||||
|
timeline =
|
||||||
|
((PlaylistTimeline) playbackInfo.timeline)
|
||||||
|
.copyWithPlaceholderTimeline(mediaSourceList.getShuffleOrder());
|
||||||
|
}
|
||||||
playbackInfo =
|
playbackInfo =
|
||||||
new PlaybackInfo(
|
new PlaybackInfo(
|
||||||
playbackInfo.timeline,
|
timeline,
|
||||||
mediaPeriodId,
|
mediaPeriodId,
|
||||||
requestedContentPositionUs,
|
requestedContentPositionUs,
|
||||||
/* discontinuityStartPositionUs= */ startPositionUs,
|
/* discontinuityStartPositionUs= */ startPositionUs,
|
||||||
|
@ -365,6 +365,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
return new PlaylistTimeline(mediaSourceHolders, shuffleOrder);
|
return new PlaylistTimeline(mediaSourceHolders, shuffleOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the shuffle order */
|
||||||
|
public ShuffleOrder getShuffleOrder() {
|
||||||
|
return shuffleOrder;
|
||||||
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private void enableMediaSource(MediaSourceHolder mediaSourceHolder) {
|
private void enableMediaSource(MediaSourceHolder mediaSourceHolder) {
|
||||||
|
@ -18,6 +18,7 @@ package androidx.media3.exoplayer;
|
|||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.exoplayer.source.ForwardingTimeline;
|
||||||
import androidx.media3.exoplayer.source.ShuffleOrder;
|
import androidx.media3.exoplayer.source.ShuffleOrder;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -39,23 +40,26 @@ import java.util.List;
|
|||||||
public PlaylistTimeline(
|
public PlaylistTimeline(
|
||||||
Collection<? extends MediaSourceInfoHolder> mediaSourceInfoHolders,
|
Collection<? extends MediaSourceInfoHolder> mediaSourceInfoHolders,
|
||||||
ShuffleOrder shuffleOrder) {
|
ShuffleOrder shuffleOrder) {
|
||||||
|
this(getTimelines(mediaSourceInfoHolders), getUids(mediaSourceInfoHolders), shuffleOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlaylistTimeline(Timeline[] timelines, Object[] uids, ShuffleOrder shuffleOrder) {
|
||||||
super(/* isAtomic= */ false, shuffleOrder);
|
super(/* isAtomic= */ false, shuffleOrder);
|
||||||
int childCount = mediaSourceInfoHolders.size();
|
int childCount = timelines.length;
|
||||||
|
this.timelines = timelines;
|
||||||
firstPeriodInChildIndices = new int[childCount];
|
firstPeriodInChildIndices = new int[childCount];
|
||||||
firstWindowInChildIndices = new int[childCount];
|
firstWindowInChildIndices = new int[childCount];
|
||||||
timelines = new Timeline[childCount];
|
this.uids = uids;
|
||||||
uids = new Object[childCount];
|
|
||||||
childIndexByUid = new HashMap<>();
|
childIndexByUid = new HashMap<>();
|
||||||
int index = 0;
|
int index = 0;
|
||||||
int windowCount = 0;
|
int windowCount = 0;
|
||||||
int periodCount = 0;
|
int periodCount = 0;
|
||||||
for (MediaSourceInfoHolder mediaSourceInfoHolder : mediaSourceInfoHolders) {
|
for (Timeline timeline : timelines) {
|
||||||
timelines[index] = mediaSourceInfoHolder.getTimeline();
|
this.timelines[index] = timeline;
|
||||||
firstWindowInChildIndices[index] = windowCount;
|
firstWindowInChildIndices[index] = windowCount;
|
||||||
firstPeriodInChildIndices[index] = periodCount;
|
firstPeriodInChildIndices[index] = periodCount;
|
||||||
windowCount += timelines[index].getWindowCount();
|
windowCount += this.timelines[index].getWindowCount();
|
||||||
periodCount += timelines[index].getPeriodCount();
|
periodCount += this.timelines[index].getPeriodCount();
|
||||||
uids[index] = mediaSourceInfoHolder.getUid();
|
|
||||||
childIndexByUid.put(uids[index], index++);
|
childIndexByUid.put(uids[index], index++);
|
||||||
}
|
}
|
||||||
this.windowCount = windowCount;
|
this.windowCount = windowCount;
|
||||||
@ -112,4 +116,40 @@ import java.util.List;
|
|||||||
public int getPeriodCount() {
|
public int getPeriodCount() {
|
||||||
return periodCount;
|
return periodCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PlaylistTimeline copyWithPlaceholderTimeline(ShuffleOrder shuffleOrder) {
|
||||||
|
Timeline[] newTimelines = new Timeline[timelines.length];
|
||||||
|
for (int i = 0; i < timelines.length; i++) {
|
||||||
|
newTimelines[i] =
|
||||||
|
new ForwardingTimeline(timelines[i]) {
|
||||||
|
@Override
|
||||||
|
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||||
|
Period superPeriod = super.getPeriod(periodIndex, period, setIds);
|
||||||
|
superPeriod.isPlaceholder = true;
|
||||||
|
return superPeriod;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return new PlaylistTimeline(newTimelines, uids, shuffleOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object[] getUids(
|
||||||
|
Collection<? extends MediaSourceInfoHolder> mediaSourceInfoHolders) {
|
||||||
|
Object[] uids = new Object[mediaSourceInfoHolders.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (MediaSourceInfoHolder holder : mediaSourceInfoHolders) {
|
||||||
|
uids[i++] = holder.getUid();
|
||||||
|
}
|
||||||
|
return uids;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Timeline[] getTimelines(
|
||||||
|
Collection<? extends MediaSourceInfoHolder> mediaSourceInfoHolders) {
|
||||||
|
Timeline[] timelines = new Timeline[mediaSourceInfoHolders.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (MediaSourceInfoHolder holder : mediaSourceInfoHolders) {
|
||||||
|
timelines[i++] = holder.getTimeline();
|
||||||
|
}
|
||||||
|
return timelines;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,7 @@ import androidx.media3.exoplayer.drm.DrmSessionManager;
|
|||||||
import androidx.media3.exoplayer.metadata.MetadataOutput;
|
import androidx.media3.exoplayer.metadata.MetadataOutput;
|
||||||
import androidx.media3.exoplayer.source.ClippingMediaSource;
|
import androidx.media3.exoplayer.source.ClippingMediaSource;
|
||||||
import androidx.media3.exoplayer.source.ConcatenatingMediaSource;
|
import androidx.media3.exoplayer.source.ConcatenatingMediaSource;
|
||||||
|
import androidx.media3.exoplayer.source.ForwardingTimeline;
|
||||||
import androidx.media3.exoplayer.source.MaskingMediaSource;
|
import androidx.media3.exoplayer.source.MaskingMediaSource;
|
||||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
import androidx.media3.exoplayer.source.MediaSource;
|
import androidx.media3.exoplayer.source.MediaSource;
|
||||||
@ -188,6 +189,7 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
@ -1629,6 +1631,7 @@ public final class ExoPlayerTest {
|
|||||||
.blockUntilEnded(TIMEOUT_MS);
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
testRunner.assertTimelineChangeReasonsEqual(
|
testRunner.assertTimelineChangeReasonsEqual(
|
||||||
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
|
||||||
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||||
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
|
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
|
||||||
|
|
||||||
@ -1667,6 +1670,119 @@ public final class ExoPlayerTest {
|
|||||||
mediaSource.assertReleased();
|
mediaSource.assertReleased();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stop_withLiveStream_currentPeriodIsPlaceholder() throws TimeoutException {
|
||||||
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
|
FakeTimeline fakeTimeline =
|
||||||
|
new FakeTimeline(
|
||||||
|
new TimelineWindowDefinition(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* id= */ 0,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ true,
|
||||||
|
/* isLive= */ true,
|
||||||
|
/* isPlaceholder= */ false,
|
||||||
|
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||||
|
/* defaultPositionUs= */ 0,
|
||||||
|
TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||||
|
AdPlaybackState.NONE));
|
||||||
|
player.addMediaSources(ImmutableList.of(new FakeMediaSource(fakeTimeline)));
|
||||||
|
player.prepare();
|
||||||
|
runUntilPlaybackState(player, Player.STATE_READY);
|
||||||
|
assertThat(
|
||||||
|
player
|
||||||
|
.getCurrentTimeline()
|
||||||
|
.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period())
|
||||||
|
.isPlaceholder)
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
player.stop();
|
||||||
|
|
||||||
|
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player);
|
||||||
|
assertThat(
|
||||||
|
player
|
||||||
|
.getCurrentTimeline()
|
||||||
|
.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period())
|
||||||
|
.isPlaceholder)
|
||||||
|
.isTrue();
|
||||||
|
player.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stop_withVodStream_currentPeriodIsPlaceholder() throws TimeoutException {
|
||||||
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
|
player.addMediaSources(ImmutableList.of(new FakeMediaSource()));
|
||||||
|
player.prepare();
|
||||||
|
runUntilPlaybackState(player, Player.STATE_READY);
|
||||||
|
assertThat(
|
||||||
|
player
|
||||||
|
.getCurrentTimeline()
|
||||||
|
.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period())
|
||||||
|
.isPlaceholder)
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
player.stop();
|
||||||
|
|
||||||
|
TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(player);
|
||||||
|
assertThat(
|
||||||
|
player
|
||||||
|
.getCurrentTimeline()
|
||||||
|
.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period())
|
||||||
|
.isPlaceholder)
|
||||||
|
.isTrue();
|
||||||
|
player.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void playbackError_withLiveStream_currentPeriodIsPlaceholder() throws TimeoutException {
|
||||||
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
|
FakeTimeline fakeTimeline =
|
||||||
|
new FakeTimeline(
|
||||||
|
new TimelineWindowDefinition(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* id= */ 0,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ true,
|
||||||
|
/* isLive= */ true,
|
||||||
|
/* isPlaceholder= */ false,
|
||||||
|
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||||
|
/* defaultPositionUs= */ 0,
|
||||||
|
TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||||
|
AdPlaybackState.NONE));
|
||||||
|
FakeMediaSource fakeMediaSource =
|
||||||
|
new FakeMediaSource(fakeTimeline) {
|
||||||
|
@Override
|
||||||
|
public Timeline getInitialTimeline() {
|
||||||
|
return fakeTimeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void prepareSourceInternal(
|
||||||
|
@Nullable TransferListener mediaTransferListener) {
|
||||||
|
super.prepareSourceInternal(mediaTransferListener);
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
player.addMediaSources(ImmutableList.of(fakeMediaSource));
|
||||||
|
player.prepare();
|
||||||
|
assertThat(
|
||||||
|
player
|
||||||
|
.getCurrentTimeline()
|
||||||
|
.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period())
|
||||||
|
.isPlaceholder)
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
runUntilError(player);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
player
|
||||||
|
.getCurrentTimeline()
|
||||||
|
.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period())
|
||||||
|
.isPlaceholder)
|
||||||
|
.isTrue();
|
||||||
|
player.release();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void release_correctMasking() throws Exception {
|
public void release_correctMasking() throws Exception {
|
||||||
int[] currentMediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
|
int[] currentMediaItemIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
|
||||||
@ -2001,9 +2117,11 @@ public final class ExoPlayerTest {
|
|||||||
.start()
|
.start()
|
||||||
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
||||||
.blockUntilEnded(TIMEOUT_MS);
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
testRunner.assertTimelinesSame(placeholderTimeline, timeline);
|
testRunner.assertTimelinesSame(
|
||||||
|
placeholderTimeline, timeline, createPlaceholderWrapperTimeline(timeline));
|
||||||
testRunner.assertTimelineChangeReasonsEqual(
|
testRunner.assertTimelineChangeReasonsEqual(
|
||||||
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
|
||||||
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2018,7 +2136,7 @@ public final class ExoPlayerTest {
|
|||||||
new IOException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED))
|
new IOException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED))
|
||||||
.waitForPlaybackState(Player.STATE_IDLE)
|
.waitForPlaybackState(Player.STATE_IDLE)
|
||||||
.prepare()
|
.prepare()
|
||||||
.waitForPlaybackState(Player.STATE_BUFFERING)
|
.waitForPlaybackState(Player.STATE_READY)
|
||||||
.build();
|
.build();
|
||||||
ExoPlayerTestRunner testRunner =
|
ExoPlayerTestRunner testRunner =
|
||||||
new ExoPlayerTestRunner.Builder(context)
|
new ExoPlayerTestRunner.Builder(context)
|
||||||
@ -2031,10 +2149,13 @@ public final class ExoPlayerTest {
|
|||||||
} catch (ExoPlaybackException e) {
|
} catch (ExoPlaybackException e) {
|
||||||
// Expected exception.
|
// Expected exception.
|
||||||
}
|
}
|
||||||
testRunner.assertTimelinesSame(placeholderTimeline, timeline);
|
|
||||||
testRunner.assertTimelineChangeReasonsEqual(
|
testRunner.assertTimelineChangeReasonsEqual(
|
||||||
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
|
||||||
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||||
|
testRunner.assertTimelinesSame(
|
||||||
|
placeholderTimeline, timeline, createPlaceholderWrapperTimeline(timeline), timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -2068,11 +2189,28 @@ public final class ExoPlayerTest {
|
|||||||
long positionWhenFullyReadyAfterReprepare = player.getCurrentPosition();
|
long positionWhenFullyReadyAfterReprepare = player.getCurrentPosition();
|
||||||
player.release();
|
player.release();
|
||||||
|
|
||||||
// Ensure we don't receive further timeline updates when repreparing.
|
verify(mockListener, times(4)).onTimelineChanged(any(), anyInt());
|
||||||
verify(mockListener)
|
InOrder inOrder = inOrder(mockListener);
|
||||||
|
inOrder
|
||||||
|
.verify(mockListener)
|
||||||
.onTimelineChanged(any(), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED));
|
.onTimelineChanged(any(), eq(Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED));
|
||||||
verify(mockListener).onTimelineChanged(any(), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE));
|
inOrder.verify(mockListener).onPlaybackStateChanged(Player.STATE_BUFFERING);
|
||||||
verify(mockListener, times(2)).onTimelineChanged(any(), anyInt());
|
// Source update at reset after playback exception (isPlaceholder=true)
|
||||||
|
inOrder
|
||||||
|
.verify(mockListener)
|
||||||
|
.onTimelineChanged(any(), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE));
|
||||||
|
inOrder.verify(mockListener).onPlaybackStateChanged(Player.STATE_READY);
|
||||||
|
// Source update replacing wrapper timeline of reset (isPlaceholder=false)
|
||||||
|
inOrder
|
||||||
|
.verify(mockListener)
|
||||||
|
.onTimelineChanged(any(), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE));
|
||||||
|
inOrder.verify(mockListener).onPlaybackStateChanged(Player.STATE_IDLE);
|
||||||
|
inOrder.verify(mockListener).onPlaybackStateChanged(Player.STATE_BUFFERING);
|
||||||
|
// Source update at second preparation
|
||||||
|
inOrder
|
||||||
|
.verify(mockListener)
|
||||||
|
.onTimelineChanged(any(), eq(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE));
|
||||||
|
inOrder.verify(mockListener).onPlaybackStateChanged(Player.STATE_READY);
|
||||||
|
|
||||||
assertThat(positionAfterSeekHandled).isEqualTo(50);
|
assertThat(positionAfterSeekHandled).isEqualTo(50);
|
||||||
assertThat(positionAfterReprepareHandled).isEqualTo(50);
|
assertThat(positionAfterReprepareHandled).isEqualTo(50);
|
||||||
@ -2330,10 +2468,16 @@ public final class ExoPlayerTest {
|
|||||||
} catch (ExoPlaybackException e) {
|
} catch (ExoPlaybackException e) {
|
||||||
// Expected exception.
|
// Expected exception.
|
||||||
}
|
}
|
||||||
testRunner.assertTimelinesSame(placeholderTimeline, timeline, placeholderTimeline, timeline);
|
testRunner.assertTimelinesSame(
|
||||||
|
placeholderTimeline,
|
||||||
|
timeline,
|
||||||
|
createPlaceholderWrapperTimeline(timeline),
|
||||||
|
placeholderTimeline,
|
||||||
|
timeline);
|
||||||
testRunner.assertTimelineChangeReasonsEqual(
|
testRunner.assertTimelineChangeReasonsEqual(
|
||||||
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
||||||
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
|
||||||
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
|
||||||
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
||||||
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||||
}
|
}
|
||||||
@ -13142,4 +13286,19 @@ public final class ExoPlayerTest {
|
|||||||
private static ArgumentMatcher<Timeline> noUid(Timeline timeline) {
|
private static ArgumentMatcher<Timeline> noUid(Timeline timeline) {
|
||||||
return argument -> timelinesAreSame(argument, timeline);
|
return argument -> timelinesAreSame(argument, timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a forwarding timeline that sets the {@link Timeline.Period#isPlaceholder} flag to true.
|
||||||
|
* This is what happens when the player is stopped or a playback exception is thrown.
|
||||||
|
*/
|
||||||
|
private static Timeline createPlaceholderWrapperTimeline(Timeline timeline) {
|
||||||
|
return new ForwardingTimeline(timeline) {
|
||||||
|
@Override
|
||||||
|
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||||
|
Period superPeriod = super.getPeriod(periodIndex, period, setIds);
|
||||||
|
superPeriod.isPlaceholder = true;
|
||||||
|
return superPeriod;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 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 androidx.media3.exoplayer;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import androidx.media3.common.Timeline;
|
||||||
|
import androidx.media3.exoplayer.source.ShuffleOrder;
|
||||||
|
import androidx.media3.test.utils.FakeTimeline;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit test for {@link PlaylistTimeline}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class PlaylistTimelineTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void copyWithPlaceholderTimeline_equalTimelineExceptPlaceholderFlag() {
|
||||||
|
MediaSourceInfoHolder mediaSourceInfoHolder1 = mock(MediaSourceInfoHolder.class);
|
||||||
|
MediaSourceInfoHolder mediaSourceInfoHolder2 = mock(MediaSourceInfoHolder.class);
|
||||||
|
ImmutableList<MediaSourceInfoHolder> mediaSourceInfoHolders =
|
||||||
|
ImmutableList.of(mediaSourceInfoHolder1, mediaSourceInfoHolder2);
|
||||||
|
FakeTimeline fakeTimeline1 = new FakeTimeline(2);
|
||||||
|
FakeTimeline fakeTimeline2 = new FakeTimeline(1);
|
||||||
|
when(mediaSourceInfoHolder1.getTimeline()).thenReturn(fakeTimeline1);
|
||||||
|
when(mediaSourceInfoHolder1.getUid()).thenReturn("uid1");
|
||||||
|
when(mediaSourceInfoHolder2.getTimeline()).thenReturn(fakeTimeline2);
|
||||||
|
when(mediaSourceInfoHolder2.getUid()).thenReturn("uid2");
|
||||||
|
ShuffleOrder.DefaultShuffleOrder shuffleOrder =
|
||||||
|
new ShuffleOrder.DefaultShuffleOrder(mediaSourceInfoHolders.size());
|
||||||
|
PlaylistTimeline playlistTimeline = new PlaylistTimeline(mediaSourceInfoHolders, shuffleOrder);
|
||||||
|
|
||||||
|
PlaylistTimeline playlistTimelineCopy =
|
||||||
|
playlistTimeline.copyWithPlaceholderTimeline(shuffleOrder);
|
||||||
|
|
||||||
|
assertThat(playlistTimelineCopy).isNotEqualTo(playlistTimeline);
|
||||||
|
assertThat(playlistTimelineCopy.getWindowCount()).isEqualTo(playlistTimeline.getWindowCount());
|
||||||
|
assertThat(playlistTimelineCopy.getPeriodCount()).isEqualTo(playlistTimeline.getPeriodCount());
|
||||||
|
List<Timeline> copiedChildTimelines = playlistTimelineCopy.getChildTimelines();
|
||||||
|
List<Timeline> originalChildTimelines = playlistTimeline.getChildTimelines();
|
||||||
|
for (int i = 0; i < copiedChildTimelines.size(); i++) {
|
||||||
|
Timeline childTimeline = copiedChildTimelines.get(i);
|
||||||
|
Timeline originalChildTimeline = originalChildTimelines.get(i);
|
||||||
|
for (int j = 0; j < childTimeline.getWindowCount(); j++) {
|
||||||
|
assertThat(childTimeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()))
|
||||||
|
.isEqualTo(
|
||||||
|
originalChildTimeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()));
|
||||||
|
Timeline.Period expectedPeriod =
|
||||||
|
originalChildTimeline.getPeriod(/* periodIndex= */ 0, new Timeline.Period());
|
||||||
|
Timeline.Period actualPeriod =
|
||||||
|
childTimeline.getPeriod(/* periodIndex= */ 0, new Timeline.Period());
|
||||||
|
assertThat(actualPeriod).isNotEqualTo(expectedPeriod);
|
||||||
|
actualPeriod.isPlaceholder = false;
|
||||||
|
assertThat(actualPeriod).isEqualTo(expectedPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -757,7 +757,11 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
period0Seq0 /* ENDED */)
|
period0Seq0 /* ENDED */)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
|
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
|
||||||
.containsExactly(WINDOW_0 /* prepared */, WINDOW_0 /* prepared */);
|
.containsExactly(
|
||||||
|
WINDOW_0 /* playlist change */,
|
||||||
|
WINDOW_0 /* prepared */,
|
||||||
|
period0Seq0 /* reset after error */,
|
||||||
|
period0Seq0 /* second prepare */);
|
||||||
assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period0Seq0);
|
assertThat(listener.getEvents(EVENT_POSITION_DISCONTINUITY)).containsExactly(period0Seq0);
|
||||||
assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0Seq0);
|
assertThat(listener.getEvents(EVENT_SEEK_STARTED)).containsExactly(period0Seq0);
|
||||||
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
assertThat(listener.getEvents(EVENT_IS_LOADING_CHANGED))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user