Integrate ExoplayerCuesDecoder and SubtitleExtractor

This CL contains integration of the ExoplayerCuesDecoder and the
SubtitleExtractor with the player. The SubtitleExtractor is integrated
inside the DefaultMediaSourceFactory. The flag was added to the
state of the DefaultMediaSourceFactory to let user decide between the
ProgressiveMediaSource and the SingleSampleMediaSource as a source for
subtitles. Choosing the ProgressiveMediaSource will cause data to flow
through the SubtitleExtractor and eventually the ExoplayerCuesDecoder.

PiperOrigin-RevId: 394500305
This commit is contained in:
apodob 2021-09-02 19:05:11 +01:00 committed by Ian Baker
parent 3213f969c0
commit dd19bc8927
2 changed files with 57 additions and 7 deletions

View File

@ -21,14 +21,18 @@ import android.content.Context;
import android.util.SparseArray; import android.util.SparseArray;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider; import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.subtitle.SubtitleExtractor;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.text.webvtt.WebvttDecoder;
import com.google.android.exoplayer2.ui.AdViewProvider; import com.google.android.exoplayer2.ui.AdViewProvider;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
@ -112,6 +116,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
private long liveMaxOffsetMs; private long liveMaxOffsetMs;
private float liveMinSpeed; private float liveMinSpeed;
private float liveMaxSpeed; private float liveMaxSpeed;
private boolean useProgressiveMediaSourceForSubtitles;
/** /**
* Creates a new instance. * Creates a new instance.
@ -166,6 +171,23 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
liveMaxSpeed = C.RATE_UNSET; liveMaxSpeed = C.RATE_UNSET;
} }
/**
* Sets whether a {@link ProgressiveMediaSource} or {@link SingleSampleMediaSource} is constructed
* to handle {@link MediaItem.PlaybackProperties#subtitles}. Defaults to false (i.e. {@link
* SingleSampleMediaSource}.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*
* @param useProgressiveMediaSourceForSubtitles Indicates that {@link ProgressiveMediaSource}
* should be used for subtitles instead of {@link SingleSampleMediaSource}.
* @return This factory, for convenience.
*/
public DefaultMediaSourceFactory experimentalUseProgressiveMediaSourceForSubtitles(
boolean useProgressiveMediaSourceForSubtitles) {
this.useProgressiveMediaSourceForSubtitles = useProgressiveMediaSourceForSubtitles;
return this;
}
/** /**
* Sets the {@link AdsLoaderProvider} that provides {@link AdsLoader} instances for media items * Sets the {@link AdsLoaderProvider} that provides {@link AdsLoader} instances for media items
* that have {@link MediaItem.PlaybackProperties#adsConfiguration ads configurations}. * that have {@link MediaItem.PlaybackProperties#adsConfiguration ads configurations}.
@ -370,14 +392,38 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
if (!subtitles.isEmpty()) { if (!subtitles.isEmpty()) {
MediaSource[] mediaSources = new MediaSource[subtitles.size() + 1]; MediaSource[] mediaSources = new MediaSource[subtitles.size() + 1];
mediaSources[0] = mediaSource; mediaSources[0] = mediaSource;
for (int i = 0; i < subtitles.size(); i++) {
if (useProgressiveMediaSourceForSubtitles
&& subtitles.get(i).mimeType.equals(MimeTypes.TEXT_VTT)) {
int index = i;
ProgressiveMediaSource.Factory progressiveMediaSourceFactory =
new ProgressiveMediaSource.Factory(
dataSourceFactory,
() ->
new Extractor[] {
new SubtitleExtractor(
new WebvttDecoder(),
new Format.Builder()
.setSampleMimeType(subtitles.get(index).mimeType)
.setLanguage(subtitles.get(index).language)
.setSelectionFlags(subtitles.get(index).selectionFlags)
.setRoleFlags(subtitles.get(index).roleFlags)
.setLabel(subtitles.get(index).label)
.build())
});
mediaSources[i + 1] =
progressiveMediaSourceFactory.createMediaSource(
MediaItem.fromUri(subtitles.get(i).uri.toString()));
} else {
SingleSampleMediaSource.Factory singleSampleSourceFactory = SingleSampleMediaSource.Factory singleSampleSourceFactory =
new SingleSampleMediaSource.Factory(dataSourceFactory) new SingleSampleMediaSource.Factory(dataSourceFactory)
.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy); .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy);
for (int i = 0; i < subtitles.size(); i++) {
mediaSources[i + 1] = mediaSources[i + 1] =
singleSampleSourceFactory.createMediaSource( singleSampleSourceFactory.createMediaSource(
subtitles.get(i), /* durationUs= */ C.TIME_UNSET); subtitles.get(i), /* durationUs= */ C.TIME_UNSET);
} }
}
mediaSource = new MergingMediaSource(mediaSources); mediaSource = new MergingMediaSource(mediaSources);
} }
return maybeWrapWithAdsMediaSource(mediaItem, maybeClipMediaSource(mediaItem, mediaSource)); return maybeWrapWithAdsMediaSource(mediaItem, maybeClipMediaSource(mediaItem, mediaSource));

View File

@ -66,6 +66,7 @@ public interface SubtitleDecoderFactory {
* <li>Cea708 ({@link Cea708Decoder}) * <li>Cea708 ({@link Cea708Decoder})
* <li>DVB ({@link DvbDecoder}) * <li>DVB ({@link DvbDecoder})
* <li>PGS ({@link PgsDecoder}) * <li>PGS ({@link PgsDecoder})
* <li>Exoplayer Cues ({@link ExoplayerCuesDecoder})
* </ul> * </ul>
*/ */
SubtitleDecoderFactory DEFAULT = SubtitleDecoderFactory DEFAULT =
@ -84,7 +85,8 @@ public interface SubtitleDecoderFactory {
|| MimeTypes.APPLICATION_MP4CEA608.equals(mimeType) || MimeTypes.APPLICATION_MP4CEA608.equals(mimeType)
|| MimeTypes.APPLICATION_CEA708.equals(mimeType) || MimeTypes.APPLICATION_CEA708.equals(mimeType)
|| MimeTypes.APPLICATION_DVBSUBS.equals(mimeType) || MimeTypes.APPLICATION_DVBSUBS.equals(mimeType)
|| MimeTypes.APPLICATION_PGS.equals(mimeType); || MimeTypes.APPLICATION_PGS.equals(mimeType)
|| MimeTypes.TEXT_EXOPLAYER_CUES.equals(mimeType);
} }
@Override @Override
@ -116,6 +118,8 @@ public interface SubtitleDecoderFactory {
return new DvbDecoder(format.initializationData); return new DvbDecoder(format.initializationData);
case MimeTypes.APPLICATION_PGS: case MimeTypes.APPLICATION_PGS:
return new PgsDecoder(); return new PgsDecoder();
case MimeTypes.TEXT_EXOPLAYER_CUES:
return new ExoplayerCuesDecoder();
default: default:
break; break;
} }