Dynamic playlists with Exoplayer 2

I would like to use ExoPlayer2 with playlists that have the ability to dynamically change tracks (add or remove them from the playlist) and change the loop settings.

Since ConcatenatingMediaSource has static arrays (not lists), I implement DynamicMediaSource, for example Concatenating, but with lists instead of arrays and one way of the addSource method to add another media source to the list.

public void addSource(MediaSource mediaSource) {
    this.mediaSources.add(mediaSource);
    duplicateFlags = buildDuplicateFlags(this.mediaSources);
    if(!mediaSources.isEmpty())
        prepareSource(mediaSources.size() -1);
    else
        prepareSource(0);
}

When I call addSource

                MediaSource ms = buildMediaSource(mynewuri, null);
                mediaSource.addSource(ms);

the track is added to the arrays, but something seems to be missing, because I always get an ArrayOutOfBoundsException in the createPeriod method.

The createPeriod method

mediaSources.get(sourceIndex)...

trying to access index = mediaSources.size ().

Can you help me?

+4
1

. . SparseArrays , .

DynamicMediaSource :

private final List<MediaSource> mediaSources;
private final SparseArray<Timeline> timelines;
private final SparseArray<Object> manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private SparseArray<Boolean> duplicateFlags;

private void handleSourceInfoRefreshed(int sourceFirstIndex, Timeline sourceTimeline,
                                       Object sourceManifest) {
    // Set the timeline and manifest.
    timelines.put(sourceFirstIndex, sourceTimeline);
    manifests.put(sourceFirstIndex, sourceManifest);

    // Also set the timeline and manifest for any duplicate entries of the same source.
    for (int i = sourceFirstIndex + 1; i < mediaSources.size(); i++) {
        if (mediaSources.get(i).equals(mediaSources.get(sourceFirstIndex))) {
            timelines.put(i, sourceTimeline);
            manifests.put(i, sourceManifest);
        }
    }

    for(int i= 0; i<mediaSources.size(); i++){
        if(timelines.get(i) == null){
            // Don't invoke the listener until all sources have timelines.
            return;
        }
    }

    timeline = new DynamicTimeline(new ArrayList(asList(timelines)));
    listener.onSourceInfoRefreshed(timeline, new ArrayList(asList(manifests)));
}

DynamicMediaSource:

public final class DynamicMediaSource implements MediaSource {

private static final String TAG = "DynamicSource";

private final List<MediaSource> mediaSources;
private final List<Timeline> timelines;
private final List<Object> manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private SparseArray<Boolean> duplicateFlags;

private Listener listener;
private DynamicTimeline timeline;

/**
 * @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same
 *                     {@link MediaSource} instance to be present more than once in the array.
 */
public DynamicMediaSource(MediaSource... mediaSources) {
    this.mediaSources = new ArrayList<MediaSource>(Arrays.asList(mediaSources));
    timelines = new ArrayList<Timeline>();
    manifests = new ArrayList<Object>();
    sourceIndexByMediaPeriod = new HashMap<>();
    duplicateFlags = buildDuplicateFlags(this.mediaSources);
}

public void addSource(MediaSource mediaSource) {
    this.mediaSources.add(mediaSource);
    duplicateFlags = buildDuplicateFlags(this.mediaSources);
    /*if(!mediaSources.isEmpty())
        prepareSource(mediaSources.size() -1);
    else
        prepareSource(0);*/
}

@Override
public void prepareSource(Listener listener) {
    this.listener = listener;
    for (int i = 0; i < mediaSources.size(); i++) {
        prepareSource(i);
        /*if (duplicateFlags.get(i) == null || !duplicateFlags.get(i)) {
            final int index = i;
            mediaSources.get(i).prepareSource(new Listener() {
                @Override
                public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
                    handleSourceInfoRefreshed(index, timeline, manifest);
                }
            });
        }*/
    }
}

private void prepareSource(int sourceindex) {
    if (duplicateFlags.get(sourceindex) == null || !duplicateFlags.get(sourceindex)) {
        final int index = sourceindex;
        mediaSources.get(sourceindex).prepareSource(new Listener() {
            @Override
            public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
                handleSourceInfoRefreshed(index, timeline, manifest);
            }
        });
    }
}

@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
    for (int i = 0; i < mediaSources.size(); i++) {
        if (duplicateFlags.get(i) == null || !duplicateFlags.get(i)) {
            mediaSources.get(i).maybeThrowSourceInfoRefreshError();
        }
    }
}

@Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
                                long positionUs) {
    int sourceIndex = timeline.getSourceIndexForPeriod(index);
    int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
    MediaPeriod mediaPeriod = mediaSources.get(sourceIndex).createPeriod(periodIndexInSource, callback,
            allocator, positionUs);
    sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex);
    return mediaPeriod;
}

@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
    int sourceIndex = sourceIndexByMediaPeriod.get(mediaPeriod);
    sourceIndexByMediaPeriod.remove(mediaPeriod);
    mediaSources.get(sourceIndex).releasePeriod(mediaPeriod);
}

@Override
public void releaseSource() {
    for (int i = 0; i < mediaSources.size(); i++) {
        if (duplicateFlags.get(i) == null || !duplicateFlags.get(i)) {
            mediaSources.get(i).releaseSource();
        }
    }
}

private void handleSourceInfoRefreshed(int sourceFirstIndex, Timeline sourceTimeline,
                                       Object sourceManifest) {
    // Set the timeline and manifest.
    timelines.add(sourceFirstIndex, sourceTimeline);
    manifests.add(sourceFirstIndex, sourceManifest);
    // Also set the timeline and manifest for any duplicate entries of the same source.
    for (int i = sourceFirstIndex + 1; i < mediaSources.size(); i++) {
        if (mediaSources.get(i).equals(mediaSources.get(sourceFirstIndex))) {
            timelines.add(i, sourceTimeline);
            manifests.add(i, sourceManifest);
        }
    }
    for (Timeline timeline : timelines) {
        if (timeline == null) {
            // Don't invoke the listener until all sources have timelines.
            return;
        }
    }
    timeline = new DynamicTimeline(new ArrayList(timelines));
    listener.onSourceInfoRefreshed(timeline, new ArrayList(manifests));
}

private static SparseArray<Boolean> buildDuplicateFlags(List<MediaSource> mediaSources) {
    SparseArray<Boolean> duplicateFlags = new SparseArray<Boolean>();
    IdentityHashMap<MediaSource, Void> sources = new IdentityHashMap<>(mediaSources.size());
    for (int i = 0; i < mediaSources.size(); i++) {
        MediaSource mediaSource = mediaSources.get(i);
        if (!sources.containsKey(mediaSource)) {
            sources.put(mediaSource, null);
        } else {
            duplicateFlags.setValueAt(i, true);
        }
    }
    return duplicateFlags;
}

/**
 * A {@link Timeline} that is the concatenation of one or more {@link Timeline}s.
 */
private static final class DynamicTimeline extends Timeline {

    private final List<Timeline> timelines;
    private final List<Integer> sourcePeriodOffsets;
    private final List<Integer> sourceWindowOffsets;

    public DynamicTimeline(List<Timeline> timelines) {
        List<Integer> sourcePeriodOffsets = new ArrayList<>();
        List<Integer> sourceWindowOffsets = new ArrayList<>();
        int periodCount = 0;
        int windowCount = 0;
        for (Timeline timeline : timelines) {
            periodCount += timeline.getPeriodCount();
            windowCount += timeline.getWindowCount();
            sourcePeriodOffsets.add(periodCount);
            sourceWindowOffsets.add(windowCount);
        }
        this.timelines = timelines;
        this.sourcePeriodOffsets = sourcePeriodOffsets;
        this.sourceWindowOffsets = sourceWindowOffsets;
    }

    @Override
    public int getWindowCount() {
        return sourceWindowOffsets.get(sourceWindowOffsets.size() - 1);
    }

    @Override
    public Window getWindow(int windowIndex, Window window, boolean setIds) {
        int sourceIndex = getSourceIndexForWindow(windowIndex);
        int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
        int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
        timelines.get(sourceIndex).getWindow(windowIndex - firstWindowIndexInSource, window, setIds);
        window.firstPeriodIndex += firstPeriodIndexInSource;
        window.lastPeriodIndex += firstPeriodIndexInSource;
        return window;
    }

    @Override
    public int getPeriodCount() {
        return sourcePeriodOffsets.get(sourcePeriodOffsets.size() - 1);
    }

    @Override
    public Period getPeriod(int periodIndex, Period period, boolean setIds) {
        int sourceIndex = getSourceIndexForPeriod(periodIndex);
        int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
        int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
        timelines.get(sourceIndex).getPeriod(periodIndex - firstPeriodIndexInSource, period, setIds);
        period.windowIndex += firstWindowIndexInSource;
        if (setIds) {
            period.uid = Pair.create(sourceIndex, period.uid);
        }
        return period;
    }

    @Override
    public int getIndexOfPeriod(Object uid) {
        if (!(uid instanceof Pair)) {
            return C.INDEX_UNSET;
        }
        Pair<?, ?> sourceIndexAndPeriodId = (Pair<?, ?>) uid;
        if (!(sourceIndexAndPeriodId.first instanceof Integer)) {
            return C.INDEX_UNSET;
        }
        int sourceIndex = (Integer) sourceIndexAndPeriodId.first;
        Object periodId = sourceIndexAndPeriodId.second;
        if (sourceIndex < 0 || sourceIndex >= timelines.size()) {
            return C.INDEX_UNSET;
        }
        int periodIndexInSource = timelines.get(sourceIndex).getIndexOfPeriod(periodId);
        return periodIndexInSource == C.INDEX_UNSET ? C.INDEX_UNSET
                : getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource;
    }

    private int getSourceIndexForPeriod(int periodIndex) {
        return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1;
    }

    private int getFirstPeriodIndexInSource(int sourceIndex) {
        return sourceIndex == 0 ? 0 : sourcePeriodOffsets.get(sourceIndex - 1);
    }

    private int getSourceIndexForWindow(int windowIndex) {
        return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1;
    }

    private int getFirstWindowIndexInSource(int sourceIndex) {
        return sourceIndex == 0 ? 0 : sourceWindowOffsets.get(sourceIndex - 1);
    }

}
}
+2

Source: https://habr.com/ru/post/1655299/


All Articles