add subtitle to the media item
PiperOrigin-RevId: 303753642
This commit is contained in:
parent
0782c5f247
commit
315ba6f324
@ -33,7 +33,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.PlaybackPreparer;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
@ -47,8 +46,6 @@ import com.google.android.exoplayer2.offline.DownloadRequest;
|
||||
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
||||
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||
import com.google.android.exoplayer2.source.SingleSampleMediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.source.ads.AdsLoader;
|
||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||
@ -412,43 +409,24 @@ public class PlayerActivity extends AppCompatActivity
|
||||
: new UriSample[] {(UriSample) intentAsSample};
|
||||
|
||||
boolean seenAdsTagUri = false;
|
||||
List<MediaSource> mediaSources = new ArrayList<>();
|
||||
for (UriSample sample : samples) {
|
||||
seenAdsTagUri |= sample.adTagUri != null;
|
||||
if (!Util.checkCleartextTrafficPermitted(sample.uri)) {
|
||||
showToast(R.string.error_cleartext_not_permitted);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, sample.uri)) {
|
||||
if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, sample.uri)
|
||||
|| (sample.subtitleInfo != null
|
||||
&& Util.maybeRequestReadExternalStoragePermission(
|
||||
/* activity= */ this, sample.subtitleInfo.uri))) {
|
||||
// The player will be reinitialized if the permission is granted.
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
List<MediaSource> mediaSources = new ArrayList<>();
|
||||
for (UriSample sample : samples) {
|
||||
MediaSource mediaSource = createLeafMediaSource(sample);
|
||||
if (mediaSource == null) {
|
||||
continue;
|
||||
if (mediaSource != null) {
|
||||
mediaSources.add(mediaSource);
|
||||
}
|
||||
Sample.SubtitleInfo subtitleInfo = sample.subtitleInfo;
|
||||
if (subtitleInfo != null) {
|
||||
if (Util.maybeRequestReadExternalStoragePermission(
|
||||
/* activity= */ this, subtitleInfo.uri)) {
|
||||
// The player will be reinitialized if the permission is granted.
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Format subtitleFormat =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(subtitleInfo.mimeType)
|
||||
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
||||
.setLanguage(subtitleInfo.language)
|
||||
.build();
|
||||
MediaSource subtitleMediaSource =
|
||||
new SingleSampleMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(subtitleInfo.uri, subtitleFormat, C.TIME_UNSET);
|
||||
mediaSource = new MergingMediaSource(mediaSource, subtitleMediaSource);
|
||||
}
|
||||
mediaSources.add(mediaSource);
|
||||
}
|
||||
if (seenAdsTagUri && mediaSources.size() == 1) {
|
||||
Uri adTagUri = samples[0].adTagUri;
|
||||
@ -497,6 +475,15 @@ public class PlayerActivity extends AppCompatActivity
|
||||
drmSessionForClearTypes = parameters.drmInfo.drmSessionForClearTypes;
|
||||
drmDataSourceFactory = ((DemoApplication) getApplication()).buildHttpDataSourceFactory();
|
||||
}
|
||||
if (parameters.subtitleInfo != null) {
|
||||
builder.setSubtitles(
|
||||
Collections.singletonList(
|
||||
new MediaItem.Subtitle(
|
||||
parameters.subtitleInfo.uri,
|
||||
parameters.subtitleInfo.mimeType,
|
||||
parameters.subtitleInfo.language,
|
||||
C.SELECTION_FLAG_DEFAULT)));
|
||||
}
|
||||
|
||||
DownloadRequest downloadRequest =
|
||||
((DemoApplication) getApplication())
|
||||
@ -531,7 +518,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||
debugViewHelper = null;
|
||||
player.release();
|
||||
player = null;
|
||||
mediaSources = null;
|
||||
mediaSources = Collections.emptyList();
|
||||
trackSelector = null;
|
||||
}
|
||||
if (adsLoader != null) {
|
||||
|
@ -61,12 +61,14 @@ public final class MediaItem {
|
||||
@Nullable private UUID drmUuid;
|
||||
private boolean drmMultiSession;
|
||||
private List<StreamKey> streamKeys;
|
||||
private List<Subtitle> subtitles;
|
||||
@Nullable private Object tag;
|
||||
@Nullable private MediaMetadata mediaMetadata;
|
||||
|
||||
/** Creates a builder. */
|
||||
public Builder() {
|
||||
streamKeys = Collections.emptyList();
|
||||
subtitles = Collections.emptyList();
|
||||
drmLicenseRequestHeaders = Collections.emptyMap();
|
||||
}
|
||||
|
||||
@ -138,6 +140,8 @@ public final class MediaItem {
|
||||
/**
|
||||
* Sets the optional request headers attached to the drm license request.
|
||||
*
|
||||
* <p>{@code null} or an empty {@link Map} can be used for a reset.
|
||||
*
|
||||
* <p>If no valid drm configuration is specified, the drm license request headers are ignored.
|
||||
*/
|
||||
public Builder setDrmLicenseRequestHeaders(
|
||||
@ -176,6 +180,8 @@ public final class MediaItem {
|
||||
* Sets the optional stream keys by which the manifest is filtered (only used for adaptive
|
||||
* streams).
|
||||
*
|
||||
* <p>{@code null} or an empty {@link List} can be used for a reset.
|
||||
*
|
||||
* <p>If a {@link PlaybackProperties#sourceUri} is set, the stream keys are used to create a
|
||||
* {@link PlaybackProperties} object. Otherwise it will be ignored.
|
||||
*/
|
||||
@ -187,6 +193,22 @@ public final class MediaItem {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional subtitles.
|
||||
*
|
||||
* <p>{@code null} or an empty {@link List} can be used for a reset.
|
||||
*
|
||||
* <p>If a {@link PlaybackProperties#sourceUri} is set, the subtitles are used to create a
|
||||
* {@link PlaybackProperties} object. Otherwise it will be ignored.
|
||||
*/
|
||||
public Builder setSubtitles(@Nullable List<Subtitle> subtitles) {
|
||||
this.subtitles =
|
||||
subtitles != null && !subtitles.isEmpty()
|
||||
? Collections.unmodifiableList(new ArrayList<>(subtitles))
|
||||
: Collections.emptyList();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional tag for custom attributes. The tag for the media source which will be
|
||||
* published in the {@link com.google.android.exoplayer2.Timeline} of the source as {@link
|
||||
@ -222,6 +244,7 @@ public final class MediaItem {
|
||||
drmUuid, drmLicenseUri, drmLicenseRequestHeaders, drmMultiSession)
|
||||
: null,
|
||||
streamKeys,
|
||||
subtitles,
|
||||
tag);
|
||||
mediaId = mediaId != null ? mediaId : sourceUri.toString();
|
||||
}
|
||||
@ -266,7 +289,7 @@ public final class MediaItem {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
if (!(obj instanceof DrmConfiguration)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -307,6 +330,9 @@ public final class MediaItem {
|
||||
/** Optional stream keys by which the manifest is filtered. */
|
||||
public final List<StreamKey> streamKeys;
|
||||
|
||||
/** Optional subtitles to be sideloaded. */
|
||||
public final List<Subtitle> subtitles;
|
||||
|
||||
/**
|
||||
* Optional tag for custom attributes. The tag for the media source which will be published in
|
||||
* the {@link com.google.android.exoplayer2.Timeline} of the source as {@link
|
||||
@ -319,11 +345,13 @@ public final class MediaItem {
|
||||
@Nullable String mimeType,
|
||||
@Nullable DrmConfiguration drmConfiguration,
|
||||
List<StreamKey> streamKeys,
|
||||
List<Subtitle> subtitles,
|
||||
@Nullable Object tag) {
|
||||
this.sourceUri = sourceUri;
|
||||
this.mimeType = mimeType;
|
||||
this.drmConfiguration = drmConfiguration;
|
||||
this.streamKeys = streamKeys;
|
||||
this.subtitles = subtitles;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
@ -332,7 +360,7 @@ public final class MediaItem {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
if (!(obj instanceof PlaybackProperties)) {
|
||||
return false;
|
||||
}
|
||||
PlaybackProperties other = (PlaybackProperties) obj;
|
||||
@ -340,7 +368,8 @@ public final class MediaItem {
|
||||
return sourceUri.equals(other.sourceUri)
|
||||
&& Util.areEqual(mimeType, other.mimeType)
|
||||
&& Util.areEqual(drmConfiguration, other.drmConfiguration)
|
||||
&& Util.areEqual(streamKeys, other.streamKeys)
|
||||
&& streamKeys.equals(other.streamKeys)
|
||||
&& subtitles.equals(other.subtitles)
|
||||
&& Util.areEqual(tag, other.tag);
|
||||
}
|
||||
|
||||
@ -350,11 +379,78 @@ public final class MediaItem {
|
||||
result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode());
|
||||
result = 31 * result + (drmConfiguration == null ? 0 : drmConfiguration.hashCode());
|
||||
result = 31 * result + streamKeys.hashCode();
|
||||
result = 31 * result + subtitles.hashCode();
|
||||
result = 31 * result + (tag == null ? 0 : tag.hashCode());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/** Properties for a text track. */
|
||||
public static final class Subtitle {
|
||||
|
||||
/** The {@link Uri} to the subtitle file. */
|
||||
public final Uri uri;
|
||||
/** The MIME type. */
|
||||
public final String mimeType;
|
||||
/** The language. */
|
||||
@Nullable public final String language;
|
||||
/** The selection flags. */
|
||||
@C.SelectionFlags public final int selectionFlags;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param uri The {@link Uri uri} to the subtitle file.
|
||||
* @param mimeType The mime type.
|
||||
* @param language The optional language.
|
||||
*/
|
||||
public Subtitle(Uri uri, String mimeType, @Nullable String language) {
|
||||
this(uri, mimeType, language, /* selectionFlags= */ 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with the given selection flags.
|
||||
*
|
||||
* @param uri The {@link Uri uri} to the subtitle file.
|
||||
* @param mimeType The mime type.
|
||||
* @param language The optional language.
|
||||
* @param selectionFlags The selection flags.
|
||||
*/
|
||||
public Subtitle(
|
||||
Uri uri, String mimeType, @Nullable String language, @C.SelectionFlags int selectionFlags) {
|
||||
this.uri = uri;
|
||||
this.mimeType = mimeType;
|
||||
this.language = language;
|
||||
this.selectionFlags = selectionFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof Subtitle)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Subtitle other = (Subtitle) obj;
|
||||
|
||||
return uri.equals(other.uri)
|
||||
&& mimeType.equals(other.mimeType)
|
||||
&& Util.areEqual(language, other.language)
|
||||
&& selectionFlags == other.selectionFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = uri.hashCode();
|
||||
result = 31 * result + mimeType.hashCode();
|
||||
result = 31 * result + (language == null ? 0 : language.hashCode());
|
||||
result = 31 * result + selectionFlags;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/** Identifies the media item. */
|
||||
public final String mediaId;
|
||||
|
||||
@ -374,15 +470,15 @@ public final class MediaItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
if (!(obj instanceof MediaItem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MediaItem other = (MediaItem) o;
|
||||
MediaItem other = (MediaItem) obj;
|
||||
|
||||
return Util.areEqual(mediaId, other.mediaId)
|
||||
&& Util.areEqual(playbackProperties, other.playbackProperties)
|
||||
|
@ -21,6 +21,7 @@ import android.util.SparseArray;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
@ -122,6 +123,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
return new DefaultMediaSourceFactory(context, dataSourceFactory);
|
||||
}
|
||||
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final SparseArray<MediaSourceFactory> mediaSourceFactories;
|
||||
@C.ContentType private final int[] supportedTypes;
|
||||
private final String userAgent;
|
||||
@ -133,6 +135,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
@Nullable private List<StreamKey> streamKeys;
|
||||
|
||||
private DefaultMediaSourceFactory(Context context, DataSource.Factory dataSourceFactory) {
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
||||
userAgent = Util.getUserAgent(context, ExoPlayerLibraryInfo.VERSION_SLASHY);
|
||||
drmHttpDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
|
||||
@ -244,7 +247,30 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
!mediaItem.playbackProperties.streamKeys.isEmpty()
|
||||
? mediaItem.playbackProperties.streamKeys
|
||||
: streamKeys);
|
||||
return mediaSourceFactory.createMediaSource(mediaItem);
|
||||
|
||||
MediaSource leafMediaSource = mediaSourceFactory.createMediaSource(mediaItem);
|
||||
|
||||
if (mediaItem.playbackProperties.subtitles.isEmpty()) {
|
||||
return leafMediaSource;
|
||||
}
|
||||
|
||||
List<MediaItem.Subtitle> subtitles = mediaItem.playbackProperties.subtitles;
|
||||
MediaSource[] mediaSources = new MediaSource[subtitles.size() + 1];
|
||||
mediaSources[0] = leafMediaSource;
|
||||
SingleSampleMediaSource.Factory singleSampleSourceFactory =
|
||||
new SingleSampleMediaSource.Factory(dataSourceFactory);
|
||||
for (int i = 0; i < subtitles.size(); i++) {
|
||||
MediaItem.Subtitle subtitle = subtitles.get(i);
|
||||
Format subtitleFormat =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(subtitle.mimeType)
|
||||
.setLanguage(subtitle.language)
|
||||
.setSelectionFlags(subtitle.selectionFlags)
|
||||
.build();
|
||||
mediaSources[i + 1] =
|
||||
singleSampleSourceFactory.createMediaSource(subtitle.uri, subtitleFormat, C.TIME_UNSET);
|
||||
}
|
||||
return new MergingMediaSource(mediaSources);
|
||||
}
|
||||
|
||||
// internal methods
|
||||
|
@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -130,6 +131,24 @@ public class MediaItemTest {
|
||||
assertThat(mediaItem.playbackProperties.streamKeys).isEqualTo(streamKeys);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetSubtitles_setsSubtitles() {
|
||||
List<MediaItem.Subtitle> subtitles =
|
||||
Arrays.asList(
|
||||
new MediaItem.Subtitle(
|
||||
Uri.parse(URI_STRING + "/en"), MimeTypes.APPLICATION_TTML, /* language= */ "en"),
|
||||
new MediaItem.Subtitle(
|
||||
Uri.parse(URI_STRING + "/de"),
|
||||
MimeTypes.APPLICATION_TTML,
|
||||
/* language= */ null,
|
||||
C.SELECTION_FLAG_DEFAULT));
|
||||
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setSourceUri(URI_STRING).setSubtitles(subtitles).build();
|
||||
|
||||
assertThat(mediaItem.playbackProperties.subtitles).isEqualTo(subtitles);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetTag_isNullByDefault() {
|
||||
MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_STRING).build();
|
||||
|
@ -17,10 +17,15 @@ package com.google.android.exoplayer2.source;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@ -29,6 +34,7 @@ import org.junit.runner.RunWith;
|
||||
public final class DefaultMediaSourceFactoryTest {
|
||||
|
||||
private static final String URI_MEDIA = "http://exoplayer.dev/video";
|
||||
private static final String URI_TEXT = "http://exoplayer.dev/text";
|
||||
|
||||
@Test
|
||||
public void createMediaSource_withoutMimeType_progressiveSource() {
|
||||
@ -80,6 +86,42 @@ public final class DefaultMediaSourceFactoryTest {
|
||||
assertThat(mediaSource).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMediaSource_withSubtitle_isMergingMediaSource() {
|
||||
DefaultMediaSourceFactory defaultMediaSourceFactory =
|
||||
DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
|
||||
List<MediaItem.Subtitle> subtitles =
|
||||
Arrays.asList(
|
||||
new MediaItem.Subtitle(Uri.parse(URI_TEXT), MimeTypes.APPLICATION_TTML, "en"),
|
||||
new MediaItem.Subtitle(
|
||||
Uri.parse(URI_TEXT), MimeTypes.APPLICATION_TTML, "de", C.SELECTION_FLAG_DEFAULT));
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setSourceUri(URI_MEDIA).setSubtitles(subtitles).build();
|
||||
|
||||
MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
|
||||
|
||||
assertThat(mediaSource).isInstanceOf(MergingMediaSource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMediaSource_withSubtitle_hasTag() {
|
||||
DefaultMediaSourceFactory defaultMediaSourceFactory =
|
||||
DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
|
||||
Object tag = new Object();
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setTag(tag)
|
||||
.setSourceUri(URI_MEDIA)
|
||||
.setSubtitles(
|
||||
Collections.singletonList(
|
||||
new MediaItem.Subtitle(Uri.parse(URI_TEXT), MimeTypes.APPLICATION_TTML, "en")))
|
||||
.build();
|
||||
|
||||
MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
|
||||
|
||||
assertThat(mediaSource.getTag()).isEqualTo(tag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSupportedTypes_coreModule_onlyOther() {
|
||||
int[] supportedTypes =
|
||||
|
Loading…
x
Reference in New Issue
Block a user