Support looping with LoopingMediaSource

Now you can do cool things (if you really want to!) like
play a video twice, then play a second video, then loop
the whole thing, all seamlessly.

new LoopingMediaSource(
  new LoopingMediaSource(firstVideoSource, 2),
  secondVideoSource));

You can also just loop, which is probably more useful :).

Issue: #490

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=132049599
This commit is contained in:
olly 2016-09-02 04:19:04 -07:00 committed by Oliver Woodman
parent fa500791c5
commit 884bcb649e

View file

@ -0,0 +1,162 @@
/*
* Copyright (C) 2016 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.Log;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
/**
* Loops a {@link MediaSource}.
*/
public final class LoopingMediaSource implements MediaSource {
private static final String TAG = "LoopingMediaSource";
private final MediaSource childSource;
private final int loopCount;
private int childPeriodCount;
/**
* Loops the provided source indefinitely.
*
* @param childSource The {@link MediaSource} to loop.
*/
public LoopingMediaSource(MediaSource childSource) {
this(childSource, Integer.MAX_VALUE);
}
/**
* Loops the provided source a specified number of times.
*
* @param childSource The {@link MediaSource} to loop.
* @param loopCount The desired number of loops. Must be strictly positive. The actual number of
* loops will be capped at the maximum value that can achieved without causing the number of
* periods exposed by the source to exceed {@link Integer#MAX_VALUE}.
*/
public LoopingMediaSource(MediaSource childSource, int loopCount) {
Assertions.checkArgument(loopCount > 0);
this.childSource = childSource;
this.loopCount = loopCount;
}
@Override
public void prepareSource(final Listener listener) {
childSource.prepareSource(new Listener() {
@Override
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
childPeriodCount = timeline.getPeriodCount();
listener.onSourceInfoRefreshed(new LoopingTimeline(timeline, loopCount), manifest);
}
});
}
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
childSource.maybeThrowSourceInfoRefreshError();
}
@Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
long positionUs) {
return childSource.createPeriod(index % childPeriodCount, callback, allocator, positionUs);
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
childSource.releasePeriod(mediaPeriod);
}
@Override
public void releaseSource() {
childSource.releaseSource();
}
private static final class LoopingTimeline extends Timeline {
private final Timeline childTimeline;
private final int childPeriodCount;
private final int childWindowCount;
private final int loopCount;
public LoopingTimeline(Timeline childTimeline, int loopCount) {
this.childTimeline = childTimeline;
childPeriodCount = childTimeline.getPeriodCount();
childWindowCount = childTimeline.getWindowCount();
// This is the maximum number of loops that can be performed without overflow.
int maxLoopCount = Integer.MAX_VALUE / childPeriodCount;
if (loopCount > maxLoopCount) {
if (loopCount != Integer.MAX_VALUE) {
Log.w(TAG, "Capped loops to avoid overflow:" + loopCount + " -> " + maxLoopCount);
}
this.loopCount = maxLoopCount;
} else {
this.loopCount = loopCount;
}
}
@Override
public int getWindowCount() {
return childWindowCount * loopCount;
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds) {
childTimeline.getWindow(windowIndex % childWindowCount, window, setIds);
int periodIndexOffset = (windowIndex / childWindowCount) * childPeriodCount;
window.firstPeriodIndex += periodIndexOffset;
window.lastPeriodIndex += periodIndexOffset;
return window;
}
@Override
public int getPeriodCount() {
return childPeriodCount * loopCount;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
childTimeline.getPeriod(periodIndex % childPeriodCount, period, setIds);
int loopCount = (periodIndex / childPeriodCount);
period.windowIndex += loopCount * childWindowCount;
if (setIds) {
period.uid = Pair.create(loopCount, period.uid);
}
return period;
}
@Override
public int getIndexOfPeriod(Object uid) {
if (!(uid instanceof Pair)) {
return C.INDEX_UNSET;
}
Pair<?, ?> loopCountAndChildUid = (Pair<?, ?>) uid;
if (!(loopCountAndChildUid.first instanceof Integer)) {
return C.INDEX_UNSET;
}
int loopCount = (Integer) loopCountAndChildUid.first;
int periodIndexOffset = loopCount * childPeriodCount;
return childTimeline.getIndexOfPeriod(loopCountAndChildUid.second) + periodIndexOffset;
}
}
}