diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b1e054a0bf..3cbb45c028 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -113,6 +113,8 @@ * Update the ffmpeg extension to release 4.2. It is necessary to rebuild the native part of the extension after this change, following the instructions in the extension's readme. +* Add support for subtitle files to the demo app + ([#5523](https://github.com/google/ExoPlayer/issues/5523)). ### 2.10.6 (2019-10-17) ### diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 712c250846..57f0045791 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -582,5 +582,17 @@ "spherical_stereo_mode": "top_bottom" } ] + }, + { + "name": "Subtitles", + "samples": [ + { + "name": "TTML", + "uri": "https://html5demos.com/assets/dizzy.mp4", + "subtitle_uri": "https://storage.googleapis.com/exoplayer-test-media-1/ttml/netflix_ttml_sample.xml", + "subtitle_mime_type": "application/ttml+xml", + "subtitle_language": "en" + } + ] } ] 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 74e919293d..85ff41c94d 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 @@ -34,6 +34,7 @@ import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C.ContentType; import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackPreparer; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.RenderersFactory; @@ -53,7 +54,9 @@ import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSourceFactory; +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.source.TrackGroupArray; import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsMediaSource; @@ -114,8 +117,11 @@ public class PlayerActivity extends AppCompatActivity public static final String DRM_KEY_REQUEST_PROPERTIES_EXTRA = "drm_key_request_properties"; public static final String DRM_MULTI_SESSION_EXTRA = "drm_multi_session"; public static final String PREFER_EXTENSION_DECODERS_EXTRA = "prefer_extension_decoders"; - public static final String TUNNELING = "tunneling"; + public static final String TUNNELING_EXTRA = "tunneling"; public static final String AD_TAG_URI_EXTRA = "ad_tag_uri"; + public static final String SUBTITLE_URI_EXTRA = "subtitle_uri"; + public static final String SUBTITLE_MIME_TYPE_EXTRA = "subtitle_mime_type"; + public static final String SUBTITLE_LANGUAGE_EXTRA = "subtitle_language"; // For backwards compatibility only. public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid"; @@ -204,7 +210,7 @@ public class PlayerActivity extends AppCompatActivity } else { DefaultTrackSelector.ParametersBuilder builder = new DefaultTrackSelector.ParametersBuilder(/* context= */ this); - boolean tunneling = intent.getBooleanExtra(TUNNELING, false); + boolean tunneling = intent.getBooleanExtra(TUNNELING_EXTRA, false); if (Util.SDK_INT >= 21 && tunneling) { builder.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(/* context= */ this)); } @@ -424,6 +430,19 @@ public class PlayerActivity extends AppCompatActivity MediaSource[] mediaSources = new MediaSource[samples.length]; for (int i = 0; i < samples.length; i++) { mediaSources[i] = createLeafMediaSource(samples[i]); + Sample.SubtitleInfo subtitleInfo = samples[i].subtitleInfo; + if (subtitleInfo != null) { + Format subtitleFormat = + Format.createTextSampleFormat( + /* id= */ null, + subtitleInfo.mimeType, + /* selectionFlags= */ 0, + subtitleInfo.language); + MediaSource subtitleMediaSource = + new SingleSampleMediaSource.Factory(dataSourceFactory) + .createMediaSource(subtitleInfo.uri, subtitleFormat, C.TIME_UNSET); + mediaSources[i] = new MergingMediaSource(mediaSources[i], subtitleMediaSource); + } } MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources); diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java index 40c20c298c..85530b993b 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/Sample.java @@ -24,6 +24,9 @@ import static com.google.android.exoplayer2.demo.PlayerActivity.DRM_SCHEME_EXTRA import static com.google.android.exoplayer2.demo.PlayerActivity.DRM_SCHEME_UUID_EXTRA; import static com.google.android.exoplayer2.demo.PlayerActivity.EXTENSION_EXTRA; import static com.google.android.exoplayer2.demo.PlayerActivity.IS_LIVE_EXTRA; +import static com.google.android.exoplayer2.demo.PlayerActivity.SUBTITLE_LANGUAGE_EXTRA; +import static com.google.android.exoplayer2.demo.PlayerActivity.SUBTITLE_MIME_TYPE_EXTRA; +import static com.google.android.exoplayer2.demo.PlayerActivity.SUBTITLE_URI_EXTRA; import static com.google.android.exoplayer2.demo.PlayerActivity.URI_EXTRA; import android.content.Intent; @@ -51,7 +54,8 @@ import java.util.UUID; isLive, DrmInfo.createFromIntent(intent, extrasKeySuffix), adTagUri, - /* sphericalStereoMode= */ null); + /* sphericalStereoMode= */ null, + SubtitleInfo.createFromIntent(intent, extrasKeySuffix)); } public final Uri uri; @@ -60,6 +64,7 @@ import java.util.UUID; public final DrmInfo drmInfo; public final Uri adTagUri; @Nullable public final String sphericalStereoMode; + @Nullable SubtitleInfo subtitleInfo; public UriSample( String name, @@ -68,7 +73,8 @@ import java.util.UUID; boolean isLive, DrmInfo drmInfo, Uri adTagUri, - @Nullable String sphericalStereoMode) { + @Nullable String sphericalStereoMode, + @Nullable SubtitleInfo subtitleInfo) { super(name); this.uri = uri; this.extension = extension; @@ -76,6 +82,7 @@ import java.util.UUID; this.drmInfo = drmInfo; this.adTagUri = adTagUri; this.sphericalStereoMode = sphericalStereoMode; + this.subtitleInfo = subtitleInfo; } @Override @@ -100,6 +107,9 @@ import java.util.UUID; if (drmInfo != null) { drmInfo.addToIntent(intent, extrasKeySuffix); } + if (subtitleInfo != null) { + subtitleInfo.addToIntent(intent, extrasKeySuffix); + } } } @@ -167,6 +177,36 @@ import java.util.UUID; } } + public static final class SubtitleInfo { + + @Nullable + public static SubtitleInfo createFromIntent(Intent intent, String extrasKeySuffix) { + if (!intent.hasExtra(SUBTITLE_URI_EXTRA + extrasKeySuffix)) { + return null; + } + return new SubtitleInfo( + Uri.parse(intent.getStringExtra(SUBTITLE_URI_EXTRA + extrasKeySuffix)), + intent.getStringExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix), + intent.getStringExtra(SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix)); + } + + public final Uri uri; + public final String mimeType; + @Nullable public final String language; + + public SubtitleInfo(Uri uri, String mimeType, @Nullable String language) { + this.uri = Assertions.checkNotNull(uri); + this.mimeType = Assertions.checkNotNull(mimeType); + this.language = language; + } + + public void addToIntent(Intent intent, String extrasKeySuffix) { + intent.putExtra(SUBTITLE_URI_EXTRA + extrasKeySuffix, uri.toString()); + intent.putExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix, mimeType); + intent.putExtra(SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix, language); + } + } + public static Sample createFromIntent(Intent intent) { if (ACTION_VIEW_LIST.equals(intent.getAction())) { ArrayList intentUris = new ArrayList<>(); diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index 22533fd410..cdce29aa5e 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -178,7 +178,7 @@ public class SampleChooserActivity extends AppCompatActivity ? PlayerActivity.ABR_ALGORITHM_RANDOM : PlayerActivity.ABR_ALGORITHM_DEFAULT; intent.putExtra(PlayerActivity.ABR_ALGORITHM_EXTRA, abrAlgorithm); - intent.putExtra(PlayerActivity.TUNNELING, isNonNullAndChecked(tunnelingMenuItem)); + intent.putExtra(PlayerActivity.TUNNELING_EXTRA, isNonNullAndChecked(tunnelingMenuItem)); sample.addToIntent(intent); startActivity(intent); return true; @@ -311,6 +311,10 @@ public class SampleChooserActivity extends AppCompatActivity ArrayList playlistSamples = null; String adTagUri = null; String sphericalStereoMode = null; + List subtitleInfos = new ArrayList<>(); + Uri subtitleUri = null; + String subtitleMimeType = null; + String subtitleLanguage = null; reader.beginObject(); while (reader.hasNext()) { @@ -352,7 +356,7 @@ public class SampleChooserActivity extends AppCompatActivity playlistSamples = new ArrayList<>(); reader.beginArray(); while (reader.hasNext()) { - playlistSamples.add((UriSample) readEntry(reader, true)); + playlistSamples.add((UriSample) readEntry(reader, /* insidePlaylist= */ true)); } reader.endArray(); break; @@ -364,6 +368,15 @@ public class SampleChooserActivity extends AppCompatActivity !insidePlaylist, "Invalid attribute on nested item: spherical_stereo_mode"); sphericalStereoMode = reader.nextString(); break; + case "subtitle_uri": + subtitleUri = Uri.parse(reader.nextString()); + break; + case "subtitle_mime_type": + subtitleMimeType = reader.nextString(); + break; + case "subtitle_language": + subtitleLanguage = reader.nextString(); + break; default: throw new ParserException("Unsupported attribute name: " + name); } @@ -377,6 +390,14 @@ public class SampleChooserActivity extends AppCompatActivity drmLicenseUrl, drmKeyRequestProperties, drmMultiSession); + Sample.SubtitleInfo subtitleInfo = + subtitleUri == null + ? null + : new Sample.SubtitleInfo( + subtitleUri, + Assertions.checkNotNull( + subtitleMimeType, "subtitle_mime_type is required if subtitle_uri is set."), + subtitleLanguage); if (playlistSamples != null) { UriSample[] playlistSamplesArray = playlistSamples.toArray(new UriSample[0]); return new PlaylistSample(sampleName, playlistSamplesArray); @@ -388,7 +409,8 @@ public class SampleChooserActivity extends AppCompatActivity isLive, drmInfo, adTagUri != null ? Uri.parse(adTagUri) : null, - sphericalStereoMode); + sphericalStereoMode, + subtitleInfo); } }