Make HlsMediaSource add the media item to the timeline

PiperOrigin-RevId: 313605791
This commit is contained in:
bachinger 2020-05-28 17:51:37 +01:00 committed by Oliver Woodman
parent 37024ae450
commit 0c81022aaa
2 changed files with 173 additions and 22 deletions

View file

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.hls; package com.google.android.exoplayer2.source.hls;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.net.Uri; 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.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener; 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.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -121,7 +122,7 @@ public final class HlsMediaSource extends BaseMediaSource
* manifests, segments and keys. * manifests, segments and keys.
*/ */
public Factory(HlsDataSourceFactory hlsDataSourceFactory) { public Factory(HlsDataSourceFactory hlsDataSourceFactory) {
this.hlsDataSourceFactory = Assertions.checkNotNull(hlsDataSourceFactory); this.hlsDataSourceFactory = checkNotNull(hlsDataSourceFactory);
playlistParserFactory = new DefaultHlsPlaylistParserFactory(); playlistParserFactory = new DefaultHlsPlaylistParserFactory();
playlistTrackerFactory = DefaultHlsPlaylistTracker.FACTORY; playlistTrackerFactory = DefaultHlsPlaylistTracker.FACTORY;
extractorFactory = HlsExtractorFactory.DEFAULT; extractorFactory = HlsExtractorFactory.DEFAULT;
@ -332,7 +333,8 @@ public final class HlsMediaSource extends BaseMediaSource
@Deprecated @Deprecated
@Override @Override
public HlsMediaSource createMediaSource(Uri uri) { 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 @Override
public HlsMediaSource createMediaSource(MediaItem mediaItem) { public HlsMediaSource createMediaSource(MediaItem mediaItem) {
Assertions.checkNotNull(mediaItem.playbackProperties); checkNotNull(mediaItem.playbackProperties);
HlsPlaylistParserFactory playlistParserFactory = this.playlistParserFactory; HlsPlaylistParserFactory playlistParserFactory = this.playlistParserFactory;
List<StreamKey> streamKeys = List<StreamKey> streamKeys =
!mediaItem.playbackProperties.streamKeys.isEmpty() mediaItem.playbackProperties.streamKeys.isEmpty()
? mediaItem.playbackProperties.streamKeys ? this.streamKeys
: this.streamKeys; : mediaItem.playbackProperties.streamKeys;
if (!streamKeys.isEmpty()) { if (!streamKeys.isEmpty()) {
playlistParserFactory = playlistParserFactory =
new FilteringHlsPlaylistParserFactory(playlistParserFactory, streamKeys); 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( return new HlsMediaSource(
mediaItem.playbackProperties.uri, mediaItem,
hlsDataSourceFactory, hlsDataSourceFactory,
extractorFactory, extractorFactory,
compositeSequenceableLoaderFactory, compositeSequenceableLoaderFactory,
@ -365,8 +378,7 @@ public final class HlsMediaSource extends BaseMediaSource
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory), hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory),
allowChunklessPreparation, allowChunklessPreparation,
metadataType, metadataType,
useSessionKeys, useSessionKeys);
mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag);
} }
@Override @Override
@ -376,7 +388,8 @@ public final class HlsMediaSource extends BaseMediaSource
} }
private final HlsExtractorFactory extractorFactory; private final HlsExtractorFactory extractorFactory;
private final Uri manifestUri; private final MediaItem mediaItem;
private final MediaItem.PlaybackProperties playbackProperties;
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private final DrmSessionManager drmSessionManager; private final DrmSessionManager drmSessionManager;
@ -385,12 +398,11 @@ public final class HlsMediaSource extends BaseMediaSource
private final @MetadataType int metadataType; private final @MetadataType int metadataType;
private final boolean useSessionKeys; private final boolean useSessionKeys;
private final HlsPlaylistTracker playlistTracker; private final HlsPlaylistTracker playlistTracker;
@Nullable private final Object tag;
@Nullable private TransferListener mediaTransferListener; @Nullable private TransferListener mediaTransferListener;
private HlsMediaSource( private HlsMediaSource(
Uri manifestUri, MediaItem mediaItem,
HlsDataSourceFactory dataSourceFactory, HlsDataSourceFactory dataSourceFactory,
HlsExtractorFactory extractorFactory, HlsExtractorFactory extractorFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
@ -399,9 +411,9 @@ public final class HlsMediaSource extends BaseMediaSource
HlsPlaylistTracker playlistTracker, HlsPlaylistTracker playlistTracker,
boolean allowChunklessPreparation, boolean allowChunklessPreparation,
@MetadataType int metadataType, @MetadataType int metadataType,
boolean useSessionKeys, boolean useSessionKeys) {
@Nullable Object tag) { this.playbackProperties = checkNotNull(mediaItem.playbackProperties);
this.manifestUri = manifestUri; this.mediaItem = mediaItem;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.extractorFactory = extractorFactory; this.extractorFactory = extractorFactory;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
@ -411,13 +423,17 @@ public final class HlsMediaSource extends BaseMediaSource
this.allowChunklessPreparation = allowChunklessPreparation; this.allowChunklessPreparation = allowChunklessPreparation;
this.metadataType = metadataType; this.metadataType = metadataType;
this.useSessionKeys = useSessionKeys; this.useSessionKeys = useSessionKeys;
this.tag = tag;
} }
@Override @Override
@Nullable @Nullable
public Object getTag() { 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 @Override
@ -425,7 +441,7 @@ public final class HlsMediaSource extends BaseMediaSource
this.mediaTransferListener = mediaTransferListener; this.mediaTransferListener = mediaTransferListener;
drmSessionManager.prepare(); drmSessionManager.prepare();
EventDispatcher eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null); EventDispatcher eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
playlistTracker.start(manifestUri, eventDispatcher, /* listener= */ this); playlistTracker.start(playbackProperties.uri, eventDispatcher, /* listener= */ this);
} }
@Override @Override
@ -477,7 +493,7 @@ public final class HlsMediaSource extends BaseMediaSource
long windowDefaultStartPositionUs = playlist.startOffsetUs; long windowDefaultStartPositionUs = playlist.startOffsetUs;
// masterPlaylist is non-null because the first playlist has been fetched by now. // masterPlaylist is non-null because the first playlist has been fetched by now.
HlsManifest manifest = HlsManifest manifest =
new HlsManifest(Assertions.checkNotNull(playlistTracker.getMasterPlaylist()), playlist); new HlsManifest(checkNotNull(playlistTracker.getMasterPlaylist()), playlist);
if (playlistTracker.isLive()) { if (playlistTracker.isLive()) {
long offsetFromInitialStartTimeUs = long offsetFromInitialStartTimeUs =
playlist.startTimeUs - playlistTracker.getInitialStartTimeUs(); playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
@ -511,7 +527,7 @@ public final class HlsMediaSource extends BaseMediaSource
/* isDynamic= */ !playlist.hasEndTag, /* isDynamic= */ !playlist.hasEndTag,
/* isLive= */ true, /* isLive= */ true,
manifest, manifest,
tag); mediaItem);
} else /* not live */ { } else /* not live */ {
if (windowDefaultStartPositionUs == C.TIME_UNSET) { if (windowDefaultStartPositionUs == C.TIME_UNSET) {
windowDefaultStartPositionUs = 0; windowDefaultStartPositionUs = 0;
@ -529,7 +545,7 @@ public final class HlsMediaSource extends BaseMediaSource
/* isDynamic= */ false, /* isDynamic= */ false,
/* isLive= */ false, /* isLive= */ false,
manifest, manifest,
tag); mediaItem);
} }
refreshSourceInfo(timeline); refreshSourceInfo(timeline);
} }

View file

@ -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);
}
}