/* * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.source; import android.net.Uri; import androidx.annotation.Nullable; import org.mozilla.thirdparty.com.google.android.exoplayer2.C; import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmSession; import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmSessionManager; import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.Extractor; import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorsFactory; import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.Allocator; import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource; import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.TransferListener; import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions; import java.io.IOException; /** * Provides one period that loads data from a {@link Uri} and extracted using an {@link Extractor}. * *

If the possible input stream container formats are known, pass a factory that instantiates * extractors for them to the constructor. Otherwise, pass a {@link DefaultExtractorsFactory} to use * the default extractors. When reading a new stream, the first {@link Extractor} in the array of * extractors created by the factory that returns {@code true} from {@link Extractor#sniff} will be * used to extract samples from the input stream. * *

Note that the built-in extractor for FLV streams does not support seeking. */ public final class ProgressiveMediaSource extends BaseMediaSource implements ProgressiveMediaPeriod.Listener { /** Factory for {@link ProgressiveMediaSource}s. */ public static final class Factory implements MediaSourceFactory { private final DataSource.Factory dataSourceFactory; private ExtractorsFactory extractorsFactory; @Nullable private String customCacheKey; @Nullable private Object tag; private DrmSessionManager drmSessionManager; private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private int continueLoadingCheckIntervalBytes; private boolean isCreateCalled; /** * Creates a new factory for {@link ProgressiveMediaSource}s, using the extractors provided by * {@link DefaultExtractorsFactory}. * * @param dataSourceFactory A factory for {@link DataSource}s to read the media. */ public Factory(DataSource.Factory dataSourceFactory) { this(dataSourceFactory, new DefaultExtractorsFactory()); } /** * Creates a new factory for {@link ProgressiveMediaSource}s. * * @param dataSourceFactory A factory for {@link DataSource}s to read the media. * @param extractorsFactory A factory for extractors used to extract media from its container. */ public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) { this.dataSourceFactory = dataSourceFactory; this.extractorsFactory = extractorsFactory; drmSessionManager = DrmSessionManager.getDummyDrmSessionManager(); loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); continueLoadingCheckIntervalBytes = DEFAULT_LOADING_CHECK_INTERVAL_BYTES; } /** * Sets the factory for {@link Extractor}s to process the media stream. The default value is an * instance of {@link DefaultExtractorsFactory}. * * @param extractorsFactory A factory for {@link Extractor}s to process the media stream. If the * possible formats are known, pass a factory that instantiates extractors for those * formats. * @return This factory, for convenience. * @throws IllegalStateException If {@link #createMediaSource(Uri)} has already been called. * @deprecated Pass the {@link ExtractorsFactory} via {@link #Factory(DataSource.Factory, * ExtractorsFactory)}. This is necessary so that proguard can treat the default extractors * factory as unused. */ @Deprecated public Factory setExtractorsFactory(ExtractorsFactory extractorsFactory) { Assertions.checkState(!isCreateCalled); this.extractorsFactory = extractorsFactory; return this; } /** * Sets the custom key that uniquely identifies the original stream. Used for cache indexing. * The default value is {@code null}. * * @param customCacheKey A custom key that uniquely identifies the original stream. Used for * cache indexing. * @return This factory, for convenience. * @throws IllegalStateException If {@link #createMediaSource(Uri)} has already been called. */ public Factory setCustomCacheKey(@Nullable String customCacheKey) { Assertions.checkState(!isCreateCalled); this.customCacheKey = customCacheKey; return this; } /** * Sets a tag for the media source which will be published in the {@link * com.google.android.exoplayer2.Timeline} of the source as {@link * com.google.android.exoplayer2.Timeline.Window#tag}. * * @param tag A tag for the media source. * @return This factory, for convenience. * @throws IllegalStateException If {@link #createMediaSource(Uri)} has already been called. */ public Factory setTag(Object tag) { Assertions.checkState(!isCreateCalled); this.tag = tag; return this; } /** * Sets the {@link LoadErrorHandlingPolicy}. The default value is created by calling {@link * DefaultLoadErrorHandlingPolicy#DefaultLoadErrorHandlingPolicy()}. * * @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}. * @return This factory, for convenience. * @throws IllegalStateException If {@link #createMediaSource(Uri)} has already been called. */ public Factory setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) { Assertions.checkState(!isCreateCalled); this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; return this; } /** * Sets the number of bytes that should be loaded between each invocation of {@link * MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. The default value is * {@link #DEFAULT_LOADING_CHECK_INTERVAL_BYTES}. * * @param continueLoadingCheckIntervalBytes The number of bytes that should be loaded between * each invocation of {@link * MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. * @return This factory, for convenience. * @throws IllegalStateException If {@link #createMediaSource(Uri)} has already been called. */ public Factory setContinueLoadingCheckIntervalBytes(int continueLoadingCheckIntervalBytes) { Assertions.checkState(!isCreateCalled); this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; return this; } /** * Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The * default value is {@link DrmSessionManager#DUMMY}. * * @param drmSessionManager The {@link DrmSessionManager}. * @return This factory, for convenience. * @throws IllegalStateException If one of the {@code create} methods has already been called. */ @Override public Factory setDrmSessionManager(DrmSessionManager drmSessionManager) { Assertions.checkState(!isCreateCalled); this.drmSessionManager = drmSessionManager; return this; } /** * Returns a new {@link ProgressiveMediaSource} using the current parameters. * * @param uri The {@link Uri}. * @return The new {@link ProgressiveMediaSource}. */ @Override public ProgressiveMediaSource createMediaSource(Uri uri) { isCreateCalled = true; return new ProgressiveMediaSource( uri, dataSourceFactory, extractorsFactory, drmSessionManager, loadErrorHandlingPolicy, customCacheKey, continueLoadingCheckIntervalBytes, tag); } @Override public int[] getSupportedTypes() { return new int[] {C.TYPE_OTHER}; } } /** * The default number of bytes that should be loaded between each each invocation of {@link * MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. */ public static final int DEFAULT_LOADING_CHECK_INTERVAL_BYTES = 1024 * 1024; private final Uri uri; private final DataSource.Factory dataSourceFactory; private final ExtractorsFactory extractorsFactory; private final DrmSessionManager drmSessionManager; private final LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy; @Nullable private final String customCacheKey; private final int continueLoadingCheckIntervalBytes; @Nullable private final Object tag; private long timelineDurationUs; private boolean timelineIsSeekable; private boolean timelineIsLive; @Nullable private TransferListener transferListener; // TODO: Make private when ExtractorMediaSource is deleted. /* package */ ProgressiveMediaSource( Uri uri, DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory, DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy, @Nullable String customCacheKey, int continueLoadingCheckIntervalBytes, @Nullable Object tag) { this.uri = uri; this.dataSourceFactory = dataSourceFactory; this.extractorsFactory = extractorsFactory; this.drmSessionManager = drmSessionManager; this.loadableLoadErrorHandlingPolicy = loadableLoadErrorHandlingPolicy; this.customCacheKey = customCacheKey; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.timelineDurationUs = C.TIME_UNSET; this.tag = tag; } @Override @Nullable public Object getTag() { return tag; } @Override protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { transferListener = mediaTransferListener; drmSessionManager.prepare(); notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable, timelineIsLive); } @Override public void maybeThrowSourceInfoRefreshError() throws IOException { // Do nothing. } @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { DataSource dataSource = dataSourceFactory.createDataSource(); if (transferListener != null) { dataSource.addTransferListener(transferListener); } return new ProgressiveMediaPeriod( uri, dataSource, extractorsFactory.createExtractors(), drmSessionManager, loadableLoadErrorHandlingPolicy, createEventDispatcher(id), this, allocator, customCacheKey, continueLoadingCheckIntervalBytes); } @Override public void releasePeriod(MediaPeriod mediaPeriod) { ((ProgressiveMediaPeriod) mediaPeriod).release(); } @Override protected void releaseSourceInternal() { drmSessionManager.release(); } // ProgressiveMediaPeriod.Listener implementation. @Override public void onSourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) { // If we already have the duration from a previous source info refresh, use it. durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs; if (timelineDurationUs == durationUs && timelineIsSeekable == isSeekable && timelineIsLive == isLive) { // Suppress no-op source info changes. return; } notifySourceInfoRefreshed(durationUs, isSeekable, isLive); } // Internal methods. private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) { timelineDurationUs = durationUs; timelineIsSeekable = isSeekable; timelineIsLive = isLive; // TODO: Split up isDynamic into multiple fields to indicate which values may change. Then // indicate that the duration may change until it's known. See [internal: b/69703223]. refreshSourceInfo( new SinglePeriodTimeline( timelineDurationUs, timelineIsSeekable, /* isDynamic= */ false, /* isLive= */ timelineIsLive, /* manifest= */ null, tag)); } }