diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java index f0adf274ee..aa279f23f4 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java @@ -19,6 +19,7 @@ import android.net.Uri; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.util.MimeTypes; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.Charset; @@ -53,12 +54,14 @@ public class HlsMasterPlaylistParserTest extends TestCase { + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" + "http://example.com/low.m3u8\n"; - public void testParseMasterPlaylist() throws IOException{ - HlsPlaylist playlist = parsePlaylist(PLAYLIST_URI, MASTER_PLAYLIST); - assertNotNull(playlist); - assertEquals(HlsPlaylist.TYPE_MASTER, playlist.type); + private static final String MASTER_PLAYLIST_WITH_CC = " #EXTM3U \n" + + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n" + + "\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" + + "http://example.com/low.m3u8\n"; - HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; + public void testParseMasterPlaylist() throws IOException{ + HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST); List variants = masterPlaylist.variants; assertNotNull(variants); @@ -98,18 +101,28 @@ public class HlsMasterPlaylistParserTest extends TestCase { public void testPlaylistWithInvalidHeader() throws IOException { try { - parsePlaylist(PLAYLIST_URI, PLAYLIST_WITH_INVALID_HEADER); + parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INVALID_HEADER); fail("Expected exception not thrown."); } catch (ParserException e) { // Expected due to invalid header. } } - private static HlsPlaylist parsePlaylist(String uri, String playlistString) throws IOException { + public void testPlaylistWithClosedCaption() throws IOException { + HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST_WITH_CC); + assertEquals(1, playlist.muxedCaptionFormats.size()); + Format closedCaptionFormat = playlist.muxedCaptionFormats.get(0); + assertEquals(MimeTypes.APPLICATION_CEA708, closedCaptionFormat.sampleMimeType); + assertEquals(4, closedCaptionFormat.accessibilityChannel); + assertEquals("es", closedCaptionFormat.language); + } + + private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString) + throws IOException { Uri playlistUri = Uri.parse(uri); ByteArrayInputStream inputStream = new ByteArrayInputStream( playlistString.getBytes(Charset.forName(C.UTF8_NAME))); - return new HlsPlaylistParser().parse(playlistUri, inputStream); + return (HlsMasterPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index c7c66fbd61..7ba5cf2df1 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.math.BigInteger; import java.util.Arrays; +import java.util.List; import java.util.Locale; /** @@ -85,6 +86,7 @@ import java.util.Locale; private final HlsUrl[] variants; private final HlsPlaylistTracker playlistTracker; private final TrackGroup trackGroup; + private final List muxedCaptionFormats; private boolean isTimestampMaster; private byte[] scratchSpace; @@ -107,14 +109,16 @@ import java.util.Locale; * @param timestampAdjusterProvider A provider of {@link TimestampAdjuster} instances. If * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the * same provider. + * @param muxedCaptionFormats List of muxed caption {@link Format}s. */ public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants, - DataSource dataSource, TimestampAdjusterProvider timestampAdjusterProvider) { + DataSource dataSource, TimestampAdjusterProvider timestampAdjusterProvider, + List muxedCaptionFormats) { this.playlistTracker = playlistTracker; this.variants = variants; this.dataSource = dataSource; this.timestampAdjusterProvider = timestampAdjusterProvider; - + this.muxedCaptionFormats = muxedCaptionFormats; Format[] variantFormats = new Format[variants.length]; int[] initialTrackSelection = new int[variants.length]; for (int i = 0; i < variants.length; i++) { @@ -282,7 +286,7 @@ import java.util.Locale; DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, null); out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, selectedUrl, - trackSelection.getSelectionReason(), trackSelection.getSelectionData(), + muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey, encryptionIv); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 5885797896..357a32f086 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source.hls; import android.text.TextUtils; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.Extractor; @@ -39,6 +40,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** @@ -84,6 +86,7 @@ import java.util.concurrent.atomic.AtomicInteger; private final Extractor previousExtractor; private final boolean shouldSpliceIn; private final boolean needNewExtractor; + private final List muxedCaptionFormats; private final boolean isPackedAudio; private final Id3Decoder id3Decoder; @@ -102,6 +105,7 @@ import java.util.concurrent.atomic.AtomicInteger; * @param dataSpec Defines the data to be loaded. * @param initDataSpec Defines the initialization data to be fed to new extractors. May be null. * @param hlsUrl The url of the playlist from which this chunk was obtained. + * @param muxedCaptionFormats List of muxed caption {@link Format}s. * @param trackSelectionReason See {@link #trackSelectionReason}. * @param trackSelectionData See {@link #trackSelectionData}. * @param startTimeUs The start time of the chunk in microseconds. @@ -115,17 +119,19 @@ import java.util.concurrent.atomic.AtomicInteger; * @param encryptionIv For AES encryption chunks, the encryption initialization vector. */ public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec, - HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, long startTimeUs, - long endTimeUs, int chunkIndex, int discontinuitySequenceNumber, - boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster, - HlsMediaChunk previousChunk, byte[] encryptionKey, byte[] encryptionIv) { + HlsUrl hlsUrl, List muxedCaptionFormats, int trackSelectionReason, + Object trackSelectionData, long startTimeUs, long endTimeUs, int chunkIndex, + int discontinuitySequenceNumber, boolean isMasterTimestampSource, + TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, byte[] encryptionKey, + byte[] encryptionIv) { super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format, trackSelectionReason, trackSelectionData, startTimeUs, endTimeUs, chunkIndex); + this.discontinuitySequenceNumber = discontinuitySequenceNumber; this.initDataSpec = initDataSpec; this.hlsUrl = hlsUrl; + this.muxedCaptionFormats = muxedCaptionFormats; this.isMasterTimestampSource = isMasterTimestampSource; this.timestampAdjuster = timestampAdjuster; - this.discontinuitySequenceNumber = discontinuitySequenceNumber; // Note: this.dataSource and dataSource may be different. this.isEncrypted = this.dataSource instanceof Aes128DataSource; lastPathSegment = dataSpec.uri.getLastPathSegment(); @@ -363,7 +369,7 @@ import java.util.concurrent.atomic.AtomicInteger; } } extractor = new TsExtractor(timestampAdjuster, - new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true); + new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, muxedCaptionFormats), true); } if (usingNewExtractor) { extractor.init(extractorOutput); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index 6082372b05..0ae8becfc0 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -317,7 +317,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()]; selectedVariants.toArray(variants); HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, - variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat); + variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormats); sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; sampleStreamWrapper.setIsTimestampMaster(true); sampleStreamWrapper.continuePreparing(); @@ -343,13 +343,12 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper } private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants, - Format muxedAudioFormat, Format muxedCaptionFormat) { + Format muxedAudioFormat, List muxedCaptionFormats) { DataSource dataSource = dataSourceFactory.createDataSource(); HlsChunkSource defaultChunkSource = new HlsChunkSource(playlistTracker, variants, dataSource, - timestampAdjusterProvider); + timestampAdjusterProvider, muxedCaptionFormats); return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, - preparePositionUs, muxedAudioFormat, muxedCaptionFormat, minLoadableRetryCount, - eventDispatcher); + preparePositionUs, muxedAudioFormat, minLoadableRetryCount, eventDispatcher); } private void continuePreparingOrLoading() { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 6980fdd7a4..0e3ee6fa9c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -77,7 +77,6 @@ import java.util.LinkedList; private final HlsChunkSource chunkSource; private final Allocator allocator; private final Format muxedAudioFormat; - private final Format muxedCaptionFormat; private final int minLoadableRetryCount; private final Loader loader; private final EventDispatcher eventDispatcher; @@ -113,21 +112,18 @@ import java.util.LinkedList; * @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param positionUs The position from which to start loading media. * @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the master playlist. - * @param muxedCaptionFormat Optional muxed closed caption {@link Format} as defined by the master - * playlist. * @param minLoadableRetryCount The minimum number of times that the source should retry a load * before propagating an error. * @param eventDispatcher A dispatcher to notify of events. */ public HlsSampleStreamWrapper(int trackType, Callback callback, HlsChunkSource chunkSource, - Allocator allocator, long positionUs, Format muxedAudioFormat, Format muxedCaptionFormat, - int minLoadableRetryCount, EventDispatcher eventDispatcher) { + Allocator allocator, long positionUs, Format muxedAudioFormat, int minLoadableRetryCount, + EventDispatcher eventDispatcher) { this.trackType = trackType; this.callback = callback; this.chunkSource = chunkSource; this.allocator = allocator; this.muxedAudioFormat = muxedAudioFormat; - this.muxedCaptionFormat = muxedCaptionFormat; this.minLoadableRetryCount = minLoadableRetryCount; this.eventDispatcher = eventDispatcher; loader = new Loader("Loader:HlsSampleStreamWrapper"); @@ -589,14 +585,8 @@ import java.util.LinkedList; trackGroups[i] = new TrackGroup(formats); primaryTrackGroupIndex = i; } else { - Format trackFormat = null; - if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) { - if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) { - trackFormat = muxedAudioFormat; - } else if (MimeTypes.APPLICATION_CEA608.equals(sampleFormat.sampleMimeType)) { - trackFormat = muxedCaptionFormat; - } - } + Format trackFormat = primaryExtractorTrackType == PRIMARY_TYPE_VIDEO + && MimeTypes.isAudio(sampleFormat.sampleMimeType) ? muxedAudioFormat : null; trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat)); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java index ab18fda2f0..5580017805 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -52,22 +52,23 @@ public final class HlsMasterPlaylist extends HlsPlaylist { public final List subtitles; public final Format muxedAudioFormat; - public final Format muxedCaptionFormat; + public final List muxedCaptionFormats; public HlsMasterPlaylist(String baseUri, List variants, List audios, - List subtitles, Format muxedAudioFormat, Format muxedCaptionFormat) { + List subtitles, Format muxedAudioFormat, List muxedCaptionFormats) { super(baseUri, HlsPlaylist.TYPE_MASTER); this.variants = Collections.unmodifiableList(variants); this.audios = Collections.unmodifiableList(audios); this.subtitles = Collections.unmodifiableList(subtitles); this.muxedAudioFormat = muxedAudioFormat; - this.muxedCaptionFormat = muxedCaptionFormat; + this.muxedCaptionFormats = Collections.unmodifiableList(muxedCaptionFormats); } public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUri) { List variant = Collections.singletonList(HlsUrl.createMediaPlaylistHlsUrl(variantUri)); List emptyList = Collections.emptyList(); - return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null, null); + return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null, + Collections.emptyList()); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 6efd1fecb2..6c29535326 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -94,7 +94,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser audios = new ArrayList<>(); ArrayList subtitles = new ArrayList<>(); Format muxedAudioFormat = null; - Format muxedCaptionFormat = null; + ArrayList muxedCaptionFormats = new ArrayList<>(); String line; while (iterator.hasNext()) { @@ -198,10 +199,18 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser