Skip to content

Commit

Permalink
Support looping with LoopingMediaSource
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ojw28 committed Sep 2, 2016
1 parent fa50079 commit 884bcb6
Showing 1 changed file with 162 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}

}

0 comments on commit 884bcb6

Please sign in to comment.