From f076a5ebd6347b3e915267dc47de424f1211d009 Mon Sep 17 00:00:00 2001 From: Steve Mayhew Date: Wed, 17 Jul 2019 16:39:24 -0700 Subject: [PATCH 1/6] IFrame only exposed as a video track.Plays correctly --- .../exoplayer2/source/hls/HlsMediaPeriod.java | 50 +++++++++++++++++++ .../hls/playlist/HlsMasterPlaylist.java | 6 ++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index 5b06c74d2a..eeb06e6b96 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -35,6 +35,7 @@ import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; +import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.IFrameVariant; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Rendition; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; @@ -470,6 +471,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper : Collections.emptyMap(); boolean hasVariants = !masterPlaylist.variants.isEmpty(); + boolean hasIFrameVariants = !masterPlaylist.iFrameVariants.isEmpty(); List audioRenditions = masterPlaylist.audios; List subtitleRenditions = masterPlaylist.subtitles; @@ -486,6 +488,15 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper overridingDrmInitData); } + if (hasIFrameVariants) { + buildAndPrepareIFrameSampleStreamWrappers( + masterPlaylist, + positionUs, + sampleStreamWrappers, + overridingDrmInitData + ); + } + // TODO: Build video stream wrappers here. buildAndPrepareAudioSampleStreamWrappers( @@ -673,6 +684,45 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper } } + /** + * Build a set of SampleStream wrappers around the IFrame (IDR) only variants found + * for the MediaPeriod at positionUS. + * + * @param masterPlaylist - master playlist with the IFrame variants + * @param positionUs - position to begin loading samples from + * @param sampleStreamWrappers - [output] list is filled. + */ + private void buildAndPrepareIFrameSampleStreamWrappers( + HlsMasterPlaylist masterPlaylist, + long positionUs, + List sampleStreamWrappers, + Map overridingDrmInitData) { + + int selectedVariantsCount = masterPlaylist.iFrameVariants.size(); + Uri[] selectedPlaylistUrls = new Uri[selectedVariantsCount]; + Format[] selectedPlaylistFormats = new Format[selectedVariantsCount]; + int[] selectedVariantIndices = new int[selectedVariantsCount]; + + int outIndex = 0; + for (IFrameVariant iFrameVariant : masterPlaylist.iFrameVariants) { + selectedPlaylistUrls[outIndex] = iFrameVariant.url; + selectedPlaylistFormats[outIndex] = iFrameVariant.format; + selectedVariantIndices[outIndex] = outIndex++; + } + + HlsSampleStreamWrapper sampleStreamWrapper = + buildSampleStreamWrapper( + C.TRACK_TYPE_VIDEO, + selectedPlaylistUrls, + selectedPlaylistFormats, + /* muxedAudioFormat= */ null, + /* muxedCaptionFormats= */ Collections.emptyList(), + overridingDrmInitData, + positionUs); + sampleStreamWrappers.add(sampleStreamWrapper); + + } + private void buildAndPrepareAudioSampleStreamWrappers( long positionUs, List audioRenditions, diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java index 72f6a361d7..392c6f556b 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -207,7 +207,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { super(baseUri, tags, hasIndependentSegments); this.mediaPlaylistUrls = Collections.unmodifiableList( - getMediaPlaylistUrls(variants, videos, audios, subtitles, closedCaptions)); + getMediaPlaylistUrls(variants, iFrameVariants, videos, audios, subtitles, closedCaptions)); this.variants = Collections.unmodifiableList(variants); this.videos = Collections.unmodifiableList(videos); this.audios = Collections.unmodifiableList(audios); @@ -265,6 +265,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { private static List getMediaPlaylistUrls( List variants, + List iFrameVariants, List videos, List audios, List subtitles, @@ -276,6 +277,9 @@ public final class HlsMasterPlaylist extends HlsPlaylist { mediaPlaylistUrls.add(uri); } } + for (IFrameVariant iFrameVariant : iFrameVariants) { + mediaPlaylistUrls.add(iFrameVariant.url); + } addMediaPlaylistUrls(videos, mediaPlaylistUrls); addMediaPlaylistUrls(audios, mediaPlaylistUrls); addMediaPlaylistUrls(subtitles, mediaPlaylistUrls); From f9151dfe23622a41aaa046bdce09f7592ee481df Mon Sep 17 00:00:00 2001 From: Steve Mayhew Date: Wed, 31 Jul 2019 17:01:14 -0700 Subject: [PATCH 2/6] Update to include iframe Variants in same TrackGroup This was a change suggested by @ojw28 to allow adpative track selection to work with IFrame only `Variant` as any other adaptive seleted `Track` in the `TrackGroup`. This actually works quite well with the exceptions that: 1. IFrame only tracks do not contain the same Rendition Group references as other non-iframe only varaints (so track selection should disable these tracks even in other non-video renderers. 2. Adapting to iFrame tracks only based on bandwidth might not be so good (as often larger spacial resolution iFrame track might have higher bitrate then lower spacial resolution non-iframe only track). This change includes one proposed workaround in the AdaptiveTrackSelection class (only use iFrame for higher playback speeds). --- .../java/com/google/android/exoplayer2/C.java | 4 + .../AdaptiveTrackSelection.java | 12 ++- .../exoplayer2/source/hls/HlsMediaPeriod.java | 73 +++----------- .../hls/playlist/HlsMasterPlaylist.java | 34 ++++++- .../hls/playlist/HlsPlaylistParser.java | 94 ++++++++++++------- 5 files changed, 120 insertions(+), 97 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/C.java b/library/common/src/main/java/com/google/android/exoplayer2/C.java index 780d825d2d..bed0d880d1 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/C.java @@ -1065,6 +1065,10 @@ public final class C { /** Indicates the track contains a text that has been edited for ease of reading. */ public static final int ROLE_FLAG_EASY_TO_READ = 1 << 13; + // TODO - not a 'role' in the sense it is parsed from the CHARACTERISTICS attribute... forced if iFrame only + /** Indicates the track is an IDR (IFrame) only track for trick play */ + public static final int ROLE_FLAG_TRICK_PLAY = 1 << 14; + /** * Converts a time in microseconds to the corresponding time in milliseconds, preserving * {@link #TIME_UNSET} and {@link #TIME_END_OF_SOURCE} values. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java index 9a599279ec..1b15ad5444 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java @@ -540,7 +540,17 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { @SuppressWarnings("unused") protected boolean canSelectFormat( Format format, int trackBitrate, float playbackSpeed, long effectiveBitrate) { - return Math.round(trackBitrate * playbackSpeed) <= effectiveBitrate; + + boolean isIframeOnly = (format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0; + boolean canSelect = Math.round(trackBitrate * playbackSpeed) <= effectiveBitrate; + + if (Math.abs(playbackSpeed) > 6.0f) { + canSelect = isIframeOnly; // TODO factor in playback speed... + } else { + canSelect = ! isIframeOnly && canSelect; + } + return canSelect; + } /** diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index eeb06e6b96..5a8a95667c 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -35,7 +35,6 @@ import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; -import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.IFrameVariant; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Rendition; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; @@ -479,22 +478,17 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ArrayList sampleStreamWrappers = new ArrayList<>(); ArrayList manifestUrlIndicesPerWrapper = new ArrayList<>(); - if (hasVariants) { + ArrayList allVariants = new ArrayList<>(); + allVariants.addAll(masterPlaylist.variants); + allVariants.addAll(masterPlaylist.iFrameVariants); + if (hasVariants){ buildAndPrepareMainSampleStreamWrapper( masterPlaylist, positionUs, sampleStreamWrappers, manifestUrlIndicesPerWrapper, - overridingDrmInitData); - } - - if (hasIFrameVariants) { - buildAndPrepareIFrameSampleStreamWrappers( - masterPlaylist, - positionUs, - sampleStreamWrappers, - overridingDrmInitData - ); + overridingDrmInitData, + allVariants); } // TODO: Build video stream wrappers here. @@ -570,12 +564,12 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper long positionUs, List sampleStreamWrappers, List manifestUrlIndicesPerWrapper, - Map overridingDrmInitData) { - int[] variantTypes = new int[masterPlaylist.variants.size()]; + Map overridingDrmInitData, List variants) { + int[] variantTypes = new int[variants.size()]; int videoVariantCount = 0; int audioVariantCount = 0; - for (int i = 0; i < masterPlaylist.variants.size(); i++) { - Variant variant = masterPlaylist.variants.get(i); + for (int i = 0; i < variants.size(); i++) { + Variant variant = variants.get(i); Format format = variant.format; if (format.height > 0 || Util.getCodecsOfType(format.codecs, C.TRACK_TYPE_VIDEO) != null) { variantTypes[i] = C.TRACK_TYPE_VIDEO; @@ -606,10 +600,10 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper Format[] selectedPlaylistFormats = new Format[selectedVariantsCount]; int[] selectedVariantIndices = new int[selectedVariantsCount]; int outIndex = 0; - for (int i = 0; i < masterPlaylist.variants.size(); i++) { + for (int i = 0; i < variants.size(); i++) { if ((!useVideoVariantsOnly || variantTypes[i] == C.TRACK_TYPE_VIDEO) && (!useNonAudioVariantsOnly || variantTypes[i] != C.TRACK_TYPE_AUDIO)) { - Variant variant = masterPlaylist.variants.get(i); + Variant variant =variants.get(i); selectedPlaylistUrls[outIndex] = variant.url; selectedPlaylistFormats[outIndex] = variant.format; selectedVariantIndices[outIndex++] = i; @@ -633,8 +627,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper List muxedTrackGroups = new ArrayList<>(); if (variantsContainVideoCodecs) { Format[] videoFormats = new Format[selectedVariantsCount]; - for (int i = 0; i < videoFormats.length; i++) { - videoFormats[i] = deriveVideoFormat(selectedPlaylistFormats[i]); + for (int i1 = 0; i1 < videoFormats.length; i1++) { + videoFormats[i1] = deriveVideoFormat(selectedPlaylistFormats[i1]); } muxedTrackGroups.add(new TrackGroup(videoFormats)); @@ -684,45 +678,6 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper } } - /** - * Build a set of SampleStream wrappers around the IFrame (IDR) only variants found - * for the MediaPeriod at positionUS. - * - * @param masterPlaylist - master playlist with the IFrame variants - * @param positionUs - position to begin loading samples from - * @param sampleStreamWrappers - [output] list is filled. - */ - private void buildAndPrepareIFrameSampleStreamWrappers( - HlsMasterPlaylist masterPlaylist, - long positionUs, - List sampleStreamWrappers, - Map overridingDrmInitData) { - - int selectedVariantsCount = masterPlaylist.iFrameVariants.size(); - Uri[] selectedPlaylistUrls = new Uri[selectedVariantsCount]; - Format[] selectedPlaylistFormats = new Format[selectedVariantsCount]; - int[] selectedVariantIndices = new int[selectedVariantsCount]; - - int outIndex = 0; - for (IFrameVariant iFrameVariant : masterPlaylist.iFrameVariants) { - selectedPlaylistUrls[outIndex] = iFrameVariant.url; - selectedPlaylistFormats[outIndex] = iFrameVariant.format; - selectedVariantIndices[outIndex] = outIndex++; - } - - HlsSampleStreamWrapper sampleStreamWrapper = - buildSampleStreamWrapper( - C.TRACK_TYPE_VIDEO, - selectedPlaylistUrls, - selectedPlaylistFormats, - /* muxedAudioFormat= */ null, - /* muxedCaptionFormats= */ Collections.emptyList(), - overridingDrmInitData, - positionUs); - sampleStreamWrappers.add(sampleStreamWrapper); - - } - private void buildAndPrepareAudioSampleStreamWrappers( long positionUs, List audioRenditions, diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java index 392c6f556b..035dce5b44 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -35,6 +35,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { /* baseUri= */ "", /* tags= */ Collections.emptyList(), /* variants= */ Collections.emptyList(), + /* iframes= */ Collections.emptyList(), /* videos= */ Collections.emptyList(), /* audios= */ Collections.emptyList(), /* subtitles= */ Collections.emptyList(), @@ -94,6 +95,28 @@ public final class HlsMasterPlaylist extends HlsPlaylist { this.captionGroupId = captionGroupId; } + /** + * Construct a Variant with only an (optional) video Rendition (for example, EXT-X-I-FRAME-STREAM-INF + * only allows alternate VIDEO Renditions, these are suggested if the non-Iframe Variant includes + * alternate video Rendition but not required) + * + * @param url See {@link #url}. + * @param format See {@link #format}. + * @param videoGroupId See {@link #videoGroupId}. + */ + public Variant( + Uri url, + Format format, + @Nullable String videoGroupId) { + this( + url, + format, + videoGroupId, + /* audioGroupId */null, + /* subtitleGroupId */null, + /* captionGroupId */null); + } + /** * Creates a variant for a given media playlist url. * @@ -152,6 +175,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist { public final List mediaPlaylistUrls; /** The variants declared by the playlist. */ public final List variants; + /** The IFrame only playlist declared by the playlist, if any. */ + public final List iFrameVariants; /** The video renditions declared by the playlist. */ public final List videos; /** The audio renditions declared by the playlist. */ @@ -181,6 +206,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { * @param baseUri See {@link #baseUri}. * @param tags See {@link #tags}. * @param variants See {@link #variants}. + * @param iFrameVariants * @param videos See {@link #videos}. * @param audios See {@link #audios}. * @param subtitles See {@link #subtitles}. @@ -195,6 +221,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { String baseUri, List tags, List variants, + List iFrameVariants, List videos, List audios, List subtitles, @@ -209,6 +236,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { Collections.unmodifiableList( getMediaPlaylistUrls(variants, iFrameVariants, videos, audios, subtitles, closedCaptions)); this.variants = Collections.unmodifiableList(variants); + this.iFrameVariants = Collections.unmodifiableList(iFrameVariants); this.videos = Collections.unmodifiableList(videos); this.audios = Collections.unmodifiableList(audios); this.subtitles = Collections.unmodifiableList(subtitles); @@ -226,6 +254,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { baseUri, tags, copyStreams(variants, GROUP_INDEX_VARIANT, streamKeys), + /* iframes */ Collections.emptyList(), // TODO: Allow stream keys to specify video renditions to be retained. /* videos= */ Collections.emptyList(), copyStreams(audios, GROUP_INDEX_AUDIO, streamKeys), @@ -252,6 +281,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { /* baseUri= */ "", /* tags= */ Collections.emptyList(), variant, + /* iframes= */ Collections.emptyList(), /* videos= */ Collections.emptyList(), /* audios= */ Collections.emptyList(), /* subtitles= */ Collections.emptyList(), @@ -265,7 +295,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { private static List getMediaPlaylistUrls( List variants, - List iFrameVariants, + List iFrameVariants, List videos, List audios, List subtitles, @@ -277,7 +307,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { mediaPlaylistUrls.add(uri); } } - for (IFrameVariant iFrameVariant : iFrameVariants) { + for (Variant iFrameVariant : iFrameVariants) { mediaPlaylistUrls.add(iFrameVariant.url); } addMediaPlaylistUrls(videos, mediaPlaylistUrls); 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 8f5c8517b1..e41d8b813b 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 @@ -69,6 +69,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser> urlToVariantInfos = new HashMap<>(); HashMap variableDefinitions = new HashMap<>(); ArrayList variants = new ArrayList<>(); + ArrayList iFrameVariants = new ArrayList<>(); ArrayList videos = new ArrayList<>(); ArrayList audios = new ArrayList<>(); ArrayList subtitles = new ArrayList<>(); @@ -302,33 +304,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variableDefinitions, + String line, String formatId, int peakBitrate, int averageBitrate, int roleFlags) { + String codecs = parseOptionalStringAttr(line, REGEX_CODECS, variableDefinitions); + String resolutionString = + parseOptionalStringAttr(line, REGEX_RESOLUTION, variableDefinitions); + int width; + int height; + if (resolutionString != null) { + String[] widthAndHeight = resolutionString.split("x"); + width = Integer.parseInt(widthAndHeight[0]); + height = Integer.parseInt(widthAndHeight[1]); + 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; + } + float frameRate = Format.NO_VALUE; + String frameRateString = + parseOptionalStringAttr(line, REGEX_FRAME_RATE, variableDefinitions); + if (frameRateString != null) { + frameRate = Float.parseFloat(frameRateString); + } + Format format = + new Format.Builder() + .setId(formatId) + .setContainerMimeType(MimeTypes.APPLICATION_M3U8) + .setCodecs(codecs) + .setAverageBitrate(averageBitrate) + .setPeakBitrate(peakBitrate) + .setWidth(width) + .setHeight(height) + .setFrameRate(frameRate) + .setRoleFlags(roleFlags) + .build(); + return format; + } + @Nullable private static Variant getVariantWithAudioGroup(ArrayList variants, String groupId) { for (int i = 0; i < variants.size(); i++) { From 33667ebc8ccad80e85cdd4c6a0815909698996ff Mon Sep 17 00:00:00 2001 From: Steve Mayhew Date: Tue, 6 Aug 2019 16:51:43 -0700 Subject: [PATCH 3/6] Add basic unit test for parsing. --- .../playlist/HlsMasterPlaylistParserTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java index 40ba379a71..01dc22d166 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry; import com.google.android.exoplayer2.util.MimeTypes; import java.io.ByteArrayInputStream; @@ -194,6 +195,22 @@ public class HlsMasterPlaylistParserTest { + "#EXT-X-MEDIA:TYPE=SUBTITLES," + "GROUP-ID=\"sub1\",NAME=\"English\",URI=\"s1/en/prog_index.m3u8\"\n"; + private static final String PLAYLIST_WITH_IFRAME_VARIANTS = + "#EXTM3U\n" + + "#EXT-X-VERSION:5\n" + + "#EXT-X-MEDIA:URI=\"AUDIO_English/index.m3u8\",TYPE=AUDIO,GROUP-ID=\"audio-aac\",LANGUAGE=\"en\",NAME=\"English\",AUTOSELECT=YES\n" + + "#EXT-X-MEDIA:URI=\"AUDIO_Spanish/index.m3u8\",TYPE=AUDIO,GROUP-ID=\"audio-aac\",LANGUAGE=\"es\",NAME=\"Spanish\",AUTOSELECT=YES\n" + + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID=\"cc1\",LANGUAGE=\"en\",NAME=\"English\",AUTOSELECT=YES,DEFAULT=YES,INSTREAM-ID=\"CC1\"\n" + + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=400000,RESOLUTION=480x320,CODECS=\"mp4a.40.2,avc1.640015\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n" + + "400000/index.m3u8\n" + + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1000000,RESOLUTION=848x480,CODECS=\"mp4a.40.2,avc1.64001f\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n" + + "1000000/index.m3u8\n" + + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=3220000,RESOLUTION=1280x720,CODECS=\"mp4a.40.2,avc1.64001f\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n" + + "3220000/index.m3u8\n" + + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=8940000,RESOLUTION=1920x1080,CODECS=\"mp4a.40.2,avc1.640028\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n" + + "8940000/index.m3u8\n" + + "#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=1313400,RESOLUTION=1920x1080,CODECS=\"avc1.640028\",URI=\"iframe_1313400/index.m3u8\"\n"; + @Test public void parseMasterPlaylist_withSimple_success() throws IOException { HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE); @@ -381,6 +398,15 @@ public class HlsMasterPlaylistParserTest { .isEqualTo(createExtXMediaMetadata(/* groupId= */ "aud3", /* name= */ "English")); } + @Test + public void testIFrameVariant() throws IOException { + HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_IFRAME_VARIANTS); + assertThat(playlist.variants).hasSize(4); + assertThat(playlist.iFrameVariants).hasSize(1); + assertThat(playlist.iFrameVariants.get(0).format.bitrate).isEqualTo(1313400); + assertThat(playlist.iFrameVariants.get(0).format.roleFlags & C.ROLE_FLAG_TRICK_PLAY).isEqualTo(C.ROLE_FLAG_TRICK_PLAY); + } + private static Metadata createExtXStreamInfMetadata(HlsTrackMetadataEntry.VariantInfo... infos) { return new Metadata( new HlsTrackMetadataEntry(/* groupId= */ null, /* name= */ null, Arrays.asList(infos))); From 64cd4383adceab12a5467869b78059f0935610d4 Mon Sep 17 00:00:00 2001 From: Steve Mayhew Date: Wed, 7 Aug 2019 11:04:50 -0700 Subject: [PATCH 4/6] Default to not select iFrame oly tracks. Pull in change to default to not use i-Frames for base AdaptiveTrackSelection. The expectation is a subclases, that support transitioning to iFrame, will select these tracks. --- .../trackselection/AdaptiveTrackSelection.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java index 1b15ad5444..b979c580fc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java @@ -541,16 +541,10 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { protected boolean canSelectFormat( Format format, int trackBitrate, float playbackSpeed, long effectiveBitrate) { - boolean isIframeOnly = (format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0; + boolean isNonIframeOnly = (format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) == 0; boolean canSelect = Math.round(trackBitrate * playbackSpeed) <= effectiveBitrate; - if (Math.abs(playbackSpeed) > 6.0f) { - canSelect = isIframeOnly; // TODO factor in playback speed... - } else { - canSelect = ! isIframeOnly && canSelect; - } - return canSelect; - + return canSelect && isNonIframeOnly; // Default is not to use the IDR only tracks in selection } /** From 1f11233ba022e00367ed32904092070d8c42f5ee Mon Sep 17 00:00:00 2001 From: Steve Mayhew Date: Tue, 17 Mar 2020 13:56:00 -0700 Subject: [PATCH 5/6] Support implicit i-Frame initialization segment. In the HLS Spec (https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6), specifically this or condition of this statement: "... defined by the EXT-X-MAP tag, or is located between the start of the resource and the offset of the first I-frame segment in that resource." Change adds code to add this "implicit" Media Initialization Segment if no EXT-X-MAP defines one explicitly. --- .../hls/playlist/HlsPlaylistParser.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) 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 e41d8b813b..d052160871 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 @@ -70,6 +70,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variableDefinitions = new HashMap<>(); List segments = new ArrayList<>(); List tags = new ArrayList<>(); @@ -574,6 +575,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser Date: Thu, 19 Mar 2020 17:12:16 -0700 Subject: [PATCH 6/6] Address the minor issues from pull request review Address the minor issues raised in @AquilesCanta's review of pull request 6270. Also remove unused variable (`hasIFrameVariants`) --- .../exoplayer2/trackselection/AdaptiveTrackSelection.java | 4 ++-- .../android/exoplayer2/source/hls/HlsMediaPeriod.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java index b979c580fc..22b203f439 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java @@ -541,10 +541,10 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { protected boolean canSelectFormat( Format format, int trackBitrate, float playbackSpeed, long effectiveBitrate) { - boolean isNonIframeOnly = (format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) == 0; + boolean isIframeOnlyFormat = (format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0; boolean canSelect = Math.round(trackBitrate * playbackSpeed) <= effectiveBitrate; - return canSelect && isNonIframeOnly; // Default is not to use the IDR only tracks in selection + return canSelect && !isIframeOnlyFormat; // Default is not to use the IDR only tracks in selection } /** diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index 5a8a95667c..ed6f59bbec 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -470,7 +470,6 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper : Collections.emptyMap(); boolean hasVariants = !masterPlaylist.variants.isEmpty(); - boolean hasIFrameVariants = !masterPlaylist.iFrameVariants.isEmpty(); List audioRenditions = masterPlaylist.audios; List subtitleRenditions = masterPlaylist.subtitles; @@ -564,7 +563,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper long positionUs, List sampleStreamWrappers, List manifestUrlIndicesPerWrapper, - Map overridingDrmInitData, List variants) { + Map overridingDrmInitData, + List variants) { int[] variantTypes = new int[variants.size()]; int videoVariantCount = 0; int audioVariantCount = 0; @@ -627,8 +627,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper List muxedTrackGroups = new ArrayList<>(); if (variantsContainVideoCodecs) { Format[] videoFormats = new Format[selectedVariantsCount]; - for (int i1 = 0; i1 < videoFormats.length; i1++) { - videoFormats[i1] = deriveVideoFormat(selectedPlaylistFormats[i1]); + for (int i = 0; i < videoFormats.length; i++) { + videoFormats[i] = deriveVideoFormat(selectedPlaylistFormats[i]); } muxedTrackGroups.add(new TrackGroup(videoFormats));