diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6c72dae654..65d4e9696e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,7 @@ ### dev-v2 (not yet released) ### +* `ExtractorMediaSource` renamed to `ProgressiveMediaSource`. * HLS: * Parse CHANNELS attribute from EXT-X-MEDIA. * Support for playing spherical videos on Daydream. diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java index 563efea11f..1db68ca08d 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java @@ -35,8 +35,8 @@ import com.google.android.exoplayer2.ext.cast.CastPlayer; import com.google.android.exoplayer2.ext.cast.MediaItem; import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; -import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; @@ -373,7 +373,7 @@ import java.util.ArrayList; case DemoUtil.MIME_TYPE_HLS: return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); case DemoUtil.MIME_TYPE_VIDEO_MP4: - return new ExtractorMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); + return new ProgressiveMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); default: { throw new IllegalStateException("Unsupported type: " + item.mimeType); } diff --git a/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java b/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java index d67c4549d8..740c1a0af6 100644 --- a/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java +++ b/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java @@ -23,8 +23,8 @@ import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; -import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource; @@ -125,7 +125,7 @@ import com.google.android.exoplayer2.util.Util; case C.TYPE_HLS: return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri); case C.TYPE_OTHER: - return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri); + return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri); default: throw new IllegalStateException("Unsupported type: " + type); } diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 582638b460..2d4efd7f3d 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -51,8 +51,8 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryExcep import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; -import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsMediaSource; @@ -483,7 +483,7 @@ public class PlayerActivity extends Activity .setStreamKeys(offlineStreamKeys) .createMediaSource(uri); case C.TYPE_OTHER: - return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri); + return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri); default: { throw new IllegalStateException("Unsupported type: " + type); } diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index 2efdde4e58..5448919626 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -28,8 +28,8 @@ import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; -import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import org.junit.Before; @@ -86,7 +86,7 @@ public class FlacPlaybackTest { player = ExoPlayerFactory.newInstance(context, new Renderer[] {audioRenderer}, trackSelector); player.addListener(this); MediaSource mediaSource = - new ExtractorMediaSource.Factory( + new ProgressiveMediaSource.Factory( new DefaultDataSourceFactory(context, "ExoPlayerExtFlacTest")) .setExtractorsFactory(MatroskaExtractor.FACTORY) .createMediaSource(uri); diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java index 5ad864c597..6a254c8230 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java @@ -28,8 +28,8 @@ import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; -import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import org.junit.Before; @@ -86,7 +86,7 @@ public class OpusPlaybackTest { player = ExoPlayerFactory.newInstance(context, new Renderer[] {audioRenderer}, trackSelector); player.addListener(this); MediaSource mediaSource = - new ExtractorMediaSource.Factory( + new ProgressiveMediaSource.Factory( new DefaultDataSourceFactory(context, "ExoPlayerExtOpusTest")) .setExtractorsFactory(MatroskaExtractor.FACTORY) .createMediaSource(uri); diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index 1de461e374..a36b578588 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -29,8 +29,8 @@ import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; -import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Log; @@ -119,7 +119,7 @@ public class VpxPlaybackTest { player = ExoPlayerFactory.newInstance(context, new Renderer[] {videoRenderer}, trackSelector); player.addListener(this); MediaSource mediaSource = - new ExtractorMediaSource.Factory( + new ProgressiveMediaSource.Factory( new DefaultDataSourceFactory(context, "ExoPlayerExtVp9Test")) .setExtractorsFactory(MatroskaExtractor.FACTORY) .createMediaSource(uri); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 5ba2394c3f..db168d9c29 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -21,10 +21,10 @@ import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.source.ClippingMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; -import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.LoopingMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.SingleSampleMediaSource; import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -48,7 +48,7 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; *
  • A {@link MediaSource} that defines the media to be played, loads the media, and from * which the loaded media can be read. A MediaSource is injected via {@link * #prepare(MediaSource)} at the start of playback. The library modules provide default - * implementations for regular media files ({@link ExtractorMediaSource}), DASH + * implementations for progressive media files ({@link ProgressiveMediaSource}), DASH * (DashMediaSource), SmoothStreaming (SsMediaSource) and HLS (HlsMediaSource), an * implementation for loading single media samples ({@link SingleSampleMediaSource}) that's * most often used for side-loaded subtitle files, and implementations for building more diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java index 5403f9f33b..5e0960aaf0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java @@ -20,6 +20,7 @@ import android.os.Handler; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorsFactory; @@ -32,25 +33,13 @@ import com.google.android.exoplayer2.upstream.TransferListener; import 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 extractors for AAC, MPEG PS/TS and FLV streams do not support seeking. - */ +/** @deprecated Use {@link ProgressiveMediaSource} instead. */ +@Deprecated +@SuppressWarnings("deprecation") public final class ExtractorMediaSource extends BaseMediaSource - implements ExtractorMediaPeriod.Listener { + implements MediaSource.SourceInfoRefreshListener { - /** - * Listener of {@link ExtractorMediaSource} events. - * - * @deprecated Use {@link MediaSourceEventListener}. - */ + /** @deprecated Use {@link MediaSourceEventListener} instead. */ @Deprecated public interface EventListener { @@ -70,7 +59,8 @@ public final class ExtractorMediaSource extends BaseMediaSource } - /** Factory for {@link ExtractorMediaSource}s. */ + /** Use {@link ProgressiveMediaSource.Factory} instead. */ + @Deprecated public static final class Factory implements AdsMediaSource.MediaSourceFactory { private final DataSource.Factory dataSourceFactory; @@ -232,23 +222,11 @@ public final class ExtractorMediaSource extends BaseMediaSource } } - /** - * 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; + @Deprecated + public static final int DEFAULT_LOADING_CHECK_INTERVAL_BYTES = + ProgressiveMediaSource.DEFAULT_LOADING_CHECK_INTERVAL_BYTES; - private final Uri uri; - private final DataSource.Factory dataSourceFactory; - private final ExtractorsFactory extractorsFactory; - private final LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy; - private final String customCacheKey; - private final int continueLoadingCheckIntervalBytes; - private final @Nullable Object tag; - - private long timelineDurationUs; - private boolean timelineIsSeekable; - private @Nullable TransferListener transferListener; + private final ProgressiveMediaSource progressiveMediaSource; /** * @param uri The {@link Uri} of the media stream. @@ -261,7 +239,6 @@ public final class ExtractorMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated - @SuppressWarnings("deprecation") public ExtractorMediaSource( Uri uri, DataSource.Factory dataSourceFactory, @@ -284,7 +261,6 @@ public final class ExtractorMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated - @SuppressWarnings("deprecation") public ExtractorMediaSource( Uri uri, DataSource.Factory dataSourceFactory, @@ -317,7 +293,6 @@ public final class ExtractorMediaSource extends BaseMediaSource * @deprecated Use {@link Factory} instead. */ @Deprecated - @SuppressWarnings("deprecation") public ExtractorMediaSource( Uri uri, DataSource.Factory dataSourceFactory, @@ -347,93 +322,57 @@ public final class ExtractorMediaSource extends BaseMediaSource @Nullable String customCacheKey, int continueLoadingCheckIntervalBytes, @Nullable Object tag) { - this.uri = uri; - this.dataSourceFactory = dataSourceFactory; - this.extractorsFactory = extractorsFactory; - this.loadableLoadErrorHandlingPolicy = loadableLoadErrorHandlingPolicy; - this.customCacheKey = customCacheKey; - this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; - this.timelineDurationUs = C.TIME_UNSET; - this.tag = tag; + progressiveMediaSource = + new ProgressiveMediaSource( + uri, + dataSourceFactory, + extractorsFactory, + loadableLoadErrorHandlingPolicy, + customCacheKey, + continueLoadingCheckIntervalBytes, + tag); } @Override @Nullable public Object getTag() { - return tag; + return progressiveMediaSource.getTag(); } @Override public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { - transferListener = mediaTransferListener; - notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable); + progressiveMediaSource.prepareSource(/* listener= */ this, mediaTransferListener); } @Override public void maybeThrowSourceInfoRefreshError() throws IOException { - // Do nothing. + progressiveMediaSource.maybeThrowSourceInfoRefreshError(); } @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { - DataSource dataSource = dataSourceFactory.createDataSource(); - if (transferListener != null) { - dataSource.addTransferListener(transferListener); - } - return new ExtractorMediaPeriod( - uri, - dataSource, - extractorsFactory.createExtractors(), - loadableLoadErrorHandlingPolicy, - createEventDispatcher(id), - this, - allocator, - customCacheKey, - continueLoadingCheckIntervalBytes); + return progressiveMediaSource.createPeriod(id, allocator, startPositionUs); } @Override public void releasePeriod(MediaPeriod mediaPeriod) { - ((ExtractorMediaPeriod) mediaPeriod).release(); + progressiveMediaSource.releasePeriod(mediaPeriod); } @Override public void releaseSourceInternal() { - // Do nothing. + progressiveMediaSource.releaseSource(/* listener= */ this); } - // ExtractorMediaPeriod.Listener implementation. - @Override - public void onSourceInfoRefreshed(long durationUs, boolean isSeekable) { - // 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) { - // Suppress no-op source info changes. - return; - } - notifySourceInfoRefreshed(durationUs, isSeekable); + public void onSourceInfoRefreshed( + MediaSource source, Timeline timeline, @Nullable Object manifest) { + refreshSourceInfo(timeline, manifest); } - // Internal methods. - - private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable) { - timelineDurationUs = durationUs; - timelineIsSeekable = isSeekable; - // TODO: Make timeline dynamic until its duration is known. This is non-trivial. See b/69703223. - refreshSourceInfo( - new SinglePeriodTimeline( - timelineDurationUs, timelineIsSeekable, /* isDynamic= */ false, tag), - /* manifest= */ null); - } - - /** - * Wraps a deprecated {@link EventListener}, invoking its callback from the equivalent callback in - * {@link MediaSourceEventListener}. - */ @Deprecated - @SuppressWarnings("deprecation") private static final class EventListenerWrapper extends DefaultMediaSourceEventListener { + private final EventListener eventListener; public EventListenerWrapper(EventListener eventListener) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java similarity index 98% rename from library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java index e842d4f253..41f6e986c6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java @@ -54,12 +54,13 @@ import java.io.IOException; import java.util.Arrays; import org.checkerframework.checker.nullness.compatqual.NullableType; -/** - * A {@link MediaPeriod} that extracts data using an {@link Extractor}. - */ -/* package */ final class ExtractorMediaPeriod implements MediaPeriod, ExtractorOutput, - Loader.Callback, Loader.ReleaseCallback, - UpstreamFormatChangedListener { +/** A {@link MediaPeriod} that extracts data using an {@link Extractor}. */ +/* package */ final class ProgressiveMediaPeriod + implements MediaPeriod, + ExtractorOutput, + Loader.Callback, + Loader.ReleaseCallback, + UpstreamFormatChangedListener { /** * Listener for information about the period. @@ -145,7 +146,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; "nullness:argument.type.incompatible", "nullness:methodref.receiver.bound.invalid" }) - public ExtractorMediaPeriod( + public ProgressiveMediaPeriod( Uri uri, DataSource dataSource, Extractor[] extractors, @@ -163,14 +164,15 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; this.allocator = allocator; this.customCacheKey = customCacheKey; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; - loader = new Loader("Loader:ExtractorMediaPeriod"); + loader = new Loader("Loader:ProgressiveMediaPeriod"); extractorHolder = new ExtractorHolder(extractors); loadCondition = new ConditionVariable(); maybeFinishPrepareRunnable = this::maybeFinishPrepare; onContinueLoadingRequestedRunnable = () -> { if (!released) { - Assertions.checkNotNull(callback).onContinueLoadingRequested(ExtractorMediaPeriod.this); + Assertions.checkNotNull(callback) + .onContinueLoadingRequested(ProgressiveMediaPeriod.this); } }; handler = new Handler(); @@ -852,23 +854,23 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; @Override public boolean isReady() { - return ExtractorMediaPeriod.this.isReady(track); + return ProgressiveMediaPeriod.this.isReady(track); } @Override public void maybeThrowError() throws IOException { - ExtractorMediaPeriod.this.maybeThrowError(); + ProgressiveMediaPeriod.this.maybeThrowError(); } @Override public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) { - return ExtractorMediaPeriod.this.readData(track, formatHolder, buffer, formatRequired); + return ProgressiveMediaPeriod.this.readData(track, formatHolder, buffer, formatRequired); } @Override public int skipData(long positionUs) { - return ExtractorMediaPeriod.this.skipData(track, positionUs); + return ProgressiveMediaPeriod.this.skipData(track, positionUs); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java new file mode 100644 index 0000000000..a44e99b9c4 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java @@ -0,0 +1,281 @@ +/* + * 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.net.Uri; +import android.support.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; +import com.google.android.exoplayer2.extractor.Extractor; +import com.google.android.exoplayer2.extractor.ExtractorsFactory; +import com.google.android.exoplayer2.source.ads.AdsMediaSource; +import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; +import com.google.android.exoplayer2.upstream.TransferListener; +import 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 extractors for AAC, MPEG PS/TS and FLV streams do not support seeking. + */ +public final class ProgressiveMediaSource extends BaseMediaSource + implements ProgressiveMediaPeriod.Listener { + + /** Factory for {@link ProgressiveMediaSource}s. */ + public static final class Factory implements AdsMediaSource.MediaSourceFactory { + + private final DataSource.Factory dataSourceFactory; + + @Nullable private ExtractorsFactory extractorsFactory; + @Nullable private String customCacheKey; + @Nullable private Object tag; + private LoadErrorHandlingPolicy loadErrorHandlingPolicy; + private int continueLoadingCheckIntervalBytes; + private boolean isCreateCalled; + + /** + * Creates a new factory for {@link ProgressiveMediaSource}s. + * + * @param dataSourceFactory A factory for {@link DataSource}s to read the media. + */ + public Factory(DataSource.Factory dataSourceFactory) { + this.dataSourceFactory = dataSourceFactory; + 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 one of the {@code create} methods has already been called. + */ + 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 one of the {@code create} methods has already been called. + */ + public Factory setCustomCacheKey(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 one of the {@code create} methods 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 one of the {@code create} methods 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 one of the {@code create} methods has already been called. + */ + public Factory setContinueLoadingCheckIntervalBytes(int continueLoadingCheckIntervalBytes) { + Assertions.checkState(!isCreateCalled); + this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; + 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; + if (extractorsFactory == null) { + extractorsFactory = new DefaultExtractorsFactory(); + } + return new ProgressiveMediaSource( + uri, + dataSourceFactory, + extractorsFactory, + 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 LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy; + @Nullable private final String customCacheKey; + private final int continueLoadingCheckIntervalBytes; + @Nullable private final Object tag; + + private long timelineDurationUs; + private boolean timelineIsSeekable; + @Nullable private TransferListener transferListener; + + // TODO: Make private when ExtractorMediaSource is deleted. + /* package */ ProgressiveMediaSource( + Uri uri, + DataSource.Factory dataSourceFactory, + ExtractorsFactory extractorsFactory, + LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy, + @Nullable String customCacheKey, + int continueLoadingCheckIntervalBytes, + @Nullable Object tag) { + this.uri = uri; + this.dataSourceFactory = dataSourceFactory; + this.extractorsFactory = extractorsFactory; + 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 + public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { + transferListener = mediaTransferListener; + notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable); + } + + @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(), + loadableLoadErrorHandlingPolicy, + createEventDispatcher(id), + this, + allocator, + customCacheKey, + continueLoadingCheckIntervalBytes); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + ((ProgressiveMediaPeriod) mediaPeriod).release(); + } + + @Override + public void releaseSourceInternal() { + // Do nothing. + } + + // ProgressiveMediaPeriod.Listener implementation. + + @Override + public void onSourceInfoRefreshed(long durationUs, boolean isSeekable) { + // 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) { + // Suppress no-op source info changes. + return; + } + notifySourceInfoRefreshed(durationUs, isSeekable); + } + + // Internal methods. + + private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable) { + timelineDurationUs = durationUs; + timelineIsSeekable = isSeekable; + // TODO: Make timeline dynamic until its duration is known. This is non-trivial. See b/69703223. + refreshSourceInfo( + new SinglePeriodTimeline( + timelineDurationUs, timelineIsSeekable, /* isDynamic= */ false, tag), + /* manifest= */ null); + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index d13fa06434..7611d76260 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -138,12 +138,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource { } /** - * Returns a new {@link ExtractorMediaSource} using the current parameters. + * Returns a new {@link SingleSampleMediaSource} using the current parameters. * * @param uri The {@link Uri}. * @param format The {@link Format} of the media stream. * @param durationUs The duration of the media stream in microseconds. - * @return The new {@link ExtractorMediaSource}. + * @return The new {@link SingleSampleMediaSource}. */ public SingleSampleMediaSource createMediaSource(Uri uri, Format format, long durationUs) { isCreateCalled = true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 4754466235..e80c797eb9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -25,13 +25,13 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.CompositeMediaSource; import com.google.android.exoplayer2.source.DeferredMediaPeriod; -import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.MediaSourceEventListener.LoadEventInfo; import com.google.android.exoplayer2.source.MediaSourceEventListener.MediaLoadData; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; @@ -203,7 +203,7 @@ public final class AdsMediaSource extends CompositeMediaSource { /** * Constructs a new source that inserts ads linearly with the content specified by {@code - * contentMediaSource}. Ad media is loaded using {@link ExtractorMediaSource}. + * contentMediaSource}. Ad media is loaded using {@link ProgressiveMediaSource}. * * @param contentMediaSource The {@link MediaSource} providing the content to play. * @param dataSourceFactory Factory for data sources used to load ad media. @@ -217,7 +217,7 @@ public final class AdsMediaSource extends CompositeMediaSource { ViewGroup adUiViewGroup) { this( contentMediaSource, - new ExtractorMediaSource.Factory(dataSourceFactory), + new ProgressiveMediaSource.Factory(dataSourceFactory), adsLoader, adUiViewGroup, /* eventHandler= */ null, @@ -249,7 +249,7 @@ public final class AdsMediaSource extends CompositeMediaSource { /** * Constructs a new source that inserts ads linearly with the content specified by {@code - * contentMediaSource}. Ad media is loaded using {@link ExtractorMediaSource}. + * contentMediaSource}. Ad media is loaded using {@link ProgressiveMediaSource}. * * @param contentMediaSource The {@link MediaSource} providing the content to play. * @param dataSourceFactory Factory for data sources used to load ad media. @@ -273,7 +273,7 @@ public final class AdsMediaSource extends CompositeMediaSource { @Nullable EventListener eventListener) { this( contentMediaSource, - new ExtractorMediaSource.Factory(dataSourceFactory), + new ProgressiveMediaSource.Factory(dataSourceFactory), adsLoader, adUiViewGroup, eventHandler,