Ответ 1
Чтобы добавить новые видеофайлы в список воспроизведения, вам понадобится новая реализация MediaSource
, которая может обрабатывать список источников, позволяющих изменять размер. Это довольно просто для достижения, самый простой способ сделать это - создать модифицированную реализацию ConcaternatingMediaSource
, которая использует списки вместо массивов для хранения и повторения по медиа-источникам. Затем вы заменяете ConcaternatingMediaSource
в playAndUpdateVideo
на новую реализацию, используя списки. Это позволит вам добавлять и удалять из плейлиста по вашему желанию, вы можете добавлять новые медиафайлы, когда запускается прослушиватель полного прослушивания. Вот полный класс для реализации медиа-источника, используя списки:
public final class DynamicMediaSource implements MediaSource {
private List<MediaSource> mediaSources;
private SparseArray<Timeline> timelines;
private SparseArray<Object> manifests;
private Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private SparseArray<Boolean> duplicateFlags;
private boolean isRepeatOneAtomic;
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(List<MediaSource> mediaSources) {
this(false, mediaSources);
}
/**
* @param isRepeatOneAtomic Whether the concatenated media source shall be treated as atomic
* (i.e., repeated in its entirety) when repeat mode is set to {@code Player.REPEAT_MODE_ONE}.
* @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(boolean isRepeatOneAtomic, List<MediaSource> mediaSources) {
for (MediaSource mediaSource : mediaSources) {
Assertions.checkNotNull(mediaSource);
}
this.mediaSources = mediaSources;
this.isRepeatOneAtomic = isRepeatOneAtomic;
timelines = new SparseArray<Timeline>();
manifests = new SparseArray<Object>();
sourceIndexByMediaPeriod = new HashMap<>();
duplicateFlags = buildDuplicateFlags(mediaSources);
}
@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
this.listener = listener;
for (int i = 0; i < mediaSources.size(); i++) {
if (!duplicateFlags.get(i)) {
final int index = i;
mediaSources.get(i).prepareSource(player, false, 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)) {
mediaSources.get(i).maybeThrowSourceInfoRefreshError();
}
}
}
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
int sourceIndex = timeline.getChildIndexByPeriodIndex(id.periodIndex);
MediaPeriodId periodIdInSource =
new MediaPeriodId(id.periodIndex - timeline.getFirstPeriodIndexByChildIndex(sourceIndex));
MediaPeriod mediaPeriod = mediaSources.get(sourceIndex).createPeriod(periodIdInSource, allocator);
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)) {
mediaSources.get(i).releaseSource();
}
}
}
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(timelines, isRepeatOneAtomic);
listener.onSourceInfoRefreshed(timeline, new ArrayList(asList(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 source = mediaSources.get(i);
if (!sources.containsKey(source)) {
sources.put(source, null);
duplicateFlags.append(i,false);
} else {
duplicateFlags.append(i,true);
}
}
return duplicateFlags;
}
/**
* A {@link Timeline} that is the concatenation of one or more {@link Timeline}s.
*/
public static final class DynamicTimeline extends AbstractConcatenatedTimeline {
private final SparseArray<Timeline> timelines;
private final int[] sourcePeriodOffsets;
private final int[] sourceWindowOffsets;
private final boolean isRepeatOneAtomic;
public DynamicTimeline(SparseArray<Timeline> timelines, boolean isRepeatOneAtomic) {
super(timelines.size());
int[] sourcePeriodOffsets = new int[timelines.size()];
int[] sourceWindowOffsets = new int[timelines.size()];
long periodCount = 0;
int windowCount = 0;
for (int i = 0; i < timelines.size(); i++) {
Timeline timeline = timelines.get(i);
periodCount += timeline.getPeriodCount();
Assertions.checkState(periodCount <= Integer.MAX_VALUE,
"ConcatenatingMediaSource children contain too many periods");
sourcePeriodOffsets[i] = (int) periodCount;
windowCount += timeline.getWindowCount();
sourceWindowOffsets[i] = windowCount;
}
this.timelines = timelines;
this.sourcePeriodOffsets = sourcePeriodOffsets;
this.sourceWindowOffsets = sourceWindowOffsets;
this.isRepeatOneAtomic = isRepeatOneAtomic;
}
@Override
public int getWindowCount() {
return sourceWindowOffsets[sourceWindowOffsets.length - 1];
}
@Override
public int getPeriodCount() {
return sourcePeriodOffsets[sourcePeriodOffsets.length - 1];
}
@Override
public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) {
if (isRepeatOneAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
repeatMode = Player.REPEAT_MODE_ALL;
}
return super.getNextWindowIndex(windowIndex, repeatMode);
}
@Override
public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) {
if (isRepeatOneAtomic && repeatMode == Player.REPEAT_MODE_ONE) {
repeatMode = Player.REPEAT_MODE_ALL;
}
return super.getPreviousWindowIndex(windowIndex, repeatMode);
}
@Override
public int getChildIndexByPeriodIndex(int periodIndex) {
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1;
}
@Override
protected int getChildIndexByWindowIndex(int windowIndex) {
return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1;
}
@Override
protected int getChildIndexByChildUid(Object childUid) {
if (!(childUid instanceof Integer)) {
return C.INDEX_UNSET;
}
return (Integer) childUid;
}
@Override
protected Timeline getTimelineByChildIndex(int childIndex) {
return timelines.get(childIndex);
}
@Override
public int getFirstPeriodIndexByChildIndex(int childIndex) {
return childIndex == 0 ? 0 : sourcePeriodOffsets[childIndex - 1];
}
@Override
protected int getFirstWindowIndexByChildIndex(int childIndex) {
return childIndex == 0 ? 0 : sourceWindowOffsets[childIndex - 1];
}
@Override
protected Object getChildUidByChildIndex(int childIndex) {
return childIndex;
}
}
}
Чтобы проверить загрузку файла и установить загруженный прослушиватель, вы можете использовать BroadcastReceiver
. Подробный пример настройки BroadcastReceiver приведен ниже.