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).
This commit is contained in:
parent
f076a5ebd6
commit
f9151dfe23
@ -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.
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<HlsSampleStreamWrapper> sampleStreamWrappers = new ArrayList<>();
|
||||
ArrayList<int[]> manifestUrlIndicesPerWrapper = new ArrayList<>();
|
||||
|
||||
if (hasVariants) {
|
||||
ArrayList<Variant> 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<HlsSampleStreamWrapper> sampleStreamWrappers,
|
||||
List<int[]> manifestUrlIndicesPerWrapper,
|
||||
Map<String, DrmInitData> overridingDrmInitData) {
|
||||
int[] variantTypes = new int[masterPlaylist.variants.size()];
|
||||
Map<String, DrmInitData> overridingDrmInitData, List<Variant> 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<TrackGroup> 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<HlsSampleStreamWrapper> sampleStreamWrappers,
|
||||
Map<String, DrmInitData> 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<Rendition> audioRenditions,
|
||||
|
@ -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<Uri> mediaPlaylistUrls;
|
||||
/** The variants declared by the playlist. */
|
||||
public final List<Variant> variants;
|
||||
/** The IFrame only playlist declared by the playlist, if any. */
|
||||
public final List<Variant> iFrameVariants;
|
||||
/** The video renditions declared by the playlist. */
|
||||
public final List<Rendition> 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<String> tags,
|
||||
List<Variant> variants,
|
||||
List<Variant> iFrameVariants,
|
||||
List<Rendition> videos,
|
||||
List<Rendition> audios,
|
||||
List<Rendition> 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<Uri> getMediaPlaylistUrls(
|
||||
List<Variant> variants,
|
||||
List<IFrameVariant> iFrameVariants,
|
||||
List<Variant> iFrameVariants,
|
||||
List<Rendition> videos,
|
||||
List<Rendition> audios,
|
||||
List<Rendition> 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);
|
||||
|
@ -69,6 +69,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
private static final String TAG_PLAYLIST_TYPE = "#EXT-X-PLAYLIST-TYPE";
|
||||
private static final String TAG_DEFINE = "#EXT-X-DEFINE";
|
||||
private static final String TAG_STREAM_INF = "#EXT-X-STREAM-INF";
|
||||
private static final String TAG_STREAM_IFRAME = "#EXT-X-I-FRAME-STREAM-INF";
|
||||
private static final String TAG_MEDIA = "#EXT-X-MEDIA";
|
||||
private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION";
|
||||
private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY";
|
||||
@ -261,6 +262,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
HashMap<Uri, ArrayList<VariantInfo>> urlToVariantInfos = new HashMap<>();
|
||||
HashMap<String, String> variableDefinitions = new HashMap<>();
|
||||
ArrayList<Variant> variants = new ArrayList<>();
|
||||
ArrayList<Variant> iFrameVariants = new ArrayList<>();
|
||||
ArrayList<Rendition> videos = new ArrayList<>();
|
||||
ArrayList<Rendition> audios = new ArrayList<>();
|
||||
ArrayList<Rendition> subtitles = new ArrayList<>();
|
||||
@ -302,33 +304,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
sessionKeyDrmInitData.add(new DrmInitData(scheme, schemeData));
|
||||
}
|
||||
} else if (line.startsWith(TAG_STREAM_INF)) {
|
||||
String formatId = Integer.toString(variants.size());
|
||||
|
||||
noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
|
||||
int peakBitrate = parseIntAttr(line, REGEX_BANDWIDTH);
|
||||
int averageBitrate = parseOptionalIntAttr(line, REGEX_AVERAGE_BANDWIDTH, -1);
|
||||
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 = parseVideoContainerFormat(variableDefinitions, line, formatId, peakBitrate, averageBitrate, 0);
|
||||
String videoGroupId = parseOptionalStringAttr(line, REGEX_VIDEO, variableDefinitions);
|
||||
String audioGroupId = parseOptionalStringAttr(line, REGEX_AUDIO, variableDefinitions);
|
||||
String subtitlesGroupId =
|
||||
@ -342,17 +323,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
replaceVariableReferences(
|
||||
iterator.next(), variableDefinitions); // #EXT-X-STREAM-INF's URI.
|
||||
Uri uri = UriUtil.resolveToUri(baseUri, line);
|
||||
Format format =
|
||||
new Format.Builder()
|
||||
.setId(variants.size())
|
||||
.setContainerMimeType(MimeTypes.APPLICATION_M3U8)
|
||||
.setCodecs(codecs)
|
||||
.setAverageBitrate(averageBitrate)
|
||||
.setPeakBitrate(peakBitrate)
|
||||
.setWidth(width)
|
||||
.setHeight(height)
|
||||
.setFrameRate(frameRate)
|
||||
.build();
|
||||
|
||||
Variant variant =
|
||||
new Variant(
|
||||
uri, format, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId);
|
||||
@ -370,6 +341,17 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
audioGroupId,
|
||||
subtitlesGroupId,
|
||||
closedCaptionsGroupId));
|
||||
} else if (line.startsWith(TAG_STREAM_IFRAME)) {
|
||||
String formatId = "iFrame-" + iFrameVariants.size();
|
||||
int peakBitrate = parseIntAttr(line, REGEX_BANDWIDTH);
|
||||
int averageBitrate = parseOptionalIntAttr(line, REGEX_AVERAGE_BANDWIDTH, -1);
|
||||
Format format = parseVideoContainerFormat(variableDefinitions, line, formatId, peakBitrate, averageBitrate,
|
||||
C.ROLE_FLAG_ALTERNATE | C.ROLE_FLAG_TRICK_PLAY);
|
||||
|
||||
String iframeUri = parseStringAttr(line, REGEX_URI, variableDefinitions);
|
||||
Uri uri = UriUtil.resolveToUri(baseUri, iframeUri);
|
||||
|
||||
iFrameVariants.add(new Variant(uri, format, null));
|
||||
}
|
||||
}
|
||||
|
||||
@ -494,6 +476,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
baseUri,
|
||||
tags,
|
||||
deduplicatedVariants,
|
||||
iFrameVariants,
|
||||
videos,
|
||||
audios,
|
||||
subtitles,
|
||||
@ -505,6 +488,47 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
sessionKeyDrmInitData);
|
||||
}
|
||||
|
||||
private static Format parseVideoContainerFormat(HashMap<String, String> 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<Variant> variants, String groupId) {
|
||||
for (int i = 0; i < variants.size(); i++) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user