mirror of
https://github.com/samsonjs/media.git
synced 2026-03-27 09:45:47 +00:00
Add dynamic concatenating media source.
(GitHub issue #1706) The media source allows adding or removing child sources before and after prepare() was called. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=160516636
This commit is contained in:
parent
a98d5bbd0a
commit
69db6cb60b
3 changed files with 1144 additions and 7 deletions
|
|
@ -63,7 +63,7 @@ public class TimelineTest extends TestCase {
|
|||
|
||||
@Override
|
||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||
return period.set(new int[] { id, periodIndex }, null, 0, WINDOW_DURATION_US, 0);
|
||||
return period.set(periodIndex, null, 0, WINDOW_DURATION_US, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -148,12 +148,33 @@ public class TimelineTest extends TestCase {
|
|||
this.timeline = timeline;
|
||||
}
|
||||
|
||||
public TimelineVerifier assertWindowIds(int... expectedWindowIds) {
|
||||
public TimelineVerifier assertEmpty() {
|
||||
assertWindowIds();
|
||||
assertPeriodCounts();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expectedWindowIds A list of expected window IDs. If an ID is unknown or not important
|
||||
* {@code null} can be passed to skip this window.
|
||||
*/
|
||||
public TimelineVerifier assertWindowIds(Object... expectedWindowIds) {
|
||||
Window window = new Window();
|
||||
assertEquals(expectedWindowIds.length, timeline.getWindowCount());
|
||||
for (int i = 0; i < timeline.getWindowCount(); i++) {
|
||||
timeline.getWindow(i, window, true);
|
||||
assertEquals(expectedWindowIds[i], window.id);
|
||||
if (expectedWindowIds[i] != null) {
|
||||
assertEquals(expectedWindowIds[i], window.id);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public TimelineVerifier assertWindowIsDynamic(boolean... windowIsDynamic) {
|
||||
Window window = new Window();
|
||||
for (int i = 0; i < timeline.getWindowCount(); i++) {
|
||||
timeline.getWindow(i, window, true);
|
||||
assertEquals(windowIsDynamic[i], window.isDynamic);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
|
@ -199,7 +220,6 @@ public class TimelineTest extends TestCase {
|
|||
expectedWindowIndex++;
|
||||
}
|
||||
assertEquals(expectedWindowIndex, period.windowIndex);
|
||||
assertEquals(i - accumulatedPeriodCounts[expectedWindowIndex], ((int[]) period.id)[1]);
|
||||
if (i < accumulatedPeriodCounts[expectedWindowIndex + 1] - 1) {
|
||||
assertEquals(i + 1, timeline.getNextPeriodIndex(i, period, window,
|
||||
ExoPlayer.REPEAT_MODE_OFF));
|
||||
|
|
@ -233,9 +253,7 @@ public class TimelineTest extends TestCase {
|
|||
}
|
||||
|
||||
public void testEmptyTimeline() {
|
||||
new TimelineVerifier(Timeline.EMPTY)
|
||||
.assertWindowIds()
|
||||
.assertPeriodCounts();
|
||||
new TimelineVerifier(Timeline.EMPTY).assertEmpty();
|
||||
}
|
||||
|
||||
public void testSinglePeriodTimeline() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,532 @@
|
|||
/*
|
||||
* 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 android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.TimelineTest;
|
||||
import com.google.android.exoplayer2.TimelineTest.FakeTimeline;
|
||||
import com.google.android.exoplayer2.TimelineTest.StubMediaSource;
|
||||
import com.google.android.exoplayer2.TimelineTest.TimelineVerifier;
|
||||
import com.google.android.exoplayer2.source.MediaSource.Listener;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DynamicConcatenatingMediaSource}
|
||||
*/
|
||||
public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
||||
|
||||
private static final int TIMEOUT_MS = 10000;
|
||||
|
||||
private Timeline timeline;
|
||||
private boolean timelineUpdated;
|
||||
|
||||
public void testPlaylistChangesAfterPreparation() throws InterruptedException {
|
||||
timeline = null;
|
||||
TimelineTest.StubMediaSource[] childSources = createMediaSources(7);
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
prepareAndListenToTimelineUpdates(mediaSource);
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline).assertEmpty();
|
||||
|
||||
// Add first source.
|
||||
mediaSource.addMediaSource(childSources[0]);
|
||||
waitForTimelineUpdate();
|
||||
assertNotNull(timeline);
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(1)
|
||||
.assertWindowIds(111);
|
||||
|
||||
// Add at front of queue.
|
||||
mediaSource.addMediaSource(0, childSources[1]);
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(2, 1)
|
||||
.assertWindowIds(222, 111);
|
||||
|
||||
// Add at back of queue.
|
||||
mediaSource.addMediaSource(childSources[2]);
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(2, 1, 3)
|
||||
.assertWindowIds(222, 111, 333);
|
||||
|
||||
// Add in the middle.
|
||||
mediaSource.addMediaSource(1, childSources[3]);
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(2, 4, 1, 3)
|
||||
.assertWindowIds(222, 444, 111, 333);
|
||||
|
||||
// Add bulk.
|
||||
mediaSource.addMediaSources(3, Arrays.asList((MediaSource) childSources[4],
|
||||
(MediaSource) childSources[5], (MediaSource) childSources[6]));
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(2, 4, 1, 5, 6, 7, 3)
|
||||
.assertWindowIds(222, 444, 111, 555, 666, 777, 333);
|
||||
|
||||
// Remove in the middle.
|
||||
mediaSource.removeMediaSource(3);
|
||||
waitForTimelineUpdate();
|
||||
mediaSource.removeMediaSource(3);
|
||||
waitForTimelineUpdate();
|
||||
mediaSource.removeMediaSource(3);
|
||||
waitForTimelineUpdate();
|
||||
mediaSource.removeMediaSource(1);
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(2, 1, 3)
|
||||
.assertWindowIds(222, 111, 333);
|
||||
for (int i = 3; i <= 6; i++) {
|
||||
childSources[i].assertReleased();
|
||||
}
|
||||
|
||||
// Remove at front of queue.
|
||||
mediaSource.removeMediaSource(0);
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(1, 3)
|
||||
.assertWindowIds(111, 333);
|
||||
childSources[1].assertReleased();
|
||||
|
||||
// Remove at back of queue.
|
||||
mediaSource.removeMediaSource(1);
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(1)
|
||||
.assertWindowIds(111);
|
||||
childSources[2].assertReleased();
|
||||
|
||||
// Remove last source.
|
||||
mediaSource.removeMediaSource(0);
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts()
|
||||
.assertWindowIds();
|
||||
childSources[3].assertReleased();
|
||||
}
|
||||
|
||||
public void testPlaylistChangesBeforePreparation() throws InterruptedException {
|
||||
timeline = null;
|
||||
TimelineTest.StubMediaSource[] childSources = createMediaSources(4);
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
mediaSource.addMediaSource(childSources[0]);
|
||||
mediaSource.addMediaSource(childSources[1]);
|
||||
mediaSource.addMediaSource(0, childSources[2]);
|
||||
mediaSource.removeMediaSource(1);
|
||||
mediaSource.addMediaSource(1, childSources[3]);
|
||||
assertNull(timeline);
|
||||
|
||||
prepareAndListenToTimelineUpdates(mediaSource);
|
||||
waitForTimelineUpdate();
|
||||
assertNotNull(timeline);
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(3, 4, 2)
|
||||
.assertWindowIds(333, 444, 222);
|
||||
|
||||
mediaSource.releaseSource();
|
||||
for (int i = 1; i < 4; i++) {
|
||||
childSources[i].assertReleased();
|
||||
}
|
||||
}
|
||||
|
||||
public void testPlaylistWithLazyMediaSource() throws InterruptedException {
|
||||
timeline = null;
|
||||
TimelineTest.StubMediaSource[] childSources = createMediaSources(2);
|
||||
LazyMediaSource[] lazySources = new LazyMediaSource[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
lazySources[i] = new LazyMediaSource();
|
||||
}
|
||||
|
||||
//Add lazy sources before preparation
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
mediaSource.addMediaSource(lazySources[0]);
|
||||
mediaSource.addMediaSource(0, childSources[0]);
|
||||
mediaSource.removeMediaSource(1);
|
||||
mediaSource.addMediaSource(1, lazySources[1]);
|
||||
assertNull(timeline);
|
||||
prepareAndListenToTimelineUpdates(mediaSource);
|
||||
waitForTimelineUpdate();
|
||||
assertNotNull(timeline);
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(1, 1)
|
||||
.assertWindowIds(111, null)
|
||||
.assertWindowIsDynamic(false, true);
|
||||
|
||||
lazySources[1].triggerTimelineUpdate(new FakeTimeline(9, 999));
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(1, 9)
|
||||
.assertWindowIds(111, 999)
|
||||
.assertWindowIsDynamic(false, false);
|
||||
|
||||
//Add lazy sources after preparation
|
||||
mediaSource.addMediaSource(1, lazySources[2]);
|
||||
waitForTimelineUpdate();
|
||||
mediaSource.addMediaSource(2, childSources[1]);
|
||||
waitForTimelineUpdate();
|
||||
mediaSource.addMediaSource(0, lazySources[3]);
|
||||
waitForTimelineUpdate();
|
||||
mediaSource.removeMediaSource(2);
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(1, 1, 2, 9)
|
||||
.assertWindowIds(null, 111, 222, 999)
|
||||
.assertWindowIsDynamic(true, false, false, false);
|
||||
|
||||
lazySources[3].triggerTimelineUpdate(new FakeTimeline(8, 888));
|
||||
waitForTimelineUpdate();
|
||||
new TimelineVerifier(timeline)
|
||||
.assertPeriodCounts(8, 1, 2, 9)
|
||||
.assertWindowIds(888, 111, 222, 999)
|
||||
.assertWindowIsDynamic(false, false, false, false);
|
||||
|
||||
mediaSource.releaseSource();
|
||||
childSources[0].assertReleased();
|
||||
childSources[1].assertReleased();
|
||||
}
|
||||
|
||||
public void testIllegalArguments() {
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
MediaSource validSource = new StubMediaSource(new FakeTimeline(1, 1));
|
||||
|
||||
// Null sources.
|
||||
try {
|
||||
mediaSource.addMediaSource(null);
|
||||
fail("Null mediaSource not allowed.");
|
||||
} catch (NullPointerException e) {
|
||||
// Expected.
|
||||
}
|
||||
|
||||
MediaSource[] mediaSources = { validSource, null };
|
||||
try {
|
||||
mediaSource.addMediaSources(Arrays.asList(mediaSources));
|
||||
fail("Null mediaSource not allowed.");
|
||||
} catch (NullPointerException e) {
|
||||
// Expected.
|
||||
}
|
||||
|
||||
// Duplicate sources.
|
||||
mediaSource.addMediaSource(validSource);
|
||||
try {
|
||||
mediaSource.addMediaSource(validSource);
|
||||
fail("Duplicate mediaSource not allowed.");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected.
|
||||
}
|
||||
|
||||
mediaSources = new MediaSource[] { new StubMediaSource(new FakeTimeline(1, 1)), validSource};
|
||||
try {
|
||||
mediaSource.addMediaSources(Arrays.asList(mediaSources));
|
||||
fail("Duplicate mediaSource not allowed.");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareAndListenToTimelineUpdates(MediaSource mediaSource) {
|
||||
mediaSource.prepareSource(new StubExoPlayer(), true, new Listener() {
|
||||
@Override
|
||||
public void onSourceInfoRefreshed(Timeline newTimeline, Object manifest) {
|
||||
timeline = newTimeline;
|
||||
synchronized (DynamicConcatenatingMediaSourceTest.this) {
|
||||
timelineUpdated = true;
|
||||
DynamicConcatenatingMediaSourceTest.this.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void waitForTimelineUpdate() throws InterruptedException {
|
||||
long timeoutMs = System.currentTimeMillis() + TIMEOUT_MS;
|
||||
while (!timelineUpdated) {
|
||||
wait(TIMEOUT_MS);
|
||||
if (System.currentTimeMillis() >= timeoutMs) {
|
||||
fail("No timeline update occurred within timeout.");
|
||||
}
|
||||
}
|
||||
timelineUpdated = false;
|
||||
}
|
||||
|
||||
private TimelineTest.StubMediaSource[] createMediaSources(int count) {
|
||||
TimelineTest.StubMediaSource[] sources = new TimelineTest.StubMediaSource[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
sources[i] = new TimelineTest.StubMediaSource(new FakeTimeline(i + 1, (i + 1) * 111));
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
private static class LazyMediaSource implements MediaSource {
|
||||
|
||||
private Listener listener;
|
||||
|
||||
public void triggerTimelineUpdate(Timeline timeline) {
|
||||
listener.onSourceInfoRefreshed(timeline, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseSource() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub ExoPlayer which only accepts custom messages and runs them on a separate handler thread.
|
||||
*/
|
||||
private static class StubExoPlayer implements ExoPlayer, Handler.Callback {
|
||||
|
||||
private final Handler handler;
|
||||
|
||||
public StubExoPlayer() {
|
||||
HandlerThread handlerThread = new HandlerThread("StubExoPlayerThread");
|
||||
handlerThread.start();
|
||||
handler = new Handler(handlerThread.getLooper(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Looper getPlaybackLooper() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(EventListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(EventListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPlaybackState() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(MediaSource mediaSource) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayWhenReady(boolean playWhenReady) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPlayWhenReady() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRepeatMode(@RepeatMode int repeatMode) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRepeatMode() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoading() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToDefaultPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToDefaultPosition(int windowIndex) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(long positionMs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(int windowIndex, long positionMs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaybackParameters getPlaybackParameters() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessages(ExoPlayerMessage... messages) {
|
||||
handler.obtainMessage(0, messages).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockingSendMessages(ExoPlayerMessage... messages) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRendererCount() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRendererType(int index) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackGroupArray getCurrentTrackGroups() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackSelectionArray getCurrentTrackSelections() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCurrentManifest() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeline getCurrentTimeline() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentPeriodIndex() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentWindowIndex() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCurrentPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferedPercentage() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentWindowDynamic() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentWindowSeekable() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayingAd() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentAdGroupIndex() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentAdIndexInAdGroup() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
ExoPlayerMessage[] messages = (ExoPlayerMessage[]) msg.obj;
|
||||
for (ExoPlayerMessage message : messages) {
|
||||
try {
|
||||
message.target.handleMessage(message.messageType, message.message);
|
||||
} catch (ExoPlaybackException e) {
|
||||
fail("Unexpected ExoPlaybackException.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,587 @@
|
|||
/*
|
||||
* 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 android.util.Pair;
|
||||
import android.util.SparseIntArray;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.ExoPlayer.ExoPlayerComponent;
|
||||
import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified
|
||||
* during playback. Access to this class is thread-safe.
|
||||
*/
|
||||
public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPlayerComponent {
|
||||
|
||||
private static final int MSG_ADD = 0;
|
||||
private static final int MSG_ADD_MULTIPLE = 1;
|
||||
private static final int MSG_REMOVE = 2;
|
||||
|
||||
// Accessed on the app thread.
|
||||
private final List<MediaSource> mediaSourcesPublic;
|
||||
|
||||
// Accessed on the playback thread.
|
||||
private final List<MediaSourceHolder> mediaSourceHolders;
|
||||
private final MediaSourceHolder query;
|
||||
private final Map<MediaPeriod, MediaSource> mediaSourceByMediaPeriod;
|
||||
private final List<DeferredMediaPeriod> deferredMediaPeriods;
|
||||
|
||||
private ExoPlayer player;
|
||||
private Listener listener;
|
||||
private boolean preventListenerNotification;
|
||||
private int windowCount;
|
||||
private int periodCount;
|
||||
|
||||
public DynamicConcatenatingMediaSource() {
|
||||
this.mediaSourceByMediaPeriod = new IdentityHashMap<>();
|
||||
this.mediaSourcesPublic = new ArrayList<>();
|
||||
this.mediaSourceHolders = new ArrayList<>();
|
||||
this.deferredMediaPeriods = new ArrayList<>(1);
|
||||
this.query = new MediaSourceHolder(null, null, -1, -1, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a {@link MediaSource} to the playlist.
|
||||
*
|
||||
* @param mediaSource The {@link MediaSource} to be added to the list.
|
||||
*/
|
||||
public synchronized void addMediaSource(MediaSource mediaSource) {
|
||||
addMediaSource(mediaSourcesPublic.size(), mediaSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link MediaSource} to the playlist.
|
||||
*
|
||||
* @param index The index at which the new {@link MediaSource} will be inserted. This index must
|
||||
* be in the range of 0 <= index <= {@link #getSize()}.
|
||||
* @param mediaSource The {@link MediaSource} to be added to the list.
|
||||
*/
|
||||
public synchronized void addMediaSource(int index, MediaSource mediaSource) {
|
||||
Assertions.checkNotNull(mediaSource);
|
||||
Assertions.checkArgument(!mediaSourcesPublic.contains(mediaSource));
|
||||
mediaSourcesPublic.add(index, mediaSource);
|
||||
if (player != null) {
|
||||
player.sendMessages(new ExoPlayerMessage(this, MSG_ADD, Pair.create(index, mediaSource)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends multiple {@link MediaSource}s to the playlist.
|
||||
*
|
||||
* @param mediaSources A collection of {@link MediaSource}s to be added to the list. The media
|
||||
* sources are added in the order in which they appear in this collection.
|
||||
*/
|
||||
public synchronized void addMediaSources(Collection<MediaSource> mediaSources) {
|
||||
addMediaSources(mediaSourcesPublic.size(), mediaSources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple {@link MediaSource}s to the playlist.
|
||||
*
|
||||
* @param index The index at which the new {@link MediaSource}s will be inserted. This index must
|
||||
* be in the range of 0 <= index <= {@link #getSize()}.
|
||||
* @param mediaSources A collection of {@link MediaSource}s to be added to the list. The media
|
||||
* sources are added in the order in which they appear in this collection.
|
||||
*/
|
||||
public synchronized void addMediaSources(int index, Collection<MediaSource> mediaSources) {
|
||||
for (MediaSource mediaSource : mediaSources) {
|
||||
Assertions.checkNotNull(mediaSource);
|
||||
Assertions.checkArgument(!mediaSourcesPublic.contains(mediaSource));
|
||||
}
|
||||
mediaSourcesPublic.addAll(index, mediaSources);
|
||||
if (player != null && !mediaSources.isEmpty()) {
|
||||
player.sendMessages(new ExoPlayerMessage(this, MSG_ADD_MULTIPLE,
|
||||
Pair.create(index, mediaSources)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link MediaSource} from the playlist.
|
||||
*
|
||||
* @param index The index at which the media source will be removed.
|
||||
*/
|
||||
public synchronized void removeMediaSource(int index) {
|
||||
mediaSourcesPublic.remove(index);
|
||||
if (player != null) {
|
||||
player.sendMessages(new ExoPlayerMessage(this, MSG_REMOVE, index));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of media sources in the playlist.
|
||||
*/
|
||||
public synchronized int getSize() {
|
||||
return mediaSourcesPublic.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaSource} at a specified index.
|
||||
*
|
||||
* @param index A index in the range of 0 <= index <= {@link #getSize()}.
|
||||
* @return The {@link MediaSource} at this index.
|
||||
*/
|
||||
public synchronized MediaSource getMediaSource(int index) {
|
||||
return mediaSourcesPublic.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void prepareSource(ExoPlayer player, boolean isTopLevelSource,
|
||||
Listener listener) {
|
||||
this.player = player;
|
||||
this.listener = listener;
|
||||
preventListenerNotification = true;
|
||||
addMediaSourcesInternal(0, mediaSourcesPublic);
|
||||
preventListenerNotification = false;
|
||||
maybeNotifyListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
||||
for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) {
|
||||
mediaSourceHolder.mediaSource.maybeThrowSourceInfoRefreshError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
|
||||
int mediaSourceHolderIndex = findMediaSourceHolderByPeriodIndex(id.periodIndex);
|
||||
MediaSourceHolder holder = mediaSourceHolders.get(mediaSourceHolderIndex);
|
||||
MediaPeriodId idInSource = new MediaPeriodId(id.periodIndex - holder.firstPeriodIndexInChild);
|
||||
MediaPeriod mediaPeriod;
|
||||
if (!holder.isPrepared) {
|
||||
mediaPeriod = new DeferredMediaPeriod(holder.mediaSource, idInSource, allocator);
|
||||
deferredMediaPeriods.add((DeferredMediaPeriod) mediaPeriod);
|
||||
} else {
|
||||
mediaPeriod = holder.mediaSource.createPeriod(idInSource, allocator);
|
||||
}
|
||||
mediaSourceByMediaPeriod.put(mediaPeriod, holder.mediaSource);
|
||||
return mediaPeriod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||
MediaSource mediaSource = mediaSourceByMediaPeriod.get(mediaPeriod);
|
||||
mediaSourceByMediaPeriod.remove(mediaPeriod);
|
||||
if (mediaPeriod instanceof DeferredMediaPeriod) {
|
||||
deferredMediaPeriods.remove(mediaPeriod);
|
||||
((DeferredMediaPeriod) mediaPeriod).releasePeriod();
|
||||
} else {
|
||||
mediaSource.releasePeriod(mediaPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseSource() {
|
||||
for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) {
|
||||
mediaSourceHolder.mediaSource.releaseSource();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
|
||||
preventListenerNotification = true;
|
||||
switch (messageType) {
|
||||
case MSG_ADD: {
|
||||
Pair<Integer, MediaSource> messageData = (Pair<Integer, MediaSource>) message;
|
||||
addMediaSourceInternal(messageData.first, messageData.second);
|
||||
break;
|
||||
}
|
||||
case MSG_ADD_MULTIPLE: {
|
||||
Pair<Integer, Collection<MediaSource>> messageData =
|
||||
(Pair<Integer, Collection<MediaSource>>) message;
|
||||
addMediaSourcesInternal(messageData.first, messageData.second);
|
||||
break;
|
||||
}
|
||||
case MSG_REMOVE: {
|
||||
removeMediaSourceInternal((Integer) message);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
preventListenerNotification = false;
|
||||
maybeNotifyListener();
|
||||
}
|
||||
|
||||
private void maybeNotifyListener() {
|
||||
if (!preventListenerNotification) {
|
||||
listener.onSourceInfoRefreshed(
|
||||
new ConcatenatedTimeline(mediaSourceHolders, windowCount, periodCount), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMediaSourceInternal(int newIndex, MediaSource newMediaSource) {
|
||||
final MediaSourceHolder newMediaSourceHolder;
|
||||
Object newUid = System.identityHashCode(newMediaSource);
|
||||
DeferredTimeline newTimeline = new DeferredTimeline();
|
||||
if (newIndex > 0) {
|
||||
MediaSourceHolder previousHolder = mediaSourceHolders.get(newIndex - 1);
|
||||
newMediaSourceHolder = new MediaSourceHolder(newMediaSource, newTimeline,
|
||||
previousHolder.firstWindowIndexInChild + previousHolder.timeline.getWindowCount(),
|
||||
previousHolder.firstPeriodIndexInChild + previousHolder.timeline.getPeriodCount(),
|
||||
newUid);
|
||||
} else {
|
||||
newMediaSourceHolder = new MediaSourceHolder(newMediaSource, newTimeline, 0, 0, newUid);
|
||||
}
|
||||
correctOffsets(newIndex, newTimeline.getWindowCount(), newTimeline.getPeriodCount());
|
||||
mediaSourceHolders.add(newIndex, newMediaSourceHolder);
|
||||
newMediaSourceHolder.mediaSource.prepareSource(player, false, new Listener() {
|
||||
@Override
|
||||
public void onSourceInfoRefreshed(Timeline newTimeline, Object manifest) {
|
||||
updateMediaSourceInternal(newMediaSourceHolder, newTimeline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addMediaSourcesInternal(int index, Collection<MediaSource> mediaSources) {
|
||||
for (MediaSource mediaSource : mediaSources) {
|
||||
addMediaSourceInternal(index++, mediaSource);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMediaSourceInternal(MediaSourceHolder mediaSourceHolder, Timeline timeline) {
|
||||
if (mediaSourceHolder == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
DeferredTimeline deferredTimeline = mediaSourceHolder.timeline;
|
||||
if (deferredTimeline.getTimeline() == timeline) {
|
||||
return;
|
||||
}
|
||||
int windowOffsetUpdate = timeline.getWindowCount() - deferredTimeline.getWindowCount();
|
||||
int periodOffsetUpdate = timeline.getPeriodCount() - deferredTimeline.getPeriodCount();
|
||||
if (windowOffsetUpdate != 0 || periodOffsetUpdate != 0) {
|
||||
int index = findMediaSourceHolderByPeriodIndex(mediaSourceHolder.firstPeriodIndexInChild);
|
||||
correctOffsets(index + 1, windowOffsetUpdate, periodOffsetUpdate);
|
||||
}
|
||||
mediaSourceHolder.timeline = deferredTimeline.cloneWithNewTimeline(timeline);
|
||||
if (!mediaSourceHolder.isPrepared) {
|
||||
for (int i = deferredMediaPeriods.size() - 1; i >= 0; i--) {
|
||||
if (deferredMediaPeriods.get(i).mediaSource == mediaSourceHolder.mediaSource) {
|
||||
deferredMediaPeriods.get(i).createPeriod();
|
||||
deferredMediaPeriods.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
mediaSourceHolder.isPrepared = true;
|
||||
maybeNotifyListener();
|
||||
}
|
||||
|
||||
private void removeMediaSourceInternal(int index) {
|
||||
MediaSourceHolder holder = mediaSourceHolders.get(index);
|
||||
mediaSourceHolders.remove(index);
|
||||
Timeline oldTimeline = holder.timeline;
|
||||
correctOffsets(index, -oldTimeline.getWindowCount(), -oldTimeline.getPeriodCount());
|
||||
holder.mediaSource.releaseSource();
|
||||
}
|
||||
|
||||
private void correctOffsets(int startIndex, int windowOffsetUpdate, int periodOffsetUpdate) {
|
||||
windowCount += windowOffsetUpdate;
|
||||
periodCount += periodOffsetUpdate;
|
||||
for (int i = startIndex; i < mediaSourceHolders.size(); i++) {
|
||||
mediaSourceHolders.get(i).firstWindowIndexInChild += windowOffsetUpdate;
|
||||
mediaSourceHolders.get(i).firstPeriodIndexInChild += periodOffsetUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
private int findMediaSourceHolderByPeriodIndex(int periodIndex) {
|
||||
query.firstPeriodIndexInChild = periodIndex;
|
||||
int index = Collections.binarySearch(mediaSourceHolders, query);
|
||||
return index >= 0 ? index : -index - 2;
|
||||
}
|
||||
|
||||
private static final class MediaSourceHolder implements Comparable<MediaSourceHolder> {
|
||||
|
||||
public final MediaSource mediaSource;
|
||||
public final Object uid;
|
||||
|
||||
public DeferredTimeline timeline;
|
||||
public int firstWindowIndexInChild;
|
||||
public int firstPeriodIndexInChild;
|
||||
public boolean isPrepared;
|
||||
|
||||
public MediaSourceHolder(MediaSource mediaSource, DeferredTimeline timeline, int window,
|
||||
int period, Object uid) {
|
||||
this.mediaSource = mediaSource;
|
||||
this.timeline = timeline;
|
||||
this.firstWindowIndexInChild = window;
|
||||
this.firstPeriodIndexInChild = period;
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MediaSourceHolder other) {
|
||||
return this.firstPeriodIndexInChild - other.firstPeriodIndexInChild;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ConcatenatedTimeline extends AbstractConcatenatedTimeline {
|
||||
|
||||
private final int windowCount;
|
||||
private final int periodCount;
|
||||
private final int[] firstPeriodInChildIndices;
|
||||
private final int[] firstWindowInChildIndices;
|
||||
private final Timeline[] timelines;
|
||||
private final int[] uids;
|
||||
private final SparseIntArray childIndexByUid;
|
||||
|
||||
public ConcatenatedTimeline(Collection<MediaSourceHolder> mediaSourceHolders, int windowCount,
|
||||
int periodCount) {
|
||||
this.windowCount = windowCount;
|
||||
this.periodCount = periodCount;
|
||||
int childCount = mediaSourceHolders.size();
|
||||
firstPeriodInChildIndices = new int[childCount];
|
||||
firstWindowInChildIndices = new int[childCount];
|
||||
timelines = new Timeline[childCount];
|
||||
uids = new int[childCount];
|
||||
childIndexByUid = new SparseIntArray();
|
||||
int index = 0;
|
||||
for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) {
|
||||
timelines[index] = mediaSourceHolder.timeline;
|
||||
firstPeriodInChildIndices[index] = mediaSourceHolder.firstPeriodIndexInChild;
|
||||
firstWindowInChildIndices[index] = mediaSourceHolder.firstWindowIndexInChild;
|
||||
uids[index] = (int) mediaSourceHolder.uid;
|
||||
childIndexByUid.put(uids[index], index++);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void getChildDataByPeriodIndex(int periodIndex, ChildDataHolder childDataHolder) {
|
||||
int index = Util.binarySearchFloor(firstPeriodInChildIndices, periodIndex, true, false);
|
||||
setChildData(index, childDataHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void getChildDataByWindowIndex(int windowIndex, ChildDataHolder childDataHolder) {
|
||||
int index = Util.binarySearchFloor(firstWindowInChildIndices, windowIndex, true, false);
|
||||
setChildData(index, childDataHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean getChildDataByChildUid(Object childUid, ChildDataHolder childDataHolder) {
|
||||
if (!(childUid instanceof Integer)) {
|
||||
return false;
|
||||
}
|
||||
int index = childIndexByUid.get((int) childUid, -1);
|
||||
if (index == -1) {
|
||||
return false;
|
||||
}
|
||||
setChildData(index, childDataHolder);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWindowCount() {
|
||||
return windowCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPeriodCount() {
|
||||
return periodCount;
|
||||
}
|
||||
|
||||
private void setChildData(int srcIndex, ChildDataHolder dest) {
|
||||
dest.setData(timelines[srcIndex], firstPeriodInChildIndices[srcIndex],
|
||||
firstWindowInChildIndices[srcIndex], uids[srcIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DeferredTimeline extends Timeline {
|
||||
|
||||
private static final Object DUMMY_ID = new Object();
|
||||
private static final Period period = new Period();
|
||||
|
||||
private final Timeline timeline;
|
||||
private final Object replacedID;
|
||||
|
||||
public DeferredTimeline() {
|
||||
timeline = null;
|
||||
replacedID = null;
|
||||
}
|
||||
|
||||
private DeferredTimeline(Timeline timeline, Object replacedID) {
|
||||
this.timeline = timeline;
|
||||
this.replacedID = replacedID;
|
||||
}
|
||||
|
||||
public DeferredTimeline cloneWithNewTimeline(Timeline timeline) {
|
||||
return new DeferredTimeline(timeline, replacedID == null && timeline.getPeriodCount() > 0
|
||||
? timeline.getPeriod(0, period, true).uid : replacedID);
|
||||
}
|
||||
|
||||
public Timeline getTimeline() {
|
||||
return timeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWindowCount() {
|
||||
return timeline == null ? 1 : timeline.getWindowCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Window getWindow(int windowIndex, Window window, boolean setIds,
|
||||
long defaultPositionProjectionUs) {
|
||||
return timeline == null
|
||||
// Dynamic window to indicate pending timeline updates.
|
||||
? window.set(setIds ? DUMMY_ID : null, C.TIME_UNSET, C.TIME_UNSET, false, true, 0,
|
||||
C.TIME_UNSET, 0, 0, 0)
|
||||
: timeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPeriodCount() {
|
||||
return timeline == null ? 1 : timeline.getPeriodCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||
if (timeline == null) {
|
||||
return period.set(setIds ? DUMMY_ID : null, setIds ? DUMMY_ID : null, 0, C.TIME_UNSET,
|
||||
C.TIME_UNSET);
|
||||
}
|
||||
timeline.getPeriod(periodIndex, period, setIds);
|
||||
if (period.uid == replacedID) {
|
||||
period.uid = DUMMY_ID;
|
||||
}
|
||||
return period;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexOfPeriod(Object uid) {
|
||||
return timeline == null ? (uid == DUMMY_ID ? 0 : C.INDEX_UNSET)
|
||||
: timeline.getIndexOfPeriod(uid == DUMMY_ID ? replacedID : uid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
|
||||
|
||||
public final MediaSource mediaSource;
|
||||
|
||||
private final MediaPeriodId id;
|
||||
private final Allocator allocator;
|
||||
|
||||
private MediaPeriod mediaPeriod;
|
||||
private Callback callback;
|
||||
private long preparePositionUs;
|
||||
|
||||
public DeferredMediaPeriod(MediaSource mediaSource, MediaPeriodId id, Allocator allocator) {
|
||||
this.id = id;
|
||||
this.allocator = allocator;
|
||||
this.mediaSource = mediaSource;
|
||||
}
|
||||
|
||||
public void createPeriod() {
|
||||
mediaPeriod = mediaSource.createPeriod(id, allocator);
|
||||
if (callback != null) {
|
||||
mediaPeriod.prepare(this, preparePositionUs);
|
||||
}
|
||||
}
|
||||
|
||||
public void releasePeriod() {
|
||||
if (mediaPeriod != null) {
|
||||
mediaSource.releasePeriod(mediaPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(Callback callback, long preparePositionUs) {
|
||||
this.callback = callback;
|
||||
this.preparePositionUs = preparePositionUs;
|
||||
if (mediaPeriod != null) {
|
||||
mediaPeriod.prepare(this, preparePositionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowPrepareError() throws IOException {
|
||||
if (mediaPeriod != null) {
|
||||
mediaPeriod.maybeThrowPrepareError();
|
||||
} else {
|
||||
mediaSource.maybeThrowSourceInfoRefreshError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackGroupArray getTrackGroups() {
|
||||
return mediaPeriod.getTrackGroups();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
|
||||
SampleStream[] streams, boolean[] streamResetFlags, long positionUs) {
|
||||
return mediaPeriod.selectTracks(selections, mayRetainStreamFlags, streams, streamResetFlags,
|
||||
positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discardBuffer(long positionUs) {
|
||||
mediaPeriod.discardBuffer(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readDiscontinuity() {
|
||||
return mediaPeriod.readDiscontinuity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
return mediaPeriod.getBufferedPositionUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long seekToUs(long positionUs) {
|
||||
return mediaPeriod.seekToUs(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextLoadPositionUs() {
|
||||
return mediaPeriod.getNextLoadPositionUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(long positionUs) {
|
||||
return mediaPeriod != null && mediaPeriod.continueLoading(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContinueLoadingRequested(MediaPeriod source) {
|
||||
callback.onContinueLoadingRequested(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepared(MediaPeriod mediaPeriod) {
|
||||
callback.onPrepared(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue