diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 24e835ee90..f75be283e2 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.source.smoothstreaming; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + import android.net.Uri; import android.os.Handler; import android.os.SystemClock; @@ -54,6 +56,7 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; @@ -104,7 +107,7 @@ public final class SsMediaSource extends BaseMediaSource public Factory( SsChunkSource.Factory chunkSourceFactory, @Nullable DataSource.Factory manifestDataSourceFactory) { - this.chunkSourceFactory = Assertions.checkNotNull(chunkSourceFactory); + this.chunkSourceFactory = checkNotNull(chunkSourceFactory); this.manifestDataSourceFactory = manifestDataSourceFactory; drmSessionManager = DrmSessionManager.getDummyDrmSessionManager(); loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); @@ -237,21 +240,47 @@ public final class SsMediaSource extends BaseMediaSource * @throws IllegalArgumentException If {@link SsManifest#isLive} is true. */ public SsMediaSource createMediaSource(SsManifest manifest) { + return createMediaSource(manifest, MediaItem.fromUri(Uri.EMPTY)); + } + + /** + * Returns a new {@link SsMediaSource} using the current parameters and the specified sideloaded + * manifest. + * + * @param manifest The manifest. {@link SsManifest#isLive} must be false. + * @param mediaItem The {@link MediaItem} to be included in the timeline. + * @return The new {@link SsMediaSource}. + * @throws IllegalArgumentException If {@link SsManifest#isLive} is true. + */ + public SsMediaSource createMediaSource(SsManifest manifest, MediaItem mediaItem) { Assertions.checkArgument(!manifest.isLive); + List streamKeys = + mediaItem.playbackProperties != null && !mediaItem.playbackProperties.streamKeys.isEmpty() + ? mediaItem.playbackProperties.streamKeys + : this.streamKeys; if (!streamKeys.isEmpty()) { manifest = manifest.copy(streamKeys); } + boolean hasUri = mediaItem.playbackProperties != null; + boolean hasTag = hasUri && mediaItem.playbackProperties.tag != null; + mediaItem = + mediaItem + .buildUpon() + .setMimeType(MimeTypes.APPLICATION_SS) + .setUri(hasUri ? mediaItem.playbackProperties.uri : Uri.EMPTY) + .setTag(hasTag ? mediaItem.playbackProperties.tag : tag) + .setStreamKeys(streamKeys) + .build(); return new SsMediaSource( + mediaItem, manifest, - /* manifestUri= */ null, /* manifestDataSourceFactory= */ null, /* manifestParser= */ null, chunkSourceFactory, compositeSequenceableLoaderFactory, drmSessionManager, loadErrorHandlingPolicy, - livePresentationDelayMs, - tag); + livePresentationDelayMs); } /** @@ -274,6 +303,7 @@ public final class SsMediaSource extends BaseMediaSource * @deprecated Use {@link #createMediaSource(Uri)} and {@link #addEventListener(Handler, * MediaSourceEventListener)} instead. */ + @SuppressWarnings("deprecation") @Deprecated public SsMediaSource createMediaSource( Uri manifestUri, @@ -295,7 +325,7 @@ public final class SsMediaSource extends BaseMediaSource */ @Override public SsMediaSource createMediaSource(MediaItem mediaItem) { - Assertions.checkNotNull(mediaItem.playbackProperties); + checkNotNull(mediaItem.playbackProperties); @Nullable ParsingLoadable.Parser manifestParser = this.manifestParser; if (manifestParser == null) { manifestParser = new SsManifestParser(); @@ -307,17 +337,27 @@ public final class SsMediaSource extends BaseMediaSource if (!streamKeys.isEmpty()) { manifestParser = new FilteringManifestParser<>(manifestParser, streamKeys); } + + boolean needsTag = mediaItem.playbackProperties.tag == null && tag != null; + boolean needsStreamKeys = + mediaItem.playbackProperties.streamKeys.isEmpty() && !streamKeys.isEmpty(); + if (needsTag && needsStreamKeys) { + mediaItem = mediaItem.buildUpon().setTag(tag).setStreamKeys(streamKeys).build(); + } else if (needsTag) { + mediaItem = mediaItem.buildUpon().setTag(tag).build(); + } else if (needsStreamKeys) { + mediaItem = mediaItem.buildUpon().setStreamKeys(streamKeys).build(); + } return new SsMediaSource( + mediaItem, /* manifest= */ null, - mediaItem.playbackProperties.uri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, compositeSequenceableLoaderFactory, drmSessionManager, loadErrorHandlingPolicy, - livePresentationDelayMs, - mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag); + livePresentationDelayMs); } @Override @@ -343,6 +383,8 @@ public final class SsMediaSource extends BaseMediaSource private final boolean sideloadedManifest; private final Uri manifestUri; + private final MediaItem.PlaybackProperties playbackProperties; + private final MediaItem mediaItem; private final DataSource.Factory manifestDataSourceFactory; private final SsChunkSource.Factory chunkSourceFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; @@ -352,7 +394,6 @@ public final class SsMediaSource extends BaseMediaSource private final EventDispatcher manifestEventDispatcher; private final ParsingLoadable.Parser manifestParser; private final ArrayList mediaPeriods; - @Nullable private final Object tag; private DataSource manifestDataSource; private Loader manifestLoader; @@ -406,16 +447,15 @@ public final class SsMediaSource extends BaseMediaSource @Nullable Handler eventHandler, @Nullable MediaSourceEventListener eventListener) { this( + new MediaItem.Builder().setUri(Uri.EMPTY).setMimeType(MimeTypes.APPLICATION_SS).build(), manifest, - /* manifestUri= */ null, /* manifestDataSourceFactory= */ null, /* manifestParser= */ null, chunkSourceFactory, new DefaultCompositeSequenceableLoaderFactory(), DrmSessionManager.getDummyDrmSessionManager(), new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount), - DEFAULT_LIVE_PRESENTATION_DELAY_MS, - /* tag= */ null); + DEFAULT_LIVE_PRESENTATION_DELAY_MS); if (eventHandler != null && eventListener != null) { addEventListener(eventHandler, eventListener); } @@ -507,35 +547,38 @@ public final class SsMediaSource extends BaseMediaSource @Nullable Handler eventHandler, @Nullable MediaSourceEventListener eventListener) { this( + new MediaItem.Builder().setUri(manifestUri).setMimeType(MimeTypes.APPLICATION_SS).build(), /* manifest= */ null, - manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, new DefaultCompositeSequenceableLoaderFactory(), DrmSessionManager.getDummyDrmSessionManager(), new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount), - livePresentationDelayMs, - /* tag= */ null); + livePresentationDelayMs); if (eventHandler != null && eventListener != null) { addEventListener(eventHandler, eventListener); } } private SsMediaSource( + MediaItem mediaItem, @Nullable SsManifest manifest, - @Nullable Uri manifestUri, @Nullable DataSource.Factory manifestDataSourceFactory, @Nullable ParsingLoadable.Parser manifestParser, SsChunkSource.Factory chunkSourceFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadErrorHandlingPolicy, - long livePresentationDelayMs, - @Nullable Object tag) { + long livePresentationDelayMs) { Assertions.checkState(manifest == null || !manifest.isLive); + this.mediaItem = mediaItem; + playbackProperties = checkNotNull(mediaItem.playbackProperties); this.manifest = manifest; - this.manifestUri = manifestUri == null ? null : SsUtil.fixManifestUri(manifestUri); + this.manifestUri = + playbackProperties.uri.equals(Uri.EMPTY) + ? null + : SsUtil.fixManifestUri(playbackProperties.uri); this.manifestDataSourceFactory = manifestDataSourceFactory; this.manifestParser = manifestParser; this.chunkSourceFactory = chunkSourceFactory; @@ -544,7 +587,6 @@ public final class SsMediaSource extends BaseMediaSource this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.livePresentationDelayMs = livePresentationDelayMs; this.manifestEventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null); - this.tag = tag; sideloadedManifest = manifest != null; mediaPeriods = new ArrayList<>(); } @@ -554,7 +596,12 @@ public final class SsMediaSource extends BaseMediaSource @Override @Nullable public Object getTag() { - return tag; + return playbackProperties.tag; + } + + // TODO(bachinger): add @Override annotation once the method is defined by MediaSource. + public MediaItem getMediaItem() { + return mediaItem; } @Override @@ -721,7 +768,7 @@ public final class SsMediaSource extends BaseMediaSource /* isDynamic= */ manifest.isLive, /* isLive= */ manifest.isLive, manifest, - tag); + mediaItem); } else if (manifest.isLive) { if (manifest.dvrWindowLengthUs != C.TIME_UNSET && manifest.dvrWindowLengthUs > 0) { startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); @@ -744,7 +791,7 @@ public final class SsMediaSource extends BaseMediaSource /* isDynamic= */ true, /* isLive= */ true, manifest, - tag); + mediaItem); } else { long durationUs = manifest.durationUs != C.TIME_UNSET ? manifest.durationUs : endTimeUs - startTimeUs; @@ -758,7 +805,7 @@ public final class SsMediaSource extends BaseMediaSource /* isDynamic= */ false, /* isLive= */ false, manifest, - tag); + mediaItem); } refreshSourceInfo(timeline); } diff --git a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSourceTest.java b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSourceTest.java new file mode 100644 index 0000000000..39b1161af7 --- /dev/null +++ b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSourceTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 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.smoothstreaming; + +import static com.google.android.exoplayer2.util.Util.castNonNull; +import static com.google.common.truth.Truth.assertThat; + +import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.offline.StreamKey; +import com.google.android.exoplayer2.upstream.FileDataSource; +import java.util.Collections; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link SsMediaSource}. */ +@RunWith(AndroidJUnit4.class) +public class SsMediaSourceTest { + + // Tests backwards compatibility + @SuppressWarnings("deprecation") + @Test + public void factorySetTag_nullMediaItemTag_setsMediaItemTag() { + Object tag = new Object(); + MediaItem mediaItem = MediaItem.fromUri("http://www.google.com"); + SsMediaSource.Factory factory = + new SsMediaSource.Factory(new FileDataSource.Factory()).setTag(tag); + + MediaItem ssMediaItem = factory.createMediaSource(mediaItem).getMediaItem(); + + assertThat(ssMediaItem.playbackProperties).isNotNull(); + assertThat(ssMediaItem.playbackProperties.uri) + .isEqualTo(castNonNull(mediaItem.playbackProperties).uri); + assertThat(ssMediaItem.playbackProperties.tag).isEqualTo(tag); + } + + // Tests backwards compatibility + @SuppressWarnings("deprecation") + @Test + public void factorySetTag_nonNullMediaItemTag_doesNotOverrideMediaItemTag() { + Object factoryTag = new Object(); + Object mediaItemTag = new Object(); + MediaItem mediaItem = + new MediaItem.Builder().setUri("http://www.google.com").setTag(mediaItemTag).build(); + SsMediaSource.Factory factory = + new SsMediaSource.Factory(new FileDataSource.Factory()).setTag(factoryTag); + + MediaItem ssMediaItem = factory.createMediaSource(mediaItem).getMediaItem(); + + assertThat(ssMediaItem.playbackProperties).isNotNull(); + assertThat(ssMediaItem.playbackProperties.uri) + .isEqualTo(castNonNull(mediaItem.playbackProperties).uri); + assertThat(ssMediaItem.playbackProperties.tag).isEqualTo(mediaItemTag); + } + + // Tests backwards compatibility + @SuppressWarnings("deprecation") + @Test + public void factorySetTag_setsDeprecatedMediaSourceTag() { + Object tag = new Object(); + MediaItem mediaItem = MediaItem.fromUri("http://www.google.com"); + SsMediaSource.Factory factory = + new SsMediaSource.Factory(new FileDataSource.Factory()).setTag(tag); + + @Nullable Object mediaSourceTag = factory.createMediaSource(mediaItem).getTag(); + + assertThat(mediaSourceTag).isEqualTo(tag); + } + + // Tests backwards compatibility + @Test + public void factoryCreateMediaSource_setsDeprecatedMediaSourceTag() { + Object tag = new Object(); + MediaItem mediaItem = + new MediaItem.Builder().setUri("http://www.google.com").setTag(tag).build(); + SsMediaSource.Factory factory = new SsMediaSource.Factory(new FileDataSource.Factory()); + + @Nullable Object mediaSourceTag = factory.createMediaSource(mediaItem).getTag(); + + assertThat(mediaSourceTag).isEqualTo(tag); + } + + // Tests backwards compatibility + @SuppressWarnings("deprecation") + @Test + public void factorySetStreamKeys_emptyMediaItemStreamKeys_setsMediaItemStreamKeys() { + MediaItem mediaItem = MediaItem.fromUri("http://www.google.com"); + StreamKey streamKey = new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 1); + SsMediaSource.Factory factory = + new SsMediaSource.Factory(new FileDataSource.Factory()) + .setStreamKeys(Collections.singletonList(streamKey)); + + MediaItem ssMediaItem = factory.createMediaSource(mediaItem).getMediaItem(); + + assertThat(ssMediaItem.playbackProperties).isNotNull(); + assertThat(ssMediaItem.playbackProperties.uri) + .isEqualTo(castNonNull(mediaItem.playbackProperties).uri); + assertThat(ssMediaItem.playbackProperties.streamKeys).containsExactly(streamKey); + } + + // Tests backwards compatibility + @SuppressWarnings("deprecation") + @Test + public void factorySetStreamKeys_withMediaItemStreamKeys_doesNotOverrideMediaItemStreamKeys() { + StreamKey mediaItemStreamKey = new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 1); + MediaItem mediaItem = + new MediaItem.Builder() + .setUri("http://www.google.com") + .setStreamKeys(Collections.singletonList(mediaItemStreamKey)) + .build(); + SsMediaSource.Factory factory = + new SsMediaSource.Factory(new FileDataSource.Factory()) + .setStreamKeys( + Collections.singletonList(new StreamKey(/* groupIndex= */ 1, /* trackIndex= */ 0))); + + MediaItem ssMediaItem = factory.createMediaSource(mediaItem).getMediaItem(); + + assertThat(ssMediaItem.playbackProperties).isNotNull(); + assertThat(ssMediaItem.playbackProperties.uri) + .isEqualTo(castNonNull(mediaItem.playbackProperties).uri); + assertThat(ssMediaItem.playbackProperties.streamKeys).containsExactly(mediaItemStreamKey); + } +}