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++) {