From 0c81022aaac0c6937de88af9f265f6ff9c752cd1 Mon Sep 17 00:00:00 2001 From: bachinger Date: Thu, 28 May 2020 17:51:37 +0100 Subject: [PATCH] Make HlsMediaSource add the media item to the timeline PiperOrigin-RevId: 313605791 --- .../exoplayer2/source/hls/HlsMediaSource.java | 60 +++++--- .../source/hls/HlsMediaSourceTest.java | 135 ++++++++++++++++++ 2 files changed, 173 insertions(+), 22 deletions(-) create mode 100644 library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 39fa99c498..fcf4386492 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.hls; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.net.Uri; @@ -49,7 +50,7 @@ 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 com.google.android.exoplayer2.util.MimeTypes; import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -121,7 +122,7 @@ public final class HlsMediaSource extends BaseMediaSource * manifests, segments and keys. */ public Factory(HlsDataSourceFactory hlsDataSourceFactory) { - this.hlsDataSourceFactory = Assertions.checkNotNull(hlsDataSourceFactory); + this.hlsDataSourceFactory = checkNotNull(hlsDataSourceFactory); playlistParserFactory = new DefaultHlsPlaylistParserFactory(); playlistTrackerFactory = DefaultHlsPlaylistTracker.FACTORY; extractorFactory = HlsExtractorFactory.DEFAULT; @@ -332,7 +333,8 @@ public final class HlsMediaSource extends BaseMediaSource @Deprecated @Override public HlsMediaSource createMediaSource(Uri uri) { - return createMediaSource(new MediaItem.Builder().setUri(uri).build()); + return createMediaSource( + new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_M3U8).build()); } /** @@ -344,18 +346,29 @@ public final class HlsMediaSource extends BaseMediaSource */ @Override public HlsMediaSource createMediaSource(MediaItem mediaItem) { - Assertions.checkNotNull(mediaItem.playbackProperties); + checkNotNull(mediaItem.playbackProperties); HlsPlaylistParserFactory playlistParserFactory = this.playlistParserFactory; List streamKeys = - !mediaItem.playbackProperties.streamKeys.isEmpty() - ? mediaItem.playbackProperties.streamKeys - : this.streamKeys; + mediaItem.playbackProperties.streamKeys.isEmpty() + ? this.streamKeys + : mediaItem.playbackProperties.streamKeys; if (!streamKeys.isEmpty()) { playlistParserFactory = new FilteringHlsPlaylistParserFactory(playlistParserFactory, 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 HlsMediaSource( - mediaItem.playbackProperties.uri, + mediaItem, hlsDataSourceFactory, extractorFactory, compositeSequenceableLoaderFactory, @@ -365,8 +378,7 @@ public final class HlsMediaSource extends BaseMediaSource hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory), allowChunklessPreparation, metadataType, - useSessionKeys, - mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag); + useSessionKeys); } @Override @@ -376,7 +388,8 @@ public final class HlsMediaSource extends BaseMediaSource } private final HlsExtractorFactory extractorFactory; - private final Uri manifestUri; + private final MediaItem mediaItem; + private final MediaItem.PlaybackProperties playbackProperties; private final HlsDataSourceFactory dataSourceFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private final DrmSessionManager drmSessionManager; @@ -385,12 +398,11 @@ public final class HlsMediaSource extends BaseMediaSource private final @MetadataType int metadataType; private final boolean useSessionKeys; private final HlsPlaylistTracker playlistTracker; - @Nullable private final Object tag; @Nullable private TransferListener mediaTransferListener; private HlsMediaSource( - Uri manifestUri, + MediaItem mediaItem, HlsDataSourceFactory dataSourceFactory, HlsExtractorFactory extractorFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, @@ -399,9 +411,9 @@ public final class HlsMediaSource extends BaseMediaSource HlsPlaylistTracker playlistTracker, boolean allowChunklessPreparation, @MetadataType int metadataType, - boolean useSessionKeys, - @Nullable Object tag) { - this.manifestUri = manifestUri; + boolean useSessionKeys) { + this.playbackProperties = checkNotNull(mediaItem.playbackProperties); + this.mediaItem = mediaItem; this.dataSourceFactory = dataSourceFactory; this.extractorFactory = extractorFactory; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; @@ -411,13 +423,17 @@ public final class HlsMediaSource extends BaseMediaSource this.allowChunklessPreparation = allowChunklessPreparation; this.metadataType = metadataType; this.useSessionKeys = useSessionKeys; - this.tag = tag; } @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 @@ -425,7 +441,7 @@ public final class HlsMediaSource extends BaseMediaSource this.mediaTransferListener = mediaTransferListener; drmSessionManager.prepare(); EventDispatcher eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null); - playlistTracker.start(manifestUri, eventDispatcher, /* listener= */ this); + playlistTracker.start(playbackProperties.uri, eventDispatcher, /* listener= */ this); } @Override @@ -477,7 +493,7 @@ public final class HlsMediaSource extends BaseMediaSource long windowDefaultStartPositionUs = playlist.startOffsetUs; // masterPlaylist is non-null because the first playlist has been fetched by now. HlsManifest manifest = - new HlsManifest(Assertions.checkNotNull(playlistTracker.getMasterPlaylist()), playlist); + new HlsManifest(checkNotNull(playlistTracker.getMasterPlaylist()), playlist); if (playlistTracker.isLive()) { long offsetFromInitialStartTimeUs = playlist.startTimeUs - playlistTracker.getInitialStartTimeUs(); @@ -511,7 +527,7 @@ public final class HlsMediaSource extends BaseMediaSource /* isDynamic= */ !playlist.hasEndTag, /* isLive= */ true, manifest, - tag); + mediaItem); } else /* not live */ { if (windowDefaultStartPositionUs == C.TIME_UNSET) { windowDefaultStartPositionUs = 0; @@ -529,7 +545,7 @@ public final class HlsMediaSource extends BaseMediaSource /* isDynamic= */ false, /* isLive= */ false, manifest, - tag); + mediaItem); } refreshSourceInfo(timeline); } diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java new file mode 100644 index 0000000000..418e51dd33 --- /dev/null +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java @@ -0,0 +1,135 @@ +/* + * Copyright 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.hls; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + +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.DataSource; +import java.util.Collections; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit test for {@link DashMediaSource}. */ +@RunWith(AndroidJUnit4.class) +public class HlsMediaSourceTest { + + // Tests backwards compatibility + @SuppressWarnings("deprecation") + @Test + public void factorySetTag_nullMediaItemTag_setsMediaItemTag() { + Object tag = new Object(); + MediaItem mediaItem = MediaItem.fromUri("http://www.google.com"); + HlsMediaSource.Factory factory = + new HlsMediaSource.Factory(mock(DataSource.Factory.class)).setTag(tag); + + MediaItem dashMediaItem = factory.createMediaSource(mediaItem).getMediaItem(); + + assertThat(dashMediaItem.playbackProperties).isNotNull(); + assertThat(dashMediaItem.playbackProperties.uri).isEqualTo(mediaItem.playbackProperties.uri); + assertThat(dashMediaItem.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(); + HlsMediaSource.Factory factory = + new HlsMediaSource.Factory(mock(DataSource.Factory.class)).setTag(factoryTag); + + MediaItem dashMediaItem = factory.createMediaSource(mediaItem).getMediaItem(); + + assertThat(dashMediaItem.playbackProperties).isNotNull(); + assertThat(dashMediaItem.playbackProperties.uri).isEqualTo(mediaItem.playbackProperties.uri); + assertThat(dashMediaItem.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"); + HlsMediaSource.Factory factory = + new HlsMediaSource.Factory(mock(DataSource.Factory.class)).setTag(tag); + + @Nullable Object mediaSourceTag = factory.createMediaSource(mediaItem).getTag(); + + assertThat(mediaSourceTag).isEqualTo(tag); + } + + // Tests backwards compatibility + @SuppressWarnings("deprecation") + @Test + public void factoryCreateMediaSource_setsDeprecatedMediaSourceTag() { + Object tag = new Object(); + MediaItem mediaItem = + new MediaItem.Builder().setUri("http://www.google.com").setTag(tag).build(); + HlsMediaSource.Factory factory = + new HlsMediaSource.Factory(mock(DataSource.Factory.class)).setTag(new Object()); + + @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); + HlsMediaSource.Factory factory = + new HlsMediaSource.Factory(mock(DataSource.Factory.class)) + .setStreamKeys(Collections.singletonList(streamKey)); + + MediaItem dashMediaItem = factory.createMediaSource(mediaItem).getMediaItem(); + + assertThat(dashMediaItem.playbackProperties).isNotNull(); + assertThat(dashMediaItem.playbackProperties.uri).isEqualTo(mediaItem.playbackProperties.uri); + assertThat(dashMediaItem.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(); + HlsMediaSource.Factory factory = + new HlsMediaSource.Factory(mock(DataSource.Factory.class)) + .setStreamKeys( + Collections.singletonList(new StreamKey(/* groupIndex= */ 1, /* trackIndex= */ 0))); + + MediaItem dashMediaItem = factory.createMediaSource(mediaItem).getMediaItem(); + + assertThat(dashMediaItem.playbackProperties).isNotNull(); + assertThat(dashMediaItem.playbackProperties.uri).isEqualTo(mediaItem.playbackProperties.uri); + assertThat(dashMediaItem.playbackProperties.streamKeys).containsExactly(mediaItemStreamKey); + } +}