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 1bd1fab352..488341d4f3 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 @@ -61,32 +61,32 @@ public class HlsMasterPlaylistParserTest extends TestCase { assertEquals(5, variants.size()); assertEquals(1280000, variants.get(0).format.bitrate); - assertNotNull(variants.get(0).codecs); - assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).codecs); + assertNotNull(variants.get(0).format.codecs); + assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).format.codecs); assertEquals(304, variants.get(0).format.width); assertEquals(128, variants.get(0).format.height); assertEquals("http://example.com/low.m3u8", variants.get(0).url); assertEquals(1280000, variants.get(1).format.bitrate); - assertNotNull(variants.get(1).codecs); - assertEquals("mp4a.40.2 , avc1.66.30 ", variants.get(1).codecs); + assertNotNull(variants.get(1).format.codecs); + assertEquals("mp4a.40.2 , avc1.66.30 ", variants.get(1).format.codecs); assertEquals("http://example.com/spaces_in_codecs.m3u8", variants.get(1).url); assertEquals(2560000, variants.get(2).format.bitrate); - assertEquals(null, variants.get(2).codecs); + assertEquals(null, variants.get(2).format.codecs); assertEquals(384, variants.get(2).format.width); assertEquals(160, variants.get(2).format.height); assertEquals("http://example.com/mid.m3u8", variants.get(2).url); assertEquals(7680000, variants.get(3).format.bitrate); - assertEquals(null, variants.get(3).codecs); + assertEquals(null, variants.get(3).format.codecs); assertEquals(Format.NO_VALUE, variants.get(3).format.width); assertEquals(Format.NO_VALUE, variants.get(3).format.height); assertEquals("http://example.com/hi.m3u8", variants.get(3).url); assertEquals(65000, variants.get(4).format.bitrate); - assertNotNull(variants.get(4).codecs); - assertEquals("mp4a.40.5", variants.get(4).codecs); + assertNotNull(variants.get(4).format.codecs); + assertEquals("mp4a.40.5", variants.get(4).format.codecs); assertEquals(Format.NO_VALUE, variants.get(4).format.width); assertEquals(Format.NO_VALUE, variants.get(4).format.height); assertEquals("http://example.com/audio-only.m3u8", variants.get(4).url); 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 948f97e887..c7d08e7ea9 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 @@ -362,7 +362,7 @@ import java.util.Locale; } // This flag ensures the change of pid between streams does not affect the sample queues. int workaroundFlags = TsExtractor.WORKAROUND_MAP_BY_TYPE; - String codecs = variants[newVariantIndex].codecs; + String codecs = variants[newVariantIndex].format.codecs; if (!TextUtils.isEmpty(codecs)) { // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really // exist. If we know from the codec attribute that they don't exist, then we can explicitly 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 3937e562e5..78e85843ea 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 @@ -39,7 +39,6 @@ import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.ParsingLoadable; -import com.google.android.exoplayer2.util.MimeTypes; import java.io.IOException; import java.util.ArrayList; import java.util.IdentityHashMap; @@ -318,10 +317,8 @@ import java.util.List; String baseUri = playlist.baseUri; if (playlist instanceof HlsMediaPlaylist) { - Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null, - Format.NO_VALUE); HlsMasterPlaylist.HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[] { - new HlsMasterPlaylist.HlsUrl(playlist.baseUri, format, null)}; + HlsMasterPlaylist.HlsUrl.createMediaPlaylistHlsUrl(playlist.baseUri)}; sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, null, null)); return sampleStreamWrappers; @@ -393,7 +390,7 @@ import java.util.List; private static boolean variantHasExplicitCodecWithPrefix(HlsMasterPlaylist.HlsUrl variant, String prefix) { - String codecs = variant.codecs; + String codecs = variant.format.codecs; if (TextUtils.isEmpty(codecs)) { return false; } 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 4d7f026498..83de29c039 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 @@ -324,8 +324,10 @@ import java.util.LinkedList; } if (loadable == null) { - Assertions.checkState(retryInMs != C.TIME_UNSET && chunkSource.isLive()); - callback.onContinueLoadingRequiredInMs(this, retryInMs); + if (retryInMs != C.TIME_UNSET) { + Assertions.checkState(chunkSource.isLive()); + callback.onContinueLoadingRequiredInMs(this, retryInMs); + } return false; } 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 6d52a5f728..c0d4890b44 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 @@ -16,6 +16,8 @@ package com.google.android.exoplayer2.source.hls.playlist; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.util.MimeTypes; + import java.util.Collections; import java.util.List; @@ -24,6 +26,36 @@ import java.util.List; */ public final class HlsMasterPlaylist extends HlsPlaylist { + /** + * Represents a url in an HLS master playlist. + */ + public static final class HlsUrl { + + public final String name; + public final String url; + public final Format format; + public final Format videoFormat; + public final Format audioFormat; + public final Format[] textFormats; + + public static HlsUrl createMediaPlaylistHlsUrl(String baseUri) { + Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null, + Format.NO_VALUE); + return new HlsUrl(null, baseUri, format, null, null, null); + } + + public HlsUrl(String name, String url, Format format, Format videoFormat, Format audioFormat, + Format[] textFormats) { + this.name = name; + this.url = url; + this.format = format; + this.videoFormat = videoFormat; + this.audioFormat = audioFormat; + this.textFormats = textFormats; + } + + } + public final List variants; public final List audios; public final List subtitles; @@ -41,21 +73,4 @@ public final class HlsMasterPlaylist extends HlsPlaylist { this.muxedCaptionFormat = muxedCaptionFormat; } - /** - * Represents a url in an HLS master playlist. - */ - public static final class HlsUrl { - - public final String url; - public final Format format; - public final String codecs; - - public HlsUrl(String url, Format format, String codecs) { - this.url = url; - this.format = format; - this.codecs = codecs; - } - - } - } 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 3bceb0237e..96f69b69db 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 @@ -62,6 +62,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variants = new ArrayList<>(); ArrayList audios = new ArrayList<>(); ArrayList subtitles = new ArrayList<>(); - int bitrate = 0; - String codecs = null; - int width = Format.NO_VALUE; - int height = Format.NO_VALUE; - String name = null; Format muxedAudioFormat = null; Format muxedCaptionFormat = null; - boolean expectingStreamInfUrl = false; String line; while (iterator.hasNext()) { line = iterator.next(); if (line.startsWith(TAG_MEDIA)) { - boolean isDefault = parseBooleanAttribute(line, REGEX_DEFAULT, false); - boolean isForced = parseBooleanAttribute(line, REGEX_FORCED, false); - boolean isAutoselect = parseBooleanAttribute(line, REGEX_AUTOSELECT, - false); - int selectionFlags = (isDefault ? Format.SELECTION_FLAG_DEFAULT : 0) - | (isForced ? Format.SELECTION_FLAG_FORCED : 0) - | (isAutoselect ? Format.SELECTION_FLAG_AUTOSELECT : 0); - String type = parseStringAttr(line, REGEX_TYPE); - if (TYPE_CLOSED_CAPTIONS.equals(type)) { - String instreamId = parseStringAttr(line, REGEX_INSTREAM_ID); - if ("CC1".equals(instreamId)) { - // We assume all subtitles belong to the same group. - String captionName = parseStringAttr(line, REGEX_NAME); - String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); - muxedCaptionFormat = Format.createTextContainerFormat(captionName, - MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE, - selectionFlags, language); - } - } else if (TYPE_SUBTITLES.equals(type)) { - // We assume all subtitles belong to the same group. - String subtitleName = parseStringAttr(line, REGEX_NAME); - String uri = parseStringAttr(line, REGEX_URI); - String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); - Format format = Format.createTextContainerFormat(subtitleName, MimeTypes.APPLICATION_M3U8, - MimeTypes.TEXT_VTT, null, bitrate, selectionFlags, language); - subtitles.add(new HlsMasterPlaylist.HlsUrl(uri, format, codecs)); - } else if (TYPE_AUDIO.equals(type)) { - // We assume all audios belong to the same group. - String uri = parseOptionalStringAttr(line, REGEX_URI); - String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); - String audioName = parseStringAttr(line, REGEX_NAME); - int audioBitrate = uri != null ? bitrate : Format.NO_VALUE; - Format format = Format.createAudioContainerFormat(audioName, MimeTypes.APPLICATION_M3U8, - null, null, audioBitrate, Format.NO_VALUE, Format.NO_VALUE, null, selectionFlags, - language); - if (uri != null) { - audios.add(new HlsMasterPlaylist.HlsUrl(uri, format, codecs)); - } else { - muxedAudioFormat = format; - } + int selectionFlags = parseSelectionFlags(line); + String uri = parseOptionalStringAttr(line, REGEX_URI); + String name = parseStringAttr(line, REGEX_NAME); + String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); + Format format; + switch (parseStringAttr(line, REGEX_TYPE)) { + case TYPE_AUDIO: + format = Format.createAudioContainerFormat(name, MimeTypes.APPLICATION_M3U8, + null, null, Format.NO_VALUE, Format.NO_VALUE, Format.NO_VALUE, null, selectionFlags, + language); + if (uri == null) { + muxedAudioFormat = format; + } else { + audios.add(new HlsMasterPlaylist.HlsUrl(name, uri, format, null, format, null)); + } + break; + case TYPE_SUBTITLES: + format = Format.createTextContainerFormat(name, MimeTypes.APPLICATION_M3U8, + MimeTypes.TEXT_VTT, null, Format.NO_VALUE, selectionFlags, language); + subtitles.add(new HlsMasterPlaylist.HlsUrl(name, uri, format, null, format, null)); + break; + case TYPE_CLOSED_CAPTIONS: + if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) { + muxedCaptionFormat = Format.createTextContainerFormat(name, + MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE, + selectionFlags, language); + } + break; + default: + // Do nothing. + break; } } else if (line.startsWith(TAG_STREAM_INF)) { - bitrate = parseIntAttr(line, REGEX_BANDWIDTH); - codecs = parseOptionalStringAttr(line, REGEX_CODECS); - name = parseOptionalStringAttr(line, REGEX_NAME); + int bitrate = parseIntAttr(line, REGEX_BANDWIDTH); + String codecs = parseOptionalStringAttr(line, REGEX_CODECS); String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION); + int width; + int height; if (resolutionString != null) { String[] widthAndHeight = resolutionString.split("x"); width = Integer.parseInt(widthAndHeight[0]); - if (width <= 0) { - // Width was invalid. - width = Format.NO_VALUE; - } height = Integer.parseInt(widthAndHeight[1]); - if (height <= 0) { - // Height was invalid. + if (width <= 0 || height <= 0) { + // Resolution string is invalid. + width = Format.NO_VALUE; height = Format.NO_VALUE; } } else { width = Format.NO_VALUE; height = Format.NO_VALUE; } - expectingStreamInfUrl = true; - } else if (!line.startsWith("#") && expectingStreamInfUrl) { - if (name == null) { - name = Integer.toString(variants.size()); - } + line = iterator.next(); + String name = Integer.toString(variants.size()); Format format = Format.createVideoContainerFormat(name, MimeTypes.APPLICATION_M3U8, null, - null, bitrate, width, height, Format.NO_VALUE, null); - variants.add(new HlsMasterPlaylist.HlsUrl(line, format, codecs)); - bitrate = 0; - codecs = null; - name = null; - width = Format.NO_VALUE; - height = Format.NO_VALUE; - expectingStreamInfUrl = false; + codecs, bitrate, width, height, Format.NO_VALUE, null); + variants.add(new HlsMasterPlaylist.HlsUrl(name, line, format, null, null, null)); } } return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat, muxedCaptionFormat); } + private static int parseSelectionFlags(String line) { + return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? Format.SELECTION_FLAG_DEFAULT : 0) + | (parseBooleanAttribute(line, REGEX_FORCED, false) ? Format.SELECTION_FLAG_FORCED : 0) + | (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? Format.SELECTION_FLAG_AUTOSELECT + : 0); + } + private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) throws IOException { int mediaSequence = 0;