diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 24132e400c..6c61c221fd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -749,6 +749,32 @@ public final class Util { + ") " + ExoPlayerLibraryInfo.VERSION_SLASHY; } + /** + * Returns a copy of {@code codecs} without the codecs whose track type doesn't match + * {@code trackType}. + * + * @param codecs A codec sequence string, as defined in RFC 6381. + * @param trackType One of {@link C}{@code .TRACK_TYPE_*}. + * @return A copy of {@code codecs} without the codecs whose track type doesn't match + * {@code trackType}. + */ + public static String getCodecsOfType(String codecs, int trackType) { + if (TextUtils.isEmpty(codecs)) { + return null; + } + String[] codecArray = codecs.trim().split("(\\s*,\\s*)"); + StringBuilder builder = new StringBuilder(); + for (String codec : codecArray) { + if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) { + if (builder.length() > 0) { + builder.append(","); + } + builder.append(codec); + } + } + return builder.length() > 0 ? builder.toString() : null; + } + /** * Converts a sample bit depth to a corresponding PCM encoding constant. * diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java index 1afe380483..68ed686c62 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util; import static com.google.android.exoplayer2.util.Util.binarySearchCeil; import static com.google.android.exoplayer2.util.Util.binarySearchFloor; import static com.google.android.exoplayer2.util.Util.escapeFileName; +import static com.google.android.exoplayer2.util.Util.getCodecsOfType; import static com.google.android.exoplayer2.util.Util.parseXsDateTime; import static com.google.android.exoplayer2.util.Util.parseXsDuration; import static com.google.android.exoplayer2.util.Util.unescapeFileName; @@ -181,6 +182,18 @@ public class UtilTest { assertThat(parseXsDateTime("2014-09-19T13:18:55.000-800")).isEqualTo(1411161535000L); } + @Test + public void testGetCodecsOfType() { + assertThat(getCodecsOfType(null, C.TRACK_TYPE_VIDEO)).isNull(); + assertThat(getCodecsOfType("avc1.64001e,vp9.63.1", C.TRACK_TYPE_AUDIO)).isNull(); + assertThat(getCodecsOfType(" vp9.63.1, ec-3 ", C.TRACK_TYPE_AUDIO)).isEqualTo("ec-3"); + assertThat(getCodecsOfType("avc1.61e, vp9.63.1, ec-3 ", C.TRACK_TYPE_VIDEO)) + .isEqualTo("avc1.61e,vp9.63.1"); + assertThat(getCodecsOfType("avc1.61e, vp9.63.1, ec-3 ", C.TRACK_TYPE_VIDEO)) + .isEqualTo("avc1.61e,vp9.63.1"); + assertThat(getCodecsOfType("invalidCodec1, invalidCodec2 ", C.TRACK_TYPE_AUDIO)).isNull(); + } + @Test public void testUnescapeInvalidFileName() { assertThat(Util.unescapeFileName("%a")).isNull(); diff --git a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java index 8b0d76d2e5..45c9d15242 100644 --- a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java +++ b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java @@ -34,7 +34,7 @@ public class HlsMasterPlaylistParserTest extends TestCase { private static final String PLAYLIST_URI = "https://example.com/test.m3u8"; - private static final String MASTER_PLAYLIST = " #EXTM3U \n" + private static final String PLAYLIST_SIMPLE = " #EXTM3U \n" + "\n" + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" + "http://example.com/low.m3u8\n" @@ -51,7 +51,7 @@ public class HlsMasterPlaylistParserTest extends TestCase { + "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n" + "http://example.com/audio-only.m3u8"; - private static final String AVG_BANDWIDTH_MASTER_PLAYLIST = " #EXTM3U \n" + private static final String PLAYLIST_WITH_AVG_BANDWIDTH = " #EXTM3U \n" + "\n" + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" + "http://example.com/low.m3u8\n" @@ -64,19 +64,33 @@ 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"; - private static final String MASTER_PLAYLIST_WITH_CC = " #EXTM3U \n" + private static final String PLAYLIST_WITH_CC = " #EXTM3U \n" + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n" + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" + "http://example.com/low.m3u8\n"; - private static final String MASTER_PLAYLIST_WITHOUT_CC = " #EXTM3U \n" + private static final String PLAYLIST_WITHOUT_CC = " #EXTM3U \n" + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n" + "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128," + "CLOSED-CAPTIONS=NONE\n" + "http://example.com/low.m3u8\n"; + private static final String PLAYLIST_WITH_AUDIO_MEDIA_TAG = "#EXTM3U\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=2227464,CODECS=\"avc1.640020,mp4a.40.2\",AUDIO=\"aud1\"\n" + + "uri1.m3u8\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=8178040,CODECS=\"avc1.64002a,mp4a.40.2\",AUDIO=\"aud1\"\n" + + "uri2.m3u8\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=2448841,CODECS=\"avc1.640020,ac-3\",AUDIO=\"aud2\"\n" + + "uri1.m3u8\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=8399417,CODECS=\"avc1.64002a,ac-3\",AUDIO=\"aud2\"\n" + + "uri2.m3u8\n" + + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",LANGUAGE=\"en\",NAME=\"English\"," + + "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"2\",URI=\"a1/prog_index.m3u8\"\n" + + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud2\",LANGUAGE=\"en\",NAME=\"English\"," + + "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"6\",URI=\"a2/prog_index.m3u8\"\n"; + public void testParseMasterPlaylist() throws IOException{ - HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST); + HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE); List variants = masterPlaylist.variants; assertEquals(5, variants.size()); @@ -116,7 +130,7 @@ public class HlsMasterPlaylistParserTest extends TestCase { public void testMasterPlaylistWithBandwdithAverage() throws IOException { HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, - AVG_BANDWIDTH_MASTER_PLAYLIST); + PLAYLIST_WITH_AVG_BANDWIDTH); List variants = masterPlaylist.variants; @@ -134,7 +148,7 @@ public class HlsMasterPlaylistParserTest extends TestCase { } public void testPlaylistWithClosedCaption() throws IOException { - HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST_WITH_CC); + HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC); assertEquals(1, playlist.muxedCaptionFormats.size()); Format closedCaptionFormat = playlist.muxedCaptionFormats.get(0); assertEquals(MimeTypes.APPLICATION_CEA708, closedCaptionFormat.sampleMimeType); @@ -143,10 +157,22 @@ public class HlsMasterPlaylistParserTest extends TestCase { } public void testPlaylistWithoutClosedCaptions() throws IOException { - HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST_WITHOUT_CC); + HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITHOUT_CC); assertEquals(Collections.emptyList(), playlist.muxedCaptionFormats); } + public void testCodecPropagation() throws IOException { + HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG); + + Format firstAudioFormat = playlist.audios.get(0).format; + assertEquals("mp4a.40.2", firstAudioFormat.codecs); + assertEquals(MimeTypes.AUDIO_AAC, firstAudioFormat.sampleMimeType); + + Format secondAudioFormat = playlist.audios.get(1).format; + assertEquals("ac-3", secondAudioFormat.codecs); + assertEquals(MimeTypes.AUDIO_AC3, secondAudioFormat.sampleMimeType); + } + private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString) throws IOException { Uri playlistUri = Uri.parse(uri); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 3eae83624b..9816e4041c 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.source.hls; import android.os.Handler; -import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; @@ -748,13 +747,8 @@ import java.util.LinkedList; if (containerFormat == null) { return sampleFormat; } - String codecs = null; int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType); - if (sampleTrackType == C.TRACK_TYPE_AUDIO) { - codecs = getAudioCodecs(containerFormat.codecs); - } else if (sampleTrackType == C.TRACK_TYPE_VIDEO) { - codecs = getVideoCodecs(containerFormat.codecs); - } + String codecs = Util.getCodecsOfType(containerFormat.codecs, sampleTrackType); return sampleFormat.copyWithContainerInfo(containerFormat.id, codecs, containerFormat.bitrate, containerFormat.width, containerFormat.height, containerFormat.selectionFlags, containerFormat.language); @@ -793,29 +787,4 @@ import java.util.LinkedList; return true; } - private static String getAudioCodecs(String codecs) { - return getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO); - } - - private static String getVideoCodecs(String codecs) { - return getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO); - } - - private static String getCodecsOfType(String codecs, int trackType) { - if (TextUtils.isEmpty(codecs)) { - return null; - } - String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)"); - StringBuilder builder = new StringBuilder(); - for (String codec : codecArray) { - if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) { - if (builder.length() > 0) { - builder.append(","); - } - builder.append(codec); - } - } - return builder.length() > 0 ? builder.toString() : null; - } - } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index c63ded6275..6536be3ffe 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -35,6 +35,7 @@ import java.io.UnsupportedEncodingException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Queue; @@ -88,6 +89,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variantUrls = new HashSet<>(); + HashMap audioGroupIdToCodecs = new HashMap<>(); ArrayList variants = new ArrayList<>(); ArrayList audios = new ArrayList<>(); ArrayList subtitles = new ArrayList<>(); + ArrayList mediaTags = new ArrayList<>(); ArrayList tags = new ArrayList<>(); Format muxedAudioFormat = null; List muxedCaptionFormats = null; @@ -208,47 +213,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser(); - } - muxedCaptionFormats.add(Format.createTextContainerFormat(id, null, mimeType, null, - Format.NO_VALUE, selectionFlags, language, accessibilityChannel)); - break; - default: - // Do nothing. - break; - } + // Media tags are parsed at the end to include codec information from #EXT-X-STREAM-INF + // tags. + mediaTags.add(line); } else if (line.startsWith(TAG_STREAM_INF)) { noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE); int bitrate = parseIntAttr(line, REGEX_BANDWIDTH); @@ -279,6 +246,10 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser(); + } + muxedCaptionFormats.add(Format.createTextContainerFormat(id, null, mimeType, null, + Format.NO_VALUE, selectionFlags, language, accessibilityChannel)); + break; + default: + // Do nothing. + break; + } + } + if (noClosedCaptions) { muxedCaptionFormats = Collections.emptyList(); }