From ae520a8c2cfac9702f3cc1c87d504faf5232d95f Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Wed, 25 Jul 2018 01:29:07 +0300 Subject: [PATCH 001/832] #4306 - Extract tags from SubRip subtitles, add support for alignment tags based on SSA v4+ --- .../exoplayer2/text/subrip/SubripDecoder.java | 201 +++++++++++++++++- .../src/test/assets/subrip/typical_with_tags | 20 ++ .../text/subrip/SubripDecoderTest.java | 22 ++ 3 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 library/core/src/test/assets/subrip/typical_with_tags diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index 6cce902e87..96c065973e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -15,7 +15,9 @@ */ package com.google.android.exoplayer2.text.subrip; +import android.support.annotation.StringDef; import android.text.Html; +import android.text.Layout; import android.text.Spanned; import android.text.TextUtils; import android.util.Log; @@ -23,7 +25,11 @@ import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; import com.google.android.exoplayer2.util.LongArray; import com.google.android.exoplayer2.util.ParsableByteArray; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -38,6 +44,33 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { private static final Pattern SUBRIP_TIMING_LINE = Pattern.compile("\\s*(" + SUBRIP_TIMECODE + ")\\s*-->\\s*(" + SUBRIP_TIMECODE + ")?\\s*"); + private static final Pattern SUBRIP_TAG_PATTERN = Pattern.compile("\\{\\\\.*?\\}"); + private static final String SUBRIP_ALIGNMENT_TAG = "\\{\\\\an[1-9]\\}"; + + private static final float DEFAULT_START_FRACTION = 0.08f; + private static final float DEFAULT_END_FRACTION = 1 - DEFAULT_START_FRACTION; + private static final float DEFAULT_MID_FRACTION = 0.5f; + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + ALIGN_BOTTOM_LEFT, ALIGN_BOTTOM_MID, ALIGN_BOTTOM_RIGHT, + ALIGN_MID_LEFT, ALIGN_MID_MID, ALIGN_MID_RIGHT, + ALIGN_TOP_LEFT, ALIGN_TOP_MID, ALIGN_TOP_RIGHT + }) + + private @interface SubRipTag {} + + // Possible valid alignment tags based on SSA v4+ specs + private static final String ALIGN_BOTTOM_LEFT = "{\\an1}"; + private static final String ALIGN_BOTTOM_MID = "{\\an2}"; + private static final String ALIGN_BOTTOM_RIGHT = "{\\an3}"; + private static final String ALIGN_MID_LEFT = "{\\an4}"; + private static final String ALIGN_MID_MID = "{\\an5}"; + private static final String ALIGN_MID_RIGHT = "{\\an6}"; + private static final String ALIGN_TOP_LEFT = "{\\an7}"; + private static final String ALIGN_TOP_MID = "{\\an8}"; + private static final String ALIGN_TOP_RIGHT = "{\\an9}"; + private final StringBuilder textBuilder; public SubripDecoder() { @@ -95,8 +128,36 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { textBuilder.append(currentLine.trim()); } - Spanned text = Html.fromHtml(textBuilder.toString()); - cues.add(new Cue(text)); + // Extract tags + SubtitleTagResult tagResult = extractTags(textBuilder); + Spanned text = Html.fromHtml(tagResult.cue); + + Cue cue = null; + + // Check if tags are present + if (tagResult.tags.length > 0) { + + boolean alignTagFound = false; + + // At end of this loop the clue must be created with the applied tags + for (String tag : tagResult.tags) { + + // Check if the tag is an alignment tag + if (tag.matches(SUBRIP_ALIGNMENT_TAG)) { + + // Based on the specs, in case of the alignment tags only the first appearance counts + if (alignTagFound) continue; + alignTagFound = true; + + AlignmentResult alignmentResult = getAlignmentValues(tag); + cue = new Cue(text, Layout.Alignment.ALIGN_NORMAL, alignmentResult.line, Cue.LINE_TYPE_FRACTION, + alignmentResult.lineAnchor, alignmentResult.position, alignmentResult.positionAnchor, Cue.DIMEN_UNSET); + } + } + } + + cues.add(cue == null ? new Cue(text) : cue); + if (haveEndTimecode) { cues.add(null); } @@ -108,6 +169,111 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { return new SubripSubtitle(cuesArray, cueTimesUsArray); } + /** + * Extracts the tags from the given {@code cue} + * The pattern that is used to extract the tags is specified in SSA v4+ specs and + * has the following form: "{\...}". + *

+ * "All override codes appear within braces {}" + * "All override codes are always preceded by a backslash \" + * + * @param cue Cue text + * @return {@link SubtitleTagResult} that holds new cue and also the extracted tags + */ + private SubtitleTagResult extractTags(StringBuilder cue) { + StringBuilder cueCopy = new StringBuilder(cue.toString()); + List tags = new ArrayList<>(); + + int replacedCharacters = 0; + + Matcher matcher = SUBRIP_TAG_PATTERN.matcher(cue.toString()); + while (matcher.find()) { + String tag = matcher.group(); + tags.add(tag); + cueCopy.replace(matcher.start() - replacedCharacters, matcher.end() - replacedCharacters, ""); + replacedCharacters += tag.length(); + } + + return new SubtitleTagResult(tags.toArray(new String[tags.size()]), cueCopy.toString()); + } + + /** + * Match the alignment tag and calculate the line, position, position anchor accordingly + * + * Based on SSA v4+ specs the alignment tag can have the following form: {\an[1-9}, + * where the number specifies the direction (based on the numpad layout). + * Note. older SSA scripts may contain tags like {\a1[1-9]} but these are based on + * other direction rules, but multiple sources says that these are deprecated, so no support here either + * + * @param tag Alignment tag + * @return {@link AlignmentResult} that holds the line, position, position anchor values + */ + private AlignmentResult getAlignmentValues(String tag) { + // Default values used for positioning the subtitle in case of align tags + float line = DEFAULT_END_FRACTION, position = DEFAULT_MID_FRACTION; + @Cue.AnchorType int positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; + @Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_END; + + switch (tag) { + case ALIGN_BOTTOM_LEFT: + line = DEFAULT_END_FRACTION; + position = DEFAULT_START_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_START; + lineAnchor = Cue.ANCHOR_TYPE_END; + break; + case ALIGN_BOTTOM_MID: + line = DEFAULT_END_FRACTION; + position = DEFAULT_MID_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; + lineAnchor = Cue.ANCHOR_TYPE_END; + break; + case ALIGN_BOTTOM_RIGHT: + line = DEFAULT_END_FRACTION; + position = DEFAULT_END_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_END; + lineAnchor = Cue.ANCHOR_TYPE_END; + break; + case ALIGN_MID_LEFT: + line = DEFAULT_MID_FRACTION; + position = DEFAULT_START_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_START; + lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; + break; + case ALIGN_MID_MID: + line = DEFAULT_MID_FRACTION; + position = DEFAULT_MID_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; + lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; + break; + case ALIGN_MID_RIGHT: + line = DEFAULT_MID_FRACTION; + position = DEFAULT_END_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_END; + lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; + break; + case ALIGN_TOP_LEFT: + line = DEFAULT_START_FRACTION; + position = DEFAULT_START_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_START; + lineAnchor = Cue.ANCHOR_TYPE_START; + break; + case ALIGN_TOP_MID: + line = DEFAULT_START_FRACTION; + position = DEFAULT_MID_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; + lineAnchor = Cue.ANCHOR_TYPE_START; + break; + case ALIGN_TOP_RIGHT: + line = DEFAULT_START_FRACTION; + position = DEFAULT_END_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_END; + lineAnchor = Cue.ANCHOR_TYPE_START; + break; + } + + return new AlignmentResult(positionAnchor, position, lineAnchor, line); + } + private static long parseTimecode(Matcher matcher, int groupOffset) { long timestampMs = Long.parseLong(matcher.group(groupOffset + 1)) * 60 * 60 * 1000; timestampMs += Long.parseLong(matcher.group(groupOffset + 2)) * 60 * 1000; @@ -116,4 +282,35 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { return timestampMs * 1000; } + /** + * Class that holds the tags, new clue after the tag extraction + */ + private static final class SubtitleTagResult { + public final String[] tags; + public final String cue; + + public SubtitleTagResult(String[] tags, String cue) { + this.tags = tags; + this.cue = cue; + } + } + + /** + * Class that holds the parsed and mapped alignment values (such as line, + * position and anchor type of line) + */ + private static final class AlignmentResult { + + public @Cue.AnchorType int positionAnchor; + public @Cue.AnchorType int lineAnchor; + public float position, line; + + public AlignmentResult(@Cue.AnchorType int positionAnchor, float position, @Cue.AnchorType int lineAnchor, float line) { + this.positionAnchor = positionAnchor; + this.position = position; + this.line = line; + this.lineAnchor = lineAnchor; + } + } + } diff --git a/library/core/src/test/assets/subrip/typical_with_tags b/library/core/src/test/assets/subrip/typical_with_tags new file mode 100644 index 0000000000..02e1ffbcd9 --- /dev/null +++ b/library/core/src/test/assets/subrip/typical_with_tags @@ -0,0 +1,20 @@ +1 +00:00:00,000 --> 00:00:01,234 +This is {\an1} the first subtitle. + +2 +00:00:02,345 --> 00:00:03,456 +This is the second subtitle. +Second {\ an 2} subtitle with second line. + +3 +00:00:04,567 --> 00:00:08,901 +This {\an2} is the third {\ tag} subtitle. + +4 +00:00:09,567 --> 00:00:12,901 +This { \an2} is the fourth subtitle. + +5 +00:00:013,567 --> 00:00:14,901 +This {\an2} is the fifth subtitle with multiple {\xyz} valid {\qwe} tags. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java index e9abaca075..a9d69076c2 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java @@ -36,6 +36,7 @@ public final class SubripDecoderTest { private static final String TYPICAL_MISSING_SEQUENCE = "subrip/typical_missing_sequence"; private static final String TYPICAL_NEGATIVE_TIMESTAMPS = "subrip/typical_negative_timestamps"; private static final String TYPICAL_UNEXPECTED_END = "subrip/typical_unexpected_end"; + private static final String TYPICAL_WITH_TAGS = "subrip/typical_with_tags"; private static final String NO_END_TIMECODES_FILE = "subrip/no_end_timecodes"; @Test @@ -154,6 +155,27 @@ public final class SubripDecoderTest { .isEqualTo("Or to the end of the media."); } + @Test + public void testDecodeCueWithTag() throws IOException{ + SubripDecoder decoder = new SubripDecoder(); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_WITH_TAGS); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); + assertThat(subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString()) + .isEqualTo("This is the first subtitle."); + assertThat(subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString()) + .isEqualTo("This is the second subtitle.\nSecond subtitle with second line."); + assertThat(subtitle.getCues(subtitle.getEventTime(4)).get(0).text.toString()) + .isEqualTo("This is the third subtitle."); + + // Based on the SSA v4+ specs the curly bracket must be followed by a backslash, so this is + // not a valid tag (won't be parsed / replaced) + assertThat(subtitle.getCues(subtitle.getEventTime(6)).get(0).text.toString()) + .isEqualTo("This { \\an2} is the fourth subtitle."); + + assertThat(subtitle.getCues(subtitle.getEventTime(8)).get(0).text.toString()) + .isEqualTo("This is the fifth subtitle with multiple valid tags."); + } + private static void assertTypicalCue1(SubripSubtitle subtitle, int eventIndex) { assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0); assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString()) From 4f834e7e1ae8cc119b9e8e24e51b7b427ab74339 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Thu, 6 Sep 2018 11:11:59 +0200 Subject: [PATCH 002/832] added maxFrameRate to parameters and use it in the adaptive track selection filtering --- .../trackselection/DefaultTrackSelector.java | 43 +++++++++++++------ .../DefaultTrackSelectorTest.java | 3 +- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index a5c4123b10..58abddc677 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -181,6 +181,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { private int viewportHeight; private boolean viewportOrientationMayChange; private int tunnelingAudioSessionId; + private int maxFrameRate; /** Creates a builder with default initial values. */ public ParametersBuilder() { @@ -211,6 +212,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { viewportHeight = initialValues.viewportHeight; viewportOrientationMayChange = initialValues.viewportOrientationMayChange; tunnelingAudioSessionId = initialValues.tunnelingAudioSessionId; + maxFrameRate = initialValues.maxFrameRate; } /** @@ -544,7 +546,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { viewportWidth, viewportHeight, viewportOrientationMayChange, - tunnelingAudioSessionId); + tunnelingAudioSessionId, + maxFrameRate); } private static SparseArray> cloneSelectionOverrides( @@ -641,6 +644,10 @@ public class DefaultTrackSelector extends MappingTrackSelector { * The default value is {@code true}. */ public final boolean viewportOrientationMayChange; + /** + * Maximum video frame rate. The default value is {@link Integer#MAX_VALUE} (i.e. no constraint). + */ + public final int maxFrameRate; // General /** @@ -700,7 +707,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { /* viewportWidth= */ Integer.MAX_VALUE, /* viewportHeight= */ Integer.MAX_VALUE, /* viewportOrientationMayChange= */ true, - /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET); + /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET, + /* maxFrameRate= */ Integer.MAX_VALUE); } /* package */ Parameters( @@ -722,7 +730,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { int viewportWidth, int viewportHeight, boolean viewportOrientationMayChange, - int tunnelingAudioSessionId) { + int tunnelingAudioSessionId, + int maxFrameRate) { this.selectionOverrides = selectionOverrides; this.rendererDisabledFlags = rendererDisabledFlags; this.preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); @@ -742,6 +751,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.viewportHeight = viewportHeight; this.viewportOrientationMayChange = viewportOrientationMayChange; this.tunnelingAudioSessionId = tunnelingAudioSessionId; + this.maxFrameRate = maxFrameRate; } /* package */ Parameters(Parcel in) { @@ -764,6 +774,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.viewportHeight = in.readInt(); this.viewportOrientationMayChange = Util.readBoolean(in); this.tunnelingAudioSessionId = in.readInt(); + this.maxFrameRate = in.readInt(); } /** @@ -832,6 +843,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { && viewportHeight == other.viewportHeight && maxVideoBitrate == other.maxVideoBitrate && tunnelingAudioSessionId == other.tunnelingAudioSessionId + && maxFrameRate == other.maxFrameRate && TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage) && TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage) && areRendererDisabledFlagsEqual(rendererDisabledFlags, other.rendererDisabledFlags) @@ -855,6 +867,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { result = 31 * result + viewportHeight; result = 31 * result + maxVideoBitrate; result = 31 * result + tunnelingAudioSessionId; + result = 31 * result + maxFrameRate; result = 31 * result + (preferredAudioLanguage == null ? 0 : preferredAudioLanguage.hashCode()); result = 31 * result + (preferredTextLanguage == null ? 0 : preferredTextLanguage.hashCode()); @@ -889,6 +902,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { dest.writeInt(viewportHeight); Util.writeBoolean(dest, viewportOrientationMayChange); dest.writeInt(tunnelingAudioSessionId); + dest.writeInt(maxFrameRate); } public static final Parcelable.Creator CREATOR = @@ -1418,7 +1432,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { TrackGroup group = groups.get(i); int[] adaptiveTracks = getAdaptiveVideoTracksForGroup(group, formatSupport[i], allowMixedMimeTypes, requiredAdaptiveSupport, params.maxVideoWidth, params.maxVideoHeight, - params.maxVideoBitrate, params.viewportWidth, params.viewportHeight, + params.maxVideoBitrate, params.maxFrameRate, params.viewportWidth, params.viewportHeight, params.viewportOrientationMayChange); if (adaptiveTracks.length > 0) { return Assertions.checkNotNull(adaptiveTrackSelectionFactory) @@ -1430,8 +1444,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { private static int[] getAdaptiveVideoTracksForGroup(TrackGroup group, int[] formatSupport, boolean allowMixedMimeTypes, int requiredAdaptiveSupport, int maxVideoWidth, - int maxVideoHeight, int maxVideoBitrate, int viewportWidth, int viewportHeight, - boolean viewportOrientationMayChange) { + int maxVideoHeight, int maxVideoBitrate, int maxFrameRate, int viewportWidth, + int viewportHeight, boolean viewportOrientationMayChange) { if (group.length < 2) { return NO_TRACKS; } @@ -1453,7 +1467,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { if (seenMimeTypes.add(sampleMimeType)) { int countForMimeType = getAdaptiveVideoTrackCountForMimeType(group, formatSupport, requiredAdaptiveSupport, sampleMimeType, maxVideoWidth, maxVideoHeight, - maxVideoBitrate, selectedTrackIndices); + maxVideoBitrate, maxFrameRate, selectedTrackIndices); if (countForMimeType > selectedMimeTypeTrackCount) { selectedMimeType = sampleMimeType; selectedMimeTypeTrackCount = countForMimeType; @@ -1464,7 +1478,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Filter by the selected mime type. filterAdaptiveVideoTrackCountForMimeType(group, formatSupport, requiredAdaptiveSupport, - selectedMimeType, maxVideoWidth, maxVideoHeight, maxVideoBitrate, selectedTrackIndices); + selectedMimeType, maxVideoWidth, maxVideoHeight, maxVideoBitrate, maxFrameRate, + selectedTrackIndices); return selectedTrackIndices.size() < 2 ? NO_TRACKS : Util.toArray(selectedTrackIndices); } @@ -1477,13 +1492,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, + int maxFrameRate, List selectedTrackIndices) { int adaptiveTrackCount = 0; for (int i = 0; i < selectedTrackIndices.size(); i++) { int trackIndex = selectedTrackIndices.get(i); if (isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType, formatSupport[trackIndex], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, - maxVideoBitrate)) { + maxVideoBitrate, maxFrameRate)) { adaptiveTrackCount++; } } @@ -1498,12 +1514,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, + int maxFrameRate, List selectedTrackIndices) { for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) { int trackIndex = selectedTrackIndices.get(i); if (!isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType, formatSupport[trackIndex], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, - maxVideoBitrate)) { + maxVideoBitrate, maxFrameRate)) { selectedTrackIndices.remove(i); } } @@ -1516,12 +1533,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { int requiredAdaptiveSupport, int maxVideoWidth, int maxVideoHeight, - int maxVideoBitrate) { + int maxVideoBitrate, + int maxFrameRate) { return isSupported(formatSupport, false) && ((formatSupport & requiredAdaptiveSupport) != 0) && (mimeType == null || Util.areEqual(format.sampleMimeType, mimeType)) && (format.width == Format.NO_VALUE || format.width <= maxVideoWidth) && (format.height == Format.NO_VALUE || format.height <= maxVideoHeight) - && (format.bitrate == Format.NO_VALUE || format.bitrate <= maxVideoBitrate); + && (format.bitrate == Format.NO_VALUE || format.bitrate <= maxVideoBitrate) + && (format.frameRate == Format.NO_VALUE || format.frameRate <= maxFrameRate); } private static @Nullable TrackSelection selectFixedVideoTrack( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 86d810989f..98c4e134dc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -158,7 +158,8 @@ public final class DefaultTrackSelectorTest { /* viewportWidth= */ 4, /* viewportHeight= */ 5, /* viewportOrientationMayChange= */ false, - /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET); + /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET, + /* maxFrameRate= */ 6); Parcel parcel = Parcel.obtain(); parametersToParcel.writeToParcel(parcel, 0); From 4c0041f93680827358c2fbf6f0f0198422b10260 Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Thu, 6 Sep 2018 11:13:03 +0200 Subject: [PATCH 003/832] added maxFrameRate to the filtering done when selecting a fixed video track --- .../exoplayer2/trackselection/DefaultTrackSelector.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 58abddc677..2e3147ca33 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -1562,7 +1562,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { boolean isWithinConstraints = selectedTrackIndices.contains(trackIndex) && (format.width == Format.NO_VALUE || format.width <= params.maxVideoWidth) && (format.height == Format.NO_VALUE || format.height <= params.maxVideoHeight) - && (format.bitrate == Format.NO_VALUE || format.bitrate <= params.maxVideoBitrate); + && (format.bitrate == Format.NO_VALUE || format.bitrate <= params.maxVideoBitrate) + && (format.frameRate == Format.NO_VALUE || format.frameRate <= params.maxFrameRate); if (!isWithinConstraints && !params.exceedVideoConstraintsIfNecessary) { // Track should not be selected. continue; From 14f7b6111b99fb5507a60b40584bac1ce5db49dd Mon Sep 17 00:00:00 2001 From: Nicklas Lundin Date: Thu, 6 Sep 2018 16:17:27 +0200 Subject: [PATCH 004/832] Added setMaxFrameRate to ParametersBuilder --- .../trackselection/DefaultTrackSelector.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 2e3147ca33..54018aedb5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -523,6 +523,16 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } + /** + * See {@link Parameters#maxFrameRate}. + * + * @return This builder. + */ + public ParametersBuilder setMaxFrameRate(int maxFrameRate) { + this.maxFrameRate = maxFrameRate; + return this; + } + /** * Builds a {@link Parameters} instance with the selected values. */ From b07eef6d440740163c09a6cc9f5a68d2d9e0b689 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 19 Sep 2018 10:27:11 -0700 Subject: [PATCH 005/832] Fix drm nullability warnings. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=213650027 --- .../exoplayer2/drm/DefaultDrmSession.java | 84 ++++++++++------- .../drm/DefaultDrmSessionManager.java | 94 ++++++++++--------- .../android/exoplayer2/drm/DrmInitData.java | 31 +++--- .../android/exoplayer2/drm/DrmSession.java | 13 ++- .../exoplayer2/drm/ErrorStateDrmSession.java | 9 +- .../exoplayer2/drm/FrameworkMediaDrm.java | 14 +-- .../exoplayer2/drm/HttpMediaDrmCallback.java | 16 +++- .../exoplayer2/drm/OfflineLicenseHelper.java | 31 +++--- .../android/exoplayer2/drm/WidevineUtil.java | 4 +- .../drm/OfflineLicenseHelperTest.java | 9 +- 10 files changed, 179 insertions(+), 126 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java index 0b1b4ba380..25546acb95 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java @@ -28,14 +28,19 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.EventDispatcher; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * A {@link DrmSession} that supports playbacks using {@link ExoMediaDrm}. @@ -84,7 +89,7 @@ import java.util.UUID; private final ExoMediaDrm mediaDrm; private final ProvisioningManager provisioningManager; private final @DefaultDrmSessionManager.Mode int mode; - private final HashMap optionalKeyRequestParameters; + private final @Nullable HashMap optionalKeyRequestParameters; private final EventDispatcher eventDispatcher; private final int initialDrmRequestRetryCount; @@ -96,13 +101,13 @@ import java.util.UUID; private int openCount; private HandlerThread requestHandlerThread; private PostRequestHandler postRequestHandler; - private T mediaCrypto; - private DrmSessionException lastException; - private byte[] sessionId; - private @Nullable byte[] offlineLicenseKeySetId; + private @Nullable T mediaCrypto; + private @Nullable DrmSessionException lastException; + private byte @MonotonicNonNull [] sessionId; + private byte @MonotonicNonNull [] offlineLicenseKeySetId; - private KeyRequest currentKeyRequest; - private ProvisionRequest currentProvisionRequest; + private @Nullable KeyRequest currentKeyRequest; + private @Nullable ProvisionRequest currentProvisionRequest; /** * Instantiates a new DRM session. @@ -129,18 +134,25 @@ import java.util.UUID; @Nullable List schemeDatas, @DefaultDrmSessionManager.Mode int mode, @Nullable byte[] offlineLicenseKeySetId, - HashMap optionalKeyRequestParameters, + @Nullable HashMap optionalKeyRequestParameters, MediaDrmCallback callback, Looper playbackLooper, EventDispatcher eventDispatcher, int initialDrmRequestRetryCount) { + if (mode == DefaultDrmSessionManager.MODE_QUERY + || mode == DefaultDrmSessionManager.MODE_RELEASE) { + Assertions.checkNotNull(offlineLicenseKeySetId); + } this.uuid = uuid; this.provisioningManager = provisioningManager; this.mediaDrm = mediaDrm; this.mode = mode; - this.offlineLicenseKeySetId = offlineLicenseKeySetId; - this.schemeDatas = - offlineLicenseKeySetId == null ? Collections.unmodifiableList(schemeDatas) : null; + if (offlineLicenseKeySetId != null) { + this.offlineLicenseKeySetId = offlineLicenseKeySetId; + this.schemeDatas = null; + } else { + this.schemeDatas = Collections.unmodifiableList(Assertions.checkNotNull(schemeDatas)); + } this.optionalKeyRequestParameters = optionalKeyRequestParameters; this.callback = callback; this.initialDrmRequestRetryCount = initialDrmRequestRetryCount; @@ -166,9 +178,9 @@ import java.util.UUID; } } - /** - * @return True if the session is closed and cleaned up, false otherwise. - */ + /** @return True if the session is closed and cleaned up, false otherwise. */ + // Assigning null to various non-null variables for clean-up. Class won't be used after release. + @SuppressWarnings("assignment.type.incompatible") public boolean release() { if (--openCount == 0) { state = STATE_RELEASED; @@ -245,33 +257,35 @@ import java.util.UUID; } @Override - public final DrmSessionException getError() { + public final @Nullable DrmSessionException getError() { return state == STATE_ERROR ? lastException : null; } @Override - public final T getMediaCrypto() { + public final @Nullable T getMediaCrypto() { return mediaCrypto; } @Override - public Map queryKeyStatus() { + public @Nullable Map queryKeyStatus() { return sessionId == null ? null : mediaDrm.queryKeyStatus(sessionId); } @Override - public byte[] getOfflineLicenseKeySetId() { + public @Nullable byte[] getOfflineLicenseKeySetId() { return offlineLicenseKeySetId; } // Internal methods. /** - * Try to open a session, do provisioning if necessary. - * @param allowProvisioning if provisioning is allowed, set this to false when calling from - * processing provision response. - * @return true on success, false otherwise. + * Try to open a session, do provisioning if necessary. + * + * @param allowProvisioning if provisioning is allowed, set this to false when calling from + * processing provision response. + * @return true on success, false otherwise. */ + @EnsuresNonNullIf(result = true, expression = "sessionId") private boolean openInternal(boolean allowProvisioning) { if (isOpen()) { // Already opened @@ -319,19 +333,20 @@ import java.util.UUID; provisioningManager.onProvisionCompleted(); } + @RequiresNonNull("sessionId") private void doLicense(boolean allowRetry) { switch (mode) { case DefaultDrmSessionManager.MODE_PLAYBACK: case DefaultDrmSessionManager.MODE_QUERY: if (offlineLicenseKeySetId == null) { - postKeyRequest(ExoMediaDrm.KEY_TYPE_STREAMING, allowRetry); + postKeyRequest(sessionId, ExoMediaDrm.KEY_TYPE_STREAMING, allowRetry); } else if (state == STATE_OPENED_WITH_KEYS || restoreKeys()) { long licenseDurationRemainingSec = getLicenseDurationRemainingSec(); if (mode == DefaultDrmSessionManager.MODE_PLAYBACK && licenseDurationRemainingSec <= MAX_LICENSE_DURATION_TO_RENEW) { Log.d(TAG, "Offline license has expired or will expire soon. " + "Remaining seconds: " + licenseDurationRemainingSec); - postKeyRequest(ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); + postKeyRequest(sessionId, ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); } else if (licenseDurationRemainingSec <= 0) { onError(new KeysExpiredException()); } else { @@ -342,19 +357,20 @@ import java.util.UUID; break; case DefaultDrmSessionManager.MODE_DOWNLOAD: if (offlineLicenseKeySetId == null) { - postKeyRequest(ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); + postKeyRequest(sessionId, ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); } else { // Renew if (restoreKeys()) { - postKeyRequest(ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); + postKeyRequest(sessionId, ExoMediaDrm.KEY_TYPE_OFFLINE, allowRetry); } } break; case DefaultDrmSessionManager.MODE_RELEASE: + Assertions.checkNotNull(offlineLicenseKeySetId); // It's not necessary to restore the key (and open a session to do that) before releasing it // but this serves as a good sanity/fast-failure check. if (restoreKeys()) { - postKeyRequest(ExoMediaDrm.KEY_TYPE_RELEASE, allowRetry); + postKeyRequest(offlineLicenseKeySetId, ExoMediaDrm.KEY_TYPE_RELEASE, allowRetry); } break; default: @@ -362,6 +378,7 @@ import java.util.UUID; } } + @RequiresNonNull({"sessionId", "offlineLicenseKeySetId"}) private boolean restoreKeys() { try { mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId); @@ -377,12 +394,12 @@ import java.util.UUID; if (!C.WIDEVINE_UUID.equals(uuid)) { return Long.MAX_VALUE; } - Pair pair = WidevineUtil.getLicenseDurationRemainingSec(this); + Pair pair = + Assertions.checkNotNull(WidevineUtil.getLicenseDurationRemainingSec(this)); return Math.min(pair.first, pair.second); } - private void postKeyRequest(int type, boolean allowRetry) { - byte[] scope = type == ExoMediaDrm.KEY_TYPE_RELEASE ? offlineLicenseKeySetId : sessionId; + private void postKeyRequest(byte[] scope, int type, boolean allowRetry) { try { currentKeyRequest = mediaDrm.getKeyRequest(scope, schemeDatas, type, optionalKeyRequestParameters); @@ -407,7 +424,7 @@ import java.util.UUID; try { byte[] responseData = (byte[]) response; if (mode == DefaultDrmSessionManager.MODE_RELEASE) { - mediaDrm.provideKeyResponse(offlineLicenseKeySetId, responseData); + mediaDrm.provideKeyResponse(Util.castNonNull(offlineLicenseKeySetId), responseData); eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmKeysRestored); } else { byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, responseData); @@ -447,6 +464,8 @@ import java.util.UUID; } } + @EnsuresNonNullIf(result = true, expression = "sessionId") + @SuppressWarnings("contracts.conditional.postcondition.not.satisfied") private boolean isOpen() { return state == STATE_OPENED || state == STATE_OPENED_WITH_KEYS; } @@ -461,8 +480,9 @@ import java.util.UUID; } @Override + @SuppressWarnings("unchecked") public void handleMessage(Message msg) { - Pair requestAndResponse = (Pair) msg.obj; + Pair requestAndResponse = (Pair) msg.obj; Object request = requestAndResponse.first; Object response = requestAndResponse.second; switch (msg.what) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index 0e751ca49e..03269f0caa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -21,7 +21,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.annotation.IntDef; -import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.DefaultDrmSession.ProvisioningManager; @@ -94,7 +94,7 @@ public class DefaultDrmSessionManager implements DrmSe private final UUID uuid; private final ExoMediaDrm mediaDrm; private final MediaDrmCallback callback; - private final HashMap optionalKeyRequestParameters; + private final @Nullable HashMap optionalKeyRequestParameters; private final EventDispatcher eventDispatcher; private final boolean multiSession; private final int initialDrmRequestRetryCount; @@ -102,11 +102,11 @@ public class DefaultDrmSessionManager implements DrmSe private final List> sessions; private final List> provisioningSessions; - private Looper playbackLooper; + private @Nullable Looper playbackLooper; private int mode; - private byte[] offlineLicenseKeySetId; + private @Nullable byte[] offlineLicenseKeySetId; - /* package */ volatile MediaDrmHandler mediaDrmHandler; + /* package */ volatile @Nullable MediaDrmHandler mediaDrmHandler; /** * @deprecated Use {@link #newWidevineInstance(MediaDrmCallback, HashMap)} and {@link @@ -115,9 +115,9 @@ public class DefaultDrmSessionManager implements DrmSe @Deprecated public static DefaultDrmSessionManager newWidevineInstance( MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener) + @Nullable HashMap optionalKeyRequestParameters, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener) throws UnsupportedDrmException { DefaultDrmSessionManager drmSessionManager = newWidevineInstance(callback, optionalKeyRequestParameters); @@ -136,7 +136,7 @@ public class DefaultDrmSessionManager implements DrmSe * @throws UnsupportedDrmException If the specified DRM scheme is not supported. */ public static DefaultDrmSessionManager newWidevineInstance( - MediaDrmCallback callback, HashMap optionalKeyRequestParameters) + MediaDrmCallback callback, @Nullable HashMap optionalKeyRequestParameters) throws UnsupportedDrmException { return newFrameworkInstance(C.WIDEVINE_UUID, callback, optionalKeyRequestParameters); } @@ -148,9 +148,9 @@ public class DefaultDrmSessionManager implements DrmSe @Deprecated public static DefaultDrmSessionManager newPlayReadyInstance( MediaDrmCallback callback, - String customData, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener) + @Nullable String customData, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener) throws UnsupportedDrmException { DefaultDrmSessionManager drmSessionManager = newPlayReadyInstance(callback, customData); @@ -171,7 +171,7 @@ public class DefaultDrmSessionManager implements DrmSe * @throws UnsupportedDrmException If the specified DRM scheme is not supported. */ public static DefaultDrmSessionManager newPlayReadyInstance( - MediaDrmCallback callback, String customData) throws UnsupportedDrmException { + MediaDrmCallback callback, @Nullable String customData) throws UnsupportedDrmException { HashMap optionalKeyRequestParameters; if (!TextUtils.isEmpty(customData)) { optionalKeyRequestParameters = new HashMap<>(); @@ -190,9 +190,9 @@ public class DefaultDrmSessionManager implements DrmSe public static DefaultDrmSessionManager newFrameworkInstance( UUID uuid, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener) + @Nullable HashMap optionalKeyRequestParameters, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener) throws UnsupportedDrmException { DefaultDrmSessionManager drmSessionManager = newFrameworkInstance(uuid, callback, optionalKeyRequestParameters); @@ -212,7 +212,9 @@ public class DefaultDrmSessionManager implements DrmSe * @throws UnsupportedDrmException If the specified DRM scheme is not supported. */ public static DefaultDrmSessionManager newFrameworkInstance( - UUID uuid, MediaDrmCallback callback, HashMap optionalKeyRequestParameters) + UUID uuid, + MediaDrmCallback callback, + @Nullable HashMap optionalKeyRequestParameters) throws UnsupportedDrmException { return new DefaultDrmSessionManager<>( uuid, @@ -228,13 +230,14 @@ public class DefaultDrmSessionManager implements DrmSe * and {@link #addListener(Handler, DefaultDrmSessionEventListener)}. */ @Deprecated + @SuppressWarnings("method.invocation.invalid") public DefaultDrmSessionManager( UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener) { + @Nullable HashMap optionalKeyRequestParameters, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener) { this(uuid, mediaDrm, callback, optionalKeyRequestParameters); if (eventHandler != null && eventListener != null) { addListener(eventHandler, eventListener); @@ -252,7 +255,7 @@ public class DefaultDrmSessionManager implements DrmSe UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters) { + @Nullable HashMap optionalKeyRequestParameters) { this( uuid, mediaDrm, @@ -267,13 +270,14 @@ public class DefaultDrmSessionManager implements DrmSe * boolean)} and {@link #addListener(Handler, DefaultDrmSessionEventListener)}. */ @Deprecated + @SuppressWarnings("method.invocation.invalid") public DefaultDrmSessionManager( UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener, + @Nullable HashMap optionalKeyRequestParameters, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener, boolean multiSession) { this(uuid, mediaDrm, callback, optionalKeyRequestParameters, multiSession); if (eventHandler != null && eventListener != null) { @@ -294,7 +298,7 @@ public class DefaultDrmSessionManager implements DrmSe UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, + @Nullable HashMap optionalKeyRequestParameters, boolean multiSession) { this( uuid, @@ -310,13 +314,14 @@ public class DefaultDrmSessionManager implements DrmSe * boolean, int)} and {@link #addListener(Handler, DefaultDrmSessionEventListener)}. */ @Deprecated + @SuppressWarnings("method.invocation.invalid") public DefaultDrmSessionManager( UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, - DefaultDrmSessionEventListener eventListener, + @Nullable HashMap optionalKeyRequestParameters, + @Nullable Handler eventHandler, + @Nullable DefaultDrmSessionEventListener eventListener, boolean multiSession, int initialDrmRequestRetryCount) { this( @@ -346,7 +351,7 @@ public class DefaultDrmSessionManager implements DrmSe UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, + @Nullable HashMap optionalKeyRequestParameters, boolean multiSession, int initialDrmRequestRetryCount) { Assertions.checkNotNull(uuid); @@ -443,21 +448,22 @@ public class DefaultDrmSessionManager implements DrmSe * required. * *

{@code mode} must be one of these: + * *

* * @param mode The mode to be set. * @param offlineLicenseKeySetId The key set id of the license to be used with the given mode. */ - public void setMode(@Mode int mode, byte[] offlineLicenseKeySetId) { + public void setMode(@Mode int mode, @Nullable byte[] offlineLicenseKeySetId) { Assertions.checkState(sessions.isEmpty()); if (mode == MODE_QUERY || mode == MODE_RELEASE) { Assertions.checkNotNull(offlineLicenseKeySetId); @@ -469,7 +475,7 @@ public class DefaultDrmSessionManager implements DrmSe // DrmSessionManager implementation. @Override - public boolean canAcquireSession(@NonNull DrmInitData drmInitData) { + public boolean canAcquireSession(DrmInitData drmInitData) { if (offlineLicenseKeySetId != null) { // An offline license can be restored so a session can always be acquired. return true; @@ -650,10 +656,14 @@ public class DefaultDrmSessionManager implements DrmSe private class MediaDrmEventListener implements OnEventListener { @Override - public void onEvent(ExoMediaDrm md, byte[] sessionId, int event, int extra, - byte[] data) { + public void onEvent( + ExoMediaDrm md, + byte[] sessionId, + int event, + int extra, + @Nullable byte[] data) { if (mode == DefaultDrmSessionManager.MODE_PLAYBACK) { - mediaDrmHandler.obtainMessage(event, sessionId).sendToTarget(); + Assertions.checkNotNull(mediaDrmHandler).obtainMessage(event, sessionId).sendToTarget(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java index b9415c74af..ef96c7ae75 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java @@ -85,10 +85,8 @@ public final class DrmInitData implements Comparator, Parcelable { // Lazily initialized hashcode. private int hashCode; - /** - * The protection scheme type, or null if not applicable or unknown. - */ - @Nullable public final String schemeType; + /** The protection scheme type, or null if not applicable or unknown. */ + public final @Nullable String schemeType; /** * Number of {@link SchemeData}s. @@ -106,7 +104,7 @@ public final class DrmInitData implements Comparator, Parcelable { * @param schemeType See {@link #schemeType}. * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ - public DrmInitData(String schemeType, List schemeDatas) { + public DrmInitData(@Nullable String schemeType, List schemeDatas) { this(schemeType, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()])); } @@ -131,11 +129,11 @@ public final class DrmInitData implements Comparator, Parcelable { if (cloneSchemeDatas) { schemeDatas = schemeDatas.clone(); } - // Sorting ensures that universal scheme data (i.e. data that applies to all schemes) is matched - // last. It's also required by the equals and hashcode implementations. - Arrays.sort(schemeDatas, this); this.schemeDatas = schemeDatas; schemeDataCount = schemeDatas.length; + // Sorting ensures that universal scheme data (i.e. data that applies to all schemes) is matched + // last. It's also required by the equals and hashcode implementations. + Arrays.sort(this.schemeDatas, this); } /* package */ DrmInitData(Parcel in) { @@ -152,7 +150,7 @@ public final class DrmInitData implements Comparator, Parcelable { * @return The initialization data for the scheme, or null if the scheme is not supported. */ @Deprecated - public SchemeData get(UUID uuid) { + public @Nullable SchemeData get(UUID uuid) { for (SchemeData schemeData : schemeDatas) { if (schemeData.matches(uuid)) { return schemeData; @@ -270,10 +268,8 @@ public final class DrmInitData implements Comparator, Parcelable { public final @Nullable String licenseServerUrl; /** The mimeType of {@link #data}. */ public final String mimeType; - /** - * The initialization data. May be null for scheme support checks only. - */ - public final byte[] data; + /** The initialization data. May be null for scheme support checks only. */ + public final @Nullable byte[] data; /** * Whether secure decryption is required. */ @@ -285,7 +281,7 @@ public final class DrmInitData implements Comparator, Parcelable { * @param mimeType See {@link #mimeType}. * @param data See {@link #data}. */ - public SchemeData(UUID uuid, String mimeType, byte[] data) { + public SchemeData(UUID uuid, String mimeType, @Nullable byte[] data) { this(uuid, mimeType, data, false); } @@ -296,7 +292,8 @@ public final class DrmInitData implements Comparator, Parcelable { * @param data See {@link #data}. * @param requiresSecureDecryption See {@link #requiresSecureDecryption}. */ - public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecureDecryption) { + public SchemeData( + UUID uuid, String mimeType, @Nullable byte[] data, boolean requiresSecureDecryption) { this(uuid, /* licenseServerUrl= */ null, mimeType, data, requiresSecureDecryption); } @@ -312,7 +309,7 @@ public final class DrmInitData implements Comparator, Parcelable { UUID uuid, @Nullable String licenseServerUrl, String mimeType, - byte[] data, + @Nullable byte[] data, boolean requiresSecureDecryption) { this.uuid = Assertions.checkNotNull(uuid); this.licenseServerUrl = licenseServerUrl; @@ -324,7 +321,7 @@ public final class DrmInitData implements Comparator, Parcelable { /* package */ SchemeData(Parcel in) { uuid = new UUID(in.readLong(), in.readLong()); licenseServerUrl = in.readString(); - mimeType = in.readString(); + mimeType = Util.castNonNull(in.readString()); data = in.createByteArray(); requiresSecureDecryption = in.readByte() != 0; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java index bed3545d78..698f6fdb25 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.drm; import android.annotation.TargetApi; import android.media.MediaDrm; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; @@ -75,21 +76,24 @@ public interface DrmSession { @State int getState(); /** - * Returns the cause of the error state. + * Returns the cause of the error state, or null if {@link #getState()} is not {@link + * #STATE_ERROR}. */ + @Nullable DrmSessionException getError(); /** * Returns a {@link ExoMediaCrypto} for the open session, or null if called before the session has * been opened or after it's been released. */ + @Nullable T getMediaCrypto(); /** * Returns a map describing the key status for the session, or null if called before the session * has been opened or after it's been released. - *

- * Since DRM license policies vary by vendor, the specific status field names are determined by + * + *

Since DRM license policies vary by vendor, the specific status field names are determined by * each DRM vendor. Refer to your DRM provider documentation for definitions of the field names * for a particular DRM engine plugin. * @@ -97,12 +101,13 @@ public interface DrmSession { * has been opened or after it's been released. * @see MediaDrm#queryKeyStatus(byte[]) */ + @Nullable Map queryKeyStatus(); /** * Returns the key set id of the offline license loaded into this session, or null if there isn't * one. */ + @Nullable byte[] getOfflineLicenseKeySetId(); - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java index d30e670c3f..f0335b50c9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.drm; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.util.Assertions; import java.util.Map; @@ -33,22 +34,22 @@ public final class ErrorStateDrmSession implements Drm } @Override - public DrmSessionException getError() { + public @Nullable DrmSessionException getError() { return error; } @Override - public T getMediaCrypto() { + public @Nullable T getMediaCrypto() { return null; } @Override - public Map queryKeyStatus() { + public @Nullable Map queryKeyStatus() { return null; } @Override - public byte[] getOfflineLicenseKeySetId() { + public @Nullable byte[] getOfflineLicenseKeySetId() { return null; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java index 254b524b30..1f862546ae 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java @@ -131,7 +131,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm= 23 && version == 1) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java index 8b8c3bec99..4ff0af3c0e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.drm; import android.annotation.TargetApi; import android.net.Uri; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; @@ -135,8 +136,12 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { return executePost(dataSourceFactory, url, request.getData(), requestProperties); } - private static byte[] executePost(HttpDataSource.Factory dataSourceFactory, String url, - byte[] data, Map requestProperties) throws IOException { + private static byte[] executePost( + HttpDataSource.Factory dataSourceFactory, + String url, + byte[] data, + @Nullable Map requestProperties) + throws IOException { HttpDataSource dataSource = dataSourceFactory.createDataSource(); if (requestProperties != null) { for (Map.Entry requestProperty : requestProperties.entrySet()) { @@ -164,17 +169,18 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { boolean manuallyRedirect = (e.responseCode == 307 || e.responseCode == 308) && manualRedirectCount++ < MAX_MANUAL_REDIRECTS; - url = manuallyRedirect ? getRedirectUrl(e) : null; - if (url == null) { + String redirectUrl = manuallyRedirect ? getRedirectUrl(e) : null; + if (redirectUrl == null) { throw e; } + url = redirectUrl; } finally { Util.closeQuietly(inputStream); } } } - private static String getRedirectUrl(InvalidResponseCodeException exception) { + private static @Nullable String getRedirectUrl(InvalidResponseCodeException exception) { Map> headerFields = exception.headerFields; if (headerFields != null) { List locationHeaders = headerFields.get("Location"); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java index 9298c16cb0..d596b4ab90 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java @@ -19,6 +19,7 @@ import android.media.MediaDrm; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; +import android.support.annotation.Nullable; import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager.Mode; @@ -34,6 +35,8 @@ import java.util.UUID; */ public final class OfflineLicenseHelper { + private static final DrmInitData DUMMY_DRM_INIT_DATA = new DrmInitData(); + private final ConditionVariable conditionVariable; private final DefaultDrmSessionManager drmSessionManager; private final HandlerThread handlerThread; @@ -95,7 +98,7 @@ public final class OfflineLicenseHelper { String defaultLicenseUrl, boolean forceDefaultLicenseUrl, Factory httpDataSourceFactory, - HashMap optionalKeyRequestParameters) + @Nullable HashMap optionalKeyRequestParameters) throws UnsupportedDrmException { return new OfflineLicenseHelper<>(C.WIDEVINE_UUID, FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID), @@ -118,7 +121,7 @@ public final class OfflineLicenseHelper { UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters) { + @Nullable HashMap optionalKeyRequestParameters) { handlerThread = new HandlerThread("OfflineLicenseHelper"); handlerThread.start(); conditionVariable = new ConditionVariable(); @@ -199,7 +202,8 @@ public final class OfflineLicenseHelper { public synchronized byte[] renewLicense(byte[] offlineLicenseKeySetId) throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); - return blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, null); + return blockingKeyRequest( + DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, DUMMY_DRM_INIT_DATA); } /** @@ -211,7 +215,8 @@ public final class OfflineLicenseHelper { public synchronized void releaseLicense(byte[] offlineLicenseKeySetId) throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); - blockingKeyRequest(DefaultDrmSessionManager.MODE_RELEASE, offlineLicenseKeySetId, null); + blockingKeyRequest( + DefaultDrmSessionManager.MODE_RELEASE, offlineLicenseKeySetId, DUMMY_DRM_INIT_DATA); } /** @@ -224,8 +229,9 @@ public final class OfflineLicenseHelper { public synchronized Pair getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId) throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); - DrmSession drmSession = openBlockingKeyRequest(DefaultDrmSessionManager.MODE_QUERY, - offlineLicenseKeySetId, null); + DrmSession drmSession = + openBlockingKeyRequest( + DefaultDrmSessionManager.MODE_QUERY, offlineLicenseKeySetId, DUMMY_DRM_INIT_DATA); DrmSessionException error = drmSession.getError(); Pair licenseDurationRemainingSec = WidevineUtil.getLicenseDurationRemainingSec(drmSession); @@ -236,7 +242,7 @@ public final class OfflineLicenseHelper { } throw error; } - return licenseDurationRemainingSec; + return Assertions.checkNotNull(licenseDurationRemainingSec); } /** @@ -246,8 +252,9 @@ public final class OfflineLicenseHelper { handlerThread.quit(); } - private byte[] blockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, - DrmInitData drmInitData) throws DrmSessionException { + private byte[] blockingKeyRequest( + @Mode int licenseMode, @Nullable byte[] offlineLicenseKeySetId, DrmInitData drmInitData) + throws DrmSessionException { DrmSession drmSession = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId, drmInitData); DrmSessionException error = drmSession.getError(); @@ -256,11 +263,11 @@ public final class OfflineLicenseHelper { if (error != null) { throw error; } - return keySetId; + return Assertions.checkNotNull(keySetId); } - private DrmSession openBlockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, - DrmInitData drmInitData) { + private DrmSession openBlockingKeyRequest( + @Mode int licenseMode, @Nullable byte[] offlineLicenseKeySetId, DrmInitData drmInitData) { drmSessionManager.setMode(licenseMode, offlineLicenseKeySetId); conditionVariable.close(); DrmSession drmSession = drmSessionManager.acquireSession(handlerThread.getLooper(), diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java index 45c38b3609..b8b80490a1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.drm; +import android.support.annotation.Nullable; import android.util.Pair; import com.google.android.exoplayer2.C; import java.util.Map; @@ -38,7 +39,8 @@ public final class WidevineUtil { * @return A {@link Pair} consisting of the remaining license and playback durations in seconds, * or null if called before the session has been opened or after it's been released. */ - public static Pair getLicenseDurationRemainingSec(DrmSession drmSession) { + public static @Nullable Pair getLicenseDurationRemainingSec( + DrmSession drmSession) { Map keyStatus = drmSession.queryKeyStatus(); if (keyStatus == null) { return null; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java index 6f62b7fcfc..bba487f321 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java @@ -92,9 +92,12 @@ public class OfflineLicenseHelperTest { public void testDownloadLicenseFailsIfNoKeySetIdIsReturned() throws Exception { setStubLicenseAndPlaybackDurationValues(1000, 200); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); - - assertThat(offlineLicenseKeySetId).isNull(); + try { + offlineLicenseHelper.downloadLicense(newDrmInitData()); + fail(); + } catch (Exception e) { + // Expected. + } } @Test From 4b33d3c8a235dfca892d987618d577ff20967e98 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 20 Sep 2018 12:46:31 +0100 Subject: [PATCH 006/832] Cleanup --- .../trackselection/DefaultTrackSelector.java | 165 ++++++++++++------ .../DefaultTrackSelectorTest.java | 10 +- 2 files changed, 112 insertions(+), 63 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index dd382d77b1..f5a347b351 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -174,6 +174,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { private boolean allowNonSeamlessAdaptiveness; private int maxVideoWidth; private int maxVideoHeight; + private int maxVideoFrameRate; private int maxVideoBitrate; private boolean exceedVideoConstraintsIfNecessary; private boolean exceedRendererCapabilitiesIfNecessary; @@ -181,7 +182,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { private int viewportHeight; private boolean viewportOrientationMayChange; private int tunnelingAudioSessionId; - private int maxFrameRate; /** Creates a builder with default initial values. */ public ParametersBuilder() { @@ -205,6 +205,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { allowNonSeamlessAdaptiveness = initialValues.allowNonSeamlessAdaptiveness; maxVideoWidth = initialValues.maxVideoWidth; maxVideoHeight = initialValues.maxVideoHeight; + maxVideoFrameRate = initialValues.maxVideoFrameRate; maxVideoBitrate = initialValues.maxVideoBitrate; exceedVideoConstraintsIfNecessary = initialValues.exceedVideoConstraintsIfNecessary; exceedRendererCapabilitiesIfNecessary = initialValues.exceedRendererCapabilitiesIfNecessary; @@ -212,7 +213,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { viewportHeight = initialValues.viewportHeight; viewportOrientationMayChange = initialValues.viewportOrientationMayChange; tunnelingAudioSessionId = initialValues.tunnelingAudioSessionId; - maxFrameRate = initialValues.maxFrameRate; } /** @@ -326,6 +326,16 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } + /** + * See {@link Parameters#maxVideoFrameRate}. + * + * @return This builder. + */ + public ParametersBuilder setMaxVideoFrameRate(int maxVideoFrameRate) { + this.maxVideoFrameRate = maxVideoFrameRate; + return this; + } + /** * See {@link Parameters#maxVideoBitrate}. * @@ -523,16 +533,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } - /** - * See {@link Parameters#maxFrameRate}. - * - * @return This builder. - */ - public ParametersBuilder setMaxFrameRate(int maxFrameRate) { - this.maxFrameRate = maxFrameRate; - return this; - } - /** * Builds a {@link Parameters} instance with the selected values. */ @@ -550,14 +550,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + maxVideoFrameRate, maxVideoBitrate, exceedVideoConstraintsIfNecessary, exceedRendererCapabilitiesIfNecessary, viewportWidth, viewportHeight, viewportOrientationMayChange, - tunnelingAudioSessionId, - maxFrameRate); + tunnelingAudioSessionId); } private static SparseArray> cloneSelectionOverrides( @@ -626,6 +626,11 @@ public class DefaultTrackSelector extends MappingTrackSelector { * #viewportHeight} and {@link #viewportOrientationMayChange}) instead. */ public final int maxVideoHeight; + /** + * Maximum allowed video frame rate. The default value is {@link Integer#MAX_VALUE} (i.e. no + * constraint). + */ + public final int maxVideoFrameRate; /** * Maximum video bitrate. The default value is {@link Integer#MAX_VALUE} (i.e. no constraint). */ @@ -654,10 +659,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { * The default value is {@code true}. */ public final boolean viewportOrientationMayChange; - /** - * Maximum video frame rate. The default value is {@link Integer#MAX_VALUE} (i.e. no constraint). - */ - public final int maxFrameRate; // General /** @@ -711,14 +712,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { /* allowNonSeamlessAdaptiveness= */ true, /* maxVideoWidth= */ Integer.MAX_VALUE, /* maxVideoHeight= */ Integer.MAX_VALUE, + /* maxVideoFrameRate= */ Integer.MAX_VALUE, /* maxVideoBitrate= */ Integer.MAX_VALUE, /* exceedVideoConstraintsIfNecessary= */ true, /* exceedRendererCapabilitiesIfNecessary= */ true, /* viewportWidth= */ Integer.MAX_VALUE, /* viewportHeight= */ Integer.MAX_VALUE, /* viewportOrientationMayChange= */ true, - /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET, - /* maxFrameRate= */ Integer.MAX_VALUE); + /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET); } /* package */ Parameters( @@ -734,14 +735,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { boolean allowNonSeamlessAdaptiveness, int maxVideoWidth, int maxVideoHeight, + int maxVideoFrameRate, int maxVideoBitrate, boolean exceedVideoConstraintsIfNecessary, boolean exceedRendererCapabilitiesIfNecessary, int viewportWidth, int viewportHeight, boolean viewportOrientationMayChange, - int tunnelingAudioSessionId, - int maxFrameRate) { + int tunnelingAudioSessionId) { this.selectionOverrides = selectionOverrides; this.rendererDisabledFlags = rendererDisabledFlags; this.preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); @@ -754,6 +755,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; this.maxVideoWidth = maxVideoWidth; this.maxVideoHeight = maxVideoHeight; + this.maxVideoFrameRate = maxVideoFrameRate; this.maxVideoBitrate = maxVideoBitrate; this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary; this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; @@ -761,7 +763,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.viewportHeight = viewportHeight; this.viewportOrientationMayChange = viewportOrientationMayChange; this.tunnelingAudioSessionId = tunnelingAudioSessionId; - this.maxFrameRate = maxFrameRate; } /* package */ Parameters(Parcel in) { @@ -777,6 +778,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.allowNonSeamlessAdaptiveness = Util.readBoolean(in); this.maxVideoWidth = in.readInt(); this.maxVideoHeight = in.readInt(); + this.maxVideoFrameRate = in.readInt(); this.maxVideoBitrate = in.readInt(); this.exceedVideoConstraintsIfNecessary = Util.readBoolean(in); this.exceedRendererCapabilitiesIfNecessary = Util.readBoolean(in); @@ -784,7 +786,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.viewportHeight = in.readInt(); this.viewportOrientationMayChange = Util.readBoolean(in); this.tunnelingAudioSessionId = in.readInt(); - this.maxFrameRate = in.readInt(); } /** @@ -846,6 +847,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { && allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness && maxVideoWidth == other.maxVideoWidth && maxVideoHeight == other.maxVideoHeight + && maxVideoFrameRate == other.maxVideoFrameRate && exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary && exceedRendererCapabilitiesIfNecessary == other.exceedRendererCapabilitiesIfNecessary && viewportOrientationMayChange == other.viewportOrientationMayChange @@ -853,7 +855,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { && viewportHeight == other.viewportHeight && maxVideoBitrate == other.maxVideoBitrate && tunnelingAudioSessionId == other.tunnelingAudioSessionId - && maxFrameRate == other.maxFrameRate && TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage) && TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage) && areRendererDisabledFlagsEqual(rendererDisabledFlags, other.rendererDisabledFlags) @@ -870,6 +871,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0); result = 31 * result + maxVideoWidth; result = 31 * result + maxVideoHeight; + result = 31 * result + maxVideoFrameRate; result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0); result = 31 * result + (exceedRendererCapabilitiesIfNecessary ? 1 : 0); result = 31 * result + (viewportOrientationMayChange ? 1 : 0); @@ -877,7 +879,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { result = 31 * result + viewportHeight; result = 31 * result + maxVideoBitrate; result = 31 * result + tunnelingAudioSessionId; - result = 31 * result + maxFrameRate; result = 31 * result + (preferredAudioLanguage == null ? 0 : preferredAudioLanguage.hashCode()); result = 31 * result + (preferredTextLanguage == null ? 0 : preferredTextLanguage.hashCode()); @@ -905,6 +906,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { Util.writeBoolean(dest, allowNonSeamlessAdaptiveness); dest.writeInt(maxVideoWidth); dest.writeInt(maxVideoHeight); + dest.writeInt(maxVideoFrameRate); dest.writeInt(maxVideoBitrate); Util.writeBoolean(dest, exceedVideoConstraintsIfNecessary); Util.writeBoolean(dest, exceedRendererCapabilitiesIfNecessary); @@ -912,7 +914,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { dest.writeInt(viewportHeight); Util.writeBoolean(dest, viewportOrientationMayChange); dest.writeInt(tunnelingAudioSessionId); - dest.writeInt(maxFrameRate); } public static final Parcelable.Creator CREATOR = @@ -1457,10 +1458,19 @@ public class DefaultTrackSelector extends MappingTrackSelector { && (mixedMimeTypeAdaptationSupports & requiredAdaptiveSupport) != 0; for (int i = 0; i < groups.length; i++) { TrackGroup group = groups.get(i); - int[] adaptiveTracks = getAdaptiveVideoTracksForGroup(group, formatSupport[i], - allowMixedMimeTypes, requiredAdaptiveSupport, params.maxVideoWidth, params.maxVideoHeight, - params.maxVideoBitrate, params.maxFrameRate, params.viewportWidth, params.viewportHeight, - params.viewportOrientationMayChange); + int[] adaptiveTracks = + getAdaptiveVideoTracksForGroup( + group, + formatSupport[i], + allowMixedMimeTypes, + requiredAdaptiveSupport, + params.maxVideoWidth, + params.maxVideoHeight, + params.maxVideoFrameRate, + params.maxVideoBitrate, + params.viewportWidth, + params.viewportHeight, + params.viewportOrientationMayChange); if (adaptiveTracks.length > 0) { return Assertions.checkNotNull(adaptiveTrackSelectionFactory) .createTrackSelection(group, bandwidthMeter, adaptiveTracks); @@ -1469,10 +1479,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { return null; } - private static int[] getAdaptiveVideoTracksForGroup(TrackGroup group, int[] formatSupport, - boolean allowMixedMimeTypes, int requiredAdaptiveSupport, int maxVideoWidth, - int maxVideoHeight, int maxVideoBitrate, int maxFrameRate, int viewportWidth, - int viewportHeight, boolean viewportOrientationMayChange) { + private static int[] getAdaptiveVideoTracksForGroup( + TrackGroup group, + int[] formatSupport, + boolean allowMixedMimeTypes, + int requiredAdaptiveSupport, + int maxVideoWidth, + int maxVideoHeight, + int maxVideoFrameRate, + int maxVideoBitrate, + int viewportWidth, + int viewportHeight, + boolean viewportOrientationMayChange) { if (group.length < 2) { return NO_TRACKS; } @@ -1492,9 +1510,17 @@ public class DefaultTrackSelector extends MappingTrackSelector { int trackIndex = selectedTrackIndices.get(i); String sampleMimeType = group.getFormat(trackIndex).sampleMimeType; if (seenMimeTypes.add(sampleMimeType)) { - int countForMimeType = getAdaptiveVideoTrackCountForMimeType(group, formatSupport, - requiredAdaptiveSupport, sampleMimeType, maxVideoWidth, maxVideoHeight, - maxVideoBitrate, maxFrameRate, selectedTrackIndices); + int countForMimeType = + getAdaptiveVideoTrackCountForMimeType( + group, + formatSupport, + requiredAdaptiveSupport, + sampleMimeType, + maxVideoWidth, + maxVideoHeight, + maxVideoFrameRate, + maxVideoBitrate, + selectedTrackIndices); if (countForMimeType > selectedMimeTypeTrackCount) { selectedMimeType = sampleMimeType; selectedMimeTypeTrackCount = countForMimeType; @@ -1504,8 +1530,15 @@ public class DefaultTrackSelector extends MappingTrackSelector { } // Filter by the selected mime type. - filterAdaptiveVideoTrackCountForMimeType(group, formatSupport, requiredAdaptiveSupport, - selectedMimeType, maxVideoWidth, maxVideoHeight, maxVideoBitrate, maxFrameRate, + filterAdaptiveVideoTrackCountForMimeType( + group, + formatSupport, + requiredAdaptiveSupport, + selectedMimeType, + maxVideoWidth, + maxVideoHeight, + maxVideoFrameRate, + maxVideoBitrate, selectedTrackIndices); return selectedTrackIndices.size() < 2 ? NO_TRACKS : Util.toArray(selectedTrackIndices); @@ -1518,15 +1551,21 @@ public class DefaultTrackSelector extends MappingTrackSelector { @Nullable String mimeType, int maxVideoWidth, int maxVideoHeight, + int maxVideoFrameRate, int maxVideoBitrate, - int maxFrameRate, List selectedTrackIndices) { int adaptiveTrackCount = 0; for (int i = 0; i < selectedTrackIndices.size(); i++) { int trackIndex = selectedTrackIndices.get(i); - if (isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType, - formatSupport[trackIndex], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, - maxVideoBitrate, maxFrameRate)) { + if (isSupportedAdaptiveVideoTrack( + group.getFormat(trackIndex), + mimeType, + formatSupport[trackIndex], + requiredAdaptiveSupport, + maxVideoWidth, + maxVideoHeight, + maxVideoFrameRate, + maxVideoBitrate)) { adaptiveTrackCount++; } } @@ -1540,14 +1579,20 @@ public class DefaultTrackSelector extends MappingTrackSelector { @Nullable String mimeType, int maxVideoWidth, int maxVideoHeight, + int maxVideoFrameRate, int maxVideoBitrate, - int maxFrameRate, List selectedTrackIndices) { for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) { int trackIndex = selectedTrackIndices.get(i); - if (!isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType, - formatSupport[trackIndex], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, - maxVideoBitrate, maxFrameRate)) { + if (!isSupportedAdaptiveVideoTrack( + group.getFormat(trackIndex), + mimeType, + formatSupport[trackIndex], + requiredAdaptiveSupport, + maxVideoWidth, + maxVideoHeight, + maxVideoFrameRate, + maxVideoBitrate)) { selectedTrackIndices.remove(i); } } @@ -1560,14 +1605,15 @@ public class DefaultTrackSelector extends MappingTrackSelector { int requiredAdaptiveSupport, int maxVideoWidth, int maxVideoHeight, - int maxVideoBitrate, - int maxFrameRate) { - return isSupported(formatSupport, false) && ((formatSupport & requiredAdaptiveSupport) != 0) + int maxVideoFrameRate, + int maxVideoBitrate) { + return isSupported(formatSupport, false) + && ((formatSupport & requiredAdaptiveSupport) != 0) && (mimeType == null || Util.areEqual(format.sampleMimeType, mimeType)) && (format.width == Format.NO_VALUE || format.width <= maxVideoWidth) && (format.height == Format.NO_VALUE || format.height <= maxVideoHeight) - && (format.bitrate == Format.NO_VALUE || format.bitrate <= maxVideoBitrate) - && (format.frameRate == Format.NO_VALUE || format.frameRate <= maxFrameRate); + && (format.frameRate == Format.NO_VALUE || format.frameRate <= maxVideoFrameRate) + && (format.bitrate == Format.NO_VALUE || format.bitrate <= maxVideoBitrate); } private static @Nullable TrackSelection selectFixedVideoTrack( @@ -1586,11 +1632,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { if (isSupported(trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) { Format format = trackGroup.getFormat(trackIndex); - boolean isWithinConstraints = selectedTrackIndices.contains(trackIndex) - && (format.width == Format.NO_VALUE || format.width <= params.maxVideoWidth) - && (format.height == Format.NO_VALUE || format.height <= params.maxVideoHeight) - && (format.bitrate == Format.NO_VALUE || format.bitrate <= params.maxVideoBitrate) - && (format.frameRate == Format.NO_VALUE || format.frameRate <= params.maxFrameRate); + boolean isWithinConstraints = + selectedTrackIndices.contains(trackIndex) + && (format.width == Format.NO_VALUE || format.width <= params.maxVideoWidth) + && (format.height == Format.NO_VALUE || format.height <= params.maxVideoHeight) + && (format.frameRate == Format.NO_VALUE + || format.frameRate <= params.maxVideoFrameRate) + && (format.bitrate == Format.NO_VALUE + || format.bitrate <= params.maxVideoBitrate); if (!isWithinConstraints && !params.exceedVideoConstraintsIfNecessary) { // Track should not be selected. continue; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 62ae60da85..94f6abd87b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -130,14 +130,14 @@ public final class DefaultTrackSelectorTest { /* allowNonSeamlessAdaptiveness= */ true, /* maxVideoWidth= */ 1, /* maxVideoHeight= */ 2, - /* maxVideoBitrate= */ 3, + /* maxVideoFrameRate= */ 3, + /* maxVideoBitrate= */ 4, /* exceedVideoConstraintsIfNecessary= */ false, /* exceedRendererCapabilitiesIfNecessary= */ true, - /* viewportWidth= */ 4, - /* viewportHeight= */ 5, + /* viewportWidth= */ 5, + /* viewportHeight= */ 6, /* viewportOrientationMayChange= */ false, - /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET, - /* maxFrameRate= */ 6); + /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET); Parcel parcel = Parcel.obtain(); parametersToParcel.writeToParcel(parcel, 0); From a0ab96623acfad89468a70677fee2c36b2e4d9ed Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 20 Sep 2018 08:40:19 -0700 Subject: [PATCH 007/832] Compensate for trimmed audio in buffer time check After a period transition the first buffer queued has the sum of previous period durations added to its source presentation timestamp. These durations take into account gapless edits, but the check on the timestamp was based on the submitted frame count, not the frame count after trimming. This change fixes an issue where audio/video would gradually drift apart due to accumulated error in the audio track position, which could lead to freezing due to the audio renderer stopping being ready and switching to the standalone media clock. Issue: #4559 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=213819908 --- RELEASENOTES.md | 3 ++ .../exoplayer2/audio/DefaultAudioSink.java | 5 +- .../audio/TrimmingAudioProcessor.java | 53 +++++++++++++++++-- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9edde561b5..ed1eff0233 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,6 +4,9 @@ * Allow setting log level for ExoPlayer logcat output ([#4665](https://github.com/google/ExoPlayer/issues/4665)). +* Fix an issue where audio and video would desynchronize when playing + concatenations of gapless content + ([#4559](https://github.com/google/ExoPlayer/issues/4559)). ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index b20d2ab4bb..fbd5b027c1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -632,7 +632,9 @@ public final class DefaultAudioSink implements AudioSink { } else { // Sanity check that presentationTimeUs is consistent with the expected value. long expectedPresentationTimeUs = - startMediaTimeUs + inputFramesToDurationUs(getSubmittedFrames()); + startMediaTimeUs + + inputFramesToDurationUs( + getSubmittedFrames() - trimmingAudioProcessor.getTrimmedFrameCount()); if (startMediaTimeState == START_IN_SYNC && Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) { Log.e(TAG, "Discontinuity detected [expected " + expectedPresentationTimeUs + ", got " @@ -955,6 +957,7 @@ public final class DefaultAudioSink implements AudioSink { playbackParametersCheckpoints.clear(); playbackParametersOffsetUs = 0; playbackParametersPositionUs = 0; + trimmingAudioProcessor.resetTrimmedFrameCount(); inputBuffer = null; outputBuffer = null; flushAudioProcessors(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java index ab097821c4..ee452cb649 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java @@ -25,11 +25,14 @@ import java.nio.ByteOrder; /** Audio processor for trimming samples from the start/end of data. */ /* package */ final class TrimmingAudioProcessor implements AudioProcessor { + private static final int OUTPUT_ENCODING = C.ENCODING_PCM_16BIT; + private boolean isActive; private int trimStartFrames; private int trimEndFrames; private int channelCount; private int sampleRateHz; + private int bytesPerFrame; private int pendingTrimStartBytes; private ByteBuffer buffer; @@ -37,6 +40,7 @@ import java.nio.ByteOrder; private byte[] endBuffer; private int endBufferSize; private boolean inputEnded; + private long trimmedFrameCount; /** Creates a new audio processor for trimming samples from the start/end of data. */ public TrimmingAudioProcessor() { @@ -61,17 +65,34 @@ import java.nio.ByteOrder; this.trimEndFrames = trimEndFrames; } + /** Sets the trimmed frame count returned by {@link #getTrimmedFrameCount()} to zero. */ + public void resetTrimmedFrameCount() { + trimmedFrameCount = 0; + } + + /** + * Returns the number of audio frames trimmed since the last call to {@link + * #resetTrimmedFrameCount()}. + */ + public long getTrimmedFrameCount() { + return trimmedFrameCount; + } + @Override public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding) throws UnhandledFormatException { - if (encoding != C.ENCODING_PCM_16BIT) { + if (encoding != OUTPUT_ENCODING) { throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); } + if (endBufferSize > 0) { + trimmedFrameCount += endBufferSize / bytesPerFrame; + } this.channelCount = channelCount; this.sampleRateHz = sampleRateHz; - endBuffer = new byte[trimEndFrames * channelCount * 2]; + bytesPerFrame = Util.getPcmFrameSize(OUTPUT_ENCODING, channelCount); + endBuffer = new byte[trimEndFrames * bytesPerFrame]; endBufferSize = 0; - pendingTrimStartBytes = trimStartFrames * channelCount * 2; + pendingTrimStartBytes = trimStartFrames * bytesPerFrame; boolean wasActive = isActive; isActive = trimStartFrames != 0 || trimEndFrames != 0; return wasActive != isActive; @@ -89,7 +110,7 @@ import java.nio.ByteOrder; @Override public int getOutputEncoding() { - return C.ENCODING_PCM_16BIT; + return OUTPUT_ENCODING; } @Override @@ -103,8 +124,13 @@ import java.nio.ByteOrder; int limit = inputBuffer.limit(); int remaining = limit - position; + if (remaining == 0) { + return; + } + // Trim any pending start bytes from the input buffer. int trimBytes = Math.min(remaining, pendingTrimStartBytes); + trimmedFrameCount += trimBytes / bytesPerFrame; pendingTrimStartBytes -= trimBytes; inputBuffer.position(position + trimBytes); if (pendingTrimStartBytes > 0) { @@ -151,9 +177,26 @@ import java.nio.ByteOrder; inputEnded = true; } + @SuppressWarnings("ReferenceEquality") @Override public ByteBuffer getOutput() { ByteBuffer outputBuffer = this.outputBuffer; + if (inputEnded && endBufferSize > 0 && outputBuffer == EMPTY_BUFFER) { + // Because audio processors may be drained in the middle of the stream we assume that the + // contents of the end buffer need to be output. Gapless transitions don't involve a call to + // queueEndOfStream so won't be affected. When audio is actually ending we play the padding + // data which is incorrect. This behavior can be fixed once we have the timestamps associated + // with input buffers. + if (buffer.capacity() < endBufferSize) { + buffer = ByteBuffer.allocateDirect(endBufferSize).order(ByteOrder.nativeOrder()); + } else { + buffer.clear(); + } + buffer.put(endBuffer, 0, endBufferSize); + endBufferSize = 0; + buffer.flip(); + outputBuffer = buffer; + } this.outputBuffer = EMPTY_BUFFER; return outputBuffer; } @@ -161,7 +204,7 @@ import java.nio.ByteOrder; @SuppressWarnings("ReferenceEquality") @Override public boolean isEnded() { - return inputEnded && outputBuffer == EMPTY_BUFFER; + return inputEnded && endBufferSize == 0 && outputBuffer == EMPTY_BUFFER; } @Override From e90d36cb699083a4ee74b27d0960df7e2352e1bc Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 20 Sep 2018 09:07:45 -0700 Subject: [PATCH 008/832] Fix ImaAdsLoaderTest ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=213824217 --- .../com/google/android/exoplayer2/ext/ima/FakePlayer.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java index c7026bab5f..0c35c9b66d 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.ext.ima; +import android.os.Looper; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; @@ -110,6 +111,11 @@ import java.util.ArrayList; // ExoPlayer methods. Other methods are unsupported. + @Override + public Looper getApplicationLooper() { + return Looper.getMainLooper(); + } + @Override public void addListener(Player.EventListener listener) { listeners.add(listener); From d0fb4b4729b9247bb1b17a14aea5b605e0e0dfa8 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 20 Sep 2018 09:34:14 -0700 Subject: [PATCH 009/832] Add IMA tests to presubmit Also fix internal build by adding some methods. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=213828434 --- .../google/android/exoplayer2/ext/ima/FakeAd.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeAd.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeAd.java index 873a1b1d09..b626a08780 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeAd.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeAd.java @@ -64,6 +64,18 @@ import java.util.Set; }; } + public int getVastMediaWidth() { + throw new UnsupportedOperationException(); + } + + public int getVastMediaHeight() { + throw new UnsupportedOperationException(); + } + + public int getVastMediaBitrate() { + throw new UnsupportedOperationException(); + } + @Override public boolean isSkippable() { return skippable; From 300dc05e85605218d958a36e95cbc666b7962364 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 20 Sep 2018 09:34:44 -0700 Subject: [PATCH 010/832] Reset start trim only if input was queued Before this change we would reset the start trim to zero after initial configuration (at the start of playback) and after seeking to any position. The fact that no trimming was applied at the start of playback meant that after the first period transition we'd see a mismatch between the next buffer timestamp (equal to the duration of the period taking into account edits) and the duration of audio submitted to the sink. This change modifies the behavior so that we reset the start trim to zero only if some audio was queued since configuration. This is incorrect in the case of starting playback at a non-zero position, but fixes the common case of starting at zero. As before, a later seek to any position is handled via a flush and resets the trim as required. Transitions from one period to the next are unaffected by this change. One way to implement start trimming correctly would be to provide the input buffer timestamp to the audio processors and only trim when handling audio from the start of the stream, but that is a larger change so left for later. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=213828511 --- .../exoplayer2/audio/TrimmingAudioProcessor.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java index ee452cb649..7b66bd63d2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java @@ -33,6 +33,7 @@ import java.nio.ByteOrder; private int channelCount; private int sampleRateHz; private int bytesPerFrame; + private boolean receivedInputSinceConfigure; private int pendingTrimStartBytes; private ByteBuffer buffer; @@ -95,6 +96,7 @@ import java.nio.ByteOrder; pendingTrimStartBytes = trimStartFrames * bytesPerFrame; boolean wasActive = isActive; isActive = trimStartFrames != 0 || trimEndFrames != 0; + receivedInputSinceConfigure = false; return wasActive != isActive; } @@ -127,6 +129,7 @@ import java.nio.ByteOrder; if (remaining == 0) { return; } + receivedInputSinceConfigure = true; // Trim any pending start bytes from the input buffer. int trimBytes = Math.min(remaining, pendingTrimStartBytes); @@ -211,9 +214,14 @@ import java.nio.ByteOrder; public void flush() { outputBuffer = EMPTY_BUFFER; inputEnded = false; - // It's no longer necessary to trim any media from the start, but it is necessary to clear the - // end buffer and refill it. - pendingTrimStartBytes = 0; + if (receivedInputSinceConfigure) { + // Audio processors are flushed after initial configuration, so we leave the pending trim + // start byte count unmodified if the processor was just configured. Otherwise we (possibly + // incorrectly) assume that this is a seek to a non-zero position. We should instead check the + // timestamp of the first input buffer queued after flushing to decide whether to trim (see + // also [Internal: b/77292509]). + pendingTrimStartBytes = 0; + } endBufferSize = 0; } From 03fe503f1c4744848a1a2a68950b96d81c2f845c Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 20 Sep 2018 09:48:44 -0700 Subject: [PATCH 011/832] Propagate output format in tunneling mode From API 23 this uses the timed format queue. Before API 23 the format is notified as soon as the buffer is queued. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=213830729 --- .../mediacodec/MediaCodecRenderer.java | 19 +++-- .../video/MediaCodecVideoRenderer.java | 71 ++++++++++++------- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 54c2b23c3b..7e933e9474 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -497,6 +497,20 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return false; } + /** + * Polls the pending output format queue for a given buffer timestamp. If a format is present, it + * is removed and returned. Otherwise returns {@code null}. Subclasses should only call this + * method if they are taking over responsibility for output format propagation (e.g., when using + * video tunneling). + */ + protected final @Nullable Format updateOutputFormatForTime(long presentationTimeUs) { + Format format = formatQueue.pollFloor(presentationTimeUs); + if (format != null) { + outputFormat = format; + } + return format; + } + protected final MediaCodec getCodec() { return codec; } @@ -1297,10 +1311,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size); } shouldSkipOutputBuffer = shouldSkipOutputBuffer(outputBufferInfo.presentationTimeUs); - Format format = formatQueue.pollFloor(outputBufferInfo.presentationTimeUs); - if (format != null) { - outputFormat = format; - } + updateOutputFormatForTime(outputBufferInfo.presentationTimeUs); } boolean processedOutputBuffer; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 35121829a3..d217b47785 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -566,7 +566,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { buffersInCodecCount++; lastInputTimeUs = Math.max(buffer.timeUs, lastInputTimeUs); if (Util.SDK_INT < 23 && tunneling) { - maybeNotifyRenderedFirstFrame(); + // In tunneled mode before API 23 we don't have a way to know when the buffer is output, so + // treat it as if it were output immediately. + onProcessedTunneledBuffer(buffer.timeUs); } } @@ -575,29 +577,15 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT) && outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM) && outputFormat.containsKey(KEY_CROP_TOP); - currentWidth = hasCrop - ? outputFormat.getInteger(KEY_CROP_RIGHT) - outputFormat.getInteger(KEY_CROP_LEFT) + 1 - : outputFormat.getInteger(MediaFormat.KEY_WIDTH); - currentHeight = hasCrop - ? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1 - : outputFormat.getInteger(MediaFormat.KEY_HEIGHT); - currentPixelWidthHeightRatio = pendingPixelWidthHeightRatio; - if (Util.SDK_INT >= 21) { - // On API level 21 and above the decoder applies the rotation when rendering to the surface. - // Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need - // to flip the width, height and pixel aspect ratio to reflect the rotation that was applied. - if (pendingRotationDegrees == 90 || pendingRotationDegrees == 270) { - int rotatedHeight = currentWidth; - currentWidth = currentHeight; - currentHeight = rotatedHeight; - currentPixelWidthHeightRatio = 1 / currentPixelWidthHeightRatio; - } - } else { - // On API level 20 and below the decoder does not apply the rotation. - currentUnappliedRotationDegrees = pendingRotationDegrees; - } - // Must be applied each time the output format changes. - codec.setVideoScalingMode(scalingMode); + int width = + hasCrop + ? outputFormat.getInteger(KEY_CROP_RIGHT) - outputFormat.getInteger(KEY_CROP_LEFT) + 1 + : outputFormat.getInteger(MediaFormat.KEY_WIDTH); + int height = + hasCrop + ? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1 + : outputFormat.getInteger(MediaFormat.KEY_HEIGHT); + processOutputFormat(codec, width, height); } @Override @@ -705,6 +693,28 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return false; } + private void processOutputFormat(MediaCodec codec, int width, int height) { + currentWidth = width; + currentHeight = height; + currentPixelWidthHeightRatio = pendingPixelWidthHeightRatio; + if (Util.SDK_INT >= 21) { + // On API level 21 and above the decoder applies the rotation when rendering to the surface. + // Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need + // to flip the width, height and pixel aspect ratio to reflect the rotation that was applied. + if (pendingRotationDegrees == 90 || pendingRotationDegrees == 270) { + int rotatedHeight = currentWidth; + currentWidth = currentHeight; + currentHeight = rotatedHeight; + currentPixelWidthHeightRatio = 1 / currentPixelWidthHeightRatio; + } + } else { + // On API level 20 and below the decoder does not apply the rotation. + currentUnappliedRotationDegrees = pendingRotationDegrees; + } + // Must be applied each time the output format changes. + codec.setVideoScalingMode(scalingMode); + } + private void notifyFrameMetadataListener( long presentationTimeUs, long releaseTimeNs, Format format) { if (frameMetadataListener != null) { @@ -722,6 +732,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return outputStreamOffsetUs; } + /** Called when a buffer was processed in tunneling mode. */ + protected void onProcessedTunneledBuffer(long presentationTimeUs) { + @Nullable Format format = updateOutputFormatForTime(presentationTimeUs); + if (format != null) { + processOutputFormat(getCodec(), format.width, format.height); + } + maybeNotifyVideoSizeChanged(); + maybeNotifyRenderedFirstFrame(); + onProcessedOutputBuffer(presentationTimeUs); + } + /** * Called when an output buffer is successfully processed. * @@ -1463,7 +1484,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { // Stale event. return; } - maybeNotifyRenderedFirstFrame(); + onProcessedTunneledBuffer(presentationTimeUs); } } From bd3bf3fc8db6b757d974ae73f98efe20cf589663 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 21 Sep 2018 01:29:49 -0700 Subject: [PATCH 012/832] Fix broken Javadoc ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=213951977 --- .../exoplayer2/ext/mediasession/MediaSessionConnector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java index 4a2464fdaa..0915ee4b03 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -401,7 +401,7 @@ public final class MediaSessionConnector { *

The order in which any {@link CustomActionProvider}s are passed determines the order of the * actions published with the playback state of the session. * - * @param player The player to be connected to the {@code MediaSession}, or {@link null} to + * @param player The player to be connected to the {@code MediaSession}, or {@code null} to * disconnect the current player. * @param playbackPreparer An optional {@link PlaybackPreparer} for preparing the player. * @param customActionProviders Optional {@link CustomActionProvider}s to publish and handle From d411ace97d0e41eb2ce172e8ef1c15fb81358aaa Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 21 Sep 2018 03:02:09 -0700 Subject: [PATCH 013/832] Add missing calls to LoadControl.onTracksSelected. This method needs to be called whenever the track selection of the current loading period changes, but also when the loading period itself (and thus the "loading track selection") changes. These are the same situations in which we update the loading media period id and thus we can move both updates in a common helper method. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=213959982 --- .../exoplayer2/ExoPlayerImplInternal.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 329e2c671a..d861020d26 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -428,7 +428,7 @@ import java.util.Collections; if (!queue.updateRepeatMode(repeatMode)) { seekToCurrentPosition(/* sendDiscontinuity= */ true); } - updateLoadingMediaPeriodId(); + handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); } private void setShuffleModeEnabledInternal(boolean shuffleModeEnabled) @@ -437,7 +437,7 @@ import java.util.Collections; if (!queue.updateShuffleModeEnabled(shuffleModeEnabled)) { seekToCurrentPosition(/* sendDiscontinuity= */ true); } - updateLoadingMediaPeriodId(); + handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); } private void seekToCurrentPosition(boolean sendDiscontinuity) throws ExoPlaybackException { @@ -706,7 +706,7 @@ import java.util.Collections; resetRendererPosition(periodPositionUs); } - updateLoadingMediaPeriodId(); + handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); handler.sendEmptyMessage(MSG_DO_SOME_WORK); return periodPositionUs; } @@ -1018,8 +1018,6 @@ import java.util.Collections; long periodPositionUs = playingPeriodHolder.applyTrackSelection( playbackInfo.positionUs, recreateStreams, streamResetFlags); - updateLoadControlTrackSelection( - playingPeriodHolder.trackGroups, playingPeriodHolder.trackSelectorResult); if (playbackInfo.playbackState != Player.STATE_ENDED && periodPositionUs != playbackInfo.positionUs) { playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs, @@ -1059,10 +1057,9 @@ import java.util.Collections; Math.max( periodHolder.info.startPositionUs, periodHolder.toPeriodTime(rendererPositionUs)); periodHolder.applyTrackSelection(loadingPeriodPositionUs, false); - updateLoadControlTrackSelection(periodHolder.trackGroups, periodHolder.trackSelectorResult); } } - updateLoadingMediaPeriodId(); + handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ true); if (playbackInfo.playbackState != Player.STATE_ENDED) { maybeContinueLoading(); updatePlaybackPositions(); @@ -1070,11 +1067,6 @@ import java.util.Collections; } } - private void updateLoadControlTrackSelection( - TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { - loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections); - } - private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) { MediaPeriodHolder periodHolder = queue.getFrontPeriod(); while (periodHolder != null) { @@ -1278,7 +1270,7 @@ import java.util.Collections; if (!queue.updateQueuedPeriods(playingPeriodId, rendererPositionUs)) { seekToCurrentPosition(/* sendDiscontinuity= */ false); } - updateLoadingMediaPeriodId(); + handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); } private void handleSourceInfoRefreshEndedPlayback() { @@ -1528,7 +1520,7 @@ import java.util.Collections; info); mediaPeriod.prepare(this, info.startPositionUs); setIsLoading(true); - updateLoadingMediaPeriodId(); + handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); } } } @@ -1666,14 +1658,28 @@ import java.util.Collections; && renderer.hasReadStreamToEnd(); } - private void updateLoadingMediaPeriodId() { + private void handleLoadingMediaPeriodChanged(boolean loadingTrackSelectionChanged) { MediaPeriodHolder loadingMediaPeriodHolder = queue.getLoadingPeriod(); MediaPeriodId loadingMediaPeriodId = loadingMediaPeriodHolder == null ? playbackInfo.periodId : loadingMediaPeriodHolder.info.id; - playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId); + boolean loadingMediaPeriodChanged = + !playbackInfo.loadingMediaPeriodId.equals(loadingMediaPeriodId); + if (loadingMediaPeriodChanged) { + playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId); + } + if ((loadingMediaPeriodChanged || loadingTrackSelectionChanged) + && loadingMediaPeriodHolder != null + && loadingMediaPeriodHolder.prepared) { + updateLoadControlTrackSelection( + loadingMediaPeriodHolder.trackGroups, loadingMediaPeriodHolder.trackSelectorResult); + } + } + + private void updateLoadControlTrackSelection( + TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { + loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections); } - @NonNull private static Format[] getFormats(TrackSelection newSelection) { // Build an array of formats contained by the selection. int length = newSelection != null ? newSelection.length() : 0; From 876080ed1a70762d06ab97fbafda7ac7575487bc Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Fri, 21 Sep 2018 15:05:43 +0300 Subject: [PATCH 014/832] #4306 - code review fixes --- .../exoplayer2/text/subrip/SubripDecoder.java | 101 ++++++------------ 1 file changed, 33 insertions(+), 68 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index 96c065973e..492eb60bfc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -29,7 +29,6 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -81,6 +80,7 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { @Override protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { ArrayList cues = new ArrayList<>(); + ArrayList tags = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); ParsableByteArray subripData = new ParsableByteArray(bytes, length); String currentLine; @@ -125,34 +125,25 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { if (textBuilder.length() > 0) { textBuilder.append("
"); } - textBuilder.append(currentLine.trim()); + textBuilder.append(processLine(currentLine, tags)); } - // Extract tags - SubtitleTagResult tagResult = extractTags(textBuilder); - Spanned text = Html.fromHtml(tagResult.cue); - + Spanned text = Html.fromHtml(textBuilder.toString()); Cue cue = null; - // Check if tags are present - if (tagResult.tags.length > 0) { + boolean alignTagFound = false; - boolean alignTagFound = false; + // At end of this loop the clue must be created with the applied tags + for (String tag : tags) { - // At end of this loop the clue must be created with the applied tags - for (String tag : tagResult.tags) { + // Check if the tag is an alignment tag + if (tag.matches(SUBRIP_ALIGNMENT_TAG)) { - // Check if the tag is an alignment tag - if (tag.matches(SUBRIP_ALIGNMENT_TAG)) { + // Based on the specs, in case of the alignment tags only the first appearance counts + if (alignTagFound) continue; + alignTagFound = true; - // Based on the specs, in case of the alignment tags only the first appearance counts - if (alignTagFound) continue; - alignTagFound = true; - - AlignmentResult alignmentResult = getAlignmentValues(tag); - cue = new Cue(text, Layout.Alignment.ALIGN_NORMAL, alignmentResult.line, Cue.LINE_TYPE_FRACTION, - alignmentResult.lineAnchor, alignmentResult.position, alignmentResult.positionAnchor, Cue.DIMEN_UNSET); - } + cue = buildCue(text, tag); } } @@ -170,51 +161,57 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { } /** - * Extracts the tags from the given {@code cue} + * Process the given line by first trimming it then extracting the tags from it + *

* The pattern that is used to extract the tags is specified in SSA v4+ specs and * has the following form: "{\...}". *

* "All override codes appear within braces {}" * "All override codes are always preceded by a backslash \" * - * @param cue Cue text - * @return {@link SubtitleTagResult} that holds new cue and also the extracted tags + * @param currentLine Current line + * @param tags Extracted tags will be stored in this array list + * @return Processed line */ - private SubtitleTagResult extractTags(StringBuilder cue) { - StringBuilder cueCopy = new StringBuilder(cue.toString()); - List tags = new ArrayList<>(); + private String processLine(String currentLine, ArrayList tags) { + // Trim line + String trimmedLine = currentLine.trim(); + // Extract tags int replacedCharacters = 0; + StringBuilder processedLine = new StringBuilder(trimmedLine); + Matcher matcher = SUBRIP_TAG_PATTERN.matcher(processedLine); - Matcher matcher = SUBRIP_TAG_PATTERN.matcher(cue.toString()); while (matcher.find()) { String tag = matcher.group(); tags.add(tag); - cueCopy.replace(matcher.start() - replacedCharacters, matcher.end() - replacedCharacters, ""); + processedLine.replace(matcher.start() - replacedCharacters, matcher.end() - replacedCharacters, ""); replacedCharacters += tag.length(); } - return new SubtitleTagResult(tags.toArray(new String[tags.size()]), cueCopy.toString()); + return processedLine.toString(); } /** + * Build a {@link Cue} based on the given text and tag + *

* Match the alignment tag and calculate the line, position, position anchor accordingly - * + *

* Based on SSA v4+ specs the alignment tag can have the following form: {\an[1-9}, * where the number specifies the direction (based on the numpad layout). * Note. older SSA scripts may contain tags like {\a1[1-9]} but these are based on * other direction rules, but multiple sources says that these are deprecated, so no support here either * - * @param tag Alignment tag - * @return {@link AlignmentResult} that holds the line, position, position anchor values + * @param alignmentTag Alignment tag + * @return Built cue */ - private AlignmentResult getAlignmentValues(String tag) { + private Cue buildCue(Spanned text, String alignmentTag) { // Default values used for positioning the subtitle in case of align tags float line = DEFAULT_END_FRACTION, position = DEFAULT_MID_FRACTION; @Cue.AnchorType int positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; @Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_END; - switch (tag) { + switch (alignmentTag) { case ALIGN_BOTTOM_LEFT: line = DEFAULT_END_FRACTION; position = DEFAULT_START_FRACTION; @@ -271,7 +268,7 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { break; } - return new AlignmentResult(positionAnchor, position, lineAnchor, line); + return new Cue(text, Layout.Alignment.ALIGN_NORMAL, line, Cue.LINE_TYPE_FRACTION, lineAnchor, position, positionAnchor, Cue.DIMEN_UNSET); } private static long parseTimecode(Matcher matcher, int groupOffset) { @@ -281,36 +278,4 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { timestampMs += Long.parseLong(matcher.group(groupOffset + 4)); return timestampMs * 1000; } - - /** - * Class that holds the tags, new clue after the tag extraction - */ - private static final class SubtitleTagResult { - public final String[] tags; - public final String cue; - - public SubtitleTagResult(String[] tags, String cue) { - this.tags = tags; - this.cue = cue; - } - } - - /** - * Class that holds the parsed and mapped alignment values (such as line, - * position and anchor type of line) - */ - private static final class AlignmentResult { - - public @Cue.AnchorType int positionAnchor; - public @Cue.AnchorType int lineAnchor; - public float position, line; - - public AlignmentResult(@Cue.AnchorType int positionAnchor, float position, @Cue.AnchorType int lineAnchor, float line) { - this.positionAnchor = positionAnchor; - this.position = position; - this.line = line; - this.lineAnchor = lineAnchor; - } - } - } From 275cfd9e772aedf6d8f6db2130628c482fe42ea4 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 21 Sep 2018 06:49:26 -0700 Subject: [PATCH 015/832] Fix broken Javadoc ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=213979058 --- .../com/google/android/exoplayer2/Player.java | 6 +++--- .../com/google/android/exoplayer2/util/Log.java | 16 ++++++++-------- .../exoplayer2/video/spherical/Projection.java | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index a120ab1305..d4b965dbd6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -144,13 +144,13 @@ public interface Player { interface VideoComponent { /** - * Sets the {@link @VideoScalingMode}. + * Sets the {@link VideoScalingMode}. * - * @param videoScalingMode The {@link @VideoScalingMode}. + * @param videoScalingMode The {@link VideoScalingMode}. */ void setVideoScalingMode(@VideoScalingMode int videoScalingMode); - /** Returns the {@link @VideoScalingMode}. */ + /** Returns the {@link VideoScalingMode}. */ @VideoScalingMode int getVideoScalingMode(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java index f2dd54af12..6a1e686dec 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java @@ -75,14 +75,14 @@ public final class Log { Log.logStackTraces = logStackTraces; } - /** @see android.util.Log#d(String, String). */ + /** @see android.util.Log#d(String, String) */ public static void d(String tag, String message) { if (logLevel == LOG_LEVEL_ALL) { android.util.Log.d(tag, message); } } - /** @see android.util.Log#d(String, String, Throwable). */ + /** @see android.util.Log#d(String, String, Throwable) */ public static void d(String tag, String message, @Nullable Throwable throwable) { if (!logStackTraces) { d(tag, appendThrowableMessage(message, throwable)); @@ -92,14 +92,14 @@ public final class Log { } } - /** @see android.util.Log#i(String, String). */ + /** @see android.util.Log#i(String, String) */ public static void i(String tag, String message) { if (logLevel <= LOG_LEVEL_INFO) { android.util.Log.i(tag, message); } } - /** @see android.util.Log#i(String, String, Throwable). */ + /** @see android.util.Log#i(String, String, Throwable) */ public static void i(String tag, String message, @Nullable Throwable throwable) { if (!logStackTraces) { i(tag, appendThrowableMessage(message, throwable)); @@ -109,14 +109,14 @@ public final class Log { } } - /** @see android.util.Log#w(String, String). */ + /** @see android.util.Log#w(String, String) */ public static void w(String tag, String message) { if (logLevel <= LOG_LEVEL_WARNING) { android.util.Log.w(tag, message); } } - /** @see android.util.Log#w(String, String, Throwable). */ + /** @see android.util.Log#w(String, String, Throwable) */ public static void w(String tag, String message, @Nullable Throwable throwable) { if (!logStackTraces) { w(tag, appendThrowableMessage(message, throwable)); @@ -126,14 +126,14 @@ public final class Log { } } - /** @see android.util.Log#e(String, String). */ + /** @see android.util.Log#e(String, String) */ public static void e(String tag, String message) { if (logLevel <= LOG_LEVEL_ERROR) { android.util.Log.e(tag, message); } } - /** @see android.util.Log#e(String, String, Throwable). */ + /** @see android.util.Log#e(String, String, Throwable) */ public static void e(String tag, String message, @Nullable Throwable throwable) { if (!logStackTraces) { e(tag, appendThrowableMessage(message, throwable)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java index 3a585ccd64..0a9d04bf0f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java @@ -59,9 +59,9 @@ public final class Projection { /** * Generates an equirectangular projection. * - * @param radius Size of the sphere. Must be > 0. - * @param latitudes Number of rows that make up the sphere. Must be >= 1. - * @param longitudes Number of columns that make up the sphere. Must be >= 1. + * @param radius Size of the sphere. Must be > 0. + * @param latitudes Number of rows that make up the sphere. Must be >= 1. + * @param longitudes Number of columns that make up the sphere. Must be >= 1. * @param verticalFovDegrees Total latitudinal degrees that are covered by the sphere. Must be in * (0, 180]. * @param horizontalFovDegrees Total longitudinal degrees that are covered by the sphere.Must be From 56f4717465d56de6bdb22f7526066d8c899c66be Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 24 Sep 2018 01:17:21 -0700 Subject: [PATCH 016/832] Update README for libvpx 1.7.0 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214227045 --- extensions/vp9/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vp9/README.md b/extensions/vp9/README.md index 9601829c91..306f04d0e2 100644 --- a/extensions/vp9/README.md +++ b/extensions/vp9/README.md @@ -49,7 +49,7 @@ git clone https://chromium.googlesource.com/libyuv/libyuv libyuv ``` cd "${VP9_EXT_PATH}/jni/libvpx" && \ -git checkout tags/v1.6.1 -b v1.6.1 && \ +git checkout tags/v1.7.0 -b v1.7.0 && \ cd "${VP9_EXT_PATH}/jni/libyuv" && \ git checkout 996a2bbd ``` From 34c37596ddb2dc7d953fc3b9b1ed241cfd1930ec Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 24 Sep 2018 06:00:38 -0700 Subject: [PATCH 017/832] Allow configuration of DefaultLoadControl back buffer Issue: #4857 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214254231 --- RELEASENOTES.md | 2 + .../exoplayer2/DefaultLoadControl.java | 89 +++++++++++++++---- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ed1eff0233..97f3b29c3e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -112,6 +112,8 @@ ([#4791](https://github.com/google/ExoPlayer/issues/4791)). * MPEG-TS: Support CEA-608/708 in H262 ([#2565](https://github.com/google/ExoPlayer/issues/2565)). +* Allow configuration of the back buffer in `DefaultLoadControl.Builder` + ([#4857](https://github.com/google/ExoPlayer/issues/4857)). * Allow apps to pass a `CacheKeyFactory` for setting custom cache keys when creating a `CacheDataSource`. * Provide additional information for adaptive track selection. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java index c466815c79..97a56b844c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java @@ -52,14 +52,20 @@ public class DefaultLoadControl implements LoadControl { public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000; /** - * The default target buffer size in bytes. When set to {@link C#LENGTH_UNSET}, the load control - * automatically determines its target buffer size. + * The default target buffer size in bytes. The value ({@link C#LENGTH_UNSET}) means that the load + * control will calculate the target buffer size based on the selected tracks. */ public static final int DEFAULT_TARGET_BUFFER_BYTES = C.LENGTH_UNSET; /** The default prioritization of buffer time constraints over size constraints. */ public static final boolean DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS = true; + /** The default back buffer duration in milliseconds. */ + public static final int DEFAULT_BACK_BUFFER_DURATION_MS = 0; + + /** The default for whether the back buffer is retained from the previous keyframe. */ + public static final boolean DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME = false; + /** Builder for {@link DefaultLoadControl}. */ public static final class Builder { @@ -71,6 +77,8 @@ public class DefaultLoadControl implements LoadControl { private int targetBufferBytes; private boolean prioritizeTimeOverSizeThresholds; private PriorityTaskManager priorityTaskManager; + private int backBufferDurationMs; + private boolean retainBackBufferFromKeyframe; /** Constructs a new instance. */ public Builder() { @@ -82,6 +90,8 @@ public class DefaultLoadControl implements LoadControl { targetBufferBytes = DEFAULT_TARGET_BUFFER_BYTES; prioritizeTimeOverSizeThresholds = DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS; priorityTaskManager = null; + backBufferDurationMs = DEFAULT_BACK_BUFFER_DURATION_MS; + retainBackBufferFromKeyframe = DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME; } /** @@ -123,8 +133,7 @@ public class DefaultLoadControl implements LoadControl { /** * Sets the target buffer size in bytes. If set to {@link C#LENGTH_UNSET}, the target buffer - * size will be calculated using {@link #calculateTargetBufferSize(Renderer[], - * TrackSelectionArray)}. + * size will be calculated based on the selected tracks. * * @param targetBufferBytes The target buffer size in bytes. * @return This builder, for convenience. @@ -147,14 +156,33 @@ public class DefaultLoadControl implements LoadControl { return this; } - /** Sets the {@link PriorityTaskManager} to use. */ + /** + * Sets the {@link PriorityTaskManager} to use. + * + * @param priorityTaskManager The {@link PriorityTaskManager} to use. + * @return This builder, for convenience. + */ public Builder setPriorityTaskManager(PriorityTaskManager priorityTaskManager) { this.priorityTaskManager = priorityTaskManager; return this; } + /** + * Sets the back buffer duration, and whether the back buffer is retained from the previous + * keyframe. + * + * @param backBufferDurationMs The back buffer duration in milliseconds. + * @param retainBackBufferFromKeyframe Whether the back buffer is retained from the previous + * keyframe. + * @return This builder, for convenience. + */ + public Builder setBackBuffer(int backBufferDurationMs, boolean retainBackBufferFromKeyframe) { + this.backBufferDurationMs = backBufferDurationMs; + this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe; + return this; + } + /** Creates a {@link DefaultLoadControl}. */ - @SuppressWarnings("deprecation") public DefaultLoadControl createDefaultLoadControl() { if (allocator == null) { allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); @@ -167,7 +195,9 @@ public class DefaultLoadControl implements LoadControl { bufferForPlaybackAfterRebufferMs, targetBufferBytes, prioritizeTimeOverSizeThresholds, - priorityTaskManager); + priorityTaskManager, + backBufferDurationMs, + retainBackBufferFromKeyframe); } } @@ -180,6 +210,8 @@ public class DefaultLoadControl implements LoadControl { private final int targetBufferBytesOverwrite; private final boolean prioritizeTimeOverSizeThresholds; private final PriorityTaskManager priorityTaskManager; + private final long backBufferDurationUs; + private final boolean retainBackBufferFromKeyframe; private int targetBufferSize; private boolean isBuffering; @@ -223,7 +255,7 @@ public class DefaultLoadControl implements LoadControl { bufferForPlaybackAfterRebufferMs, targetBufferBytes, prioritizeTimeOverSizeThresholds, - null); + /* priorityTaskManager= */ null); } /** @deprecated Use {@link Builder} instead. */ @@ -237,6 +269,30 @@ public class DefaultLoadControl implements LoadControl { int targetBufferBytes, boolean prioritizeTimeOverSizeThresholds, PriorityTaskManager priorityTaskManager) { + this( + allocator, + minBufferMs, + maxBufferMs, + bufferForPlaybackMs, + bufferForPlaybackAfterRebufferMs, + targetBufferBytes, + prioritizeTimeOverSizeThresholds, + priorityTaskManager, + DEFAULT_BACK_BUFFER_DURATION_MS, + DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME); + } + + protected DefaultLoadControl( + DefaultAllocator allocator, + int minBufferMs, + int maxBufferMs, + int bufferForPlaybackMs, + int bufferForPlaybackAfterRebufferMs, + int targetBufferBytes, + boolean prioritizeTimeOverSizeThresholds, + PriorityTaskManager priorityTaskManager, + int backBufferDurationMs, + boolean retainBackBufferFromKeyframe) { assertGreaterOrEqual(bufferForPlaybackMs, 0, "bufferForPlaybackMs", "0"); assertGreaterOrEqual( bufferForPlaybackAfterRebufferMs, 0, "bufferForPlaybackAfterRebufferMs", "0"); @@ -247,15 +303,18 @@ public class DefaultLoadControl implements LoadControl { "minBufferMs", "bufferForPlaybackAfterRebufferMs"); assertGreaterOrEqual(maxBufferMs, minBufferMs, "maxBufferMs", "minBufferMs"); + assertGreaterOrEqual(backBufferDurationMs, 0, "backBufferDurationMs", "0"); this.allocator = allocator; - minBufferUs = minBufferMs * 1000L; - maxBufferUs = maxBufferMs * 1000L; - bufferForPlaybackUs = bufferForPlaybackMs * 1000L; - bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 1000L; - targetBufferBytesOverwrite = targetBufferBytes; + this.minBufferUs = C.msToUs(minBufferMs); + this.maxBufferUs = C.msToUs(maxBufferMs); + this.bufferForPlaybackUs = C.msToUs(bufferForPlaybackMs); + this.bufferForPlaybackAfterRebufferUs = C.msToUs(bufferForPlaybackAfterRebufferMs); + this.targetBufferBytesOverwrite = targetBufferBytes; this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds; this.priorityTaskManager = priorityTaskManager; + this.backBufferDurationUs = C.msToUs(backBufferDurationMs); + this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe; } @Override @@ -290,12 +349,12 @@ public class DefaultLoadControl implements LoadControl { @Override public long getBackBufferDurationUs() { - return 0; + return backBufferDurationUs; } @Override public boolean retainBackBufferFromKeyframe() { - return false; + return retainBackBufferFromKeyframe; } @Override From c934ce744dbc55ddd6dbde29037c441afde7671c Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 24 Sep 2018 06:34:12 -0700 Subject: [PATCH 018/832] Don't request session sharing on CDMs that don't support it Issue: #4834 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214258163 --- RELEASENOTES.md | 3 +++ .../android/exoplayer2/drm/DefaultDrmSessionManager.java | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 97f3b29c3e..55ad062859 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -145,6 +145,9 @@ looping ([#3829](https://github.com/google/ExoPlayer/issues/3829)). * Fix issue where `player.getCurrentTag()` throws an `IndexOutOfBoundsException` ([#4822](https://github.com/google/ExoPlayer/issues/4822)). +* Fix bug preventing use of multiple key session support (`multiSession=true`) + for non-Widevine `DefaultDrmSessionManager` instances + ([#4834](https://github.com/google/ExoPlayer/issues/4834)). * IMA extension: * Refine the previous fix for empty ad groups to avoid discarding ad breaks unnecessarily ([#4030](https://github.com/google/ExoPlayer/issues/4030) and diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index 03269f0caa..460ee357ea 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -367,7 +367,11 @@ public class DefaultDrmSessionManager implements DrmSe mode = MODE_PLAYBACK; sessions = new ArrayList<>(); provisioningSessions = new ArrayList<>(); - if (multiSession) { + if (multiSession && C.WIDEVINE_UUID.equals(uuid) && Util.SDK_INT >= 19) { + // TODO: Enabling session sharing probably doesn't do anything useful here. It would only be + // useful if DefaultDrmSession instances were aware of one another's state, which is not + // implemented. Or if custom renderers are being used that allow playback to proceed before + // keys, which seems unlikely to be true in practice. mediaDrm.setPropertyString("sessionSharing", "enable"); } mediaDrm.setOnEventListener(new MediaDrmEventListener()); From e86d5efb2147c1f99074311ee3852cca307823ad Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 24 Sep 2018 07:09:26 -0700 Subject: [PATCH 019/832] Recenter SphericalSurfaceView view on startup ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214261973 --- .../video/spherical/FrameRotationQueue.java | 7 +- .../exoplayer2/ui/spherical/GlUtil.java | 4 +- .../ui/spherical/OrientationListener.java | 126 ++++++++++++++++++ .../ui/spherical/ProjectionRenderer.java | 4 +- .../ui/spherical/SceneRenderer.java | 2 +- .../ui/spherical/SphericalSurfaceView.java | 88 ++---------- .../exoplayer2/ui/spherical/TouchTracker.java | 11 +- 7 files changed, 152 insertions(+), 90 deletions(-) create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/OrientationListener.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java index d7404cbce4..d464bf04fa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/FrameRotationQueue.java @@ -61,7 +61,7 @@ public final class FrameRotationQueue { * timestamp to {@code matrix}. Removes all older rotations and the returned one from the queue. * Does nothing if there is no such rotation. * - * @param matrix A float array to hold the rotation matrix. + * @param matrix The rotation matrix. * @param timestampUs The time in microseconds to query the rotation. * @return Whether a rotation matrix is copied to {@code matrix}. */ @@ -83,8 +83,11 @@ public final class FrameRotationQueue { /** * Computes a recentering matrix from the given angle-axis rotation only accounting for yaw. Roll * and tilt will not be compensated. + * + * @param recenterMatrix The recenter matrix. + * @param rotationMatrix The rotation matrix. */ - private static void computeRecenterMatrix(float[] recenterMatrix, float[] rotationMatrix) { + public static void computeRecenterMatrix(float[] recenterMatrix, float[] rotationMatrix) { // The re-centering matrix is computed as follows: // recenter.row(2) = temp.col(2).transpose(); // recenter.row(0) = recenter.row(1).cross(recenter.row(2)).normalized(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlUtil.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlUtil.java index 8fd5d64e1c..68a85938a3 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlUtil.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlUtil.java @@ -30,7 +30,7 @@ import java.nio.FloatBuffer; import java.nio.IntBuffer; /** GL utility methods. */ -/*package*/ final class GlUtil { +/* package */ final class GlUtil { private static final String TAG = "Spherical.Utils"; /** Class only contains static methods. */ @@ -79,7 +79,9 @@ import java.nio.IntBuffer; int program = GLES20.glCreateProgram(); GLES20.glAttachShader(program, vertexShader); + GLES20.glDeleteShader(vertexShader); GLES20.glAttachShader(program, fragmentShader); + GLES20.glDeleteShader(fragmentShader); // Link and check for errors. GLES20.glLinkProgram(program); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/OrientationListener.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/OrientationListener.java new file mode 100644 index 0000000000..2371f6f65c --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/OrientationListener.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui.spherical; + +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.opengl.Matrix; +import android.support.annotation.BinderThread; +import android.view.Display; +import android.view.Surface; +import com.google.android.exoplayer2.video.spherical.FrameRotationQueue; + +/** + * Listens for orientation sensor events, converts event data to rotation matrix and roll value, and + * notifies its own listeners. + */ +/* package */ final class OrientationListener implements SensorEventListener { + /** A listener for orientation changes. */ + public interface Listener { + /** + * Called on device orientation change. + * + * @param deviceOrientationMatrix A 4x4 matrix defining device orientation. + * @param deviceRoll Device roll value, in radians. The range of values is -π/2 to π/2. + */ + void onOrientationChange(float[] deviceOrientationMatrix, float deviceRoll); + } + + private final float[] deviceOrientationMatrix4x4 = new float[16]; + private final float[] tempMatrix4x4 = new float[16]; + private final float[] recenterMatrix4x4 = new float[16]; + private final float[] angles = new float[3]; + private final Display display; + private final Listener[] listeners; + private boolean recenterMatrixComputed; + + public OrientationListener(Display display, Listener... listeners) { + this.display = display; + this.listeners = listeners; + } + + @Override + @BinderThread + public void onSensorChanged(SensorEvent event) { + SensorManager.getRotationMatrixFromVector(deviceOrientationMatrix4x4, event.values); + rotateAroundZ(deviceOrientationMatrix4x4, display.getRotation()); + float roll = extractRoll(deviceOrientationMatrix4x4); + // Rotation vector sensor assumes Y is parallel to the ground. + rotateYtoSky(deviceOrientationMatrix4x4); + recenter(deviceOrientationMatrix4x4); + notifyListeners(deviceOrientationMatrix4x4, roll); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Do nothing. + } + + private void notifyListeners(float[] deviceOrientationMatrix, float roll) { + for (Listener listener : listeners) { + listener.onOrientationChange(deviceOrientationMatrix, roll); + } + } + + private void recenter(float[] matrix) { + if (!recenterMatrixComputed) { + FrameRotationQueue.computeRecenterMatrix(recenterMatrix4x4, matrix); + recenterMatrixComputed = true; + } + System.arraycopy(matrix, 0, tempMatrix4x4, 0, tempMatrix4x4.length); + Matrix.multiplyMM(matrix, 0, tempMatrix4x4, 0, recenterMatrix4x4, 0); + } + + private float extractRoll(float[] matrix) { + // Remapping is required since we need the calculated roll of the phone to be independent of the + // phone's pitch & yaw. + SensorManager.remapCoordinateSystem( + matrix, SensorManager.AXIS_X, SensorManager.AXIS_MINUS_Z, tempMatrix4x4); + SensorManager.getOrientation(tempMatrix4x4, angles); + return angles[2]; + } + + private void rotateAroundZ(float[] matrix, int rotation) { + int xAxis; + int yAxis; + switch (rotation) { + case Surface.ROTATION_270: + xAxis = SensorManager.AXIS_MINUS_Y; + yAxis = SensorManager.AXIS_X; + break; + case Surface.ROTATION_180: + xAxis = SensorManager.AXIS_MINUS_X; + yAxis = SensorManager.AXIS_MINUS_Y; + break; + case Surface.ROTATION_90: + xAxis = SensorManager.AXIS_Y; + yAxis = SensorManager.AXIS_MINUS_X; + break; + case Surface.ROTATION_0: + return; + default: + throw new IllegalStateException(); + } + System.arraycopy(matrix, 0, tempMatrix4x4, 0, tempMatrix4x4.length); + SensorManager.remapCoordinateSystem(tempMatrix4x4, xAxis, yAxis, matrix); + } + + private static void rotateYtoSky(float[] matrix) { + Matrix.rotateM(matrix, 0, 90, 1, 0, 0); + } +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java index a52a25d085..b0760ec2bd 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java @@ -30,10 +30,10 @@ import org.checkerframework.checker.nullness.qual.Nullable; * thread when ready. */ @TargetApi(15) -/*package*/ final class ProjectionRenderer { +/* package */ final class ProjectionRenderer { /** Defines the constants identifying the current eye type. */ - /*package*/ interface EyeType { + /* package */ interface EyeType { /** Single eye in monocular rendering. */ int MONOCULAR = 0; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java index a8d4abcf07..ff3641135c 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java @@ -36,7 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** Renders a GL Scene. */ -/*package*/ class SceneRenderer implements VideoFrameMetadataListener, CameraMotionListener { +/* package */ class SceneRenderer implements VideoFrameMetadataListener, CameraMotionListener { private final AtomicBoolean frameAvailable; private final AtomicBoolean resetRotationAtNextFrame; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java index c6ddb8148b..9139137af4 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java @@ -20,8 +20,6 @@ import android.content.Context; import android.graphics.PointF; import android.graphics.SurfaceTexture; import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.opengl.GLES20; import android.opengl.GLSurfaceView; @@ -79,11 +77,11 @@ public final class SphericalSurfaceView extends GLSurfaceView { // TODO Calculate this depending on surface size and field of view. private static final float PX_PER_DEGREES = 25; - /*package*/ static final float UPRIGHT_ROLL = (float) Math.PI; + /* package */ static final float UPRIGHT_ROLL = (float) Math.PI; private final SensorManager sensorManager; private final @Nullable Sensor orientationSensor; - private final PhoneOrientationListener phoneOrientationListener; + private final OrientationListener orientationListener; private final Renderer renderer; private final Handler mainHandler; private final TouchTracker touchTracker; @@ -117,7 +115,7 @@ public final class SphericalSurfaceView extends GLSurfaceView { touchTracker = new TouchTracker(context, renderer, PX_PER_DEGREES); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = Assertions.checkNotNull(windowManager).getDefaultDisplay(); - phoneOrientationListener = new PhoneOrientationListener(display, touchTracker, renderer); + orientationListener = new OrientationListener(display, touchTracker, renderer); setEGLContextClientVersion(2); setRenderer(renderer); @@ -173,14 +171,14 @@ public final class SphericalSurfaceView extends GLSurfaceView { super.onResume(); if (orientationSensor != null) { sensorManager.registerListener( - phoneOrientationListener, orientationSensor, SensorManager.SENSOR_DELAY_FASTEST); + orientationListener, orientationSensor, SensorManager.SENSOR_DELAY_FASTEST); } } @Override public void onPause() { if (orientationSensor != null) { - sensorManager.unregisterListener(phoneOrientationListener); + sensorManager.unregisterListener(orientationListener); } super.onPause(); } @@ -229,82 +227,13 @@ public final class SphericalSurfaceView extends GLSurfaceView { } } - /** Detects sensor events and saves them as a matrix. */ - private static class PhoneOrientationListener implements SensorEventListener { - private final float[] phoneInWorldSpaceMatrix = new float[16]; - private final float[] remappedPhoneMatrix = new float[16]; - private final float[] angles = new float[3]; - private final Display display; - private final TouchTracker touchTracker; - private final Renderer renderer; - - public PhoneOrientationListener(Display display, TouchTracker touchTracker, Renderer renderer) { - this.display = display; - this.touchTracker = touchTracker; - this.renderer = renderer; - } - - @Override - @BinderThread - public void onSensorChanged(SensorEvent event) { - SensorManager.getRotationMatrixFromVector(remappedPhoneMatrix, event.values); - - // If we're not in upright portrait mode, remap the axes of the coordinate system according to - // the display rotation. - int xAxis; - int yAxis; - switch (display.getRotation()) { - case Surface.ROTATION_270: - xAxis = SensorManager.AXIS_MINUS_Y; - yAxis = SensorManager.AXIS_X; - break; - case Surface.ROTATION_180: - xAxis = SensorManager.AXIS_MINUS_X; - yAxis = SensorManager.AXIS_MINUS_Y; - break; - case Surface.ROTATION_90: - xAxis = SensorManager.AXIS_Y; - yAxis = SensorManager.AXIS_MINUS_X; - break; - case Surface.ROTATION_0: - default: - xAxis = SensorManager.AXIS_X; - yAxis = SensorManager.AXIS_Y; - break; - } - SensorManager.remapCoordinateSystem( - remappedPhoneMatrix, xAxis, yAxis, phoneInWorldSpaceMatrix); - - // Extract the phone's roll and pass it on to touchTracker & renderer. Remapping is required - // since we need the calculated roll of the phone to be independent of the phone's pitch & - // yaw. Any operation that decomposes rotation to Euler angles needs to be performed - // carefully. - SensorManager.remapCoordinateSystem( - phoneInWorldSpaceMatrix, - SensorManager.AXIS_X, - SensorManager.AXIS_MINUS_Z, - remappedPhoneMatrix); - SensorManager.getOrientation(remappedPhoneMatrix, angles); - float roll = angles[2]; - touchTracker.setRoll(roll); - - // Rotate from Android coordinates to OpenGL coordinates. Android's coordinate system - // assumes Y points North and Z points to the sky. OpenGL has Y pointing up and Z pointing - // toward the user. - Matrix.rotateM(phoneInWorldSpaceMatrix, 0, 90, 1, 0, 0); - renderer.setDeviceOrientation(phoneInWorldSpaceMatrix, roll); - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) {} - } - /** * Standard GL Renderer implementation. The notable code is the matrix multiplication in * onDrawFrame and updatePitchMatrix. */ // @VisibleForTesting - /*package*/ class Renderer implements GLSurfaceView.Renderer, TouchTracker.Listener { + /* package */ class Renderer + implements GLSurfaceView.Renderer, TouchTracker.Listener, OrientationListener.Listener { private final SceneRenderer scene; private final float[] projectionMatrix = new float[16]; @@ -362,8 +291,9 @@ public final class SphericalSurfaceView extends GLSurfaceView { } /** Adjusts the GL camera's rotation based on device rotation. Runs on the sensor thread. */ + @Override @BinderThread - public synchronized void setDeviceOrientation(float[] matrix, float deviceRoll) { + public synchronized void onOrientationChange(float[] matrix, float deviceRoll) { System.arraycopy(matrix, 0, deviceOrientationMatrix, 0, deviceOrientationMatrix.length); this.deviceRoll = -deviceRoll; updatePitchMatrix(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java index 335f611b58..2da913fc32 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java @@ -45,16 +45,16 @@ import android.view.View; * Mesh as the user moves their finger. However, that requires quaternion interpolation. */ // @VisibleForTesting -/*package*/ class TouchTracker extends GestureDetector.SimpleOnGestureListener - implements View.OnTouchListener { +/* package */ class TouchTracker extends GestureDetector.SimpleOnGestureListener + implements View.OnTouchListener, OrientationListener.Listener { - /*package*/ interface Listener { + /* package */ interface Listener { void onScrollChange(PointF scrollOffsetDegrees); } // Touch input won't change the pitch beyond +/- 45 degrees. This reduces awkward situations // where the touch-based pitch and gyro-based pitch interact badly near the poles. - /*package*/ static final float MAX_PITCH_DEGREES = 45; + /* package */ static final float MAX_PITCH_DEGREES = 45; // With every touch event, update the accumulated degrees offset by the new pixel amount. private final PointF previousTouchPointPx = new PointF(); @@ -132,8 +132,9 @@ import android.view.View; return false; } + @Override @BinderThread - public void setRoll(float roll) { + public void onOrientationChange(float[] deviceOrientationMatrix, float roll) { // We compensate for roll by rotating in the opposite direction. this.roll = -roll; } From 083350b7dbae4569a1c2a5838715b968929c3510 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 24 Sep 2018 09:15:41 -0700 Subject: [PATCH 020/832] Do not retry failed loads whose error is FileNotFoundException ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214277073 --- RELEASENOTES.md | 1 + .../upstream/DefaultLoadErrorHandlingPolicy.java | 8 +++++--- .../upstream/DefaultLoadErrorHandlingPolicyTest.java | 9 ++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 55ad062859..032596a425 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,7 @@ ### dev-v2 (not yet released) ### +* Do not retry failed loads whose error is `FileNotFoundException`. * Allow setting log level for ExoPlayer logcat output ([#4665](https://github.com/google/ExoPlayer/issues/4665)). * Fix an issue where audio and video would desynchronize when playing diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java index 4eec495cab..8d8f156951 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.upstream; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; +import java.io.FileNotFoundException; import java.io.IOException; /** Default implementation of {@link LoadErrorHandlingPolicy}. */ @@ -76,13 +77,14 @@ public final class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPo } /** - * Retries for any exception that is not a subclass of {@link ParserException}. The retry delay is - * calculated as {@code Math.min((errorCount - 1) * 1000, 5000)}. + * Retries for any exception that is not a subclass of {@link ParserException} or {@link + * FileNotFoundException}. The retry delay is calculated as {@code Math.min((errorCount - 1) * + * 1000, 5000)}. */ @Override public long getRetryDelayMsFor( int dataType, long loadDurationMs, IOException exception, int errorCount) { - return exception instanceof ParserException + return exception instanceof ParserException || exception instanceof FileNotFoundException ? C.TIME_UNSET : Math.min((errorCount - 1) * 1000, 5000); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java index e1700e3b20..0c2b938a64 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java @@ -21,7 +21,6 @@ import android.net.Uri; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; -import java.io.FileNotFoundException; import java.io.IOException; import java.util.Collections; import org.junit.Test; @@ -57,7 +56,7 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_dontBlacklistUnexpectedExceptions() { - FileNotFoundException exception = new FileNotFoundException(); + IOException exception = new IOException(); assertThat(getDefaultPolicyBlacklistOutputFor(exception)).isEqualTo(C.TIME_UNSET); } @@ -69,9 +68,9 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getRetryDelayMsFor_successiveRetryDelays() { - assertThat(getDefaultPolicyRetryDelayOutputFor(new FileNotFoundException(), 3)).isEqualTo(2000); - assertThat(getDefaultPolicyRetryDelayOutputFor(new FileNotFoundException(), 5)).isEqualTo(4000); - assertThat(getDefaultPolicyRetryDelayOutputFor(new FileNotFoundException(), 9)).isEqualTo(5000); + assertThat(getDefaultPolicyRetryDelayOutputFor(new IOException(), 3)).isEqualTo(2000); + assertThat(getDefaultPolicyRetryDelayOutputFor(new IOException(), 5)).isEqualTo(4000); + assertThat(getDefaultPolicyRetryDelayOutputFor(new IOException(), 9)).isEqualTo(5000); } private static long getDefaultPolicyBlacklistOutputFor(IOException exception) { From b2e0a365d35feb5e5edb6439293f056ab022432c Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 26 Sep 2018 03:27:52 -0700 Subject: [PATCH 021/832] Add convenience methods player.next() and player.previous() This simplifies code skipping items in a playlist programatically. Issue:#4863 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214580742 --- RELEASENOTES.md | 3 ++ .../exoplayer2/ext/cast/CastPlayer.java | 26 ++++++++++++++++ .../android/exoplayer2/ExoPlayerImpl.java | 26 ++++++++++++++++ .../com/google/android/exoplayer2/Player.java | 26 ++++++++++++++++ .../android/exoplayer2/SimpleExoPlayer.java | 30 +++++++++++++++++++ .../exoplayer2/testutil/StubExoPlayer.java | 20 +++++++++++++ 6 files changed, 131 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 032596a425..bcf05590bf 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,6 +8,9 @@ * Fix an issue where audio and video would desynchronize when playing concatenations of gapless content ([#4559](https://github.com/google/ExoPlayer/issues/4559)). +* Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext` + and `Player.hasPrevious` + ([#4863](https://github.com/google/ExoPlayer/issues/4863)). ### 2.9.0 ### diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 97b05e3f0a..f630ea1628 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -382,6 +382,32 @@ public final class CastPlayer implements Player { } } + @Override + public boolean hasPrevious() { + return getPreviousWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void previous() { + int previousWindowIndex = getPreviousWindowIndex(); + if (previousWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(previousWindowIndex); + } + } + + @Override + public boolean hasNext() { + return getNextWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void next() { + int nextWindowIndex = getPreviousWindowIndex(); + if (nextWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(nextWindowIndex); + } + } + @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { // Unsupported by the RemoteMediaClient API. Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 027f316493..83854ffca9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -348,6 +348,32 @@ import java.util.concurrent.CopyOnWriteArraySet; } } + @Override + public boolean hasPrevious() { + return getPreviousWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void previous() { + int previousWindowIndex = getPreviousWindowIndex(); + if (previousWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(previousWindowIndex); + } + } + + @Override + public boolean hasNext() { + return getNextWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void next() { + int nextWindowIndex = getPreviousWindowIndex(); + if (nextWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(nextWindowIndex); + } + } + @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { if (playbackParameters == null) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index d4b965dbd6..3640928c54 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -655,6 +655,32 @@ public interface Player { */ void seekTo(int windowIndex, long positionMs); + /** + * Returns whether a previous window exists, which may depend on the current repeat mode and + * whether shuffle mode is enabled. + */ + boolean hasPrevious(); + + /** + * Seeks to the default position of the previous window in the timeline, which may depend on the + * current repeat mode and whether shuffle mode is enabled. Does nothing if {@link #hasPrevious()} + * is {@code false}. + */ + void previous(); + + /** + * Returns whether a next window exists, which may depend on the current repeat mode and whether + * shuffle mode is enabled. + */ + boolean hasNext(); + + /** + * Seeks to the default position of the next window in the timeline, which may depend on the + * current repeat mode and whether shuffle mode is enabled. Does nothing if {@link #hasNext()} is + * {@code false}. + */ + void next(); + /** * Attempts to set the playback parameters. Passing {@code null} sets the parameters to the * default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 39f1655ab5..7e5feec024 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -955,6 +955,36 @@ public class SimpleExoPlayer player.seekTo(windowIndex, positionMs); } + @Override + public boolean hasPrevious() { + verifyApplicationThread(); + return getPreviousWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void previous() { + verifyApplicationThread(); + if (hasPrevious()) { + analyticsCollector.notifySeekStarted(); + player.previous(); + } + } + + @Override + public boolean hasNext() { + verifyApplicationThread(); + return getNextWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void next() { + verifyApplicationThread(); + if (hasNext()) { + analyticsCollector.notifySeekStarted(); + player.next(); + } + } + @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { verifyApplicationThread(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index f1349c1158..7882c7d16c 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -149,6 +149,26 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } + @Override + public boolean hasPrevious() { + throw new UnsupportedOperationException(); + } + + @Override + public void previous() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasNext() { + throw new UnsupportedOperationException(); + } + + @Override + public void next() { + throw new UnsupportedOperationException(); + } + @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { throw new UnsupportedOperationException(); From c9ebaacae01d1e2afbb95850e4f8f0d6791243a1 Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 26 Sep 2018 03:47:54 -0700 Subject: [PATCH 022/832] Use GlUtil class to share GL methods ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214582295 --- .../exoplayer2/ext/vp9/VpxRenderer.java | 81 +++-------------- .../exoplayer2/util/EGLSurfaceTexture.java | 5 +- .../android/exoplayer2/util}/GlUtil.java | 86 +++++++++++-------- .../ui/spherical/ProjectionRenderer.java | 3 +- .../ui/spherical/SceneRenderer.java | 3 +- 5 files changed, 68 insertions(+), 110 deletions(-) rename library/{ui/src/main/java/com/google/android/exoplayer2/ui/spherical => core/src/main/java/com/google/android/exoplayer2/util}/GlUtil.java (63%) diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java index 837539593e..d82f5a6071 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java @@ -17,8 +17,7 @@ package com.google.android.exoplayer2.ext.vp9; import android.opengl.GLES20; import android.opengl.GLSurfaceView; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; +import com.google.android.exoplayer2.util.GlUtil; import java.nio.FloatBuffer; import java.util.concurrent.atomic.AtomicReference; import javax.microedition.khronos.egl.EGLConfig; @@ -72,11 +71,8 @@ import javax.microedition.khronos.opengles.GL10; + " gl_FragColor = vec4(mColorConversion * yuv, 1.0);\n" + "}\n"; - private static final FloatBuffer TEXTURE_VERTICES = nativeFloatBuffer( - -1.0f, 1.0f, - -1.0f, -1.0f, - 1.0f, 1.0f, - 1.0f, -1.0f); + private static final FloatBuffer TEXTURE_VERTICES = + GlUtil.createBuffer(new float[] {-1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f}); private final int[] yuvTextures = new int[3]; private final AtomicReference pendingOutputBufferReference; @@ -114,21 +110,7 @@ import javax.microedition.khronos.opengles.GL10; @Override public void onSurfaceCreated(GL10 unused, EGLConfig config) { - // Create the GL program. - program = GLES20.glCreateProgram(); - - // Add the vertex and fragment shaders. - addShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER, program); - addShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER, program); - - // Link the GL program. - GLES20.glLinkProgram(program); - int[] result = new int[] { - GLES20.GL_FALSE - }; - result[0] = GLES20.GL_FALSE; - GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, result, 0); - abortUnless(result[0] == GLES20.GL_TRUE, GLES20.glGetProgramInfoLog(program)); + program = GlUtil.compileProgram(VERTEX_SHADER, FRAGMENT_SHADER); GLES20.glUseProgram(program); int posLocation = GLES20.glGetAttribLocation(program, "in_pos"); GLES20.glEnableVertexAttribArray(posLocation); @@ -136,11 +118,11 @@ import javax.microedition.khronos.opengles.GL10; posLocation, 2, GLES20.GL_FLOAT, false, 0, TEXTURE_VERTICES); texLocation = GLES20.glGetAttribLocation(program, "in_tc"); GLES20.glEnableVertexAttribArray(texLocation); - checkNoGLES2Error(); + GlUtil.checkGlError(); colorMatrixLocation = GLES20.glGetUniformLocation(program, "mColorConversion"); - checkNoGLES2Error(); + GlUtil.checkGlError(); setupTextures(); - checkNoGLES2Error(); + GlUtil.checkGlError(); } @Override @@ -191,11 +173,8 @@ import javax.microedition.khronos.opengles.GL10; float crop = (float) outputBuffer.width / outputBuffer.yuvStrides[0]; // This buffer is consumed during each call to glDrawArrays. It needs to be a member variable // rather than a local variable to ensure that it doesn't get garbage collected. - textureCoords = nativeFloatBuffer( - 0.0f, 0.0f, - 0.0f, 1.0f, - crop, 0.0f, - crop, 1.0f); + textureCoords = + GlUtil.createBuffer(new float[] {0.0f, 0.0f, 0.0f, 1.0f, crop, 0.0f, crop, 1.0f}); GLES20.glVertexAttribPointer( texLocation, 2, GLES20.GL_FLOAT, false, 0, textureCoords); previousWidth = outputBuffer.width; @@ -203,23 +182,7 @@ import javax.microedition.khronos.opengles.GL10; } GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - checkNoGLES2Error(); - } - - private void addShader(int type, String source, int program) { - int[] result = new int[] { - GLES20.GL_FALSE - }; - int shader = GLES20.glCreateShader(type); - GLES20.glShaderSource(shader, source); - GLES20.glCompileShader(shader); - GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0); - abortUnless(result[0] == GLES20.GL_TRUE, - GLES20.glGetShaderInfoLog(shader) + ", source: " + source); - GLES20.glAttachShader(program, shader); - GLES20.glDeleteShader(shader); - - checkNoGLES2Error(); + GlUtil.checkGlError(); } private void setupTextures() { @@ -237,28 +200,6 @@ import javax.microedition.khronos.opengles.GL10; GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); } - checkNoGLES2Error(); + GlUtil.checkGlError(); } - - private void abortUnless(boolean condition, String msg) { - if (!condition) { - throw new RuntimeException(msg); - } - } - - private void checkNoGLES2Error() { - int error = GLES20.glGetError(); - if (error != GLES20.GL_NO_ERROR) { - throw new RuntimeException("GLES20 error: " + error); - } - } - - private static FloatBuffer nativeFloatBuffer(float... array) { - FloatBuffer buffer = ByteBuffer.allocateDirect(array.length * 4).order( - ByteOrder.nativeOrder()).asFloatBuffer(); - buffer.put(array); - buffer.flip(); - return buffer; - } - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java index 90e37de828..bf4d78ee10 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java @@ -305,9 +305,6 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL private static void generateTextureIds(int[] textureIdHolder) { GLES20.glGenTextures(/* n= */ 1, textureIdHolder, /* offset= */ 0); - int errorCode = GLES20.glGetError(); - if (errorCode != GLES20.GL_NO_ERROR) { - throw new GlException("glGenTextures failed. Error: " + Integer.toHexString(errorCode)); - } + GlUtil.checkGlError(); } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java similarity index 63% rename from library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java index 68a85938a3..72a782c2ac 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.ui.spherical; +package com.google.android.exoplayer2.util; import static android.opengl.GLU.gluErrorString; @@ -23,15 +23,14 @@ import android.opengl.GLES20; import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; -import com.google.android.exoplayer2.util.Log; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.IntBuffer; /** GL utility methods. */ -/* package */ final class GlUtil { - private static final String TAG = "Spherical.Utils"; +public final class GlUtil { + private static final String TAG = "GlUtil"; /** Class only contains static methods. */ private GlUtil() {} @@ -57,42 +56,39 @@ import java.nio.IntBuffer; } /** - * Builds a GL shader program from vertex & fragment shader code. The vertex and fragment shaders - * are passed as arrays of strings in order to make debugging compilation issues easier. + * Builds a GL shader program from vertex & fragment shader code. + * + * @param vertexCode GLES20 vertex shader program as arrays of strings. Strings are joined by + * adding a new line character in between each of them. + * @param fragmentCode GLES20 fragment shader program as arrays of strings. Strings are joined by + * adding a new line character in between each of them. + * @return GLES20 program id. + */ + public static int compileProgram(String[] vertexCode, String[] fragmentCode) { + return compileProgram(TextUtils.join("\n", vertexCode), TextUtils.join("\n", fragmentCode)); + } + + /** + * Builds a GL shader program from vertex & fragment shader code. * * @param vertexCode GLES20 vertex shader program. * @param fragmentCode GLES20 fragment shader program. * @return GLES20 program id. */ - public static int compileProgram(String[] vertexCode, String[] fragmentCode) { - checkGlError(); - // prepare shaders and OpenGL program - int vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER); - GLES20.glShaderSource(vertexShader, TextUtils.join("\n", vertexCode)); - GLES20.glCompileShader(vertexShader); - checkGlError(); - - int fragmentShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER); - GLES20.glShaderSource(fragmentShader, TextUtils.join("\n", fragmentCode)); - GLES20.glCompileShader(fragmentShader); - checkGlError(); - + public static int compileProgram(String vertexCode, String fragmentCode) { int program = GLES20.glCreateProgram(); - GLES20.glAttachShader(program, vertexShader); - GLES20.glDeleteShader(vertexShader); - GLES20.glAttachShader(program, fragmentShader); - GLES20.glDeleteShader(fragmentShader); + checkGlError(); + + // Add the vertex and fragment shaders. + addShader(GLES20.GL_VERTEX_SHADER, vertexCode, program); + addShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode, program); // Link and check for errors. GLES20.glLinkProgram(program); - int[] linkStatus = new int[1]; + int[] linkStatus = new int[] {GLES20.GL_FALSE}; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] != GLES20.GL_TRUE) { - String errorMsg = "Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(program); - Log.e(TAG, errorMsg); - if (ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED) { - throw new RuntimeException(errorMsg); - } + throwGlError("Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(program)); } checkGlError(); @@ -101,12 +97,11 @@ import java.nio.IntBuffer; /** Allocates a FloatBuffer with the given data. */ public static FloatBuffer createBuffer(float[] data) { - ByteBuffer bb = ByteBuffer.allocateDirect(data.length * C.BYTES_PER_FLOAT); - bb.order(ByteOrder.nativeOrder()); - FloatBuffer buffer = bb.asFloatBuffer(); + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * C.BYTES_PER_FLOAT); + byteBuffer.order(ByteOrder.nativeOrder()); + FloatBuffer buffer = byteBuffer.asFloatBuffer(); buffer.put(data); - buffer.position(0); - + buffer.flip(); return buffer; } @@ -130,4 +125,27 @@ import java.nio.IntBuffer; checkGlError(); return texId[0]; } + + private static void addShader(int type, String source, int program) { + int shader = GLES20.glCreateShader(type); + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + + int[] result = new int[] {GLES20.GL_FALSE}; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0); + if (result[0] != GLES20.GL_TRUE) { + throwGlError(GLES20.glGetShaderInfoLog(shader) + ", source: " + source); + } + + GLES20.glAttachShader(program, shader); + GLES20.glDeleteShader(shader); + checkGlError(); + } + + private static void throwGlError(String errorMsg) { + Log.e(TAG, errorMsg); + if (ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED) { + throw new RuntimeException(errorMsg); + } + } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java index b0760ec2bd..47515b023a 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java @@ -15,12 +15,13 @@ */ package com.google.android.exoplayer2.ui.spherical; -import static com.google.android.exoplayer2.ui.spherical.GlUtil.checkGlError; +import static com.google.android.exoplayer2.util.GlUtil.checkGlError; import android.annotation.TargetApi; import android.opengl.GLES11Ext; import android.opengl.GLES20; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.video.spherical.Projection; import java.nio.FloatBuffer; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java index ff3641135c..048c346781 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java @@ -15,7 +15,7 @@ */ package com.google.android.exoplayer2.ui.spherical; -import static com.google.android.exoplayer2.ui.spherical.GlUtil.checkGlError; +import static com.google.android.exoplayer2.util.GlUtil.checkGlError; import android.graphics.SurfaceTexture; import android.opengl.GLES20; @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ui.spherical.ProjectionRenderer.EyeType; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.util.TimedValueQueue; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; From a8efa27fabf18dcbfb61d5375c58d6aada0dfbb6 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 26 Sep 2018 04:22:36 -0700 Subject: [PATCH 023/832] Make DefaultLoadErrorHandlingPolicy non-final ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214584874 --- .../exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java index 8d8f156951..720a8b08d4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java @@ -22,7 +22,7 @@ import java.io.FileNotFoundException; import java.io.IOException; /** Default implementation of {@link LoadErrorHandlingPolicy}. */ -public final class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPolicy { +public class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPolicy { /** The default minimum number of times to retry loading data prior to propagating the error. */ public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; From 776ad20a507048edd848e99b4b3f9c5a27c42a7a Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 26 Sep 2018 06:55:17 -0700 Subject: [PATCH 024/832] Add a MediaQueue abstraction to the cast extension ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214598078 --- .../exoplayer2/castdemo/PlayerManager.java | 3 +- extensions/cast/build.gradle | 2 + .../exoplayer2/ext/cast/CastPlayer.java | 35 +-- .../exoplayer2/ext/cast/MediaItem.java | 293 ++++++++++++++++++ .../exoplayer2/ext/cast/MediaItemQueue.java | 85 +++++ .../exoplayer2/ext/cast/RemotePlayer.java | 45 +++ .../exoplayer2/ext/cast/MediaItemTest.java | 92 ++++++ 7 files changed, 528 insertions(+), 27 deletions(-) create mode 100644 extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java create mode 100644 extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemQueue.java create mode 100644 extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/RemotePlayer.java create mode 100644 extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java index d188469de8..0c69e40164 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.castdemo.DemoUtil.Sample; import com.google.android.exoplayer2.ext.cast.CastPlayer; +import com.google.android.exoplayer2.ext.cast.RemotePlayer; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; @@ -51,7 +52,7 @@ import java.util.ArrayList; /** Manages players and an internal media queue for the ExoPlayer/Cast demo app. */ /* package */ final class PlayerManager - implements EventListener, CastPlayer.SessionAvailabilityListener { + implements EventListener, RemotePlayer.SessionAvailabilityListener { /** * Listener for changes in the media queue playback position. diff --git a/extensions/cast/build.gradle b/extensions/cast/build.gradle index bee73cac12..ab14b4034a 100644 --- a/extensions/cast/build.gradle +++ b/extensions/cast/build.gradle @@ -32,6 +32,8 @@ android { dependencies { api 'com.google.android.gms:play-services-cast-framework:16.0.1' + compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion + compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-ui') testImplementation project(modulePrefix + 'testutils') diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index f630ea1628..ecaa56427f 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -52,35 +52,18 @@ import java.util.concurrent.CopyOnWriteArraySet; * {@link Player} implementation that communicates with a Cast receiver app. * *

The behavior of this class depends on the underlying Cast session, which is obtained from the - * Cast context passed to {@link #CastPlayer}. To keep track of the session, - * {@link #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be - * implemented and attached to the player.

+ * Cast context passed to {@link #CastPlayer}. To keep track of the session, {@link + * #isCastSessionAvailable()} can be queried and {@link RemotePlayer.SessionAvailabilityListener} + * can be implemented and attached to the player. * - *

If no session is available, the player state will remain unchanged and calls to methods that + *

If no session is available, the player state will remain unchanged and calls to methods that * alter it will be ignored. Querying the player state is possible even when no session is - * available, in which case, the last observed receiver app state is reported.

+ * available, in which case, the last observed receiver app state is reported. * - *

Methods should be called on the application's main thread.

+ *

Methods should be called on the application's main thread. */ public final class CastPlayer implements Player { - /** - * Listener of changes in the cast session availability. - */ - public interface SessionAvailabilityListener { - - /** - * Called when a cast session becomes available to the player. - */ - void onCastSessionAvailable(); - - /** - * Called when the cast session becomes unavailable. - */ - void onCastSessionUnavailable(); - - } - private static final String TAG = "CastPlayer"; private static final int RENDERER_COUNT = 3; @@ -106,7 +89,7 @@ public final class CastPlayer implements Player { // Listeners. private final CopyOnWriteArraySet listeners; - private SessionAvailabilityListener sessionAvailabilityListener; + private RemotePlayer.SessionAvailabilityListener sessionAvailabilityListener; // Internal state. private CastTimeline currentTimeline; @@ -276,9 +259,9 @@ public final class CastPlayer implements Player { /** * Sets a listener for updates on the cast session availability. * - * @param listener The {@link SessionAvailabilityListener}. + * @param listener The {@link RemotePlayer.SessionAvailabilityListener}. */ - public void setSessionAvailabilityListener(SessionAvailabilityListener listener) { + public void setSessionAvailabilityListener(RemotePlayer.SessionAvailabilityListener listener) { sessionAvailabilityListener = listener; } diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java new file mode 100644 index 0000000000..67428e8af4 --- /dev/null +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.cast; + +import android.net.Uri; +import android.support.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.Assertions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; + +/** Representation of an item that can be played by a media player. */ +public final class MediaItem { + + /** A builder for {@link MediaItem} instances. */ + public static final class Builder { + + @Nullable private UUID uuid; + private String title; + private String description; + private MediaItem.UriBundle media; + @Nullable private Object attachment; + private List drmSchemes; + private long startPositionUs; + private long endPositionUs; + private String mimeType; + + /** Creates an builder with default field values. */ + public Builder() { + clearInternal(); + } + + /** See {@link MediaItem#uuid}. */ + public Builder setUuid(UUID uuid) { + this.uuid = uuid; + return this; + } + + /** See {@link MediaItem#title}. */ + public Builder setTitle(String title) { + this.title = title; + return this; + } + + /** See {@link MediaItem#description}. */ + public Builder setDescription(String description) { + this.description = description; + return this; + } + + /** Equivalent to {@link #setMedia(UriBundle) setMedia(new UriBundle(uri))}. */ + public Builder setMedia(String uri) { + return setMedia(new UriBundle(uri)); + } + + /** See {@link MediaItem#media}. */ + public Builder setMedia(UriBundle media) { + this.media = media; + return this; + } + + /** See {@link MediaItem#attachment}. */ + public Builder setAttachment(Object attachment) { + this.attachment = attachment; + return this; + } + + /** See {@link MediaItem#drmSchemes}. */ + public Builder setDrmSchemes(List drmSchemes) { + this.drmSchemes = Collections.unmodifiableList(new ArrayList<>(drmSchemes)); + return this; + } + + /** See {@link MediaItem#startPositionUs}. */ + public Builder setStartPositionUs(long startPositionUs) { + this.startPositionUs = startPositionUs; + return this; + } + + /** See {@link MediaItem#endPositionUs}. */ + public Builder setEndPositionUs(long endPositionUs) { + Assertions.checkArgument(endPositionUs != C.TIME_END_OF_SOURCE); + this.endPositionUs = endPositionUs; + return this; + } + + /** See {@link MediaItem#mimeType}. */ + public Builder setMimeType(String mimeType) { + this.mimeType = mimeType; + return this; + } + + /** + * Equivalent to {@link #build()}, except it also calls {@link #clear()} after creating the + * {@link MediaItem}. + */ + public MediaItem buildAndClear() { + MediaItem item = build(); + clearInternal(); + return item; + } + + /** Returns the builder to default values. */ + public Builder clear() { + clearInternal(); + return this; + } + + /** + * Returns a new {@link MediaItem} instance with the current builder values. This method also + * clears any values passed to {@link #setUuid(UUID)}. + */ + public MediaItem build() { + UUID uuid = this.uuid; + this.uuid = null; + return new MediaItem( + uuid != null ? uuid : UUID.randomUUID(), + title, + description, + media, + attachment, + drmSchemes, + startPositionUs, + endPositionUs, + mimeType); + } + + @EnsuresNonNull({"title", "description", "media", "drmSchemes", "mimeType"}) + private void clearInternal(@UnknownInitialization Builder this) { + uuid = null; + title = ""; + description = ""; + media = UriBundle.EMPTY; + attachment = null; + drmSchemes = Collections.emptyList(); + startPositionUs = C.TIME_UNSET; + endPositionUs = C.TIME_UNSET; + mimeType = ""; + } + } + + /** Bundles a resource's URI with headers to attach to any request to that URI. */ + public static final class UriBundle { + + /** An empty {@link UriBundle}. */ + public static final UriBundle EMPTY = new UriBundle(""); + + /** A URI. */ + public final Uri uri; + + /** The headers to attach to any request for the given URI. */ + public final Map requestHeaders; + + /** + * Creates an instance from the given string with no request headers. + * + * @param uriString See {@link #uri}. + */ + public UriBundle(String uriString) { + this(Uri.parse(uriString), Collections.emptyMap()); + } + + /** + * Creates an instance with the given URI and request headers. + * + * @param uri See {@link #uri}. + * @param requestHeaders See {@link #requestHeaders}. + */ + public UriBundle(Uri uri, Map requestHeaders) { + this.uri = uri; + this.requestHeaders = Collections.unmodifiableMap(new HashMap<>(requestHeaders)); + } + } + + /** + * Represents a DRM protection scheme, and optionally provides information about how to acquire + * the license for the media. + */ + public static final class DrmScheme { + + /** The UUID of the protection scheme. */ + public final UUID uuid; + + /** + * A optional {@link UriBundle} for the license server. If no license server is provided, the + * server must be provided by the media. + */ + @Nullable public final UriBundle licenseServerUri; + + /** + * Creates an instance. + * + * @param uuid See {@link #uuid}. + * @param licenseServerUri See {@link #licenseServerUri}. + */ + public DrmScheme(UUID uuid, @Nullable UriBundle licenseServerUri) { + this.uuid = uuid; + this.licenseServerUri = licenseServerUri; + } + } + + /** + * A UUID that identifies this item, potentially across different devices. The default value is + * obtained by calling {@link UUID#randomUUID()}. + */ + public final UUID uuid; + + /** The title of the item. The default value is an empty string. */ + public final String title; + + /** A description for the item. The default value is an empty string. */ + public final String description; + + /** + * A {@link UriBundle} to fetch the media content. The default value is {@link UriBundle#EMPTY}. + */ + public final UriBundle media; + + /** + * An optional opaque object to attach to the media item. Handling of this attachment is + * implementation specific. The default value is null. + */ + @Nullable public final Object attachment; + + /** + * Immutable list of {@link DrmScheme} instances sorted in decreasing order of preference. The + * default value is an empty list. + */ + public final List drmSchemes; + + /** + * The position in microseconds at which playback of this media item should start. {@link + * C#TIME_UNSET} if playback should start at the default position. The default value is {@link + * C#TIME_UNSET}. + */ + public final long startPositionUs; + + /** + * The position in microseconds at which playback of this media item should end. {@link + * C#TIME_UNSET} if playback should end at the end of the media. The default value is {@link + * C#TIME_UNSET}. + */ + public final long endPositionUs; + + /** + * The mime type of this media item. The default value is an empty string. + * + *

The usage of this mime type is optional and player implementation specific. + */ + public final String mimeType; + + // TODO: Add support for sideloaded tracks, artwork, icon, and subtitle. + + private MediaItem( + UUID uuid, + String title, + String description, + UriBundle media, + @Nullable Object attachment, + List drmSchemes, + long startPositionUs, + long endPositionUs, + String mimeType) { + this.uuid = uuid; + this.title = title; + this.description = description; + this.media = media; + this.attachment = attachment; + this.drmSchemes = drmSchemes; + this.startPositionUs = startPositionUs; + this.endPositionUs = endPositionUs; + this.mimeType = mimeType; + } +} diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemQueue.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemQueue.java new file mode 100644 index 0000000000..184e347e1c --- /dev/null +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemQueue.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.cast; + +/** Represents a sequence of {@link MediaItem MediaItems}. */ +public interface MediaItemQueue { + + /** + * Returns the item at the given index. + * + * @param index The index of the item to retrieve. + * @return The item at the given index. + * @throws IndexOutOfBoundsException If {@code index < 0 || index >= getSize()}. + */ + MediaItem get(int index); + + /** Returns the number of items in this queue. */ + int getSize(); + + /** + * Appends the given sequence of items to the queue. + * + * @param items The sequence of items to append. + */ + void add(MediaItem... items); + + /** + * Adds the given sequence of items to the queue at the given position, so that the first of + * {@code items} is placed at the given index. + * + * @param index The index at which {@code items} will be inserted. + * @param items The sequence of items to append. + * @throws IndexOutOfBoundsException If {@code index < 0 || index > getSize()}. + */ + void add(int index, MediaItem... items); + + /** + * Moves an existing item within the playlist. + * + *

Calling this method is equivalent to removing the item at position {@code indexFrom} and + * immediately inserting it at position {@code indexTo}. If the moved item is being played at the + * moment of the invocation, playback will stick with the moved item. + * + * @param indexFrom The index of the item to move. + * @param indexTo The index at which the item will be placed after this operation. + * @throws IndexOutOfBoundsException If for either index, {@code index < 0 || index >= getSize()}. + */ + void move(int indexFrom, int indexTo); + + /** + * Removes an item from the queue. + * + * @param index The index of the item to remove from the queue. + * @throws IndexOutOfBoundsException If {@code index < 0 || index >= getSize()}. + */ + void remove(int index); + + /** + * Removes a range of items from the queue. + * + *

Does nothing if an empty range ({@code from == exclusiveTo}) is passed. + * + * @param from The inclusive index at which the range to remove starts. + * @param exclusiveTo The exclusive index at which the range to remove ends. + * @throws IndexOutOfBoundsException If {@code from < 0 || exclusiveTo > getSize() || from > + * exclusiveTo}. + */ + void removeRange(int from, int exclusiveTo); + + /** Removes all items in the queue. */ + void clear(); +} diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/RemotePlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/RemotePlayer.java new file mode 100644 index 0000000000..98d5895db8 --- /dev/null +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/RemotePlayer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.cast; + +import com.google.android.exoplayer2.Player; + +/** A {@link Player} for playing media remotely using the Google Cast framework. */ +public interface RemotePlayer extends Player { + + /** Listener of changes in the cast session availability. */ + interface SessionAvailabilityListener { + + /** Called when a cast session becomes available to the player. */ + void onCastSessionAvailable(); + + /** Called when the cast session becomes unavailable. */ + void onCastSessionUnavailable(); + } + + /** Returns whether a cast session is available. */ + boolean isCastSessionAvailable(); + + /** + * Sets a listener for updates on the cast session availability. + * + * @param listener The {@link SessionAvailabilityListener}. + */ + void setSessionAvailabilityListener(SessionAvailabilityListener listener); + + /** Returns the {@link MediaItemQueue} associated to this player. */ + MediaItemQueue getMediaItemQueue(); +} diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java new file mode 100644 index 0000000000..3c7ec6cab3 --- /dev/null +++ b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.cast; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.MimeTypes; +import java.util.UUID; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Test for {@link MediaItem}. */ +@RunWith(RobolectricTestRunner.class) +public class MediaItemTest { + + @Test + public void buildMediaItem_resetsUuid() { + MediaItem.Builder builder = new MediaItem.Builder(); + UUID uuid = new UUID(1, 1); + MediaItem item1 = builder.setUuid(uuid).build(); + MediaItem item2 = builder.build(); + MediaItem item3 = builder.build(); + assertThat(item1.uuid).isEqualTo(uuid); + assertThat(item2.uuid).isNotEqualTo(uuid); + assertThat(item3.uuid).isNotEqualTo(item2.uuid); + assertThat(item3.uuid).isNotEqualTo(uuid); + } + + @Test + public void buildMediaItem_doesNotChangeState() { + MediaItem.Builder builder = new MediaItem.Builder(); + MediaItem item1 = + builder + .setMedia("http://example.com") + .setTitle("title") + .setMimeType(MimeTypes.AUDIO_MP4) + .setStartPositionUs(3) + .setEndPositionUs(4) + .build(); + MediaItem item2 = builder.build(); + assertThat(item1.title).isEqualTo(item2.title); + assertThat(item1.media.uri).isEqualTo(item2.media.uri); + assertThat(item1.mimeType).isEqualTo(item2.mimeType); + assertThat(item1.startPositionUs).isEqualTo(item2.startPositionUs); + assertThat(item1.endPositionUs).isEqualTo(item2.endPositionUs); + } + + @Test + public void buildMediaItem_assertDefaultValues() { + assertDefaultValues(new MediaItem.Builder().build()); + } + + @Test + public void buildMediaItem_testClear() { + MediaItem.Builder builder = new MediaItem.Builder(); + builder + .setMedia("http://example.com") + .setTitle("title") + .setMimeType(MimeTypes.AUDIO_MP4) + .setStartPositionUs(3) + .setEndPositionUs(4) + .buildAndClear(); + assertDefaultValues(builder.build()); + } + + private static void assertDefaultValues(MediaItem item) { + assertThat(item.title).isEmpty(); + assertThat(item.description).isEmpty(); + assertThat(item.media.uri).isEqualTo(Uri.EMPTY); + assertThat(item.attachment).isNull(); + assertThat(item.drmSchemes).isEmpty(); + assertThat(item.startPositionUs).isEqualTo(C.TIME_UNSET); + assertThat(item.endPositionUs).isEqualTo(C.TIME_UNSET); + assertThat(item.mimeType).isEmpty(); + } +} From 3073d38a5af0b61c5029957f3a730dd667fbdcc1 Mon Sep 17 00:00:00 2001 From: cblay Date: Wed, 26 Sep 2018 11:31:44 -0700 Subject: [PATCH 025/832] Pass MediaPeriodId to TrackSelector.selectTracks() ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214638939 --- .../android/exoplayer2/ExoPlayerImpl.java | 2 +- .../android/exoplayer2/MediaPeriodHolder.java | 2 +- .../trackselection/MappingTrackSelector.java | 8 +- .../trackselection/TrackSelector.java | 35 ++- .../DefaultTrackSelectorTest.java | 273 ++++++++++++------ .../MappingTrackSelectorTest.java | 8 +- .../trackselection/TrackSelectorTest.java | 23 +- 7 files changed, 225 insertions(+), 126 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 83854ffca9..96e7703559 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -51,7 +51,7 @@ import java.util.concurrent.CopyOnWriteArraySet; * This empty track selector result can only be used for {@link PlaybackInfo#trackSelectorResult} * when the player does not have any track selection made (such as when player is reset, or when * player seeks to an unprepared period). It will not be used as result of any {@link - * TrackSelector#selectTracks(RendererCapabilities[], TrackGroupArray)} operation. + * TrackSelector#selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId)} operation. */ /* package */ final TrackSelectorResult emptyTrackSelectorResult; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index 4941b4efc6..70c9354e71 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -162,7 +162,7 @@ import com.google.android.exoplayer2.util.Log; public boolean selectTracks(float playbackSpeed) throws ExoPlaybackException { TrackSelectorResult selectorResult = - trackSelector.selectTracks(rendererCapabilities, trackGroups); + trackSelector.selectTracks(rendererCapabilities, trackGroups, info.id); if (selectorResult.isEquivalent(periodTrackSelectorResult)) { return false; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index c2fda67728..ce5dff8556 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.util.Util; @@ -326,8 +327,11 @@ public abstract class MappingTrackSelector extends TrackSelector { } @Override - public final TrackSelectorResult selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray trackGroups) throws ExoPlaybackException { + public final TrackSelectorResult selectTracks( + RendererCapabilities[] rendererCapabilities, + TrackGroupArray trackGroups, + MediaPeriodId periodId) + throws ExoPlaybackException { // Structures into which data will be written during the selection. The extra item at the end // of each array is to store data associated with track groups that cannot be associated with // any renderer. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java index 3bb603318f..bc2460036c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.Assertions; @@ -40,9 +41,9 @@ import com.google.android.exoplayer2.util.Assertions; *

  • When the player is created it will initialize the track selector by calling {@link * #init(InvalidationListener, BandwidthMeter)}. *
  • When the player needs to make a track selection it will call {@link - * #selectTracks(RendererCapabilities[], TrackGroupArray)}. This typically occurs at the start - * of playback, when the player starts to buffer a new period of the media being played, and - * when the track selector invalidates its previous selections. + * #selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId)}. This typically + * occurs at the start of playback, when the player starts to buffer a new period of the media + * being played, and when the track selector invalidates its previous selections. *
  • The player may perform a track selection well in advance of the selected tracks becoming * active, where active is defined to mean that the renderers are actually consuming media * corresponding to the selection that was made. For example when playing media containing @@ -66,14 +67,14 @@ import com.google.android.exoplayer2.util.Assertions; *

    Renderer configuration

    * * The {@link TrackSelectorResult} returned by {@link #selectTracks(RendererCapabilities[], - * TrackGroupArray)} contains not only {@link TrackSelection}s for each renderer, but also {@link - * RendererConfiguration}s defining configuration parameters that the renderers should apply when - * consuming the corresponding media. Whilst it may seem counter-intuitive for a track selector to - * also specify renderer configuration information, in practice the two are tightly bound together. - * It may only be possible to play a certain combination tracks if the renderers are configured in a - * particular way. Equally, it may only be possible to configure renderers in a particular way if - * certain tracks are selected. Hence it makes sense to determined the track selection and - * corresponding renderer configurations in a single step. + * TrackGroupArray, MediaPeriodId)} contains not only {@link TrackSelection}s for each renderer, but + * also {@link RendererConfiguration}s defining configuration parameters that the renderers should + * apply when consuming the corresponding media. Whilst it may seem counter-intuitive for a track + * selector to also specify renderer configuration information, in practice the two are tightly + * bound together. It may only be possible to play a certain combination tracks if the renderers are + * configured in a particular way. Equally, it may only be possible to configure renderers in a + * particular way if certain tracks are selected. Hence it makes sense to determined the track + * selection and corresponding renderer configurations in a single step. * *

    Threading model

    * @@ -117,15 +118,19 @@ public abstract class TrackSelector { * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which tracks * are to be selected. * @param trackGroups The available track groups. + * @param periodId The {@link MediaPeriodId} of the period for which tracks are to be selected. * @return A {@link TrackSelectorResult} describing the track selections. * @throws ExoPlaybackException If an error occurs selecting tracks. */ - public abstract TrackSelectorResult selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray trackGroups) throws ExoPlaybackException; + public abstract TrackSelectorResult selectTracks( + RendererCapabilities[] rendererCapabilities, + TrackGroupArray trackGroups, + MediaPeriodId periodId) + throws ExoPlaybackException; /** - * Called by the player when a {@link TrackSelectorResult} previously generated by - * {@link #selectTracks(RendererCapabilities[], TrackGroupArray)} is activated. + * Called by the player when a {@link TrackSelectorResult} previously generated by {@link + * #selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId)} is activated. * * @param info The value of {@link TrackSelectorResult#info} in the activated selection. */ diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 94f6abd87b..4b1357c1a6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters; @@ -90,6 +91,7 @@ public final class DefaultTrackSelectorTest { }; private static final TrackSelection[] TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER = new TrackSelection[] {new FixedTrackSelection(VIDEO_TRACK_GROUP, 0), null}; + private static final MediaPeriodId PERIOD_ID = new MediaPeriodId(/* periodUid= */ new Object()); @Mock private InvalidationListener invalidationListener; @@ -176,7 +178,8 @@ public final class DefaultTrackSelectorTest { trackSelector .buildUponParameters() .setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null)); - TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS); + TrackSelectorResult result = + trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, PERIOD_ID); assertTrackSelections(result, new TrackSelection[] {null, TRACK_SELECTIONS[1]}); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {null, DEFAULT}); @@ -192,7 +195,8 @@ public final class DefaultTrackSelectorTest { .buildUponParameters() .setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null) .clearSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP))); - TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS); + TrackSelectorResult result = + trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, PERIOD_ID); assertTrackSelections(result, TRACK_SELECTIONS); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {DEFAULT, DEFAULT}); @@ -210,7 +214,8 @@ public final class DefaultTrackSelectorTest { TrackSelectorResult result = trackSelector.selectTracks( RENDERER_CAPABILITIES, - new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP)); + new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP), + PERIOD_ID); assertTrackSelections(result, TRACK_SELECTIONS); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {DEFAULT, DEFAULT}); @@ -222,7 +227,8 @@ public final class DefaultTrackSelectorTest { DefaultTrackSelector trackSelector = new DefaultTrackSelector(); trackSelector.init(invalidationListener, bandwidthMeter); trackSelector.setParameters(trackSelector.buildUponParameters().setRendererDisabled(1, true)); - TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS); + TrackSelectorResult result = + trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, PERIOD_ID); assertTrackSelections(result, new TrackSelection[] {TRACK_SELECTIONS[0], null}); assertThat(new RendererConfiguration[] {DEFAULT, null}) .isEqualTo(result.rendererConfigurations); @@ -238,7 +244,8 @@ public final class DefaultTrackSelectorTest { .buildUponParameters() .setRendererDisabled(1, true) .setRendererDisabled(1, false)); - TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS); + TrackSelectorResult result = + trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, PERIOD_ID); assertTrackSelections(result, TRACK_SELECTIONS); assertThat(new RendererConfiguration[] {DEFAULT, DEFAULT}) .isEqualTo(result.rendererConfigurations); @@ -250,7 +257,8 @@ public final class DefaultTrackSelectorTest { DefaultTrackSelector trackSelector = new DefaultTrackSelector(); trackSelector.init(invalidationListener, bandwidthMeter); TrackSelectorResult result = - trackSelector.selectTracks(RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS); + trackSelector.selectTracks( + RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS, PERIOD_ID); assertTrackSelections(result, TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER); assertThat(new RendererConfiguration[] {DEFAULT, DEFAULT}) .isEqualTo(result.rendererConfigurations); @@ -263,7 +271,8 @@ public final class DefaultTrackSelectorTest { trackSelector.init(invalidationListener, bandwidthMeter); trackSelector.setParameters(trackSelector.buildUponParameters().setRendererDisabled(1, true)); TrackSelectorResult result = - trackSelector.selectTracks(RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS); + trackSelector.selectTracks( + RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS, PERIOD_ID); assertTrackSelections(result, TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER); assertThat(new RendererConfiguration[] {DEFAULT, null}) .isEqualTo(result.rendererConfigurations); @@ -322,9 +331,11 @@ public final class DefaultTrackSelectorTest { Format formatWithSelectionFlag = buildAudioFormat("audio", /* language= */ null, C.SELECTION_FLAG_DEFAULT); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(formatWithSelectionFlag, audioFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + singleTrackGroup(formatWithSelectionFlag, audioFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(formatWithSelectionFlag); } @@ -347,7 +358,8 @@ public final class DefaultTrackSelectorTest { TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - wrapFormats(frAudioFormat, enAudioFormat)); + wrapFormats(frAudioFormat, enAudioFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(enAudioFormat); } @@ -368,9 +380,11 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "eng"); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - wrapFormats(frAudioFormat, enAudioFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + wrapFormats(frAudioFormat, enAudioFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(enAudioFormat); } @@ -390,9 +404,11 @@ public final class DefaultTrackSelectorTest { RendererCapabilities mappedAudioRendererCapabilities = new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {mappedAudioRendererCapabilities}, - singleTrackGroup(exceededFormat, supportedFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {mappedAudioRendererCapabilities}, + singleTrackGroup(exceededFormat, supportedFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFormat); } @@ -407,9 +423,11 @@ public final class DefaultTrackSelectorTest { Format audioFormat = Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(audioFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, + singleTrackGroup(audioFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(audioFormat); } @@ -428,9 +446,11 @@ public final class DefaultTrackSelectorTest { Format audioFormat = Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(audioFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, + singleTrackGroup(audioFormat), + PERIOD_ID); assertThat(result.selections.get(0)).isNull(); } @@ -455,9 +475,11 @@ public final class DefaultTrackSelectorTest { RendererCapabilities mappedAudioRendererCapabilities = new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {mappedAudioRendererCapabilities}, - singleTrackGroup(exceededWithSelectionFlagFormat, supportedFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {mappedAudioRendererCapabilities}, + singleTrackGroup(exceededWithSelectionFlagFormat, supportedFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFormat); } @@ -485,9 +507,11 @@ public final class DefaultTrackSelectorTest { RendererCapabilities mappedAudioRendererCapabilities = new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {mappedAudioRendererCapabilities}, - singleTrackGroup(exceededEnFormat, supportedFrFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {mappedAudioRendererCapabilities}, + singleTrackGroup(exceededEnFormat, supportedFrFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFrFormat); } @@ -515,9 +539,11 @@ public final class DefaultTrackSelectorTest { RendererCapabilities mappedAudioRendererCapabilities = new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {mappedAudioRendererCapabilities}, - singleTrackGroup(exceededDefaultSelectionEnFormat, supportedFrFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {mappedAudioRendererCapabilities}, + singleTrackGroup(exceededDefaultSelectionEnFormat, supportedFrFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFrFormat); } @@ -536,9 +562,11 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 6, 44100, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherChannelFormat, lowerChannelFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + singleTrackGroup(higherChannelFormat, lowerChannelFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherChannelFormat); } @@ -557,9 +585,11 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 22050, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherSampleRateFormat, lowerSampleRateFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + singleTrackGroup(higherSampleRateFormat, lowerSampleRateFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherSampleRateFormat); } @@ -578,9 +608,11 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 44100, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(lowerBitrateFormat, higherBitrateFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherBitrateFormat); } @@ -600,9 +632,12 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 6, 22050, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + singleTrackGroup( + higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()) .isEqualTo(higherChannelLowerSampleRateFormat); @@ -623,9 +658,12 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 22050, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + singleTrackGroup( + higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()) .isEqualTo(higherSampleRateLowerBitrateFormat); @@ -645,9 +683,11 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 6, 44100, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherChannelFormat, lowerChannelFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, + singleTrackGroup(higherChannelFormat, lowerChannelFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerChannelFormat); } @@ -666,9 +706,11 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherSampleRateFormat, lowerSampleRateFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, + singleTrackGroup(higherSampleRateFormat, lowerSampleRateFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerSampleRateFormat); } @@ -687,9 +729,11 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 44100, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(lowerBitrateFormat, higherBitrateFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, + singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerBitrateFormat); } @@ -709,9 +753,12 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 6, 22050, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, + singleTrackGroup( + higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()) .isEqualTo(lowerChannelHigherSampleRateFormat); @@ -732,9 +779,12 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 22050, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, + singleTrackGroup( + higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()) .isEqualTo(lowerSampleRateHigherBitrateFormat); @@ -756,18 +806,22 @@ public final class DefaultTrackSelectorTest { // There is no text language preference, the first track flagged as default should be selected. TrackSelectorResult result = trackSelector.selectTracks( - textRendererCapabilities, wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag)); + textRendererCapabilities, + wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag), + PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedDefault); // Ditto. result = trackSelector.selectTracks( - textRendererCapabilities, wrapFormats(forcedOnly, noFlag, defaultOnly)); + textRendererCapabilities, wrapFormats(forcedOnly, noFlag, defaultOnly), PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(defaultOnly); // With no language preference and no text track flagged as default, the first forced should be // selected. - result = trackSelector.selectTracks(textRendererCapabilities, wrapFormats(forcedOnly, noFlag)); + result = + trackSelector.selectTracks( + textRendererCapabilities, wrapFormats(forcedOnly, noFlag), PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedOnly); trackSelector.setParameters( @@ -779,7 +833,9 @@ public final class DefaultTrackSelectorTest { // Default flags are disabled, so the first track flagged as forced should be selected. result = trackSelector.selectTracks( - textRendererCapabilities, wrapFormats(defaultOnly, noFlag, forcedOnly, forcedDefault)); + textRendererCapabilities, + wrapFormats(defaultOnly, noFlag, forcedOnly, forcedDefault), + PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedOnly); trackSelector.setParameters( @@ -790,7 +846,8 @@ public final class DefaultTrackSelectorTest { result = trackSelector.selectTracks( textRendererCapabilities, - wrapFormats(forcedDefault, forcedOnly, defaultOnly, noFlag, forcedOnlySpanish)); + wrapFormats(forcedDefault, forcedOnly, defaultOnly, noFlag, forcedOnlySpanish), + PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedOnlySpanish); trackSelector.setParameters( @@ -804,7 +861,9 @@ public final class DefaultTrackSelectorTest { // selected. result = trackSelector.selectTracks( - textRendererCapabilities, wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag)); + textRendererCapabilities, + wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag), + PERIOD_ID); assertThat(result.selections.get(0)).isNull(); trackSelector.setParameters( @@ -814,7 +873,9 @@ public final class DefaultTrackSelectorTest { // be selected. result = trackSelector.selectTracks( - textRendererCapabilities, wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag)); + textRendererCapabilities, + wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag), + PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedDefault); trackSelector.setParameters( @@ -829,7 +890,9 @@ public final class DefaultTrackSelectorTest { // forced subtitles. result = trackSelector.selectTracks( - textRendererCapabilities, wrapFormats(noFlag, forcedOnly, forcedDefault, defaultOnly)); + textRendererCapabilities, + wrapFormats(noFlag, forcedOnly, forcedDefault, defaultOnly), + PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(noFlag); } @@ -851,35 +914,49 @@ public final class DefaultTrackSelectorTest { TrackSelectorResult result = trackSelector.selectTracks( textRendererCapabilites, - wrapFormats(spanish, german, undeterminedUnd, undeterminedNull)); + wrapFormats(spanish, german, undeterminedUnd, undeterminedNull), + PERIOD_ID); assertThat(result.selections.get(0)).isNull(); trackSelector.setParameters( new ParametersBuilder().setSelectUndeterminedTextLanguage(true).build()); - result = trackSelector.selectTracks(textRendererCapabilites, - wrapFormats(spanish, german, undeterminedUnd, undeterminedNull)); + result = + trackSelector.selectTracks( + textRendererCapabilites, + wrapFormats(spanish, german, undeterminedUnd, undeterminedNull), + PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(undeterminedUnd); ParametersBuilder builder = new ParametersBuilder().setPreferredTextLanguage("spa"); trackSelector.setParameters(builder.build()); - result = trackSelector.selectTracks(textRendererCapabilites, - wrapFormats(spanish, german, undeterminedUnd, undeterminedNull)); + result = + trackSelector.selectTracks( + textRendererCapabilites, + wrapFormats(spanish, german, undeterminedUnd, undeterminedNull), + PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(spanish); - result = trackSelector.selectTracks(textRendererCapabilites, - wrapFormats(german, undeterminedUnd, undeterminedNull)); + result = + trackSelector.selectTracks( + textRendererCapabilites, + wrapFormats(german, undeterminedUnd, undeterminedNull), + PERIOD_ID); assertThat(result.selections.get(0)).isNull(); trackSelector.setParameters(builder.setSelectUndeterminedTextLanguage(true).build()); - result = trackSelector.selectTracks(textRendererCapabilites, - wrapFormats(german, undeterminedUnd, undeterminedNull)); + result = + trackSelector.selectTracks( + textRendererCapabilites, + wrapFormats(german, undeterminedUnd, undeterminedNull), + PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(undeterminedUnd); - result = trackSelector.selectTracks(textRendererCapabilites, - wrapFormats(german, undeterminedNull)); + result = + trackSelector.selectTracks( + textRendererCapabilites, wrapFormats(german, undeterminedNull), PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(undeterminedNull); - result = trackSelector.selectTracks(textRendererCapabilites, wrapFormats(german)); + result = trackSelector.selectTracks(textRendererCapabilites, wrapFormats(german), PERIOD_ID); assertThat(result.selections.get(0)).isNull(); } @@ -908,19 +985,21 @@ public final class DefaultTrackSelectorTest { // Without an explicit language preference, nothing should be selected. TrackSelectorResult result = - trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german)); + trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); assertThat(result.selections.get(0)).isNull(); assertThat(result.selections.get(1)).isNull(); // Explicit language preference for english. First renderer should be used. trackSelector.setParameters(trackSelector.buildUponParameters().setPreferredTextLanguage("en")); - result = trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german)); + result = + trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(english); assertThat(result.selections.get(1)).isNull(); // Explicit language preference for German. Second renderer should be used. trackSelector.setParameters(trackSelector.buildUponParameters().setPreferredTextLanguage("de")); - result = trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german)); + result = + trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); assertThat(result.selections.get(0)).isNull(); assertThat(result.selections.get(1).getFormat(0)).isSameAs(german); } @@ -941,9 +1020,11 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 44100, null, null, 0, null); - TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(lowerBitrateFormat, higherBitrateFormat)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), + PERIOD_ID); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerBitrateFormat); } @@ -962,7 +1043,7 @@ public final class DefaultTrackSelectorTest { TrackGroupArray trackGroupArray = singleTrackGroup(AUDIO_FORMAT, AUDIO_FORMAT); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroupArray); + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroupArray, PERIOD_ID); assertThat(result.length).isEqualTo(1); assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); @@ -991,7 +1072,7 @@ public final class DefaultTrackSelectorTest { new SelectionOverride(/* groupIndex= */ 0, /* tracks= */ 1, 2))); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroupArray); + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroupArray, PERIOD_ID); assertThat(result.length).isEqualTo(1); assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); @@ -1024,21 +1105,23 @@ public final class DefaultTrackSelectorTest { // Without an explicit language preference, prefer the first renderer. TrackSelectorResult result = - trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german)); + trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(english); assertThat(result.selections.get(1)).isNull(); // Explicit language preference for english. First renderer should be used. trackSelector.setParameters( trackSelector.buildUponParameters().setPreferredAudioLanguage("en")); - result = trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german)); + result = + trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); assertThat(result.selections.get(0).getFormat(0)).isSameAs(english); assertThat(result.selections.get(1)).isNull(); // Explicit language preference for German. Second renderer should be used. trackSelector.setParameters( trackSelector.buildUponParameters().setPreferredAudioLanguage("de")); - result = trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german)); + result = + trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); assertThat(result.selections.get(0)).isNull(); assertThat(result.selections.get(1).getFormat(0)).isSameAs(german); } @@ -1057,7 +1140,7 @@ public final class DefaultTrackSelectorTest { TrackGroupArray trackGroupArray = singleTrackGroup(VIDEO_FORMAT, VIDEO_FORMAT); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroupArray); + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroupArray, PERIOD_ID); assertThat(result.length).isEqualTo(1); assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); @@ -1086,7 +1169,7 @@ public final class DefaultTrackSelectorTest { new SelectionOverride(/* groupIndex= */ 0, /* tracks= */ 1, 2))); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroupArray); + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroupArray, PERIOD_ID); assertThat(result.length).isEqualTo(1); assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java index fa3d74b15f..3899ebfd37 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.util.MimeTypes; @@ -51,6 +52,7 @@ public final class MappingTrackSelectorTest { Format.NO_VALUE, 2, 44100, null, null, 0, null)); private static final TrackGroupArray TRACK_GROUPS = new TrackGroupArray( VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP); + private static final MediaPeriodId PERIOD_ID = new MediaPeriodId(/* periodUid= */ new Object()); /** * Tests that the video and audio track groups are mapped onto the correct renderers. @@ -58,7 +60,7 @@ public final class MappingTrackSelectorTest { @Test public void testMapping() throws ExoPlaybackException { FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); - trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS); + trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, PERIOD_ID); trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP); trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP); } @@ -72,7 +74,7 @@ public final class MappingTrackSelectorTest { FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); RendererCapabilities[] reverseOrderRendererCapabilities = new RendererCapabilities[] { AUDIO_CAPABILITIES, VIDEO_CAPABILITIES}; - trackSelector.selectTracks(reverseOrderRendererCapabilities, TRACK_GROUPS); + trackSelector.selectTracks(reverseOrderRendererCapabilities, TRACK_GROUPS, PERIOD_ID); trackSelector.assertMappedTrackGroups(0, AUDIO_TRACK_GROUP); trackSelector.assertMappedTrackGroups(1, VIDEO_TRACK_GROUP); } @@ -86,7 +88,7 @@ public final class MappingTrackSelectorTest { FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); TrackGroupArray multiTrackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP); - trackSelector.selectTracks(RENDERER_CAPABILITIES, multiTrackGroups); + trackSelector.selectTracks(RENDERER_CAPABILITIES, multiTrackGroups, PERIOD_ID); trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP, VIDEO_TRACK_GROUP); trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectorTest.java index 615f680bb5..781f2b4fc9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectorTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.fail; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.RendererCapabilities; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener; import com.google.android.exoplayer2.upstream.BandwidthMeter; @@ -37,16 +38,20 @@ public class TrackSelectorTest { @Before public void setUp() { - trackSelector = new TrackSelector() { - @Override - public TrackSelectorResult selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray trackGroups) throws ExoPlaybackException { - throw new UnsupportedOperationException(); - } + trackSelector = + new TrackSelector() { + @Override + public TrackSelectorResult selectTracks( + RendererCapabilities[] rendererCapabilities, + TrackGroupArray trackGroups, + MediaPeriodId periodId) + throws ExoPlaybackException { + throw new UnsupportedOperationException(); + } - @Override - public void onSelectionActivated(Object info) {} - }; + @Override + public void onSelectionActivated(Object info) {} + }; } @Test From d487b599f8404a6af0a50f606e8acff237e45d1e Mon Sep 17 00:00:00 2001 From: cblay Date: Wed, 26 Sep 2018 12:36:47 -0700 Subject: [PATCH 026/832] Also plumb new Timeline through to TrackSelector.selectTracks() so periodId is useful ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214650418 --- .../android/exoplayer2/ExoPlayerImpl.java | 3 +- .../exoplayer2/ExoPlayerImplInternal.java | 5 +- .../android/exoplayer2/MediaPeriodHolder.java | 8 +- .../trackselection/MappingTrackSelector.java | 4 +- .../trackselection/TrackSelector.java | 29 ++-- .../DefaultTrackSelectorTest.java | 162 ++++++++++++------ .../MappingTrackSelectorTest.java | 18 +- .../trackselection/TrackSelectorTest.java | 4 +- 8 files changed, 152 insertions(+), 81 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 96e7703559..5d565cdc90 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -51,7 +51,8 @@ import java.util.concurrent.CopyOnWriteArraySet; * This empty track selector result can only be used for {@link PlaybackInfo#trackSelectorResult} * when the player does not have any track selection made (such as when player is reset, or when * player seeks to an unprepared period). It will not be used as result of any {@link - * TrackSelector#selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId)} operation. + * TrackSelector#selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId, Timeline)} + * operation. */ /* package */ final TrackSelectorResult emptyTrackSelectorResult; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index d861020d26..1d5125384f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -998,7 +998,7 @@ import java.util.Collections; // The reselection did not change any prepared periods. return; } - if (periodHolder.selectTracks(playbackSpeed)) { + if (periodHolder.selectTracks(playbackSpeed, playbackInfo.timeline)) { // Selected tracks have changed for this period. break; } @@ -1531,7 +1531,8 @@ import java.util.Collections; return; } MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod(); - loadingPeriodHolder.handlePrepared(mediaClock.getPlaybackParameters().speed); + loadingPeriodHolder.handlePrepared( + mediaClock.getPlaybackParameters().speed, playbackInfo.timeline); updateLoadControlTrackSelection( loadingPeriodHolder.trackGroups, loadingPeriodHolder.trackSelectorResult); if (!queue.hasPlayingPeriod()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index 70c9354e71..a59ee61088 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -140,10 +140,10 @@ import com.google.android.exoplayer2.util.Log; return !prepared ? 0 : mediaPeriod.getNextLoadPositionUs(); } - public void handlePrepared(float playbackSpeed) throws ExoPlaybackException { + public void handlePrepared(float playbackSpeed, Timeline timeline) throws ExoPlaybackException { prepared = true; trackGroups = mediaPeriod.getTrackGroups(); - selectTracks(playbackSpeed); + selectTracks(playbackSpeed, timeline); long newStartPositionUs = applyTrackSelection(info.startPositionUs, false); rendererPositionOffsetUs += info.startPositionUs - newStartPositionUs; info = info.copyWithStartPositionUs(newStartPositionUs); @@ -160,9 +160,9 @@ import com.google.android.exoplayer2.util.Log; mediaPeriod.continueLoading(loadingPeriodPositionUs); } - public boolean selectTracks(float playbackSpeed) throws ExoPlaybackException { + public boolean selectTracks(float playbackSpeed, Timeline timeline) throws ExoPlaybackException { TrackSelectorResult selectorResult = - trackSelector.selectTracks(rendererCapabilities, trackGroups, info.id); + trackSelector.selectTracks(rendererCapabilities, trackGroups, info.id, timeline); if (selectorResult.isEquivalent(periodTrackSelectorResult)) { return false; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index ce5dff8556..81fbc16630 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -330,7 +331,8 @@ public abstract class MappingTrackSelector extends TrackSelector { public final TrackSelectorResult selectTracks( RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups, - MediaPeriodId periodId) + MediaPeriodId periodId, + Timeline timeline) throws ExoPlaybackException { // Structures into which data will be written during the selection. The extra item at the end // of each array is to store data associated with track groups that cannot be associated with diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java index bc2460036c..2a66186b29 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.upstream.BandwidthMeter; @@ -41,9 +42,9 @@ import com.google.android.exoplayer2.util.Assertions; *
  • When the player is created it will initialize the track selector by calling {@link * #init(InvalidationListener, BandwidthMeter)}. *
  • When the player needs to make a track selection it will call {@link - * #selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId)}. This typically - * occurs at the start of playback, when the player starts to buffer a new period of the media - * being played, and when the track selector invalidates its previous selections. + * #selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId, Timeline)}. This + * typically occurs at the start of playback, when the player starts to buffer a new period of + * the media being played, and when the track selector invalidates its previous selections. *
  • The player may perform a track selection well in advance of the selected tracks becoming * active, where active is defined to mean that the renderers are actually consuming media * corresponding to the selection that was made. For example when playing media containing @@ -67,14 +68,14 @@ import com.google.android.exoplayer2.util.Assertions; *

    Renderer configuration

    * * The {@link TrackSelectorResult} returned by {@link #selectTracks(RendererCapabilities[], - * TrackGroupArray, MediaPeriodId)} contains not only {@link TrackSelection}s for each renderer, but - * also {@link RendererConfiguration}s defining configuration parameters that the renderers should - * apply when consuming the corresponding media. Whilst it may seem counter-intuitive for a track - * selector to also specify renderer configuration information, in practice the two are tightly - * bound together. It may only be possible to play a certain combination tracks if the renderers are - * configured in a particular way. Equally, it may only be possible to configure renderers in a - * particular way if certain tracks are selected. Hence it makes sense to determined the track - * selection and corresponding renderer configurations in a single step. + * TrackGroupArray, MediaPeriodId, Timeline)} contains not only {@link TrackSelection}s for each + * renderer, but also {@link RendererConfiguration}s defining configuration parameters that the + * renderers should apply when consuming the corresponding media. Whilst it may seem counter- + * intuitive for a track selector to also specify renderer configuration information, in practice + * the two are tightly bound together. It may only be possible to play a certain combination tracks + * if the renderers are configured in a particular way. Equally, it may only be possible to + * configure renderers in a particular way if certain tracks are selected. Hence it makes sense to + * determined the track selection and corresponding renderer configurations in a single step. * *

    Threading model

    * @@ -119,18 +120,20 @@ public abstract class TrackSelector { * are to be selected. * @param trackGroups The available track groups. * @param periodId The {@link MediaPeriodId} of the period for which tracks are to be selected. + * @param timeline The {@link Timeline} holding the period for which tracks are to be selected. * @return A {@link TrackSelectorResult} describing the track selections. * @throws ExoPlaybackException If an error occurs selecting tracks. */ public abstract TrackSelectorResult selectTracks( RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups, - MediaPeriodId periodId) + MediaPeriodId periodId, + Timeline timeline) throws ExoPlaybackException; /** * Called by the player when a {@link TrackSelectorResult} previously generated by {@link - * #selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId)} is activated. + * #selectTracks(RendererCapabilities[], TrackGroupArray, MediaPeriodId, Timeline)} is activated. * * @param info The value of {@link TrackSelectorResult#info} in the activated selection. */ diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 4b1357c1a6..ee3d2cf9b0 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -37,9 +37,11 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.ParametersBuilder; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; @@ -49,6 +51,7 @@ import com.google.android.exoplayer2.util.MimeTypes; import java.util.HashMap; import java.util.Map; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -91,14 +94,20 @@ public final class DefaultTrackSelectorTest { }; private static final TrackSelection[] TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER = new TrackSelection[] {new FixedTrackSelection(VIDEO_TRACK_GROUP, 0), null}; - private static final MediaPeriodId PERIOD_ID = new MediaPeriodId(/* periodUid= */ new Object()); + private static final Timeline TIMELINE = new FakeTimeline(/* windowCount= */ 1); - @Mock - private InvalidationListener invalidationListener; + private static MediaPeriodId periodId; + + @Mock private InvalidationListener invalidationListener; @Mock private BandwidthMeter bandwidthMeter; private DefaultTrackSelector trackSelector; + @BeforeClass + public static void setUpBeforeClass() { + periodId = new MediaPeriodId(TIMELINE.getUidOfPeriod(/* periodIndex= */ 0)); + } + @Before public void setUp() { initMocks(this); @@ -179,7 +188,7 @@ public final class DefaultTrackSelectorTest { .buildUponParameters() .setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null)); TrackSelectorResult result = - trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, PERIOD_ID); + trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); assertTrackSelections(result, new TrackSelection[] {null, TRACK_SELECTIONS[1]}); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {null, DEFAULT}); @@ -196,7 +205,7 @@ public final class DefaultTrackSelectorTest { .setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null) .clearSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP))); TrackSelectorResult result = - trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, PERIOD_ID); + trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); assertTrackSelections(result, TRACK_SELECTIONS); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {DEFAULT, DEFAULT}); @@ -215,7 +224,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( RENDERER_CAPABILITIES, new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP), - PERIOD_ID); + periodId, + TIMELINE); assertTrackSelections(result, TRACK_SELECTIONS); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {DEFAULT, DEFAULT}); @@ -228,7 +238,7 @@ public final class DefaultTrackSelectorTest { trackSelector.init(invalidationListener, bandwidthMeter); trackSelector.setParameters(trackSelector.buildUponParameters().setRendererDisabled(1, true)); TrackSelectorResult result = - trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, PERIOD_ID); + trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); assertTrackSelections(result, new TrackSelection[] {TRACK_SELECTIONS[0], null}); assertThat(new RendererConfiguration[] {DEFAULT, null}) .isEqualTo(result.rendererConfigurations); @@ -245,7 +255,7 @@ public final class DefaultTrackSelectorTest { .setRendererDisabled(1, true) .setRendererDisabled(1, false)); TrackSelectorResult result = - trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, PERIOD_ID); + trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); assertTrackSelections(result, TRACK_SELECTIONS); assertThat(new RendererConfiguration[] {DEFAULT, DEFAULT}) .isEqualTo(result.rendererConfigurations); @@ -258,7 +268,7 @@ public final class DefaultTrackSelectorTest { trackSelector.init(invalidationListener, bandwidthMeter); TrackSelectorResult result = trackSelector.selectTracks( - RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS, PERIOD_ID); + RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS, periodId, TIMELINE); assertTrackSelections(result, TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER); assertThat(new RendererConfiguration[] {DEFAULT, DEFAULT}) .isEqualTo(result.rendererConfigurations); @@ -272,7 +282,7 @@ public final class DefaultTrackSelectorTest { trackSelector.setParameters(trackSelector.buildUponParameters().setRendererDisabled(1, true)); TrackSelectorResult result = trackSelector.selectTracks( - RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS, PERIOD_ID); + RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS, periodId, TIMELINE); assertTrackSelections(result, TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER); assertThat(new RendererConfiguration[] {DEFAULT, null}) .isEqualTo(result.rendererConfigurations); @@ -335,7 +345,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, singleTrackGroup(formatWithSelectionFlag, audioFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(formatWithSelectionFlag); } @@ -359,7 +370,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, wrapFormats(frAudioFormat, enAudioFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(enAudioFormat); } @@ -384,7 +396,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, wrapFormats(frAudioFormat, enAudioFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(enAudioFormat); } @@ -408,7 +421,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {mappedAudioRendererCapabilities}, singleTrackGroup(exceededFormat, supportedFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFormat); } @@ -427,7 +441,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, singleTrackGroup(audioFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(audioFormat); } @@ -450,7 +465,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, singleTrackGroup(audioFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0)).isNull(); } @@ -479,7 +495,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {mappedAudioRendererCapabilities}, singleTrackGroup(exceededWithSelectionFlagFormat, supportedFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFormat); } @@ -511,7 +528,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {mappedAudioRendererCapabilities}, singleTrackGroup(exceededEnFormat, supportedFrFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFrFormat); } @@ -543,7 +561,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {mappedAudioRendererCapabilities}, singleTrackGroup(exceededDefaultSelectionEnFormat, supportedFrFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFrFormat); } @@ -566,7 +585,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, singleTrackGroup(higherChannelFormat, lowerChannelFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherChannelFormat); } @@ -589,7 +609,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, singleTrackGroup(higherSampleRateFormat, lowerSampleRateFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherSampleRateFormat); } @@ -612,7 +633,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherBitrateFormat); } @@ -637,7 +659,8 @@ public final class DefaultTrackSelectorTest { new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, singleTrackGroup( higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()) .isEqualTo(higherChannelLowerSampleRateFormat); @@ -663,7 +686,8 @@ public final class DefaultTrackSelectorTest { new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, singleTrackGroup( higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()) .isEqualTo(higherSampleRateLowerBitrateFormat); @@ -687,7 +711,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, singleTrackGroup(higherChannelFormat, lowerChannelFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerChannelFormat); } @@ -710,7 +735,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, singleTrackGroup(higherSampleRateFormat, lowerSampleRateFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerSampleRateFormat); } @@ -733,7 +759,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerBitrateFormat); } @@ -758,7 +785,8 @@ public final class DefaultTrackSelectorTest { new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, singleTrackGroup( higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()) .isEqualTo(lowerChannelHigherSampleRateFormat); @@ -784,7 +812,8 @@ public final class DefaultTrackSelectorTest { new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, singleTrackGroup( higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()) .isEqualTo(lowerSampleRateHigherBitrateFormat); @@ -808,20 +837,24 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( textRendererCapabilities, wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedDefault); // Ditto. result = trackSelector.selectTracks( - textRendererCapabilities, wrapFormats(forcedOnly, noFlag, defaultOnly), PERIOD_ID); + textRendererCapabilities, + wrapFormats(forcedOnly, noFlag, defaultOnly), + periodId, + TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(defaultOnly); // With no language preference and no text track flagged as default, the first forced should be // selected. result = trackSelector.selectTracks( - textRendererCapabilities, wrapFormats(forcedOnly, noFlag), PERIOD_ID); + textRendererCapabilities, wrapFormats(forcedOnly, noFlag), periodId, TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedOnly); trackSelector.setParameters( @@ -835,7 +868,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( textRendererCapabilities, wrapFormats(defaultOnly, noFlag, forcedOnly, forcedDefault), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedOnly); trackSelector.setParameters( @@ -847,7 +881,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( textRendererCapabilities, wrapFormats(forcedDefault, forcedOnly, defaultOnly, noFlag, forcedOnlySpanish), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedOnlySpanish); trackSelector.setParameters( @@ -863,7 +898,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( textRendererCapabilities, wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0)).isNull(); trackSelector.setParameters( @@ -875,7 +911,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( textRendererCapabilities, wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedDefault); trackSelector.setParameters( @@ -892,7 +929,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( textRendererCapabilities, wrapFormats(noFlag, forcedOnly, forcedDefault, defaultOnly), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(noFlag); } @@ -915,7 +953,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( textRendererCapabilites, wrapFormats(spanish, german, undeterminedUnd, undeterminedNull), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0)).isNull(); trackSelector.setParameters( @@ -924,7 +963,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( textRendererCapabilites, wrapFormats(spanish, german, undeterminedUnd, undeterminedNull), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(undeterminedUnd); ParametersBuilder builder = new ParametersBuilder().setPreferredTextLanguage("spa"); @@ -933,14 +973,16 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( textRendererCapabilites, wrapFormats(spanish, german, undeterminedUnd, undeterminedNull), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(spanish); result = trackSelector.selectTracks( textRendererCapabilites, wrapFormats(german, undeterminedUnd, undeterminedNull), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0)).isNull(); trackSelector.setParameters(builder.setSelectUndeterminedTextLanguage(true).build()); @@ -948,15 +990,18 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( textRendererCapabilites, wrapFormats(german, undeterminedUnd, undeterminedNull), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(undeterminedUnd); result = trackSelector.selectTracks( - textRendererCapabilites, wrapFormats(german, undeterminedNull), PERIOD_ID); + textRendererCapabilites, wrapFormats(german, undeterminedNull), periodId, TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(undeterminedNull); - result = trackSelector.selectTracks(textRendererCapabilites, wrapFormats(german), PERIOD_ID); + result = + trackSelector.selectTracks( + textRendererCapabilites, wrapFormats(german), periodId, TIMELINE); assertThat(result.selections.get(0)).isNull(); } @@ -985,21 +1030,24 @@ public final class DefaultTrackSelectorTest { // Without an explicit language preference, nothing should be selected. TrackSelectorResult result = - trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); + trackSelector.selectTracks( + rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); assertThat(result.selections.get(0)).isNull(); assertThat(result.selections.get(1)).isNull(); // Explicit language preference for english. First renderer should be used. trackSelector.setParameters(trackSelector.buildUponParameters().setPreferredTextLanguage("en")); result = - trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); + trackSelector.selectTracks( + rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(english); assertThat(result.selections.get(1)).isNull(); // Explicit language preference for German. Second renderer should be used. trackSelector.setParameters(trackSelector.buildUponParameters().setPreferredTextLanguage("de")); result = - trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); + trackSelector.selectTracks( + rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); assertThat(result.selections.get(0)).isNull(); assertThat(result.selections.get(1).getFormat(0)).isSameAs(german); } @@ -1024,7 +1072,8 @@ public final class DefaultTrackSelectorTest { trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), - PERIOD_ID); + periodId, + TIMELINE); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerBitrateFormat); } @@ -1043,7 +1092,7 @@ public final class DefaultTrackSelectorTest { TrackGroupArray trackGroupArray = singleTrackGroup(AUDIO_FORMAT, AUDIO_FORMAT); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroupArray, PERIOD_ID); + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroupArray, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); @@ -1072,7 +1121,7 @@ public final class DefaultTrackSelectorTest { new SelectionOverride(/* groupIndex= */ 0, /* tracks= */ 1, 2))); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroupArray, PERIOD_ID); + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroupArray, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); @@ -1105,7 +1154,8 @@ public final class DefaultTrackSelectorTest { // Without an explicit language preference, prefer the first renderer. TrackSelectorResult result = - trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); + trackSelector.selectTracks( + rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(english); assertThat(result.selections.get(1)).isNull(); @@ -1113,7 +1163,8 @@ public final class DefaultTrackSelectorTest { trackSelector.setParameters( trackSelector.buildUponParameters().setPreferredAudioLanguage("en")); result = - trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); + trackSelector.selectTracks( + rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); assertThat(result.selections.get(0).getFormat(0)).isSameAs(english); assertThat(result.selections.get(1)).isNull(); @@ -1121,7 +1172,8 @@ public final class DefaultTrackSelectorTest { trackSelector.setParameters( trackSelector.buildUponParameters().setPreferredAudioLanguage("de")); result = - trackSelector.selectTracks(rendererCapabilities, wrapFormats(english, german), PERIOD_ID); + trackSelector.selectTracks( + rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); assertThat(result.selections.get(0)).isNull(); assertThat(result.selections.get(1).getFormat(0)).isSameAs(german); } @@ -1140,7 +1192,7 @@ public final class DefaultTrackSelectorTest { TrackGroupArray trackGroupArray = singleTrackGroup(VIDEO_FORMAT, VIDEO_FORMAT); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroupArray, PERIOD_ID); + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroupArray, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); @@ -1169,7 +1221,7 @@ public final class DefaultTrackSelectorTest { new SelectionOverride(/* groupIndex= */ 0, /* tracks= */ 1, 2))); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroupArray, PERIOD_ID); + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroupArray, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java index 3899ebfd37..140a9733b3 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java @@ -23,10 +23,13 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.util.MimeTypes; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -52,7 +55,14 @@ public final class MappingTrackSelectorTest { Format.NO_VALUE, 2, 44100, null, null, 0, null)); private static final TrackGroupArray TRACK_GROUPS = new TrackGroupArray( VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP); - private static final MediaPeriodId PERIOD_ID = new MediaPeriodId(/* periodUid= */ new Object()); + private static final Timeline TIMELINE = new FakeTimeline(/* windowCount= */ 1); + + private static MediaPeriodId periodId; + + @BeforeClass + public static void setUpBeforeClass() { + periodId = new MediaPeriodId(TIMELINE.getUidOfPeriod(/* periodIndex= */ 0)); + } /** * Tests that the video and audio track groups are mapped onto the correct renderers. @@ -60,7 +70,7 @@ public final class MappingTrackSelectorTest { @Test public void testMapping() throws ExoPlaybackException { FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); - trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, PERIOD_ID); + trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP); trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP); } @@ -74,7 +84,7 @@ public final class MappingTrackSelectorTest { FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); RendererCapabilities[] reverseOrderRendererCapabilities = new RendererCapabilities[] { AUDIO_CAPABILITIES, VIDEO_CAPABILITIES}; - trackSelector.selectTracks(reverseOrderRendererCapabilities, TRACK_GROUPS, PERIOD_ID); + trackSelector.selectTracks(reverseOrderRendererCapabilities, TRACK_GROUPS, periodId, TIMELINE); trackSelector.assertMappedTrackGroups(0, AUDIO_TRACK_GROUP); trackSelector.assertMappedTrackGroups(1, VIDEO_TRACK_GROUP); } @@ -88,7 +98,7 @@ public final class MappingTrackSelectorTest { FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); TrackGroupArray multiTrackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP); - trackSelector.selectTracks(RENDERER_CAPABILITIES, multiTrackGroups, PERIOD_ID); + trackSelector.selectTracks(RENDERER_CAPABILITIES, multiTrackGroups, periodId, TIMELINE); trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP, VIDEO_TRACK_GROUP); trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectorTest.java index 781f2b4fc9..3b88c00e2e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectorTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.fail; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.RendererCapabilities; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener; @@ -44,7 +45,8 @@ public class TrackSelectorTest { public TrackSelectorResult selectTracks( RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups, - MediaPeriodId periodId) + MediaPeriodId periodId, + Timeline timeline) throws ExoPlaybackException { throw new UnsupportedOperationException(); } From 2a50621118df38a88232ea2d2f3d887ec2bfde10 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 27 Sep 2018 03:42:40 -0700 Subject: [PATCH 027/832] Use media item in the cast demo app ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214750185 --- .../android/exoplayer2/castdemo/DemoUtil.java | 84 ++++++++----------- .../exoplayer2/castdemo/MainActivity.java | 22 +++-- .../exoplayer2/castdemo/PlayerManager.java | 39 +++++---- .../exoplayer2/ext/cast/CastTimeline.java | 2 +- .../exoplayer2/ext/cast/MediaItem.java | 14 ++-- 5 files changed, 80 insertions(+), 81 deletions(-) diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java index 26ab5eb0dd..0ff2e09a39 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java @@ -15,8 +15,8 @@ */ package com.google.android.exoplayer2.castdemo; +import com.google.android.exoplayer2.ext.cast.MediaItem; import com.google.android.exoplayer2.util.MimeTypes; -import com.google.android.gms.cast.MediaInfo; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -31,57 +31,45 @@ import java.util.List; public static final String MIME_TYPE_SS = MimeTypes.APPLICATION_SS; public static final String MIME_TYPE_VIDEO_MP4 = MimeTypes.VIDEO_MP4; - /** - * The list of samples available in the cast demo app. - */ - public static final List SAMPLES; - - /** - * Represents a media sample. - */ - public static final class Sample { - - /** - * The uri from which the media sample is obtained. - */ - public final String uri; - /** - * A descriptive name for the sample. - */ - public final String name; - /** - * The mime type of the media sample, as required by {@link MediaInfo#setContentType}. - */ - public final String mimeType; - - /** - * @param uri See {@link #uri}. - * @param name See {@link #name}. - * @param mimeType See {@link #mimeType}. - */ - public Sample(String uri, String name, String mimeType) { - this.uri = uri; - this.name = name; - this.mimeType = mimeType; - } - - @Override - public String toString() { - return name; - } - - } + /** The list of samples available in the cast demo app. */ + public static final List SAMPLES; static { // App samples. - ArrayList samples = new ArrayList<>(); - samples.add(new Sample("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd", - "DASH (clear,MP4,H264)", MIME_TYPE_DASH)); - samples.add(new Sample("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/" - + "hls/TearsOfSteel.m3u8", "Tears of Steel (HLS)", MIME_TYPE_HLS)); - samples.add(new Sample("https://html5demos.com/assets/dizzy.mp4", "Dizzy (MP4)", - MIME_TYPE_VIDEO_MP4)); + ArrayList samples = new ArrayList<>(); + MediaItem.Builder sampleBuilder = new MediaItem.Builder(); + samples.add( + sampleBuilder + .setTitle("DASH (clear,MP4,H264)") + .setMimeType(MIME_TYPE_DASH) + .setMedia("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd") + .buildAndClear()); + + samples.add( + sampleBuilder + .setTitle("Tears of Steel (HLS)") + .setMimeType(MIME_TYPE_HLS) + .setMedia( + "https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/" + + "hls/TearsOfSteel.m3u8") + .buildAndClear()); + + samples.add( + sampleBuilder + .setTitle("HLS Basic (TS)") + .setMimeType(MIME_TYPE_HLS) + .setMedia( + "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3" + + "/bipbop_4x3_variant.m3u8") + .buildAndClear()); + + samples.add( + sampleBuilder + .setTitle("Dizzy (MP4)") + .setMimeType(MIME_TYPE_VIDEO_MP4) + .setMedia("https://html5demos.com/assets/dizzy.mp4") + .buildAndClear()); SAMPLES = Collections.unmodifiableList(samples); diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java index 30968b8f85..44305afb5b 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.castdemo; import android.content.Context; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.graphics.ColorUtils; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; @@ -35,18 +36,18 @@ import android.widget.ListView; import android.widget.TextView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.castdemo.DemoUtil.Sample; -import com.google.android.exoplayer2.ext.cast.CastPlayer; +import com.google.android.exoplayer2.ext.cast.MediaItem; import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.gms.cast.framework.CastButtonFactory; import com.google.android.gms.cast.framework.CastContext; /** - * An activity that plays video using {@link SimpleExoPlayer} and {@link CastPlayer}. + * An activity that plays video using {@link SimpleExoPlayer} and supports casting using ExoPlayer's + * Cast extension. */ -public class MainActivity extends AppCompatActivity implements OnClickListener, - PlayerManager.QueuePositionListener { +public class MainActivity extends AppCompatActivity + implements OnClickListener, PlayerManager.QueuePositionListener { private PlayerView localPlayerView; private PlayerControlView castControlView; @@ -181,7 +182,7 @@ public class MainActivity extends AppCompatActivity implements OnClickListener, @Override public void onBindViewHolder(QueueItemViewHolder holder, int position) { TextView view = holder.textView; - view.setText(playerManager.getItem(position).name); + view.setText(playerManager.getItem(position).title); // TODO: Solve coloring using the theme's ColorStateList. view.setTextColor(ColorUtils.setAlphaComponent(view.getCurrentTextColor(), position == playerManager.getCurrentItemIndex() ? 255 : 100)); @@ -244,12 +245,19 @@ public class MainActivity extends AppCompatActivity implements OnClickListener, } - private static final class SampleListAdapter extends ArrayAdapter { + private static final class SampleListAdapter extends ArrayAdapter { public SampleListAdapter(Context context) { super(context, android.R.layout.simple_list_item_1, DemoUtil.SAMPLES); } + @Override + public View getView(int position, @Nullable View convertView, ViewGroup parent) { + TextView view = (TextView) super.getView(position, convertView, parent); + MediaItem sample = DemoUtil.SAMPLES.get(position); + view.setText(sample.title); + return view; + } } } diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java index 0c69e40164..eb4808d1ed 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java @@ -31,8 +31,8 @@ import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; -import com.google.android.exoplayer2.castdemo.DemoUtil.Sample; import com.google.android.exoplayer2.ext.cast.CastPlayer; +import com.google.android.exoplayer2.ext.cast.MediaItem; import com.google.android.exoplayer2.ext.cast.RemotePlayer; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource; @@ -74,7 +74,7 @@ import java.util.ArrayList; private final PlayerControlView castControlView; private final SimpleExoPlayer exoPlayer; private final CastPlayer castPlayer; - private final ArrayList mediaQueue; + private final ArrayList mediaQueue; private final QueuePositionListener queuePositionListener; private final ConcatenatingMediaSource concatenatingMediaSource; @@ -146,15 +146,15 @@ import java.util.ArrayList; } /** - * Appends {@code sample} to the media queue. + * Appends {@code item} to the media queue. * - * @param sample The {@link Sample} to append. + * @param item The {@link MediaItem} to append. */ - public void addItem(Sample sample) { - mediaQueue.add(sample); - concatenatingMediaSource.addMediaSource(buildMediaSource(sample)); + public void addItem(MediaItem item) { + mediaQueue.add(item); + concatenatingMediaSource.addMediaSource(buildMediaSource(item)); if (currentPlayer == castPlayer) { - castPlayer.addItems(buildMediaQueueItem(sample)); + castPlayer.addItems(buildMediaQueueItem(item)); } } @@ -171,7 +171,7 @@ import java.util.ArrayList; * @param position The index of the item. * @return The item at the given index in the media queue. */ - public Sample getItem(int position) { + public MediaItem getItem(int position) { return mediaQueue.get(position); } @@ -388,9 +388,9 @@ import java.util.ArrayList; } } - private static MediaSource buildMediaSource(DemoUtil.Sample sample) { - Uri uri = Uri.parse(sample.uri); - switch (sample.mimeType) { + private static MediaSource buildMediaSource(MediaItem item) { + Uri uri = item.media.uri; + switch (item.mimeType) { case DemoUtil.MIME_TYPE_SS: return new SsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); case DemoUtil.MIME_TYPE_DASH: @@ -400,17 +400,20 @@ import java.util.ArrayList; case DemoUtil.MIME_TYPE_VIDEO_MP4: return new ExtractorMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); default: { - throw new IllegalStateException("Unsupported type: " + sample.mimeType); + throw new IllegalStateException("Unsupported type: " + item.mimeType); } } } - private static MediaQueueItem buildMediaQueueItem(DemoUtil.Sample sample) { + private static MediaQueueItem buildMediaQueueItem(MediaItem item) { MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); - movieMetadata.putString(MediaMetadata.KEY_TITLE, sample.name); - MediaInfo mediaInfo = new MediaInfo.Builder(sample.uri) - .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED).setContentType(sample.mimeType) - .setMetadata(movieMetadata).build(); + movieMetadata.putString(MediaMetadata.KEY_TITLE, item.title); + MediaInfo mediaInfo = + new MediaInfo.Builder(item.media.uri.toString()) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setContentType(item.mimeType) + .setMetadata(movieMetadata) + .build(); return new MediaQueueItem.Builder(mediaInfo).build(); } diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java index 4939e62a2b..d86c4b3ebf 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastTimeline.java @@ -108,7 +108,7 @@ import java.util.Map; } @Override - public Object getUidOfPeriod(int periodIndex) { + public Integer getUidOfPeriod(int periodIndex) { return ids[periodIndex]; } diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java index 67428e8af4..f9188e08be 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java @@ -67,9 +67,9 @@ public final class MediaItem { return this; } - /** Equivalent to {@link #setMedia(UriBundle) setMedia(new UriBundle(uri))}. */ + /** Equivalent to {@link #setMedia(UriBundle) setMedia(new UriBundle(Uri.parse(uri)))}. */ public Builder setMedia(String uri) { - return setMedia(new UriBundle(uri)); + return setMedia(new UriBundle(Uri.parse(uri))); } /** See {@link MediaItem#media}. */ @@ -162,7 +162,7 @@ public final class MediaItem { public static final class UriBundle { /** An empty {@link UriBundle}. */ - public static final UriBundle EMPTY = new UriBundle(""); + public static final UriBundle EMPTY = new UriBundle(Uri.EMPTY); /** A URI. */ public final Uri uri; @@ -171,12 +171,12 @@ public final class MediaItem { public final Map requestHeaders; /** - * Creates an instance from the given string with no request headers. + * Creates an instance with no request headers. * - * @param uriString See {@link #uri}. + * @param uri See {@link #uri}. */ - public UriBundle(String uriString) { - this(Uri.parse(uriString), Collections.emptyMap()); + public UriBundle(Uri uri) { + this(uri, Collections.emptyMap()); } /** From e50214a5a1fd378d66db0ab088f1d9a4f2ccb2d9 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 27 Sep 2018 07:37:39 -0700 Subject: [PATCH 028/832] Add constructor for adding payload reader factory flags Issue:#4861 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214772527 --- RELEASENOTES.md | 3 ++ .../ts/DefaultTsPayloadReaderFactory.java | 26 ++++++++++++ .../hls/DefaultHlsExtractorFactory.java | 42 +++++++++++++++---- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bcf05590bf..a1e70fa018 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -11,6 +11,9 @@ * Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext` and `Player.hasPrevious` ([#4863](https://github.com/google/ExoPlayer/issues/4863)). +* HLS: + * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload reader + factory flags ([#4861](https://github.com/google/ExoPlayer/issues/4861)). ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 06a60776c2..7353b25e9c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -52,11 +52,37 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact }) public @interface Flags {} + /** + * When extracting H.264 samples, whether to treat samples consisting of non-IDR I slices as + * synchronization samples (key-frames). + */ public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1; + /** + * Prevents the creation of {@link AdtsReader} and {@link LatmReader} instances. This flag should + * be enabled if the transport stream contains no packets for an AAC elementary stream that is + * declared in the PMT. + */ public static final int FLAG_IGNORE_AAC_STREAM = 1 << 1; + /** + * Prevents the creation of {@link H264Reader} instances. This flag should be enabled if the + * transport stream contains no packets for an H.264 elementary stream that is declared in the + * PMT. + */ public static final int FLAG_IGNORE_H264_STREAM = 1 << 2; + /** + * When extracting H.264 samples, whether to split the input stream into access units (samples) + * based on slice headers. This flag should be disabled if the stream contains access unit + * delimiters (AUDs). + */ public static final int FLAG_DETECT_ACCESS_UNITS = 1 << 3; + /** Prevents the creation of {@link SpliceInfoSectionReader} instances. */ public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 1 << 4; + /** + * Whether the list of {@code closedCaptionFormats} passed to {@link + * DefaultTsPayloadReaderFactory#DefaultTsPayloadReaderFactory(int, List)} should be used in spite + * of any closed captions service descriptors. If this flag is disabled, {@code + * closedCaptionFormats} will be ignored if the PMT contains closed captions service descriptors. + */ public static final int FLAG_OVERRIDE_CAPTION_DESCRIPTORS = 1 << 5; private static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java index 951e6c95e0..3d75923553 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java @@ -51,6 +51,24 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { public static final String VTT_FILE_EXTENSION = ".vtt"; public static final String WEBVTT_FILE_EXTENSION = ".webvtt"; + @DefaultTsPayloadReaderFactory.Flags private final int payloadReaderFactoryFlags; + + /** Creates a factory for HLS segment extractors. */ + public DefaultHlsExtractorFactory() { + this(/* payloadReaderFactoryFlags= */ 0); + } + + /** + * Creates a factory for HLS segment extractors. + * + * @param payloadReaderFactoryFlags Flags to add when constructing any {@link + * DefaultTsPayloadReaderFactory} instances. Other flags may be added on top of {@code + * payloadReaderFactoryFlags} when creating {@link DefaultTsPayloadReaderFactory}. + */ + public DefaultHlsExtractorFactory(int payloadReaderFactoryFlags) { + this.payloadReaderFactoryFlags = payloadReaderFactoryFlags; + } + @Override public Pair createExtractor( Extractor previousExtractor, @@ -138,7 +156,9 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { } if (!(extractorByFileExtension instanceof TsExtractor)) { - TsExtractor tsExtractor = createTsExtractor(format, muxedCaptionFormats, timestampAdjuster); + TsExtractor tsExtractor = + createTsExtractor( + payloadReaderFactoryFlags, format, muxedCaptionFormats, timestampAdjuster); if (sniffQuietly(tsExtractor, extractorInput)) { return buildResult(tsExtractor); } @@ -180,17 +200,23 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { muxedCaptionFormats != null ? muxedCaptionFormats : Collections.emptyList()); } else { // For any other file extension, we assume TS format. - return createTsExtractor(format, muxedCaptionFormats, timestampAdjuster); + return createTsExtractor( + payloadReaderFactoryFlags, format, muxedCaptionFormats, timestampAdjuster); } } private static TsExtractor createTsExtractor( - Format format, List muxedCaptionFormats, TimestampAdjuster timestampAdjuster) { + @DefaultTsPayloadReaderFactory.Flags int userProvidedPayloadReaderFactoryFlags, + Format format, + List muxedCaptionFormats, + TimestampAdjuster timestampAdjuster) { @DefaultTsPayloadReaderFactory.Flags - int esReaderFactoryFlags = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM; + int payloadReaderFactoryFlags = + DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM + | userProvidedPayloadReaderFactoryFlags; if (muxedCaptionFormats != null) { // The playlist declares closed caption renditions, we should ignore descriptors. - esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS; + payloadReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS; } else { // The playlist does not provide any closed caption information. We preemptively declare a // closed caption track on channel 0. @@ -208,17 +234,17 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { // exist. If we know from the codec attribute that they don't exist, then we can // explicitly ignore them even if they're declared. if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { - esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; + payloadReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; } if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { - esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; + payloadReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; } } return new TsExtractor( TsExtractor.MODE_HLS, timestampAdjuster, - new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, muxedCaptionFormats)); + new DefaultTsPayloadReaderFactory(payloadReaderFactoryFlags, muxedCaptionFormats)); } private static Pair buildResult(Extractor extractor) { From a394ab7be31e403c6fc214b75c2fd49ae9d513bd Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 27 Sep 2018 09:16:37 -0700 Subject: [PATCH 029/832] Update release notes for 2.9.0 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214784773 --- RELEASENOTES.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a1e70fa018..234a4a1664 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,11 +3,6 @@ ### dev-v2 (not yet released) ### * Do not retry failed loads whose error is `FileNotFoundException`. -* Allow setting log level for ExoPlayer logcat output - ([#4665](https://github.com/google/ExoPlayer/issues/4665)). -* Fix an issue where audio and video would desynchronize when playing - concatenations of gapless content - ([#4559](https://github.com/google/ExoPlayer/issues/4559)). * Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext` and `Player.hasPrevious` ([#4863](https://github.com/google/ExoPlayer/issues/4863)). @@ -33,6 +28,8 @@ * Allow passing a `Looper`, which specifies the thread that must be used to access the player, when instantiating player instances using `ExoPlayerFactory` ([#4278](https://github.com/google/ExoPlayer/issues/4278)). +* Allow setting log level for ExoPlayer logcat output + ([#4665](https://github.com/google/ExoPlayer/issues/4665)). * Simplify `BandwidthMeter` injection: The `BandwidthMeter` should now be passed directly to `ExoPlayerFactory`, instead of to `TrackSelection.Factory` and `DataSource.Factory`. The `BandwidthMeter` is passed to the components @@ -155,6 +152,9 @@ * Fix bug preventing use of multiple key session support (`multiSession=true`) for non-Widevine `DefaultDrmSessionManager` instances ([#4834](https://github.com/google/ExoPlayer/issues/4834)). +* Fix issue where audio and video would desynchronize when playing + concatenations of gapless content + ([#4559](https://github.com/google/ExoPlayer/issues/4559)). * IMA extension: * Refine the previous fix for empty ad groups to avoid discarding ad breaks unnecessarily ([#4030](https://github.com/google/ExoPlayer/issues/4030) and From 827ea636571998b4d97a43d564410e1c6251b146 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 27 Sep 2018 09:33:36 -0700 Subject: [PATCH 030/832] Fix issue with stale createPeriod events in ConcatenatingMediaSource. If a source is removed from the playlist, the player may still call createPeriod for a period of the removed source as long as the new timeline hasn't been handled by the player. These events are stale and can be ignored by using a dummy media source. The stale media period will be released when the player handles the updated timeline. Issue:#4871 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214787090 --- RELEASENOTES.md | 2 + .../source/ConcatenatingMediaSource.java | 39 ++++++++++++++++++- .../android/exoplayer2/ExoPlayerTest.java | 27 +++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 234a4a1664..db9f5254ef 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -138,6 +138,8 @@ * Fix bugs reporting events for multi-period media sources ([#4492](https://github.com/google/ExoPlayer/issues/4492) and [#4634](https://github.com/google/ExoPlayer/issues/4634)). +* Fix issue where removing looping media from a playlist throws an exception + ([#4871](https://github.com/google/ExoPlayer/issues/4871). * Fix issue where the preferred audio or text track would not be selected if mapped onto a secondary renderer of the corresponding type ([#4711](http://github.com/google/ExoPlayer/issues/4711)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 30e764dfe5..e0b7da8506 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -474,7 +474,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource Date: Thu, 27 Sep 2018 10:02:21 -0700 Subject: [PATCH 031/832] Don't recreate the audio decoder if Format is unchanged More specifically, if the parts of the Format that are used for decoder configuration are unchanged. Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214791234 --- .../audio/MediaCodecAudioRenderer.java | 42 +++++-- .../mediacodec/MediaCodecRenderer.java | 105 ++++++++++++------ 2 files changed, 103 insertions(+), 44 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 624e698ad6..89d2ee16b2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -346,7 +346,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats()); codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); passthroughEnabled = codecInfo.passthrough; - String codecMimeType = codecInfo.mimeType == null ? MimeTypes.AUDIO_RAW : codecInfo.mimeType; + String codecMimeType = passthroughEnabled ? MimeTypes.AUDIO_RAW : codecInfo.mimeType; MediaFormat mediaFormat = getMediaFormat(format, codecMimeType, codecMaxInputSize, codecOperatingRate); codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0); @@ -362,14 +362,22 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media @Override protected @KeepCodecResult int canKeepCodec( MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { - if (getCodecMaxInputSize(codecInfo, newFormat) <= codecMaxInputSize - && codecInfo.isSeamlessAdaptationSupported( - oldFormat, newFormat, /* isNewFormatComplete= */ true) - && oldFormat.encoderDelay == 0 - && oldFormat.encoderPadding == 0 - && newFormat.encoderDelay == 0 - && newFormat.encoderPadding == 0) { + // TODO: We currently rely on recreating the codec when encoder delay or padding is non-zero. + // Re-creating the codec is necessary to guarantee that onOutputFormatChanged is called, which + // is where encoder delay and padding are propagated to the sink. We should find a better way to + // propagate these values, and then allow the codec to be re-used in cases where this would + // otherwise be possible. + if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize + || oldFormat.encoderDelay != 0 + || oldFormat.encoderPadding != 0 + || newFormat.encoderDelay != 0 + || newFormat.encoderPadding != 0) { + return KEEP_CODEC_RESULT_NO; + } else if (codecInfo.isSeamlessAdaptationSupported( + oldFormat, newFormat, /* isNewFormatComplete= */ true)) { return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION; + } else if (areCodecConfigurationCompatible(oldFormat, newFormat)) { + return KEEP_CODEC_RESULT_YES_WITH_FLUSH; } else { return KEEP_CODEC_RESULT_NO; } @@ -720,6 +728,24 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media return format.maxInputSize; } + /** + * Returns whether two {@link Format}s will cause the same codec to be configured in an identical + * way, excluding {@link MediaFormat#KEY_MAX_INPUT_SIZE} and configuration that does not come from + * the {@link Format}. + * + * @param oldFormat The first format. + * @param newFormat The second format. + * @return Whether the two formats will cause a codec to be configured in an identical way, + * excluding {@link MediaFormat#KEY_MAX_INPUT_SIZE} and configuration that does not come from + * the {@link Format}. + */ + protected boolean areCodecConfigurationCompatible(Format oldFormat, Format newFormat) { + return Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType) + && oldFormat.channelCount == newFormat.channelCount + && oldFormat.sampleRate == newFormat.sampleRate + && oldFormat.initializationDataEquals(newFormat); + } + /** * Returns the framework {@link MediaFormat} that can be used to configure a {@link MediaCodec} * for decoding the given {@link Format} for playback. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 7e933e9474..bd77f32f5b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -185,19 +185,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Retention(RetentionPolicy.SOURCE) @IntDef({ KEEP_CODEC_RESULT_NO, - KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION, - KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION + KEEP_CODEC_RESULT_YES_WITH_FLUSH, + KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION, + KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION }) protected @interface KeepCodecResult {} /** The codec cannot be kept. */ protected static final int KEEP_CODEC_RESULT_NO = 0; - /** The codec can be kept. No reconfiguration is required. */ - protected static final int KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION = 1; + /** The codec can be kept, but must be flushed. */ + protected static final int KEEP_CODEC_RESULT_YES_WITH_FLUSH = 1; /** - * The codec can be kept, but must be reconfigured by prefixing the next input buffer with the new - * format's configuration data. + * The codec can be kept. It does not need to be flushed, but must be reconfigured by prefixing + * the next input buffer with the new format's configuration data. */ - protected static final int KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION = 3; + protected static final int KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION = 2; + /** The codec can be kept. It does not need to be flushed and no reconfiguration is required. */ + protected static final int KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION = 3; @Retention(RetentionPolicy.SOURCE) @IntDef({RECONFIGURATION_STATE_NONE, RECONFIGURATION_STATE_WRITE_PENDING, @@ -309,6 +312,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private boolean codecReconfigured; private @ReconfigurationState int codecReconfigurationState; private @ReinitializationState int codecReinitializationState; + private boolean codecReinitializationIsRelease; private boolean codecReceivedBuffers; private boolean codecReceivedEos; @@ -587,6 +591,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecReceivedEos = false; codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReinitializationState = REINITIALIZATION_STATE_NONE; + codecReinitializationIsRelease = false; codecConfiguredWithOperatingRate = false; if (codec != null) { decoderCounters.decoderReleaseCount++; @@ -682,10 +687,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { releaseCodec(); maybeInitCodec(); } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { - // We're already waiting to release and re-initialize the codec. Since we're now flushing, - // there's no need to wait any longer. - releaseCodec(); - maybeInitCodec(); + // We're already waiting to re-initialize the codec. Since we're now flushing, there's no need + // to wait any longer. + if (codecReinitializationIsRelease) { + releaseCodec(); + maybeInitCodec(); + } else { + codec.flush(); + codecReceivedBuffers = false; + codecReinitializationState = REINITIALIZATION_STATE_NONE; + } } else { // We can flush and re-use the existing decoder. codec.flush(); @@ -865,7 +876,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private boolean feedInputBuffer() throws ExoPlaybackException { if (codec == null || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM || inputStreamEnded) { - // We need to reinitialize the codec or the input stream has ended. + // We need to re-initialize the codec or the input stream has ended. return false; } @@ -1043,7 +1054,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * Called when a new format is read from the upstream {@link MediaPeriod}. * * @param newFormat The new format. - * @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}. + * @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}. */ protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { Format oldFormat = format; @@ -1067,18 +1078,28 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } } - boolean keepingCodec = false; - if (pendingDrmSession == drmSession && codec != null) { + if (codec == null) { + maybeInitCodec(); + return; + } + + // We have an existing codec that we may need to reconfigure or re-initialize. If the existing + // codec instance is being kept then its operating rate may need to be updated. + if (pendingDrmSession != drmSession) { + reinitializeCodec(/* release= */ true); + } else { switch (canKeepCodec(codec, codecInfo, oldFormat, format)) { case KEEP_CODEC_RESULT_NO: - // Do nothing. + reinitializeCodec(/* release= */ true); break; - case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION: - keepingCodec = true; + case KEEP_CODEC_RESULT_YES_WITH_FLUSH: + reinitializeCodec(/* release= */ false); + updateCodecOperatingRate(); break; case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION: - if (!codecNeedsReconfigureWorkaround) { - keepingCodec = true; + if (codecNeedsReconfigureWorkaround) { + reinitializeCodec(/* release= */ true); + } else { codecReconfigured = true; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; codecNeedsAdaptationWorkaroundBuffer = @@ -1086,18 +1107,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { || (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION && format.width == oldFormat.width && format.height == oldFormat.height); + updateCodecOperatingRate(); } break; + case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION: + updateCodecOperatingRate(); + break; default: throw new IllegalStateException(); // Never happens. } } - - if (!keepingCodec) { - reinitializeCodec(); - } else { - updateCodecOperatingRate(); - } } /** @@ -1211,13 +1230,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } this.codecOperatingRate = codecOperatingRate; - if (codec == null || codecReinitializationState != REINITIALIZATION_STATE_NONE) { - // Either no codec, or it's about to be reinitialized anyway. + if (codec == null + || (codecReinitializationState != REINITIALIZATION_STATE_NONE + && codecReinitializationIsRelease)) { + // Either no codec, or it's about to be released due to re-initialization anyway. } else if (codecOperatingRate == CODEC_OPERATING_RATE_UNSET && codecConfiguredWithOperatingRate) { // We need to clear the operating rate. The only way to do so is to instantiate a new codec // instance. See [Internal ref: b/71987865]. - reinitializeCodec(); + reinitializeCodec(/* release= */ true); } else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET && (codecConfiguredWithOperatingRate || codecOperatingRate > assumedMinimumCodecOperatingRate)) { @@ -1231,18 +1252,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } /** - * Starts the process of releasing the existing codec and initializing a new one. This may occur - * immediately, or be deferred until any final output buffers have been dequeued. + * Starts the process of re-initializing the codec. This may occur immediately, or be deferred + * until any final output buffers have been dequeued. * + * @param release Whether re-initialization requires fully releasing the codec and instantiating a + * new instance, as opposed to flushing and reusing the current instance. * @throws ExoPlaybackException If an error occurs releasing or initializing a codec. */ - private void reinitializeCodec() throws ExoPlaybackException { + private void reinitializeCodec(boolean release) throws ExoPlaybackException { availableCodecInfos = null; if (codecReceivedBuffers) { // Signal end of stream and wait for any final output buffers before re-initialization. codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; - } else { - // There aren't any final output buffers, so perform re-initialization immediately. + codecReinitializationIsRelease = release; + return; + } + + // Nothing has been queued to the decoder. If we need to fully release the codec and instantiate + // a new instance, do so immediately. If only a flush is required then we can do nothing, since + // flushing will be a no-op. + if (release) { releaseCodec(); maybeInitCodec(); } @@ -1449,8 +1478,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private void processEndOfStream() throws ExoPlaybackException { if (codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { // We're waiting to re-initialize the codec, and have now processed all final buffers. - releaseCodec(); - maybeInitCodec(); + if (codecReinitializationIsRelease) { + releaseCodec(); + maybeInitCodec(); + } else { + flushCodec(); + } } else { outputStreamEnded = true; renderToEndOfStream(); From 47ce263bee3505206c51d2446843d47090d254c6 Mon Sep 17 00:00:00 2001 From: hacker1024 Date: Fri, 28 Sep 2018 08:10:57 +1000 Subject: [PATCH 032/832] Call rating with extras --- .../ext/mediasession/MediaSessionConnector.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java index 0915ee4b03..d0cde5f693 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -259,6 +259,9 @@ public final class MediaSessionConnector { /** See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat)}. */ void onSetRating(Player player, RatingCompat rating); + + /** See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat, Bundle)}. */ + void onSetRating(Player player, RatingCompat rating, Bundle extras); } /** @@ -1002,6 +1005,13 @@ public final class MediaSessionConnector { ratingCallback.onSetRating(player, rating); } } + + @Override + public void onSetRating(RatingCompat rating, Bundle extras) { + if (canDispatchToRatingCallback(PlaybackStateCompat.ACTION_SET_RATING)) { + ratingCallback.onSetRating(player, rating, extras); + } + } @Override public void onAddQueueItem(MediaDescriptionCompat description) { From 9b111fbd95c56221e75fa20e408ed7f978c5ae79 Mon Sep 17 00:00:00 2001 From: Andrew Shu Date: Thu, 27 Sep 2018 18:34:49 -0700 Subject: [PATCH 033/832] Make DefaultTrackSelector.AudioTrackScore protected This allows selectAudioTrack() to be overridden. --- .../android/exoplayer2/trackselection/DefaultTrackSelector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index f5a347b351..4a75b6f722 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -2141,7 +2141,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { } /** Represents how well an audio track matches the selection {@link Parameters}. */ - private static final class AudioTrackScore implements Comparable { + protected static final class AudioTrackScore implements Comparable { private final Parameters parameters; private final int withinRendererCapabilitiesScore; From 5c8dabade63769b4cdb289a87d7ae003cb78376a Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 28 Sep 2018 10:04:14 -0700 Subject: [PATCH 034/832] Add @Documented to @IntDef and @StringDef annotations. This makes the annotations appear in the generated JavaDoc. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214952419 --- .../ext/cronet/CronetEngineWrapper.java | 2 ++ .../exoplayer2/ext/flac/FlacExtractor.java | 2 ++ .../exoplayer2/ext/ima/ImaAdsLoader.java | 2 ++ .../ext/vp9/LibvpxVideoRenderer.java | 9 +++++-- .../java/com/google/android/exoplayer2/C.java | 18 +++++++++++++ .../exoplayer2/DefaultRenderersFactory.java | 2 ++ .../exoplayer2/ExoPlaybackException.java | 2 ++ .../com/google/android/exoplayer2/Player.java | 4 +++ .../google/android/exoplayer2/Renderer.java | 2 ++ .../android/exoplayer2/audio/Ac3Util.java | 2 ++ .../exoplayer2/audio/AudioFocusManager.java | 3 +++ .../audio/AudioTimestampPoller.java | 2 ++ .../audio/AudioTrackPositionTracker.java | 2 ++ .../exoplayer2/audio/DefaultAudioSink.java | 7 ++--- .../audio/SilenceSkippingAudioProcessor.java | 2 ++ .../audio/SimpleDecoderAudioRenderer.java | 9 +++++-- .../decoder/DecoderInputBuffer.java | 2 ++ .../drm/DefaultDrmSessionManager.java | 2 ++ .../android/exoplayer2/drm/DrmSession.java | 2 ++ .../drm/UnsupportedDrmException.java | 2 ++ .../extractor/BinarySearchSeeker.java | 2 ++ .../exoplayer2/extractor/Extractor.java | 2 ++ .../extractor/amr/AmrExtractor.java | 2 ++ .../extractor/flv/FlvExtractor.java | 15 +++++++---- .../extractor/mkv/DefaultEbmlReader.java | 2 ++ .../extractor/mkv/EbmlReaderOutput.java | 2 ++ .../extractor/mkv/MatroskaExtractor.java | 2 ++ .../extractor/mp3/Mp3Extractor.java | 2 ++ .../extractor/mp4/FragmentedMp4Extractor.java | 2 ++ .../extractor/mp4/Mp4Extractor.java | 8 +++--- .../exoplayer2/extractor/mp4/Track.java | 2 ++ .../exoplayer2/extractor/ts/Ac3Reader.java | 3 +++ .../extractor/ts/AdtsExtractor.java | 2 ++ .../ts/DefaultTsPayloadReaderFactory.java | 2 ++ .../exoplayer2/extractor/ts/TsExtractor.java | 2 ++ .../mediacodec/MediaCodecRenderer.java | 26 ++++++++++++++----- .../exoplayer2/offline/DownloadManager.java | 3 +++ .../exoplayer2/scheduler/Requirements.java | 2 ++ .../source/ClippingMediaSource.java | 2 ++ .../exoplayer2/source/MergingMediaSource.java | 2 ++ .../source/ads/AdPlaybackState.java | 2 ++ .../exoplayer2/source/ads/AdsMediaSource.java | 2 ++ .../exoplayer2/text/CaptionStyleCompat.java | 2 ++ .../google/android/exoplayer2/text/Cue.java | 4 +++ .../android/exoplayer2/text/TextRenderer.java | 9 +++++-- .../exoplayer2/text/ttml/TtmlStyle.java | 12 +++++++-- .../text/webvtt/WebvttCssStyle.java | 5 ++++ .../trackselection/MappingTrackSelector.java | 2 ++ .../android/exoplayer2/upstream/DataSpec.java | 3 +++ .../exoplayer2/upstream/HttpDataSource.java | 3 +++ .../android/exoplayer2/upstream/Loader.java | 2 ++ .../upstream/cache/CacheDataSource.java | 3 +++ .../exoplayer2/util/EGLSurfaceTexture.java | 2 ++ .../google/android/exoplayer2/util/Log.java | 2 ++ .../exoplayer2/util/NotificationUtil.java | 2 ++ .../exoplayer2/util/RepeatModeUtil.java | 2 ++ .../video/spherical/Projection.java | 2 ++ .../source/dash/DashMediaPeriod.java | 2 ++ .../source/hls/playlist/HlsMediaPlaylist.java | 2 ++ .../exoplayer2/ui/AspectRatioFrameLayout.java | 2 ++ .../ui/PlayerNotificationManager.java | 3 +++ .../android/exoplayer2/ui/PlayerView.java | 2 ++ 62 files changed, 208 insertions(+), 25 deletions(-) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java index dd39ea2822..829b53f863 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java @@ -19,6 +19,7 @@ import android.content.Context; import android.support.annotation.IntDef; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; @@ -43,6 +44,7 @@ public final class CronetEngineWrapper { * Source of {@link CronetEngine}. One of {@link #SOURCE_NATIVE}, {@link #SOURCE_GMS}, {@link * #SOURCE_UNKNOWN}, {@link #SOURCE_USER_PROVIDED} or {@link #SOURCE_UNAVAILABLE}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({SOURCE_NATIVE, SOURCE_GMS, SOURCE_UNKNOWN, SOURCE_USER_PROVIDED, SOURCE_UNAVAILABLE}) public @interface CronetEngineSource {} diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java index a1fbcc69d6..8f5dcef16b 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.util.FlacStreamInfo; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -54,6 +55,7 @@ public final class FlacExtractor implements Extractor { * Flags controlling the behavior of the extractor. Possible flag value is {@link * #FLAG_DISABLE_ID3_METADATA}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 224f5aa6ee..95a3a588b4 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -60,6 +60,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -230,6 +231,7 @@ public final class ImaAdsLoader private static final int TIMEOUT_UNSET = -1; /** The state of ad playback. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) private @interface ImaAdState {} diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index c09d2fe55a..e3081cd2d2 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -64,9 +65,13 @@ import java.lang.annotation.RetentionPolicy; */ public class LibvpxVideoRenderer extends BaseRenderer { + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, - REINITIALIZATION_STATE_WAIT_END_OF_STREAM}) + @IntDef({ + REINITIALIZATION_STATE_NONE, + REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, + REINITIALIZATION_STATE_WAIT_END_OF_STREAM + }) private @interface ReinitializationState {} /** * The decoder does not need to be re-initialized. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 0cbdc14b1c..6c72dd8d0a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.UUID; @@ -114,6 +115,7 @@ public final class C { * Crypto modes for a codec. One of {@link #CRYPTO_MODE_UNENCRYPTED}, {@link #CRYPTO_MODE_AES_CTR} * or {@link #CRYPTO_MODE_AES_CBC}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC}) public @interface CryptoMode {} @@ -144,6 +146,7 @@ public final class C { * #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link * #ENCODING_DOLBY_TRUEHD}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ Format.NO_VALUE, @@ -169,6 +172,7 @@ public final class C { * #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link * #ENCODING_PCM_MU_LAW} or {@link #ENCODING_PCM_A_LAW}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ Format.NO_VALUE, @@ -215,6 +219,7 @@ public final class C { * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link * #STREAM_TYPE_USE_DEFAULT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ STREAM_TYPE_ALARM, @@ -269,6 +274,7 @@ public final class C { * #CONTENT_TYPE_MOVIE}, {@link #CONTENT_TYPE_MUSIC}, {@link #CONTENT_TYPE_SONIFICATION}, {@link * #CONTENT_TYPE_SPEECH} or {@link #CONTENT_TYPE_UNKNOWN}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ CONTENT_TYPE_MOVIE, @@ -309,6 +315,7 @@ public final class C { *

    Note that {@code FLAG_HW_AV_SYNC} is not available because the player takes care of setting * the flag when tunneling is enabled via a track selector. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -331,6 +338,7 @@ public final class C { * #USAGE_UNKNOWN}, {@link #USAGE_VOICE_COMMUNICATION} or {@link * #USAGE_VOICE_COMMUNICATION_SIGNALLING}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ USAGE_ALARM, @@ -427,6 +435,7 @@ public final class C { * #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link * #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ AUDIOFOCUS_NONE, @@ -454,6 +463,7 @@ public final class C { * #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_ENCRYPTED} and * {@link #BUFFER_FLAG_DECODE_ONLY}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -482,6 +492,7 @@ public final class C { * Video scaling modes for {@link MediaCodec}-based {@link Renderer}s. One of {@link * #VIDEO_SCALING_MODE_SCALE_TO_FIT} or {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}) public @interface VideoScalingMode {} @@ -504,6 +515,7 @@ public final class C { * Track selection flags. Possible flag values are {@link #SELECTION_FLAG_DEFAULT}, {@link * #SELECTION_FLAG_FORCED} and {@link #SELECTION_FLAG_AUTOSELECT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -530,6 +542,7 @@ public final class C { * Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link * #TYPE_HLS} or {@link #TYPE_OTHER}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER}) public @interface ContentType {} @@ -796,6 +809,7 @@ public final class C { * #STEREO_MODE_MONO}, {@link #STEREO_MODE_TOP_BOTTOM}, {@link #STEREO_MODE_LEFT_RIGHT} or {@link * #STEREO_MODE_STEREO_MESH}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ Format.NO_VALUE, @@ -827,6 +841,7 @@ public final class C { * Video colorspaces. One of {@link Format#NO_VALUE}, {@link #COLOR_SPACE_BT709}, {@link * #COLOR_SPACE_BT601} or {@link #COLOR_SPACE_BT2020}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020}) public @interface ColorSpace {} @@ -847,6 +862,7 @@ public final class C { * Video color transfer characteristics. One of {@link Format#NO_VALUE}, {@link * #COLOR_TRANSFER_SDR}, {@link #COLOR_TRANSFER_ST2084} or {@link #COLOR_TRANSFER_HLG}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG}) public @interface ColorTransfer {} @@ -867,6 +883,7 @@ public final class C { * Video color range. One of {@link Format#NO_VALUE}, {@link #COLOR_RANGE_LIMITED} or {@link * #COLOR_RANGE_FULL}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL}) public @interface ColorRange {} @@ -899,6 +916,7 @@ public final class C { * #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link #NETWORK_TYPE_ETHERNET} or * {@link #NETWORK_TYPE_OTHER}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ NETWORK_TYPE_UNKNOWN, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index 4e69bc316e..cc16c43b05 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.spherical.CameraMotionRenderer; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; @@ -56,6 +57,7 @@ public class DefaultRenderersFactory implements RenderersFactory { * Modes for using extension renderers. One of {@link #EXTENSION_RENDERER_MODE_OFF}, {@link * #EXTENSION_RENDERER_MODE_ON} or {@link #EXTENSION_RENDERER_MODE_PREFER}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, EXTENSION_RENDERER_MODE_PREFER}) public @interface ExtensionRendererMode {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java index d591876a51..6b84245141 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -31,6 +32,7 @@ public final class ExoPlaybackException extends Exception { * The type of source that produced the error. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} * or {@link #TYPE_UNEXPECTED}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED}) public @interface Type {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 3640928c54..16f8aa2878 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoListener; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -446,6 +447,7 @@ public interface Player { * Repeat modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link * #REPEAT_MODE_ALL}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL}) @interface RepeatMode {} @@ -467,6 +469,7 @@ public interface Player { * {@link #DISCONTINUITY_REASON_SEEK}, {@link #DISCONTINUITY_REASON_SEEK_ADJUSTMENT}, {@link * #DISCONTINUITY_REASON_AD_INSERTION} or {@link #DISCONTINUITY_REASON_INTERNAL}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ DISCONTINUITY_REASON_PERIOD_TRANSITION, @@ -497,6 +500,7 @@ public interface Player { * Reasons for timeline and/or manifest changes. One of {@link #TIMELINE_CHANGE_REASON_PREPARED}, * {@link #TIMELINE_CHANGE_REASON_RESET} or {@link #TIMELINE_CHANGE_REASON_DYNAMIC}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ TIMELINE_CHANGE_REASON_PREPARED, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index d1e1541cdc..c6456e5f7f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.util.MediaClock; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -38,6 +39,7 @@ public interface Renderer extends PlayerMessage.Target { * The renderer states. One of {@link #STATE_DISABLED}, {@link #STATE_ENABLED} or {@link * #STATE_STARTED}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED}) @interface State {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index 3e00bcc902..230b96d01f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -40,6 +41,7 @@ public final class Ac3Util { * AC3 stream types. See also ETSI TS 102 366 E.1.3.1.1. One of {@link #STREAM_TYPE_UNDEFINED}, * {@link #STREAM_TYPE_TYPE0}, {@link #STREAM_TYPE_TYPE1} or {@link #STREAM_TYPE_TYPE2}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STREAM_TYPE_UNDEFINED, STREAM_TYPE_TYPE0, STREAM_TYPE_TYPE1, STREAM_TYPE_TYPE2}) public @interface StreamType {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java index 5fb571d195..ca4e0c299e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -56,6 +57,7 @@ public final class AudioFocusManager { * Player commands. One of {@link #PLAYER_COMMAND_DO_NOT_PLAY}, {@link * #PLAYER_COMMAND_WAIT_FOR_CALLBACK} or {@link #PLAYER_COMMAND_PLAY_WHEN_READY}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ PLAYER_COMMAND_DO_NOT_PLAY, @@ -71,6 +73,7 @@ public final class AudioFocusManager { public static final int PLAYER_COMMAND_PLAY_WHEN_READY = 1; /** Audio focus state. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ AUDIO_FOCUS_STATE_LOST_FOCUS, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java index 47120e7375..569260efeb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java @@ -22,6 +22,7 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -45,6 +46,7 @@ import java.lang.annotation.RetentionPolicy; /* package */ final class AudioTimestampPoller { /** Timestamp polling states. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ STATE_INITIALIZING, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java index 0095001299..62b120f00a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java @@ -25,6 +25,7 @@ import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; @@ -97,6 +98,7 @@ import java.lang.reflect.Method; } /** {@link AudioTrack} playback states. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({PLAYSTATE_STOPPED, PLAYSTATE_PAUSED, PLAYSTATE_PLAYING}) private @interface PlayState {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index fbd5b027c1..4ce34ad41a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -30,6 +30,7 @@ import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -195,12 +196,12 @@ public final class DefaultAudioSink implements AudioSink { private static final String TAG = "AudioTrack"; - /** - * Represents states of the {@link #startMediaTimeUs} value. - */ + /** Represents states of the {@link #startMediaTimeUs} value. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({START_NOT_SET, START_IN_SYNC, START_NEED_SYNC}) private @interface StartMediaTimeState {} + private static final int START_NOT_SET = 0; private static final int START_IN_SYNC = 1; private static final int START_NEED_SYNC = 2; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java index 7c4eacecfb..a1ff7028c1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -54,6 +55,7 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor { private static final byte SILENCE_THRESHOLD_LEVEL_MSB = (SILENCE_THRESHOLD_LEVEL + 128) >> 8; /** Trimming states. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ STATE_NOISY, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index a527a58be4..cecb17d96c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -43,6 +43,7 @@ import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -65,9 +66,13 @@ import java.lang.annotation.RetentionPolicy; */ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock { + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, - REINITIALIZATION_STATE_WAIT_END_OF_STREAM}) + @IntDef({ + REINITIALIZATION_STATE_NONE, + REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, + REINITIALIZATION_STATE_WAIT_END_OF_STREAM + }) private @interface ReinitializationState {} /** * The decoder does not need to be re-initialized. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java index 7a32ef128b..983c96f89d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.decoder; import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -31,6 +32,7 @@ public class DecoderInputBuffer extends Buffer { * #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} or {@link * #BUFFER_REPLACEMENT_MODE_DIRECT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ BUFFER_REPLACEMENT_MODE_DISABLED, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index 460ee357ea..d71c6fc64c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.EventDispatcher; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -70,6 +71,7 @@ public class DefaultDrmSessionManager implements DrmSe * Determines the action to be done after a session acquired. One of {@link #MODE_PLAYBACK}, * {@link #MODE_QUERY}, {@link #MODE_DOWNLOAD} or {@link #MODE_RELEASE}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE}) public @interface Mode {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java index 698f6fdb25..a68415287e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java @@ -19,6 +19,7 @@ import android.annotation.TargetApi; import android.media.MediaDrm; import android.support.annotation.IntDef; import android.support.annotation.Nullable; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; @@ -44,6 +45,7 @@ public interface DrmSession { * The state of the DRM session. One of {@link #STATE_RELEASED}, {@link #STATE_ERROR}, {@link * #STATE_OPENING}, {@link #STATE_OPENED} or {@link #STATE_OPENED_WITH_KEYS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) @interface State {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java index 5bea83d020..7f4a0f5f03 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.drm; import android.support.annotation.IntDef; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,6 +29,7 @@ public final class UnsupportedDrmException extends Exception { * The reason for the exception. One of {@link #REASON_UNSUPPORTED_SCHEME} or {@link * #REASON_INSTANTIATION_ERROR}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR}) public @interface Reason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java index 435fb13648..3b0b834427 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -437,6 +438,7 @@ public abstract class BinarySearchSeeker { public static final int RESULT_POSITION_UNDERESTIMATED = -2; public static final int RESULT_NO_TIMESTAMP = -3; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ RESULT_TARGET_TIMESTAMP_FOUND, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java index 26d0788b33..05f5d98d3c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor; import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -48,6 +49,7 @@ public interface Extractor { * Result values that can be returned by {@link #read(ExtractorInput, PositionHolder)}. One of * {@link #RESULT_CONTINUE}, {@link #RESULT_SEEK} or {@link #RESULT_END_OF_INPUT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef(value = {RESULT_CONTINUE, RESULT_SEEK, RESULT_END_OF_INPUT}) @interface ReadResult {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java index dfdce02450..b93969acfe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -51,6 +52,7 @@ public final class AmrExtractor implements Extractor { * Flags controlling the behavior of the extractor. Possible flag value is {@link * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java index 604a520526..4211cab489 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,13 +38,17 @@ public final class FlvExtractor implements Extractor { /** Factory for {@link FlvExtractor} instances. */ public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new FlvExtractor()}; - /** - * Extractor states. - */ + /** Extractor states. */ + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({STATE_READING_FLV_HEADER, STATE_SKIPPING_TO_TAG_HEADER, STATE_READING_TAG_HEADER, - STATE_READING_TAG_DATA}) + @IntDef({ + STATE_READING_FLV_HEADER, + STATE_SKIPPING_TO_TAG_HEADER, + STATE_READING_TAG_HEADER, + STATE_READING_TAG_DATA + }) private @interface States {} + private static final int STATE_READING_FLV_HEADER = 1; private static final int STATE_SKIPPING_TO_TAG_HEADER = 2; private static final int STATE_READING_TAG_HEADER = 3; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java index c0494e1ee0..0987bc473f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.Assertions; import java.io.EOFException; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; @@ -31,6 +32,7 @@ import java.util.ArrayDeque; */ /* package */ final class DefaultEbmlReader implements EbmlReader { + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ELEMENT_STATE_READ_ID, ELEMENT_STATE_READ_CONTENT_SIZE, ELEMENT_STATE_READ_CONTENT}) private @interface ElementState {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java index 067c88b552..cc17af5632 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.extractor.ExtractorInput; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -31,6 +32,7 @@ import java.lang.annotation.RetentionPolicy; * EBML element types. One of {@link #TYPE_UNKNOWN}, {@link #TYPE_MASTER}, {@link * #TYPE_UNSIGNED_INT}, {@link #TYPE_STRING}, {@link #TYPE_BINARY} or {@link #TYPE_FLOAT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNKNOWN, TYPE_MASTER, TYPE_UNSIGNED_INT, TYPE_STRING, TYPE_BINARY, TYPE_FLOAT}) @interface ElementType {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 35b6969084..63fee48771 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.video.AvcConfig; import com.google.android.exoplayer2.video.ColorInfo; import com.google.android.exoplayer2.video.HevcConfig; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -68,6 +69,7 @@ public final class MatroskaExtractor implements Extractor { * Flags controlling the behavior of the extractor. Possible flag value is {@link * #FLAG_DISABLE_SEEK_FOR_CUES}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 26a8bcce75..92cf590a49 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -35,6 +35,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,6 +51,7 @@ public final class Mp3Extractor implements Extractor { * Flags controlling the behavior of the extractor. Possible flag values are {@link * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} and {@link #FLAG_DISABLE_ID3_METADATA}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index d8adcde28c..0f1fd8f649 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -43,6 +43,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; @@ -67,6 +68,7 @@ public final class FragmentedMp4Extractor implements Extractor { * {@link #FLAG_ENABLE_EMSG_TRACK}, {@link #FLAG_SIDELOADED} and {@link * #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 7cf61b4ff3..17c82c2c5b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -35,6 +35,7 @@ import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; @@ -53,6 +54,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { * Flags controlling the behavior of the extractor. Possible flag value is {@link * #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -63,12 +65,12 @@ public final class Mp4Extractor implements Extractor, SeekMap { */ public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1; - /** - * Parser states. - */ + /** Parser states. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_READING_ATOM_HEADER, STATE_READING_ATOM_PAYLOAD, STATE_READING_SAMPLE}) private @interface State {} + private static final int STATE_READING_ATOM_HEADER = 0; private static final int STATE_READING_ATOM_PAYLOAD = 1; private static final int STATE_READING_SAMPLE = 2; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java index 867e037f4b..59cd602209 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -31,6 +32,7 @@ public final class Track { * The transformation to apply to samples in the track, if any. One of {@link * #TRANSFORMATION_NONE} or {@link #TRANSFORMATION_CEA608_CDAT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT}) public @interface Transformation {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java index 4141f83370..2ef9704a7a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -33,9 +34,11 @@ import java.lang.annotation.RetentionPolicy; */ public final class Ac3Reader implements ElementaryStreamReader { + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_FINDING_SYNC, STATE_READING_HEADER, STATE_READING_SAMPLE}) private @interface State {} + private static final int STATE_FINDING_SYNC = 0; private static final int STATE_READING_HEADER = 1; private static final int STATE_READING_SAMPLE = 2; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java index 0c2a0545dc..04a6b571bd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -47,6 +48,7 @@ public final class AdtsExtractor implements Extractor { * Flags controlling the behavior of the extractor. Possible flag value is {@link * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 7353b25e9c..a5506e2cfb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.text.cea.Cea708InitializationData; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -39,6 +40,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact * #FLAG_IGNORE_H264_STREAM}, {@link #FLAG_DETECT_ACCESS_UNITS}, {@link * #FLAG_IGNORE_SPLICE_INFO_STREAM} and {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 978b1e8813..f47a481d7e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -57,6 +58,7 @@ public final class TsExtractor implements Extractor { * Modes for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} or {@link * #MODE_HLS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS}) public @interface Mode {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index bd77f32f5b..87d56fdf3d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -46,6 +46,7 @@ import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.TimedValueQueue; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -182,6 +183,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * The possible return values for {@link #canKeepCodec(MediaCodec, MediaCodecInfo, Format, * Format)}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ KEEP_CODEC_RESULT_NO, @@ -202,9 +204,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { /** The codec can be kept. It does not need to be flushed and no reconfiguration is required. */ protected static final int KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION = 3; + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({RECONFIGURATION_STATE_NONE, RECONFIGURATION_STATE_WRITE_PENDING, - RECONFIGURATION_STATE_QUEUE_PENDING}) + @IntDef({ + RECONFIGURATION_STATE_NONE, + RECONFIGURATION_STATE_WRITE_PENDING, + RECONFIGURATION_STATE_QUEUE_PENDING + }) private @interface ReconfigurationState {} /** * There is no pending adaptive reconfiguration work. @@ -220,9 +226,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { */ private static final int RECONFIGURATION_STATE_QUEUE_PENDING = 2; + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, - REINITIALIZATION_STATE_WAIT_END_OF_STREAM}) + @IntDef({ + REINITIALIZATION_STATE_NONE, + REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, + REINITIALIZATION_STATE_WAIT_END_OF_STREAM + }) private @interface ReinitializationState {} /** * The codec does not need to be re-initialized. @@ -241,9 +251,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { */ private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2; + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({ADAPTATION_WORKAROUND_MODE_NEVER, ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION, - ADAPTATION_WORKAROUND_MODE_ALWAYS}) + @IntDef({ + ADAPTATION_WORKAROUND_MODE_NEVER, + ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION, + ADAPTATION_WORKAROUND_MODE_ALWAYS + }) private @interface AdaptationWorkaroundMode {} /** * The adaptation workaround is never used. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java index 3b26741897..409f79f30b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.util.Util; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -532,6 +533,7 @@ public final class DownloadManager { * -> failed * */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_QUEUED, STATE_STARTED, STATE_COMPLETED, STATE_CANCELED, STATE_FAILED}) public @interface State {} @@ -621,6 +623,7 @@ public final class DownloadManager { * +-----------+------+-------+---------+-----------+-----------+--------+--------+------+ * */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ STATE_QUEUED, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java index 0ee8d76bc7..4d6dbd83be 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java @@ -27,6 +27,7 @@ import android.os.PowerManager; import android.support.annotation.IntDef; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -39,6 +40,7 @@ public final class Requirements { * Network types. One of {@link #NETWORK_TYPE_NONE}, {@link #NETWORK_TYPE_ANY}, {@link * #NETWORK_TYPE_UNMETERED}, {@link #NETWORK_TYPE_NOT_ROAMING} or {@link #NETWORK_TYPE_METERED}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ NETWORK_TYPE_NONE, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index 88e98e811f..5f80725805 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -41,6 +42,7 @@ public final class ClippingMediaSource extends CompositeMediaSource { * The reason clipping failed. One of {@link #REASON_INVALID_PERIOD_COUNT}, {@link * #REASON_NOT_SEEKABLE_TO_START} or {@link #REASON_START_EXCEEDS_END}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({REASON_INVALID_PERIOD_COUNT, REASON_NOT_SEEKABLE_TO_START, REASON_START_EXCEEDS_END}) public @interface Reason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index 746af5719e..ecb4b10c6a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.TransferListener; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -41,6 +42,7 @@ public final class MergingMediaSource extends CompositeMediaSource { public static final class IllegalMergeException extends IOException { /** The reason the merge failed. One of {@link #REASON_PERIOD_COUNT_MISMATCH}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({REASON_PERIOD_COUNT_MISMATCH}) public @interface Reason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 72fc162bc3..41adb78906 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -20,6 +20,7 @@ import android.support.annotation.CheckResult; import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -239,6 +240,7 @@ public final class AdPlaybackState { * #AD_STATE_AVAILABLE}, {@link #AD_STATE_SKIPPED}, {@link #AD_STATE_PLAYED} or {@link * #AD_STATE_ERROR}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ AD_STATE_UNAVAILABLE, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 3d5c41e8bc..7fc0f22bf3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -39,6 +39,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -87,6 +88,7 @@ public final class AdsMediaSource extends CompositeMediaSource { * Types of ad load exceptions. One of {@link #TYPE_AD}, {@link #TYPE_AD_GROUP}, {@link * #TYPE_ALL_ADS} or {@link #TYPE_UNEXPECTED}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_AD, TYPE_AD_GROUP, TYPE_ALL_ADS, TYPE_UNEXPECTED}) public @interface Type {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java b/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java index 87dcb97a81..e7bb0e16bf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java @@ -22,6 +22,7 @@ import android.support.annotation.IntDef; import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager.CaptionStyle; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,6 +36,7 @@ public final class CaptionStyleCompat { * #EDGE_TYPE_OUTLINE}, {@link #EDGE_TYPE_DROP_SHADOW}, {@link #EDGE_TYPE_RAISED} or {@link * #EDGE_TYPE_DEPRESSED}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ EDGE_TYPE_NONE, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java index e1305acd14..a5c666c44a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -19,6 +19,7 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.support.annotation.IntDef; import android.text.Layout.Alignment; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -36,6 +37,7 @@ public class Cue { * The type of anchor, which may be unset. One of {@link #TYPE_UNSET}, {@link #ANCHOR_TYPE_START}, * {@link #ANCHOR_TYPE_MIDDLE} or {@link #ANCHOR_TYPE_END}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END}) public @interface AnchorType {} @@ -66,6 +68,7 @@ public class Cue { * The type of line, which may be unset. One of {@link #TYPE_UNSET}, {@link #LINE_TYPE_FRACTION} * or {@link #LINE_TYPE_NUMBER}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER}) public @interface LineType {} @@ -85,6 +88,7 @@ public class Cue { * {@link #TEXT_SIZE_TYPE_FRACTIONAL}, {@link #TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING} or {@link * #TEXT_SIZE_TYPE_ABSOLUTE}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ TYPE_UNSET, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java index 5b74bd1505..16f82a7293 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; @@ -49,9 +50,13 @@ public final class TextRenderer extends BaseRenderer implements Callback { @Deprecated public interface Output extends TextOutput {} + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({REPLACEMENT_STATE_NONE, REPLACEMENT_STATE_SIGNAL_END_OF_STREAM, - REPLACEMENT_STATE_WAIT_END_OF_STREAM}) + @IntDef({ + REPLACEMENT_STATE_NONE, + REPLACEMENT_STATE_SIGNAL_END_OF_STREAM, + REPLACEMENT_STATE_WAIT_END_OF_STREAM + }) private @interface ReplacementState {} /** * The decoder does not need to be replaced. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java index 90f93d5b21..a4f0cca955 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java @@ -19,6 +19,7 @@ import android.graphics.Typeface; import android.support.annotation.IntDef; import android.text.Layout; import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -29,25 +30,32 @@ import java.lang.annotation.RetentionPolicy; public static final int UNSPECIFIED = -1; + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, - STYLE_BOLD_ITALIC}) + @IntDef( + flag = true, + value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, STYLE_BOLD_ITALIC}) public @interface StyleFlags {} + public static final int STYLE_NORMAL = Typeface.NORMAL; public static final int STYLE_BOLD = Typeface.BOLD; public static final int STYLE_ITALIC = Typeface.ITALIC; public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) public @interface FontSizeUnit {} + public static final int FONT_SIZE_UNIT_PIXEL = 1; public static final int FONT_SIZE_UNIT_EM = 2; public static final int FONT_SIZE_UNIT_PERCENT = 3; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({UNSPECIFIED, OFF, ON}) private @interface OptionalBoolean {} + private static final int OFF = 0; private static final int ON = 1; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java index 0e46fa0d2f..fe274a6241 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java @@ -19,6 +19,7 @@ import android.graphics.Typeface; import android.support.annotation.IntDef; import android.text.Layout; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -39,6 +40,7 @@ public final class WebvttCssStyle { * Style flag enum. Possible flag values are {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link * #STYLE_BOLD}, {@link #STYLE_ITALIC} and {@link #STYLE_BOLD_ITALIC}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -54,6 +56,7 @@ public final class WebvttCssStyle { * Font size unit enum. One of {@link #UNSPECIFIED}, {@link #FONT_SIZE_UNIT_PIXEL}, {@link * #FONT_SIZE_UNIT_EM} or {@link #FONT_SIZE_UNIT_PERCENT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) public @interface FontSizeUnit {} @@ -62,9 +65,11 @@ public final class WebvttCssStyle { public static final int FONT_SIZE_UNIT_EM = 2; public static final int FONT_SIZE_UNIT_PERCENT = 3; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({UNSPECIFIED, OFF, ON}) private @interface OptionalBoolean {} + private static final int OFF = 0; private static final int ON = 1; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 81fbc16630..f9c63805f2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -50,6 +51,7 @@ public abstract class MappingTrackSelector extends TrackSelector { * {@link #RENDERER_SUPPORT_NO_TRACKS}, {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS}, {@link * #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS} or {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ RENDERER_SUPPORT_NO_TRACKS, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java index c968921822..4a4cc021f4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java @@ -20,6 +20,7 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -33,6 +34,7 @@ public final class DataSpec { * The flags that apply to any request for data. Possible flag values are {@link #FLAG_ALLOW_GZIP} * and {@link #FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -61,6 +63,7 @@ public final class DataSpec { * The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. One of {@link * #HTTP_METHOD_GET}, {@link #HTTP_METHOD_POST} or {@link #HTTP_METHOD_HEAD}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD}) public @interface HttpMethod {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index daf5d3281a..0be7b857df 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -20,6 +20,7 @@ import android.text.TextUtils; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; @@ -226,9 +227,11 @@ public interface HttpDataSource extends DataSource { */ class HttpDataSourceException extends IOException { + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_OPEN, TYPE_READ, TYPE_CLOSE}) public @interface Type {} + public static final int TYPE_OPEN = 1; public static final int TYPE_READ = 2; public static final int TYPE_CLOSE = 3; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java index 03219380c7..ac3b3c5c5e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.ExecutorService; @@ -136,6 +137,7 @@ public final class Loader implements LoaderErrorThrower { } /** Types of action that can be taken in response to a load error. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ ACTION_TYPE_RETRY, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index a91e3246cc..fa2068b99d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.upstream.cache.Cache.CacheException; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; import java.io.InterruptedIOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -59,6 +60,7 @@ public final class CacheDataSource implements DataSource { * Flags controlling the cache's behavior. Possible flag values are {@link #FLAG_BLOCK_ON_CACHE}, * {@link #FLAG_IGNORE_CACHE_ON_ERROR} and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -91,6 +93,7 @@ public final class CacheDataSource implements DataSource { * Reasons the cache may be ignored. One of {@link #CACHE_IGNORED_REASON_ERROR} or {@link * #CACHE_IGNORED_REASON_UNSET_LENGTH}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({CACHE_IGNORED_REASON_ERROR, CACHE_IGNORED_REASON_UNSET_LENGTH}) public @interface CacheIgnoredReason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java index bf4d78ee10..216ddb3c4e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java @@ -26,6 +26,7 @@ import android.opengl.GLES20; import android.os.Handler; import android.support.annotation.IntDef; import android.support.annotation.Nullable; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -43,6 +44,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL * Secure mode to be used by the EGL surface and context. One of {@link #SECURE_MODE_NONE}, {@link * #SECURE_MODE_SURFACELESS_CONTEXT} or {@link #SECURE_MODE_PROTECTED_PBUFFER}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({SECURE_MODE_NONE, SECURE_MODE_SURFACELESS_CONTEXT, SECURE_MODE_PROTECTED_PBUFFER}) public @interface SecureMode {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java index 6a1e686dec..34fb684d25 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.text.TextUtils; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,6 +29,7 @@ public final class Log { * Log level for ExoPlayer logcat logging. One of {@link #LOG_LEVEL_ALL}, {@link #LOG_LEVEL_INFO}, * {@link #LOG_LEVEL_WARNING}, {@link #LOG_LEVEL_ERROR} or {@link #LOG_LEVEL_OFF}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({LOG_LEVEL_ALL, LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR, LOG_LEVEL_OFF}) @interface LogLevel {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java index e70f576754..e45ab0952e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.support.annotation.StringRes; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -36,6 +37,7 @@ public final class NotificationUtil { * #IMPORTANCE_NONE}, {@link #IMPORTANCE_MIN}, {@link #IMPORTANCE_LOW}, {@link * #IMPORTANCE_DEFAULT} or {@link #IMPORTANCE_HIGH}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ IMPORTANCE_UNSPECIFIED, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java index de92e1ad93..5816e40623 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.util; import android.support.annotation.IntDef; import com.google.android.exoplayer2.Player; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,6 +31,7 @@ public final class RepeatModeUtil { * {@link #REPEAT_TOGGLE_MODE_NONE}, {@link #REPEAT_TOGGLE_MODE_ONE} and {@link * #REPEAT_TOGGLE_MODE_ALL}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java index 0a9d04bf0f..3d4879d50a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C.StereoMode; import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,6 +27,7 @@ import java.lang.annotation.RetentionPolicy; public final class Projection { /** Enforces allowed (sub) mesh draw modes. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({DRAW_MODE_TRIANGLES, DRAW_MODE_TRIANGLES_STRIP, DRAW_MODE_TRIANGLES_FAN}) public @interface DrawMode {} diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index a501435262..5c9a933508 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -47,6 +47,7 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.MimeTypes; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -660,6 +661,7 @@ import java.util.List; private static final class TrackGroupInfo { + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({CATEGORY_PRIMARY, CATEGORY_EMBEDDED, CATEGORY_MANIFEST_EVENTS}) public @interface TrackGroupCategory {} diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index a29808933b..81d4e7a818 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -21,6 +21,7 @@ import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.offline.StreamKey; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; @@ -157,6 +158,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { * Type of the playlist, as defined by #EXT-X-PLAYLIST-TYPE. One of {@link * #PLAYLIST_TYPE_UNKNOWN}, {@link #PLAYLIST_TYPE_VOD} or {@link #PLAYLIST_TYPE_EVENT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({PLAYLIST_TYPE_UNKNOWN, PLAYLIST_TYPE_VOD, PLAYLIST_TYPE_EVENT}) public @interface PlaylistType {} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java index 9d977d63b3..0d4c6a4038 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java @@ -20,6 +20,7 @@ import android.content.res.TypedArray; import android.support.annotation.IntDef; import android.util.AttributeSet; import android.widget.FrameLayout; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,6 +51,7 @@ public final class AspectRatioFrameLayout extends FrameLayout { * #RESIZE_MODE_FIXED_WIDTH}, {@link #RESIZE_MODE_FIXED_HEIGHT}, {@link #RESIZE_MODE_FILL} or * {@link #RESIZE_MODE_ZOOM}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ RESIZE_MODE_FIT, diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index 778f81375e..47025d9bba 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java @@ -42,6 +42,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -250,6 +251,7 @@ public class PlayerNotificationManager { * NotificationCompat#VISIBILITY_PRIVATE}, {@link NotificationCompat#VISIBILITY_PUBLIC} or {@link * NotificationCompat#VISIBILITY_SECRET}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ NotificationCompat.VISIBILITY_PRIVATE, @@ -264,6 +266,7 @@ public class PlayerNotificationManager { * NotificationCompat#PRIORITY_HIGH}, {@link NotificationCompat#PRIORITY_LOW }or {@link * NotificationCompat#PRIORITY_MIN}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ NotificationCompat.PRIORITY_DEFAULT, diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java index 8127fd617a..e429f5bfa0 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java @@ -67,6 +67,7 @@ import com.google.android.exoplayer2.util.ErrorMessageProvider; import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoListener; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -249,6 +250,7 @@ public class PlayerView extends FrameLayout { * Determines when the buffering view is shown. One of {@link #SHOW_BUFFERING_NEVER}, {@link * #SHOW_BUFFERING_WHEN_PLAYING} or {@link #SHOW_BUFFERING_ALWAYS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({SHOW_BUFFERING_NEVER, SHOW_BUFFERING_WHEN_PLAYING, SHOW_BUFFERING_ALWAYS}) public @interface ShowBuffering {} From 5d5b641c1dec222eb4e504391fc021c35d73f6fd Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 28 Sep 2018 13:33:23 -0700 Subject: [PATCH 035/832] Extend physical display size workaround for ATVs Extend Sony workaround up to and including Oreo. Due to a platform issue the Display API couldn't report 1080p UI and 4k SurfaceView support until Oreo. Since Oreo it is still common for devices to misreport their display sizes via Display, so this change switches to using system properties up to and including Pie. On Pie treble may prevent writing sys.display-size so check for vendor.display-size instead. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214987203 --- .../audio/MediaCodecAudioRenderer.java | 22 ++--- .../google/android/exoplayer2/util/Util.java | 86 ++++++++++++------- 2 files changed, 61 insertions(+), 47 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 89d2ee16b2..a68ee5b727 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.audio; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; -import android.content.pm.PackageManager; import android.media.MediaCodec; import android.media.MediaCrypto; import android.media.MediaFormat; @@ -707,21 +706,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media * be determined. */ private int getCodecMaxInputSize(MediaCodecInfo codecInfo, Format format) { - if (Util.SDK_INT < 24 && "OMX.google.raw.decoder".equals(codecInfo.name)) { - // OMX.google.raw.decoder didn't resize its output buffers correctly prior to N, so there's no - // point requesting a non-default input size. Doing so may cause a native crash, where-as not - // doing so will cause a more controlled failure when attempting to fill an input buffer. See: - // https://github.com/google/ExoPlayer/issues/4057. - boolean needsRawDecoderWorkaround = true; - if (Util.SDK_INT == 23) { - PackageManager packageManager = context.getPackageManager(); - if (packageManager != null - && packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { - // The workaround is not required for AndroidTV devices running M. - needsRawDecoderWorkaround = false; - } - } - if (needsRawDecoderWorkaround) { + if ("OMX.google.raw.decoder".equals(codecInfo.name)) { + // OMX.google.raw.decoder didn't resize its output buffers correctly prior to N, except on + // Android TV running M, so there's no point requesting a non-default input size. Doing so may + // cause a native crash, whereas not doing so will cause a more controlled failure when + // attempting to fill an input buffer. See: https://github.com/google/ExoPlayer/issues/4057. + if (Util.SDK_INT < 24 && !(Util.SDK_INT == 23 && Util.isAndroidTv(context))) { return Format.NO_VALUE; } } 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 eafb05226c..33c8dc5643 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 @@ -1728,6 +1728,18 @@ public final class Util { } } + /** + * Returns whether the device is an Android TV. + * + * @param context Any context. + * @return Whether the device is an Android TV. + */ + public static boolean isAndroidTv(Context context) { + PackageManager packageManager = context.getPackageManager(); + return packageManager != null + && packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK); + } + /** * Gets the physical size of the default display, in pixels. * @@ -1747,43 +1759,42 @@ public final class Util { * @return The physical display size, in pixels. */ public static Point getPhysicalDisplaySize(Context context, Display display) { - if (Util.SDK_INT < 25 && display.getDisplayId() == Display.DEFAULT_DISPLAY) { - // Before API 25 the Display object does not provide a working way to identify Android TVs - // that can show 4k resolution in a SurfaceView, so check for supported devices here. - if ("Sony".equals(Util.MANUFACTURER) && Util.MODEL.startsWith("BRAVIA") + if (Util.SDK_INT <= 28 + && display.getDisplayId() == Display.DEFAULT_DISPLAY + && Util.isAndroidTv(context)) { + // On Android TVs it is common for the UI to be configured for a lower resolution than + // SurfaceViews can output. Before API 26 the Display object does not provide a way to + // identify this case, and up to and including API 28 many devices still do not correctly set + // their hardware compositor output size. + + // Sony Android TVs advertise support for 4k output via a system feature. + if ("Sony".equals(Util.MANUFACTURER) + && Util.MODEL.startsWith("BRAVIA") && context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) { return new Point(3840, 2160); - } else if (("NVIDIA".equals(Util.MANUFACTURER) && Util.MODEL.contains("SHIELD")) - || ("philips".equals(Util.toLowerInvariant(Util.MANUFACTURER)) - && (Util.MODEL.startsWith("QM1") - || Util.MODEL.equals("QV151E") - || Util.MODEL.equals("TPM171E")))) { - // Attempt to read sys.display-size. - String sysDisplaySize = null; + } + + // Otherwise check the system property for display size. From API 28 treble may prevent the + // system from writing sys.display-size so we check vendor.display-size instead. + String displaySize = + Util.SDK_INT < 28 + ? getSystemProperty("sys.display-size") + : getSystemProperty("vendor.display-size"); + // If we managed to read the display size, attempt to parse it. + if (!TextUtils.isEmpty(displaySize)) { try { - @SuppressLint("PrivateApi") - Class systemProperties = Class.forName("android.os.SystemProperties"); - Method getMethod = systemProperties.getMethod("get", String.class); - sysDisplaySize = (String) getMethod.invoke(systemProperties, "sys.display-size"); - } catch (Exception e) { - Log.e(TAG, "Failed to read sys.display-size", e); - } - // If we managed to read sys.display-size, attempt to parse it. - if (!TextUtils.isEmpty(sysDisplaySize)) { - try { - String[] sysDisplaySizeParts = split(sysDisplaySize.trim(), "x"); - if (sysDisplaySizeParts.length == 2) { - int width = Integer.parseInt(sysDisplaySizeParts[0]); - int height = Integer.parseInt(sysDisplaySizeParts[1]); - if (width > 0 && height > 0) { - return new Point(width, height); - } + String[] displaySizeParts = split(displaySize.trim(), "x"); + if (displaySizeParts.length == 2) { + int width = Integer.parseInt(displaySizeParts[0]); + int height = Integer.parseInt(displaySizeParts[1]); + if (width > 0 && height > 0) { + return new Point(width, height); } - } catch (NumberFormatException e) { - // Do nothing. } - Log.e(TAG, "Invalid sys.display-size: " + sysDisplaySize); + } catch (NumberFormatException e) { + // Do nothing. } + Log.e(TAG, "Invalid display size: " + displaySize); } } @@ -1800,6 +1811,19 @@ public final class Util { return displaySize; } + @Nullable + private static String getSystemProperty(String name) { + try { + @SuppressLint("PrivateApi") + Class systemProperties = Class.forName("android.os.SystemProperties"); + Method getMethod = systemProperties.getMethod("get", String.class); + return (String) getMethod.invoke(systemProperties, name); + } catch (Exception e) { + Log.e(TAG, "Failed to read system property " + name, e); + return null; + } + } + @TargetApi(23) private static void getDisplaySizeV23(Display display, Point outSize) { Display.Mode mode = display.getMode(); From f59f557704cb0406a4bfa4e542b4914e977171d2 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 1 Oct 2018 01:50:07 -0700 Subject: [PATCH 036/832] Use recommended way of checking for TV Switch to the recommended way of checking whether the app is running on a TV device. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215177587 --- .../audio/MediaCodecAudioRenderer.java | 2 +- .../google/android/exoplayer2/util/Util.java | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index a68ee5b727..59a077dff8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -711,7 +711,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media // Android TV running M, so there's no point requesting a non-default input size. Doing so may // cause a native crash, whereas not doing so will cause a more controlled failure when // attempting to fill an input buffer. See: https://github.com/google/ExoPlayer/issues/4057. - if (Util.SDK_INT < 24 && !(Util.SDK_INT == 23 && Util.isAndroidTv(context))) { + if (Util.SDK_INT < 24 && !(Util.SDK_INT == 23 && Util.isTv(context))) { return Format.NO_VALUE; } } 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 33c8dc5643..2d3a0aeb78 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 @@ -15,16 +15,20 @@ */ package com.google.android.exoplayer2.util; +import static android.content.Context.UI_MODE_SERVICE; + import android.Manifest.permission; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; +import android.app.UiModeManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Configuration; import android.graphics.Point; import android.media.AudioFormat; import android.net.ConnectivityManager; @@ -173,7 +177,7 @@ public final class Util { return false; } for (Uri uri : uris) { - if (Util.isLocalFileUri(uri)) { + if (isLocalFileUri(uri)) { if (activity.checkSelfPermission(permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { activity.requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE}, 0); @@ -242,7 +246,7 @@ public final class Util { */ public static boolean contains(Object[] items, Object item) { for (Object arrayItem : items) { - if (Util.areEqual(arrayItem, item)) { + if (areEqual(arrayItem, item)) { return true; } } @@ -1352,7 +1356,7 @@ public final class Util { * @return The derived {@link UUID}, or {@code null} if one could not be derived. */ public static @Nullable UUID getDrmUuid(String drmScheme) { - switch (Util.toLowerInvariant(drmScheme)) { + switch (toLowerInvariant(drmScheme)) { case "widevine": return C.WIDEVINE_UUID; case "playready": @@ -1402,7 +1406,7 @@ public final class Util { */ @C.ContentType public static int inferContentType(String fileName) { - fileName = Util.toLowerInvariant(fileName); + fileName = toLowerInvariant(fileName); if (fileName.endsWith(".mpd")) { return C.TYPE_DASH; } else if (fileName.endsWith(".m3u8")) { @@ -1565,7 +1569,7 @@ public final class Util { * and is not declared to be thrown. */ public static void sneakyThrow(Throwable t) { - Util.sneakyThrowInternal(t); + sneakyThrowInternal(t); } @SuppressWarnings("unchecked") @@ -1729,15 +1733,17 @@ public final class Util { } /** - * Returns whether the device is an Android TV. + * Returns whether the app is running on a TV device. * * @param context Any context. - * @return Whether the device is an Android TV. + * @return Whether the app is running on a TV device. */ - public static boolean isAndroidTv(Context context) { - PackageManager packageManager = context.getPackageManager(); - return packageManager != null - && packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK); + public static boolean isTv(Context context) { + // See https://developer.android.com/training/tv/start/hardware.html#runtime-check. + UiModeManager uiModeManager = + (UiModeManager) context.getApplicationContext().getSystemService(UI_MODE_SERVICE); + return uiModeManager != null + && uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; } /** @@ -1759,9 +1765,7 @@ public final class Util { * @return The physical display size, in pixels. */ public static Point getPhysicalDisplaySize(Context context, Display display) { - if (Util.SDK_INT <= 28 - && display.getDisplayId() == Display.DEFAULT_DISPLAY - && Util.isAndroidTv(context)) { + if (Util.SDK_INT <= 28 && display.getDisplayId() == Display.DEFAULT_DISPLAY && isTv(context)) { // On Android TVs it is common for the UI to be configured for a lower resolution than // SurfaceViews can output. Before API 26 the Display object does not provide a way to // identify this case, and up to and including API 28 many devices still do not correctly set From d97d289b6b3d2021bdb0340c47083079453bc2f0 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Oct 2018 05:45:24 -0700 Subject: [PATCH 037/832] Clarify and improve TrackSelection .enable() and .disable() calls. These two methods are meant to indicate to the track selection that it's started or stopped being used. This is helpful to schedule background tasks related to track selection (e.g. register network change listeners etc.). This intention is not clearly stated in the method docs. Also, all track selections of all prebuffered periods stay enabled in parallel at the moment. As the whole purpose of these methods is to know whether dynamic updates via updateSelectedTrack may happen, it's better to only enable track selections of the current loading media period. That's similar to how we always forward the loading track selections to the LoadControl. This change: 1. Improves the JavaDoc of TrackSelection. 2. Disables track selections if loading moves to another period. 3. Reenables track selection if loading moves back to a previous period. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215199987 --- .../exoplayer2/ExoPlayerImplInternal.java | 28 +++++----- .../android/exoplayer2/MediaPeriodHolder.java | 54 ++++++++++++------- .../android/exoplayer2/MediaPeriodQueue.java | 38 ++++++------- .../trackselection/TrackSelection.java | 21 +++++--- 4 files changed, 84 insertions(+), 57 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 1d5125384f..df822a3867 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -1006,7 +1006,7 @@ import java.util.Collections; // The track reselection didn't affect any period that has been read. selectionsChangedForReadPeriod = false; } - periodHolder = periodHolder.next; + periodHolder = periodHolder.getNext(); } if (selectionsChangedForReadPeriod) { @@ -1078,7 +1078,7 @@ import java.util.Collections; } } } - periodHolder = periodHolder.next; + periodHolder = periodHolder.getNext(); } } @@ -1107,11 +1107,12 @@ import java.util.Collections; private boolean isTimelineReady() { MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); + MediaPeriodHolder nextPeriodHolder = playingPeriodHolder.getNext(); long playingPeriodDurationUs = playingPeriodHolder.info.durationUs; return playingPeriodDurationUs == C.TIME_UNSET || playbackInfo.positionUs < playingPeriodDurationUs - || (playingPeriodHolder.next != null - && (playingPeriodHolder.next.prepared || playingPeriodHolder.next.info.id.isAd())); + || (nextPeriodHolder != null + && (nextPeriodHolder.prepared || nextPeriodHolder.info.id.isAd())); } private void maybeThrowSourceInfoRefreshError() throws IOException { @@ -1130,8 +1131,9 @@ import java.util.Collections; private void maybeThrowPeriodPrepareError() throws IOException { MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod(); MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); - if (loadingPeriodHolder != null && !loadingPeriodHolder.prepared - && (readingPeriodHolder == null || readingPeriodHolder.next == loadingPeriodHolder)) { + if (loadingPeriodHolder != null + && !loadingPeriodHolder.prepared + && (readingPeriodHolder == null || readingPeriodHolder.getNext() == loadingPeriodHolder)) { for (Renderer renderer : enabledRenderers) { if (!renderer.hasReadStreamToEnd()) { return; @@ -1241,8 +1243,8 @@ import java.util.Collections; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(newPeriodUid, contentPositionUs); if (periodHolder != null) { // Update the new playing media period info if it already exists. - while (periodHolder.next != null) { - periodHolder = periodHolder.next; + while (periodHolder.getNext() != null) { + periodHolder = periodHolder.getNext(); if (periodHolder.info.id.equals(periodId)) { periodHolder.info = queue.getUpdatedMediaPeriodInfo(periodHolder.info); } @@ -1404,7 +1406,7 @@ import java.util.Collections; boolean advancedPlayingPeriod = false; while (playWhenReady && playingPeriodHolder != readingPeriodHolder - && rendererPositionUs >= playingPeriodHolder.next.getStartPositionRendererTime()) { + && rendererPositionUs >= playingPeriodHolder.getNext().getStartPositionRendererTime()) { // All enabled renderers' streams have been read to the end, and the playback position reached // the end of the playing period, so advance playback to the next period. if (advancedPlayingPeriod) { @@ -1440,7 +1442,7 @@ import java.util.Collections; } // Advance the reading period if necessary. - if (readingPeriodHolder.next == null) { + if (readingPeriodHolder.getNext() == null) { // We don't have a successor to advance the reading period to. return; } @@ -1455,7 +1457,7 @@ import java.util.Collections; } } - if (!readingPeriodHolder.next.prepared) { + if (!readingPeriodHolder.getNext().prepared) { // The successor is not prepared yet. maybeThrowPeriodPrepareError(); return; @@ -1655,8 +1657,8 @@ import java.util.Collections; private boolean rendererWaitingForNextStream(Renderer renderer) { MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); - return readingPeriodHolder.next != null && readingPeriodHolder.next.prepared - && renderer.hasReadStreamToEnd(); + MediaPeriodHolder nextPeriodHolder = readingPeriodHolder.getNext(); + return nextPeriodHolder != null && nextPeriodHolder.prepared && renderer.hasReadStreamToEnd(); } private void handleLoadingMediaPeriodChanged(boolean loadingTrackSelectionChanged) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index a59ee61088..526ad63768 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.source.ClippingMediaPeriod; import com.google.android.exoplayer2.source.EmptySampleStream; import com.google.android.exoplayer2.source.MediaPeriod; @@ -42,7 +43,6 @@ import com.google.android.exoplayer2.util.Log; public boolean prepared; public boolean hasEnabledTracks; public MediaPeriodInfo info; - public MediaPeriodHolder next; public TrackGroupArray trackGroups; public TrackSelectorResult trackSelectorResult; @@ -50,6 +50,7 @@ import com.google.android.exoplayer2.util.Log; private final TrackSelector trackSelector; private final MediaSource mediaSource; + private MediaPeriodHolder next; private long rendererPositionOffsetUs; private TrackSelectorResult periodTrackSelectorResult; @@ -190,7 +191,9 @@ import com.google.android.exoplayer2.util.Log; // Undo the effect of previous call to associate no-sample renderers with empty tracks // so the mediaPeriod receives back whatever it sent us before. disassociateNoSampleRenderersWithEmptySampleStream(sampleStreams); - updatePeriodTrackSelectorResult(trackSelectorResult); + disableTrackSelectionsInResult(); + periodTrackSelectorResult = trackSelectorResult; + enableTrackSelectionsInResult(); // Disable streams on the period and get new streams for updated/newly-enabled tracks. TrackSelectionArray trackSelections = trackSelectorResult.selections; positionUs = @@ -219,7 +222,8 @@ import com.google.android.exoplayer2.util.Log; } public void release() { - updatePeriodTrackSelectorResult(null); + disableTrackSelectionsInResult(); + periodTrackSelectorResult = null; try { if (info.id.endPositionUs != C.TIME_END_OF_SOURCE) { mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); @@ -232,30 +236,40 @@ import com.google.android.exoplayer2.util.Log; } } - private void updatePeriodTrackSelectorResult(TrackSelectorResult trackSelectorResult) { - if (periodTrackSelectorResult != null) { - disableTrackSelectionsInResult(periodTrackSelectorResult); - } - periodTrackSelectorResult = trackSelectorResult; - if (periodTrackSelectorResult != null) { - enableTrackSelectionsInResult(periodTrackSelectorResult); + public void setNext(@Nullable MediaPeriodHolder nextMediaPeriodHolder) { + if (nextMediaPeriodHolder == next) { + return; } + disableTrackSelectionsInResult(); + next = nextMediaPeriodHolder; + enableTrackSelectionsInResult(); } - private void enableTrackSelectionsInResult(TrackSelectorResult trackSelectorResult) { - for (int i = 0; i < trackSelectorResult.length; i++) { - boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i); - TrackSelection trackSelection = trackSelectorResult.selections.get(i); + @Nullable + public MediaPeriodHolder getNext() { + return next; + } + + private void enableTrackSelectionsInResult() { + if (!isLoadingMediaPeriod() || periodTrackSelectorResult == null) { + return; + } + for (int i = 0; i < periodTrackSelectorResult.length; i++) { + boolean rendererEnabled = periodTrackSelectorResult.isRendererEnabled(i); + TrackSelection trackSelection = periodTrackSelectorResult.selections.get(i); if (rendererEnabled && trackSelection != null) { trackSelection.enable(); } } } - private void disableTrackSelectionsInResult(TrackSelectorResult trackSelectorResult) { - for (int i = 0; i < trackSelectorResult.length; i++) { - boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i); - TrackSelection trackSelection = trackSelectorResult.selections.get(i); + private void disableTrackSelectionsInResult() { + if (!isLoadingMediaPeriod() || periodTrackSelectorResult == null) { + return; + } + for (int i = 0; i < periodTrackSelectorResult.length; i++) { + boolean rendererEnabled = periodTrackSelectorResult.isRendererEnabled(i); + TrackSelection trackSelection = periodTrackSelectorResult.selections.get(i); if (rendererEnabled && trackSelection != null) { trackSelection.disable(); } @@ -286,4 +300,8 @@ import com.google.android.exoplayer2.util.Log; } } } + + private boolean isLoadingMediaPeriod() { + return next == null; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 2edf7bb8c6..6370299334 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -156,7 +156,7 @@ import com.google.android.exoplayer2.util.Assertions; info); if (loading != null) { Assertions.checkState(hasPlayingPeriod()); - loading.next = newPeriodHolder; + loading.setNext(newPeriodHolder); } oldFrontPeriodUid = null; loading = newPeriodHolder; @@ -207,8 +207,8 @@ import com.google.android.exoplayer2.util.Assertions; * @return The updated reading period holder. */ public MediaPeriodHolder advanceReadingPeriod() { - Assertions.checkState(reading != null && reading.next != null); - reading = reading.next; + Assertions.checkState(reading != null && reading.getNext() != null); + reading = reading.getNext(); return reading; } @@ -222,7 +222,7 @@ import com.google.android.exoplayer2.util.Assertions; public MediaPeriodHolder advancePlayingPeriod() { if (playing != null) { if (playing == reading) { - reading = playing.next; + reading = playing.getNext(); } playing.release(); length--; @@ -231,7 +231,7 @@ import com.google.android.exoplayer2.util.Assertions; oldFrontPeriodUid = playing.uid; oldFrontPeriodWindowSequenceNumber = playing.info.id.windowSequenceNumber; } - playing = playing.next; + playing = playing.getNext(); } else { playing = loading; reading = loading; @@ -251,8 +251,8 @@ import com.google.android.exoplayer2.util.Assertions; Assertions.checkState(mediaPeriodHolder != null); boolean removedReading = false; loading = mediaPeriodHolder; - while (mediaPeriodHolder.next != null) { - mediaPeriodHolder = mediaPeriodHolder.next; + while (mediaPeriodHolder.getNext() != null) { + mediaPeriodHolder = mediaPeriodHolder.getNext(); if (mediaPeriodHolder == reading) { reading = playing; removedReading = true; @@ -260,7 +260,7 @@ import com.google.android.exoplayer2.util.Assertions; mediaPeriodHolder.release(); length--; } - loading.next = null; + loading.setNext(null); return removedReading; } @@ -337,7 +337,7 @@ import com.google.android.exoplayer2.util.Assertions; } previousPeriodHolder = periodHolder; - periodHolder = periodHolder.next; + periodHolder = periodHolder.getNext(); } return true; } @@ -439,7 +439,7 @@ import com.google.android.exoplayer2.util.Assertions; // Reuse window sequence number of first exact period match. return mediaPeriodHolder.info.id.windowSequenceNumber; } - mediaPeriodHolder = mediaPeriodHolder.next; + mediaPeriodHolder = mediaPeriodHolder.getNext(); } mediaPeriodHolder = getFrontPeriod(); while (mediaPeriodHolder != null) { @@ -451,7 +451,7 @@ import com.google.android.exoplayer2.util.Assertions; return mediaPeriodHolder.info.id.windowSequenceNumber; } } - mediaPeriodHolder = mediaPeriodHolder.next; + mediaPeriodHolder = mediaPeriodHolder.getNext(); } // If no match is found, create new sequence number. return nextWindowSequenceNumber++; @@ -482,19 +482,20 @@ import com.google.android.exoplayer2.util.Assertions; int nextPeriodIndex = timeline.getNextPeriodIndex( currentPeriodIndex, period, window, repeatMode, shuffleModeEnabled); - while (lastValidPeriodHolder.next != null + while (lastValidPeriodHolder.getNext() != null && !lastValidPeriodHolder.info.isLastInTimelinePeriod) { - lastValidPeriodHolder = lastValidPeriodHolder.next; + lastValidPeriodHolder = lastValidPeriodHolder.getNext(); } - if (nextPeriodIndex == C.INDEX_UNSET || lastValidPeriodHolder.next == null) { + MediaPeriodHolder nextMediaPeriodHolder = lastValidPeriodHolder.getNext(); + if (nextPeriodIndex == C.INDEX_UNSET || nextMediaPeriodHolder == null) { break; } - int nextPeriodHolderPeriodIndex = timeline.getIndexOfPeriod(lastValidPeriodHolder.next.uid); + int nextPeriodHolderPeriodIndex = timeline.getIndexOfPeriod(nextMediaPeriodHolder.uid); if (nextPeriodHolderPeriodIndex != nextPeriodIndex) { break; } - lastValidPeriodHolder = lastValidPeriodHolder.next; + lastValidPeriodHolder = nextMediaPeriodHolder; currentPeriodIndex = nextPeriodIndex; } @@ -567,8 +568,9 @@ import com.google.android.exoplayer2.util.Assertions; } nextPeriodUid = defaultPosition.first; startPositionUs = defaultPosition.second; - if (mediaPeriodHolder.next != null && mediaPeriodHolder.next.uid.equals(nextPeriodUid)) { - windowSequenceNumber = mediaPeriodHolder.next.info.id.windowSequenceNumber; + MediaPeriodHolder nextMediaPeriodHolder = mediaPeriodHolder.getNext(); + if (nextMediaPeriodHolder != null && nextMediaPeriodHolder.uid.equals(nextPeriodUid)) { + windowSequenceNumber = nextMediaPeriodHolder.info.id.windowSequenceNumber; } else { windowSequenceNumber = nextWindowSequenceNumber++; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java index 78d052ac3c..a7c0658708 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java @@ -29,8 +29,9 @@ import java.util.List; * TrackGroup}, and a possibly varying individual selected track from the subset. * *

    Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual - * selected track may change as a result of calling {@link #updateSelectedTrack(long, long, long, - * List, MediaChunkIterator[])}. + * selected track may change dynamically as a result of calling {@link #updateSelectedTrack(long, + * long, long, List, MediaChunkIterator[])} or {@link #evaluateQueueSize(long, List)}. This only + * happens between calls to {@link #enable()} and {@link #disable()}. */ public interface TrackSelection { @@ -53,16 +54,20 @@ public interface TrackSelection { } /** - * Enables the track selection. - *

    - * This method may not be called when the track selection is already enabled. + * Enables the track selection. Dynamic changes via {@link #updateSelectedTrack(long, long, long, + * List, MediaChunkIterator[])} or {@link #evaluateQueueSize(long, List)} will only happen after + * this call. + * + *

    This method may not be called when the track selection is already enabled. */ void enable(); /** - * Disables this track selection. - *

    - * This method may only be called when the track selection is already enabled. + * Disables this track selection. No further dynamic changes via {@link #updateSelectedTrack(long, + * long, long, List, MediaChunkIterator[])} or {@link #evaluateQueueSize(long, List)} will happen + * after this call. + * + *

    This method may only be called when the track selection is already enabled. */ void disable(); From 90ca3716aabb38a813e68c56cd2d416368256b9e Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Oct 2018 06:44:49 -0700 Subject: [PATCH 038/832] Improve MediaPeriodHolder documentation and member access. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215205796 --- .../exoplayer2/ExoPlayerImplInternal.java | 46 ++-- .../android/exoplayer2/MediaPeriodHolder.java | 198 ++++++++++++++---- .../source/ExtractorMediaPeriod.java | 2 +- .../exoplayer2/source/MediaPeriod.java | 5 +- .../trackselection/TrackSelectorResult.java | 5 +- 5 files changed, 187 insertions(+), 69 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index df822a3867..6810c86558 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -993,12 +993,14 @@ import java.util.Collections; MediaPeriodHolder periodHolder = queue.getPlayingPeriod(); MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); boolean selectionsChangedForReadPeriod = true; + TrackSelectorResult newTrackSelectorResult = null; while (true) { if (periodHolder == null || !periodHolder.prepared) { // The reselection did not change any prepared periods. return; } - if (periodHolder.selectTracks(playbackSpeed, playbackInfo.timeline)) { + newTrackSelectorResult = periodHolder.selectTracks(playbackSpeed, playbackInfo.timeline); + if (newTrackSelectorResult != null) { // Selected tracks have changed for this period. break; } @@ -1017,7 +1019,7 @@ import java.util.Collections; boolean[] streamResetFlags = new boolean[renderers.length]; long periodPositionUs = playingPeriodHolder.applyTrackSelection( - playbackInfo.positionUs, recreateStreams, streamResetFlags); + newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags); if (playbackInfo.playbackState != Player.STATE_ENDED && periodPositionUs != playbackInfo.positionUs) { playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs, @@ -1047,7 +1049,7 @@ import java.util.Collections; } playbackInfo = playbackInfo.copyWithTrackInfo( - playingPeriodHolder.trackGroups, playingPeriodHolder.trackSelectorResult); + playingPeriodHolder.getTrackGroups(), playingPeriodHolder.getTrackSelectorResult()); enableRenderers(rendererWasEnabledFlags, enabledRendererCount); } else { // Release and re-prepare/buffer periods after the one whose selection changed. @@ -1056,7 +1058,7 @@ import java.util.Collections; long loadingPeriodPositionUs = Math.max( periodHolder.info.startPositionUs, periodHolder.toPeriodTime(rendererPositionUs)); - periodHolder.applyTrackSelection(loadingPeriodPositionUs, false); + periodHolder.applyTrackSelection(newTrackSelectorResult, loadingPeriodPositionUs, false); } } handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ true); @@ -1069,13 +1071,11 @@ import java.util.Collections; private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) { MediaPeriodHolder periodHolder = queue.getFrontPeriod(); - while (periodHolder != null) { - if (periodHolder.trackSelectorResult != null) { - TrackSelection[] trackSelections = periodHolder.trackSelectorResult.selections.getAll(); - for (TrackSelection trackSelection : trackSelections) { - if (trackSelection != null) { - trackSelection.onPlaybackSpeed(playbackSpeed); - } + while (periodHolder != null && periodHolder.prepared) { + TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll(); + for (TrackSelection trackSelection : trackSelections) { + if (trackSelection != null) { + trackSelection.onPlaybackSpeed(playbackSpeed); } } periodHolder = periodHolder.getNext(); @@ -1463,9 +1463,9 @@ import java.util.Collections; return; } - TrackSelectorResult oldTrackSelectorResult = readingPeriodHolder.trackSelectorResult; + TrackSelectorResult oldTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult(); readingPeriodHolder = queue.advanceReadingPeriod(); - TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.trackSelectorResult; + TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult(); boolean initialDiscontinuity = readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET; @@ -1536,7 +1536,7 @@ import java.util.Collections; loadingPeriodHolder.handlePrepared( mediaClock.getPlaybackParameters().speed, playbackInfo.timeline); updateLoadControlTrackSelection( - loadingPeriodHolder.trackGroups, loadingPeriodHolder.trackSelectorResult); + loadingPeriodHolder.getTrackGroups(), loadingPeriodHolder.getTrackSelectorResult()); if (!queue.hasPlayingPeriod()) { // This is the first prepared period, so start playing it. MediaPeriodHolder playingPeriodHolder = queue.advancePlayingPeriod(); @@ -1596,11 +1596,11 @@ import java.util.Collections; for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; - if (newPlayingPeriodHolder.trackSelectorResult.isRendererEnabled(i)) { + if (newPlayingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)) { enabledRendererCount++; } if (rendererWasEnabledFlags[i] - && (!newPlayingPeriodHolder.trackSelectorResult.isRendererEnabled(i) + && (!newPlayingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i) || (renderer.isCurrentStreamFinal() && renderer.getStream() == oldPlayingPeriodHolder.sampleStreams[i]))) { // The renderer should be disabled before playing the next period, either because it's not @@ -1611,7 +1611,8 @@ import java.util.Collections; } playbackInfo = playbackInfo.copyWithTrackInfo( - newPlayingPeriodHolder.trackGroups, newPlayingPeriodHolder.trackSelectorResult); + newPlayingPeriodHolder.getTrackGroups(), + newPlayingPeriodHolder.getTrackSelectorResult()); enableRenderers(rendererWasEnabledFlags, enabledRendererCount); } @@ -1621,7 +1622,7 @@ import java.util.Collections; int enabledRendererCount = 0; MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); for (int i = 0; i < renderers.length; i++) { - if (playingPeriodHolder.trackSelectorResult.isRendererEnabled(i)) { + if (playingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)) { enableRenderer(i, rendererWasEnabledFlags[i], enabledRendererCount++); } } @@ -1634,10 +1635,10 @@ import java.util.Collections; Renderer renderer = renderers[rendererIndex]; enabledRenderers[enabledRendererIndex] = renderer; if (renderer.getState() == Renderer.STATE_DISABLED) { + TrackSelectorResult trackSelectorResult = playingPeriodHolder.getTrackSelectorResult(); RendererConfiguration rendererConfiguration = - playingPeriodHolder.trackSelectorResult.rendererConfigurations[rendererIndex]; - TrackSelection newSelection = playingPeriodHolder.trackSelectorResult.selections.get( - rendererIndex); + trackSelectorResult.rendererConfigurations[rendererIndex]; + TrackSelection newSelection = trackSelectorResult.selections.get(rendererIndex); Format[] formats = getFormats(newSelection); // The renderer needs enabling with its new track selection. boolean playing = playWhenReady && playbackInfo.playbackState == Player.STATE_READY; @@ -1674,7 +1675,8 @@ import java.util.Collections; && loadingMediaPeriodHolder != null && loadingMediaPeriodHolder.prepared) { updateLoadControlTrackSelection( - loadingMediaPeriodHolder.trackGroups, loadingMediaPeriodHolder.trackSelectorResult); + loadingMediaPeriodHolder.getTrackGroups(), + loadingMediaPeriodHolder.getTrackSelectorResult()); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index 526ad63768..3ba96d2b80 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -29,30 +29,38 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; +import org.checkerframework.checker.nullness.compatqual.NullableType; /** Holds a {@link MediaPeriod} with information required to play it as part of a timeline. */ /* package */ final class MediaPeriodHolder { private static final String TAG = "MediaPeriodHolder"; + /** The {@link MediaPeriod} wrapped by this class. */ public final MediaPeriod mediaPeriod; + /** The unique timeline period identifier the media period belongs to. */ public final Object uid; - public final SampleStream[] sampleStreams; - public final boolean[] mayRetainStreamFlags; + /** + * The sample streams for each renderer associated with this period. May contain null elements. + */ + public final @NullableType SampleStream[] sampleStreams; + /** Whether the media period has finished preparing. */ public boolean prepared; + /** Whether any of the tracks of this media period are enabled. */ public boolean hasEnabledTracks; + /** {@link MediaPeriodInfo} about this media period. */ public MediaPeriodInfo info; - public TrackGroupArray trackGroups; - public TrackSelectorResult trackSelectorResult; + private final boolean[] mayRetainStreamFlags; private final RendererCapabilities[] rendererCapabilities; private final TrackSelector trackSelector; private final MediaSource mediaSource; - private MediaPeriodHolder next; + @Nullable private MediaPeriodHolder next; + @Nullable private TrackGroupArray trackGroups; + @Nullable private TrackSelectorResult trackSelectorResult; private long rendererPositionOffsetUs; - private TrackSelectorResult periodTrackSelectorResult; /** * Creates a new holder with information required to play it as part of a timeline. @@ -76,7 +84,7 @@ import com.google.android.exoplayer2.util.Log; this.rendererPositionOffsetUs = rendererPositionOffsetUs - info.startPositionUs; this.trackSelector = trackSelector; this.mediaSource = mediaSource; - this.uid = Assertions.checkNotNull(info.id.periodUid); + this.uid = info.id.periodUid; this.info = info; sampleStreams = new SampleStream[rendererCapabilities.length]; mayRetainStreamFlags = new boolean[rendererCapabilities.length]; @@ -92,31 +100,38 @@ import com.google.android.exoplayer2.util.Log; this.mediaPeriod = mediaPeriod; } + /** + * Converts time relative to the start of the period to the respective renderer time using {@link + * #getRendererOffset()}, in microseconds. + */ public long toRendererTime(long periodTimeUs) { return periodTimeUs + getRendererOffset(); } + /** + * Converts renderer time to the respective time relative to the start of the period using {@link + * #getRendererOffset()}, in microseconds. + */ public long toPeriodTime(long rendererTimeUs) { return rendererTimeUs - getRendererOffset(); } + /** Returns the renderer time of the start of the period, in microseconds. */ public long getRendererOffset() { return rendererPositionOffsetUs; } + /** Returns start position of period in renderer time. */ public long getStartPositionRendererTime() { return info.startPositionUs + rendererPositionOffsetUs; } + /** Returns whether the period is fully buffered. */ public boolean isFullyBuffered() { return prepared && (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE); } - public long getDurationUs() { - return info.durationUs; - } - /** * Returns the buffered position in microseconds. If the period is buffered to the end then * {@link C#TIME_END_OF_SOURCE} is returned unless {@code convertEosToDuration} is true, in which @@ -137,65 +152,132 @@ import com.google.android.exoplayer2.util.Log; : bufferedPositionUs; } + /** + * Returns the next load time relative to the start of the period, or {@link C#TIME_END_OF_SOURCE} + * if loading has finished. + */ public long getNextLoadPositionUs() { return !prepared ? 0 : mediaPeriod.getNextLoadPositionUs(); } + /** + * Handles period preparation. + * + * @param playbackSpeed The current playback speed. + * @param timeline The current {@link Timeline}. + * @throws ExoPlaybackException If an error occurs during track selection. + */ public void handlePrepared(float playbackSpeed, Timeline timeline) throws ExoPlaybackException { prepared = true; trackGroups = mediaPeriod.getTrackGroups(); - selectTracks(playbackSpeed, timeline); - long newStartPositionUs = applyTrackSelection(info.startPositionUs, false); + TrackSelectorResult selectorResult = + Assertions.checkNotNull(selectTracks(playbackSpeed, timeline)); + long newStartPositionUs = + applyTrackSelection( + selectorResult, info.startPositionUs, /* forceRecreateStreams= */ false); rendererPositionOffsetUs += info.startPositionUs - newStartPositionUs; info = info.copyWithStartPositionUs(newStartPositionUs); } + /** + * Reevaluates the buffer of the media period at the given renderer position. Should only be + * called if this is the loading media period. + * + * @param rendererPositionUs The playing position in renderer time, in microseconds. + */ public void reevaluateBuffer(long rendererPositionUs) { + Assertions.checkState(isLoadingMediaPeriod()); if (prepared) { mediaPeriod.reevaluateBuffer(toPeriodTime(rendererPositionUs)); } } + /** + * Continues loading the media period at the given renderer position. Should only be called if + * this is the loading media period. + * + * @param rendererPositionUs The load position in renderer time, in microseconds. + */ public void continueLoading(long rendererPositionUs) { + Assertions.checkState(isLoadingMediaPeriod()); long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs); mediaPeriod.continueLoading(loadingPeriodPositionUs); } - public boolean selectTracks(float playbackSpeed, Timeline timeline) throws ExoPlaybackException { + /** + * Selects tracks for the period and returns the new result if the selection changed. Must only be + * called if {@link #prepared} is {@code true}. + * + * @param playbackSpeed The current playback speed. + * @param timeline The current {@link Timeline}. + * @return The {@link TrackSelectorResult} if the result changed. Or null if nothing changed. + * @throws ExoPlaybackException If an error occurs during track selection. + */ + @Nullable + public TrackSelectorResult selectTracks(float playbackSpeed, Timeline timeline) + throws ExoPlaybackException { TrackSelectorResult selectorResult = - trackSelector.selectTracks(rendererCapabilities, trackGroups, info.id, timeline); - if (selectorResult.isEquivalent(periodTrackSelectorResult)) { - return false; + trackSelector.selectTracks(rendererCapabilities, getTrackGroups(), info.id, timeline); + if (selectorResult.isEquivalent(trackSelectorResult)) { + return null; } - trackSelectorResult = selectorResult; - for (TrackSelection trackSelection : trackSelectorResult.selections.getAll()) { + for (TrackSelection trackSelection : selectorResult.selections.getAll()) { if (trackSelection != null) { trackSelection.onPlaybackSpeed(playbackSpeed); } } - return true; - } - - public long applyTrackSelection(long positionUs, boolean forceRecreateStreams) { - return applyTrackSelection( - positionUs, forceRecreateStreams, new boolean[rendererCapabilities.length]); + return selectorResult; } + /** + * Applies a {@link TrackSelectorResult} to the period. + * + * @param trackSelectorResult The {@link TrackSelectorResult} to apply. + * @param positionUs The position relative to the start of the period at which to apply the new + * track selections, in microseconds. + * @param forceRecreateStreams Whether all streams are forced to be recreated. + * @return The actual position relative to the start of the period at which the new track + * selections are applied. + */ public long applyTrackSelection( - long positionUs, boolean forceRecreateStreams, boolean[] streamResetFlags) { - for (int i = 0; i < trackSelectorResult.length; i++) { + TrackSelectorResult trackSelectorResult, long positionUs, boolean forceRecreateStreams) { + return applyTrackSelection( + trackSelectorResult, + positionUs, + forceRecreateStreams, + new boolean[rendererCapabilities.length]); + } + + /** + * Applies a {@link TrackSelectorResult} to the period. + * + * @param newTrackSelectorResult The {@link TrackSelectorResult} to apply. + * @param positionUs The position relative to the start of the period at which to apply the new + * track selections, in microseconds. + * @param forceRecreateStreams Whether all streams are forced to be recreated. + * @param streamResetFlags Will be populated to indicate which streams have been reset or were + * newly created. + * @return The actual position relative to the start of the period at which the new track + * selections are applied. + */ + public long applyTrackSelection( + TrackSelectorResult newTrackSelectorResult, + long positionUs, + boolean forceRecreateStreams, + boolean[] streamResetFlags) { + for (int i = 0; i < newTrackSelectorResult.length; i++) { mayRetainStreamFlags[i] = - !forceRecreateStreams && trackSelectorResult.isEquivalent(periodTrackSelectorResult, i); + !forceRecreateStreams && newTrackSelectorResult.isEquivalent(trackSelectorResult, i); } // Undo the effect of previous call to associate no-sample renderers with empty tracks // so the mediaPeriod receives back whatever it sent us before. disassociateNoSampleRenderersWithEmptySampleStream(sampleStreams); disableTrackSelectionsInResult(); - periodTrackSelectorResult = trackSelectorResult; + trackSelectorResult = newTrackSelectorResult; enableTrackSelectionsInResult(); // Disable streams on the period and get new streams for updated/newly-enabled tracks. - TrackSelectionArray trackSelections = trackSelectorResult.selections; + TrackSelectionArray trackSelections = newTrackSelectorResult.selections; positionUs = mediaPeriod.selectTracks( trackSelections.getAll(), @@ -209,7 +291,7 @@ import com.google.android.exoplayer2.util.Log; hasEnabledTracks = false; for (int i = 0; i < sampleStreams.length; i++) { if (sampleStreams[i] != null) { - Assertions.checkState(trackSelectorResult.isRendererEnabled(i)); + Assertions.checkState(newTrackSelectorResult.isRendererEnabled(i)); // hasEnabledTracks should be true only when non-empty streams exists. if (rendererCapabilities[i].getTrackType() != C.TRACK_TYPE_NONE) { hasEnabledTracks = true; @@ -221,9 +303,10 @@ import com.google.android.exoplayer2.util.Log; return positionUs; } + /** Releases the media period. No other method should be called after the release. */ public void release() { disableTrackSelectionsInResult(); - periodTrackSelectorResult = null; + trackSelectorResult = null; try { if (info.id.endPositionUs != C.TIME_END_OF_SOURCE) { mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); @@ -236,6 +319,12 @@ import com.google.android.exoplayer2.util.Log; } } + /** + * Sets the next media period holder in the queue. + * + * @param nextMediaPeriodHolder The next holder, or null if this will be the new loading media + * period holder at the end of the queue. + */ public void setNext(@Nullable MediaPeriodHolder nextMediaPeriodHolder) { if (nextMediaPeriodHolder == next) { return; @@ -245,18 +334,39 @@ import com.google.android.exoplayer2.util.Log; enableTrackSelectionsInResult(); } + /** + * Returns the next media period holder in the queue, or null if this is the last media period + * (and thus the loading media period). + */ @Nullable public MediaPeriodHolder getNext() { return next; } + /** + * Returns the {@link TrackGroupArray} exposed by this media period. Must only be called if {@link + * #prepared} is {@code true}. + */ + public TrackGroupArray getTrackGroups() { + return Assertions.checkNotNull(trackGroups); + } + + /** + * Returns the {@link TrackSelectorResult} which is currently applied. Must only be called if + * {@link #prepared} is {@code true}. + */ + public TrackSelectorResult getTrackSelectorResult() { + return Assertions.checkNotNull(trackSelectorResult); + } + private void enableTrackSelectionsInResult() { - if (!isLoadingMediaPeriod() || periodTrackSelectorResult == null) { + TrackSelectorResult trackSelectorResult = this.trackSelectorResult; + if (!isLoadingMediaPeriod() || trackSelectorResult == null) { return; } - for (int i = 0; i < periodTrackSelectorResult.length; i++) { - boolean rendererEnabled = periodTrackSelectorResult.isRendererEnabled(i); - TrackSelection trackSelection = periodTrackSelectorResult.selections.get(i); + for (int i = 0; i < trackSelectorResult.length; i++) { + boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i); + TrackSelection trackSelection = trackSelectorResult.selections.get(i); if (rendererEnabled && trackSelection != null) { trackSelection.enable(); } @@ -264,12 +374,13 @@ import com.google.android.exoplayer2.util.Log; } private void disableTrackSelectionsInResult() { - if (!isLoadingMediaPeriod() || periodTrackSelectorResult == null) { + TrackSelectorResult trackSelectorResult = this.trackSelectorResult; + if (!isLoadingMediaPeriod() || trackSelectorResult == null) { return; } - for (int i = 0; i < periodTrackSelectorResult.length; i++) { - boolean rendererEnabled = periodTrackSelectorResult.isRendererEnabled(i); - TrackSelection trackSelection = periodTrackSelectorResult.selections.get(i); + for (int i = 0; i < trackSelectorResult.length; i++) { + boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i); + TrackSelection trackSelection = trackSelectorResult.selections.get(i); if (rendererEnabled && trackSelection != null) { trackSelection.disable(); } @@ -280,7 +391,8 @@ import com.google.android.exoplayer2.util.Log; * For each renderer of type {@link C#TRACK_TYPE_NONE}, we will remove the dummy {@link * EmptySampleStream} that was associated with it. */ - private void disassociateNoSampleRenderersWithEmptySampleStream(SampleStream[] sampleStreams) { + private void disassociateNoSampleRenderersWithEmptySampleStream( + @NullableType SampleStream[] sampleStreams) { for (int i = 0; i < rendererCapabilities.length; i++) { if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE) { sampleStreams[i] = null; @@ -292,7 +404,9 @@ import com.google.android.exoplayer2.util.Log; * For each renderer of type {@link C#TRACK_TYPE_NONE} that was enabled, we will associate it with * a dummy {@link EmptySampleStream}. */ - private void associateNoSampleRenderersWithEmptySampleStream(SampleStream[] sampleStreams) { + private void associateNoSampleRenderersWithEmptySampleStream( + @NullableType SampleStream[] sampleStreams) { + TrackSelectorResult trackSelectorResult = Assertions.checkNotNull(this.trackSelectorResult); for (int i = 0; i < rendererCapabilities.length; i++) { if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE && trackSelectorResult.isRendererEnabled(i)) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index 31daf65d38..49209023e6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -215,7 +215,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; @Override public long selectTracks( - TrackSelection[] selections, + @NullableType TrackSelection[] selections, boolean[] mayRetainStreamFlags, @NullableType SampleStream[] streams, boolean[] streamResetFlags, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java index 997f94bbfe..b7f5a07b60 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.trackselection.TrackSelection; import java.io.IOException; +import org.checkerframework.checker.nullness.compatqual.NullableType; /** * Loads media corresponding to a {@link Timeline.Period}, and allows that media to be read. All @@ -108,9 +109,9 @@ public interface MediaPeriod extends SequenceableLoader { * @return The actual position at which the tracks were enabled, in microseconds. */ long selectTracks( - TrackSelection[] selections, + @NullableType TrackSelection[] selections, boolean[] mayRetainStreamFlags, - SampleStream[] streams, + @NullableType SampleStream[] streams, boolean[] streamResetFlags, long positionUs); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java index f1136f0be5..4bc4975a1c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.trackselection; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.RendererConfiguration; import com.google.android.exoplayer2.util.Util; import org.checkerframework.checker.nullness.compatqual.NullableType; @@ -70,7 +71,7 @@ public final class TrackSelectorResult { * will be returned. * @return Whether this result is equivalent to {@code other} for all renderers. */ - public boolean isEquivalent(TrackSelectorResult other) { + public boolean isEquivalent(@Nullable TrackSelectorResult other) { if (other == null || other.selections.length != selections.length) { return false; } @@ -93,7 +94,7 @@ public final class TrackSelectorResult { * @return Whether this result is equivalent to {@code other} for the renderer at the specified * index. */ - public boolean isEquivalent(TrackSelectorResult other, int index) { + public boolean isEquivalent(@Nullable TrackSelectorResult other, int index) { if (other == null) { return false; } From 46731cdd1dc5ac940f95414ad28a18910431a644 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Oct 2018 07:50:26 -0700 Subject: [PATCH 039/832] Fix prepare position of DeferredMediaPeriods for windows with non-zero offset. If we prepare a deferred media period before the actual timeline is available, we either prepare with position zero (= the default) or with a non-zero initial seek position. So far, the zero (default) position got replaced by the actual default position (including any potential non-zero window offset) when the timeline became known. However, a non-zero initial seek position was not corrected by the non-zero window offset. This is fixed by this change. Related to that, we always assumed that the deferred media period will the first period in the actual timeline. This is not true if we prepare with an offset (either because of an initial seek position or because of a default window position). So, we also determine the actual first period when the timeline becomes known. Issue:#4873 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215213030 --- RELEASENOTES.md | 3 + .../source/ConcatenatingMediaSource.java | 89 +++++++++++++---- .../source/DeferredMediaPeriod.java | 30 +++--- .../android/exoplayer2/ExoPlayerTest.java | 98 ++++++++++++++++++- .../analytics/AnalyticsCollectorTest.java | 2 +- .../exoplayer2/testutil/ActionSchedule.java | 11 ++- 6 files changed, 192 insertions(+), 41 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index db9f5254ef..adfec3dacc 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,6 +9,9 @@ * HLS: * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload reader factory flags ([#4861](https://github.com/google/ExoPlayer/issues/4861)). +* Fix an issue with blind seeking to windows with non-zero offset in a + `ConcatenatingMediaSource` + ([#4873](https://github.com/google/ExoPlayer/issues/4873)). ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index e0b7da8506..7418e84449 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source; import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; @@ -65,6 +66,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource periodPosition = + timeline.getPeriodPosition(window, period, /* windowIndex= */ 0, windowStartPositionUs); + Object periodUid = periodPosition.first; + long periodPositionUs = periodPosition.second; + mediaSourceHolder.timeline = DeferredTimeline.createWithRealTimeline(timeline, periodUid); + if (deferredMediaPeriod != null) { + deferredMediaPeriod.overridePreparePositionUs(periodPositionUs); MediaPeriodId idInSource = deferredMediaPeriod.id.copyWithPeriodUid( getChildPeriodUid(mediaSourceHolder, deferredMediaPeriod.id.periodUid)); deferredMediaPeriod.createPeriod(idInSource); } - mediaSourceHolder.isPrepared = true; } + mediaSourceHolder.isPrepared = true; scheduleListenerNotification(/* actionOnCompletion= */ null); } @@ -897,18 +932,32 @@ public class ConcatenatingMediaSource extends CompositeMediaSource 0 - ? timeline.getUidOfPeriod(0) - : replacedId); + /** + * Returns a copy with an updated timeline. This keeps the existing period replacement. + * + * @param timeline The new timeline. + */ + public DeferredTimeline cloneWithUpdatedTimeline(Timeline timeline) { + return new DeferredTimeline(timeline, replacedId); } + /** Returns wrapped timeline. */ public Timeline getTimeline() { return timeline; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java index 5f84731b8d..26c25a749e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java @@ -79,23 +79,19 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb this.listener = listener; } + /** Returns the position at which the deferred media period was prepared, in microseconds. */ + public long getPreparePositionUs() { + return preparePositionUs; + } + /** - * Sets the default prepare position at which to prepare the media period. This value is only used - * if the call to {@link MediaPeriod#prepare(Callback, long)} is being deferred and the call was - * made with a (presumably default) prepare position of 0. + * Overrides the default prepare position at which to prepare the media period. This value is only + * used if the call to {@link MediaPeriod#prepare(Callback, long)} is being deferred. * - *

    Note that this will override an intentional seek to zero in the corresponding non-seekable - * timeline window. This is unlikely to be a problem as a non-zero default position usually only - * occurs for live playbacks and seeking to zero in a live window would cause - * BehindLiveWindowExceptions anyway. - * - * @param defaultPreparePositionUs The actual default prepare position, in microseconds. + * @param defaultPreparePositionUs The default prepare position to use, in microseconds. */ - public void setDefaultPreparePositionUs(long defaultPreparePositionUs) { - if (preparePositionUs == 0 && defaultPreparePositionUs != 0) { - preparePositionOverrideUs = defaultPreparePositionUs; - preparePositionUs = defaultPreparePositionUs; - } + public void overridePreparePositionUs(long defaultPreparePositionUs) { + preparePositionOverrideUs = defaultPreparePositionUs; } /** @@ -108,6 +104,10 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb public void createPeriod(MediaPeriodId id) { mediaPeriod = mediaSource.createPeriod(id, allocator); if (callback != null) { + long preparePositionUs = + preparePositionOverrideUs != C.TIME_UNSET + ? preparePositionOverrideUs + : this.preparePositionUs; mediaPeriod.prepare(this, preparePositionUs); } } @@ -157,7 +157,7 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb @Override public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { - if (preparePositionOverrideUs != C.TIME_UNSET && positionUs == 0) { + if (preparePositionOverrideUs != C.TIME_UNSET && positionUs == preparePositionUs) { positionUs = preparePositionOverrideUs; preparePositionOverrideUs = C.TIME_UNSET; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 8414be1588..407e9a3827 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -60,6 +60,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; import org.junit.Test; @@ -1346,7 +1347,7 @@ public final class ExoPlayerTest { () -> concatenatingMediaSource.addMediaSources( Arrays.asList(mediaSource, mediaSource))) - .waitForTimelineChanged(null) + .waitForTimelineChanged() .executeRunnable( new PlayerRunnable() { @Override @@ -2192,14 +2193,14 @@ public final class ExoPlayerTest { startPositionUs + expectedDurationUs); Clock clock = new AutoAdvancingFakeClock(); AtomicReference playerReference = new AtomicReference<>(); - AtomicReference positionAtDiscontinuityMs = new AtomicReference<>(); - AtomicReference clockAtStartMs = new AtomicReference<>(); - AtomicReference clockAtDiscontinuityMs = new AtomicReference<>(); + AtomicLong positionAtDiscontinuityMs = new AtomicLong(C.TIME_UNSET); + AtomicLong clockAtStartMs = new AtomicLong(C.TIME_UNSET); + AtomicLong clockAtDiscontinuityMs = new AtomicLong(C.TIME_UNSET); EventListener eventListener = new EventListener() { @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - if (playbackState == Player.STATE_READY && clockAtStartMs.get() == null) { + if (playbackState == Player.STATE_READY && clockAtStartMs.get() == C.TIME_UNSET) { clockAtStartMs.set(clock.elapsedRealtime()); } } @@ -2446,6 +2447,93 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); } + @Test + public void seekToUnpreparedWindowWithNonZeroOffsetInConcatenationStartsAtCorrectPosition() + throws Exception { + Timeline timeline = new FakeTimeline(/* windowCount= */ 1); + FakeMediaSource mediaSource = new FakeMediaSource(/* timeline= */ null, /* manifest= */ null); + MediaSource clippedMediaSource = + new ClippingMediaSource( + mediaSource, + /* startPositionUs= */ 3 * C.MICROS_PER_SECOND, + /* endPositionUs= */ C.TIME_END_OF_SOURCE); + MediaSource concatenatedMediaSource = new ConcatenatingMediaSource(clippedMediaSource); + AtomicLong positionWhenReady = new AtomicLong(); + ActionSchedule actionSchedule = + new ActionSchedule.Builder("seekToUnpreparedWindowWithNonZeroOffsetInConcatenation") + .pause() + .waitForPlaybackState(Player.STATE_BUFFERING) + .seek(/* positionMs= */ 10) + .waitForTimelineChanged() + .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline, /* newManifest= */ null)) + .waitForTimelineChanged() + .waitForPlaybackState(Player.STATE_READY) + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + positionWhenReady.set(player.getContentPosition()); + } + }) + .play() + .build(); + new Builder() + .setMediaSource(concatenatedMediaSource) + .setActionSchedule(actionSchedule) + .build(context) + .start() + .blockUntilEnded(TIMEOUT_MS); + + assertThat(positionWhenReady.get()).isEqualTo(10); + } + + @Test + public void seekToUnpreparedWindowWithMultiplePeriodsInConcatenationStartsAtCorrectPeriod() + throws Exception { + long periodDurationMs = 5000; + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount =*/ 2, + /* id= */ new Object(), + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 2 * periodDurationMs * 1000)); + FakeMediaSource mediaSource = new FakeMediaSource(/* timeline= */ null, /* manifest= */ null); + MediaSource concatenatedMediaSource = new ConcatenatingMediaSource(mediaSource); + AtomicInteger periodIndexWhenReady = new AtomicInteger(); + AtomicLong positionWhenReady = new AtomicLong(); + ActionSchedule actionSchedule = + new ActionSchedule.Builder("seekToUnpreparedWindowWithMultiplePeriodsInConcatenation") + .pause() + .waitForPlaybackState(Player.STATE_BUFFERING) + // Seek 10ms into the second period. + .seek(/* positionMs= */ periodDurationMs + 10) + .waitForTimelineChanged() + .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline, /* newManifest= */ null)) + .waitForTimelineChanged() + .waitForPlaybackState(Player.STATE_READY) + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + periodIndexWhenReady.set(player.getCurrentPeriodIndex()); + positionWhenReady.set(player.getContentPosition()); + } + }) + .play() + .build(); + new Builder() + .setMediaSource(concatenatedMediaSource) + .setActionSchedule(actionSchedule) + .build(context) + .start() + .blockUntilEnded(TIMEOUT_MS); + + assertThat(periodIndexWhenReady.get()).isEqualTo(1); + assertThat(positionWhenReady.get()).isEqualTo(periodDurationMs + 10); + } + // Internal methods. private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index e0feae6f49..3649685f3e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -585,7 +585,7 @@ public final class AnalyticsCollectorTest { () -> concatenatedMediaSource.moveMediaSource( /* currentIndex= */ 0, /* newIndex= */ 1)) - .waitForTimelineChanged(/* expectedTimeline= */ null) + .waitForTimelineChanged() .play() .build(); TestAnalyticsListener listener = runAnalyticsTest(concatenatedMediaSource, actionSchedule); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index 6e37d7d070..54d97fb905 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -375,6 +375,15 @@ public final class ActionSchedule { return apply(new SendMessages(tag, target, windowIndex, positionMs, deleteAfterDelivery)); } + /** + * Schedules a delay until any timeline change. + * + * @return The builder, for convenience. + */ + public Builder waitForTimelineChanged() { + return apply(new WaitForTimelineChanged(tag, /* expectedTimeline= */ null)); + } + /** * Schedules a delay until the timeline changed to a specified expected timeline. * @@ -382,7 +391,7 @@ public final class ActionSchedule { * change. * @return The builder, for convenience. */ - public Builder waitForTimelineChanged(@Nullable Timeline expectedTimeline) { + public Builder waitForTimelineChanged(Timeline expectedTimeline) { return apply(new WaitForTimelineChanged(tag, expectedTimeline)); } From 7940fdfc5ed7ea1e358b0b166ec8f70db78f03e5 Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 1 Oct 2018 07:52:26 -0700 Subject: [PATCH 040/832] Fix DownloadManager.TaskState javadoc ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215213255 --- .../exoplayer2/offline/DownloadManager.java | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java index 409f79f30b..3757517f5d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java @@ -528,9 +528,9 @@ public final class DownloadManager { *

    Transition diagram: * *

    -     *                    -> canceled
    -     * queued <-> started -> completed
    -     *                    -> failed
    +     *    ┌────────┬─────→ canceled
    +     * queued ↔ started ┬→ completed
    +     *                  └→ failed
          * 
    */ @Documented @@ -609,18 +609,15 @@ public final class DownloadManager { * {@link #STATE_QUEUED_CANCELING}, {@link #STATE_STARTED_CANCELING} or {@link * #STATE_STARTED_STOPPING}. * - *

    Transition map (vertical states are source states): + *

    Transition diagram: * *

    -     *             +------+-------+---------+-----------+-----------+--------+--------+------+
    -     *             |queued|started|completed|q_canceling|s_canceling|canceled|stopping|failed|
    -     * +-----------+------+-------+---------+-----------+-----------+--------+--------+------+
    -     * |queued     |      |   X   |         |     X     |           |        |        |      |
    -     * |started    |      |       |    X    |           |     X     |        |   X    |   X  |
    -     * |q_canceling|      |       |         |           |           |   X    |        |      |
    -     * |s_canceling|      |       |         |           |           |   X    |        |      |
    -     * |stopping   |   X  |       |         |           |           |        |        |      |
    -     * +-----------+------+-------+---------+-----------+-----------+--------+--------+------+
    +     *    ┌───→ q_canceling ┬→ canceled
    +     *    │     s_canceling ┘
    +     *    │         ↑
    +     * queued → started ────┬→ completed
    +     *    ↑         ↓       └→ failed
    +     *    └──── s_stopping
          * 
    */ @Documented From 6cc049f6efca7227b92670e32ab5cb223f763996 Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 1 Oct 2018 08:05:01 -0700 Subject: [PATCH 041/832] Increase supported libflac version to 1.3.2 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215214894 --- extensions/flac/README.md | 9 +++++---- extensions/flac/src/main/jni/Android.mk | 4 ++-- extensions/flac/src/main/jni/Application.mk | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/extensions/flac/README.md b/extensions/flac/README.md index fda5f0085d..54701eea1d 100644 --- a/extensions/flac/README.md +++ b/extensions/flac/README.md @@ -28,18 +28,19 @@ EXOPLAYER_ROOT="$(pwd)" FLAC_EXT_PATH="${EXOPLAYER_ROOT}/extensions/flac/src/main" ``` -* Download the [Android NDK][] and set its location in an environment variable: +* Download the [Android NDK][] (version <= 17c) and set its location in an + environment variable: ``` NDK_PATH="" ``` -* Download and extract flac-1.3.1 as "${FLAC_EXT_PATH}/jni/flac" folder: +* Download and extract flac-1.3.2 as "${FLAC_EXT_PATH}/jni/flac" folder: ``` cd "${FLAC_EXT_PATH}/jni" && \ -curl https://ftp.osuosl.org/pub/xiph/releases/flac/flac-1.3.1.tar.xz | tar xJ && \ -mv flac-1.3.1 flac +curl https://ftp.osuosl.org/pub/xiph/releases/flac/flac-1.3.2.tar.xz | tar xJ && \ +mv flac-1.3.2 flac ``` * Build the JNI native libraries from the command line: diff --git a/extensions/flac/src/main/jni/Android.mk b/extensions/flac/src/main/jni/Android.mk index ff54c1b3c0..69520a16e5 100644 --- a/extensions/flac/src/main/jni/Android.mk +++ b/extensions/flac/src/main/jni/Android.mk @@ -30,9 +30,9 @@ LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/flac/src/libFLAC/include LOCAL_SRC_FILES := $(FLAC_SOURCES) -LOCAL_CFLAGS += '-DVERSION="1.3.1"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY -DFLAC__NO_ASM +LOCAL_CFLAGS += '-DPACKAGE_VERSION="1.3.2"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_COMMON_IMPLEMENTATION -fPIC -DHAVE_SYS_PARAM_H -LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions +LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions -DFLAC__NO_ASM '-DFLAC__HAS_OGG=0' LOCAL_LDLIBS := -llog -lz -lm include $(BUILD_SHARED_LIBRARY) diff --git a/extensions/flac/src/main/jni/Application.mk b/extensions/flac/src/main/jni/Application.mk index 59bf5f8f87..eba20352f4 100644 --- a/extensions/flac/src/main/jni/Application.mk +++ b/extensions/flac/src/main/jni/Application.mk @@ -17,4 +17,4 @@ APP_OPTIM := release APP_STL := gnustl_static APP_CPPFLAGS := -frtti -APP_PLATFORM := android-9 +APP_PLATFORM := android-14 From fc5eb12e7955e1272f52a90ec25a1a385429a47c Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Mon, 1 Oct 2018 22:45:15 +0300 Subject: [PATCH 042/832] #4306 - breaking after the first alignment tag is found --- .../exoplayer2/text/subrip/SubripDecoder.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index 492eb60bfc..e50184e403 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -80,7 +80,6 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { @Override protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { ArrayList cues = new ArrayList<>(); - ArrayList tags = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); ParsableByteArray subripData = new ParsableByteArray(bytes, length); String currentLine; @@ -120,6 +119,7 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { } // Read and parse the text. + ArrayList tags = new ArrayList<>(); textBuilder.setLength(0); while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { if (textBuilder.length() > 0) { @@ -131,19 +131,15 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { Spanned text = Html.fromHtml(textBuilder.toString()); Cue cue = null; - boolean alignTagFound = false; - // At end of this loop the clue must be created with the applied tags for (String tag : tags) { // Check if the tag is an alignment tag if (tag.matches(SUBRIP_ALIGNMENT_TAG)) { - - // Based on the specs, in case of the alignment tags only the first appearance counts - if (alignTagFound) continue; - alignTagFound = true; - cue = buildCue(text, tag); + + // Based on the specs, in case of alignment tags only the first appearance counts, so break + break; } } From 75a7385bbb4a2e6940f6a97ba8d5814e2dcd6049 Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Mon, 1 Oct 2018 23:16:48 +0300 Subject: [PATCH 043/832] #4306 - extends test case with line and position anchor verifications --- .../exoplayer2/text/subrip/SubripDecoder.java | 2 +- .../src/test/assets/subrip/typical_with_tags | 36 ++++++++++ .../text/subrip/SubripDecoderTest.java | 67 +++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index e50184e403..63887906c6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -176,7 +176,7 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { // Extract tags int replacedCharacters = 0; StringBuilder processedLine = new StringBuilder(trimmedLine); - Matcher matcher = SUBRIP_TAG_PATTERN.matcher(processedLine); + Matcher matcher = SUBRIP_TAG_PATTERN.matcher(trimmedLine); while (matcher.find()) { String tag = matcher.group(); diff --git a/library/core/src/test/assets/subrip/typical_with_tags b/library/core/src/test/assets/subrip/typical_with_tags index 02e1ffbcd9..af196f8a04 100644 --- a/library/core/src/test/assets/subrip/typical_with_tags +++ b/library/core/src/test/assets/subrip/typical_with_tags @@ -18,3 +18,39 @@ This { \an2} is the fourth subtitle. 5 00:00:013,567 --> 00:00:14,901 This {\an2} is the fifth subtitle with multiple {\xyz} valid {\qwe} tags. + +6 +00:00:015,567 --> 00:00:15,901 +This {\an1} is a lines. + +7 +00:00:016,567 --> 00:00:16,901 +This {\an2} is a line. + +8 +00:00:017,567 --> 00:00:17,901 +This {\an3} is a line. + +9 +00:00:018,567 --> 00:00:18,901 +This {\an4} is a line. + +10 +00:00:019,567 --> 00:00:19,901 +This {\an5} is a line. + +11 +00:00:020,567 --> 00:00:20,901 +This {\an6} is a line. + +12 +00:00:021,567 --> 00:00:22,901 +This {\an7} is a line. + +13 +00:00:023,567 --> 00:00:23,901 +This {\an8} is a line. + +14 +00:00:024,567 --> 00:00:24,901 +This {\an9} is a line. \ No newline at end of file diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java index a9d69076c2..554184da5d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java @@ -18,6 +18,8 @@ package com.google.android.exoplayer2.text.subrip; import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.text.Cue; + import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; @@ -174,6 +176,71 @@ public final class SubripDecoderTest { assertThat(subtitle.getCues(subtitle.getEventTime(8)).get(0).text.toString()) .isEqualTo("This is the fifth subtitle with multiple valid tags."); + + // Verify positions + + // {/an1} + assertThat(subtitle.getCues(subtitle.getEventTime(10)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + + assertThat(subtitle.getCues(subtitle.getEventTime(10)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + // {/an2} + assertThat(subtitle.getCues(subtitle.getEventTime(12)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + assertThat(subtitle.getCues(subtitle.getEventTime(12)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + // {/an3} + assertThat(subtitle.getCues(subtitle.getEventTime(14)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + assertThat(subtitle.getCues(subtitle.getEventTime(14)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + // {/an4} + assertThat(subtitle.getCues(subtitle.getEventTime(16)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + + assertThat(subtitle.getCues(subtitle.getEventTime(16)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + // {/an5} + assertThat(subtitle.getCues(subtitle.getEventTime(18)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + assertThat(subtitle.getCues(subtitle.getEventTime(18)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + // {/an6} + assertThat(subtitle.getCues(subtitle.getEventTime(20)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + assertThat(subtitle.getCues(subtitle.getEventTime(20)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + // {/an7} + assertThat(subtitle.getCues(subtitle.getEventTime(22)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + + assertThat(subtitle.getCues(subtitle.getEventTime(22)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + + // {/an8} + assertThat(subtitle.getCues(subtitle.getEventTime(24)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + assertThat(subtitle.getCues(subtitle.getEventTime(24)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + + // {/an9} + assertThat(subtitle.getCues(subtitle.getEventTime(26)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + assertThat(subtitle.getCues(subtitle.getEventTime(26)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); } private static void assertTypicalCue1(SubripSubtitle subtitle, int eventIndex) { From 56c7e1ff475ea6b9a7fb06ad9dd14303b1104e26 Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Tue, 2 Oct 2018 18:45:06 +0300 Subject: [PATCH 044/832] #4306 - grouping line/lineAnchor and position/positionAnchor assignments, setting Cue's textAlignment to def value - null --- .../exoplayer2/text/subrip/SubripDecoder.java | 80 +++++++++---------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index 63887906c6..cf2d3c11bc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -202,69 +202,63 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { * @return Built cue */ private Cue buildCue(Spanned text, String alignmentTag) { - // Default values used for positioning the subtitle in case of align tags - float line = DEFAULT_END_FRACTION, position = DEFAULT_MID_FRACTION; - @Cue.AnchorType int positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; - @Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_END; + float line, position; + @Cue.AnchorType int positionAnchor; + @Cue.AnchorType int lineAnchor; + // Set position and position anchor (horizontal alignment) switch (alignmentTag) { case ALIGN_BOTTOM_LEFT: - line = DEFAULT_END_FRACTION; + case ALIGN_MID_LEFT: + case ALIGN_TOP_LEFT: position = DEFAULT_START_FRACTION; positionAnchor = Cue.ANCHOR_TYPE_START; - lineAnchor = Cue.ANCHOR_TYPE_END; break; case ALIGN_BOTTOM_MID: - line = DEFAULT_END_FRACTION; + case ALIGN_MID_MID: + case ALIGN_TOP_MID: position = DEFAULT_MID_FRACTION; positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; - lineAnchor = Cue.ANCHOR_TYPE_END; break; case ALIGN_BOTTOM_RIGHT: - line = DEFAULT_END_FRACTION; - position = DEFAULT_END_FRACTION; - positionAnchor = Cue.ANCHOR_TYPE_END; - lineAnchor = Cue.ANCHOR_TYPE_END; - break; - case ALIGN_MID_LEFT: - line = DEFAULT_MID_FRACTION; - position = DEFAULT_START_FRACTION; - positionAnchor = Cue.ANCHOR_TYPE_START; - lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; - break; - case ALIGN_MID_MID: - line = DEFAULT_MID_FRACTION; - position = DEFAULT_MID_FRACTION; - positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; - lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; - break; case ALIGN_MID_RIGHT: - line = DEFAULT_MID_FRACTION; + case ALIGN_TOP_RIGHT: position = DEFAULT_END_FRACTION; positionAnchor = Cue.ANCHOR_TYPE_END; - lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; break; - case ALIGN_TOP_LEFT: - line = DEFAULT_START_FRACTION; - position = DEFAULT_START_FRACTION; - positionAnchor = Cue.ANCHOR_TYPE_START; - lineAnchor = Cue.ANCHOR_TYPE_START; - break; - case ALIGN_TOP_MID: - line = DEFAULT_START_FRACTION; + default: position = DEFAULT_MID_FRACTION; positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; - lineAnchor = Cue.ANCHOR_TYPE_START; - break; - case ALIGN_TOP_RIGHT: - line = DEFAULT_START_FRACTION; - position = DEFAULT_END_FRACTION; - positionAnchor = Cue.ANCHOR_TYPE_END; - lineAnchor = Cue.ANCHOR_TYPE_START; break; } - return new Cue(text, Layout.Alignment.ALIGN_NORMAL, line, Cue.LINE_TYPE_FRACTION, lineAnchor, position, positionAnchor, Cue.DIMEN_UNSET); + // Set line and line anchor (vertical alignment) + switch (alignmentTag) { + case ALIGN_BOTTOM_LEFT: + case ALIGN_BOTTOM_MID: + case ALIGN_BOTTOM_RIGHT: + line = DEFAULT_END_FRACTION; + lineAnchor = Cue.ANCHOR_TYPE_END; + break; + case ALIGN_MID_LEFT: + case ALIGN_MID_MID: + case ALIGN_MID_RIGHT: + line = DEFAULT_MID_FRACTION; + lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; + break; + case ALIGN_TOP_LEFT: + case ALIGN_TOP_MID: + case ALIGN_TOP_RIGHT: + line = DEFAULT_START_FRACTION; + lineAnchor = Cue.ANCHOR_TYPE_START; + break; + default: + line = DEFAULT_END_FRACTION; + lineAnchor = Cue.ANCHOR_TYPE_END; + break; + } + + return new Cue(text, null, line, Cue.LINE_TYPE_FRACTION, lineAnchor, position, positionAnchor, Cue.DIMEN_UNSET); } private static long parseTimecode(Matcher matcher, int groupOffset) { From 687756ed31090e91cc57d26dd13e2593c4d4e343 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 1 Oct 2018 12:40:42 -0700 Subject: [PATCH 045/832] Update moe equivalence ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215261807 --- .../mediasession/MediaSessionConnector.java | 4 +- .../CountryAndNetworkTypeBandwidthMeter.java | 592 ++++++++++++++++++ ...untryAndNetworkTypeBandwidthMeterTest.java | 534 ++++++++++++++++ 3 files changed, 1128 insertions(+), 2 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeter.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeterTest.java diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java index d0cde5f693..c4110a10f7 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -259,7 +259,7 @@ public final class MediaSessionConnector { /** See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat)}. */ void onSetRating(Player player, RatingCompat rating); - + /** See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat, Bundle)}. */ void onSetRating(Player player, RatingCompat rating, Bundle extras); } @@ -1005,7 +1005,7 @@ public final class MediaSessionConnector { ratingCallback.onSetRating(player, rating); } } - + @Override public void onSetRating(RatingCompat rating, Bundle extras) { if (canDispatchToRatingCallback(PlaybackStateCompat.ACTION_SET_RATING)) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeter.java new file mode 100644 index 0000000000..7dce8a5049 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeter.java @@ -0,0 +1,592 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.upstream; + +import android.content.Context; +import android.os.Handler; +import android.support.annotation.Nullable; +import android.util.SparseArray; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.ClosedSource; +import com.google.android.exoplayer2.util.EventDispatcher; +import com.google.android.exoplayer2.util.SlidingPercentile; +import com.google.android.exoplayer2.util.Util; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Estimates bandwidth by listening to data transfers. + * + *

    The bandwidth estimate is calculated using a {@link SlidingPercentile} and is updated each + * time a transfer ends. The initial estimate is based on the current operator's network country + * code or the locale of the user, as well as the network connection type. This can be configured in + * the {@link Builder}. + */ +@ClosedSource(reason = "Needs testing") +public final class CountryAndNetworkTypeBandwidthMeter implements BandwidthMeter, TransferListener { + + /** + * Country groups used to determine the default initial bitrate estimate. The group assignment for + * each country is an array of group indices for [Wifi, 2G, 3G, 4G]. + */ + public static final Map DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS = + createInitialBitrateCountryGroupAssignment(); + + /** Default initial Wifi bitrate estimate in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI = + new long[] {5_700_000, 3_400_000, 1_900_000, 1_000_000, 400_000}; + + /** Default initial 2G bitrate estimates in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_2G = + new long[] {169_000, 129_000, 114_000, 102_000, 87_000}; + + /** Default initial 3G bitrate estimates in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_3G = + new long[] {2_100_000, 1_300_000, 950_000, 700_000, 400_000}; + + /** Default initial 4G bitrate estimates in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_4G = + new long[] {6_900_000, 4_300_000, 2_700_000, 1_600_000, 450_000}; + + /** + * Default initial bitrate estimate used when the device is offline or the network type cannot be + * determined, in bits per second. + */ + public static final long DEFAULT_INITIAL_BITRATE_ESTIMATE = 1_000_000; + + /** Default maximum weight for the sliding window. */ + public static final int DEFAULT_SLIDING_WINDOW_MAX_WEIGHT = 2000; + + /** Builder for a bandwidth meter. */ + public static final class Builder { + + private final @Nullable Context context; + + private @Nullable Handler eventHandler; + private @Nullable EventListener eventListener; + private SparseArray initialBitrateEstimates; + private int slidingWindowMaxWeight; + private Clock clock; + + /** @deprecated Use {@link #Builder(Context)} instead. */ + @Deprecated + public Builder() { + this(/* context= */ null); + } + + /** + * Creates a builder with default parameters and without listener. + * + * @param context A context. + */ + public Builder(@Nullable Context context) { + this.context = context == null ? null : context.getApplicationContext(); + initialBitrateEstimates = getInitialBitrateEstimatesForCountry(Util.getCountryCode(context)); + slidingWindowMaxWeight = DEFAULT_SLIDING_WINDOW_MAX_WEIGHT; + clock = Clock.DEFAULT; + } + + /** + * Sets an event listener for new bandwidth estimates. + * + * @param eventHandler A handler for events. + * @param eventListener A listener of events. + * @return This builder. + * @throws IllegalArgumentException If the event handler or listener are null. + */ + public Builder setEventListener(Handler eventHandler, EventListener eventListener) { + Assertions.checkArgument(eventHandler != null && eventListener != null); + this.eventHandler = eventHandler; + this.eventListener = eventListener; + return this; + } + + /** + * Sets the maximum weight for the sliding window. + * + * @param slidingWindowMaxWeight The maximum weight for the sliding window. + * @return This builder. + */ + public Builder setSlidingWindowMaxWeight(int slidingWindowMaxWeight) { + this.slidingWindowMaxWeight = slidingWindowMaxWeight; + return this; + } + + /** + * Sets the initial bitrate estimate in bits per second that should be assumed when a bandwidth + * estimate is unavailable. + * + * @param initialBitrateEstimate The initial bitrate estimate in bits per second. + * @return This builder. + */ + public Builder setInitialBitrateEstimate(long initialBitrateEstimate) { + for (int i = 0; i < initialBitrateEstimates.size(); i++) { + initialBitrateEstimates.setValueAt(i, initialBitrateEstimate); + } + return this; + } + + /** + * Sets the initial bitrate estimate in bits per second for a network type that should be + * assumed when a bandwidth estimate is unavailable and the current network connection is of the + * specified type. + * + * @param networkType The {@link C.NetworkType} this initial estimate is for. + * @param initialBitrateEstimate The initial bitrate estimate in bits per second. + * @return This builder. + */ + public Builder setInitialBitrateEstimate( + @C.NetworkType int networkType, long initialBitrateEstimate) { + initialBitrateEstimates.put(networkType, initialBitrateEstimate); + return this; + } + + /** + * Sets the initial bitrate estimates to the default values of the specified country. The + * initial estimates are used when a bandwidth estimate is unavailable. + * + * @param countryCode The ISO 3166-1 alpha-2 country code of the country whose default bitrate + * estimates should be used. + * @return This builder. + */ + public Builder setInitialBitrateEstimate(String countryCode) { + initialBitrateEstimates = + getInitialBitrateEstimatesForCountry(Util.toUpperInvariant(countryCode)); + return this; + } + + /** + * Sets the clock used to estimate bandwidth from data transfers. Should only be set for testing + * purposes. + * + * @param clock The clock used to estimate bandwidth from data transfers. + * @return This builder. + */ + public Builder setClock(Clock clock) { + this.clock = clock; + return this; + } + + /** + * Builds the bandwidth meter. + * + * @return A bandwidth meter with the configured properties. + */ + public CountryAndNetworkTypeBandwidthMeter build() { + Long initialBitrateEstimate = initialBitrateEstimates.get(Util.getNetworkType(context)); + if (initialBitrateEstimate == null) { + initialBitrateEstimate = initialBitrateEstimates.get(C.NETWORK_TYPE_UNKNOWN); + } + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter( + initialBitrateEstimate, slidingWindowMaxWeight, clock); + if (eventHandler != null && eventListener != null) { + bandwidthMeter.addEventListener(eventHandler, eventListener); + } + return bandwidthMeter; + } + + private static SparseArray getInitialBitrateEstimatesForCountry(String countryCode) { + int[] groupIndices = getCountryGroupIndices(countryCode); + SparseArray result = new SparseArray<>(/* initialCapacity= */ 6); + result.append(C.NETWORK_TYPE_UNKNOWN, DEFAULT_INITIAL_BITRATE_ESTIMATE); + result.append(C.NETWORK_TYPE_WIFI, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); + result.append(C.NETWORK_TYPE_2G, DEFAULT_INITIAL_BITRATE_ESTIMATES_2G[groupIndices[1]]); + result.append(C.NETWORK_TYPE_3G, DEFAULT_INITIAL_BITRATE_ESTIMATES_3G[groupIndices[2]]); + result.append(C.NETWORK_TYPE_4G, DEFAULT_INITIAL_BITRATE_ESTIMATES_4G[groupIndices[3]]); + // Assume default Wifi bitrate for Ethernet to prevent using the slower fallback bitrate. + result.append( + C.NETWORK_TYPE_ETHERNET, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); + return result; + } + + private static int[] getCountryGroupIndices(String countryCode) { + int[] groupIndices = DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS.get(countryCode); + // Assume median group if not found. + return groupIndices == null ? new int[] {2, 2, 2, 2} : groupIndices; + } + } + + private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; + private static final int BYTES_TRANSFERRED_FOR_ESTIMATE = 512 * 1024; + + private final EventDispatcher eventDispatcher; + private final SlidingPercentile slidingPercentile; + private final Clock clock; + + private int streamCount; + private long sampleStartTimeMs; + private long sampleBytesTransferred; + + private long totalElapsedTimeMs; + private long totalBytesTransferred; + private long bitrateEstimate; + + /** Creates a bandwidth meter with default parameters. */ + public CountryAndNetworkTypeBandwidthMeter() { + this(DEFAULT_INITIAL_BITRATE_ESTIMATE, DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, Clock.DEFAULT); + } + + /** @deprecated Use {@link Builder} instead. */ + @Deprecated + public CountryAndNetworkTypeBandwidthMeter(Handler eventHandler, EventListener eventListener) { + this(DEFAULT_INITIAL_BITRATE_ESTIMATE, DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, Clock.DEFAULT); + if (eventHandler != null && eventListener != null) { + addEventListener(eventHandler, eventListener); + } + } + + /** @deprecated Use {@link Builder} instead. */ + @Deprecated + public CountryAndNetworkTypeBandwidthMeter( + Handler eventHandler, EventListener eventListener, int maxWeight) { + this(DEFAULT_INITIAL_BITRATE_ESTIMATE, maxWeight, Clock.DEFAULT); + if (eventHandler != null && eventListener != null) { + addEventListener(eventHandler, eventListener); + } + } + + private CountryAndNetworkTypeBandwidthMeter( + long initialBitrateEstimate, int maxWeight, Clock clock) { + this.eventDispatcher = new EventDispatcher<>(); + this.slidingPercentile = new SlidingPercentile(maxWeight); + this.clock = clock; + bitrateEstimate = initialBitrateEstimate; + } + + @Override + public synchronized long getBitrateEstimate() { + return bitrateEstimate; + } + + @Override + public @Nullable TransferListener getTransferListener() { + return this; + } + + @Override + public void addEventListener(Handler eventHandler, EventListener eventListener) { + eventDispatcher.addListener(eventHandler, eventListener); + } + + @Override + public void removeEventListener(EventListener eventListener) { + eventDispatcher.removeListener(eventListener); + } + + @Override + public void onTransferInitializing(DataSource source, DataSpec dataSpec, boolean isNetwork) { + // Do nothing. + } + + @Override + public synchronized void onTransferStart( + DataSource source, DataSpec dataSpec, boolean isNetwork) { + if (!isNetwork) { + return; + } + if (streamCount == 0) { + sampleStartTimeMs = clock.elapsedRealtime(); + } + streamCount++; + } + + @Override + public synchronized void onBytesTransferred( + DataSource source, DataSpec dataSpec, boolean isNetwork, int bytes) { + if (!isNetwork) { + return; + } + sampleBytesTransferred += bytes; + } + + @Override + public synchronized void onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork) { + if (!isNetwork) { + return; + } + Assertions.checkState(streamCount > 0); + long nowMs = clock.elapsedRealtime(); + int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs); + totalElapsedTimeMs += sampleElapsedTimeMs; + totalBytesTransferred += sampleBytesTransferred; + if (sampleElapsedTimeMs > 0) { + float bitsPerSecond = (sampleBytesTransferred * 8000) / sampleElapsedTimeMs; + slidingPercentile.addSample((int) Math.sqrt(sampleBytesTransferred), bitsPerSecond); + if (totalElapsedTimeMs >= ELAPSED_MILLIS_FOR_ESTIMATE + || totalBytesTransferred >= BYTES_TRANSFERRED_FOR_ESTIMATE) { + bitrateEstimate = (long) slidingPercentile.getPercentile(0.5f); + } + } + notifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); + if (--streamCount > 0) { + sampleStartTimeMs = nowMs; + } + sampleBytesTransferred = 0; + } + + private void notifyBandwidthSample(int elapsedMs, long bytes, long bitrate) { + eventDispatcher.dispatch(listener -> listener.onBandwidthSample(elapsedMs, bytes, bitrate)); + } + + private static Map createInitialBitrateCountryGroupAssignment() { + HashMap countryGroupAssignment = new HashMap<>(); + countryGroupAssignment.put("AD", new int[] {1, 0, 0, 0}); + countryGroupAssignment.put("AE", new int[] {1, 3, 4, 4}); + countryGroupAssignment.put("AF", new int[] {4, 4, 3, 2}); + countryGroupAssignment.put("AG", new int[] {3, 2, 1, 2}); + countryGroupAssignment.put("AI", new int[] {1, 0, 0, 2}); + countryGroupAssignment.put("AL", new int[] {1, 1, 1, 1}); + countryGroupAssignment.put("AM", new int[] {2, 2, 4, 3}); + countryGroupAssignment.put("AO", new int[] {2, 4, 2, 0}); + countryGroupAssignment.put("AR", new int[] {2, 3, 2, 3}); + countryGroupAssignment.put("AS", new int[] {3, 4, 4, 1}); + countryGroupAssignment.put("AT", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("AU", new int[] {0, 3, 0, 0}); + countryGroupAssignment.put("AW", new int[] {1, 1, 0, 4}); + countryGroupAssignment.put("AX", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("AZ", new int[] {3, 3, 2, 2}); + countryGroupAssignment.put("BA", new int[] {1, 1, 1, 2}); + countryGroupAssignment.put("BB", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("BD", new int[] {2, 1, 3, 2}); + countryGroupAssignment.put("BE", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("BF", new int[] {4, 4, 4, 1}); + countryGroupAssignment.put("BG", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("BH", new int[] {2, 1, 3, 4}); + countryGroupAssignment.put("BI", new int[] {4, 3, 4, 4}); + countryGroupAssignment.put("BJ", new int[] {4, 3, 4, 3}); + countryGroupAssignment.put("BL", new int[] {1, 0, 1, 2}); + countryGroupAssignment.put("BM", new int[] {1, 0, 0, 0}); + countryGroupAssignment.put("BN", new int[] {4, 3, 3, 3}); + countryGroupAssignment.put("BO", new int[] {2, 2, 1, 2}); + countryGroupAssignment.put("BQ", new int[] {1, 1, 2, 4}); + countryGroupAssignment.put("BR", new int[] {2, 3, 2, 2}); + countryGroupAssignment.put("BS", new int[] {1, 1, 0, 2}); + countryGroupAssignment.put("BT", new int[] {3, 0, 2, 1}); + countryGroupAssignment.put("BW", new int[] {4, 4, 2, 3}); + countryGroupAssignment.put("BY", new int[] {1, 1, 1, 1}); + countryGroupAssignment.put("BZ", new int[] {2, 3, 3, 1}); + countryGroupAssignment.put("CA", new int[] {0, 2, 2, 3}); + countryGroupAssignment.put("CD", new int[] {4, 4, 2, 1}); + countryGroupAssignment.put("CF", new int[] {4, 4, 3, 3}); + countryGroupAssignment.put("CG", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("CH", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("CI", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("CK", new int[] {2, 4, 2, 0}); + countryGroupAssignment.put("CL", new int[] {2, 2, 2, 3}); + countryGroupAssignment.put("CM", new int[] {3, 4, 3, 1}); + countryGroupAssignment.put("CN", new int[] {2, 0, 1, 2}); + countryGroupAssignment.put("CO", new int[] {2, 3, 2, 1}); + countryGroupAssignment.put("CR", new int[] {2, 2, 4, 4}); + countryGroupAssignment.put("CU", new int[] {4, 4, 4, 1}); + countryGroupAssignment.put("CV", new int[] {2, 2, 2, 4}); + countryGroupAssignment.put("CW", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("CX", new int[] {1, 2, 2, 2}); + countryGroupAssignment.put("CY", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("CZ", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("DE", new int[] {0, 2, 2, 2}); + countryGroupAssignment.put("DJ", new int[] {3, 4, 4, 0}); + countryGroupAssignment.put("DK", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("DM", new int[] {2, 0, 3, 4}); + countryGroupAssignment.put("DO", new int[] {3, 3, 4, 4}); + countryGroupAssignment.put("DZ", new int[] {3, 3, 4, 4}); + countryGroupAssignment.put("EC", new int[] {2, 3, 3, 1}); + countryGroupAssignment.put("EE", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("EG", new int[] {3, 3, 1, 1}); + countryGroupAssignment.put("EH", new int[] {2, 0, 2, 3}); + countryGroupAssignment.put("ER", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("ES", new int[] {0, 0, 1, 1}); + countryGroupAssignment.put("ET", new int[] {4, 4, 4, 0}); + countryGroupAssignment.put("FI", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("FJ", new int[] {3, 2, 3, 3}); + countryGroupAssignment.put("FK", new int[] {3, 4, 2, 1}); + countryGroupAssignment.put("FM", new int[] {4, 2, 4, 0}); + countryGroupAssignment.put("FO", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("FR", new int[] {1, 0, 2, 1}); + countryGroupAssignment.put("GA", new int[] {3, 3, 2, 1}); + countryGroupAssignment.put("GB", new int[] {0, 1, 3, 2}); + countryGroupAssignment.put("GD", new int[] {2, 0, 3, 0}); + countryGroupAssignment.put("GE", new int[] {1, 1, 0, 3}); + countryGroupAssignment.put("GF", new int[] {1, 2, 4, 4}); + countryGroupAssignment.put("GG", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("GH", new int[] {3, 2, 2, 2}); + countryGroupAssignment.put("GI", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("GL", new int[] {2, 4, 1, 4}); + countryGroupAssignment.put("GM", new int[] {4, 3, 3, 0}); + countryGroupAssignment.put("GN", new int[] {4, 4, 3, 4}); + countryGroupAssignment.put("GP", new int[] {2, 2, 1, 3}); + countryGroupAssignment.put("GQ", new int[] {4, 4, 3, 1}); + countryGroupAssignment.put("GR", new int[] {1, 1, 0, 1}); + countryGroupAssignment.put("GT", new int[] {3, 2, 3, 4}); + countryGroupAssignment.put("GU", new int[] {1, 0, 4, 4}); + countryGroupAssignment.put("GW", new int[] {4, 4, 4, 0}); + countryGroupAssignment.put("GY", new int[] {3, 4, 1, 0}); + countryGroupAssignment.put("HK", new int[] {0, 2, 3, 4}); + countryGroupAssignment.put("HN", new int[] {3, 3, 2, 2}); + countryGroupAssignment.put("HR", new int[] {1, 0, 0, 2}); + countryGroupAssignment.put("HT", new int[] {3, 3, 3, 3}); + countryGroupAssignment.put("HU", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("ID", new int[] {2, 3, 3, 4}); + countryGroupAssignment.put("IE", new int[] {0, 0, 1, 1}); + countryGroupAssignment.put("IL", new int[] {0, 1, 1, 3}); + countryGroupAssignment.put("IM", new int[] {0, 1, 0, 1}); + countryGroupAssignment.put("IN", new int[] {2, 3, 3, 4}); + countryGroupAssignment.put("IO", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("IQ", new int[] {3, 3, 4, 3}); + countryGroupAssignment.put("IR", new int[] {3, 2, 4, 4}); + countryGroupAssignment.put("IS", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("IT", new int[] {1, 0, 1, 3}); + countryGroupAssignment.put("JE", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("JM", new int[] {3, 3, 3, 2}); + countryGroupAssignment.put("JO", new int[] {1, 1, 1, 2}); + countryGroupAssignment.put("JP", new int[] {0, 1, 1, 2}); + countryGroupAssignment.put("KE", new int[] {3, 3, 3, 3}); + countryGroupAssignment.put("KG", new int[] {2, 2, 3, 3}); + countryGroupAssignment.put("KH", new int[] {1, 0, 4, 4}); + countryGroupAssignment.put("KI", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("KM", new int[] {4, 4, 2, 2}); + countryGroupAssignment.put("KN", new int[] {1, 0, 1, 3}); + countryGroupAssignment.put("KP", new int[] {1, 2, 2, 2}); + countryGroupAssignment.put("KR", new int[] {0, 4, 0, 2}); + countryGroupAssignment.put("KW", new int[] {1, 2, 1, 2}); + countryGroupAssignment.put("KY", new int[] {1, 1, 0, 2}); + countryGroupAssignment.put("KZ", new int[] {1, 2, 2, 3}); + countryGroupAssignment.put("LA", new int[] {3, 2, 2, 2}); + countryGroupAssignment.put("LB", new int[] {3, 2, 0, 0}); + countryGroupAssignment.put("LC", new int[] {2, 2, 1, 0}); + countryGroupAssignment.put("LI", new int[] {0, 0, 1, 2}); + countryGroupAssignment.put("LK", new int[] {1, 1, 2, 2}); + countryGroupAssignment.put("LR", new int[] {3, 4, 3, 1}); + countryGroupAssignment.put("LS", new int[] {3, 3, 2, 0}); + countryGroupAssignment.put("LT", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("LU", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("LV", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("LY", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("MA", new int[] {2, 1, 2, 2}); + countryGroupAssignment.put("MC", new int[] {1, 0, 1, 0}); + countryGroupAssignment.put("MD", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("ME", new int[] {1, 2, 2, 3}); + countryGroupAssignment.put("MF", new int[] {1, 4, 3, 3}); + countryGroupAssignment.put("MG", new int[] {3, 4, 1, 2}); + countryGroupAssignment.put("MH", new int[] {4, 0, 2, 3}); + countryGroupAssignment.put("MK", new int[] {1, 0, 0, 1}); + countryGroupAssignment.put("ML", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("MM", new int[] {2, 3, 1, 2}); + countryGroupAssignment.put("MN", new int[] {2, 2, 2, 4}); + countryGroupAssignment.put("MO", new int[] {0, 1, 4, 4}); + countryGroupAssignment.put("MP", new int[] {0, 0, 4, 4}); + countryGroupAssignment.put("MQ", new int[] {1, 1, 1, 3}); + countryGroupAssignment.put("MR", new int[] {4, 2, 4, 2}); + countryGroupAssignment.put("MS", new int[] {1, 2, 1, 2}); + countryGroupAssignment.put("MT", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("MU", new int[] {2, 2, 4, 4}); + countryGroupAssignment.put("MV", new int[] {4, 2, 0, 1}); + countryGroupAssignment.put("MW", new int[] {3, 2, 1, 1}); + countryGroupAssignment.put("MX", new int[] {2, 4, 3, 1}); + countryGroupAssignment.put("MY", new int[] {2, 3, 3, 3}); + countryGroupAssignment.put("MZ", new int[] {3, 3, 2, 4}); + countryGroupAssignment.put("NA", new int[] {4, 2, 1, 1}); + countryGroupAssignment.put("NC", new int[] {2, 1, 3, 3}); + countryGroupAssignment.put("NE", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("NF", new int[] {0, 2, 2, 2}); + countryGroupAssignment.put("NG", new int[] {3, 4, 2, 2}); + countryGroupAssignment.put("NI", new int[] {3, 4, 3, 3}); + countryGroupAssignment.put("NL", new int[] {0, 1, 3, 2}); + countryGroupAssignment.put("NO", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("NP", new int[] {2, 3, 2, 2}); + countryGroupAssignment.put("NR", new int[] {4, 3, 4, 1}); + countryGroupAssignment.put("NU", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("NZ", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("OM", new int[] {2, 2, 1, 3}); + countryGroupAssignment.put("PA", new int[] {1, 3, 2, 3}); + countryGroupAssignment.put("PE", new int[] {2, 2, 4, 4}); + countryGroupAssignment.put("PF", new int[] {2, 2, 0, 1}); + countryGroupAssignment.put("PG", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("PH", new int[] {3, 0, 4, 4}); + countryGroupAssignment.put("PK", new int[] {3, 3, 3, 3}); + countryGroupAssignment.put("PL", new int[] {1, 0, 1, 3}); + countryGroupAssignment.put("PM", new int[] {0, 2, 2, 3}); + countryGroupAssignment.put("PR", new int[] {2, 3, 4, 3}); + countryGroupAssignment.put("PS", new int[] {2, 3, 0, 4}); + countryGroupAssignment.put("PT", new int[] {1, 1, 1, 1}); + countryGroupAssignment.put("PW", new int[] {3, 2, 3, 0}); + countryGroupAssignment.put("PY", new int[] {2, 1, 3, 3}); + countryGroupAssignment.put("QA", new int[] {2, 3, 1, 2}); + countryGroupAssignment.put("RE", new int[] {1, 1, 2, 2}); + countryGroupAssignment.put("RO", new int[] {0, 1, 1, 3}); + countryGroupAssignment.put("RS", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("RU", new int[] {0, 1, 1, 1}); + countryGroupAssignment.put("RW", new int[] {3, 4, 3, 1}); + countryGroupAssignment.put("SA", new int[] {3, 2, 2, 3}); + countryGroupAssignment.put("SB", new int[] {4, 4, 3, 0}); + countryGroupAssignment.put("SC", new int[] {4, 2, 0, 1}); + countryGroupAssignment.put("SD", new int[] {3, 4, 4, 4}); + countryGroupAssignment.put("SE", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("SG", new int[] {1, 2, 3, 3}); + countryGroupAssignment.put("SH", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("SI", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("SJ", new int[] {3, 2, 0, 2}); + countryGroupAssignment.put("SK", new int[] {0, 1, 0, 1}); + countryGroupAssignment.put("SL", new int[] {4, 3, 2, 4}); + countryGroupAssignment.put("SM", new int[] {1, 0, 1, 1}); + countryGroupAssignment.put("SN", new int[] {4, 4, 4, 2}); + countryGroupAssignment.put("SO", new int[] {4, 4, 4, 3}); + countryGroupAssignment.put("SR", new int[] {3, 2, 2, 3}); + countryGroupAssignment.put("SS", new int[] {4, 3, 4, 2}); + countryGroupAssignment.put("ST", new int[] {3, 2, 2, 2}); + countryGroupAssignment.put("SV", new int[] {2, 3, 2, 3}); + countryGroupAssignment.put("SX", new int[] {2, 4, 2, 0}); + countryGroupAssignment.put("SY", new int[] {4, 4, 2, 0}); + countryGroupAssignment.put("SZ", new int[] {3, 4, 1, 1}); + countryGroupAssignment.put("TC", new int[] {2, 1, 2, 1}); + countryGroupAssignment.put("TD", new int[] {4, 4, 4, 3}); + countryGroupAssignment.put("TG", new int[] {3, 2, 2, 0}); + countryGroupAssignment.put("TH", new int[] {1, 3, 4, 4}); + countryGroupAssignment.put("TJ", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("TL", new int[] {4, 2, 4, 4}); + countryGroupAssignment.put("TM", new int[] {4, 1, 3, 3}); + countryGroupAssignment.put("TN", new int[] {2, 2, 1, 2}); + countryGroupAssignment.put("TO", new int[] {2, 3, 3, 1}); + countryGroupAssignment.put("TR", new int[] {1, 2, 0, 2}); + countryGroupAssignment.put("TT", new int[] {2, 1, 1, 0}); + countryGroupAssignment.put("TV", new int[] {4, 2, 2, 4}); + countryGroupAssignment.put("TW", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("TZ", new int[] {3, 3, 3, 2}); + countryGroupAssignment.put("UA", new int[] {0, 2, 1, 3}); + countryGroupAssignment.put("UG", new int[] {4, 3, 2, 2}); + countryGroupAssignment.put("US", new int[] {0, 1, 3, 3}); + countryGroupAssignment.put("UY", new int[] {2, 1, 2, 2}); + countryGroupAssignment.put("UZ", new int[] {4, 3, 2, 4}); + countryGroupAssignment.put("VA", new int[] {1, 2, 2, 2}); + countryGroupAssignment.put("VC", new int[] {2, 0, 3, 2}); + countryGroupAssignment.put("VE", new int[] {3, 4, 4, 3}); + countryGroupAssignment.put("VG", new int[] {3, 1, 3, 4}); + countryGroupAssignment.put("VI", new int[] {1, 0, 2, 4}); + countryGroupAssignment.put("VN", new int[] {0, 2, 4, 4}); + countryGroupAssignment.put("VU", new int[] {4, 1, 3, 2}); + countryGroupAssignment.put("WS", new int[] {3, 2, 3, 0}); + countryGroupAssignment.put("XK", new int[] {1, 2, 1, 0}); + countryGroupAssignment.put("YE", new int[] {4, 4, 4, 2}); + countryGroupAssignment.put("YT", new int[] {3, 1, 1, 2}); + countryGroupAssignment.put("ZA", new int[] {2, 3, 1, 2}); + countryGroupAssignment.put("ZM", new int[] {3, 3, 3, 1}); + countryGroupAssignment.put("ZW", new int[] {3, 3, 2, 1}); + return Collections.unmodifiableMap(countryGroupAssignment); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeterTest.java new file mode 100644 index 0000000000..2549e004e5 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeterTest.java @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.upstream; + +import static android.Manifest.permission.ACCESS_NETWORK_STATE; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.telephony.TelephonyManager; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.ClosedSource; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowNetworkInfo; + +/** Unit test for {@link CountryAndNetworkTypeBandwidthMeter}. */ +@ClosedSource(reason = "Not ready yet") +@RunWith(RobolectricTestRunner.class) +public final class CountryAndNetworkTypeBandwidthMeterTest { + + private static final String FAST_COUNTRY_ISO = "EE"; + private static final String SLOW_COUNTRY_ISO = "PG"; + + private TelephonyManager telephonyManager; + private ConnectivityManager connectivityManager; + private NetworkInfo networkInfoOffline; + private NetworkInfo networkInfoWifi; + private NetworkInfo networkInfo2g; + private NetworkInfo networkInfo3g; + private NetworkInfo networkInfo4g; + private NetworkInfo networkInfoEthernet; + + @Before + public void setUp() { + connectivityManager = + (ConnectivityManager) + RuntimeEnvironment.application.getSystemService(Context.CONNECTIVITY_SERVICE); + telephonyManager = + (TelephonyManager) + RuntimeEnvironment.application.getSystemService(Context.TELEPHONY_SERVICE); + Shadows.shadowOf(telephonyManager).setNetworkCountryIso(FAST_COUNTRY_ISO); + networkInfoOffline = + ShadowNetworkInfo.newInstance( + DetailedState.DISCONNECTED, + ConnectivityManager.TYPE_WIFI, + /* subType= */ 0, + /* isAvailable= */ false, + /* isConnected= */ false); + networkInfoWifi = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_WIFI, + /* subType= */ 0, + /* isAvailable= */ true, + /* isConnected= */ true); + networkInfo2g = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_GPRS, + /* isAvailable= */ true, + /* isConnected= */ true); + networkInfo3g = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_HSDPA, + /* isAvailable= */ true, + /* isConnected= */ true); + networkInfo4g = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_LTE, + /* isAvailable= */ true, + /* isConnected= */ true); + networkInfoEthernet = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_ETHERNET, + /* subType= */ 0, + /* isAvailable= */ true, + /* isConnected= */ true); + } + + @Test + public void defaultInitialBitrateEstimate_forWifi_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfoWifi); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterWifi = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateWifi = bandwidthMeterWifi.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter2g = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimateWifi).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_forWifi_isGreaterThanEstimateFor3G() { + setActiveNetworkInfo(networkInfoWifi); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterWifi = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateWifi = bandwidthMeterWifi.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo3g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter3g = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + assertThat(initialEstimateWifi).isGreaterThan(initialEstimate3g); + } + + @Test + public void defaultInitialBitrateEstimate_forEthernet_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfoEthernet); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterEthernet = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateEthernet = bandwidthMeterEthernet.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter2g = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimateEthernet).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_forEthernet_isGreaterThanEstimateFor3G() { + setActiveNetworkInfo(networkInfoEthernet); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterEthernet = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateEthernet = bandwidthMeterEthernet.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo3g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter3g = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + assertThat(initialEstimateEthernet).isGreaterThan(initialEstimate3g); + } + + @Test + public void defaultInitialBitrateEstimate_for4G_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfo4g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter4g = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate4g = bandwidthMeter4g.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter2g = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimate4g).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_for4G_isGreaterThanEstimateFor3G() { + setActiveNetworkInfo(networkInfo4g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter4g = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate4g = bandwidthMeter4g.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo3g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter3g = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + assertThat(initialEstimate4g).isGreaterThan(initialEstimate3g); + } + + @Test + public void defaultInitialBitrateEstimate_for3G_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfo3g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter3g = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter2g = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimate3g).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_forOffline_isReasonable() { + setActiveNetworkInfo(networkInfoOffline); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isGreaterThan(100_000L); + assertThat(initialEstimate).isLessThan(50_000_000L); + } + + @Test + public void + defaultInitialBitrateEstimate_forWifi_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfoWifi); + setNetworkCountryIso(FAST_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterFast = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_forEthernet_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfoEthernet); + setNetworkCountryIso(FAST_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterFast = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_for2G_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfo2g); + setNetworkCountryIso(FAST_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterFast = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_for3G_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfo3g); + setNetworkCountryIso(FAST_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterFast = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_for4g_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfo4g); + setNetworkCountryIso(FAST_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterFast = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void initialBitrateEstimateOverwrite_whileConnectedToNetwork_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_whileOffline_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoOffline); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_forWifi_whileConnectedToWifi_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forWifi_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfo2g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forEthernet_whileConnectedToEthernet_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoEthernet); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_ETHERNET, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forEthernet_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfo2g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_for2G_whileConnectedTo2G_setsInitialEstimate() { + setActiveNetworkInfo(networkInfo2g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_2G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_for2G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_2G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_for3G_whileConnectedTo3G_setsInitialEstimate() { + setActiveNetworkInfo(networkInfo3g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_3G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_for3G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_3G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_for4G_whileConnectedTo4G_setsInitialEstimate() { + setActiveNetworkInfo(networkInfo4g); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_4G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_for4G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_4G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_forOffline_whileOffline_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoOffline); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_OFFLINE, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forOffline_whileConnectedToNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_OFFLINE, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_forCountry_usesDefaultValuesForCountry() { + setNetworkCountryIso(SLOW_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + setNetworkCountryIso(FAST_COUNTRY_ISO); + CountryAndNetworkTypeBandwidthMeter bandwidthMeterFastWithSlowOverwrite = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(SLOW_COUNTRY_ISO) + .build(); + long initialEstimateFastWithSlowOverwrite = + bandwidthMeterFastWithSlowOverwrite.getBitrateEstimate(); + + assertThat(initialEstimateFastWithSlowOverwrite).isEqualTo(initialEstimateSlow); + } + + @Test + public void defaultInitialBitrateEstimate_withoutContext_isReasonable() { + CountryAndNetworkTypeBandwidthMeter bandwidthMeterWithBuilder = + new CountryAndNetworkTypeBandwidthMeter.Builder(/* context= */ null).build(); + long initialEstimateWithBuilder = bandwidthMeterWithBuilder.getBitrateEstimate(); + + CountryAndNetworkTypeBandwidthMeter bandwidthMeterWithoutBuilder = + new CountryAndNetworkTypeBandwidthMeter(); + long initialEstimateWithoutBuilder = bandwidthMeterWithoutBuilder.getBitrateEstimate(); + + assertThat(initialEstimateWithBuilder).isGreaterThan(100_000L); + assertThat(initialEstimateWithBuilder).isLessThan(50_000_000L); + assertThat(initialEstimateWithoutBuilder).isGreaterThan(100_000L); + assertThat(initialEstimateWithoutBuilder).isLessThan(50_000_000L); + } + + @Test + public void defaultInitialBitrateEstimate_withoutAccessNetworkStatePermission_isReasonable() { + Shadows.shadowOf(RuntimeEnvironment.application).denyPermissions(ACCESS_NETWORK_STATE); + CountryAndNetworkTypeBandwidthMeter bandwidthMeter = + new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isGreaterThan(100_000L); + assertThat(initialEstimate).isLessThan(50_000_000L); + } + + private void setActiveNetworkInfo(NetworkInfo networkInfo) { + Shadows.shadowOf(connectivityManager).setActiveNetworkInfo(networkInfo); + } + + private void setNetworkCountryIso(String countryIso) { + Shadows.shadowOf(telephonyManager).setNetworkCountryIso(countryIso); + } +} From 8b2d99844069d61f694ded113a169cede05358ba Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Oct 2018 03:52:27 -0700 Subject: [PATCH 046/832] Remove deprecated ExoPlayer members with same name as non-deprecated version. For example, using ExoPlayer.STATE_IDLE falls back to the Player.STATE_IDLE automatically and thus there is no need to keep the deprecated members. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215363841 --- .../google/android/exoplayer2/ExoPlayer.java | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 0e8c176486..96ddb2eb9c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -117,12 +117,6 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; */ public interface ExoPlayer extends Player { - /** - * @deprecated Use {@link Player.EventListener} instead. - */ - @Deprecated - interface EventListener extends Player.EventListener {} - /** @deprecated Use {@link PlayerMessage.Target} instead. */ @Deprecated interface ExoPlayerComponent extends PlayerMessage.Target {} @@ -147,43 +141,6 @@ public interface ExoPlayer extends Player { } } - /** - * @deprecated Use {@link Player#STATE_IDLE} instead. - */ - @Deprecated - int STATE_IDLE = Player.STATE_IDLE; - /** - * @deprecated Use {@link Player#STATE_BUFFERING} instead. - */ - @Deprecated - int STATE_BUFFERING = Player.STATE_BUFFERING; - /** - * @deprecated Use {@link Player#STATE_READY} instead. - */ - @Deprecated - int STATE_READY = Player.STATE_READY; - /** - * @deprecated Use {@link Player#STATE_ENDED} instead. - */ - @Deprecated - int STATE_ENDED = Player.STATE_ENDED; - - /** - * @deprecated Use {@link Player#REPEAT_MODE_OFF} instead. - */ - @Deprecated - @RepeatMode int REPEAT_MODE_OFF = Player.REPEAT_MODE_OFF; - /** - * @deprecated Use {@link Player#REPEAT_MODE_ONE} instead. - */ - @Deprecated - @RepeatMode int REPEAT_MODE_ONE = Player.REPEAT_MODE_ONE; - /** - * @deprecated Use {@link Player#REPEAT_MODE_ALL} instead. - */ - @Deprecated - @RepeatMode int REPEAT_MODE_ALL = Player.REPEAT_MODE_ALL; - /** Returns the {@link Looper} associated with the playback thread. */ Looper getPlaybackLooper(); From a8b851ce34e2932172bce0bae3b71e176ceb3929 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Oct 2018 05:52:56 -0700 Subject: [PATCH 047/832] Add BasePlayer to avoid code duplication for common convenience methods. A lot of methods just forward to other methods and there is no conceivable way a player should implement it another way. Moving these methods to a base player class allows to remove duplicated code across our player implementations. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215374192 --- .../exoplayer2/ext/cast/CastPlayer.java | 101 +------------- .../exoplayer2/ext/ima/FakePlayer.java | 12 -- .../google/android/exoplayer2/BasePlayer.java | 132 ++++++++++++++++++ .../android/exoplayer2/ExoPlayerImpl.java | 104 +------------- .../android/exoplayer2/SimpleExoPlayer.java | 100 +------------ .../exoplayer2/testutil/StubExoPlayer.java | 79 +---------- 6 files changed, 140 insertions(+), 388 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index ecaa56427f..e5832f3b31 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ext.cast; import android.os.Looper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.google.android.exoplayer2.BasePlayer; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.PlaybackParameters; @@ -31,7 +32,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; -import com.google.android.exoplayer2.util.Util; import com.google.android.gms.cast.CastStatusCodes; import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaQueueItem; @@ -62,7 +62,7 @@ import java.util.concurrent.CopyOnWriteArraySet; * *

    Methods should be called on the application's main thread. */ -public final class CastPlayer implements Player { +public final class CastPlayer extends BasePlayer { private static final String TAG = "CastPlayer"; @@ -78,7 +78,6 @@ public final class CastPlayer implements Player { private final CastContext castContext; // TODO: Allow custom implementations of CastTimelineTracker. private final CastTimelineTracker timelineTracker; - private final Timeline.Window window; private final Timeline.Period period; private RemoteMediaClient remoteMediaClient; @@ -111,7 +110,6 @@ public final class CastPlayer implements Player { public CastPlayer(CastContext castContext) { this.castContext = castContext; timelineTracker = new CastTimelineTracker(); - window = new Timeline.Window(); period = new Timeline.Period(); statusListener = new StatusListener(); seekResultCallback = new SeekResultCallback(); @@ -324,21 +322,6 @@ public final class CastPlayer implements Player { return playWhenReady; } - @Override - public void seekToDefaultPosition() { - seekTo(0); - } - - @Override - public void seekToDefaultPosition(int windowIndex) { - seekTo(windowIndex, 0); - } - - @Override - public void seekTo(long positionMs) { - seekTo(getCurrentWindowIndex(), positionMs); - } - @Override public void seekTo(int windowIndex, long positionMs) { MediaStatus mediaStatus = getMediaStatus(); @@ -365,32 +348,6 @@ public final class CastPlayer implements Player { } } - @Override - public boolean hasPrevious() { - return getPreviousWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void previous() { - int previousWindowIndex = getPreviousWindowIndex(); - if (previousWindowIndex != C.INDEX_UNSET) { - seekToDefaultPosition(previousWindowIndex); - } - } - - @Override - public boolean hasNext() { - return getNextWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void next() { - int nextWindowIndex = getPreviousWindowIndex(); - if (nextWindowIndex != C.INDEX_UNSET) { - seekToDefaultPosition(nextWindowIndex); - } - } - @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { // Unsupported by the RemoteMediaClient API. Do nothing. @@ -401,11 +358,6 @@ public final class CastPlayer implements Player { return PlaybackParameters.DEFAULT; } - @Override - public void stop() { - stop(/* reset= */ false); - } - @Override public void stop(boolean reset) { playbackState = STATE_IDLE; @@ -495,32 +447,11 @@ public final class CastPlayer implements Player { return pendingSeekWindowIndex != C.INDEX_UNSET ? pendingSeekWindowIndex : currentWindowIndex; } - @Override - public int getNextWindowIndex() { - return currentTimeline.isEmpty() ? C.INDEX_UNSET - : currentTimeline.getNextWindowIndex(getCurrentWindowIndex(), repeatMode, false); - } - - @Override - public int getPreviousWindowIndex() { - return currentTimeline.isEmpty() ? C.INDEX_UNSET - : currentTimeline.getPreviousWindowIndex(getCurrentWindowIndex(), repeatMode, false); - } - - @Override - public @Nullable Object getCurrentTag() { - int windowIndex = getCurrentWindowIndex(); - return windowIndex >= currentTimeline.getWindowCount() - ? null - : currentTimeline.getWindow(windowIndex, window, /* setTag= */ true).tag; - } - // TODO: Fill the cast timeline information with ProgressListener's duration updates. // See [Internal: b/65152553]. @Override public long getDuration() { - return currentTimeline.isEmpty() ? C.TIME_UNSET - : currentTimeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + return getContentDuration(); } @Override @@ -537,15 +468,6 @@ public final class CastPlayer implements Player { return getCurrentPosition(); } - @Override - public int getBufferedPercentage() { - long position = getBufferedPosition(); - long duration = getDuration(); - return position == C.TIME_UNSET || duration == C.TIME_UNSET - ? 0 - : duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100); - } - @Override public long getTotalBufferedDuration() { long bufferedPosition = getBufferedPosition(); @@ -555,18 +477,6 @@ public final class CastPlayer implements Player { : bufferedPosition - currentPosition; } - @Override - public boolean isCurrentWindowDynamic() { - return !currentTimeline.isEmpty() - && currentTimeline.getWindow(getCurrentWindowIndex(), window).isDynamic; - } - - @Override - public boolean isCurrentWindowSeekable() { - return !currentTimeline.isEmpty() - && currentTimeline.getWindow(getCurrentWindowIndex(), window).isSeekable; - } - @Override public boolean isPlayingAd() { return false; @@ -582,11 +492,6 @@ public final class CastPlayer implements Player { return C.INDEX_UNSET; } - @Override - public long getContentDuration() { - return getDuration(); - } - @Override public boolean isLoading() { return false; diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java index 0c35c9b66d..b8024d6534 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java @@ -26,7 +26,6 @@ import java.util.ArrayList; /* package */ final class FakePlayer extends StubExoPlayer { private final ArrayList listeners; - private final Timeline.Window window; private final Timeline.Period period; private final Timeline timeline; @@ -41,7 +40,6 @@ import java.util.ArrayList; public FakePlayer() { listeners = new ArrayList<>(); - window = new Timeline.Window(); period = new Timeline.Period(); state = Player.STATE_IDLE; playWhenReady = true; @@ -151,16 +149,6 @@ import java.util.ArrayList; return 0; } - @Override - public int getNextWindowIndex() { - return C.INDEX_UNSET; - } - - @Override - public int getPreviousWindowIndex() { - return C.INDEX_UNSET; - } - @Override public long getDuration() { if (timeline.isEmpty()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java new file mode 100644 index 0000000000..6ff0853d9b --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import android.support.annotation.Nullable; +import com.google.android.exoplayer2.util.Util; + +/** Abstract base {@link Player} which implements common implementation independent methods. */ +public abstract class BasePlayer implements Player { + + protected final Timeline.Window window; + + public BasePlayer() { + window = new Timeline.Window(); + } + + @Override + public final void seekToDefaultPosition() { + seekToDefaultPosition(getCurrentWindowIndex()); + } + + @Override + public final void seekToDefaultPosition(int windowIndex) { + seekTo(windowIndex, /* positionMs= */ C.TIME_UNSET); + } + + @Override + public final void seekTo(long positionMs) { + seekTo(getCurrentWindowIndex(), positionMs); + } + + @Override + public final boolean hasPrevious() { + return getPreviousWindowIndex() != C.INDEX_UNSET; + } + + @Override + public final void previous() { + int previousWindowIndex = getPreviousWindowIndex(); + if (previousWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(previousWindowIndex); + } + } + + @Override + public final boolean hasNext() { + return getNextWindowIndex() != C.INDEX_UNSET; + } + + @Override + public final void next() { + int nextWindowIndex = getPreviousWindowIndex(); + if (nextWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(nextWindowIndex); + } + } + + @Override + public final void stop() { + stop(/* reset= */ false); + } + + @Override + public final int getNextWindowIndex() { + Timeline timeline = getCurrentTimeline(); + return timeline.isEmpty() + ? C.INDEX_UNSET + : timeline.getNextWindowIndex( + getCurrentWindowIndex(), getRepeatMode(), getShuffleModeEnabled()); + } + + @Override + public final int getPreviousWindowIndex() { + Timeline timeline = getCurrentTimeline(); + return timeline.isEmpty() + ? C.INDEX_UNSET + : timeline.getPreviousWindowIndex( + getCurrentWindowIndex(), getRepeatMode(), getShuffleModeEnabled()); + } + + @Override + @Nullable + public final Object getCurrentTag() { + int windowIndex = getCurrentWindowIndex(); + Timeline timeline = getCurrentTimeline(); + return windowIndex >= timeline.getWindowCount() + ? null + : timeline.getWindow(windowIndex, window, /* setTag= */ true).tag; + } + + @Override + public final int getBufferedPercentage() { + long position = getBufferedPosition(); + long duration = getDuration(); + return position == C.TIME_UNSET || duration == C.TIME_UNSET + ? 0 + : duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100); + } + + @Override + public final boolean isCurrentWindowDynamic() { + Timeline timeline = getCurrentTimeline(); + return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic; + } + + @Override + public final boolean isCurrentWindowSeekable() { + Timeline timeline = getCurrentTimeline(); + return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; + } + + @Override + public final long getContentDuration() { + Timeline timeline = getCurrentTimeline(); + return timeline.isEmpty() + ? C.TIME_UNSET + : timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 5d565cdc90..5fb6202d2f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -40,10 +40,8 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -/** - * An {@link ExoPlayer} implementation. Instances can be obtained from {@link ExoPlayerFactory}. - */ -/* package */ final class ExoPlayerImpl implements ExoPlayer { +/** An {@link ExoPlayer} implementation. Instances can be obtained from {@link ExoPlayerFactory}. */ +/* package */ final class ExoPlayerImpl extends BasePlayer implements ExoPlayer { private static final String TAG = "ExoPlayerImpl"; @@ -62,7 +60,6 @@ import java.util.concurrent.CopyOnWriteArraySet; private final ExoPlayerImplInternal internalPlayer; private final Handler internalPlayerHandler; private final CopyOnWriteArraySet listeners; - private final Timeline.Window window; private final Timeline.Period period; private final ArrayDeque pendingPlaybackInfoUpdates; @@ -119,7 +116,6 @@ import java.util.concurrent.CopyOnWriteArraySet; new RendererConfiguration[renderers.length], new TrackSelection[renderers.length], null); - window = new Timeline.Window(); period = new Timeline.Period(); playbackParameters = PlaybackParameters.DEFAULT; seekParameters = SeekParameters.DEFAULT; @@ -294,21 +290,6 @@ import java.util.concurrent.CopyOnWriteArraySet; return playbackInfo.isLoading; } - @Override - public void seekToDefaultPosition() { - seekToDefaultPosition(getCurrentWindowIndex()); - } - - @Override - public void seekToDefaultPosition(int windowIndex) { - seekTo(windowIndex, C.TIME_UNSET); - } - - @Override - public void seekTo(long positionMs) { - seekTo(getCurrentWindowIndex(), positionMs); - } - @Override public void seekTo(int windowIndex, long positionMs) { Timeline timeline = playbackInfo.timeline; @@ -349,32 +330,6 @@ import java.util.concurrent.CopyOnWriteArraySet; } } - @Override - public boolean hasPrevious() { - return getPreviousWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void previous() { - int previousWindowIndex = getPreviousWindowIndex(); - if (previousWindowIndex != C.INDEX_UNSET) { - seekToDefaultPosition(previousWindowIndex); - } - } - - @Override - public boolean hasNext() { - return getNextWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void next() { - int nextWindowIndex = getPreviousWindowIndex(); - if (nextWindowIndex != C.INDEX_UNSET) { - seekToDefaultPosition(nextWindowIndex); - } - } - @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { if (playbackParameters == null) { @@ -404,19 +359,6 @@ import java.util.concurrent.CopyOnWriteArraySet; return seekParameters; } - @Override - public @Nullable Object getCurrentTag() { - int windowIndex = getCurrentWindowIndex(); - return windowIndex >= playbackInfo.timeline.getWindowCount() - ? null - : playbackInfo.timeline.getWindow(windowIndex, window, /* setTag= */ true).tag; - } - - @Override - public void stop() { - stop(/* reset= */ false); - } - @Override public void stop(boolean reset) { if (reset) { @@ -521,20 +463,6 @@ import java.util.concurrent.CopyOnWriteArraySet; } } - @Override - public int getNextWindowIndex() { - Timeline timeline = playbackInfo.timeline; - return timeline.isEmpty() ? C.INDEX_UNSET - : timeline.getNextWindowIndex(getCurrentWindowIndex(), repeatMode, shuffleModeEnabled); - } - - @Override - public int getPreviousWindowIndex() { - Timeline timeline = playbackInfo.timeline; - return timeline.isEmpty() ? C.INDEX_UNSET - : timeline.getPreviousWindowIndex(getCurrentWindowIndex(), repeatMode, shuffleModeEnabled); - } - @Override public long getDuration() { if (isPlayingAd()) { @@ -567,32 +495,11 @@ import java.util.concurrent.CopyOnWriteArraySet; return getContentBufferedPosition(); } - @Override - public int getBufferedPercentage() { - long position = getBufferedPosition(); - long duration = getDuration(); - return position == C.TIME_UNSET || duration == C.TIME_UNSET - ? 0 - : (duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100)); - } - @Override public long getTotalBufferedDuration() { return Math.max(0, C.usToMs(playbackInfo.totalBufferedDurationUs)); } - @Override - public boolean isCurrentWindowDynamic() { - Timeline timeline = playbackInfo.timeline; - return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic; - } - - @Override - public boolean isCurrentWindowSeekable() { - Timeline timeline = playbackInfo.timeline; - return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; - } - @Override public boolean isPlayingAd() { return !shouldMaskPosition() && playbackInfo.periodId.isAd(); @@ -608,13 +515,6 @@ import java.util.concurrent.CopyOnWriteArraySet; return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; } - @Override - public long getContentDuration() { - return playbackInfo.timeline.isEmpty() - ? C.TIME_UNSET - : playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); - } - @Override public long getContentPosition() { if (isPlayingAd()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 7e5feec024..8517556887 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -64,7 +64,7 @@ import java.util.concurrent.CopyOnWriteArraySet; * be obtained from {@link ExoPlayerFactory}. */ @TargetApi(16) -public class SimpleExoPlayer +public class SimpleExoPlayer extends BasePlayer implements ExoPlayer, Player.AudioComponent, Player.VideoComponent, Player.TextComponent { /** @deprecated Use {@link com.google.android.exoplayer2.video.VideoListener}. */ @@ -927,27 +927,6 @@ public class SimpleExoPlayer return player.isLoading(); } - @Override - public void seekToDefaultPosition() { - verifyApplicationThread(); - analyticsCollector.notifySeekStarted(); - player.seekToDefaultPosition(); - } - - @Override - public void seekToDefaultPosition(int windowIndex) { - verifyApplicationThread(); - analyticsCollector.notifySeekStarted(); - player.seekToDefaultPosition(windowIndex); - } - - @Override - public void seekTo(long positionMs) { - verifyApplicationThread(); - analyticsCollector.notifySeekStarted(); - player.seekTo(positionMs); - } - @Override public void seekTo(int windowIndex, long positionMs) { verifyApplicationThread(); @@ -955,36 +934,6 @@ public class SimpleExoPlayer player.seekTo(windowIndex, positionMs); } - @Override - public boolean hasPrevious() { - verifyApplicationThread(); - return getPreviousWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void previous() { - verifyApplicationThread(); - if (hasPrevious()) { - analyticsCollector.notifySeekStarted(); - player.previous(); - } - } - - @Override - public boolean hasNext() { - verifyApplicationThread(); - return getNextWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void next() { - verifyApplicationThread(); - if (hasNext()) { - analyticsCollector.notifySeekStarted(); - player.next(); - } - } - @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { verifyApplicationThread(); @@ -1009,17 +958,6 @@ public class SimpleExoPlayer return player.getSeekParameters(); } - @Override - public @Nullable Object getCurrentTag() { - verifyApplicationThread(); - return player.getCurrentTag(); - } - - @Override - public void stop() { - stop(/* reset= */ false); - } - @Override public void stop(boolean reset) { verifyApplicationThread(); @@ -1122,18 +1060,6 @@ public class SimpleExoPlayer return player.getCurrentWindowIndex(); } - @Override - public int getNextWindowIndex() { - verifyApplicationThread(); - return player.getNextWindowIndex(); - } - - @Override - public int getPreviousWindowIndex() { - verifyApplicationThread(); - return player.getPreviousWindowIndex(); - } - @Override public long getDuration() { verifyApplicationThread(); @@ -1152,30 +1078,12 @@ public class SimpleExoPlayer return player.getBufferedPosition(); } - @Override - public int getBufferedPercentage() { - verifyApplicationThread(); - return player.getBufferedPercentage(); - } - @Override public long getTotalBufferedDuration() { verifyApplicationThread(); return player.getTotalBufferedDuration(); } - @Override - public boolean isCurrentWindowDynamic() { - verifyApplicationThread(); - return player.isCurrentWindowDynamic(); - } - - @Override - public boolean isCurrentWindowSeekable() { - verifyApplicationThread(); - return player.isCurrentWindowSeekable(); - } - @Override public boolean isPlayingAd() { verifyApplicationThread(); @@ -1194,12 +1102,6 @@ public class SimpleExoPlayer return player.getCurrentAdIndexInAdGroup(); } - @Override - public long getContentDuration() { - verifyApplicationThread(); - return player.getContentDuration(); - } - @Override public long getContentPosition() { verifyApplicationThread(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 7882c7d16c..156b573df8 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -16,7 +16,7 @@ package com.google.android.exoplayer2.testutil; import android.os.Looper; -import android.support.annotation.Nullable; +import com.google.android.exoplayer2.BasePlayer; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.PlaybackParameters; @@ -32,7 +32,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; * An abstract {@link ExoPlayer} implementation that throws {@link UnsupportedOperationException} * from every method. */ -public abstract class StubExoPlayer implements ExoPlayer { +public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { @Override public AudioComponent getAudioComponent() { @@ -129,46 +129,11 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public void seekToDefaultPosition() { - throw new UnsupportedOperationException(); - } - - @Override - public void seekToDefaultPosition(int windowIndex) { - throw new UnsupportedOperationException(); - } - - @Override - public void seekTo(long positionMs) { - throw new UnsupportedOperationException(); - } - @Override public void seekTo(int windowIndex, long positionMs) { throw new UnsupportedOperationException(); } - @Override - public boolean hasPrevious() { - throw new UnsupportedOperationException(); - } - - @Override - public void previous() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasNext() { - throw new UnsupportedOperationException(); - } - - @Override - public void next() { - throw new UnsupportedOperationException(); - } - @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { throw new UnsupportedOperationException(); @@ -189,16 +154,6 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public @Nullable Object getCurrentTag() { - throw new UnsupportedOperationException(); - } - - @Override - public void stop() { - throw new UnsupportedOperationException(); - } - @Override public void stop(boolean resetStateAndPosition) { throw new UnsupportedOperationException(); @@ -268,16 +223,6 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public int getNextWindowIndex() { - throw new UnsupportedOperationException(); - } - - @Override - public int getPreviousWindowIndex() { - throw new UnsupportedOperationException(); - } - @Override public long getDuration() { throw new UnsupportedOperationException(); @@ -293,26 +238,11 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public int getBufferedPercentage() { - throw new UnsupportedOperationException(); - } - @Override public long getTotalBufferedDuration() { throw new UnsupportedOperationException(); } - @Override - public boolean isCurrentWindowDynamic() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isCurrentWindowSeekable() { - throw new UnsupportedOperationException(); - } - @Override public boolean isPlayingAd() { throw new UnsupportedOperationException(); @@ -328,11 +258,6 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public long getContentDuration() { - throw new UnsupportedOperationException(); - } - @Override public long getContentPosition() { throw new UnsupportedOperationException(); From 34c286682e19d11d7989d94c346eb00b425f78de Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 2 Oct 2018 08:04:50 -0700 Subject: [PATCH 048/832] Extract PlayerManager interface in the Cast demo app ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215389291 --- .../DefaultReceiverPlayerManager.java | 408 ++++++++++++++++++ .../exoplayer2/castdemo/MainActivity.java | 23 +- .../exoplayer2/castdemo/PlayerManager.java | 406 ++--------------- 3 files changed, 449 insertions(+), 388 deletions(-) create mode 100644 demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java new file mode 100644 index 0000000000..f30d3e602f --- /dev/null +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.castdemo; + +import android.content.Context; +import android.net.Uri; +import android.support.annotation.Nullable; +import android.view.KeyEvent; +import android.view.View; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.DefaultRenderersFactory; +import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Player.DiscontinuityReason; +import com.google.android.exoplayer2.Player.EventListener; +import com.google.android.exoplayer2.Player.TimelineChangeReason; +import com.google.android.exoplayer2.RenderersFactory; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.Timeline.Period; +import com.google.android.exoplayer2.ext.cast.CastPlayer; +import com.google.android.exoplayer2.ext.cast.MediaItem; +import com.google.android.exoplayer2.ext.cast.RemotePlayer; +import com.google.android.exoplayer2.source.ConcatenatingMediaSource; +import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.dash.DashMediaSource; +import com.google.android.exoplayer2.source.hls.HlsMediaSource; +import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.ui.PlayerControlView; +import com.google.android.exoplayer2.ui.PlayerView; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.google.android.gms.cast.MediaInfo; +import com.google.android.gms.cast.MediaMetadata; +import com.google.android.gms.cast.MediaQueueItem; +import com.google.android.gms.cast.framework.CastContext; +import java.util.ArrayList; + +/** Manages players and an internal media queue for the ExoPlayer/Cast demo app. */ +/* package */ class DefaultReceiverPlayerManager + implements EventListener, RemotePlayer.SessionAvailabilityListener, PlayerManager { + + private static final String USER_AGENT = "ExoCastDemoPlayer"; + private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY = + new DefaultHttpDataSourceFactory(USER_AGENT); + + private final PlayerView localPlayerView; + private final PlayerControlView castControlView; + private final SimpleExoPlayer exoPlayer; + private final CastPlayer castPlayer; + private final ArrayList mediaQueue; + private final QueuePositionListener queuePositionListener; + private final ConcatenatingMediaSource concatenatingMediaSource; + + private boolean castMediaQueueCreationPending; + private int currentItemIndex; + private Player currentPlayer; + + /** + * @param queuePositionListener A {@link QueuePositionListener} for queue position changes. + * @param localPlayerView The {@link PlayerView} for local playback. + * @param castControlView The {@link PlayerControlView} to control remote playback. + * @param context A {@link Context}. + * @param castContext The {@link CastContext}. + */ + public static DefaultReceiverPlayerManager createPlayerManager( + QueuePositionListener queuePositionListener, + PlayerView localPlayerView, + PlayerControlView castControlView, + Context context, + CastContext castContext) { + DefaultReceiverPlayerManager defaultReceiverPlayerManager = + new DefaultReceiverPlayerManager( + queuePositionListener, localPlayerView, castControlView, context, castContext); + defaultReceiverPlayerManager.init(); + return defaultReceiverPlayerManager; + } + + private DefaultReceiverPlayerManager( + QueuePositionListener queuePositionListener, + PlayerView localPlayerView, + PlayerControlView castControlView, + Context context, + CastContext castContext) { + this.queuePositionListener = queuePositionListener; + this.localPlayerView = localPlayerView; + this.castControlView = castControlView; + mediaQueue = new ArrayList<>(); + currentItemIndex = C.INDEX_UNSET; + concatenatingMediaSource = new ConcatenatingMediaSource(); + + DefaultTrackSelector trackSelector = new DefaultTrackSelector(); + RenderersFactory renderersFactory = new DefaultRenderersFactory(context); + exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector); + exoPlayer.addListener(this); + localPlayerView.setPlayer(exoPlayer); + + castPlayer = new CastPlayer(castContext); + castPlayer.addListener(this); + castPlayer.setSessionAvailabilityListener(this); + castControlView.setPlayer(castPlayer); + } + + // Queue manipulation methods. + + /** + * Plays a specified queue item in the current player. + * + * @param itemIndex The index of the item to play. + */ + public void selectQueueItem(int itemIndex) { + setCurrentItem(itemIndex, C.TIME_UNSET, true); + } + + /** + * Returns the index of the currently played item. + */ + public int getCurrentItemIndex() { + return currentItemIndex; + } + + /** + * Appends {@code item} to the media queue. + * + * @param item The {@link MediaItem} to append. + */ + public void addItem(MediaItem item) { + mediaQueue.add(item); + concatenatingMediaSource.addMediaSource(buildMediaSource(item)); + if (currentPlayer == castPlayer) { + castPlayer.addItems(buildMediaQueueItem(item)); + } + } + + /** + * Returns the size of the media queue. + */ + public int getMediaQueueSize() { + return mediaQueue.size(); + } + + /** + * Returns the item at the given index in the media queue. + * + * @param position The index of the item. + * @return The item at the given index in the media queue. + */ + public MediaItem getItem(int position) { + return mediaQueue.get(position); + } + + /** + * Removes the item at the given index from the media queue. + * + * @param itemIndex The index of the item to remove. + * @return Whether the removal was successful. + */ + public boolean removeItem(int itemIndex) { + concatenatingMediaSource.removeMediaSource(itemIndex); + if (currentPlayer == castPlayer) { + if (castPlayer.getPlaybackState() != Player.STATE_IDLE) { + Timeline castTimeline = castPlayer.getCurrentTimeline(); + if (castTimeline.getPeriodCount() <= itemIndex) { + return false; + } + castPlayer.removeItem((int) castTimeline.getPeriod(itemIndex, new Period()).id); + } + } + mediaQueue.remove(itemIndex); + if (itemIndex == currentItemIndex && itemIndex == mediaQueue.size()) { + maybeSetCurrentItemAndNotify(C.INDEX_UNSET); + } else if (itemIndex < currentItemIndex) { + maybeSetCurrentItemAndNotify(currentItemIndex - 1); + } + return true; + } + + /** + * Moves an item within the queue. + * + * @param fromIndex The index of the item to move. + * @param toIndex The target index of the item in the queue. + * @return Whether the item move was successful. + */ + public boolean moveItem(int fromIndex, int toIndex) { + // Player update. + concatenatingMediaSource.moveMediaSource(fromIndex, toIndex); + if (currentPlayer == castPlayer && castPlayer.getPlaybackState() != Player.STATE_IDLE) { + Timeline castTimeline = castPlayer.getCurrentTimeline(); + int periodCount = castTimeline.getPeriodCount(); + if (periodCount <= fromIndex || periodCount <= toIndex) { + return false; + } + int elementId = (int) castTimeline.getPeriod(fromIndex, new Period()).id; + castPlayer.moveItem(elementId, toIndex); + } + + mediaQueue.add(toIndex, mediaQueue.remove(fromIndex)); + + // Index update. + if (fromIndex == currentItemIndex) { + maybeSetCurrentItemAndNotify(toIndex); + } else if (fromIndex < currentItemIndex && toIndex >= currentItemIndex) { + maybeSetCurrentItemAndNotify(currentItemIndex - 1); + } else if (fromIndex > currentItemIndex && toIndex <= currentItemIndex) { + maybeSetCurrentItemAndNotify(currentItemIndex + 1); + } + + return true; + } + + // Miscellaneous methods. + + /** + * Dispatches a given {@link KeyEvent} to the corresponding view of the current player. + * + * @param event The {@link KeyEvent}. + * @return Whether the event was handled by the target view. + */ + public boolean dispatchKeyEvent(KeyEvent event) { + if (currentPlayer == exoPlayer) { + return localPlayerView.dispatchKeyEvent(event); + } else /* currentPlayer == castPlayer */ { + return castControlView.dispatchKeyEvent(event); + } + } + + /** + * Releases the manager and the players that it holds. + */ + public void release() { + currentItemIndex = C.INDEX_UNSET; + mediaQueue.clear(); + concatenatingMediaSource.clear(); + castPlayer.setSessionAvailabilityListener(null); + castPlayer.release(); + localPlayerView.setPlayer(null); + exoPlayer.release(); + } + + // Player.EventListener implementation. + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + updateCurrentItemIndex(); + } + + @Override + public void onPositionDiscontinuity(@DiscontinuityReason int reason) { + updateCurrentItemIndex(); + } + + @Override + public void onTimelineChanged( + Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) { + updateCurrentItemIndex(); + if (timeline.isEmpty()) { + castMediaQueueCreationPending = true; + } + } + + // CastPlayer.SessionAvailabilityListener implementation. + + @Override + public void onCastSessionAvailable() { + setCurrentPlayer(castPlayer); + } + + @Override + public void onCastSessionUnavailable() { + setCurrentPlayer(exoPlayer); + } + + // Internal methods. + + private void init() { + setCurrentPlayer(castPlayer.isCastSessionAvailable() ? castPlayer : exoPlayer); + } + + private void updateCurrentItemIndex() { + int playbackState = currentPlayer.getPlaybackState(); + maybeSetCurrentItemAndNotify( + playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED + ? currentPlayer.getCurrentWindowIndex() : C.INDEX_UNSET); + } + + private void setCurrentPlayer(Player currentPlayer) { + if (this.currentPlayer == currentPlayer) { + return; + } + + // View management. + if (currentPlayer == exoPlayer) { + localPlayerView.setVisibility(View.VISIBLE); + castControlView.hide(); + } else /* currentPlayer == castPlayer */ { + localPlayerView.setVisibility(View.GONE); + castControlView.show(); + } + + // Player state management. + long playbackPositionMs = C.TIME_UNSET; + int windowIndex = C.INDEX_UNSET; + boolean playWhenReady = false; + if (this.currentPlayer != null) { + int playbackState = this.currentPlayer.getPlaybackState(); + if (playbackState != Player.STATE_ENDED) { + playbackPositionMs = this.currentPlayer.getCurrentPosition(); + playWhenReady = this.currentPlayer.getPlayWhenReady(); + windowIndex = this.currentPlayer.getCurrentWindowIndex(); + if (windowIndex != currentItemIndex) { + playbackPositionMs = C.TIME_UNSET; + windowIndex = currentItemIndex; + } + } + this.currentPlayer.stop(true); + } else { + // This is the initial setup. No need to save any state. + } + + this.currentPlayer = currentPlayer; + + // Media queue management. + castMediaQueueCreationPending = currentPlayer == castPlayer; + if (currentPlayer == exoPlayer) { + exoPlayer.prepare(concatenatingMediaSource); + } + + // Playback transition. + if (windowIndex != C.INDEX_UNSET) { + setCurrentItem(windowIndex, playbackPositionMs, playWhenReady); + } + } + + /** + * Starts playback of the item at the given position. + * + * @param itemIndex The index of the item to play. + * @param positionMs The position at which playback should start. + * @param playWhenReady Whether the player should proceed when ready to do so. + */ + private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) { + maybeSetCurrentItemAndNotify(itemIndex); + if (castMediaQueueCreationPending) { + MediaQueueItem[] items = new MediaQueueItem[mediaQueue.size()]; + for (int i = 0; i < items.length; i++) { + items[i] = buildMediaQueueItem(mediaQueue.get(i)); + } + castMediaQueueCreationPending = false; + castPlayer.loadItems(items, itemIndex, positionMs, Player.REPEAT_MODE_OFF); + } else { + currentPlayer.seekTo(itemIndex, positionMs); + currentPlayer.setPlayWhenReady(playWhenReady); + } + } + + private void maybeSetCurrentItemAndNotify(int currentItemIndex) { + if (this.currentItemIndex != currentItemIndex) { + int oldIndex = this.currentItemIndex; + this.currentItemIndex = currentItemIndex; + queuePositionListener.onQueuePositionChanged(oldIndex, currentItemIndex); + } + } + + private static MediaSource buildMediaSource(MediaItem item) { + Uri uri = item.media.uri; + switch (item.mimeType) { + case DemoUtil.MIME_TYPE_SS: + return new SsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); + case DemoUtil.MIME_TYPE_DASH: + return new DashMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); + case DemoUtil.MIME_TYPE_HLS: + return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); + case DemoUtil.MIME_TYPE_VIDEO_MP4: + return new ExtractorMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); + default: { + throw new IllegalStateException("Unsupported type: " + item.mimeType); + } + } + } + + private static MediaQueueItem buildMediaQueueItem(MediaItem item) { + MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); + movieMetadata.putString(MediaMetadata.KEY_TITLE, item.title); + MediaInfo mediaInfo = + new MediaInfo.Builder(item.media.uri.toString()) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setContentType(item.mimeType) + .setMetadata(movieMetadata) + .build(); + return new MediaQueueItem.Builder(mediaInfo).build(); + } + +} diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java index 44305afb5b..8ebfee1294 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java @@ -39,6 +39,7 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.ext.cast.MediaItem; import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.ui.PlayerView; +import com.google.android.gms.cast.CastMediaControlIntent; import com.google.android.gms.cast.framework.CastButtonFactory; import com.google.android.gms.cast.framework.CastContext; @@ -92,13 +93,20 @@ public class MainActivity extends AppCompatActivity @Override public void onResume() { super.onResume(); - playerManager = - PlayerManager.createPlayerManager( - /* queuePositionListener= */ this, - localPlayerView, - castControlView, - /* context= */ this, - castContext); + String applicationId = castContext.getCastOptions().getReceiverApplicationId(); + switch (applicationId) { + case CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID: + playerManager = + DefaultReceiverPlayerManager.createPlayerManager( + /* queuePositionListener= */ this, + localPlayerView, + castControlView, + /* context= */ this, + castContext); + break; + default: + throw new IllegalStateException("Illegal receiver app id: " + applicationId); + } mediaQueueList.setAdapter(mediaQueueListAdapter); } @@ -108,6 +116,7 @@ public class MainActivity extends AppCompatActivity mediaQueueListAdapter.notifyItemRangeRemoved(0, mediaQueueListAdapter.getItemCount()); mediaQueueList.setAdapter(null); playerManager.release(); + playerManager = null; } // Activity input. diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java index eb4808d1ed..c56f0eb855 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,49 +15,15 @@ */ package com.google.android.exoplayer2.castdemo; -import android.content.Context; -import android.net.Uri; -import android.support.annotation.Nullable; import android.view.KeyEvent; -import android.view.View; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.DefaultRenderersFactory; -import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.Player.DiscontinuityReason; -import com.google.android.exoplayer2.Player.EventListener; -import com.google.android.exoplayer2.Player.TimelineChangeReason; -import com.google.android.exoplayer2.RenderersFactory; -import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.Timeline.Period; -import com.google.android.exoplayer2.ext.cast.CastPlayer; import com.google.android.exoplayer2.ext.cast.MediaItem; -import com.google.android.exoplayer2.ext.cast.RemotePlayer; -import com.google.android.exoplayer2.source.ConcatenatingMediaSource; -import com.google.android.exoplayer2.source.ExtractorMediaSource; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.dash.DashMediaSource; -import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.ui.PlayerControlView; -import com.google.android.exoplayer2.ui.PlayerView; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; -import com.google.android.gms.cast.MediaInfo; -import com.google.android.gms.cast.MediaMetadata; -import com.google.android.gms.cast.MediaQueueItem; -import com.google.android.gms.cast.framework.CastContext; -import java.util.ArrayList; -/** Manages players and an internal media queue for the ExoPlayer/Cast demo app. */ -/* package */ final class PlayerManager - implements EventListener, RemotePlayer.SessionAvailabilityListener { +/** Manages the players in the Cast demo app. */ +interface PlayerManager { - /** - * Listener for changes in the media queue playback position. - */ - public interface QueuePositionListener { + /** Listener for changes in the media queue playback position. */ + interface QueuePositionListener { /** * Called when the currently played item of the media queue changes. @@ -66,355 +32,33 @@ import java.util.ArrayList; } - private static final String USER_AGENT = "ExoCastDemoPlayer"; - private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY = - new DefaultHttpDataSourceFactory(USER_AGENT); + /** Redirects the given {@code keyEvent} to the active player. */ + boolean dispatchKeyEvent(KeyEvent keyEvent); - private final PlayerView localPlayerView; - private final PlayerControlView castControlView; - private final SimpleExoPlayer exoPlayer; - private final CastPlayer castPlayer; - private final ArrayList mediaQueue; - private final QueuePositionListener queuePositionListener; - private final ConcatenatingMediaSource concatenatingMediaSource; + /** Appends the given {@link MediaItem} to the media queue. */ + void addItem(MediaItem mediaItem); - private boolean castMediaQueueCreationPending; - private int currentItemIndex; - private Player currentPlayer; + /** Returns the number of items in the media queue. */ + int getMediaQueueSize(); + + /** Selects the item at the given position for playback. */ + void selectQueueItem(int position); /** - * @param queuePositionListener A {@link QueuePositionListener} for queue position changes. - * @param localPlayerView The {@link PlayerView} for local playback. - * @param castControlView The {@link PlayerControlView} to control remote playback. - * @param context A {@link Context}. - * @param castContext The {@link CastContext}. + * Returns the position of the item currently being played, or {@link C#INDEX_UNSET} if no item is + * being played. */ - public static PlayerManager createPlayerManager( - QueuePositionListener queuePositionListener, - PlayerView localPlayerView, - PlayerControlView castControlView, - Context context, - CastContext castContext) { - PlayerManager playerManager = - new PlayerManager( - queuePositionListener, localPlayerView, castControlView, context, castContext); - playerManager.init(); - return playerManager; - } + int getCurrentItemIndex(); - private PlayerManager( - QueuePositionListener queuePositionListener, - PlayerView localPlayerView, - PlayerControlView castControlView, - Context context, - CastContext castContext) { - this.queuePositionListener = queuePositionListener; - this.localPlayerView = localPlayerView; - this.castControlView = castControlView; - mediaQueue = new ArrayList<>(); - currentItemIndex = C.INDEX_UNSET; - concatenatingMediaSource = new ConcatenatingMediaSource(); + /** Returns the {@link MediaItem} at the given {@code position}. */ + MediaItem getItem(int position); - DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - RenderersFactory renderersFactory = new DefaultRenderersFactory(context); - exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector); - exoPlayer.addListener(this); - localPlayerView.setPlayer(exoPlayer); + /** Moves the item at position {@code from} to position {@code to}. */ + boolean moveItem(int from, int to); - castPlayer = new CastPlayer(castContext); - castPlayer.addListener(this); - castPlayer.setSessionAvailabilityListener(this); - castControlView.setPlayer(castPlayer); - } - - // Queue manipulation methods. - - /** - * Plays a specified queue item in the current player. - * - * @param itemIndex The index of the item to play. - */ - public void selectQueueItem(int itemIndex) { - setCurrentItem(itemIndex, C.TIME_UNSET, true); - } - - /** - * Returns the index of the currently played item. - */ - public int getCurrentItemIndex() { - return currentItemIndex; - } - - /** - * Appends {@code item} to the media queue. - * - * @param item The {@link MediaItem} to append. - */ - public void addItem(MediaItem item) { - mediaQueue.add(item); - concatenatingMediaSource.addMediaSource(buildMediaSource(item)); - if (currentPlayer == castPlayer) { - castPlayer.addItems(buildMediaQueueItem(item)); - } - } - - /** - * Returns the size of the media queue. - */ - public int getMediaQueueSize() { - return mediaQueue.size(); - } - - /** - * Returns the item at the given index in the media queue. - * - * @param position The index of the item. - * @return The item at the given index in the media queue. - */ - public MediaItem getItem(int position) { - return mediaQueue.get(position); - } - - /** - * Removes the item at the given index from the media queue. - * - * @param itemIndex The index of the item to remove. - * @return Whether the removal was successful. - */ - public boolean removeItem(int itemIndex) { - concatenatingMediaSource.removeMediaSource(itemIndex); - if (currentPlayer == castPlayer) { - if (castPlayer.getPlaybackState() != Player.STATE_IDLE) { - Timeline castTimeline = castPlayer.getCurrentTimeline(); - if (castTimeline.getPeriodCount() <= itemIndex) { - return false; - } - castPlayer.removeItem((int) castTimeline.getPeriod(itemIndex, new Period()).id); - } - } - mediaQueue.remove(itemIndex); - if (itemIndex == currentItemIndex && itemIndex == mediaQueue.size()) { - maybeSetCurrentItemAndNotify(C.INDEX_UNSET); - } else if (itemIndex < currentItemIndex) { - maybeSetCurrentItemAndNotify(currentItemIndex - 1); - } - return true; - } - - /** - * Moves an item within the queue. - * - * @param fromIndex The index of the item to move. - * @param toIndex The target index of the item in the queue. - * @return Whether the item move was successful. - */ - public boolean moveItem(int fromIndex, int toIndex) { - // Player update. - concatenatingMediaSource.moveMediaSource(fromIndex, toIndex); - if (currentPlayer == castPlayer && castPlayer.getPlaybackState() != Player.STATE_IDLE) { - Timeline castTimeline = castPlayer.getCurrentTimeline(); - int periodCount = castTimeline.getPeriodCount(); - if (periodCount <= fromIndex || periodCount <= toIndex) { - return false; - } - int elementId = (int) castTimeline.getPeriod(fromIndex, new Period()).id; - castPlayer.moveItem(elementId, toIndex); - } - - mediaQueue.add(toIndex, mediaQueue.remove(fromIndex)); - - // Index update. - if (fromIndex == currentItemIndex) { - maybeSetCurrentItemAndNotify(toIndex); - } else if (fromIndex < currentItemIndex && toIndex >= currentItemIndex) { - maybeSetCurrentItemAndNotify(currentItemIndex - 1); - } else if (fromIndex > currentItemIndex && toIndex <= currentItemIndex) { - maybeSetCurrentItemAndNotify(currentItemIndex + 1); - } - - return true; - } - - // Miscellaneous methods. - - /** - * Dispatches a given {@link KeyEvent} to the corresponding view of the current player. - * - * @param event The {@link KeyEvent}. - * @return Whether the event was handled by the target view. - */ - public boolean dispatchKeyEvent(KeyEvent event) { - if (currentPlayer == exoPlayer) { - return localPlayerView.dispatchKeyEvent(event); - } else /* currentPlayer == castPlayer */ { - return castControlView.dispatchKeyEvent(event); - } - } - - /** - * Releases the manager and the players that it holds. - */ - public void release() { - currentItemIndex = C.INDEX_UNSET; - mediaQueue.clear(); - concatenatingMediaSource.clear(); - castPlayer.setSessionAvailabilityListener(null); - castPlayer.release(); - localPlayerView.setPlayer(null); - exoPlayer.release(); - } - - // Player.EventListener implementation. - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - updateCurrentItemIndex(); - } - - @Override - public void onPositionDiscontinuity(@DiscontinuityReason int reason) { - updateCurrentItemIndex(); - } - - @Override - public void onTimelineChanged( - Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) { - updateCurrentItemIndex(); - if (timeline.isEmpty()) { - castMediaQueueCreationPending = true; - } - } - - // CastPlayer.SessionAvailabilityListener implementation. - - @Override - public void onCastSessionAvailable() { - setCurrentPlayer(castPlayer); - } - - @Override - public void onCastSessionUnavailable() { - setCurrentPlayer(exoPlayer); - } - - // Internal methods. - - private void init() { - setCurrentPlayer(castPlayer.isCastSessionAvailable() ? castPlayer : exoPlayer); - } - - private void updateCurrentItemIndex() { - int playbackState = currentPlayer.getPlaybackState(); - maybeSetCurrentItemAndNotify( - playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED - ? currentPlayer.getCurrentWindowIndex() : C.INDEX_UNSET); - } - - private void setCurrentPlayer(Player currentPlayer) { - if (this.currentPlayer == currentPlayer) { - return; - } - - // View management. - if (currentPlayer == exoPlayer) { - localPlayerView.setVisibility(View.VISIBLE); - castControlView.hide(); - } else /* currentPlayer == castPlayer */ { - localPlayerView.setVisibility(View.GONE); - castControlView.show(); - } - - // Player state management. - long playbackPositionMs = C.TIME_UNSET; - int windowIndex = C.INDEX_UNSET; - boolean playWhenReady = false; - if (this.currentPlayer != null) { - int playbackState = this.currentPlayer.getPlaybackState(); - if (playbackState != Player.STATE_ENDED) { - playbackPositionMs = this.currentPlayer.getCurrentPosition(); - playWhenReady = this.currentPlayer.getPlayWhenReady(); - windowIndex = this.currentPlayer.getCurrentWindowIndex(); - if (windowIndex != currentItemIndex) { - playbackPositionMs = C.TIME_UNSET; - windowIndex = currentItemIndex; - } - } - this.currentPlayer.stop(true); - } else { - // This is the initial setup. No need to save any state. - } - - this.currentPlayer = currentPlayer; - - // Media queue management. - castMediaQueueCreationPending = currentPlayer == castPlayer; - if (currentPlayer == exoPlayer) { - exoPlayer.prepare(concatenatingMediaSource); - } - - // Playback transition. - if (windowIndex != C.INDEX_UNSET) { - setCurrentItem(windowIndex, playbackPositionMs, playWhenReady); - } - } - - /** - * Starts playback of the item at the given position. - * - * @param itemIndex The index of the item to play. - * @param positionMs The position at which playback should start. - * @param playWhenReady Whether the player should proceed when ready to do so. - */ - private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) { - maybeSetCurrentItemAndNotify(itemIndex); - if (castMediaQueueCreationPending) { - MediaQueueItem[] items = new MediaQueueItem[mediaQueue.size()]; - for (int i = 0; i < items.length; i++) { - items[i] = buildMediaQueueItem(mediaQueue.get(i)); - } - castMediaQueueCreationPending = false; - castPlayer.loadItems(items, itemIndex, positionMs, Player.REPEAT_MODE_OFF); - } else { - currentPlayer.seekTo(itemIndex, positionMs); - currentPlayer.setPlayWhenReady(playWhenReady); - } - } - - private void maybeSetCurrentItemAndNotify(int currentItemIndex) { - if (this.currentItemIndex != currentItemIndex) { - int oldIndex = this.currentItemIndex; - this.currentItemIndex = currentItemIndex; - queuePositionListener.onQueuePositionChanged(oldIndex, currentItemIndex); - } - } - - private static MediaSource buildMediaSource(MediaItem item) { - Uri uri = item.media.uri; - switch (item.mimeType) { - case DemoUtil.MIME_TYPE_SS: - return new SsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); - case DemoUtil.MIME_TYPE_DASH: - return new DashMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); - case DemoUtil.MIME_TYPE_HLS: - return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); - case DemoUtil.MIME_TYPE_VIDEO_MP4: - return new ExtractorMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); - default: { - throw new IllegalStateException("Unsupported type: " + item.mimeType); - } - } - } - - private static MediaQueueItem buildMediaQueueItem(MediaItem item) { - MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); - movieMetadata.putString(MediaMetadata.KEY_TITLE, item.title); - MediaInfo mediaInfo = - new MediaInfo.Builder(item.media.uri.toString()) - .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) - .setContentType(item.mimeType) - .setMetadata(movieMetadata) - .build(); - return new MediaQueueItem.Builder(mediaInfo).build(); - } + /** Removes the item at position {@code index}. */ + boolean removeItem(int index); + /** Releases any acquired resources. */ + void release(); } From 4386d5d4660d26965f99de1b6247dff7f2215c11 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Oct 2018 09:41:59 -0700 Subject: [PATCH 049/832] Replace DefaultBandwidthMeter with CountryAndNetworkTypeBandwidthMeter. This removes the experimental bandwidth meter and uses it as the new default. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215404065 --- RELEASENOTES.md | 2 + .../CountryAndNetworkTypeBandwidthMeter.java | 592 ------------------ .../upstream/DefaultBandwidthMeter.java | 376 ++++++++++- ...st.java => DefaultBandwidthMeterTest.java} | 181 +++--- 4 files changed, 453 insertions(+), 698 deletions(-) delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeter.java rename library/core/src/test/java/com/google/android/exoplayer2/upstream/{CountryAndNetworkTypeBandwidthMeterTest.java => DefaultBandwidthMeterTest.java} (69%) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index adfec3dacc..2e42b9a709 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### dev-v2 (not yet released) ### +* Improve initial bandwidth meter estimates using the current country and + network type. * Do not retry failed loads whose error is `FileNotFoundException`. * Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext` and `Player.hasPrevious` diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeter.java deleted file mode 100644 index 7dce8a5049..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeter.java +++ /dev/null @@ -1,592 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.upstream; - -import android.content.Context; -import android.os.Handler; -import android.support.annotation.Nullable; -import android.util.SparseArray; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.Clock; -import com.google.android.exoplayer2.util.ClosedSource; -import com.google.android.exoplayer2.util.EventDispatcher; -import com.google.android.exoplayer2.util.SlidingPercentile; -import com.google.android.exoplayer2.util.Util; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * Estimates bandwidth by listening to data transfers. - * - *

    The bandwidth estimate is calculated using a {@link SlidingPercentile} and is updated each - * time a transfer ends. The initial estimate is based on the current operator's network country - * code or the locale of the user, as well as the network connection type. This can be configured in - * the {@link Builder}. - */ -@ClosedSource(reason = "Needs testing") -public final class CountryAndNetworkTypeBandwidthMeter implements BandwidthMeter, TransferListener { - - /** - * Country groups used to determine the default initial bitrate estimate. The group assignment for - * each country is an array of group indices for [Wifi, 2G, 3G, 4G]. - */ - public static final Map DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS = - createInitialBitrateCountryGroupAssignment(); - - /** Default initial Wifi bitrate estimate in bits per second. */ - public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI = - new long[] {5_700_000, 3_400_000, 1_900_000, 1_000_000, 400_000}; - - /** Default initial 2G bitrate estimates in bits per second. */ - public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_2G = - new long[] {169_000, 129_000, 114_000, 102_000, 87_000}; - - /** Default initial 3G bitrate estimates in bits per second. */ - public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_3G = - new long[] {2_100_000, 1_300_000, 950_000, 700_000, 400_000}; - - /** Default initial 4G bitrate estimates in bits per second. */ - public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_4G = - new long[] {6_900_000, 4_300_000, 2_700_000, 1_600_000, 450_000}; - - /** - * Default initial bitrate estimate used when the device is offline or the network type cannot be - * determined, in bits per second. - */ - public static final long DEFAULT_INITIAL_BITRATE_ESTIMATE = 1_000_000; - - /** Default maximum weight for the sliding window. */ - public static final int DEFAULT_SLIDING_WINDOW_MAX_WEIGHT = 2000; - - /** Builder for a bandwidth meter. */ - public static final class Builder { - - private final @Nullable Context context; - - private @Nullable Handler eventHandler; - private @Nullable EventListener eventListener; - private SparseArray initialBitrateEstimates; - private int slidingWindowMaxWeight; - private Clock clock; - - /** @deprecated Use {@link #Builder(Context)} instead. */ - @Deprecated - public Builder() { - this(/* context= */ null); - } - - /** - * Creates a builder with default parameters and without listener. - * - * @param context A context. - */ - public Builder(@Nullable Context context) { - this.context = context == null ? null : context.getApplicationContext(); - initialBitrateEstimates = getInitialBitrateEstimatesForCountry(Util.getCountryCode(context)); - slidingWindowMaxWeight = DEFAULT_SLIDING_WINDOW_MAX_WEIGHT; - clock = Clock.DEFAULT; - } - - /** - * Sets an event listener for new bandwidth estimates. - * - * @param eventHandler A handler for events. - * @param eventListener A listener of events. - * @return This builder. - * @throws IllegalArgumentException If the event handler or listener are null. - */ - public Builder setEventListener(Handler eventHandler, EventListener eventListener) { - Assertions.checkArgument(eventHandler != null && eventListener != null); - this.eventHandler = eventHandler; - this.eventListener = eventListener; - return this; - } - - /** - * Sets the maximum weight for the sliding window. - * - * @param slidingWindowMaxWeight The maximum weight for the sliding window. - * @return This builder. - */ - public Builder setSlidingWindowMaxWeight(int slidingWindowMaxWeight) { - this.slidingWindowMaxWeight = slidingWindowMaxWeight; - return this; - } - - /** - * Sets the initial bitrate estimate in bits per second that should be assumed when a bandwidth - * estimate is unavailable. - * - * @param initialBitrateEstimate The initial bitrate estimate in bits per second. - * @return This builder. - */ - public Builder setInitialBitrateEstimate(long initialBitrateEstimate) { - for (int i = 0; i < initialBitrateEstimates.size(); i++) { - initialBitrateEstimates.setValueAt(i, initialBitrateEstimate); - } - return this; - } - - /** - * Sets the initial bitrate estimate in bits per second for a network type that should be - * assumed when a bandwidth estimate is unavailable and the current network connection is of the - * specified type. - * - * @param networkType The {@link C.NetworkType} this initial estimate is for. - * @param initialBitrateEstimate The initial bitrate estimate in bits per second. - * @return This builder. - */ - public Builder setInitialBitrateEstimate( - @C.NetworkType int networkType, long initialBitrateEstimate) { - initialBitrateEstimates.put(networkType, initialBitrateEstimate); - return this; - } - - /** - * Sets the initial bitrate estimates to the default values of the specified country. The - * initial estimates are used when a bandwidth estimate is unavailable. - * - * @param countryCode The ISO 3166-1 alpha-2 country code of the country whose default bitrate - * estimates should be used. - * @return This builder. - */ - public Builder setInitialBitrateEstimate(String countryCode) { - initialBitrateEstimates = - getInitialBitrateEstimatesForCountry(Util.toUpperInvariant(countryCode)); - return this; - } - - /** - * Sets the clock used to estimate bandwidth from data transfers. Should only be set for testing - * purposes. - * - * @param clock The clock used to estimate bandwidth from data transfers. - * @return This builder. - */ - public Builder setClock(Clock clock) { - this.clock = clock; - return this; - } - - /** - * Builds the bandwidth meter. - * - * @return A bandwidth meter with the configured properties. - */ - public CountryAndNetworkTypeBandwidthMeter build() { - Long initialBitrateEstimate = initialBitrateEstimates.get(Util.getNetworkType(context)); - if (initialBitrateEstimate == null) { - initialBitrateEstimate = initialBitrateEstimates.get(C.NETWORK_TYPE_UNKNOWN); - } - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter( - initialBitrateEstimate, slidingWindowMaxWeight, clock); - if (eventHandler != null && eventListener != null) { - bandwidthMeter.addEventListener(eventHandler, eventListener); - } - return bandwidthMeter; - } - - private static SparseArray getInitialBitrateEstimatesForCountry(String countryCode) { - int[] groupIndices = getCountryGroupIndices(countryCode); - SparseArray result = new SparseArray<>(/* initialCapacity= */ 6); - result.append(C.NETWORK_TYPE_UNKNOWN, DEFAULT_INITIAL_BITRATE_ESTIMATE); - result.append(C.NETWORK_TYPE_WIFI, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); - result.append(C.NETWORK_TYPE_2G, DEFAULT_INITIAL_BITRATE_ESTIMATES_2G[groupIndices[1]]); - result.append(C.NETWORK_TYPE_3G, DEFAULT_INITIAL_BITRATE_ESTIMATES_3G[groupIndices[2]]); - result.append(C.NETWORK_TYPE_4G, DEFAULT_INITIAL_BITRATE_ESTIMATES_4G[groupIndices[3]]); - // Assume default Wifi bitrate for Ethernet to prevent using the slower fallback bitrate. - result.append( - C.NETWORK_TYPE_ETHERNET, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); - return result; - } - - private static int[] getCountryGroupIndices(String countryCode) { - int[] groupIndices = DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS.get(countryCode); - // Assume median group if not found. - return groupIndices == null ? new int[] {2, 2, 2, 2} : groupIndices; - } - } - - private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; - private static final int BYTES_TRANSFERRED_FOR_ESTIMATE = 512 * 1024; - - private final EventDispatcher eventDispatcher; - private final SlidingPercentile slidingPercentile; - private final Clock clock; - - private int streamCount; - private long sampleStartTimeMs; - private long sampleBytesTransferred; - - private long totalElapsedTimeMs; - private long totalBytesTransferred; - private long bitrateEstimate; - - /** Creates a bandwidth meter with default parameters. */ - public CountryAndNetworkTypeBandwidthMeter() { - this(DEFAULT_INITIAL_BITRATE_ESTIMATE, DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, Clock.DEFAULT); - } - - /** @deprecated Use {@link Builder} instead. */ - @Deprecated - public CountryAndNetworkTypeBandwidthMeter(Handler eventHandler, EventListener eventListener) { - this(DEFAULT_INITIAL_BITRATE_ESTIMATE, DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, Clock.DEFAULT); - if (eventHandler != null && eventListener != null) { - addEventListener(eventHandler, eventListener); - } - } - - /** @deprecated Use {@link Builder} instead. */ - @Deprecated - public CountryAndNetworkTypeBandwidthMeter( - Handler eventHandler, EventListener eventListener, int maxWeight) { - this(DEFAULT_INITIAL_BITRATE_ESTIMATE, maxWeight, Clock.DEFAULT); - if (eventHandler != null && eventListener != null) { - addEventListener(eventHandler, eventListener); - } - } - - private CountryAndNetworkTypeBandwidthMeter( - long initialBitrateEstimate, int maxWeight, Clock clock) { - this.eventDispatcher = new EventDispatcher<>(); - this.slidingPercentile = new SlidingPercentile(maxWeight); - this.clock = clock; - bitrateEstimate = initialBitrateEstimate; - } - - @Override - public synchronized long getBitrateEstimate() { - return bitrateEstimate; - } - - @Override - public @Nullable TransferListener getTransferListener() { - return this; - } - - @Override - public void addEventListener(Handler eventHandler, EventListener eventListener) { - eventDispatcher.addListener(eventHandler, eventListener); - } - - @Override - public void removeEventListener(EventListener eventListener) { - eventDispatcher.removeListener(eventListener); - } - - @Override - public void onTransferInitializing(DataSource source, DataSpec dataSpec, boolean isNetwork) { - // Do nothing. - } - - @Override - public synchronized void onTransferStart( - DataSource source, DataSpec dataSpec, boolean isNetwork) { - if (!isNetwork) { - return; - } - if (streamCount == 0) { - sampleStartTimeMs = clock.elapsedRealtime(); - } - streamCount++; - } - - @Override - public synchronized void onBytesTransferred( - DataSource source, DataSpec dataSpec, boolean isNetwork, int bytes) { - if (!isNetwork) { - return; - } - sampleBytesTransferred += bytes; - } - - @Override - public synchronized void onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork) { - if (!isNetwork) { - return; - } - Assertions.checkState(streamCount > 0); - long nowMs = clock.elapsedRealtime(); - int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs); - totalElapsedTimeMs += sampleElapsedTimeMs; - totalBytesTransferred += sampleBytesTransferred; - if (sampleElapsedTimeMs > 0) { - float bitsPerSecond = (sampleBytesTransferred * 8000) / sampleElapsedTimeMs; - slidingPercentile.addSample((int) Math.sqrt(sampleBytesTransferred), bitsPerSecond); - if (totalElapsedTimeMs >= ELAPSED_MILLIS_FOR_ESTIMATE - || totalBytesTransferred >= BYTES_TRANSFERRED_FOR_ESTIMATE) { - bitrateEstimate = (long) slidingPercentile.getPercentile(0.5f); - } - } - notifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); - if (--streamCount > 0) { - sampleStartTimeMs = nowMs; - } - sampleBytesTransferred = 0; - } - - private void notifyBandwidthSample(int elapsedMs, long bytes, long bitrate) { - eventDispatcher.dispatch(listener -> listener.onBandwidthSample(elapsedMs, bytes, bitrate)); - } - - private static Map createInitialBitrateCountryGroupAssignment() { - HashMap countryGroupAssignment = new HashMap<>(); - countryGroupAssignment.put("AD", new int[] {1, 0, 0, 0}); - countryGroupAssignment.put("AE", new int[] {1, 3, 4, 4}); - countryGroupAssignment.put("AF", new int[] {4, 4, 3, 2}); - countryGroupAssignment.put("AG", new int[] {3, 2, 1, 2}); - countryGroupAssignment.put("AI", new int[] {1, 0, 0, 2}); - countryGroupAssignment.put("AL", new int[] {1, 1, 1, 1}); - countryGroupAssignment.put("AM", new int[] {2, 2, 4, 3}); - countryGroupAssignment.put("AO", new int[] {2, 4, 2, 0}); - countryGroupAssignment.put("AR", new int[] {2, 3, 2, 3}); - countryGroupAssignment.put("AS", new int[] {3, 4, 4, 1}); - countryGroupAssignment.put("AT", new int[] {0, 1, 0, 0}); - countryGroupAssignment.put("AU", new int[] {0, 3, 0, 0}); - countryGroupAssignment.put("AW", new int[] {1, 1, 0, 4}); - countryGroupAssignment.put("AX", new int[] {0, 1, 0, 0}); - countryGroupAssignment.put("AZ", new int[] {3, 3, 2, 2}); - countryGroupAssignment.put("BA", new int[] {1, 1, 1, 2}); - countryGroupAssignment.put("BB", new int[] {0, 1, 0, 0}); - countryGroupAssignment.put("BD", new int[] {2, 1, 3, 2}); - countryGroupAssignment.put("BE", new int[] {0, 0, 0, 0}); - countryGroupAssignment.put("BF", new int[] {4, 4, 4, 1}); - countryGroupAssignment.put("BG", new int[] {0, 0, 0, 1}); - countryGroupAssignment.put("BH", new int[] {2, 1, 3, 4}); - countryGroupAssignment.put("BI", new int[] {4, 3, 4, 4}); - countryGroupAssignment.put("BJ", new int[] {4, 3, 4, 3}); - countryGroupAssignment.put("BL", new int[] {1, 0, 1, 2}); - countryGroupAssignment.put("BM", new int[] {1, 0, 0, 0}); - countryGroupAssignment.put("BN", new int[] {4, 3, 3, 3}); - countryGroupAssignment.put("BO", new int[] {2, 2, 1, 2}); - countryGroupAssignment.put("BQ", new int[] {1, 1, 2, 4}); - countryGroupAssignment.put("BR", new int[] {2, 3, 2, 2}); - countryGroupAssignment.put("BS", new int[] {1, 1, 0, 2}); - countryGroupAssignment.put("BT", new int[] {3, 0, 2, 1}); - countryGroupAssignment.put("BW", new int[] {4, 4, 2, 3}); - countryGroupAssignment.put("BY", new int[] {1, 1, 1, 1}); - countryGroupAssignment.put("BZ", new int[] {2, 3, 3, 1}); - countryGroupAssignment.put("CA", new int[] {0, 2, 2, 3}); - countryGroupAssignment.put("CD", new int[] {4, 4, 2, 1}); - countryGroupAssignment.put("CF", new int[] {4, 4, 3, 3}); - countryGroupAssignment.put("CG", new int[] {4, 4, 4, 4}); - countryGroupAssignment.put("CH", new int[] {0, 0, 0, 0}); - countryGroupAssignment.put("CI", new int[] {4, 4, 4, 4}); - countryGroupAssignment.put("CK", new int[] {2, 4, 2, 0}); - countryGroupAssignment.put("CL", new int[] {2, 2, 2, 3}); - countryGroupAssignment.put("CM", new int[] {3, 4, 3, 1}); - countryGroupAssignment.put("CN", new int[] {2, 0, 1, 2}); - countryGroupAssignment.put("CO", new int[] {2, 3, 2, 1}); - countryGroupAssignment.put("CR", new int[] {2, 2, 4, 4}); - countryGroupAssignment.put("CU", new int[] {4, 4, 4, 1}); - countryGroupAssignment.put("CV", new int[] {2, 2, 2, 4}); - countryGroupAssignment.put("CW", new int[] {1, 1, 0, 0}); - countryGroupAssignment.put("CX", new int[] {1, 2, 2, 2}); - countryGroupAssignment.put("CY", new int[] {1, 1, 0, 0}); - countryGroupAssignment.put("CZ", new int[] {0, 1, 0, 0}); - countryGroupAssignment.put("DE", new int[] {0, 2, 2, 2}); - countryGroupAssignment.put("DJ", new int[] {3, 4, 4, 0}); - countryGroupAssignment.put("DK", new int[] {0, 0, 0, 0}); - countryGroupAssignment.put("DM", new int[] {2, 0, 3, 4}); - countryGroupAssignment.put("DO", new int[] {3, 3, 4, 4}); - countryGroupAssignment.put("DZ", new int[] {3, 3, 4, 4}); - countryGroupAssignment.put("EC", new int[] {2, 3, 3, 1}); - countryGroupAssignment.put("EE", new int[] {0, 0, 0, 0}); - countryGroupAssignment.put("EG", new int[] {3, 3, 1, 1}); - countryGroupAssignment.put("EH", new int[] {2, 0, 2, 3}); - countryGroupAssignment.put("ER", new int[] {4, 2, 2, 2}); - countryGroupAssignment.put("ES", new int[] {0, 0, 1, 1}); - countryGroupAssignment.put("ET", new int[] {4, 4, 4, 0}); - countryGroupAssignment.put("FI", new int[] {0, 0, 1, 0}); - countryGroupAssignment.put("FJ", new int[] {3, 2, 3, 3}); - countryGroupAssignment.put("FK", new int[] {3, 4, 2, 1}); - countryGroupAssignment.put("FM", new int[] {4, 2, 4, 0}); - countryGroupAssignment.put("FO", new int[] {0, 0, 0, 1}); - countryGroupAssignment.put("FR", new int[] {1, 0, 2, 1}); - countryGroupAssignment.put("GA", new int[] {3, 3, 2, 1}); - countryGroupAssignment.put("GB", new int[] {0, 1, 3, 2}); - countryGroupAssignment.put("GD", new int[] {2, 0, 3, 0}); - countryGroupAssignment.put("GE", new int[] {1, 1, 0, 3}); - countryGroupAssignment.put("GF", new int[] {1, 2, 4, 4}); - countryGroupAssignment.put("GG", new int[] {0, 1, 0, 0}); - countryGroupAssignment.put("GH", new int[] {3, 2, 2, 2}); - countryGroupAssignment.put("GI", new int[] {0, 0, 0, 1}); - countryGroupAssignment.put("GL", new int[] {2, 4, 1, 4}); - countryGroupAssignment.put("GM", new int[] {4, 3, 3, 0}); - countryGroupAssignment.put("GN", new int[] {4, 4, 3, 4}); - countryGroupAssignment.put("GP", new int[] {2, 2, 1, 3}); - countryGroupAssignment.put("GQ", new int[] {4, 4, 3, 1}); - countryGroupAssignment.put("GR", new int[] {1, 1, 0, 1}); - countryGroupAssignment.put("GT", new int[] {3, 2, 3, 4}); - countryGroupAssignment.put("GU", new int[] {1, 0, 4, 4}); - countryGroupAssignment.put("GW", new int[] {4, 4, 4, 0}); - countryGroupAssignment.put("GY", new int[] {3, 4, 1, 0}); - countryGroupAssignment.put("HK", new int[] {0, 2, 3, 4}); - countryGroupAssignment.put("HN", new int[] {3, 3, 2, 2}); - countryGroupAssignment.put("HR", new int[] {1, 0, 0, 2}); - countryGroupAssignment.put("HT", new int[] {3, 3, 3, 3}); - countryGroupAssignment.put("HU", new int[] {0, 0, 1, 0}); - countryGroupAssignment.put("ID", new int[] {2, 3, 3, 4}); - countryGroupAssignment.put("IE", new int[] {0, 0, 1, 1}); - countryGroupAssignment.put("IL", new int[] {0, 1, 1, 3}); - countryGroupAssignment.put("IM", new int[] {0, 1, 0, 1}); - countryGroupAssignment.put("IN", new int[] {2, 3, 3, 4}); - countryGroupAssignment.put("IO", new int[] {4, 2, 2, 2}); - countryGroupAssignment.put("IQ", new int[] {3, 3, 4, 3}); - countryGroupAssignment.put("IR", new int[] {3, 2, 4, 4}); - countryGroupAssignment.put("IS", new int[] {0, 0, 0, 0}); - countryGroupAssignment.put("IT", new int[] {1, 0, 1, 3}); - countryGroupAssignment.put("JE", new int[] {0, 0, 0, 1}); - countryGroupAssignment.put("JM", new int[] {3, 3, 3, 2}); - countryGroupAssignment.put("JO", new int[] {1, 1, 1, 2}); - countryGroupAssignment.put("JP", new int[] {0, 1, 1, 2}); - countryGroupAssignment.put("KE", new int[] {3, 3, 3, 3}); - countryGroupAssignment.put("KG", new int[] {2, 2, 3, 3}); - countryGroupAssignment.put("KH", new int[] {1, 0, 4, 4}); - countryGroupAssignment.put("KI", new int[] {4, 4, 4, 4}); - countryGroupAssignment.put("KM", new int[] {4, 4, 2, 2}); - countryGroupAssignment.put("KN", new int[] {1, 0, 1, 3}); - countryGroupAssignment.put("KP", new int[] {1, 2, 2, 2}); - countryGroupAssignment.put("KR", new int[] {0, 4, 0, 2}); - countryGroupAssignment.put("KW", new int[] {1, 2, 1, 2}); - countryGroupAssignment.put("KY", new int[] {1, 1, 0, 2}); - countryGroupAssignment.put("KZ", new int[] {1, 2, 2, 3}); - countryGroupAssignment.put("LA", new int[] {3, 2, 2, 2}); - countryGroupAssignment.put("LB", new int[] {3, 2, 0, 0}); - countryGroupAssignment.put("LC", new int[] {2, 2, 1, 0}); - countryGroupAssignment.put("LI", new int[] {0, 0, 1, 2}); - countryGroupAssignment.put("LK", new int[] {1, 1, 2, 2}); - countryGroupAssignment.put("LR", new int[] {3, 4, 3, 1}); - countryGroupAssignment.put("LS", new int[] {3, 3, 2, 0}); - countryGroupAssignment.put("LT", new int[] {0, 0, 0, 1}); - countryGroupAssignment.put("LU", new int[] {0, 0, 1, 0}); - countryGroupAssignment.put("LV", new int[] {0, 0, 0, 0}); - countryGroupAssignment.put("LY", new int[] {4, 4, 4, 4}); - countryGroupAssignment.put("MA", new int[] {2, 1, 2, 2}); - countryGroupAssignment.put("MC", new int[] {1, 0, 1, 0}); - countryGroupAssignment.put("MD", new int[] {1, 1, 0, 0}); - countryGroupAssignment.put("ME", new int[] {1, 2, 2, 3}); - countryGroupAssignment.put("MF", new int[] {1, 4, 3, 3}); - countryGroupAssignment.put("MG", new int[] {3, 4, 1, 2}); - countryGroupAssignment.put("MH", new int[] {4, 0, 2, 3}); - countryGroupAssignment.put("MK", new int[] {1, 0, 0, 1}); - countryGroupAssignment.put("ML", new int[] {4, 4, 4, 4}); - countryGroupAssignment.put("MM", new int[] {2, 3, 1, 2}); - countryGroupAssignment.put("MN", new int[] {2, 2, 2, 4}); - countryGroupAssignment.put("MO", new int[] {0, 1, 4, 4}); - countryGroupAssignment.put("MP", new int[] {0, 0, 4, 4}); - countryGroupAssignment.put("MQ", new int[] {1, 1, 1, 3}); - countryGroupAssignment.put("MR", new int[] {4, 2, 4, 2}); - countryGroupAssignment.put("MS", new int[] {1, 2, 1, 2}); - countryGroupAssignment.put("MT", new int[] {0, 0, 0, 0}); - countryGroupAssignment.put("MU", new int[] {2, 2, 4, 4}); - countryGroupAssignment.put("MV", new int[] {4, 2, 0, 1}); - countryGroupAssignment.put("MW", new int[] {3, 2, 1, 1}); - countryGroupAssignment.put("MX", new int[] {2, 4, 3, 1}); - countryGroupAssignment.put("MY", new int[] {2, 3, 3, 3}); - countryGroupAssignment.put("MZ", new int[] {3, 3, 2, 4}); - countryGroupAssignment.put("NA", new int[] {4, 2, 1, 1}); - countryGroupAssignment.put("NC", new int[] {2, 1, 3, 3}); - countryGroupAssignment.put("NE", new int[] {4, 4, 4, 4}); - countryGroupAssignment.put("NF", new int[] {0, 2, 2, 2}); - countryGroupAssignment.put("NG", new int[] {3, 4, 2, 2}); - countryGroupAssignment.put("NI", new int[] {3, 4, 3, 3}); - countryGroupAssignment.put("NL", new int[] {0, 1, 3, 2}); - countryGroupAssignment.put("NO", new int[] {0, 0, 1, 0}); - countryGroupAssignment.put("NP", new int[] {2, 3, 2, 2}); - countryGroupAssignment.put("NR", new int[] {4, 3, 4, 1}); - countryGroupAssignment.put("NU", new int[] {4, 2, 2, 2}); - countryGroupAssignment.put("NZ", new int[] {0, 0, 0, 1}); - countryGroupAssignment.put("OM", new int[] {2, 2, 1, 3}); - countryGroupAssignment.put("PA", new int[] {1, 3, 2, 3}); - countryGroupAssignment.put("PE", new int[] {2, 2, 4, 4}); - countryGroupAssignment.put("PF", new int[] {2, 2, 0, 1}); - countryGroupAssignment.put("PG", new int[] {4, 4, 4, 4}); - countryGroupAssignment.put("PH", new int[] {3, 0, 4, 4}); - countryGroupAssignment.put("PK", new int[] {3, 3, 3, 3}); - countryGroupAssignment.put("PL", new int[] {1, 0, 1, 3}); - countryGroupAssignment.put("PM", new int[] {0, 2, 2, 3}); - countryGroupAssignment.put("PR", new int[] {2, 3, 4, 3}); - countryGroupAssignment.put("PS", new int[] {2, 3, 0, 4}); - countryGroupAssignment.put("PT", new int[] {1, 1, 1, 1}); - countryGroupAssignment.put("PW", new int[] {3, 2, 3, 0}); - countryGroupAssignment.put("PY", new int[] {2, 1, 3, 3}); - countryGroupAssignment.put("QA", new int[] {2, 3, 1, 2}); - countryGroupAssignment.put("RE", new int[] {1, 1, 2, 2}); - countryGroupAssignment.put("RO", new int[] {0, 1, 1, 3}); - countryGroupAssignment.put("RS", new int[] {1, 1, 0, 0}); - countryGroupAssignment.put("RU", new int[] {0, 1, 1, 1}); - countryGroupAssignment.put("RW", new int[] {3, 4, 3, 1}); - countryGroupAssignment.put("SA", new int[] {3, 2, 2, 3}); - countryGroupAssignment.put("SB", new int[] {4, 4, 3, 0}); - countryGroupAssignment.put("SC", new int[] {4, 2, 0, 1}); - countryGroupAssignment.put("SD", new int[] {3, 4, 4, 4}); - countryGroupAssignment.put("SE", new int[] {0, 0, 0, 0}); - countryGroupAssignment.put("SG", new int[] {1, 2, 3, 3}); - countryGroupAssignment.put("SH", new int[] {4, 2, 2, 2}); - countryGroupAssignment.put("SI", new int[] {0, 1, 0, 0}); - countryGroupAssignment.put("SJ", new int[] {3, 2, 0, 2}); - countryGroupAssignment.put("SK", new int[] {0, 1, 0, 1}); - countryGroupAssignment.put("SL", new int[] {4, 3, 2, 4}); - countryGroupAssignment.put("SM", new int[] {1, 0, 1, 1}); - countryGroupAssignment.put("SN", new int[] {4, 4, 4, 2}); - countryGroupAssignment.put("SO", new int[] {4, 4, 4, 3}); - countryGroupAssignment.put("SR", new int[] {3, 2, 2, 3}); - countryGroupAssignment.put("SS", new int[] {4, 3, 4, 2}); - countryGroupAssignment.put("ST", new int[] {3, 2, 2, 2}); - countryGroupAssignment.put("SV", new int[] {2, 3, 2, 3}); - countryGroupAssignment.put("SX", new int[] {2, 4, 2, 0}); - countryGroupAssignment.put("SY", new int[] {4, 4, 2, 0}); - countryGroupAssignment.put("SZ", new int[] {3, 4, 1, 1}); - countryGroupAssignment.put("TC", new int[] {2, 1, 2, 1}); - countryGroupAssignment.put("TD", new int[] {4, 4, 4, 3}); - countryGroupAssignment.put("TG", new int[] {3, 2, 2, 0}); - countryGroupAssignment.put("TH", new int[] {1, 3, 4, 4}); - countryGroupAssignment.put("TJ", new int[] {4, 4, 4, 4}); - countryGroupAssignment.put("TL", new int[] {4, 2, 4, 4}); - countryGroupAssignment.put("TM", new int[] {4, 1, 3, 3}); - countryGroupAssignment.put("TN", new int[] {2, 2, 1, 2}); - countryGroupAssignment.put("TO", new int[] {2, 3, 3, 1}); - countryGroupAssignment.put("TR", new int[] {1, 2, 0, 2}); - countryGroupAssignment.put("TT", new int[] {2, 1, 1, 0}); - countryGroupAssignment.put("TV", new int[] {4, 2, 2, 4}); - countryGroupAssignment.put("TW", new int[] {0, 0, 0, 1}); - countryGroupAssignment.put("TZ", new int[] {3, 3, 3, 2}); - countryGroupAssignment.put("UA", new int[] {0, 2, 1, 3}); - countryGroupAssignment.put("UG", new int[] {4, 3, 2, 2}); - countryGroupAssignment.put("US", new int[] {0, 1, 3, 3}); - countryGroupAssignment.put("UY", new int[] {2, 1, 2, 2}); - countryGroupAssignment.put("UZ", new int[] {4, 3, 2, 4}); - countryGroupAssignment.put("VA", new int[] {1, 2, 2, 2}); - countryGroupAssignment.put("VC", new int[] {2, 0, 3, 2}); - countryGroupAssignment.put("VE", new int[] {3, 4, 4, 3}); - countryGroupAssignment.put("VG", new int[] {3, 1, 3, 4}); - countryGroupAssignment.put("VI", new int[] {1, 0, 2, 4}); - countryGroupAssignment.put("VN", new int[] {0, 2, 4, 4}); - countryGroupAssignment.put("VU", new int[] {4, 1, 3, 2}); - countryGroupAssignment.put("WS", new int[] {3, 2, 3, 0}); - countryGroupAssignment.put("XK", new int[] {1, 2, 1, 0}); - countryGroupAssignment.put("YE", new int[] {4, 4, 4, 2}); - countryGroupAssignment.put("YT", new int[] {3, 1, 1, 2}); - countryGroupAssignment.put("ZA", new int[] {2, 3, 1, 2}); - countryGroupAssignment.put("ZM", new int[] {3, 3, 3, 1}); - countryGroupAssignment.put("ZW", new int[] {3, 3, 2, 1}); - return Collections.unmodifiableMap(countryGroupAssignment); - } -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index 6e0fba27ae..e9f70ec92a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -15,20 +15,57 @@ */ package com.google.android.exoplayer2.upstream; +import android.content.Context; import android.os.Handler; import android.support.annotation.Nullable; +import android.util.SparseArray; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.EventDispatcher; import com.google.android.exoplayer2.util.SlidingPercentile; +import com.google.android.exoplayer2.util.Util; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; /** - * Estimates bandwidth by listening to data transfers. The bandwidth estimate is calculated using a - * {@link SlidingPercentile} and is updated each time a transfer ends. + * Estimates bandwidth by listening to data transfers. + * + *

    The bandwidth estimate is calculated using a {@link SlidingPercentile} and is updated each + * time a transfer ends. The initial estimate is based on the current operator's network country + * code or the locale of the user, as well as the network connection type. This can be configured in + * the {@link Builder}. */ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferListener { - /** Default initial bitrate estimate in bits per second. */ + /** + * Country groups used to determine the default initial bitrate estimate. The group assignment for + * each country is an array of group indices for [Wifi, 2G, 3G, 4G]. + */ + public static final Map DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS = + createInitialBitrateCountryGroupAssignment(); + + /** Default initial Wifi bitrate estimate in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI = + new long[] {5_700_000, 3_400_000, 1_900_000, 1_000_000, 400_000}; + + /** Default initial 2G bitrate estimates in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_2G = + new long[] {169_000, 129_000, 114_000, 102_000, 87_000}; + + /** Default initial 3G bitrate estimates in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_3G = + new long[] {2_100_000, 1_300_000, 950_000, 700_000, 400_000}; + + /** Default initial 4G bitrate estimates in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_4G = + new long[] {6_900_000, 4_300_000, 2_700_000, 1_600_000, 450_000}; + + /** + * Default initial bitrate estimate used when the device is offline or the network type cannot be + * determined, in bits per second. + */ public static final long DEFAULT_INITIAL_BITRATE_ESTIMATE = 1_000_000; /** Default maximum weight for the sliding window. */ @@ -37,15 +74,28 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList /** Builder for a bandwidth meter. */ public static final class Builder { - private @Nullable Handler eventHandler; - private @Nullable EventListener eventListener; - private long initialBitrateEstimate; + @Nullable private final Context context; + + @Nullable private Handler eventHandler; + @Nullable private EventListener eventListener; + private SparseArray initialBitrateEstimates; private int slidingWindowMaxWeight; private Clock clock; - /** Creates a builder with default parameters and without listener. */ + /** @deprecated Use {@link #Builder(Context)} instead. */ + @Deprecated public Builder() { - initialBitrateEstimate = DEFAULT_INITIAL_BITRATE_ESTIMATE; + this(/* context= */ null); + } + + /** + * Creates a builder with default parameters and without listener. + * + * @param context A context. + */ + public Builder(@Nullable Context context) { + this.context = context == null ? null : context.getApplicationContext(); + initialBitrateEstimates = getInitialBitrateEstimatesForCountry(Util.getCountryCode(context)); slidingWindowMaxWeight = DEFAULT_SLIDING_WINDOW_MAX_WEIGHT; clock = Clock.DEFAULT; } @@ -84,7 +134,38 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList * @return This builder. */ public Builder setInitialBitrateEstimate(long initialBitrateEstimate) { - this.initialBitrateEstimate = initialBitrateEstimate; + for (int i = 0; i < initialBitrateEstimates.size(); i++) { + initialBitrateEstimates.setValueAt(i, initialBitrateEstimate); + } + return this; + } + + /** + * Sets the initial bitrate estimate in bits per second for a network type that should be + * assumed when a bandwidth estimate is unavailable and the current network connection is of the + * specified type. + * + * @param networkType The {@link C.NetworkType} this initial estimate is for. + * @param initialBitrateEstimate The initial bitrate estimate in bits per second. + * @return This builder. + */ + public Builder setInitialBitrateEstimate( + @C.NetworkType int networkType, long initialBitrateEstimate) { + initialBitrateEstimates.put(networkType, initialBitrateEstimate); + return this; + } + + /** + * Sets the initial bitrate estimates to the default values of the specified country. The + * initial estimates are used when a bandwidth estimate is unavailable. + * + * @param countryCode The ISO 3166-1 alpha-2 country code of the country whose default bitrate + * estimates should be used. + * @return This builder. + */ + public Builder setInitialBitrateEstimate(String countryCode) { + initialBitrateEstimates = + getInitialBitrateEstimatesForCountry(Util.toUpperInvariant(countryCode)); return this; } @@ -106,6 +187,10 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList * @return A bandwidth meter with the configured properties. */ public DefaultBandwidthMeter build() { + Long initialBitrateEstimate = initialBitrateEstimates.get(Util.getNetworkType(context)); + if (initialBitrateEstimate == null) { + initialBitrateEstimate = initialBitrateEstimates.get(C.NETWORK_TYPE_UNKNOWN); + } DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(initialBitrateEstimate, slidingWindowMaxWeight, clock); if (eventHandler != null && eventListener != null) { @@ -113,6 +198,26 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } return bandwidthMeter; } + + private static SparseArray getInitialBitrateEstimatesForCountry(String countryCode) { + int[] groupIndices = getCountryGroupIndices(countryCode); + SparseArray result = new SparseArray<>(/* initialCapacity= */ 6); + result.append(C.NETWORK_TYPE_UNKNOWN, DEFAULT_INITIAL_BITRATE_ESTIMATE); + result.append(C.NETWORK_TYPE_WIFI, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); + result.append(C.NETWORK_TYPE_2G, DEFAULT_INITIAL_BITRATE_ESTIMATES_2G[groupIndices[1]]); + result.append(C.NETWORK_TYPE_3G, DEFAULT_INITIAL_BITRATE_ESTIMATES_3G[groupIndices[2]]); + result.append(C.NETWORK_TYPE_4G, DEFAULT_INITIAL_BITRATE_ESTIMATES_4G[groupIndices[3]]); + // Assume default Wifi bitrate for Ethernet to prevent using the slower fallback bitrate. + result.append( + C.NETWORK_TYPE_ETHERNET, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); + return result; + } + + private static int[] getCountryGroupIndices(String countryCode) { + int[] groupIndices = DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS.get(countryCode); + // Assume median group if not found. + return groupIndices == null ? new int[] {2, 2, 2, 2} : groupIndices; + } } private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; @@ -153,10 +258,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } } - private DefaultBandwidthMeter( - long initialBitrateEstimate, - int maxWeight, - Clock clock) { + private DefaultBandwidthMeter(long initialBitrateEstimate, int maxWeight, Clock clock) { this.eventDispatcher = new EventDispatcher<>(); this.slidingPercentile = new SlidingPercentile(maxWeight); this.clock = clock; @@ -169,7 +271,8 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } @Override - public @Nullable TransferListener getTransferListener() { + @Nullable + public TransferListener getTransferListener() { return this; } @@ -237,4 +340,249 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private void notifyBandwidthSample(int elapsedMs, long bytes, long bitrate) { eventDispatcher.dispatch(listener -> listener.onBandwidthSample(elapsedMs, bytes, bitrate)); } + + private static Map createInitialBitrateCountryGroupAssignment() { + HashMap countryGroupAssignment = new HashMap<>(); + countryGroupAssignment.put("AD", new int[] {1, 0, 0, 0}); + countryGroupAssignment.put("AE", new int[] {1, 3, 4, 4}); + countryGroupAssignment.put("AF", new int[] {4, 4, 3, 2}); + countryGroupAssignment.put("AG", new int[] {3, 2, 1, 2}); + countryGroupAssignment.put("AI", new int[] {1, 0, 0, 2}); + countryGroupAssignment.put("AL", new int[] {1, 1, 1, 1}); + countryGroupAssignment.put("AM", new int[] {2, 2, 4, 3}); + countryGroupAssignment.put("AO", new int[] {2, 4, 2, 0}); + countryGroupAssignment.put("AR", new int[] {2, 3, 2, 3}); + countryGroupAssignment.put("AS", new int[] {3, 4, 4, 1}); + countryGroupAssignment.put("AT", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("AU", new int[] {0, 3, 0, 0}); + countryGroupAssignment.put("AW", new int[] {1, 1, 0, 4}); + countryGroupAssignment.put("AX", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("AZ", new int[] {3, 3, 2, 2}); + countryGroupAssignment.put("BA", new int[] {1, 1, 1, 2}); + countryGroupAssignment.put("BB", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("BD", new int[] {2, 1, 3, 2}); + countryGroupAssignment.put("BE", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("BF", new int[] {4, 4, 4, 1}); + countryGroupAssignment.put("BG", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("BH", new int[] {2, 1, 3, 4}); + countryGroupAssignment.put("BI", new int[] {4, 3, 4, 4}); + countryGroupAssignment.put("BJ", new int[] {4, 3, 4, 3}); + countryGroupAssignment.put("BL", new int[] {1, 0, 1, 2}); + countryGroupAssignment.put("BM", new int[] {1, 0, 0, 0}); + countryGroupAssignment.put("BN", new int[] {4, 3, 3, 3}); + countryGroupAssignment.put("BO", new int[] {2, 2, 1, 2}); + countryGroupAssignment.put("BQ", new int[] {1, 1, 2, 4}); + countryGroupAssignment.put("BR", new int[] {2, 3, 2, 2}); + countryGroupAssignment.put("BS", new int[] {1, 1, 0, 2}); + countryGroupAssignment.put("BT", new int[] {3, 0, 2, 1}); + countryGroupAssignment.put("BW", new int[] {4, 4, 2, 3}); + countryGroupAssignment.put("BY", new int[] {1, 1, 1, 1}); + countryGroupAssignment.put("BZ", new int[] {2, 3, 3, 1}); + countryGroupAssignment.put("CA", new int[] {0, 2, 2, 3}); + countryGroupAssignment.put("CD", new int[] {4, 4, 2, 1}); + countryGroupAssignment.put("CF", new int[] {4, 4, 3, 3}); + countryGroupAssignment.put("CG", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("CH", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("CI", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("CK", new int[] {2, 4, 2, 0}); + countryGroupAssignment.put("CL", new int[] {2, 2, 2, 3}); + countryGroupAssignment.put("CM", new int[] {3, 4, 3, 1}); + countryGroupAssignment.put("CN", new int[] {2, 0, 1, 2}); + countryGroupAssignment.put("CO", new int[] {2, 3, 2, 1}); + countryGroupAssignment.put("CR", new int[] {2, 2, 4, 4}); + countryGroupAssignment.put("CU", new int[] {4, 4, 4, 1}); + countryGroupAssignment.put("CV", new int[] {2, 2, 2, 4}); + countryGroupAssignment.put("CW", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("CX", new int[] {1, 2, 2, 2}); + countryGroupAssignment.put("CY", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("CZ", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("DE", new int[] {0, 2, 2, 2}); + countryGroupAssignment.put("DJ", new int[] {3, 4, 4, 0}); + countryGroupAssignment.put("DK", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("DM", new int[] {2, 0, 3, 4}); + countryGroupAssignment.put("DO", new int[] {3, 3, 4, 4}); + countryGroupAssignment.put("DZ", new int[] {3, 3, 4, 4}); + countryGroupAssignment.put("EC", new int[] {2, 3, 3, 1}); + countryGroupAssignment.put("EE", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("EG", new int[] {3, 3, 1, 1}); + countryGroupAssignment.put("EH", new int[] {2, 0, 2, 3}); + countryGroupAssignment.put("ER", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("ES", new int[] {0, 0, 1, 1}); + countryGroupAssignment.put("ET", new int[] {4, 4, 4, 0}); + countryGroupAssignment.put("FI", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("FJ", new int[] {3, 2, 3, 3}); + countryGroupAssignment.put("FK", new int[] {3, 4, 2, 1}); + countryGroupAssignment.put("FM", new int[] {4, 2, 4, 0}); + countryGroupAssignment.put("FO", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("FR", new int[] {1, 0, 2, 1}); + countryGroupAssignment.put("GA", new int[] {3, 3, 2, 1}); + countryGroupAssignment.put("GB", new int[] {0, 1, 3, 2}); + countryGroupAssignment.put("GD", new int[] {2, 0, 3, 0}); + countryGroupAssignment.put("GE", new int[] {1, 1, 0, 3}); + countryGroupAssignment.put("GF", new int[] {1, 2, 4, 4}); + countryGroupAssignment.put("GG", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("GH", new int[] {3, 2, 2, 2}); + countryGroupAssignment.put("GI", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("GL", new int[] {2, 4, 1, 4}); + countryGroupAssignment.put("GM", new int[] {4, 3, 3, 0}); + countryGroupAssignment.put("GN", new int[] {4, 4, 3, 4}); + countryGroupAssignment.put("GP", new int[] {2, 2, 1, 3}); + countryGroupAssignment.put("GQ", new int[] {4, 4, 3, 1}); + countryGroupAssignment.put("GR", new int[] {1, 1, 0, 1}); + countryGroupAssignment.put("GT", new int[] {3, 2, 3, 4}); + countryGroupAssignment.put("GU", new int[] {1, 0, 4, 4}); + countryGroupAssignment.put("GW", new int[] {4, 4, 4, 0}); + countryGroupAssignment.put("GY", new int[] {3, 4, 1, 0}); + countryGroupAssignment.put("HK", new int[] {0, 2, 3, 4}); + countryGroupAssignment.put("HN", new int[] {3, 3, 2, 2}); + countryGroupAssignment.put("HR", new int[] {1, 0, 0, 2}); + countryGroupAssignment.put("HT", new int[] {3, 3, 3, 3}); + countryGroupAssignment.put("HU", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("ID", new int[] {2, 3, 3, 4}); + countryGroupAssignment.put("IE", new int[] {0, 0, 1, 1}); + countryGroupAssignment.put("IL", new int[] {0, 1, 1, 3}); + countryGroupAssignment.put("IM", new int[] {0, 1, 0, 1}); + countryGroupAssignment.put("IN", new int[] {2, 3, 3, 4}); + countryGroupAssignment.put("IO", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("IQ", new int[] {3, 3, 4, 3}); + countryGroupAssignment.put("IR", new int[] {3, 2, 4, 4}); + countryGroupAssignment.put("IS", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("IT", new int[] {1, 0, 1, 3}); + countryGroupAssignment.put("JE", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("JM", new int[] {3, 3, 3, 2}); + countryGroupAssignment.put("JO", new int[] {1, 1, 1, 2}); + countryGroupAssignment.put("JP", new int[] {0, 1, 1, 2}); + countryGroupAssignment.put("KE", new int[] {3, 3, 3, 3}); + countryGroupAssignment.put("KG", new int[] {2, 2, 3, 3}); + countryGroupAssignment.put("KH", new int[] {1, 0, 4, 4}); + countryGroupAssignment.put("KI", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("KM", new int[] {4, 4, 2, 2}); + countryGroupAssignment.put("KN", new int[] {1, 0, 1, 3}); + countryGroupAssignment.put("KP", new int[] {1, 2, 2, 2}); + countryGroupAssignment.put("KR", new int[] {0, 4, 0, 2}); + countryGroupAssignment.put("KW", new int[] {1, 2, 1, 2}); + countryGroupAssignment.put("KY", new int[] {1, 1, 0, 2}); + countryGroupAssignment.put("KZ", new int[] {1, 2, 2, 3}); + countryGroupAssignment.put("LA", new int[] {3, 2, 2, 2}); + countryGroupAssignment.put("LB", new int[] {3, 2, 0, 0}); + countryGroupAssignment.put("LC", new int[] {2, 2, 1, 0}); + countryGroupAssignment.put("LI", new int[] {0, 0, 1, 2}); + countryGroupAssignment.put("LK", new int[] {1, 1, 2, 2}); + countryGroupAssignment.put("LR", new int[] {3, 4, 3, 1}); + countryGroupAssignment.put("LS", new int[] {3, 3, 2, 0}); + countryGroupAssignment.put("LT", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("LU", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("LV", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("LY", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("MA", new int[] {2, 1, 2, 2}); + countryGroupAssignment.put("MC", new int[] {1, 0, 1, 0}); + countryGroupAssignment.put("MD", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("ME", new int[] {1, 2, 2, 3}); + countryGroupAssignment.put("MF", new int[] {1, 4, 3, 3}); + countryGroupAssignment.put("MG", new int[] {3, 4, 1, 2}); + countryGroupAssignment.put("MH", new int[] {4, 0, 2, 3}); + countryGroupAssignment.put("MK", new int[] {1, 0, 0, 1}); + countryGroupAssignment.put("ML", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("MM", new int[] {2, 3, 1, 2}); + countryGroupAssignment.put("MN", new int[] {2, 2, 2, 4}); + countryGroupAssignment.put("MO", new int[] {0, 1, 4, 4}); + countryGroupAssignment.put("MP", new int[] {0, 0, 4, 4}); + countryGroupAssignment.put("MQ", new int[] {1, 1, 1, 3}); + countryGroupAssignment.put("MR", new int[] {4, 2, 4, 2}); + countryGroupAssignment.put("MS", new int[] {1, 2, 1, 2}); + countryGroupAssignment.put("MT", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("MU", new int[] {2, 2, 4, 4}); + countryGroupAssignment.put("MV", new int[] {4, 2, 0, 1}); + countryGroupAssignment.put("MW", new int[] {3, 2, 1, 1}); + countryGroupAssignment.put("MX", new int[] {2, 4, 3, 1}); + countryGroupAssignment.put("MY", new int[] {2, 3, 3, 3}); + countryGroupAssignment.put("MZ", new int[] {3, 3, 2, 4}); + countryGroupAssignment.put("NA", new int[] {4, 2, 1, 1}); + countryGroupAssignment.put("NC", new int[] {2, 1, 3, 3}); + countryGroupAssignment.put("NE", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("NF", new int[] {0, 2, 2, 2}); + countryGroupAssignment.put("NG", new int[] {3, 4, 2, 2}); + countryGroupAssignment.put("NI", new int[] {3, 4, 3, 3}); + countryGroupAssignment.put("NL", new int[] {0, 1, 3, 2}); + countryGroupAssignment.put("NO", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("NP", new int[] {2, 3, 2, 2}); + countryGroupAssignment.put("NR", new int[] {4, 3, 4, 1}); + countryGroupAssignment.put("NU", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("NZ", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("OM", new int[] {2, 2, 1, 3}); + countryGroupAssignment.put("PA", new int[] {1, 3, 2, 3}); + countryGroupAssignment.put("PE", new int[] {2, 2, 4, 4}); + countryGroupAssignment.put("PF", new int[] {2, 2, 0, 1}); + countryGroupAssignment.put("PG", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("PH", new int[] {3, 0, 4, 4}); + countryGroupAssignment.put("PK", new int[] {3, 3, 3, 3}); + countryGroupAssignment.put("PL", new int[] {1, 0, 1, 3}); + countryGroupAssignment.put("PM", new int[] {0, 2, 2, 3}); + countryGroupAssignment.put("PR", new int[] {2, 3, 4, 3}); + countryGroupAssignment.put("PS", new int[] {2, 3, 0, 4}); + countryGroupAssignment.put("PT", new int[] {1, 1, 1, 1}); + countryGroupAssignment.put("PW", new int[] {3, 2, 3, 0}); + countryGroupAssignment.put("PY", new int[] {2, 1, 3, 3}); + countryGroupAssignment.put("QA", new int[] {2, 3, 1, 2}); + countryGroupAssignment.put("RE", new int[] {1, 1, 2, 2}); + countryGroupAssignment.put("RO", new int[] {0, 1, 1, 3}); + countryGroupAssignment.put("RS", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("RU", new int[] {0, 1, 1, 1}); + countryGroupAssignment.put("RW", new int[] {3, 4, 3, 1}); + countryGroupAssignment.put("SA", new int[] {3, 2, 2, 3}); + countryGroupAssignment.put("SB", new int[] {4, 4, 3, 0}); + countryGroupAssignment.put("SC", new int[] {4, 2, 0, 1}); + countryGroupAssignment.put("SD", new int[] {3, 4, 4, 4}); + countryGroupAssignment.put("SE", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("SG", new int[] {1, 2, 3, 3}); + countryGroupAssignment.put("SH", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("SI", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("SJ", new int[] {3, 2, 0, 2}); + countryGroupAssignment.put("SK", new int[] {0, 1, 0, 1}); + countryGroupAssignment.put("SL", new int[] {4, 3, 2, 4}); + countryGroupAssignment.put("SM", new int[] {1, 0, 1, 1}); + countryGroupAssignment.put("SN", new int[] {4, 4, 4, 2}); + countryGroupAssignment.put("SO", new int[] {4, 4, 4, 3}); + countryGroupAssignment.put("SR", new int[] {3, 2, 2, 3}); + countryGroupAssignment.put("SS", new int[] {4, 3, 4, 2}); + countryGroupAssignment.put("ST", new int[] {3, 2, 2, 2}); + countryGroupAssignment.put("SV", new int[] {2, 3, 2, 3}); + countryGroupAssignment.put("SX", new int[] {2, 4, 2, 0}); + countryGroupAssignment.put("SY", new int[] {4, 4, 2, 0}); + countryGroupAssignment.put("SZ", new int[] {3, 4, 1, 1}); + countryGroupAssignment.put("TC", new int[] {2, 1, 2, 1}); + countryGroupAssignment.put("TD", new int[] {4, 4, 4, 3}); + countryGroupAssignment.put("TG", new int[] {3, 2, 2, 0}); + countryGroupAssignment.put("TH", new int[] {1, 3, 4, 4}); + countryGroupAssignment.put("TJ", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("TL", new int[] {4, 2, 4, 4}); + countryGroupAssignment.put("TM", new int[] {4, 1, 3, 3}); + countryGroupAssignment.put("TN", new int[] {2, 2, 1, 2}); + countryGroupAssignment.put("TO", new int[] {2, 3, 3, 1}); + countryGroupAssignment.put("TR", new int[] {1, 2, 0, 2}); + countryGroupAssignment.put("TT", new int[] {2, 1, 1, 0}); + countryGroupAssignment.put("TV", new int[] {4, 2, 2, 4}); + countryGroupAssignment.put("TW", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("TZ", new int[] {3, 3, 3, 2}); + countryGroupAssignment.put("UA", new int[] {0, 2, 1, 3}); + countryGroupAssignment.put("UG", new int[] {4, 3, 2, 2}); + countryGroupAssignment.put("US", new int[] {0, 1, 3, 3}); + countryGroupAssignment.put("UY", new int[] {2, 1, 2, 2}); + countryGroupAssignment.put("UZ", new int[] {4, 3, 2, 4}); + countryGroupAssignment.put("VA", new int[] {1, 2, 2, 2}); + countryGroupAssignment.put("VC", new int[] {2, 0, 3, 2}); + countryGroupAssignment.put("VE", new int[] {3, 4, 4, 3}); + countryGroupAssignment.put("VG", new int[] {3, 1, 3, 4}); + countryGroupAssignment.put("VI", new int[] {1, 0, 2, 4}); + countryGroupAssignment.put("VN", new int[] {0, 2, 4, 4}); + countryGroupAssignment.put("VU", new int[] {4, 1, 3, 2}); + countryGroupAssignment.put("WS", new int[] {3, 2, 3, 0}); + countryGroupAssignment.put("XK", new int[] {1, 2, 1, 0}); + countryGroupAssignment.put("YE", new int[] {4, 4, 4, 2}); + countryGroupAssignment.put("YT", new int[] {3, 1, 1, 2}); + countryGroupAssignment.put("ZA", new int[] {2, 3, 1, 2}); + countryGroupAssignment.put("ZM", new int[] {3, 3, 3, 1}); + countryGroupAssignment.put("ZW", new int[] {3, 3, 2, 1}); + return Collections.unmodifiableMap(countryGroupAssignment); + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java similarity index 69% rename from library/core/src/test/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeterTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java index 2549e004e5..ebdb45909b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/CountryAndNetworkTypeBandwidthMeterTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java @@ -24,7 +24,6 @@ import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.telephony.TelephonyManager; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.util.ClosedSource; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,10 +32,9 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.shadows.ShadowNetworkInfo; -/** Unit test for {@link CountryAndNetworkTypeBandwidthMeter}. */ -@ClosedSource(reason = "Not ready yet") +/** Unit test for {@link DefaultBandwidthMeter}. */ @RunWith(RobolectricTestRunner.class) -public final class CountryAndNetworkTypeBandwidthMeterTest { +public final class DefaultBandwidthMeterTest { private static final String FAST_COUNTRY_ISO = "EE"; private static final String SLOW_COUNTRY_ISO = "PG"; @@ -106,13 +104,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void defaultInitialBitrateEstimate_forWifi_isGreaterThanEstimateFor2G() { setActiveNetworkInfo(networkInfoWifi); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterWifi = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterWifi = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateWifi = bandwidthMeterWifi.getBitrateEstimate(); setActiveNetworkInfo(networkInfo2g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter2g = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter2g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); assertThat(initialEstimateWifi).isGreaterThan(initialEstimate2g); @@ -121,13 +119,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void defaultInitialBitrateEstimate_forWifi_isGreaterThanEstimateFor3G() { setActiveNetworkInfo(networkInfoWifi); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterWifi = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterWifi = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateWifi = bandwidthMeterWifi.getBitrateEstimate(); setActiveNetworkInfo(networkInfo3g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter3g = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter3g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); assertThat(initialEstimateWifi).isGreaterThan(initialEstimate3g); @@ -136,13 +134,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void defaultInitialBitrateEstimate_forEthernet_isGreaterThanEstimateFor2G() { setActiveNetworkInfo(networkInfoEthernet); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterEthernet = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterEthernet = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateEthernet = bandwidthMeterEthernet.getBitrateEstimate(); setActiveNetworkInfo(networkInfo2g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter2g = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter2g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); assertThat(initialEstimateEthernet).isGreaterThan(initialEstimate2g); @@ -151,13 +149,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void defaultInitialBitrateEstimate_forEthernet_isGreaterThanEstimateFor3G() { setActiveNetworkInfo(networkInfoEthernet); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterEthernet = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterEthernet = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateEthernet = bandwidthMeterEthernet.getBitrateEstimate(); setActiveNetworkInfo(networkInfo3g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter3g = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter3g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); assertThat(initialEstimateEthernet).isGreaterThan(initialEstimate3g); @@ -166,13 +164,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void defaultInitialBitrateEstimate_for4G_isGreaterThanEstimateFor2G() { setActiveNetworkInfo(networkInfo4g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter4g = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter4g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate4g = bandwidthMeter4g.getBitrateEstimate(); setActiveNetworkInfo(networkInfo2g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter2g = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter2g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); assertThat(initialEstimate4g).isGreaterThan(initialEstimate2g); @@ -181,13 +179,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void defaultInitialBitrateEstimate_for4G_isGreaterThanEstimateFor3G() { setActiveNetworkInfo(networkInfo4g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter4g = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter4g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate4g = bandwidthMeter4g.getBitrateEstimate(); setActiveNetworkInfo(networkInfo3g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter3g = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter3g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); assertThat(initialEstimate4g).isGreaterThan(initialEstimate3g); @@ -196,13 +194,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void defaultInitialBitrateEstimate_for3G_isGreaterThanEstimateFor2G() { setActiveNetworkInfo(networkInfo3g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter3g = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter3g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); setActiveNetworkInfo(networkInfo2g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter2g = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter2g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); assertThat(initialEstimate3g).isGreaterThan(initialEstimate2g); @@ -211,8 +209,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void defaultInitialBitrateEstimate_forOffline_isReasonable() { setActiveNetworkInfo(networkInfoOffline); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); assertThat(initialEstimate).isGreaterThan(100_000L); @@ -224,13 +222,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { defaultInitialBitrateEstimate_forWifi_forFastCountry_isGreaterThanEstimateForSlowCountry() { setActiveNetworkInfo(networkInfoWifi); setNetworkCountryIso(FAST_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterFast = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); setNetworkCountryIso(SLOW_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); @@ -241,13 +239,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { defaultInitialBitrateEstimate_forEthernet_forFastCountry_isGreaterThanEstimateForSlowCountry() { setActiveNetworkInfo(networkInfoEthernet); setNetworkCountryIso(FAST_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterFast = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); setNetworkCountryIso(SLOW_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); @@ -258,13 +256,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { defaultInitialBitrateEstimate_for2G_forFastCountry_isGreaterThanEstimateForSlowCountry() { setActiveNetworkInfo(networkInfo2g); setNetworkCountryIso(FAST_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterFast = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); setNetworkCountryIso(SLOW_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); @@ -275,13 +273,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { defaultInitialBitrateEstimate_for3G_forFastCountry_isGreaterThanEstimateForSlowCountry() { setActiveNetworkInfo(networkInfo3g); setNetworkCountryIso(FAST_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterFast = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); setNetworkCountryIso(SLOW_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); @@ -292,13 +290,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { defaultInitialBitrateEstimate_for4g_forFastCountry_isGreaterThanEstimateForSlowCountry() { setActiveNetworkInfo(networkInfo4g); setNetworkCountryIso(FAST_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterFast = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); setNetworkCountryIso(SLOW_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); @@ -307,8 +305,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void initialBitrateEstimateOverwrite_whileConnectedToNetwork_setsInitialEstimate() { setActiveNetworkInfo(networkInfoWifi); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -319,8 +317,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void initialBitrateEstimateOverwrite_whileOffline_setsInitialEstimate() { setActiveNetworkInfo(networkInfoOffline); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -331,8 +329,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void initialBitrateEstimateOverwrite_forWifi_whileConnectedToWifi_setsInitialEstimate() { setActiveNetworkInfo(networkInfoWifi); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -344,8 +342,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { public void initialBitrateEstimateOverwrite_forWifi_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { setActiveNetworkInfo(networkInfo2g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -357,8 +355,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { public void initialBitrateEstimateOverwrite_forEthernet_whileConnectedToEthernet_setsInitialEstimate() { setActiveNetworkInfo(networkInfoEthernet); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_ETHERNET, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -370,8 +368,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { public void initialBitrateEstimateOverwrite_forEthernet_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { setActiveNetworkInfo(networkInfo2g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -382,8 +380,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void initialBitrateEstimateOverwrite_for2G_whileConnectedTo2G_setsInitialEstimate() { setActiveNetworkInfo(networkInfo2g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_2G, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -395,8 +393,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { public void initialBitrateEstimateOverwrite_for2G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { setActiveNetworkInfo(networkInfoWifi); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_2G, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -407,8 +405,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void initialBitrateEstimateOverwrite_for3G_whileConnectedTo3G_setsInitialEstimate() { setActiveNetworkInfo(networkInfo3g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_3G, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -420,8 +418,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { public void initialBitrateEstimateOverwrite_for3G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { setActiveNetworkInfo(networkInfoWifi); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_3G, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -432,8 +430,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void initialBitrateEstimateOverwrite_for4G_whileConnectedTo4G_setsInitialEstimate() { setActiveNetworkInfo(networkInfo4g); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_4G, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -445,8 +443,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { public void initialBitrateEstimateOverwrite_for4G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { setActiveNetworkInfo(networkInfoWifi); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_4G, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -457,8 +455,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void initialBitrateEstimateOverwrite_forOffline_whileOffline_setsInitialEstimate() { setActiveNetworkInfo(networkInfoOffline); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_OFFLINE, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -470,8 +468,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { public void initialBitrateEstimateOverwrite_forOffline_whileConnectedToNetwork_doesNotSetInitialEstimate() { setActiveNetworkInfo(networkInfoWifi); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(C.NETWORK_TYPE_OFFLINE, 123456789) .build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); @@ -482,13 +480,13 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void initialBitrateEstimateOverwrite_forCountry_usesDefaultValuesForCountry() { setNetworkCountryIso(SLOW_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterSlow = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); setNetworkCountryIso(FAST_COUNTRY_ISO); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterFastWithSlowOverwrite = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application) + DefaultBandwidthMeter bandwidthMeterFastWithSlowOverwrite = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) .setInitialBitrateEstimate(SLOW_COUNTRY_ISO) .build(); long initialEstimateFastWithSlowOverwrite = @@ -499,12 +497,11 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void defaultInitialBitrateEstimate_withoutContext_isReasonable() { - CountryAndNetworkTypeBandwidthMeter bandwidthMeterWithBuilder = - new CountryAndNetworkTypeBandwidthMeter.Builder(/* context= */ null).build(); + DefaultBandwidthMeter bandwidthMeterWithBuilder = + new DefaultBandwidthMeter.Builder(/* context= */ null).build(); long initialEstimateWithBuilder = bandwidthMeterWithBuilder.getBitrateEstimate(); - CountryAndNetworkTypeBandwidthMeter bandwidthMeterWithoutBuilder = - new CountryAndNetworkTypeBandwidthMeter(); + DefaultBandwidthMeter bandwidthMeterWithoutBuilder = new DefaultBandwidthMeter(); long initialEstimateWithoutBuilder = bandwidthMeterWithoutBuilder.getBitrateEstimate(); assertThat(initialEstimateWithBuilder).isGreaterThan(100_000L); @@ -516,8 +513,8 @@ public final class CountryAndNetworkTypeBandwidthMeterTest { @Test public void defaultInitialBitrateEstimate_withoutAccessNetworkStatePermission_isReasonable() { Shadows.shadowOf(RuntimeEnvironment.application).denyPermissions(ACCESS_NETWORK_STATE); - CountryAndNetworkTypeBandwidthMeter bandwidthMeter = - new CountryAndNetworkTypeBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); long initialEstimate = bandwidthMeter.getBitrateEstimate(); assertThat(initialEstimate).isGreaterThan(100_000L); From 43c01562d15782d68c06b9d438f96e05780833ea Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 3 Oct 2018 02:33:50 -0700 Subject: [PATCH 050/832] Fix positioning of subtitles. SubtitleView forwards the cue box position to SubtitlePainter. This should be the position relative to the canvas of the SubtitleView. Currently, however, we forward the position relative to the parent of SubtitleView. That causes problems if SubtitleView has a non-zero offset position to its parent. Issue:#4788 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215535281 --- RELEASENOTES.md | 3 +++ .../google/android/exoplayer2/ui/SubtitleView.java | 14 ++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2e42b9a709..9d9d1fa0c6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -14,6 +14,9 @@ * Fix an issue with blind seeking to windows with non-zero offset in a `ConcatenatingMediaSource` ([#4873](https://github.com/google/ExoPlayer/issues/4873)). +* Fix issue where subtitles have a wrong position if SubtitleView has a non-zero + offset to its parent + ([#4788](https://github.com/google/ExoPlayer/issues/4788)). ### 2.9.0 ### diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java index 7426671041..50a923bced 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java @@ -247,19 +247,17 @@ public final class SubtitleView extends View implements TextOutput { @Override public void dispatchDraw(Canvas canvas) { int cueCount = (cues == null) ? 0 : cues.size(); - int rawTop = getTop(); - int rawBottom = getBottom(); + int rawViewHeight = getHeight(); - // Calculate the bounds after padding is taken into account. - int left = getLeft() + getPaddingLeft(); - int top = rawTop + getPaddingTop(); - int right = getRight() - getPaddingRight(); - int bottom = rawBottom - getPaddingBottom(); + // Calculate the cue box bounds relative to the canvas after padding is taken into account. + int left = getPaddingLeft(); + int top = getPaddingTop(); + int right = getWidth() - getPaddingRight(); + int bottom = rawViewHeight - getPaddingBottom(); if (bottom <= top || right <= left) { // No space to draw subtitles. return; } - int rawViewHeight = rawBottom - rawTop; int viewHeightMinusPadding = bottom - top; float defaultViewTextSizePx = From f0e0a7cf57323b41f44de611e2e52f865f760f01 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 3 Oct 2018 02:33:53 -0700 Subject: [PATCH 051/832] Fix left position for subtitles. When SubtitlePainter positions the cues centred in the given box, it must add the left offset of the box to get the correct position. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215535289 --- .../java/com/google/android/exoplayer2/ui/SubtitlePainter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index d8c61f5e05..4f22362de6 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -310,7 +310,7 @@ import com.google.android.exoplayer2.util.Util; textLeft = Math.max(textLeft, parentLeft); textRight = Math.min(textLeft + textWidth, parentRight); } else { - textLeft = (parentWidth - textWidth) / 2; + textLeft = (parentWidth - textWidth) / 2 + parentLeft; textRight = textLeft + textWidth; } From 9f8c22d7195d55eccca6a1d9b35cef23a3d3dce8 Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 3 Oct 2018 03:45:00 -0700 Subject: [PATCH 052/832] Make it possible to use CacheKeyFactory with CacheDataSourceFactory and DownloaderConstructorHelper ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215541062 --- .../offline/DownloaderConstructorHelper.java | 137 ++++++++++++------ .../offline/ProgressiveDownloader.java | 2 +- .../exoplayer2/offline/SegmentDownloader.java | 4 +- .../upstream/cache/CacheDataSource.java | 16 +- .../cache/CacheDataSourceFactory.java | 96 ++++++++---- 5 files changed, 175 insertions(+), 80 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloaderConstructorHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloaderConstructorHelper.java index 18387b9d92..28e898216d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloaderConstructorHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloaderConstructorHelper.java @@ -19,55 +19,116 @@ import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSink; import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DataSource.Factory; import com.google.android.exoplayer2.upstream.DummyDataSource; -import com.google.android.exoplayer2.upstream.FileDataSource; -import com.google.android.exoplayer2.upstream.PriorityDataSource; +import com.google.android.exoplayer2.upstream.FileDataSourceFactory; +import com.google.android.exoplayer2.upstream.PriorityDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.Cache; -import com.google.android.exoplayer2.upstream.cache.CacheDataSink; +import com.google.android.exoplayer2.upstream.cache.CacheDataSinkFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; -import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; +import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory; import com.google.android.exoplayer2.util.PriorityTaskManager; /** A helper class that holds necessary parameters for {@link Downloader} construction. */ public final class DownloaderConstructorHelper { private final Cache cache; - private final Factory upstreamDataSourceFactory; - private final Factory cacheReadDataSourceFactory; - private final DataSink.Factory cacheWriteDataSinkFactory; - private final PriorityTaskManager priorityTaskManager; + @Nullable private final PriorityTaskManager priorityTaskManager; + private final CacheDataSourceFactory onlineCacheDataSourceFactory; + private final CacheDataSourceFactory offlineCacheDataSourceFactory; /** * @param cache Cache instance to be used to store downloaded data. - * @param upstreamDataSourceFactory A {@link Factory} for downloading data. + * @param upstreamFactory A {@link DataSource.Factory} for creating {@link DataSource}s for + * downloading data. */ - public DownloaderConstructorHelper(Cache cache, Factory upstreamDataSourceFactory) { - this(cache, upstreamDataSourceFactory, null, null, null); + public DownloaderConstructorHelper(Cache cache, DataSource.Factory upstreamFactory) { + this( + cache, + upstreamFactory, + /* cacheReadDataSourceFactory= */ null, + /* cacheWriteDataSinkFactory= */ null, + /* priorityTaskManager= */ null); } /** * @param cache Cache instance to be used to store downloaded data. - * @param upstreamDataSourceFactory A {@link Factory} for downloading data. - * @param cacheReadDataSourceFactory A {@link Factory} for reading data from the cache. If null - * then standard {@link FileDataSource} instances will be used. - * @param cacheWriteDataSinkFactory A {@link DataSink.Factory} for writing data to the cache. If - * null then standard {@link CacheDataSink} instances will be used. + * @param upstreamFactory A {@link DataSource.Factory} for creating {@link DataSource}s for + * downloading data. + * @param cacheReadDataSourceFactory A {@link DataSource.Factory} for creating {@link DataSource}s + * for reading data from the cache. If null then a {@link FileDataSourceFactory} will be used. + * @param cacheWriteDataSinkFactory A {@link DataSink.Factory} for creating {@link DataSource}s + * for writing data to the cache. If null then a {@link CacheDataSinkFactory} will be used. * @param priorityTaskManager A {@link PriorityTaskManager} to use when downloading. If non-null, * downloaders will register as tasks with priority {@link C#PRIORITY_DOWNLOAD} whilst * downloading. */ public DownloaderConstructorHelper( Cache cache, - Factory upstreamDataSourceFactory, - @Nullable Factory cacheReadDataSourceFactory, + DataSource.Factory upstreamFactory, + @Nullable DataSource.Factory cacheReadDataSourceFactory, @Nullable DataSink.Factory cacheWriteDataSinkFactory, @Nullable PriorityTaskManager priorityTaskManager) { - Assertions.checkNotNull(upstreamDataSourceFactory); + this( + cache, + upstreamFactory, + cacheReadDataSourceFactory, + cacheWriteDataSinkFactory, + priorityTaskManager, + /* cacheKeyFactory= */ null); + } + + /** + * @param cache Cache instance to be used to store downloaded data. + * @param upstreamFactory A {@link DataSource.Factory} for creating {@link DataSource}s for + * downloading data. + * @param cacheReadDataSourceFactory A {@link DataSource.Factory} for creating {@link DataSource}s + * for reading data from the cache. If null then a {@link FileDataSourceFactory} will be used. + * @param cacheWriteDataSinkFactory A {@link DataSink.Factory} for creating {@link DataSource}s + * for writing data to the cache. If null then a {@link CacheDataSinkFactory} will be used. + * @param priorityTaskManager A {@link PriorityTaskManager} to use when downloading. If non-null, + * downloaders will register as tasks with priority {@link C#PRIORITY_DOWNLOAD} whilst + * downloading. + * @param cacheKeyFactory An optional factory for cache keys. + */ + public DownloaderConstructorHelper( + Cache cache, + DataSource.Factory upstreamFactory, + @Nullable DataSource.Factory cacheReadDataSourceFactory, + @Nullable DataSink.Factory cacheWriteDataSinkFactory, + @Nullable PriorityTaskManager priorityTaskManager, + @Nullable CacheKeyFactory cacheKeyFactory) { + if (priorityTaskManager != null) { + upstreamFactory = + new PriorityDataSourceFactory(upstreamFactory, priorityTaskManager, C.PRIORITY_DOWNLOAD); + } + DataSource.Factory readDataSourceFactory = + cacheReadDataSourceFactory != null + ? cacheReadDataSourceFactory + : new FileDataSourceFactory(); + DataSink.Factory writeDataSinkFactory = + cacheWriteDataSinkFactory != null + ? cacheWriteDataSinkFactory + : new CacheDataSinkFactory(cache, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE); + onlineCacheDataSourceFactory = + new CacheDataSourceFactory( + cache, + upstreamFactory, + readDataSourceFactory, + writeDataSinkFactory, + CacheDataSource.FLAG_BLOCK_ON_CACHE, + /* eventListener= */ null, + cacheKeyFactory); + offlineCacheDataSourceFactory = + new CacheDataSourceFactory( + cache, + DummyDataSource.FACTORY, + readDataSourceFactory, + null, + CacheDataSource.FLAG_BLOCK_ON_CACHE, + /* eventListener= */ null, + cacheKeyFactory); this.cache = cache; - this.upstreamDataSourceFactory = upstreamDataSourceFactory; - this.cacheReadDataSourceFactory = cacheReadDataSourceFactory; - this.cacheWriteDataSinkFactory = cacheWriteDataSinkFactory; this.priorityTaskManager = priorityTaskManager; } @@ -83,26 +144,16 @@ public final class DownloaderConstructorHelper { return priorityTaskManager != null ? priorityTaskManager : new PriorityTaskManager(); } - /** - * Returns a new {@link CacheDataSource} instance. If {@code offline} is true, it can only read - * data from the cache. - */ - public CacheDataSource buildCacheDataSource(boolean offline) { - DataSource cacheReadDataSource = cacheReadDataSourceFactory != null - ? cacheReadDataSourceFactory.createDataSource() : new FileDataSource(); - if (offline) { - return new CacheDataSource(cache, DummyDataSource.INSTANCE, - cacheReadDataSource, null, CacheDataSource.FLAG_BLOCK_ON_CACHE, null); - } else { - DataSink cacheWriteDataSink = cacheWriteDataSinkFactory != null - ? cacheWriteDataSinkFactory.createDataSink() - : new CacheDataSink(cache, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE); - DataSource upstream = upstreamDataSourceFactory.createDataSource(); - upstream = priorityTaskManager == null ? upstream - : new PriorityDataSource(upstream, priorityTaskManager, C.PRIORITY_DOWNLOAD); - return new CacheDataSource(cache, upstream, cacheReadDataSource, - cacheWriteDataSink, CacheDataSource.FLAG_BLOCK_ON_CACHE, null); - } + /** Returns a new {@link CacheDataSource} instance. */ + public CacheDataSource createCacheDataSource() { + return onlineCacheDataSourceFactory.createDataSource(); } + /** + * Returns a new {@link CacheDataSource} instance which accesses cache read-only and throws an + * exception on cache miss. + */ + public CacheDataSource createOfflineCacheDataSource() { + return offlineCacheDataSourceFactory.createDataSource(); + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java index 8c80a23d67..adb1c80b0e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java @@ -50,7 +50,7 @@ public final class ProgressiveDownloader implements Downloader { Uri uri, String customCacheKey, DownloaderConstructorHelper constructorHelper) { this.dataSpec = new DataSpec(uri, 0, C.LENGTH_UNSET, customCacheKey, 0); this.cache = constructorHelper.getCache(); - this.dataSource = constructorHelper.buildCacheDataSource(false); + this.dataSource = constructorHelper.createCacheDataSource(); this.priorityTaskManager = constructorHelper.getPriorityTaskManager(); cachingCounters = new CachingCounters(); isCanceled = new AtomicBoolean(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java index 625ec4f5e7..2fdaaa4cbd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java @@ -84,8 +84,8 @@ public abstract class SegmentDownloader> impleme this.manifestUri = manifestUri; this.streamKeys = new ArrayList<>(streamKeys); this.cache = constructorHelper.getCache(); - this.dataSource = constructorHelper.buildCacheDataSource(false); - this.offlineDataSource = constructorHelper.buildCacheDataSource(true); + this.dataSource = constructorHelper.createCacheDataSource(); + this.offlineDataSource = constructorHelper.createOfflineCacheDataSource(); this.priorityTaskManager = constructorHelper.getPriorityTaskManager(); totalSegments = C.LENGTH_UNSET; isCanceled = new AtomicBoolean(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index fa2068b99d..37bc0aca37 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -57,8 +57,9 @@ public final class CacheDataSource implements DataSource { public static final long DEFAULT_MAX_CACHE_FILE_SIZE = 2 * 1024 * 1024; /** - * Flags controlling the cache's behavior. Possible flag values are {@link #FLAG_BLOCK_ON_CACHE}, - * {@link #FLAG_IGNORE_CACHE_ON_ERROR} and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}. + * Flags controlling the CacheDataSource's behavior. Possible flag values are {@link + * #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} and {@link + * #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -219,8 +220,13 @@ public final class CacheDataSource implements DataSource { * and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0. * @param eventListener An optional {@link EventListener} to receive events. */ - public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, - DataSink cacheWriteDataSink, @Flags int flags, @Nullable EventListener eventListener) { + public CacheDataSource( + Cache cache, + DataSource upstream, + DataSource cacheReadDataSource, + @Nullable DataSink cacheWriteDataSink, + @Flags int flags, + @Nullable EventListener eventListener) { this( cache, upstream, @@ -250,7 +256,7 @@ public final class CacheDataSource implements DataSource { Cache cache, DataSource upstream, DataSource cacheReadDataSource, - DataSink cacheWriteDataSink, + @Nullable DataSink cacheWriteDataSink, @Flags int flags, @Nullable EventListener eventListener, @Nullable CacheKeyFactory cacheKeyFactory) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java index f0285da274..d9f9470fe3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java @@ -15,69 +15,107 @@ */ package com.google.android.exoplayer2.upstream.cache; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.upstream.DataSink; import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DataSource.Factory; import com.google.android.exoplayer2.upstream.FileDataSourceFactory; -import com.google.android.exoplayer2.upstream.cache.CacheDataSource.EventListener; -/** - * A {@link DataSource.Factory} that produces {@link CacheDataSource}. - */ +/** A {@link DataSource.Factory} that produces {@link CacheDataSource}. */ public final class CacheDataSourceFactory implements DataSource.Factory { private final Cache cache; private final DataSource.Factory upstreamFactory; private final DataSource.Factory cacheReadDataSourceFactory; - private final DataSink.Factory cacheWriteDataSinkFactory; - private final int flags; - private final EventListener eventListener; + @CacheDataSource.Flags private final int flags; + @Nullable private final DataSink.Factory cacheWriteDataSinkFactory; + @Nullable private final CacheDataSource.EventListener eventListener; + @Nullable private final CacheKeyFactory cacheKeyFactory; /** - * @see CacheDataSource#CacheDataSource(Cache, DataSource) + * Constructs a factory which creates {@link CacheDataSource} instances with default {@link + * DataSource} and {@link DataSink} instances for reading and writing the cache. + * + * @param cache The cache. + * @param upstreamFactory A {@link DataSource.Factory} for creating upstream {@link DataSource}s + * for reading data not in the cache. */ public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory) { - this(cache, upstreamFactory, 0); + this(cache, upstreamFactory, /* flags= */ 0); } - /** - * @see CacheDataSource#CacheDataSource(Cache, DataSource, int) - */ - public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, - @CacheDataSource.Flags int flags) { + /** @see CacheDataSource#CacheDataSource(Cache, DataSource, int) */ + public CacheDataSourceFactory( + Cache cache, DataSource.Factory upstreamFactory, @CacheDataSource.Flags int flags) { this(cache, upstreamFactory, flags, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE); } - /** - * @see CacheDataSource#CacheDataSource(Cache, DataSource, int, long) - */ - public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, - @CacheDataSource.Flags int flags, long maxCacheFileSize) { - this(cache, upstreamFactory, new FileDataSourceFactory(), - new CacheDataSinkFactory(cache, maxCacheFileSize), flags, null); + /** @see CacheDataSource#CacheDataSource(Cache, DataSource, int, long) */ + public CacheDataSourceFactory( + Cache cache, + DataSource.Factory upstreamFactory, + @CacheDataSource.Flags int flags, + long maxCacheFileSize) { + this( + cache, + upstreamFactory, + new FileDataSourceFactory(), + new CacheDataSinkFactory(cache, maxCacheFileSize), + flags, + /* eventListener= */ null); } /** * @see CacheDataSource#CacheDataSource(Cache, DataSource, DataSource, DataSink, int, - * EventListener) + * CacheDataSource.EventListener) */ - public CacheDataSourceFactory(Cache cache, Factory upstreamFactory, - Factory cacheReadDataSourceFactory, DataSink.Factory cacheWriteDataSinkFactory, - @CacheDataSource.Flags int flags, EventListener eventListener) { + public CacheDataSourceFactory( + Cache cache, + DataSource.Factory upstreamFactory, + DataSource.Factory cacheReadDataSourceFactory, + @Nullable DataSink.Factory cacheWriteDataSinkFactory, + @CacheDataSource.Flags int flags, + @Nullable CacheDataSource.EventListener eventListener) { + this( + cache, + upstreamFactory, + cacheReadDataSourceFactory, + cacheWriteDataSinkFactory, + flags, + eventListener, + /* cacheKeyFactory= */ null); + } + + /** + * @see CacheDataSource#CacheDataSource(Cache, DataSource, DataSource, DataSink, int, + * CacheDataSource.EventListener, CacheKeyFactory) + */ + public CacheDataSourceFactory( + Cache cache, + DataSource.Factory upstreamFactory, + DataSource.Factory cacheReadDataSourceFactory, + @Nullable DataSink.Factory cacheWriteDataSinkFactory, + @CacheDataSource.Flags int flags, + @Nullable CacheDataSource.EventListener eventListener, + @Nullable CacheKeyFactory cacheKeyFactory) { this.cache = cache; this.upstreamFactory = upstreamFactory; this.cacheReadDataSourceFactory = cacheReadDataSourceFactory; this.cacheWriteDataSinkFactory = cacheWriteDataSinkFactory; this.flags = flags; this.eventListener = eventListener; + this.cacheKeyFactory = cacheKeyFactory; } @Override public CacheDataSource createDataSource() { - return new CacheDataSource(cache, upstreamFactory.createDataSource(), + return new CacheDataSource( + cache, + upstreamFactory.createDataSource(), cacheReadDataSourceFactory.createDataSource(), - cacheWriteDataSinkFactory != null ? cacheWriteDataSinkFactory.createDataSink() : null, - flags, eventListener); + cacheWriteDataSinkFactory == null ? null : cacheWriteDataSinkFactory.createDataSink(), + flags, + eventListener, + cacheKeyFactory); } } From 645f08af73f4e69fa42a5256e64c11ae504ae2b8 Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 3 Oct 2018 06:04:16 -0700 Subject: [PATCH 053/832] Prevent binding to DownloadService DownloadService isn't designed to be bound. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215552423 --- .../google/android/exoplayer2/offline/DownloadService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java index 5c6cf88b48..e9492f6b87 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java @@ -303,10 +303,11 @@ public abstract class DownloadService extends Service { maybeStopWatchingRequirements(); } + /** DownloadService isn't designed to be bound. */ @Nullable @Override - public IBinder onBind(Intent intent) { - return null; + public final IBinder onBind(Intent intent) { + throw new UnsupportedOperationException(); } /** From e91065c4b070d239d17b1a5d22e4c5bf3ac27222 Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 3 Oct 2018 07:56:51 -0700 Subject: [PATCH 054/832] Remove ProjectionRenderer.EyeType ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215562599 --- .../ui/spherical/ProjectionRenderer.java | 23 +++++-------------- .../ui/spherical/SceneRenderer.java | 8 +++---- .../ui/spherical/SphericalSurfaceView.java | 3 +-- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java index 47515b023a..5e8a6d71d2 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/ProjectionRenderer.java @@ -33,18 +33,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; @TargetApi(15) /* package */ final class ProjectionRenderer { - /** Defines the constants identifying the current eye type. */ - /* package */ interface EyeType { - /** Single eye in monocular rendering. */ - int MONOCULAR = 0; - - /** The left eye in stereo rendering. */ - int LEFT = 1; - - /** The right eye in stereo rendering. */ - int RIGHT = 2; - } - /** * Returns whether {@code projection} is supported. At least it should have left mesh and there * should be only one sub mesh per mesh. @@ -148,10 +136,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; * * @param textureId GL_TEXTURE_EXTERNAL_OES used for this mesh. * @param mvpMatrix The Model View Projection matrix. - * @param eyeType An {@link EyeType} value. + * @param rightEye Whether the right eye view should be drawn. If {@code false}, the left eye view + * is drawn. */ - /* package */ void draw(int textureId, float[] mvpMatrix, int eyeType) { - MeshData meshData = eyeType == EyeType.RIGHT ? rightMeshData : leftMeshData; + /* package */ void draw(int textureId, float[] mvpMatrix, boolean rightEye) { + MeshData meshData = rightEye ? rightMeshData : leftMeshData; if (meshData == null) { return; } @@ -166,9 +155,9 @@ import org.checkerframework.checker.nullness.qual.Nullable; float[] texMatrix; if (stereoMode == C.STEREO_MODE_TOP_BOTTOM) { - texMatrix = eyeType == EyeType.RIGHT ? TEX_MATRIX_BOTTOM : TEX_MATRIX_TOP; + texMatrix = rightEye ? TEX_MATRIX_BOTTOM : TEX_MATRIX_TOP; } else if (stereoMode == C.STEREO_MODE_LEFT_RIGHT) { - texMatrix = eyeType == EyeType.RIGHT ? TEX_MATRIX_RIGHT : TEX_MATRIX_LEFT; + texMatrix = rightEye ? TEX_MATRIX_RIGHT : TEX_MATRIX_LEFT; } else { texMatrix = TEX_MATRIX_WHOLE; } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java index 048c346781..5099b2187d 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java @@ -23,7 +23,6 @@ import android.opengl.Matrix; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.ui.spherical.ProjectionRenderer.EyeType; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.util.TimedValueQueue; @@ -103,9 +102,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * Draws the scene with a given eye pose and type. * * @param viewProjectionMatrix 16 element GL matrix. - * @param eyeType An {@link EyeType} value + * @param rightEye Whether the right eye view should be drawn. If {@code false}, the left eye view + * is drawn. */ - public void drawFrame(float[] viewProjectionMatrix, int eyeType) { + public void drawFrame(float[] viewProjectionMatrix, boolean rightEye) { // glClear isn't strictly necessary when rendering fully spherical panoramas, but it can improve // performance on tiled renderers by causing the GPU to discard previous data. GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); @@ -128,7 +128,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } } Matrix.multiplyMM(tempMatrix, 0, viewProjectionMatrix, 0, rotationMatrix, 0); - projectionRenderer.draw(textureId, tempMatrix, eyeType); + projectionRenderer.draw(textureId, tempMatrix, rightEye); } // Methods called on playback thread. diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java index 9139137af4..bae29a6858 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java @@ -36,7 +36,6 @@ import android.view.Surface; import android.view.WindowManager; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.ui.spherical.ProjectionRenderer.EyeType; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import javax.microedition.khronos.egl.EGLConfig; @@ -287,7 +286,7 @@ public final class SphericalSurfaceView extends GLSurfaceView { } Matrix.multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0); - scene.drawFrame(viewProjectionMatrix, EyeType.MONOCULAR); + scene.drawFrame(viewProjectionMatrix, /* rightEye= */ false); } /** Adjusts the GL camera's rotation based on device rotation. Runs on the sensor thread. */ From 7849a5eb52a434883a0c0d33f0b9a9ab20067679 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 3 Oct 2018 22:19:13 +0100 Subject: [PATCH 055/832] Subrip cleanup --- RELEASENOTES.md | 2 + .../exoplayer2/text/subrip/SubripDecoder.java | 171 ++++++++---------- .../src/test/assets/subrip/typical_with_tags | 4 +- .../text/subrip/SubripDecoderTest.java | 105 +++-------- 4 files changed, 111 insertions(+), 171 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9d9d1fa0c6..0510055942 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -17,6 +17,8 @@ * Fix issue where subtitles have a wrong position if SubtitleView has a non-zero offset to its parent ([#4788](https://github.com/google/ExoPlayer/issues/4788)). +* SubRip: Add support for alignment tags, and remove tags from the displayed + captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index 182f1cf4b0..3b039061b0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -15,9 +15,8 @@ */ package com.google.android.exoplayer2.text.subrip; -import android.support.annotation.StringDef; +import android.support.annotation.Nullable; import android.text.Html; -import android.text.Layout; import android.text.Spanned; import android.text.TextUtils; import com.google.android.exoplayer2.text.Cue; @@ -25,9 +24,6 @@ import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.LongArray; import com.google.android.exoplayer2.util.ParsableByteArray; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,6 +33,11 @@ import java.util.regex.Pattern; */ public final class SubripDecoder extends SimpleSubtitleDecoder { + // Fractional positions for use when alignment tags are present. + /* package */ static final float START_FRACTION = 0.08f; + /* package */ static final float END_FRACTION = 1 - START_FRACTION; + /* package */ static final float MID_FRACTION = 0.5f; + private static final String TAG = "SubripDecoder"; private static final String SUBRIP_TIMECODE = "(?:(\\d+):)?(\\d+):(\\d+),(\\d+)"; @@ -46,35 +47,24 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { private static final Pattern SUBRIP_TAG_PATTERN = Pattern.compile("\\{\\\\.*?\\}"); private static final String SUBRIP_ALIGNMENT_TAG = "\\{\\\\an[1-9]\\}"; - private static final float DEFAULT_START_FRACTION = 0.08f; - private static final float DEFAULT_END_FRACTION = 1 - DEFAULT_START_FRACTION; - private static final float DEFAULT_MID_FRACTION = 0.5f; - - @Retention(RetentionPolicy.SOURCE) - @StringDef({ - ALIGN_BOTTOM_LEFT, ALIGN_BOTTOM_MID, ALIGN_BOTTOM_RIGHT, - ALIGN_MID_LEFT, ALIGN_MID_MID, ALIGN_MID_RIGHT, - ALIGN_TOP_LEFT, ALIGN_TOP_MID, ALIGN_TOP_RIGHT - }) - - private @interface SubRipTag {} - - // Possible valid alignment tags based on SSA v4+ specs - private static final String ALIGN_BOTTOM_LEFT = "{\\an1}"; - private static final String ALIGN_BOTTOM_MID = "{\\an2}"; + // Alignment tags for SSA V4+. + private static final String ALIGN_BOTTOM_LEFT = "{\\an1}"; + private static final String ALIGN_BOTTOM_MID = "{\\an2}"; private static final String ALIGN_BOTTOM_RIGHT = "{\\an3}"; - private static final String ALIGN_MID_LEFT = "{\\an4}"; - private static final String ALIGN_MID_MID = "{\\an5}"; - private static final String ALIGN_MID_RIGHT = "{\\an6}"; - private static final String ALIGN_TOP_LEFT = "{\\an7}"; - private static final String ALIGN_TOP_MID = "{\\an8}"; - private static final String ALIGN_TOP_RIGHT = "{\\an9}"; + private static final String ALIGN_MID_LEFT = "{\\an4}"; + private static final String ALIGN_MID_MID = "{\\an5}"; + private static final String ALIGN_MID_RIGHT = "{\\an6}"; + private static final String ALIGN_TOP_LEFT = "{\\an7}"; + private static final String ALIGN_TOP_MID = "{\\an8}"; + private static final String ALIGN_TOP_RIGHT = "{\\an9}"; private final StringBuilder textBuilder; + private final ArrayList tags; public SubripDecoder() { super("SubripDecoder"); textBuilder = new StringBuilder(); + tags = new ArrayList<>(); } @Override @@ -118,9 +108,9 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { continue; } - // Read and parse the text. - ArrayList tags = new ArrayList<>(); + // Read and parse the text and tags. textBuilder.setLength(0); + tags.clear(); while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { if (textBuilder.length() > 0) { textBuilder.append("
    "); @@ -129,21 +119,17 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { } Spanned text = Html.fromHtml(textBuilder.toString()); - Cue cue = null; - // At end of this loop the clue must be created with the applied tags - for (String tag : tags) { - - // Check if the tag is an alignment tag + String alignmentTag = null; + for (int i = 0; i < tags.size(); i++) { + String tag = tags.get(i); if (tag.matches(SUBRIP_ALIGNMENT_TAG)) { - cue = buildCue(text, tag); - - // Based on the specs, in case of alignment tags only the first appearance counts, so break + alignmentTag = tag; + // Subsequent alignment tags should be ignored. break; } } - - cues.add(cue == null ? new Cue(text) : cue); + cues.add(buildCue(text, alignmentTag)); if (haveEndTimecode) { cues.add(null); @@ -157,108 +143,93 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { } /** - * Process the given line by first trimming it then extracting the tags from it - *

    - * The pattern that is used to extract the tags is specified in SSA v4+ specs and - * has the following form: "{\...}". - *

    - * "All override codes appear within braces {}" - * "All override codes are always preceded by a backslash \" + * Trims and removes tags from the given line. The removed tags are added to {@code tags}. * - * @param currentLine Current line - * @param tags Extracted tags will be stored in this array list - * @return Processed line + * @param line The line to process. + * @param tags A list to which removed tags will be added. + * @return The processed line. */ - private String processLine(String currentLine, ArrayList tags) { - // Trim line - String trimmedLine = currentLine.trim(); - - // Extract tags - int replacedCharacters = 0; - StringBuilder processedLine = new StringBuilder(trimmedLine); - Matcher matcher = SUBRIP_TAG_PATTERN.matcher(trimmedLine); + private String processLine(String line, ArrayList tags) { + line = line.trim(); + int removedCharacterCount = 0; + StringBuilder processedLine = new StringBuilder(line); + Matcher matcher = SUBRIP_TAG_PATTERN.matcher(line); while (matcher.find()) { String tag = matcher.group(); tags.add(tag); - processedLine.replace(matcher.start() - replacedCharacters, matcher.end() - replacedCharacters, ""); - replacedCharacters += tag.length(); + int start = matcher.start() - removedCharacterCount; + int tagLength = tag.length(); + processedLine.replace(start, /* end= */ start + tagLength, /* str= */ ""); + removedCharacterCount += tagLength; } return processedLine.toString(); } /** - * Build a {@link Cue} based on the given text and tag - *

    - * Match the alignment tag and calculate the line, position, position anchor accordingly - *

    - * Based on SSA v4+ specs the alignment tag can have the following form: {\an[1-9}, - * where the number specifies the direction (based on the numpad layout). - * Note. older SSA scripts may contain tags like {\a1[1-9]} but these are based on - * other direction rules, but multiple sources says that these are deprecated, so no support here either + * Build a {@link Cue} based on the given text and alignment tag. * - * @param alignmentTag Alignment tag + * @param text The text. + * @param alignmentTag The alignment tag, or {@code null} if no alignment tag is available. * @return Built cue */ - private Cue buildCue(Spanned text, String alignmentTag) { - float line, position; - @Cue.AnchorType int positionAnchor; - @Cue.AnchorType int lineAnchor; + private Cue buildCue(Spanned text, @Nullable String alignmentTag) { + if (alignmentTag == null) { + return new Cue(text); + } - // Set position and position anchor (horizontal alignment) + // Horizontal alignment. + @Cue.AnchorType int positionAnchor; switch (alignmentTag) { case ALIGN_BOTTOM_LEFT: case ALIGN_MID_LEFT: case ALIGN_TOP_LEFT: - position = DEFAULT_START_FRACTION; positionAnchor = Cue.ANCHOR_TYPE_START; break; - case ALIGN_BOTTOM_MID: - case ALIGN_MID_MID: - case ALIGN_TOP_MID: - position = DEFAULT_MID_FRACTION; - positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; - break; case ALIGN_BOTTOM_RIGHT: case ALIGN_MID_RIGHT: case ALIGN_TOP_RIGHT: - position = DEFAULT_END_FRACTION; positionAnchor = Cue.ANCHOR_TYPE_END; break; + case ALIGN_BOTTOM_MID: + case ALIGN_MID_MID: + case ALIGN_TOP_MID: default: - position = DEFAULT_MID_FRACTION; positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; break; } - // Set line and line anchor (vertical alignment) + // Vertical alignment. + @Cue.AnchorType int lineAnchor; switch (alignmentTag) { case ALIGN_BOTTOM_LEFT: case ALIGN_BOTTOM_MID: case ALIGN_BOTTOM_RIGHT: - line = DEFAULT_END_FRACTION; lineAnchor = Cue.ANCHOR_TYPE_END; break; - case ALIGN_MID_LEFT: - case ALIGN_MID_MID: - case ALIGN_MID_RIGHT: - line = DEFAULT_MID_FRACTION; - lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; - break; case ALIGN_TOP_LEFT: case ALIGN_TOP_MID: case ALIGN_TOP_RIGHT: - line = DEFAULT_START_FRACTION; lineAnchor = Cue.ANCHOR_TYPE_START; break; + case ALIGN_MID_LEFT: + case ALIGN_MID_MID: + case ALIGN_MID_RIGHT: default: - line = DEFAULT_END_FRACTION; - lineAnchor = Cue.ANCHOR_TYPE_END; + lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; break; } - return new Cue(text, null, line, Cue.LINE_TYPE_FRACTION, lineAnchor, position, positionAnchor, Cue.DIMEN_UNSET); + return new Cue( + text, + /* textAlignment= */ null, + getFractionalPositionForAnchorType(lineAnchor), + Cue.LINE_TYPE_FRACTION, + lineAnchor, + getFractionalPositionForAnchorType(positionAnchor), + positionAnchor, + Cue.DIMEN_UNSET); } private static long parseTimecode(Matcher matcher, int groupOffset) { @@ -268,4 +239,16 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { timestampMs += Long.parseLong(matcher.group(groupOffset + 4)); return timestampMs * 1000; } + + /* package */ static float getFractionalPositionForAnchorType(@Cue.AnchorType int anchorType) { + switch (anchorType) { + case Cue.ANCHOR_TYPE_START: + return SubripDecoder.START_FRACTION; + case Cue.ANCHOR_TYPE_MIDDLE: + return SubripDecoder.MID_FRACTION; + case Cue.ANCHOR_TYPE_END: + default: + return SubripDecoder.END_FRACTION; + } + } } diff --git a/library/core/src/test/assets/subrip/typical_with_tags b/library/core/src/test/assets/subrip/typical_with_tags index af196f8a04..85e304b498 100644 --- a/library/core/src/test/assets/subrip/typical_with_tags +++ b/library/core/src/test/assets/subrip/typical_with_tags @@ -13,7 +13,7 @@ This {\an2} is the third {\ tag} subtitle. 4 00:00:09,567 --> 00:00:12,901 -This { \an2} is the fourth subtitle. +This { \an2} is not a valid tag due to the space after the opening bracket. 5 00:00:013,567 --> 00:00:14,901 @@ -53,4 +53,4 @@ This {\an8} is a line. 14 00:00:024,567 --> 00:00:24,901 -This {\an9} is a line. \ No newline at end of file +This {\an9} is a line. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java index 554184da5d..1430c70e09 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.text.Cue; - import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; @@ -158,89 +157,30 @@ public final class SubripDecoderTest { } @Test - public void testDecodeCueWithTag() throws IOException{ + public void testDecodeCueWithTag() throws IOException { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_WITH_TAGS); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); - assertThat(subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString()) - .isEqualTo("This is the first subtitle."); - assertThat(subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString()) - .isEqualTo("This is the second subtitle.\nSecond subtitle with second line."); - assertThat(subtitle.getCues(subtitle.getEventTime(4)).get(0).text.toString()) - .isEqualTo("This is the third subtitle."); - // Based on the SSA v4+ specs the curly bracket must be followed by a backslash, so this is - // not a valid tag (won't be parsed / replaced) + assertTypicalCue1(subtitle, 0); + assertTypicalCue2(subtitle, 2); + assertTypicalCue3(subtitle, 4); + assertThat(subtitle.getCues(subtitle.getEventTime(6)).get(0).text.toString()) - .isEqualTo("This { \\an2} is the fourth subtitle."); + .isEqualTo("This { \\an2} is not a valid tag due to the space after the opening bracket."); assertThat(subtitle.getCues(subtitle.getEventTime(8)).get(0).text.toString()) .isEqualTo("This is the fifth subtitle with multiple valid tags."); - // Verify positions - - // {/an1} - assertThat(subtitle.getCues(subtitle.getEventTime(10)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); - - assertThat(subtitle.getCues(subtitle.getEventTime(10)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - // {/an2} - assertThat(subtitle.getCues(subtitle.getEventTime(12)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - assertThat(subtitle.getCues(subtitle.getEventTime(12)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - // {/an3} - assertThat(subtitle.getCues(subtitle.getEventTime(14)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - assertThat(subtitle.getCues(subtitle.getEventTime(14)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - // {/an4} - assertThat(subtitle.getCues(subtitle.getEventTime(16)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); - - assertThat(subtitle.getCues(subtitle.getEventTime(16)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - // {/an5} - assertThat(subtitle.getCues(subtitle.getEventTime(18)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - assertThat(subtitle.getCues(subtitle.getEventTime(18)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - // {/an6} - assertThat(subtitle.getCues(subtitle.getEventTime(20)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - assertThat(subtitle.getCues(subtitle.getEventTime(20)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - // {/an7} - assertThat(subtitle.getCues(subtitle.getEventTime(22)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); - - assertThat(subtitle.getCues(subtitle.getEventTime(22)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); - - // {/an8} - assertThat(subtitle.getCues(subtitle.getEventTime(24)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - assertThat(subtitle.getCues(subtitle.getEventTime(24)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); - - // {/an9} - assertThat(subtitle.getCues(subtitle.getEventTime(26)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - assertThat(subtitle.getCues(subtitle.getEventTime(26)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); + assertAlignmentCue(subtitle, 10, Cue.ANCHOR_TYPE_END, Cue.ANCHOR_TYPE_START); // {/an1} + assertAlignmentCue(subtitle, 12, Cue.ANCHOR_TYPE_END, Cue.ANCHOR_TYPE_MIDDLE); // {/an2} + assertAlignmentCue(subtitle, 14, Cue.ANCHOR_TYPE_END, Cue.ANCHOR_TYPE_END); // {/an3} + assertAlignmentCue(subtitle, 16, Cue.ANCHOR_TYPE_MIDDLE, Cue.ANCHOR_TYPE_START); // {/an4} + assertAlignmentCue(subtitle, 18, Cue.ANCHOR_TYPE_MIDDLE, Cue.ANCHOR_TYPE_MIDDLE); // {/an5} + assertAlignmentCue(subtitle, 20, Cue.ANCHOR_TYPE_MIDDLE, Cue.ANCHOR_TYPE_END); // {/an6} + assertAlignmentCue(subtitle, 22, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_START); // {/an7} + assertAlignmentCue(subtitle, 24, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_MIDDLE); // {/an8} + assertAlignmentCue(subtitle, 26, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_END); // {/an9} } private static void assertTypicalCue1(SubripSubtitle subtitle, int eventIndex) { @@ -263,4 +203,19 @@ public final class SubripDecoderTest { .isEqualTo("This is the third subtitle."); assertThat(subtitle.getEventTime(eventIndex + 1)).isEqualTo(8901000); } + + private static void assertAlignmentCue( + SubripSubtitle subtitle, + int eventIndex, + @Cue.AnchorType int lineAnchor, + @Cue.AnchorType int positionAnchor) { + long eventTimeUs = subtitle.getEventTime(eventIndex); + Cue cue = subtitle.getCues(eventTimeUs).get(0); + assertThat(cue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(cue.lineAnchor).isEqualTo(lineAnchor); + assertThat(cue.line).isEqualTo(SubripDecoder.getFractionalPositionForAnchorType(lineAnchor)); + assertThat(cue.positionAnchor).isEqualTo(positionAnchor); + assertThat(cue.position) + .isEqualTo(SubripDecoder.getFractionalPositionForAnchorType(positionAnchor)); + } } From 35cc479f76ec8d96a27409398acc0275f672a713 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Thu, 4 Oct 2018 11:44:59 -0600 Subject: [PATCH 056/832] Create unique id for hls audio and text tracks Combine groupId and name for uniqueness --- .../hls/playlist/HlsPlaylistParser.java | 7 ++-- .../playlist/HlsMasterPlaylistParserTest.java | 37 ++++++++++++++++++- 2 files changed, 40 insertions(+), 4 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 49826902cd..bd2bea0197 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 @@ -341,6 +341,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser Date: Fri, 5 Oct 2018 08:37:30 -0600 Subject: [PATCH 057/832] add test for setForceHighestSupportedBitrate --- .../DefaultTrackSelectorTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index ee3d2cf9b0..8bb638d267 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -1078,6 +1078,33 @@ public final class DefaultTrackSelectorTest { assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerBitrateFormat); } + /** + * Tests that track selector will select audio tracks with higher bitrate when {@link Parameters} + * indicate highest bitrate preference, even when tracks are within capabilities. + */ + @Test + public void testSelectTracksWithinCapabilitiesAndForceHighestBitrateSelectHigherBitrate() + throws Exception { + trackSelector.setParameters(new ParametersBuilder().setForceHighestSupportedBitrate(true).build()); + + Format lowerBitrateFormat = + Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000, + Format.NO_VALUE, 2, 44100, null, null, 0, null); + Format higherBitrateFormat = + Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, + Format.NO_VALUE, 2, 44100, null, null, 0, null); + + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), + periodId, + TIMELINE); + + assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherBitrateFormat); + } + + @Test public void testSelectTracksWithMultipleAudioTracksReturnsAdaptiveTrackSelection() throws Exception { From e42786d6f2e0a6ece1c2e26f9e62e2e8638d1ecc Mon Sep 17 00:00:00 2001 From: "Lieblich, Jonathan" Date: Wed, 10 Oct 2018 15:34:29 -0600 Subject: [PATCH 058/832] added parsing for program information --- .../source/dash/manifest/DashManifest.java | 10 ++- .../dash/manifest/DashManifestParser.java | 46 ++++++++++++- .../dash/manifest/ProgramInformation.java | 67 +++++++++++++++++++ library/dash/src/test/assets/sample_mpd_1 | 3 + .../dash/manifest/DashManifestParserTest.java | 12 ++++ .../dash/manifest/DashManifestTest.java | 2 +- 6 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java index 1fdb137be9..6446909808 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java @@ -86,12 +86,17 @@ public class DashManifest implements FilterableManifest { */ public final Uri location; + /** + * The ProgramInformation of this manifest. + */ + public final ProgramInformation programInformation; + private final List periods; public DashManifest(long availabilityStartTimeMs, long durationMs, long minBufferTimeMs, boolean dynamic, long minUpdatePeriodMs, long timeShiftBufferDepthMs, long suggestedPresentationDelayMs, long publishTimeMs, UtcTimingElement utcTiming, - Uri location, List periods) { + Uri location, ProgramInformation programInformation, List periods) { this.availabilityStartTimeMs = availabilityStartTimeMs; this.durationMs = durationMs; this.minBufferTimeMs = minBufferTimeMs; @@ -102,6 +107,7 @@ public class DashManifest implements FilterableManifest { this.publishTimeMs = publishTimeMs; this.utcTiming = utcTiming; this.location = location; + this.programInformation = programInformation; this.periods = periods == null ? Collections.emptyList() : periods; } @@ -150,7 +156,7 @@ public class DashManifest implements FilterableManifest { long newDuration = durationMs != C.TIME_UNSET ? durationMs - shiftMs : C.TIME_UNSET; return new DashManifest(availabilityStartTimeMs, newDuration, minBufferTimeMs, dynamic, minUpdatePeriodMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, publishTimeMs, - utcTiming, location, copyPeriods); + utcTiming, location, programInformation, copyPeriods); } private static ArrayList copyAdaptationSets( diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 153856af8c..ce629f6509 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -122,6 +122,7 @@ public class DashManifestParser extends DefaultHandler long publishTimeMs = parseDateTime(xpp, "publishTime", C.TIME_UNSET); UtcTimingElement utcTiming = null; Uri location = null; + ProgramInformation programInformation = null; List periods = new ArrayList<>(); long nextPeriodStartMs = dynamic ? C.TIME_UNSET : 0; @@ -138,6 +139,8 @@ public class DashManifestParser extends DefaultHandler utcTiming = parseUtcTiming(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "Location")) { location = Uri.parse(xpp.nextText()); + } else if (XmlPullParserUtil.isStartTag(xpp, "ProgramInformation")) { + programInformation = parseProgramInformation(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) { Pair periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs); Period period = periodWithDurationMs.first; @@ -173,16 +176,16 @@ public class DashManifestParser extends DefaultHandler return buildMediaPresentationDescription(availabilityStartTime, durationMs, minBufferTimeMs, dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, - publishTimeMs, utcTiming, location, periods); + publishTimeMs, utcTiming, location, programInformation, periods); } protected DashManifest buildMediaPresentationDescription(long availabilityStartTime, long durationMs, long minBufferTimeMs, boolean dynamic, long minUpdateTimeMs, long timeShiftBufferDepthMs, long suggestedPresentationDelayMs, long publishTimeMs, - UtcTimingElement utcTiming, Uri location, List periods) { + UtcTimingElement utcTiming, Uri location, ProgramInformation programInformation, List periods) { return new DashManifest(availabilityStartTime, durationMs, minBufferTimeMs, dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, - publishTimeMs, utcTiming, location, periods); + publishTimeMs, utcTiming, location, programInformation, periods); } protected UtcTimingElement parseUtcTiming(XmlPullParser xpp) { @@ -978,6 +981,43 @@ public class DashManifestParser extends DefaultHandler return new RangedUri(urlText, rangeStart, rangeLength); } + protected ProgramInformation parseProgramInformation(XmlPullParser xpp) throws IOException, XmlPullParserException { + String title = ""; + String source = ""; + String copyright = ""; + List customEvents = new ArrayList<>(); + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "Title")) { + title = xpp.getText(); + } else if (XmlPullParserUtil.isStartTag(xpp, "Source")) { + source = xpp.getText(); + } else if (XmlPullParserUtil.isStartTag(xpp, "Copyright")) { + copyright = xpp.getText(); + } else { + byte[] customElement = parseCustomElement(xpp, new ByteArrayOutputStream(512)); + if (customElement.length > 0) { + customEvents.add(customElement); + } + } + } while (!XmlPullParserUtil.isEndTag(xpp, "ProgramInformation")); + return new ProgramInformation(title, source, copyright, customEvents); + } + + private byte[] parseCustomElement(XmlPullParser xpp, ByteArrayOutputStream outputStream) throws IOException, XmlPullParserException { + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(outputStream, C.UTF8_NAME); + if (xpp.getEventType() == XmlPullParser.START_TAG) { + serializer.startTag(xpp.getNamespace(), xpp.getName()); + for (int i = 0; i < xpp.getAttributeCount(); i++) { + serializer.attribute(xpp.getAttributeNamespace(i), xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + serializer.endTag(xpp.getNamespace(), xpp.getName()); + } + serializer.flush(); + return outputStream.toByteArray(); + } + // AudioChannelConfiguration parsing. protected int parseAudioChannelConfiguration(XmlPullParser xpp) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java new file mode 100644 index 0000000000..7d162336bc --- /dev/null +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java @@ -0,0 +1,67 @@ +package com.google.android.exoplayer2.source.dash.manifest; + +import java.util.Arrays; +import java.util.List; + +public class ProgramInformation { + /** + * The title for the media presentation. + */ + public final String title; + + /** + * Information about the original source of the media presentation. + */ + public final String source; + + /** + * A copyright statement for the media presentation. + */ + public final String copyright; + + /** + * A list of custom elements. + */ + public final List customEvents; + + public ProgramInformation(String title, String source, String copyright, List customEvents) { + this.title = title; + this.source = source; + this.copyright = copyright; + this.customEvents = customEvents; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (title != null ? title.hashCode() : 0); + result = 31 * result + (source != null ? source.hashCode() : 0); + result = 31 * result + (copyright != null ? copyright.hashCode() : 0); + for (int i = 0; i < customEvents.size(); i++) { + result = 31 * result + Arrays.hashCode(customEvents.get(i)); + } + return result; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ProgramInformation + && ((ProgramInformation)obj).title.equals(this.title) + && ((ProgramInformation)obj).source.equals(this.source) + && ((ProgramInformation)obj).copyright.equals(this.copyright) + && validateEvents(((ProgramInformation)obj).customEvents); + } + + private boolean validateEvents(List customEvents) { + for (int i = 0; i < customEvents.size() && i < this.customEvents.size(); i++) { + byte[] comparator = customEvents.get(i); + byte[] current = this.customEvents.get(i); + for (int j = 0; j < comparator.length && j < current.length; j++) { + if (current[j] != comparator[j]) { + return false; + } + } + } + return true; + } +} \ No newline at end of file diff --git a/library/dash/src/test/assets/sample_mpd_1 b/library/dash/src/test/assets/sample_mpd_1 index 07bcdd4f50..e75c1234c0 100644 --- a/library/dash/src/test/assets/sample_mpd_1 +++ b/library/dash/src/test/assets/sample_mpd_1 @@ -9,6 +9,9 @@ xmlns="urn:mpeg:DASH:schema:MPD:2011" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" yt:earliestMediaSequence="1266404" > + + + list = new ArrayList<>(); + list.add("".getBytes()); + ProgramInformation programInformation = new ProgramInformation("", "", "", list); + assertThat(programInformation).isEqualTo(mpd.programInformation); + } + @Test public void testParseCea608AccessibilityChannel() { assertThat( diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java index 94f7a28a73..17a96fded3 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java @@ -219,7 +219,7 @@ public class DashManifestTest { private static DashManifest newDashManifest(int duration, Period... periods) { return new DashManifest( - 0, duration, 1, false, 2, 3, 4, 12345, DUMMY_UTC_TIMING, Uri.EMPTY, Arrays.asList(periods)); + 0, duration, 1, false, 2, 3, 4, 12345, DUMMY_UTC_TIMING, Uri.EMPTY, null, Arrays.asList(periods)); } private static Period newPeriod(String id, int startMs, AdaptationSet... adaptationSets) { From 8a671c1cfecfad30a7f5f41e49b302b68b5dc905 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Thu, 11 Oct 2018 12:57:23 -0600 Subject: [PATCH 059/832] add check to ensure single track selection --- .../exoplayer2/trackselection/DefaultTrackSelectorTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 8bb638d267..f9f750aec8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -1075,6 +1075,7 @@ public final class DefaultTrackSelectorTest { periodId, TIMELINE); + assertThat(result.selections.get(0).length()).isEqualTo(1); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerBitrateFormat); } @@ -1101,6 +1102,7 @@ public final class DefaultTrackSelectorTest { periodId, TIMELINE); + assertThat(result.selections.get(0).length()).isEqualTo(1); assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherBitrateFormat); } From a5b72762299adaf78b3609b454e8d28ce404aab6 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Thu, 11 Oct 2018 13:01:33 -0600 Subject: [PATCH 060/832] Correct javadoc for forced bitrate tests --- .../trackselection/DefaultTrackSelectorTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index f9f750aec8..5d0b48bd7f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -1053,8 +1053,8 @@ public final class DefaultTrackSelectorTest { } /** - * Tests that track selector will select audio tracks with lower bitrate when {@link Parameters} - * indicate lowest bitrate preference, even when tracks are within capabilities. + * Tests that track selector will select the lowest supported audio track when + * {@link Parameters#forceLowestBitrate} is set. */ @Test public void testSelectTracksWithinCapabilitiesAndForceLowestBitrateSelectLowerBitrate() @@ -1080,8 +1080,8 @@ public final class DefaultTrackSelectorTest { } /** - * Tests that track selector will select audio tracks with higher bitrate when {@link Parameters} - * indicate highest bitrate preference, even when tracks are within capabilities. + * Tests that track selector will select the highest supported audio track when + * {@link Parameters#forceHighestSupportedBitrate} is set. */ @Test public void testSelectTracksWithinCapabilitiesAndForceHighestBitrateSelectHigherBitrate() From 211a2dfd7a9dc044f6c215a806e37e6c0d4ec074 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Thu, 11 Oct 2018 13:04:17 -0600 Subject: [PATCH 061/832] Ensure formats have unique ids --- .../trackselection/DefaultTrackSelectorTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 5d0b48bd7f..3e6e0cbd24 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -1062,10 +1062,10 @@ public final class DefaultTrackSelectorTest { trackSelector.setParameters(new ParametersBuilder().setForceLowestBitrate(true).build()); Format lowerBitrateFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000, + Format.createAudioSampleFormat("lowerBitrateFormat", MimeTypes.AUDIO_AAC, null, 15000, Format.NO_VALUE, 2, 44100, null, null, 0, null); Format higherBitrateFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, + Format.createAudioSampleFormat("higherBitrateFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 44100, null, null, 0, null); TrackSelectorResult result = @@ -1089,10 +1089,10 @@ public final class DefaultTrackSelectorTest { trackSelector.setParameters(new ParametersBuilder().setForceHighestSupportedBitrate(true).build()); Format lowerBitrateFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000, + Format.createAudioSampleFormat("lowerBitrateFormat", MimeTypes.AUDIO_AAC, null, 15000, Format.NO_VALUE, 2, 44100, null, null, 0, null); Format higherBitrateFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, + Format.createAudioSampleFormat("higherBitrateFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 44100, null, null, 0, null); TrackSelectorResult result = From 30c7954482d10ef305803dca7e04ff55d7e7f3b5 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Thu, 11 Oct 2018 13:05:21 -0600 Subject: [PATCH 062/832] add bitrate that is unsupported by renderer --- .../DefaultTrackSelectorTest.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 3e6e0cbd24..2eb01e051e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -1061,6 +1061,9 @@ public final class DefaultTrackSelectorTest { throws Exception { trackSelector.setParameters(new ParametersBuilder().setForceLowestBitrate(true).build()); + Format exceedsBitrateFormat = + Format.createAudioSampleFormat("exceedsBitrateFormat", MimeTypes.AUDIO_AAC, null, 5000, + Format.NO_VALUE, 2, 44100, null, null, 0, null); Format lowerBitrateFormat = Format.createAudioSampleFormat("lowerBitrateFormat", MimeTypes.AUDIO_AAC, null, 15000, Format.NO_VALUE, 2, 44100, null, null, 0, null); @@ -1068,10 +1071,17 @@ public final class DefaultTrackSelectorTest { Format.createAudioSampleFormat("higherBitrateFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 44100, null, null, 0, null); + Map mappedCapabilities = new HashMap<>(); + mappedCapabilities.put(exceedsBitrateFormat.id, FORMAT_EXCEEDS_CAPABILITIES); + mappedCapabilities.put(lowerBitrateFormat.id, FORMAT_HANDLED); + mappedCapabilities.put(higherBitrateFormat.id, FORMAT_HANDLED); + RendererCapabilities mappedAudioRendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities); + TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), + new RendererCapabilities[] {mappedAudioRendererCapabilities}, + singleTrackGroup(exceedsBitrateFormat, lowerBitrateFormat, higherBitrateFormat), periodId, TIMELINE); @@ -1094,11 +1104,21 @@ public final class DefaultTrackSelectorTest { Format higherBitrateFormat = Format.createAudioSampleFormat("higherBitrateFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 44100, null, null, 0, null); + Format exceedsBitrateFormat = + Format.createAudioSampleFormat("exceedsBitrateFormat", MimeTypes.AUDIO_AAC, null, 45000, + Format.NO_VALUE, 2, 44100, null, null, 0, null); + + Map mappedCapabilities = new HashMap<>(); + mappedCapabilities.put(lowerBitrateFormat.id, FORMAT_HANDLED); + mappedCapabilities.put(higherBitrateFormat.id, FORMAT_HANDLED); + mappedCapabilities.put(exceedsBitrateFormat.id, FORMAT_EXCEEDS_CAPABILITIES); + RendererCapabilities mappedAudioRendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), + new RendererCapabilities[] {mappedAudioRendererCapabilities}, + singleTrackGroup(lowerBitrateFormat, higherBitrateFormat, exceedsBitrateFormat), periodId, TIMELINE); From ce0b5e4c445b96ca4a62bab722f34a29c844d9e7 Mon Sep 17 00:00:00 2001 From: "Lieblich, Jonathan" Date: Thu, 11 Oct 2018 16:25:21 -0600 Subject: [PATCH 063/832] removed custom element parsing --- .../dash/manifest/DashManifestParser.java | 28 ++------- .../dash/manifest/ProgramInformation.java | 62 +++++-------------- 2 files changed, 21 insertions(+), 69 deletions(-) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index ce629f6509..1bbcab690b 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -982,10 +982,9 @@ public class DashManifestParser extends DefaultHandler } protected ProgramInformation parseProgramInformation(XmlPullParser xpp) throws IOException, XmlPullParserException { - String title = ""; - String source = ""; - String copyright = ""; - List customEvents = new ArrayList<>(); + String title = null; + String source = null; + String copyright = null; do { xpp.next(); if (XmlPullParserUtil.isStartTag(xpp, "Title")) { @@ -994,28 +993,9 @@ public class DashManifestParser extends DefaultHandler source = xpp.getText(); } else if (XmlPullParserUtil.isStartTag(xpp, "Copyright")) { copyright = xpp.getText(); - } else { - byte[] customElement = parseCustomElement(xpp, new ByteArrayOutputStream(512)); - if (customElement.length > 0) { - customEvents.add(customElement); - } } } while (!XmlPullParserUtil.isEndTag(xpp, "ProgramInformation")); - return new ProgramInformation(title, source, copyright, customEvents); - } - - private byte[] parseCustomElement(XmlPullParser xpp, ByteArrayOutputStream outputStream) throws IOException, XmlPullParserException { - XmlSerializer serializer = Xml.newSerializer(); - serializer.setOutput(outputStream, C.UTF8_NAME); - if (xpp.getEventType() == XmlPullParser.START_TAG) { - serializer.startTag(xpp.getNamespace(), xpp.getName()); - for (int i = 0; i < xpp.getAttributeCount(); i++) { - serializer.attribute(xpp.getAttributeNamespace(i), xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - serializer.endTag(xpp.getNamespace(), xpp.getName()); - } - serializer.flush(); - return outputStream.toByteArray(); + return new ProgramInformation(title, source, copyright); } // AudioChannelConfiguration parsing. diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java index 7d162336bc..faf938e3df 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java @@ -1,8 +1,20 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.google.android.exoplayer2.source.dash.manifest; -import java.util.Arrays; -import java.util.List; - public class ProgramInformation { /** * The title for the media presentation. @@ -19,49 +31,9 @@ public class ProgramInformation { */ public final String copyright; - /** - * A list of custom elements. - */ - public final List customEvents; - - public ProgramInformation(String title, String source, String copyright, List customEvents) { + public ProgramInformation(String title, String source, String copyright) { this.title = title; this.source = source; this.copyright = copyright; - this.customEvents = customEvents; } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + (title != null ? title.hashCode() : 0); - result = 31 * result + (source != null ? source.hashCode() : 0); - result = 31 * result + (copyright != null ? copyright.hashCode() : 0); - for (int i = 0; i < customEvents.size(); i++) { - result = 31 * result + Arrays.hashCode(customEvents.get(i)); - } - return result; - } - - @Override - public boolean equals(Object obj) { - return obj instanceof ProgramInformation - && ((ProgramInformation)obj).title.equals(this.title) - && ((ProgramInformation)obj).source.equals(this.source) - && ((ProgramInformation)obj).copyright.equals(this.copyright) - && validateEvents(((ProgramInformation)obj).customEvents); - } - - private boolean validateEvents(List customEvents) { - for (int i = 0; i < customEvents.size() && i < this.customEvents.size(); i++) { - byte[] comparator = customEvents.get(i); - byte[] current = this.customEvents.get(i); - for (int j = 0; j < comparator.length && j < current.length; j++) { - if (current[j] != comparator[j]) { - return false; - } - } - } - return true; - } -} \ No newline at end of file +} From 6cbac152c9da36cddec56d6722666abd1721ba73 Mon Sep 17 00:00:00 2001 From: "Lieblich, Jonathan" Date: Thu, 11 Oct 2018 17:09:00 -0600 Subject: [PATCH 064/832] added attribute parsin --- .../dash/manifest/DashManifestParser.java | 10 +++-- .../dash/manifest/ProgramInformation.java | 40 ++++++++++++++++++- library/dash/src/test/assets/sample_mpd_1 | 7 +++- .../dash/manifest/DashManifestParserTest.java | 5 +-- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 1bbcab690b..9938f8564c 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -985,17 +985,19 @@ public class DashManifestParser extends DefaultHandler String title = null; String source = null; String copyright = null; + String moreInformationURL = parseString(xpp, "moreInformationURL", null); + String lang = parseString(xpp, "lang", null); do { xpp.next(); if (XmlPullParserUtil.isStartTag(xpp, "Title")) { - title = xpp.getText(); + title = xpp.nextText(); } else if (XmlPullParserUtil.isStartTag(xpp, "Source")) { - source = xpp.getText(); + source = xpp.nextText(); } else if (XmlPullParserUtil.isStartTag(xpp, "Copyright")) { - copyright = xpp.getText(); + copyright = xpp.nextText(); } } while (!XmlPullParserUtil.isEndTag(xpp, "ProgramInformation")); - return new ProgramInformation(title, source, copyright); + return new ProgramInformation(title, source, copyright, moreInformationURL, lang); } // AudioChannelConfiguration parsing. diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java index faf938e3df..d3fc909ef8 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.source.dash.manifest; +import com.google.android.exoplayer2.util.Util; + public class ProgramInformation { /** * The title for the media presentation. @@ -31,9 +33,45 @@ public class ProgramInformation { */ public final String copyright; - public ProgramInformation(String title, String source, String copyright) { + /** + * A URL that provides more information about the media presentation. + */ + public final String moreInformationURL; + + /** + * Declares the language code(s) for this ProgramInformation. + */ + public final String lang; + + public ProgramInformation(String title, String source, String copyright, String moreInformationURL, String lang) { this.title = title; this.source = source; this.copyright = copyright; + this.moreInformationURL = moreInformationURL; + this.lang = lang; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ProgramInformation)) { + return false; + } + ProgramInformation other = (ProgramInformation) obj; + return Util.areEqual(this.title, other.title) + && Util.areEqual(this.source, other.source) + && Util.areEqual(this.copyright, other.copyright) + && Util.areEqual(this.moreInformationURL, other.moreInformationURL) + && Util.areEqual(this.lang, other.lang); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (title != null ? title.hashCode() : 0); + result = 31 * result + (source != null ? source.hashCode() : 0); + result = 31 * result + (copyright != null ? copyright.hashCode() : 0); + result = 31 * result + (moreInformationURL != null ? moreInformationURL.hashCode() : 0); + result = 31 * result + (lang != null ? lang.hashCode() : 0); + return result; } } diff --git a/library/dash/src/test/assets/sample_mpd_1 b/library/dash/src/test/assets/sample_mpd_1 index e75c1234c0..ccd3ab4dd6 100644 --- a/library/dash/src/test/assets/sample_mpd_1 +++ b/library/dash/src/test/assets/sample_mpd_1 @@ -9,8 +9,11 @@ xmlns="urn:mpeg:DASH:schema:MPD:2011" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" yt:earliestMediaSequence="1266404" > - - + + MediaTitle + MediaSource + MediaCopyright list = new ArrayList<>(); - list.add("".getBytes()); - ProgramInformation programInformation = new ProgramInformation("", "", "", list); + ProgramInformation programInformation = new ProgramInformation("MediaTitle", "MediaSource", + "MediaCopyright", "www.example.com", "enUs"); assertThat(programInformation).isEqualTo(mpd.programInformation); } From ced302fd83c24258ee43932c68fa3245ea6ac789 Mon Sep 17 00:00:00 2001 From: "Lieblich, Jonathan" Date: Fri, 12 Oct 2018 11:00:17 -0600 Subject: [PATCH 065/832] added javadoc for ProgramInformation --- .../exoplayer2/source/dash/manifest/ProgramInformation.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java index d3fc909ef8..71b4af3a21 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java @@ -17,6 +17,9 @@ package com.google.android.exoplayer2.source.dash.manifest; import com.google.android.exoplayer2.util.Util; +/** + * A parsed ProgramInformation element. + */ public class ProgramInformation { /** * The title for the media presentation. From 9ca6544311c30a12b7f77dbea034a3fd2c922fbf Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 02:06:32 -0700 Subject: [PATCH 066/832] Simplify some buffered position related code. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215704344 --- .../exoplayer2/ExoPlayerImplInternal.java | 27 ++++++++++++------- .../android/exoplayer2/MediaPeriodHolder.java | 13 +++------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 6810c86558..8b22999679 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -496,10 +496,8 @@ import java.util.Collections; // Update the buffered position and total buffered duration. MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod(); - playbackInfo.bufferedPositionUs = - loadingPeriod.getBufferedPositionUs(/* convertEosToDuration= */ true); - playbackInfo.totalBufferedDurationUs = - playbackInfo.bufferedPositionUs - loadingPeriod.toPeriodTime(rendererPositionUs); + playbackInfo.bufferedPositionUs = loadingPeriod.getBufferedPositionUs(); + playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs(); } private void doSomeWork() throws ExoPlaybackException, IOException { @@ -1097,12 +1095,10 @@ import java.util.Collections; } // Renderers are ready and we're loading. Ask the LoadControl whether to transition. MediaPeriodHolder loadingHolder = queue.getLoadingPeriod(); - long bufferedPositionUs = loadingHolder.getBufferedPositionUs(!loadingHolder.info.isFinal); - return bufferedPositionUs == C.TIME_END_OF_SOURCE + boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal; + return bufferedToEnd || loadControl.shouldStartPlayback( - bufferedPositionUs - loadingHolder.toPeriodTime(rendererPositionUs), - mediaClock.getPlaybackParameters().speed, - rebuffering); + getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering); } private boolean isTimelineReady() { @@ -1574,7 +1570,7 @@ import java.util.Collections; return; } long bufferedDurationUs = - nextLoadPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs); + getTotalBufferedDurationUs(/* bufferedPositionInLoadingPeriodUs= */ nextLoadPositionUs); boolean continueLoading = loadControl.shouldContinueLoading( bufferedDurationUs, mediaClock.getPlaybackParameters().speed); @@ -1680,6 +1676,17 @@ import java.util.Collections; } } + private long getTotalBufferedDurationUs() { + return getTotalBufferedDurationUs(playbackInfo.bufferedPositionUs); + } + + private long getTotalBufferedDurationUs(long bufferedPositionInLoadingPeriodUs) { + MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod(); + return loadingPeriodHolder == null + ? 0 + : bufferedPositionInLoadingPeriodUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs); + } + private void updateLoadControlTrackSelection( TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index 3ba96d2b80..4fc21475d9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -133,23 +133,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } /** - * Returns the buffered position in microseconds. If the period is buffered to the end then - * {@link C#TIME_END_OF_SOURCE} is returned unless {@code convertEosToDuration} is true, in which - * case the period duration is returned. + * Returns the buffered position in microseconds. If the period is buffered to the end, then the + * period duration is returned. * - * @param convertEosToDuration Whether to return the period duration rather than - * {@link C#TIME_END_OF_SOURCE} if the period is fully buffered. * @return The buffered position in microseconds. */ - public long getBufferedPositionUs(boolean convertEosToDuration) { + public long getBufferedPositionUs() { if (!prepared) { return info.startPositionUs; } long bufferedPositionUs = hasEnabledTracks ? mediaPeriod.getBufferedPositionUs() : C.TIME_END_OF_SOURCE; - return bufferedPositionUs == C.TIME_END_OF_SOURCE && convertEosToDuration - ? info.durationUs - : bufferedPositionUs; + return bufferedPositionUs == C.TIME_END_OF_SOURCE ? info.durationUs : bufferedPositionUs; } /** From 711f9f8b6ada665a5d2f9cd29724f3b718304d9d Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 02:07:17 -0700 Subject: [PATCH 067/832] Add test action to wait for an isLoading change. This allows to wait until loading started or finished. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215704424 --- .../android/exoplayer2/testutil/Action.java | 50 +++++++++++++++++++ .../exoplayer2/testutil/ActionSchedule.java | 11 ++++ 2 files changed, 61 insertions(+) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java index 14d62e85c8..c988c0c172 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java @@ -686,6 +686,56 @@ public abstract class Action { } } + /** + * Waits for a specified loading state, returning either immediately or after a call to {@link + * Player.EventListener#onLoadingChanged(boolean)}. + */ + public static final class WaitForIsLoading extends Action { + + private final boolean targetIsLoading; + + /** + * @param tag A tag to use for logging. + * @param targetIsLoading The loading state to wait for. + */ + public WaitForIsLoading(String tag, boolean targetIsLoading) { + super(tag, "WaitForIsLoading"); + this.targetIsLoading = targetIsLoading; + } + + @Override + protected void doActionAndScheduleNextImpl( + final SimpleExoPlayer player, + final DefaultTrackSelector trackSelector, + final Surface surface, + final HandlerWrapper handler, + final ActionNode nextAction) { + if (nextAction == null) { + return; + } + if (targetIsLoading == player.isLoading()) { + nextAction.schedule(player, trackSelector, surface, handler); + } else { + player.addListener( + new Player.EventListener() { + @Override + public void onLoadingChanged(boolean isLoading) { + if (targetIsLoading == isLoading) { + player.removeListener(this); + nextAction.schedule(player, trackSelector, surface, handler); + } + } + }); + } + } + + @Override + protected void doActionImpl( + SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) { + // Not triggered. + } + } + /** * Waits for {@link Player.EventListener#onSeekProcessed()}. */ diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index 54d97fb905..71f5fdeae1 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -41,6 +41,7 @@ import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled; import com.google.android.exoplayer2.testutil.Action.SetVideoSurface; import com.google.android.exoplayer2.testutil.Action.Stop; import com.google.android.exoplayer2.testutil.Action.ThrowPlaybackException; +import com.google.android.exoplayer2.testutil.Action.WaitForIsLoading; import com.google.android.exoplayer2.testutil.Action.WaitForPlaybackState; import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity; import com.google.android.exoplayer2.testutil.Action.WaitForSeekProcessed; @@ -414,6 +415,16 @@ public final class ActionSchedule { return apply(new WaitForPlaybackState(tag, targetPlaybackState)); } + /** + * Schedules a delay until {@code player.isLoading()} changes to the specified value. + * + * @param targetIsLoading The target value of {@code player.isLoading()}. + * @return The builder, for convenience. + */ + public Builder waitForIsLoading(boolean targetIsLoading) { + return apply(new WaitForIsLoading(tag, targetIsLoading)); + } + /** * Schedules a {@link Runnable} to be executed. * From d4b87cdc2338e79f6d020d076da82491566fdfff Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 03:32:54 -0700 Subject: [PATCH 068/832] Fix updates of loading period and buffered positions in PlaybackInfo. This makes the following changes to improve consistency among the PlaybackInfo values: 1. Update buffered position and total buffered duration after loading period is set as both values are affected by a loading period update. 2. Add copyWithPosition to allow updating the position without resetting the loading period. 3. Forward the total buffered duration to playing position updates as it may have changed with the new playing position. Issue:#4899 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215712328 --- RELEASENOTES.md | 3 + .../android/exoplayer2/ExoPlayerImpl.java | 2 +- .../exoplayer2/ExoPlayerImplInternal.java | 55 +++++++--- .../android/exoplayer2/PlaybackInfo.java | 102 +++++++++++++++++- .../android/exoplayer2/ExoPlayerTest.java | 53 +++++++++ 5 files changed, 198 insertions(+), 17 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0510055942..e2f33d9150 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -19,6 +19,9 @@ ([#4788](https://github.com/google/ExoPlayer/issues/4788)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). +* Fix issue where buffered position is not updated correctly when transitioning + between periods + ([#4899](https://github.com/google/ExoPlayer/issues/4899)). ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 5fb6202d2f..e1f942147d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -619,7 +619,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (playbackInfo.startPositionUs == C.TIME_UNSET) { // Replace internal unset start position with externally visible start position of zero. playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( playbackInfo.periodId, /* startPositionUs= */ 0, playbackInfo.contentPositionUs); } if ((!this.playbackInfo.timeline.isEmpty() || hasPendingPrepare) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 8b22999679..40716c14c0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -448,7 +448,11 @@ import java.util.Collections; seekToPeriodPosition(periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true); if (newPositionUs != playbackInfo.positionUs) { playbackInfo = - playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs); + playbackInfo.copyWithNewPosition( + periodId, + newPositionUs, + playbackInfo.contentPositionUs, + getTotalBufferedDurationUs()); if (sendDiscontinuity) { playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); } @@ -483,8 +487,12 @@ import java.util.Collections; // A MediaPeriod may report a discontinuity at the current playback position to ensure the // renderers are flushed. Only report the discontinuity externally if the position changed. if (periodPositionUs != playbackInfo.positionUs) { - playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs, - playbackInfo.contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + playbackInfo.periodId, + periodPositionUs, + playbackInfo.contentPositionUs, + getTotalBufferedDurationUs()); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); } } else { @@ -645,7 +653,9 @@ import java.util.Collections; periodPositionUs = newPeriodPositionUs; } } finally { - playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + periodId, periodPositionUs, contentPositionUs, getTotalBufferedDurationUs()); if (seekPositionAdjusted) { playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT); } @@ -1020,8 +1030,12 @@ import java.util.Collections; newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags); if (playbackInfo.playbackState != Player.STATE_ENDED && periodPositionUs != playbackInfo.positionUs) { - playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs, - playbackInfo.contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + playbackInfo.periodId, + periodPositionUs, + playbackInfo.contentPositionUs, + getTotalBufferedDurationUs()); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); resetRendererPosition(periodPositionUs); } @@ -1163,7 +1177,7 @@ import java.util.Collections; resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true); } catch (IllegalSeekPositionException e) { playbackInfo = - playbackInfo.fromNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET); + playbackInfo.resetToNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET); throw e; } pendingInitialSeekPosition = null; @@ -1176,7 +1190,7 @@ import java.util.Collections; long positionUs = periodPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, positionUs); playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs); } } else if (playbackInfo.startPositionUs == C.TIME_UNSET) { @@ -1190,7 +1204,7 @@ import java.util.Collections; long startPositionUs = defaultPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs); playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( periodId, periodId.isAd() ? 0 : startPositionUs, /* contentPositionUs= */ startPositionUs); @@ -1209,7 +1223,7 @@ import java.util.Collections; long startPositionUs = defaultPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs); playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( periodId, /* startPositionUs= */ periodId.isAd() ? 0 : startPositionUs, /* contentPositionUs= */ startPositionUs); @@ -1248,7 +1262,9 @@ import java.util.Collections; } // Actually do the seek. long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs); - playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs()); return; } @@ -1260,7 +1276,9 @@ import java.util.Collections; // The previously playing ad should no longer be played, so skip it. long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs); - playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs()); return; } } @@ -1416,8 +1434,12 @@ import java.util.Collections; MediaPeriodHolder oldPlayingPeriodHolder = playingPeriodHolder; playingPeriodHolder = queue.advancePlayingPeriod(); updatePlayingPeriodRenderers(oldPlayingPeriodHolder); - playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id, - playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + playingPeriodHolder.info.id, + playingPeriodHolder.info.startPositionUs, + playingPeriodHolder.info.contentPositionUs, + getTotalBufferedDurationUs()); playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason); updatePlaybackPositions(); advancedPlayingPeriod = true; @@ -1667,6 +1689,11 @@ import java.util.Collections; if (loadingMediaPeriodChanged) { playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId); } + playbackInfo.bufferedPositionUs = + loadingMediaPeriodHolder == null + ? playbackInfo.positionUs + : loadingMediaPeriodHolder.getBufferedPositionUs(); + playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs(); if ((loadingMediaPeriodChanged || loadingTrackSelectionChanged) && loadingMediaPeriodHolder != null && loadingMediaPeriodHolder.prepared) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 02058c0484..8c73fde3be 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2; +import android.support.annotation.CheckResult; import android.support.annotation.Nullable; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -40,7 +41,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; public final MediaPeriodId periodId; /** * The start position at which playback started in {@link #periodId} relative to the start of the - * associated period in the {@link #timeline}, in microseconds. + * associated period in the {@link #timeline}, in microseconds. Note that this value changes for + * each position discontinuity. */ public final long startPositionUs; /** @@ -103,6 +105,23 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; startPositionUs); } + /** + * Create playback info. + * + * @param timeline See {@link #timeline}. + * @param manifest See {@link #manifest}. + * @param periodId See {@link #periodId}. + * @param startPositionUs See {@link #startPositionUs}. + * @param contentPositionUs See {@link #contentPositionUs}. + * @param playbackState See {@link #playbackState}. + * @param isLoading See {@link #isLoading}. + * @param trackGroups See {@link #trackGroups}. + * @param trackSelectorResult See {@link #trackSelectorResult}. + * @param loadingMediaPeriodId See {@link #loadingMediaPeriodId}. + * @param bufferedPositionUs See {@link #bufferedPositionUs}. + * @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}. + * @param positionUs See {@link #positionUs}. + */ public PlaybackInfo( Timeline timeline, @Nullable Object manifest, @@ -132,7 +151,17 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; this.positionUs = positionUs; } - public PlaybackInfo fromNewPosition( + /** + * Copies playback info and resets playing and loading position. + * + * @param periodId New playing and loading {@link MediaPeriodId}. + * @param startPositionUs New start position. See {@link #startPositionUs}. + * @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored + * if {@code periodId.isAd()} is true. + * @return Copied playback info with reset position. + */ + @CheckResult + public PlaybackInfo resetToNewPosition( MediaPeriodId periodId, long startPositionUs, long contentPositionUs) { return new PlaybackInfo( timeline, @@ -150,6 +179,46 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; startPositionUs); } + /** + * Copied playback info with new playing position. + * + * @param periodId New playing media period. See {@link #periodId}. + * @param positionUs New position. See {@link #positionUs}. + * @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored + * if {@code periodId.isAd()} is true. + * @param totalBufferedDurationUs New buffered duration. See {@link #totalBufferedDurationUs}. + * @return Copied playback info with new playing position. + */ + @CheckResult + public PlaybackInfo copyWithNewPosition( + MediaPeriodId periodId, + long positionUs, + long contentPositionUs, + long totalBufferedDurationUs) { + return new PlaybackInfo( + timeline, + manifest, + periodId, + positionUs, + periodId.isAd() ? contentPositionUs : C.TIME_UNSET, + playbackState, + isLoading, + trackGroups, + trackSelectorResult, + loadingMediaPeriodId, + bufferedPositionUs, + totalBufferedDurationUs, + positionUs); + } + + /** + * Copies playback info with new timeline and manifest. + * + * @param timeline New timeline. See {@link #timeline}. + * @param manifest New manifest. See {@link #manifest}. + * @return Copied playback info with new timeline and manifest. + */ + @CheckResult public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) { return new PlaybackInfo( timeline, @@ -167,6 +236,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new playback state. + * + * @param playbackState New playback state. See {@link #playbackState}. + * @return Copied playback info with new playback state. + */ + @CheckResult public PlaybackInfo copyWithPlaybackState(int playbackState) { return new PlaybackInfo( timeline, @@ -184,6 +260,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new loading state. + * + * @param isLoading New loading state. See {@link #isLoading}. + * @return Copied playback info with new loading state. + */ + @CheckResult public PlaybackInfo copyWithIsLoading(boolean isLoading) { return new PlaybackInfo( timeline, @@ -201,6 +284,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new track information. + * + * @param trackGroups New track groups. See {@link #trackGroups}. + * @param trackSelectorResult New track selector result. See {@link #trackSelectorResult}. + * @return Copied playback info with new track information. + */ + @CheckResult public PlaybackInfo copyWithTrackInfo( TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { return new PlaybackInfo( @@ -219,6 +310,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new loading media period. + * + * @param loadingMediaPeriodId New loading media period id. See {@link #loadingMediaPeriodId}. + * @return Copied playback info with new loading media period. + */ + @CheckResult public PlaybackInfo copyWithLoadingMediaPeriodId(MediaPeriodId loadingMediaPeriodId) { return new PlaybackInfo( timeline, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 407e9a3827..d131ed0f51 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -2534,6 +2534,59 @@ public final class ExoPlayerTest { assertThat(positionWhenReady.get()).isEqualTo(periodDurationMs + 10); } + @Test + public void periodTransitionReportsCorrectBufferedPosition() throws Exception { + int periodCount = 3; + long periodDurationUs = 5 * C.MICROS_PER_SECOND; + long windowDurationUs = periodCount * periodDurationUs; + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition( + periodCount, + /* id= */ new Object(), + /* isSeekable= */ true, + /* isDynamic= */ false, + windowDurationUs)); + AtomicReference playerReference = new AtomicReference<>(); + AtomicLong bufferedPositionAtFirstDiscontinuityMs = new AtomicLong(C.TIME_UNSET); + EventListener eventListener = + new EventListener() { + @Override + public void onPositionDiscontinuity(@DiscontinuityReason int reason) { + if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) { + if (bufferedPositionAtFirstDiscontinuityMs.get() == C.TIME_UNSET) { + bufferedPositionAtFirstDiscontinuityMs.set( + playerReference.get().getBufferedPosition()); + } + } + } + }; + ActionSchedule actionSchedule = + new ActionSchedule.Builder("periodTransitionReportsCorrectBufferedPosition") + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + playerReference.set(player); + player.addListener(eventListener); + } + }) + .pause() + // Wait until all periods are fully buffered. + .waitForIsLoading(/* targetIsLoading= */ true) + .waitForIsLoading(/* targetIsLoading= */ false) + .play() + .build(); + new Builder() + .setTimeline(timeline) + .setActionSchedule(actionSchedule) + .build(context) + .start() + .blockUntilEnded(TIMEOUT_MS); + + assertThat(bufferedPositionAtFirstDiscontinuityMs.get()).isEqualTo(C.usToMs(windowDurationUs)); + } + // Internal methods. private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { From cc7684d09f0a66de0e501882da668cdd35504752 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 4 Oct 2018 06:56:29 -0700 Subject: [PATCH 069/832] Introduce Renderer.reset - It's a no-op for now - Renderers that want to support retaining resources will move some functionality from their disable() implementations into reset() - ExoPlayerImplInternal will be updated to not always call reset() immediately after disable(), which is what will enable resources to actually be retained. Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215729783 --- .../android/exoplayer2/BaseRenderer.java | 15 +++++++++++++++ .../exoplayer2/ExoPlayerImplInternal.java | 1 + .../android/exoplayer2/NoSampleRenderer.java | 15 +++++++++++++++ .../google/android/exoplayer2/Renderer.java | 18 +++++++++++++++--- .../video/spherical/CameraMotionRenderer.java | 6 +++--- .../audio/SimpleDecoderAudioRendererTest.java | 1 + 6 files changed, 50 insertions(+), 6 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index 51e724bee1..73602d85aa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -153,6 +153,12 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { onDisabled(); } + @Override + public final void reset() { + Assertions.checkState(state == STATE_DISABLED); + onReset(); + } + // RendererCapabilities implementation. @Override @@ -247,6 +253,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { // Do nothing. } + /** + * Called when the renderer is reset. + * + *

    The default implementation is a no-op. + */ + protected void onReset() { + // Do nothing. + } + // Methods to be called by subclasses. /** Returns the formats of the currently enabled stream. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 40716c14c0..891ea48108 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -989,6 +989,7 @@ import java.util.Collections; mediaClock.onRendererDisabled(renderer); ensureStopped(renderer); renderer.disable(); + renderer.reset(); } private void reselectTracksInternal() throws ExoPlaybackException { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java index 45d6537b84..6645850d3b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java @@ -158,6 +158,12 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities onDisabled(); } + @Override + public final void reset() { + Assertions.checkState(state == STATE_DISABLED); + onReset(); + } + @Override public boolean isReady() { return true; @@ -260,6 +266,15 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities // Do nothing. } + /** + * Called when the renderer is reset. + * + *

    The default implementation is a no-op. + */ + protected void onReset() { + // Do nothing. + } + // Methods to be called by subclasses. /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index c6456e5f7f..1d4d587aeb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -44,12 +44,16 @@ public interface Renderer extends PlayerMessage.Target { @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED}) @interface State {} /** - * The renderer is disabled. + * The renderer is disabled. A renderer in this state may hold resources that it requires for + * rendering (e.g. media decoders), for use if it's subsequently enabled. {@link #reset()} can be + * called to force the renderer to release these resources. */ int STATE_DISABLED = 0; /** - * The renderer is enabled but not started. A renderer in this state is not actively rendering - * media, but will typically hold resources that it requires for rendering (e.g. media decoders). + * The renderer is enabled but not started. A renderer in this state may render media at the + * current position (e.g. an initial video frame), but the position will not advance. A renderer + * in this state will typically hold resources that it requires for rendering (e.g. media + * decoders). */ int STATE_ENABLED = 1; /** @@ -279,4 +283,12 @@ public interface Renderer extends PlayerMessage.Target { */ void disable(); + /** + * Forces the renderer to give up any resources (e.g. media decoders) that it may be holding. If + * the renderer is not holding any resources, the call is a no-op. + * + *

    This method may be called when the renderer is in the following states: {@link + * #STATE_DISABLED}. + */ + void reset(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java index 4a8354d17f..663c9fe284 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/CameraMotionRenderer.java @@ -72,12 +72,12 @@ public class CameraMotionRenderer extends BaseRenderer { @Override protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { - reset(); + resetListener(); } @Override protected void onDisabled() { - reset(); + resetListener(); } @Override @@ -124,7 +124,7 @@ public class CameraMotionRenderer extends BaseRenderer { return result; } - private void reset() { + private void resetListener() { lastTimestampUs = 0; if (listener != null) { listener.onCameraMotionReset(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java index 8dc60a15a4..48e71c619c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java @@ -103,6 +103,7 @@ public class SimpleDecoderAudioRendererTest { } verify(mockAudioSink, times(1)).playToEndOfStream(); audioRenderer.disable(); + audioRenderer.reset(); verify(mockAudioSink, times(1)).release(); } From 76a1569e79dbb3edb64ed225c075727252336927 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 07:24:07 -0700 Subject: [PATCH 070/832] Update release notes to better describe Java 8 change. Apps need to set the target compatibility to VERSION_1_8 to enable the automatic desugaring if they haven't done so already. Issue:#4907 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215733070 --- RELEASENOTES.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e2f33d9150..6f04fcd8f1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -25,9 +25,8 @@ ### 2.9.0 ### -* Turn on Java 8 compiler support for the ExoPlayer library. Apps that depend - on ExoPlayer via its source code rather than an AAR may need to add - `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their +* Turn on Java 8 compiler support for the ExoPlayer library. Apps may need to + add `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their gradle settings to ensure bytecode compatibility. * Set `compileSdkVersion` and `targetSdkVersion` to 28. * Support for automatic audio focus handling via From c5f9ad9f8b9594c9abb12b25b9bc088eb3dee76c Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 4 Oct 2018 10:40:29 -0700 Subject: [PATCH 071/832] Minor fixes for period clipping - Always clip to period duration for the last chunk. We previously did this only when the last chunk explicitly exceeded the period end time. We now also do it when the chunk claims to end at the period boundary, but still contains samples that exceed it. - If pendingResetPositionUs == chunk.startTimeUs == 0 but the chunk still contains samples with negative timestamps, we now clip them by setting the decode only flag. Previously we only clipped such samples if the first chunk explicitly preceeded the start of the period. Issue: #4899 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215763467 --- .../android/exoplayer2/source/chunk/ChunkSampleStream.java | 4 ++-- .../exoplayer2/source/dash/DefaultDashChunkSource.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index a993c79b4a..9fac69b281 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -308,7 +308,7 @@ public class ChunkSampleStream implements SampleStream, S // chunk even if the sample timestamps are slightly offset from the chunk start times. seekInsideBuffer = primarySampleQueue.setReadPosition(seekToMediaChunk.getFirstSampleIndex(0)); - decodeOnlyUntilPositionUs = Long.MIN_VALUE; + decodeOnlyUntilPositionUs = 0; } else { seekInsideBuffer = primarySampleQueue.advanceTo( @@ -583,7 +583,7 @@ public class ChunkSampleStream implements SampleStream, S if (pendingReset) { boolean resetToMediaChunk = mediaChunk.startTimeUs == pendingResetPositionUs; // Only enable setting of the decode only flag if we're not resetting to a chunk boundary. - decodeOnlyUntilPositionUs = resetToMediaChunk ? Long.MIN_VALUE : pendingResetPositionUs; + decodeOnlyUntilPositionUs = resetToMediaChunk ? 0 : pendingResetPositionUs; pendingResetPositionUs = C.TIME_UNSET; } mediaChunk.init(mediaChunkOutput); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 37c9e313ae..1ea25ecc36 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -544,7 +544,7 @@ public class DefaultDashChunkSource implements DashChunkSource { long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1); long periodDurationUs = representationHolder.periodDurationUs; long clippedEndTimeUs = - periodDurationUs != C.TIME_UNSET && periodDurationUs < endTimeUs + periodDurationUs != C.TIME_UNSET && periodDurationUs <= endTimeUs ? periodDurationUs : C.TIME_UNSET; DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl), From 8dedbd5748b87f4bda71e83bb57cb60f1808cd9d Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 5 Oct 2018 01:01:51 -0700 Subject: [PATCH 072/832] Fix release notes Markdown syntax ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215868785 --- RELEASENOTES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6f04fcd8f1..6433788846 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -348,18 +348,18 @@ begins, and poll the audio timestamp less frequently once it starts advancing ([#3841](https://github.com/google/ExoPlayer/issues/3841)). * Add an option to skip silent audio in `PlaybackParameters` - ((#2635)[https://github.com/google/ExoPlayer/issues/2635]). + ([#2635](https://github.com/google/ExoPlayer/issues/2635)). * Fix an issue where playback of TrueHD streams would get stuck after seeking due to not finding a syncframe - ((#3845)[https://github.com/google/ExoPlayer/issues/3845]). + ([#3845](https://github.com/google/ExoPlayer/issues/3845)). * Fix an issue with eac3-joc playback where a codec would fail to configure - ((#4165)[https://github.com/google/ExoPlayer/issues/4165]). + ([#4165](https://github.com/google/ExoPlayer/issues/4165)). * Handle non-empty end-of-stream buffers, to fix gapless playback of streams with encoder padding when the decoder returns a non-empty final buffer. * Allow trimming more than one sample when applying an elst audio edit via gapless playback info. * Allow overriding skipping/scaling with custom `AudioProcessor`s - ((#3142)[https://github.com/google/ExoPlayer/issues/3142]). + ([#3142](https://github.com/google/ExoPlayer/issues/3142)). * Caching: * Add release method to the `Cache` interface, and prevent multiple instances of `SimpleCache` using the same folder at the same time. From 0a7745bc03c93709e178c578a4649eb534e8a9e0 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 5 Oct 2018 07:36:40 -0700 Subject: [PATCH 073/832] Fix some random Android Studio warnings. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215904019 --- .../android/exoplayer2/demo/SampleChooserActivity.java | 3 +-- .../google/android/exoplayer2/DefaultRenderersFactory.java | 2 +- .../google/android/exoplayer2/ExoPlayerImplInternal.java | 2 +- .../google/android/exoplayer2/audio/DefaultAudioSink.java | 3 +-- .../java/com/google/android/exoplayer2/drm/DrmInitData.java | 4 ++-- .../android/exoplayer2/extractor/BinarySearchSeeker.java | 3 ++- .../android/exoplayer2/extractor/mp4/Mp4Extractor.java | 2 +- .../android/exoplayer2/source/ConcatenatingMediaSource.java | 2 +- .../exoplayer2/upstream/cache/CachedRegionTracker.java | 4 ++-- .../java/com/google/android/exoplayer2/ui/PlayerView.java | 6 ++++++ 10 files changed, 18 insertions(+), 13 deletions(-) diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java index 6395ea4c24..20e27d8d48 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java @@ -350,8 +350,7 @@ public class SampleChooserActivity extends Activity ? null : new DrmInfo(drmScheme, drmLicenseUrl, drmKeyRequestProperties, drmMultiSession); if (playlistSamples != null) { - UriSample[] playlistSamplesArray = playlistSamples.toArray( - new UriSample[playlistSamples.size()]); + UriSample[] playlistSamplesArray = playlistSamples.toArray(new UriSample[0]); return new PlaylistSample(sampleName, drmInfo, playlistSamplesArray); } else { return new UriSample( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index cc16c43b05..6ccda2b8e9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -187,7 +187,7 @@ public class DefaultRenderersFactory implements RenderersFactory { extensionRendererMode, renderersList); buildCameraMotionRenderers(context, extensionRendererMode, renderersList); buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList); - return renderersList.toArray(new Renderer[renderersList.size()]); + return renderersList.toArray(new Renderer[0]); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 891ea48108..0c6c3ca202 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -1002,7 +1002,7 @@ import java.util.Collections; MediaPeriodHolder periodHolder = queue.getPlayingPeriod(); MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); boolean selectionsChangedForReadPeriod = true; - TrackSelectorResult newTrackSelectorResult = null; + TrackSelectorResult newTrackSelectorResult; while (true) { if (periodHolder == null || !periodHolder.prepared) { // The reselection did not change any prepared periods. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 4ce34ad41a..6ad56a78d9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -351,8 +351,7 @@ public final class DefaultAudioSink implements AudioSink { channelMappingAudioProcessor, trimmingAudioProcessor); Collections.addAll(toIntPcmAudioProcessors, audioProcessorChain.getAudioProcessors()); - toIntPcmAvailableAudioProcessors = - toIntPcmAudioProcessors.toArray(new AudioProcessor[toIntPcmAudioProcessors.size()]); + toIntPcmAvailableAudioProcessors = toIntPcmAudioProcessors.toArray(new AudioProcessor[0]); toFloatPcmAvailableAudioProcessors = new AudioProcessor[] {new FloatResamplingAudioProcessor()}; volume = 1.0f; startMediaTimeState = START_NOT_SET; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java index ef96c7ae75..ff01cbc2b5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java @@ -97,7 +97,7 @@ public final class DrmInitData implements Comparator, Parcelable { * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ public DrmInitData(List schemeDatas) { - this(null, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()])); + this(null, false, schemeDatas.toArray(new SchemeData[0])); } /** @@ -105,7 +105,7 @@ public final class DrmInitData implements Comparator, Parcelable { * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ public DrmInitData(@Nullable String schemeType, List schemeDatas) { - this(schemeType, false, schemeDatas.toArray(new SchemeData[schemeDatas.size()])); + this(schemeType, false, schemeDatas.toArray(new SchemeData[0])); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java index 3b0b834427..022b5b4c75 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java @@ -81,8 +81,9 @@ public abstract class BinarySearchSeeker { */ public static final class OutputFrameHolder { + public final ByteBuffer byteBuffer; + public long timeUs; - public ByteBuffer byteBuffer; /** Constructs an instance, wrapping the given byte buffer. */ public OutputFrameHolder(ByteBuffer outputByteBuffer) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 17c82c2c5b..ec24bed964 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -423,7 +423,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { } this.firstVideoTrackIndex = firstVideoTrackIndex; this.durationUs = durationUs; - this.tracks = tracks.toArray(new Mp4Track[tracks.size()]); + this.tracks = tracks.toArray(new Mp4Track[0]); accumulatedSampleSizes = calculateAccumulatedSampleSizes(this.tracks); extractorOutput.endTracks(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 7418e84449..fb99eef6e7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -801,6 +801,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource activeMediaPeriods; public DeferredTimeline timeline; public int childIndex; @@ -809,7 +810,6 @@ public class ConcatenatingMediaSource extends CompositeMediaSource activeMediaPeriods; public MediaSourceHolder(MediaSource mediaSource) { this.mediaSource = mediaSource; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java index 6d090d073e..e8315385e0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.upstream.cache; import android.support.annotation.NonNull; import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.Iterator; import java.util.NavigableSet; @@ -195,8 +196,7 @@ public final class CachedRegionTracker implements Cache.Listener { @Override public int compareTo(@NonNull Region another) { - return startOffset < another.startOffset ? -1 - : startOffset == another.startOffset ? 0 : 1; + return Util.compareLong(startOffset, another.startOffset); } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java index e429f5bfa0..7e693111ab 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java @@ -1032,6 +1032,12 @@ public class PlayerView extends FrameLayout { if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) { return false; } + return performClick(); + } + + @Override + public boolean performClick() { + super.performClick(); return toggleControllerVisibility(); } From 048c8bd68f437ab384a573c4b5b1ba481393ecda Mon Sep 17 00:00:00 2001 From: vsethia Date: Wed, 10 Oct 2018 19:39:52 -0700 Subject: [PATCH 074/832] Snapshots are going to become the default soon and these tests are failing when reloading frmo snapshots. b/117347850. This is a no-op change as the current default == _fullboot. When we globally flip and make snapshots the default, these tests would fail and we are moving them over so that when we flip your tests still pass. https://groups.google.com[]forum/#!topic/mobile-ninjas/Y69PTAT5GyY ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216634430 --- library/ui/src/main/res/values-mr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui/src/main/res/values-mr/strings.xml b/library/ui/src/main/res/values-mr/strings.xml index 753e6ba8d8..45bac7c61f 100644 --- a/library/ui/src/main/res/values-mr/strings.xml +++ b/library/ui/src/main/res/values-mr/strings.xml @@ -11,7 +11,7 @@ एक रीपीट करा सर्व रीपीट करा शफल करा - पूर्ण स्क्रीन मोड + फुल स्क्रीन मोड डाउनलोड करा डाउनलोड डाउनलोड होत आहे From 81a9293072faabdd95f241f84b29e32217982ac9 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 11 Oct 2018 04:27:22 -0700 Subject: [PATCH 075/832] Project start position for preroll ad to content transitions ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216675981 --- RELEASENOTES.md | 3 ++ .../android/exoplayer2/MediaPeriodQueue.java | 41 +++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6433788846..90ba31c623 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -22,6 +22,9 @@ * Fix issue where buffered position is not updated correctly when transitioning between periods ([#4899](https://github.com/google/ExoPlayer/issues/4899)). +* IMA extension: + * For preroll to live stream transitions, project forward the loading position + to avoid being behind the live window. ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 6370299334..40bc658953 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -533,6 +533,11 @@ import com.google.android.exoplayer2.util.Assertions; // until the timeline is updated. Store whether the next timeline period is ready when the // timeline is updated, to avoid repeatedly checking the same timeline. MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info; + // The expected delay until playback transitions to the new period is equal the duration of + // media that's currently buffered (assuming no interruptions). This is used to project forward + // the start position for transitions to new windows. + long bufferedDurationUs = + mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs; if (mediaPeriodInfo.isLastInTimelinePeriod) { int currentPeriodIndex = timeline.getIndexOfPeriod(mediaPeriodInfo.id.periodUid); int nextPeriodIndex = @@ -550,19 +555,15 @@ import com.google.android.exoplayer2.util.Assertions; long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber; if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) { // We're starting to buffer a new window. When playback transitions to this window we'll - // want it to be from its default start position. The expected delay until playback - // transitions is equal the duration of media that's currently buffered (assuming no - // interruptions). Hence we project the default start position forward by the duration of - // the buffer, and start buffering from this point. - long defaultPositionProjectionUs = - mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs; + // want it to be from its default start position, so project the default start position + // forward by the duration of the buffer, and start buffering from this point. Pair defaultPosition = timeline.getPeriodPosition( window, period, nextWindowIndex, - C.TIME_UNSET, - Math.max(0, defaultPositionProjectionUs)); + /* windowPositionUs= */ C.TIME_UNSET, + /* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs)); if (defaultPosition == null) { return null; } @@ -603,11 +604,27 @@ import com.google.android.exoplayer2.util.Assertions; mediaPeriodInfo.contentPositionUs, currentPeriodId.windowSequenceNumber); } else { - // Play content from the ad group position. + // Play content from the ad group position. As a special case, if we're transitioning from a + // preroll ad group to content and there are no other ad groups, project the start position + // forward as if this were a transition to a new window. No attempt is made to handle + // midrolls in live streams, as it's unclear what content position should play after an ad + // (server-side dynamic ad insertion is more appropriate for this use case). + long startPositionUs = mediaPeriodInfo.contentPositionUs; + if (period.getAdGroupCount() == 1 && period.getAdGroupTimeUs(0) == 0) { + Pair defaultPosition = + timeline.getPeriodPosition( + window, + period, + period.windowIndex, + /* windowPositionUs= */ C.TIME_UNSET, + /* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs)); + if (defaultPosition == null) { + return null; + } + startPositionUs = defaultPosition.second; + } return getMediaPeriodInfoForContent( - currentPeriodId.periodUid, - mediaPeriodInfo.contentPositionUs, - currentPeriodId.windowSequenceNumber); + currentPeriodId.periodUid, startPositionUs, currentPeriodId.windowSequenceNumber); } } else if (mediaPeriodInfo.id.endPositionUs != C.TIME_END_OF_SOURCE) { // Play the next ad group if it's available. From 42c3ff393413966bcd3743421511a1c05b016ef1 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 11 Oct 2018 09:00:00 -0700 Subject: [PATCH 076/832] Handle rotation signaled in MKV track name from HTC devices ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216704331 --- .../extractor/mkv/MatroskaExtractor.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 63fee48771..86b750e821 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -157,6 +157,7 @@ public final class MatroskaExtractor implements Extractor { private static final int ID_FLAG_DEFAULT = 0x88; private static final int ID_FLAG_FORCED = 0x55AA; private static final int ID_DEFAULT_DURATION = 0x23E383; + private static final int ID_NAME = 0x536E; private static final int ID_CODEC_ID = 0x86; private static final int ID_CODEC_PRIVATE = 0x63A2; private static final int ID_CODEC_DELAY = 0x56AA; @@ -815,6 +816,9 @@ public final class MatroskaExtractor implements Extractor { throw new ParserException("DocType " + value + " not supported"); } break; + case ID_NAME: + currentTrack.name = value; + break; case ID_CODEC_ID: currentTrack.codecId = value; break; @@ -1463,6 +1467,7 @@ public final class MatroskaExtractor implements Extractor { case ID_MAX_FALL: return TYPE_UNSIGNED_INT; case ID_DOC_TYPE: + case ID_NAME: case ID_CODEC_ID: case ID_LANGUAGE: return TYPE_STRING; @@ -1609,6 +1614,7 @@ public final class MatroskaExtractor implements Extractor { private static final int DEFAULT_MAX_FALL = 200; // nits. // Common elements. + public String name; public String codecId; public int number; public int type; @@ -1833,10 +1839,34 @@ public final class MatroskaExtractor implements Extractor { byte[] hdrStaticInfo = getHdrStaticInfo(); colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo); } - format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, - Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, - Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, - drmInitData); + int rotationDegrees = Format.NO_VALUE; + // Some HTC devices signal rotation in track names. + if ("htc_video_rotA-000".equals(name)) { + rotationDegrees = 0; + } else if ("htc_video_rotA-090".equals(name)) { + rotationDegrees = 90; + } else if ("htc_video_rotA-180".equals(name)) { + rotationDegrees = 180; + } else if ("htc_video_rotA-270".equals(name)) { + rotationDegrees = 270; + } + format = + Format.createVideoSampleFormat( + Integer.toString(trackId), + mimeType, + /* codecs= */ null, + /* bitrate= */ Format.NO_VALUE, + maxInputSize, + width, + height, + /* frameRate= */ Format.NO_VALUE, + initializationData, + rotationDegrees, + pixelWidthHeightRatio, + projectionData, + stereoMode, + colorInfo, + drmInitData); } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { type = C.TRACK_TYPE_TEXT; format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, selectionFlags, From 842f622d29eb1fa3929a0b2e91f5fc425c78313b Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 12 Oct 2018 02:57:28 -0700 Subject: [PATCH 077/832] Allow codec flushing without re-initialization For decoder reuse, we want disable() to flush the decoder. However, if the flush needs to release the decoder for some reason, it seems non-ideal to immediately re-initialize it. Re-initialization can also throw ExoPlaybackException, which we don't want for disabling. This change allows a variant of flush that wont re-initialize the decoder if it has to be released, which will be used from disable(). Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216834862 --- .../mediacodec/MediaCodecRenderer.java | 41 +++++++++++++++---- .../video/MediaCodecVideoRenderer.java | 11 +++-- .../testutil/DebugRenderersFactory.java | 9 ++-- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 87d56fdf3d..8554d84420 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -546,9 +546,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { inputStreamEnded = false; outputStreamEnded = false; - if (codec != null) { - flushCodec(); - } + flushOrReinitCodec(); formatQueue.clear(); } @@ -687,10 +685,36 @@ public abstract class MediaCodecRenderer extends BaseRenderer { decoderCounters.ensureUpdated(); } - protected void flushCodec() throws ExoPlaybackException { + /** + * Flushes the codec. If flushing is not possible, the codec will be released and re-instantiated. + * This method is a no-op if the codec is {@code null}. + * + *

    The implementation of this method calls {@link #flushOrReleaseCodec()}, and {@link + * #maybeInitCodec()} if the codec needs to be re-instantiated. + * + * @throws ExoPlaybackException If an error occurs re-instantiating the codec. + */ + protected final void flushOrReinitCodec() throws ExoPlaybackException { + if (flushOrReleaseCodec()) { + maybeInitCodec(); + } + } + + /** + * Flushes the codec. If flushing is not possible, the codec will be released. This method is a + * no-op if the codec is {@code null}. + * + * @return Whether the codec was released. + */ + protected boolean flushOrReleaseCodec() { + if (codec == null) { + // Nothing to do. + return false; + } codecHotswapDeadlineMs = C.TIME_UNSET; resetInputBuffer(); resetOutputBuffer(); + codecReceivedBuffers = false; waitingForFirstSyncFrame = true; waitingForKeys = false; shouldSkipOutputBuffer = false; @@ -699,28 +723,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer { shouldSkipAdaptationWorkaroundOutputBuffer = false; if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) { releaseCodec(); - maybeInitCodec(); + return true; } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { // We're already waiting to re-initialize the codec. Since we're now flushing, there's no need // to wait any longer. if (codecReinitializationIsRelease) { releaseCodec(); - maybeInitCodec(); + return true; } else { codec.flush(); - codecReceivedBuffers = false; codecReinitializationState = REINITIALIZATION_STATE_NONE; } } else { // We can flush and re-use the existing decoder. codec.flush(); - codecReceivedBuffers = false; } if (codecReconfigured && format != null) { // Any reconfiguration data that we send shortly before the flush may be discarded. We // avoid this issue by sending reconfiguration data following every flush. codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; } + return false; } private boolean initCodecWithFallback(MediaCrypto crypto, boolean drmSessionRequiresSecureDecoder) @@ -1496,7 +1519,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { releaseCodec(); maybeInitCodec(); } else { - flushCodec(); + flushOrReinitCodec(); } } else { outputStreamEnded = true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index d217b47785..2b2e628017 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -520,9 +520,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @CallSuper @Override - protected void flushCodec() throws ExoPlaybackException { - super.flushCodec(); - buffersInCodecCount = 0; + protected boolean flushOrReleaseCodec() { + try { + return super.flushOrReleaseCodec(); + } finally { + buffersInCodecCount = 0; + } } @Override @@ -859,7 +862,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { // We dropped some buffers to catch up, so update the decoder counters and flush the codec, // which releases all pending buffers buffers including the current output buffer. updateDroppedBufferCounters(buffersInCodecCount + droppedSourceBufferCount); - flushCodec(); + flushOrReinitCodec(); return true; } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java index 627b5b72f3..39194d48fe 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DebugRenderersFactory.java @@ -105,9 +105,12 @@ public class DebugRenderersFactory extends DefaultRenderersFactory { } @Override - protected void flushCodec() throws ExoPlaybackException { - super.flushCodec(); - clearTimestamps(); + protected boolean flushOrReleaseCodec() { + try { + return super.flushOrReleaseCodec(); + } finally { + clearTimestamps(); + } } @Override From ba6a1189981cddb4950bb0bcff64ead93555664d Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 12 Oct 2018 06:21:39 -0700 Subject: [PATCH 078/832] Retain decoder instance after the renderer is disabled Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216852679 --- .../exoplayer2/mediacodec/MediaCodecRenderer.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 8554d84420..27b0d89a3f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -558,6 +558,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Override protected void onDisabled() { + if (drmSession != null || pendingDrmSession != null) { + // TODO: Do something better with this case. + onReset(); + } else { + flushOrReleaseCodec(); + } + } + + @Override + protected void onReset() { format = null; availableCodecInfos = null; try { From d511370338415733a1b9a40a23c3e9837dd5ed7d Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Oct 2018 01:03:23 -0700 Subject: [PATCH 079/832] Fix DashManifestParser to properly skip unknown tags Robustness fix to make sure we ignore tags with known names, but which are nested inside of unknown tags. For example we don't want to parse the third period in: ... ... ... ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217101630 --- .../dash/manifest/DashManifestParser.java | 75 +++++++++++++++---- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 153856af8c..31ff7d283e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -155,6 +155,8 @@ public class DashManifestParser extends DefaultHandler : (period.startMs + periodDurationMs); periods.add(period); } + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "MPD")); @@ -221,6 +223,8 @@ public class DashManifestParser extends DefaultHandler segmentBase = parseSegmentList(xpp, null); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { segmentBase = parseSegmentTemplate(xpp, null); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "Period")); @@ -409,22 +413,26 @@ public class DashManifestParser extends DefaultHandler } else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) { String robustnessLevel = xpp.getAttributeValue(null, "robustness_level"); requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW"); - } else if (data == null) { - if (XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh") - && xpp.next() == XmlPullParser.TEXT) { - // The cenc:pssh element is defined in 23001-7:2015. - data = Base64.decode(xpp.getText(), Base64.DEFAULT); - uuid = PsshAtomUtil.parseUuid(data); - if (uuid == null) { - Log.w(TAG, "Skipping malformed cenc:pssh data"); - data = null; - } - } else if (C.PLAYREADY_UUID.equals(uuid) && XmlPullParserUtil.isStartTag(xpp, "mspr:pro") - && xpp.next() == XmlPullParser.TEXT) { - // The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady. - data = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID, - Base64.decode(xpp.getText(), Base64.DEFAULT)); + } else if (data == null + && XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh") + && xpp.next() == XmlPullParser.TEXT) { + // The cenc:pssh element is defined in 23001-7:2015. + data = Base64.decode(xpp.getText(), Base64.DEFAULT); + uuid = PsshAtomUtil.parseUuid(data); + if (uuid == null) { + Log.w(TAG, "Skipping malformed cenc:pssh data"); + data = null; } + } else if (data == null + && C.PLAYREADY_UUID.equals(uuid) + && XmlPullParserUtil.isStartTag(xpp, "mspr:pro") + && xpp.next() == XmlPullParser.TEXT) { + // The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady. + data = + PsshAtomUtil.buildPsshAtom( + C.PLAYREADY_UUID, Base64.decode(xpp.getText(), Base64.DEFAULT)); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); SchemeData schemeData = @@ -462,7 +470,7 @@ public class DashManifestParser extends DefaultHandler */ protected void parseAdaptationSetChild(XmlPullParser xpp) throws XmlPullParserException, IOException { - // pass + maybeSkipTag(xpp); } // Representation parsing. @@ -526,6 +534,8 @@ public class DashManifestParser extends DefaultHandler inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream")); } else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) { supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty")); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "Representation")); @@ -664,6 +674,8 @@ public class DashManifestParser extends DefaultHandler xpp.next(); if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { initialization = parseInitialization(xpp); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase")); @@ -701,6 +713,8 @@ public class DashManifestParser extends DefaultHandler segments = new ArrayList<>(); } segments.add(parseSegmentUrl(xpp)); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList")); @@ -747,6 +761,8 @@ public class DashManifestParser extends DefaultHandler initialization = parseInitialization(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { timeline = parseSegmentTimeline(xpp); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTemplate")); @@ -794,6 +810,8 @@ public class DashManifestParser extends DefaultHandler EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale, scratchOutputStream); eventMessages.add(event); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "EventStream")); @@ -932,6 +950,8 @@ public class DashManifestParser extends DefaultHandler segmentTimeline.add(buildSegmentTimelineElement(elapsedTime, duration)); elapsedTime += duration; } + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTimeline")); return segmentTimeline; @@ -995,6 +1015,29 @@ public class DashManifestParser extends DefaultHandler // Utility methods. + /** + * If the provided {@link XmlPullParser} is currently positioned at the start of a tag, skips + * forward to the end of that tag. + * + * @param xpp The {@link XmlPullParser}. + * @throws XmlPullParserException If an error occurs parsing the stream. + * @throws IOException If an error occurs reading the stream. + */ + public static void maybeSkipTag(XmlPullParser xpp) throws IOException, XmlPullParserException { + if (!XmlPullParserUtil.isStartTag(xpp)) { + return; + } + int depth = 1; + while (depth != 0) { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp)) { + depth++; + } else if (XmlPullParserUtil.isEndTag(xpp)) { + depth--; + } + } + } + /** * Removes unnecessary {@link SchemeData}s with null {@link SchemeData#data}. */ From 6fb8f66ba6e86850d52777bf591941d9ef479c34 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Oct 2018 02:49:05 -0700 Subject: [PATCH 080/832] Remove assertion causing failure on some Samsung devices The assertion was so weak it probably wouldn't detect genuine misuse of the DefaultAllocator API, so it seems fine just to remove it. We don't really know what happens when the player is allowed to continue on the affected devices, but hopefully it either "just works" or fails in a more graceful way. Issue: #4532 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217113060 --- .../exoplayer2/upstream/DefaultAllocator.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java index 06ca83dd93..71e2d8d19f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java @@ -117,19 +117,6 @@ public final class DefaultAllocator implements Allocator { Math.max(availableAllocations.length * 2, availableCount + allocations.length)); } for (Allocation allocation : allocations) { - // Weak sanity check that the allocation probably originated from this pool. - if (allocation.data != initialAllocationBlock - && allocation.data.length != individualAllocationSize) { - throw new IllegalArgumentException( - "Unexpected allocation: " - + System.identityHashCode(allocation.data) - + ", " - + System.identityHashCode(initialAllocationBlock) - + ", " - + allocation.data.length - + ", " - + individualAllocationSize); - } availableAllocations[availableCount++] = allocation; } allocatedCount -= allocations.length; From 0af9ccc99090f87a509965805d343a426f439b16 Mon Sep 17 00:00:00 2001 From: "Lieblich, Jonathan" Date: Mon, 15 Oct 2018 15:37:42 -0600 Subject: [PATCH 081/832] added handling of unexpected tags --- .../exoplayer2/source/dash/manifest/DashManifestParser.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index aacafc5a40..d048e22b33 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -1015,6 +1015,8 @@ public class DashManifestParser extends DefaultHandler source = xpp.nextText(); } else if (XmlPullParserUtil.isStartTag(xpp, "Copyright")) { copyright = xpp.nextText(); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "ProgramInformation")); return new ProgramInformation(title, source, copyright, moreInformationURL, lang); From 6e7b41512f7f7fd2910134a2dbe2621f68a36c84 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Oct 2018 07:28:20 -0700 Subject: [PATCH 082/832] No-op cleanup of DefaultDrmSessionManager ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217139869 --- .../exoplayer2/drm/DefaultDrmSession.java | 30 +++++++++++-------- .../drm/DefaultDrmSessionManager.java | 12 ++++++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java index 25546acb95..514d9da073 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java @@ -209,23 +209,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; @SuppressWarnings("deprecation") public void onMediaDrmEvent(int what) { - if (!isOpen()) { - return; - } switch (what) { + case ExoMediaDrm.EVENT_PROVISION_REQUIRED: + onProvisionRequired(); + break; case ExoMediaDrm.EVENT_KEY_REQUIRED: - doLicense(false); + onKeysRequired(); break; case ExoMediaDrm.EVENT_KEY_EXPIRED: - // When an already expired key is loaded MediaDrm sends this event immediately. Ignore - // this event if the state isn't STATE_OPENED_WITH_KEYS yet which means we're still - // waiting for key response. onKeysExpired(); break; - case ExoMediaDrm.EVENT_PROVISION_REQUIRED: - state = STATE_OPENED; - provisioningManager.provisionRequired(this); - break; default: break; } @@ -441,8 +434,21 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } } + private void onProvisionRequired() { + if (mode == DefaultDrmSessionManager.MODE_PLAYBACK && isOpen()) { + state = STATE_OPENED; + provisioningManager.provisionRequired(this); + } + } + + private void onKeysRequired() { + if (mode == DefaultDrmSessionManager.MODE_PLAYBACK && isOpen()) { + doLicense(/* allowRetry= */ false); + } + } + private void onKeysExpired() { - if (state == STATE_OPENED_WITH_KEYS) { + if (mode == DefaultDrmSessionManager.MODE_PLAYBACK && state == STATE_OPENED_WITH_KEYS) { state = STATE_OPENED; onError(new KeysExpiredException()); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index d71c6fc64c..5f166c6114 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -589,6 +589,10 @@ public class DefaultDrmSessionManager implements DrmSe @Override public void provisionRequired(DefaultDrmSession session) { + if (provisioningSessions.contains(session)) { + // The session has already requested provisioning. + return; + } provisioningSessions.add(session); if (provisioningSessions.size() == 1) { // This is the first session requesting provisioning, so have it perform the operation. @@ -649,6 +653,10 @@ public class DefaultDrmSessionManager implements DrmSe @Override public void handleMessage(Message msg) { byte[] sessionId = (byte[]) msg.obj; + if (sessionId == null) { + // The event is not associated with any particular session. + return; + } for (DefaultDrmSession session : sessions) { if (session.hasSessionId(sessionId)) { session.onMediaDrmEvent(msg.what); @@ -668,9 +676,7 @@ public class DefaultDrmSessionManager implements DrmSe int event, int extra, @Nullable byte[] data) { - if (mode == DefaultDrmSessionManager.MODE_PLAYBACK) { - Assertions.checkNotNull(mediaDrmHandler).obtainMessage(event, sessionId).sendToTarget(); - } + Assertions.checkNotNull(mediaDrmHandler).obtainMessage(event, sessionId).sendToTarget(); } } From dbf5b073a60b2e6224e3d2275981ee915c731abc Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Oct 2018 08:30:28 -0700 Subject: [PATCH 083/832] Network type cleanup 1. Remove option to pass null context to Util.getNetworkType. IMO it's clearer for the caller to just not call the method in this case. 2. Stop using deprecated DefaultBandwidthMeter.Builder constructor in all but one place. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217147091 --- .../core/src/androidTest/AndroidManifest.xml | 2 ++ .../upstream/DefaultBandwidthMeter.java | 4 ++- .../google/android/exoplayer2/util/Util.java | 25 +++++++------------ .../testutil/ExoPlayerTestRunner.java | 2 +- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/library/core/src/androidTest/AndroidManifest.xml b/library/core/src/androidTest/AndroidManifest.xml index d9104b1077..c3c0852b29 100644 --- a/library/core/src/androidTest/AndroidManifest.xml +++ b/library/core/src/androidTest/AndroidManifest.xml @@ -18,6 +18,8 @@ xmlns:tools="http://schemas.android.com/tools" package="com.google.android.exoplayer2.core.test"> + + diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index e9f70ec92a..21e2ed4f65 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -187,7 +187,9 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList * @return A bandwidth meter with the configured properties. */ public DefaultBandwidthMeter build() { - Long initialBitrateEstimate = initialBitrateEstimates.get(Util.getNetworkType(context)); + Long initialBitrateEstimate = + initialBitrateEstimates.get( + context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context)); if (initialBitrateEstimate == null) { initialBitrateEstimate = initialBitrateEstimates.get(C.NETWORK_TYPE_UNKNOWN); } 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 2d3a0aeb78..116ad860e0 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 @@ -1620,31 +1620,24 @@ public final class Util { } /** - * Returns the {@link C.NetworkType} of the current network connection. {@link - * C#NETWORK_TYPE_UNKNOWN} will be returned if the {@code ACCESS_NETWORK_STATE} permission is not - * granted or the network connection type couldn't be determined. + * Returns the {@link C.NetworkType} of the current network connection. * * @param context A context to access the connectivity manager. - * @return The {@link C.NetworkType} of the current network connection, or {@link - * C#NETWORK_TYPE_UNKNOWN} if the {@code ACCESS_NETWORK_STATE} permission is not granted or - * {@code context} is null. + * @return The {@link C.NetworkType} of the current network connection. */ - public static @C.NetworkType int getNetworkType(@Nullable Context context) { + @C.NetworkType + public static int getNetworkType(Context context) { if (context == null) { + // Note: This is for backward compatibility only (context used to be @Nullable). return C.NETWORK_TYPE_UNKNOWN; } NetworkInfo networkInfo; - try { - ConnectivityManager connectivityManager = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivityManager == null) { - return C.NETWORK_TYPE_UNKNOWN; - } - networkInfo = connectivityManager.getActiveNetworkInfo(); - } catch (SecurityException e) { - // Permission ACCESS_NETWORK_STATE not granted. + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivityManager == null) { return C.NETWORK_TYPE_UNKNOWN; } + networkInfo = connectivityManager.getActiveNetworkInfo(); if (networkInfo == null || !networkInfo.isConnected()) { return C.NETWORK_TYPE_OFFLINE; } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java index b613f7f364..401a2fae1b 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java @@ -287,7 +287,7 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc trackSelector = new DefaultTrackSelector(); } if (bandwidthMeter == null) { - bandwidthMeter = new DefaultBandwidthMeter.Builder().build(); + bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build(); } if (renderersFactory == null) { if (renderers == null) { From bfd67992f41a7c5698700322e712544432b23bc2 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Oct 2018 12:37:44 -0700 Subject: [PATCH 084/832] Add ExoPlayer.setForegroundMode Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217189082 --- RELEASENOTES.md | 2 + .../google/android/exoplayer2/ExoPlayer.java | 30 ++++ .../android/exoplayer2/ExoPlayerImpl.java | 9 ++ .../exoplayer2/ExoPlayerImplInternal.java | 132 +++++++++++++++--- .../android/exoplayer2/SimpleExoPlayer.java | 5 + .../exoplayer2/testutil/StubExoPlayer.java | 5 + 6 files changed, 165 insertions(+), 18 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 90ba31c623..01103a7a22 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### dev-v2 (not yet released) ### +* Improve decoder re-use between playbacks. TODO: Write and link a blog post + here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). * Improve initial bandwidth meter estimates using the current country and network type. * Do not retry failed loads whose error is `FileNotFoundException`. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 96ddb2eb9c..5ba2394c3f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -209,4 +209,34 @@ public interface ExoPlayer extends Player { /** Returns the currently active {@link SeekParameters} of the player. */ SeekParameters getSeekParameters(); + + /** + * Sets whether the player is allowed to keep holding limited resources such as video decoders, + * even when in the idle state. By doing so, the player may be able to reduce latency when + * starting to play another piece of content for which the same resources are required. + * + *

    This mode should be used with caution, since holding limited resources may prevent other + * players of media components from acquiring them. It should only be enabled when both + * of the following conditions are true: + * + *

      + *
    • The application that owns the player is in the foreground. + *
    • The player is used in a way that may benefit from foreground mode. For this to be true, + * the same player instance must be used to play multiple pieces of content, and there must + * be gaps between the playbacks (i.e. {@link #stop} is called to halt one playback, and + * {@link #prepare} is called some time later to start a new one). + *
    + * + *

    Note that foreground mode is not useful for switching between content without gaps + * between the playbacks. For this use case {@link #stop} does not need to be called, and simply + * calling {@link #prepare} for the new media will cause limited resources to be retained even if + * foreground mode is not enabled. + * + *

    If foreground mode is enabled, it's the application's responsibility to disable it when the + * conditions described above no longer hold. + * + * @param foregroundMode Whether the player is allowed to keep limited resources even when in the + * idle state. + */ + void setForegroundMode(boolean foregroundMode); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index e1f942147d..130bf48921 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -71,6 +71,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private int pendingOperationAcks; private boolean hasPendingPrepare; private boolean hasPendingSeek; + private boolean foregroundMode; private PlaybackParameters playbackParameters; private SeekParameters seekParameters; private @Nullable ExoPlaybackException playbackError; @@ -359,6 +360,14 @@ import java.util.concurrent.CopyOnWriteArraySet; return seekParameters; } + @Override + public void setForegroundMode(boolean foregroundMode) { + if (this.foregroundMode != foregroundMode) { + this.foregroundMode = foregroundMode; + internalPlayer.setForegroundMode(foregroundMode); + } + } + @Override public void stop(boolean reset) { if (reset) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 0c6c3ca202..3034ead032 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -44,6 +44,7 @@ import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; /** Implements the internal behavior of {@link ExoPlayerImpl}. */ /* package */ final class ExoPlayerImplInternal @@ -76,9 +77,10 @@ import java.util.Collections; private static final int MSG_TRACK_SELECTION_INVALIDATED = 11; private static final int MSG_SET_REPEAT_MODE = 12; private static final int MSG_SET_SHUFFLE_ENABLED = 13; - private static final int MSG_SEND_MESSAGE = 14; - private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 15; - private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 16; + private static final int MSG_SET_FOREGROUND_MODE = 14; + private static final int MSG_SEND_MESSAGE = 15; + private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 16; + private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 17; private static final int PREPARING_SOURCE_INTERVAL_MS = 10; private static final int RENDERING_INTERVAL_MS = 10; @@ -115,6 +117,7 @@ import java.util.Collections; private boolean rebuffering; @Player.RepeatMode private int repeatMode; private boolean shuffleModeEnabled; + private boolean foregroundMode; private int pendingPrepareCount; private SeekPosition pendingInitialSeekPosition; @@ -218,6 +221,29 @@ import java.util.Collections; handler.obtainMessage(MSG_SEND_MESSAGE, message).sendToTarget(); } + public synchronized void setForegroundMode(boolean foregroundMode) { + if (foregroundMode) { + handler.obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 1, 0).sendToTarget(); + } else { + AtomicBoolean processedFlag = new AtomicBoolean(); + handler + .obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 0, 0, processedFlag) + .sendToTarget(); + boolean wasInterrupted = false; + while (!processedFlag.get() && !released) { + try { + wait(); + } catch (InterruptedException e) { + wasInterrupted = true; + } + } + if (wasInterrupted) { + // Restore the interrupted status. + Thread.currentThread().interrupt(); + } + } + } + public synchronized void release() { if (released) { return; @@ -311,8 +337,15 @@ import java.util.Collections; case MSG_SET_SEEK_PARAMETERS: setSeekParametersInternal((SeekParameters) msg.obj); break; + case MSG_SET_FOREGROUND_MODE: + setForegroundModeInternal( + /* foregroundMode= */ msg.arg1 != 0, /* processedFlag= */ (AtomicBoolean) msg.obj); + break; case MSG_STOP: - stopInternal(/* reset= */ msg.arg1 != 0, /* acknowledgeStop= */ true); + stopInternal( + /* forceResetRenderers= */ false, + /* resetPositionAndState= */ msg.arg1 != 0, + /* acknowledgeStop= */ true); break; case MSG_PERIOD_PREPARED: handlePeriodPrepared((MediaPeriod) msg.obj); @@ -345,17 +378,26 @@ import java.util.Collections; maybeNotifyPlaybackInfoChanged(); } catch (ExoPlaybackException e) { Log.e(TAG, "Playback error.", e); - stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); + stopInternal( + /* forceResetRenderers= */ true, + /* resetPositionAndState= */ false, + /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget(); maybeNotifyPlaybackInfoChanged(); } catch (IOException e) { Log.e(TAG, "Source error.", e); - stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); + stopInternal( + /* forceResetRenderers= */ false, + /* resetPositionAndState= */ false, + /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForSource(e)).sendToTarget(); maybeNotifyPlaybackInfoChanged(); } catch (RuntimeException e) { Log.e(TAG, "Internal runtime error.", e); - stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); + stopInternal( + /* forceResetRenderers= */ true, + /* resetPositionAndState= */ false, + /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForUnexpected(e)) .sendToTarget(); maybeNotifyPlaybackInfoChanged(); @@ -394,7 +436,8 @@ import java.util.Collections; private void prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) { pendingPrepareCount++; - resetInternal(/* releaseMediaSource= */ true, resetPosition, resetState); + resetInternal( + /* resetRenderers= */ false, /* releaseMediaSource= */ true, resetPosition, resetState); loadControl.onPrepared(); this.mediaSource = mediaSource; setState(Player.STATE_BUFFERING); @@ -631,7 +674,10 @@ import java.util.Collections; // End playback, as we didn't manage to find a valid seek position. setState(Player.STATE_ENDED); resetInternal( - /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); + /* resetRenderers= */ false, + /* releaseMediaSource= */ false, + /* resetPosition= */ true, + /* resetState= */ false); } else { // Execute the seek in the current media periods. long newPeriodPositionUs = periodPositionUs; @@ -738,9 +784,33 @@ import java.util.Collections; this.seekParameters = seekParameters; } - private void stopInternal(boolean reset, boolean acknowledgeStop) { + private void setForegroundModeInternal( + boolean foregroundMode, @Nullable AtomicBoolean processedFlag) { + if (this.foregroundMode != foregroundMode) { + this.foregroundMode = foregroundMode; + if (!foregroundMode) { + for (Renderer renderer : renderers) { + if (renderer.getState() == Renderer.STATE_DISABLED) { + renderer.reset(); + } + } + } + } + if (processedFlag != null) { + synchronized (this) { + processedFlag.set(true); + notifyAll(); + } + } + } + + private void stopInternal( + boolean forceResetRenderers, boolean resetPositionAndState, boolean acknowledgeStop) { resetInternal( - /* releaseMediaSource= */ true, /* resetPosition= */ reset, /* resetState= */ reset); + /* resetRenderers= */ forceResetRenderers || !foregroundMode, + /* releaseMediaSource= */ true, + /* resetPosition= */ resetPositionAndState, + /* resetState= */ resetPositionAndState); playbackInfoUpdate.incrementPendingOperationAcks( pendingPrepareCount + (acknowledgeStop ? 1 : 0)); pendingPrepareCount = 0; @@ -750,7 +820,10 @@ import java.util.Collections; private void releaseInternal() { resetInternal( - /* releaseMediaSource= */ true, /* resetPosition= */ true, /* resetState= */ true); + /* resetRenderers= */ true, + /* releaseMediaSource= */ true, + /* resetPosition= */ true, + /* resetState= */ true); loadControl.onReleased(); setState(Player.STATE_IDLE); internalPlaybackThread.quit(); @@ -772,7 +845,10 @@ import java.util.Collections; } private void resetInternal( - boolean releaseMediaSource, boolean resetPosition, boolean resetState) { + boolean resetRenderers, + boolean releaseMediaSource, + boolean resetPosition, + boolean resetState) { handler.removeMessages(MSG_DO_SOME_WORK); rebuffering = false; mediaClock.stop(); @@ -782,7 +858,17 @@ import java.util.Collections; disableRenderer(renderer); } catch (ExoPlaybackException | RuntimeException e) { // There's nothing we can do. - Log.e(TAG, "Stop failed.", e); + Log.e(TAG, "Disable failed.", e); + } + } + if (resetRenderers) { + for (Renderer renderer : renderers) { + try { + renderer.reset(); + } catch (RuntimeException e) { + // There's nothing we can do. + Log.e(TAG, "Reset failed.", e); + } } } enabledRenderers = new Renderer[0]; @@ -989,7 +1075,6 @@ import java.util.Collections; mediaClock.onRendererDisabled(renderer); ensureStopped(renderer); renderer.disable(); - renderer.reset(); } private void reselectTracksInternal() throws ExoPlaybackException { @@ -1294,7 +1379,10 @@ import java.util.Collections; setState(Player.STATE_ENDED); // Reset, but retain the source so that it can still be used should a seek occur. resetInternal( - /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); + /* resetRenderers= */ false, + /* releaseMediaSource= */ false, + /* resetPosition= */ true, + /* resetState= */ false); } /** @@ -1639,9 +1727,17 @@ import java.util.Collections; throws ExoPlaybackException { enabledRenderers = new Renderer[totalEnabledRendererCount]; int enabledRendererCount = 0; - MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); + TrackSelectorResult trackSelectorResult = queue.getPlayingPeriod().getTrackSelectorResult(); + // Reset all disabled renderers before enabling any new ones. This makes sure resources released + // by the disabled renderers will be available to renderers that are being enabled. for (int i = 0; i < renderers.length; i++) { - if (playingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)) { + if (!trackSelectorResult.isRendererEnabled(i)) { + renderers[i].reset(); + } + } + // Enable the renderers. + for (int i = 0; i < renderers.length; i++) { + if (trackSelectorResult.isRendererEnabled(i)) { enableRenderer(i, rendererWasEnabledFlags[i], enabledRendererCount++); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 8517556887..4ca6b51ce2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -958,6 +958,11 @@ public class SimpleExoPlayer extends BasePlayer return player.getSeekParameters(); } + @Override + public void setForegroundMode(boolean foregroundMode) { + player.setForegroundMode(foregroundMode); + } + @Override public void stop(boolean reset) { verifyApplicationThread(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 156b573df8..1ac19591c0 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -267,4 +267,9 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { public long getContentBufferedPosition() { throw new UnsupportedOperationException(); } + + @Override + public void setForegroundMode(boolean foregroundMode) { + throw new UnsupportedOperationException(); + } } From ee02c6789a6a404a935fb4f23fdffa7d6bd62d9e Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 15 Oct 2018 19:44:39 -0700 Subject: [PATCH 085/832] Support seeking based on MLLT metadata Issue: #3241 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217252254 --- RELEASENOTES.md | 3 + .../extractor/GaplessInfoHolder.java | 10 -- .../exoplayer2/extractor/mp3/MlltSeeker.java | 121 ++++++++++++++++++ .../extractor/mp3/Mp3Extractor.java | 41 +++++- .../exoplayer2/metadata/id3/Id3Decoder.java | 33 +++++ .../exoplayer2/metadata/id3/MlltFrame.java | 112 ++++++++++++++++ .../metadata/id3/MlltFrameTest.java | 49 +++++++ 7 files changed, 354 insertions(+), 15 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/MlltFrame.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 01103a7a22..060b5f4daf 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -21,6 +21,9 @@ ([#4788](https://github.com/google/ExoPlayer/issues/4788)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). +* Audio: + * Support seeking based on MLLT metadata + ([#3241](https://github.com/google/ExoPlayer/issues/3241)). * Fix issue where buffered position is not updated correctly when transitioning between periods ([#4899](https://github.com/google/ExoPlayer/issues/4899)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java index 0742d96a06..a0effc0df8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.extractor; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.CommentFrame; -import com.google.android.exoplayer2.metadata.id3.Id3Decoder.FramePredicate; import com.google.android.exoplayer2.metadata.id3.InternalFrame; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,15 +27,6 @@ import java.util.regex.Pattern; */ public final class GaplessInfoHolder { - /** - * A {@link FramePredicate} suitable for use when decoding {@link Metadata} that will be passed to - * {@link #setFromMetadata(Metadata)}. Only frames that might contain gapless playback information - * are decoded. - */ - public static final FramePredicate GAPLESS_INFO_ID3_FRAME_PREDICATE = - (majorVersion, id0, id1, id2, id3) -> - id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2); - private static final String GAPLESS_DOMAIN = "com.apple.iTunes"; private static final String GAPLESS_DESCRIPTION = "iTunSMPB"; private static final Pattern GAPLESS_COMMENT_PATTERN = diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java new file mode 100644 index 0000000000..ff607b9482 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor.mp3; + +import android.util.Pair; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.extractor.SeekPoint; +import com.google.android.exoplayer2.metadata.id3.MlltFrame; +import com.google.android.exoplayer2.util.Util; + +/** MP3 seeker that uses metadata from an {@link MlltFrame}. */ +/* package */ final class MlltSeeker implements Mp3Extractor.Seeker { + + /** + * Returns an {@link MlltSeeker} for seeking in the stream. + * + * @param firstFramePosition The position of the start of the first frame in the stream. + * @param mlltFrame The MLLT frame with seeking metadata. + * @return An {@link MlltSeeker} for seeking in the stream. + */ + public static MlltSeeker create(long firstFramePosition, MlltFrame mlltFrame) { + int referenceCount = mlltFrame.bytesDeviations.length; + long[] referencePositions = new long[1 + referenceCount]; + long[] referenceTimesMs = new long[1 + referenceCount]; + referencePositions[0] = firstFramePosition; + referenceTimesMs[0] = 0; + long position = firstFramePosition; + long timeMs = 0; + for (int i = 1; i <= referenceCount; i++) { + position += mlltFrame.bytesBetweenReference + mlltFrame.bytesDeviations[i - 1]; + timeMs += mlltFrame.millisecondsBetweenReference + mlltFrame.millisecondsDeviations[i - 1]; + referencePositions[i] = position; + referenceTimesMs[i] = timeMs; + } + return new MlltSeeker(referencePositions, referenceTimesMs); + } + + private final long[] referencePositions; + private final long[] referenceTimesMs; + private final long durationUs; + + private MlltSeeker(long[] referencePositions, long[] referenceTimesMs) { + this.referencePositions = referencePositions; + this.referenceTimesMs = referenceTimesMs; + // Use the last reference point as the duration, as extrapolating variable bitrate at the end of + // the stream may give a large error. + durationUs = C.msToUs(referenceTimesMs[referenceTimesMs.length - 1]); + } + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public SeekPoints getSeekPoints(long timeUs) { + timeUs = Util.constrainValue(timeUs, 0, durationUs); + Pair timeMsAndPosition = + linearlyInterpolate(C.usToMs(timeUs), referenceTimesMs, referencePositions); + timeUs = C.msToUs(timeMsAndPosition.first); + long position = timeMsAndPosition.second; + return new SeekPoints(new SeekPoint(timeUs, position)); + } + + @Override + public long getTimeUs(long position) { + Pair positionAndTimeMs = + linearlyInterpolate(position, referencePositions, referenceTimesMs); + return C.msToUs(positionAndTimeMs.second); + } + + @Override + public long getDurationUs() { + return durationUs; + } + + /** + * Given a set of reference points as coordinates in {@code xReferences} and {@code yReferences} + * and an x-axis value, linearly interpolates between corresponding reference points to give a + * y-axis value. + * + * @param x The x-axis value for which a y-axis value is needed. + * @param xReferences x coordinates of reference points. + * @param yReferences y coordinates of reference points. + * @return The linearly interpolated y-axis value. + */ + private static Pair linearlyInterpolate( + long x, long[] xReferences, long[] yReferences) { + int previousReferenceIndex = + Util.binarySearchFloor(xReferences, x, /* inclusive= */ true, /* stayInBounds= */ true); + long xPreviousReference = xReferences[previousReferenceIndex]; + long yPreviousReference = yReferences[previousReferenceIndex]; + int nextReferenceIndex = previousReferenceIndex + 1; + if (nextReferenceIndex == xReferences.length) { + return Pair.create(xPreviousReference, yPreviousReference); + } else { + long xNextReference = xReferences[nextReferenceIndex]; + long yNextReference = yReferences[nextReferenceIndex]; + double proportion = + xNextReference == xPreviousReference + ? 0.0 + : ((double) x - xPreviousReference) / (xNextReference - xPreviousReference); + long y = (long) (proportion * (yNextReference - yPreviousReference)) + yPreviousReference; + return Pair.create(x, y); + } + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 92cf590a49..84f620734b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.extractor.mp3; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; @@ -31,6 +32,8 @@ import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.Id3Decoder; +import com.google.android.exoplayer2.metadata.id3.Id3Decoder.FramePredicate; +import com.google.android.exoplayer2.metadata.id3.MlltFrame; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.EOFException; @@ -68,6 +71,12 @@ public final class Mp3Extractor implements Extractor { */ public static final int FLAG_DISABLE_ID3_METADATA = 2; + /** Predicate that matches ID3 frames containing only required gapless/seeking metadata. */ + private static final FramePredicate REQUIRED_ID3_FRAME_PREDICATE = + (majorVersion, id0, id1, id2, id3) -> + ((id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2)) + || (id0 == 'M' && id1 == 'L' && id2 == 'L' && (id3 == 'T' || majorVersion == 2))); + /** * The maximum number of bytes to search when synchronizing, before giving up. */ @@ -174,7 +183,15 @@ public final class Mp3Extractor implements Extractor { } } if (seeker == null) { - seeker = maybeReadSeekFrame(input); + // Read past any seek frame and set the seeker based on metadata or a seek frame. Metadata + // takes priority as it can provide greater precision. + Seeker seekFrameSeeker = maybeReadSeekFrame(input); + Seeker metadataSeeker = maybeHandleSeekMetadata(metadata, input.getPosition()); + if (metadataSeeker != null) { + seeker = metadataSeeker; + } else if (seekFrameSeeker != null) { + seeker = seekFrameSeeker; + } if (seeker == null || (!seeker.isSeekable() && (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0)) { seeker = getConstantBitrateSeeker(input); @@ -253,11 +270,11 @@ public final class Mp3Extractor implements Extractor { int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES; input.resetPeekPosition(); if (input.getPosition() == 0) { - // We need to parse enough ID3 metadata to retrieve any gapless playback information even - // if ID3 metadata parsing is disabled. - boolean onlyDecodeGaplessInfoFrames = (flags & FLAG_DISABLE_ID3_METADATA) != 0; + // We need to parse enough ID3 metadata to retrieve any gapless/seeking playback information + // even if ID3 metadata parsing is disabled. + boolean parseAllId3Frames = (flags & FLAG_DISABLE_ID3_METADATA) == 0; Id3Decoder.FramePredicate id3FramePredicate = - onlyDecodeGaplessInfoFrames ? GaplessInfoHolder.GAPLESS_INFO_ID3_FRAME_PREDICATE : null; + parseAllId3Frames ? null : REQUIRED_ID3_FRAME_PREDICATE; metadata = id3Peeker.peekId3Data(input, id3FramePredicate); if (metadata != null) { gaplessInfoHolder.setFromMetadata(metadata); @@ -401,6 +418,20 @@ public final class Mp3Extractor implements Extractor { return SEEK_HEADER_UNSET; } + @Nullable + private static MlltSeeker maybeHandleSeekMetadata(Metadata metadata, long firstFramePosition) { + if (metadata != null) { + int length = metadata.length(); + for (int i = 0; i < length; i++) { + Metadata.Entry entry = metadata.get(i); + if (entry instanceof MlltFrame) { + return MlltSeeker.create(firstFramePosition, (MlltFrame) entry); + } + } + } + return null; + } + /** * {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be * used to work out the new sample basis timestamp after seeking and resynchronization. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 0bf9d3b249..63bf30dd11 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataDecoder; import com.google.android.exoplayer2.metadata.MetadataInputBuffer; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.UnsupportedEncodingException; @@ -382,6 +383,8 @@ public final class Id3Decoder implements MetadataDecoder { } else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') { frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, frameHeaderSize, framePredicate); + } else if (frameId0 == 'M' && frameId1 == 'L' && frameId2 == 'L' && frameId3 == 'T') { + frame = decodeMlltFrame(id3Data, frameSize); } else { String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); frame = decodeBinaryFrame(id3Data, frameSize, id); @@ -662,6 +665,36 @@ public final class Id3Decoder implements MetadataDecoder { return new ChapterTocFrame(elementId, isRoot, isOrdered, children, subFrameArray); } + private static MlltFrame decodeMlltFrame(ParsableByteArray id3Data, int frameSize) { + // See ID3v2.4.0 native frames subsection 4.6. + int mpegFramesBetweenReference = id3Data.readUnsignedShort(); + int bytesBetweenReference = id3Data.readUnsignedInt24(); + int millisecondsBetweenReference = id3Data.readUnsignedInt24(); + int bitsForBytesDeviation = id3Data.readUnsignedByte(); + int bitsForMillisecondsDeviation = id3Data.readUnsignedByte(); + + ParsableBitArray references = new ParsableBitArray(); + references.reset(id3Data); + int referencesBits = 8 * (frameSize - 10); + int bitsPerReference = bitsForBytesDeviation + bitsForMillisecondsDeviation; + int referencesCount = referencesBits / bitsPerReference; + int[] bytesDeviations = new int[referencesCount]; + int[] millisecondsDeviations = new int[referencesCount]; + for (int i = 0; i < referencesCount; i++) { + int bytesDeviation = references.readBits(bitsForBytesDeviation); + int millisecondsDeviation = references.readBits(bitsForMillisecondsDeviation); + bytesDeviations[i] = bytesDeviation; + millisecondsDeviations[i] = millisecondsDeviation; + } + + return new MlltFrame( + mpegFramesBetweenReference, + bytesBetweenReference, + millisecondsBetweenReference, + bytesDeviations, + millisecondsDeviations); + } + private static BinaryFrame decodeBinaryFrame(ParsableByteArray id3Data, int frameSize, String id) { byte[] frame = new byte[frameSize]; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/MlltFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/MlltFrame.java new file mode 100644 index 0000000000..06a4dd9d2d --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/MlltFrame.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.support.annotation.Nullable; +import java.util.Arrays; + +/** MPEG location lookup table frame. */ +public final class MlltFrame extends Id3Frame { + + public static final String ID = "MLLT"; + + public final int mpegFramesBetweenReference; + public final int bytesBetweenReference; + public final int millisecondsBetweenReference; + public final int[] bytesDeviations; + public final int[] millisecondsDeviations; + + public MlltFrame( + int mpegFramesBetweenReference, + int bytesBetweenReference, + int millisecondsBetweenReference, + int[] bytesDeviations, + int[] millisecondsDeviations) { + super(ID); + this.mpegFramesBetweenReference = mpegFramesBetweenReference; + this.bytesBetweenReference = bytesBetweenReference; + this.millisecondsBetweenReference = millisecondsBetweenReference; + this.bytesDeviations = bytesDeviations; + this.millisecondsDeviations = millisecondsDeviations; + } + + /* package */ MlltFrame(Parcel in) { + super(ID); + this.mpegFramesBetweenReference = in.readInt(); + this.bytesBetweenReference = in.readInt(); + this.millisecondsBetweenReference = in.readInt(); + this.bytesDeviations = in.createIntArray(); + this.millisecondsDeviations = in.createIntArray(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + MlltFrame other = (MlltFrame) obj; + return mpegFramesBetweenReference == other.mpegFramesBetweenReference + && bytesBetweenReference == other.bytesBetweenReference + && millisecondsBetweenReference == other.millisecondsBetweenReference + && Arrays.equals(bytesDeviations, other.bytesDeviations) + && Arrays.equals(millisecondsDeviations, other.millisecondsDeviations); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + mpegFramesBetweenReference; + result = 31 * result + bytesBetweenReference; + result = 31 * result + millisecondsBetweenReference; + result = 31 * result + Arrays.hashCode(bytesDeviations); + result = 31 * result + Arrays.hashCode(millisecondsDeviations); + return result; + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mpegFramesBetweenReference); + dest.writeInt(bytesBetweenReference); + dest.writeInt(millisecondsBetweenReference); + dest.writeIntArray(bytesDeviations); + dest.writeIntArray(millisecondsDeviations); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public MlltFrame createFromParcel(Parcel in) { + return new MlltFrame(in); + } + + @Override + public MlltFrame[] newArray(int size) { + return new MlltFrame[size]; + } + }; +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java new file mode 100644 index 0000000000..3e6520beca --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.metadata.id3; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Test for {@link MlltFrame}. */ +@RunWith(RobolectricTestRunner.class) +public final class MlltFrameTest { + + @Test + public void testParcelable() { + MlltFrame mlltFrameToParcel = + new MlltFrame( + /* mpegFramesBetweenReference= */ 1, + /* bytesBetweenReference= */ 1, + /* millisecondsBetweenReference= */ 1, + /* bytesDeviations= */ new int[] {1, 2}, + /* millisecondsDeviations= */ new int[] {1, 2}); + + Parcel parcel = Parcel.obtain(); + mlltFrameToParcel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + MlltFrame mlltFrameFromParcel = MlltFrame.CREATOR.createFromParcel(parcel); + assertThat(mlltFrameFromParcel).isEqualTo(mlltFrameToParcel); + + parcel.recycle(); + } + +} From cc914494d7d8ef51ba305dc7e0baa04fa130b9d4 Mon Sep 17 00:00:00 2001 From: cpaulino Date: Tue, 16 Oct 2018 11:01:37 -0700 Subject: [PATCH 086/832] Rename libvpxJNI.so to libvpxV2JNI.so Renamed to stop collision between the ExoV1 and ExoV2 implementation. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217348150 --- .../com/google/android/exoplayer2/ext/vp9/VpxLibrary.java | 2 +- extensions/vp9/src/main/jni/Android.mk | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java index 854576b4b2..5a65fc56ff 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxLibrary.java @@ -27,7 +27,7 @@ public final class VpxLibrary { ExoPlayerLibraryInfo.registerModule("goog.exo.vpx"); } - private static final LibraryLoader LOADER = new LibraryLoader("vpx", "vpxJNI"); + private static final LibraryLoader LOADER = new LibraryLoader("vpx", "vpxV2JNI"); private VpxLibrary() {} diff --git a/extensions/vp9/src/main/jni/Android.mk b/extensions/vp9/src/main/jni/Android.mk index 868b869d56..fdcdc57b41 100644 --- a/extensions/vp9/src/main/jni/Android.mk +++ b/extensions/vp9/src/main/jni/Android.mk @@ -28,10 +28,10 @@ include $(LIBYUV_ROOT)/Android.mk LOCAL_PATH := $(WORKING_DIR) include libvpx.mk -# build libvpxJNI.so +# build libvpxV2JNI.so include $(CLEAR_VARS) LOCAL_PATH := $(WORKING_DIR) -LOCAL_MODULE := libvpxJNI +LOCAL_MODULE := libvpxV2JNI LOCAL_ARM_MODE := arm LOCAL_CPP_EXTENSION := .cc LOCAL_SRC_FILES := vpx_jni.cc From 423a954e1b0940ddaa3a1458ee80500ecdf09e5f Mon Sep 17 00:00:00 2001 From: falhassen Date: Tue, 16 Oct 2018 11:44:56 -0700 Subject: [PATCH 087/832] Automated g4 rollback of changelist 217189082. *** Reason for rollback *** Broke photos (b/117818304) *** Original change description *** Add ExoPlayer.setForegroundMode Issue: #2826 *** ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217356705 --- RELEASENOTES.md | 2 - .../google/android/exoplayer2/ExoPlayer.java | 30 ---- .../android/exoplayer2/ExoPlayerImpl.java | 9 -- .../exoplayer2/ExoPlayerImplInternal.java | 132 +++--------------- .../android/exoplayer2/SimpleExoPlayer.java | 5 - .../exoplayer2/testutil/StubExoPlayer.java | 5 - 6 files changed, 18 insertions(+), 165 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 060b5f4daf..4be871821b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,8 +2,6 @@ ### dev-v2 (not yet released) ### -* Improve decoder re-use between playbacks. TODO: Write and link a blog post - here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). * Improve initial bandwidth meter estimates using the current country and network type. * Do not retry failed loads whose error is `FileNotFoundException`. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 5ba2394c3f..96ddb2eb9c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -209,34 +209,4 @@ public interface ExoPlayer extends Player { /** Returns the currently active {@link SeekParameters} of the player. */ SeekParameters getSeekParameters(); - - /** - * Sets whether the player is allowed to keep holding limited resources such as video decoders, - * even when in the idle state. By doing so, the player may be able to reduce latency when - * starting to play another piece of content for which the same resources are required. - * - *

    This mode should be used with caution, since holding limited resources may prevent other - * players of media components from acquiring them. It should only be enabled when both - * of the following conditions are true: - * - *

      - *
    • The application that owns the player is in the foreground. - *
    • The player is used in a way that may benefit from foreground mode. For this to be true, - * the same player instance must be used to play multiple pieces of content, and there must - * be gaps between the playbacks (i.e. {@link #stop} is called to halt one playback, and - * {@link #prepare} is called some time later to start a new one). - *
    - * - *

    Note that foreground mode is not useful for switching between content without gaps - * between the playbacks. For this use case {@link #stop} does not need to be called, and simply - * calling {@link #prepare} for the new media will cause limited resources to be retained even if - * foreground mode is not enabled. - * - *

    If foreground mode is enabled, it's the application's responsibility to disable it when the - * conditions described above no longer hold. - * - * @param foregroundMode Whether the player is allowed to keep limited resources even when in the - * idle state. - */ - void setForegroundMode(boolean foregroundMode); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 130bf48921..e1f942147d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -71,7 +71,6 @@ import java.util.concurrent.CopyOnWriteArraySet; private int pendingOperationAcks; private boolean hasPendingPrepare; private boolean hasPendingSeek; - private boolean foregroundMode; private PlaybackParameters playbackParameters; private SeekParameters seekParameters; private @Nullable ExoPlaybackException playbackError; @@ -360,14 +359,6 @@ import java.util.concurrent.CopyOnWriteArraySet; return seekParameters; } - @Override - public void setForegroundMode(boolean foregroundMode) { - if (this.foregroundMode != foregroundMode) { - this.foregroundMode = foregroundMode; - internalPlayer.setForegroundMode(foregroundMode); - } - } - @Override public void stop(boolean reset) { if (reset) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 3034ead032..0c6c3ca202 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -44,7 +44,6 @@ import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.concurrent.atomic.AtomicBoolean; /** Implements the internal behavior of {@link ExoPlayerImpl}. */ /* package */ final class ExoPlayerImplInternal @@ -77,10 +76,9 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_TRACK_SELECTION_INVALIDATED = 11; private static final int MSG_SET_REPEAT_MODE = 12; private static final int MSG_SET_SHUFFLE_ENABLED = 13; - private static final int MSG_SET_FOREGROUND_MODE = 14; - private static final int MSG_SEND_MESSAGE = 15; - private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 16; - private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 17; + private static final int MSG_SEND_MESSAGE = 14; + private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 15; + private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 16; private static final int PREPARING_SOURCE_INTERVAL_MS = 10; private static final int RENDERING_INTERVAL_MS = 10; @@ -117,7 +115,6 @@ import java.util.concurrent.atomic.AtomicBoolean; private boolean rebuffering; @Player.RepeatMode private int repeatMode; private boolean shuffleModeEnabled; - private boolean foregroundMode; private int pendingPrepareCount; private SeekPosition pendingInitialSeekPosition; @@ -221,29 +218,6 @@ import java.util.concurrent.atomic.AtomicBoolean; handler.obtainMessage(MSG_SEND_MESSAGE, message).sendToTarget(); } - public synchronized void setForegroundMode(boolean foregroundMode) { - if (foregroundMode) { - handler.obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 1, 0).sendToTarget(); - } else { - AtomicBoolean processedFlag = new AtomicBoolean(); - handler - .obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 0, 0, processedFlag) - .sendToTarget(); - boolean wasInterrupted = false; - while (!processedFlag.get() && !released) { - try { - wait(); - } catch (InterruptedException e) { - wasInterrupted = true; - } - } - if (wasInterrupted) { - // Restore the interrupted status. - Thread.currentThread().interrupt(); - } - } - } - public synchronized void release() { if (released) { return; @@ -337,15 +311,8 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_SET_SEEK_PARAMETERS: setSeekParametersInternal((SeekParameters) msg.obj); break; - case MSG_SET_FOREGROUND_MODE: - setForegroundModeInternal( - /* foregroundMode= */ msg.arg1 != 0, /* processedFlag= */ (AtomicBoolean) msg.obj); - break; case MSG_STOP: - stopInternal( - /* forceResetRenderers= */ false, - /* resetPositionAndState= */ msg.arg1 != 0, - /* acknowledgeStop= */ true); + stopInternal(/* reset= */ msg.arg1 != 0, /* acknowledgeStop= */ true); break; case MSG_PERIOD_PREPARED: handlePeriodPrepared((MediaPeriod) msg.obj); @@ -378,26 +345,17 @@ import java.util.concurrent.atomic.AtomicBoolean; maybeNotifyPlaybackInfoChanged(); } catch (ExoPlaybackException e) { Log.e(TAG, "Playback error.", e); - stopInternal( - /* forceResetRenderers= */ true, - /* resetPositionAndState= */ false, - /* acknowledgeStop= */ false); + stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget(); maybeNotifyPlaybackInfoChanged(); } catch (IOException e) { Log.e(TAG, "Source error.", e); - stopInternal( - /* forceResetRenderers= */ false, - /* resetPositionAndState= */ false, - /* acknowledgeStop= */ false); + stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForSource(e)).sendToTarget(); maybeNotifyPlaybackInfoChanged(); } catch (RuntimeException e) { Log.e(TAG, "Internal runtime error.", e); - stopInternal( - /* forceResetRenderers= */ true, - /* resetPositionAndState= */ false, - /* acknowledgeStop= */ false); + stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForUnexpected(e)) .sendToTarget(); maybeNotifyPlaybackInfoChanged(); @@ -436,8 +394,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private void prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) { pendingPrepareCount++; - resetInternal( - /* resetRenderers= */ false, /* releaseMediaSource= */ true, resetPosition, resetState); + resetInternal(/* releaseMediaSource= */ true, resetPosition, resetState); loadControl.onPrepared(); this.mediaSource = mediaSource; setState(Player.STATE_BUFFERING); @@ -674,10 +631,7 @@ import java.util.concurrent.atomic.AtomicBoolean; // End playback, as we didn't manage to find a valid seek position. setState(Player.STATE_ENDED); resetInternal( - /* resetRenderers= */ false, - /* releaseMediaSource= */ false, - /* resetPosition= */ true, - /* resetState= */ false); + /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); } else { // Execute the seek in the current media periods. long newPeriodPositionUs = periodPositionUs; @@ -784,33 +738,9 @@ import java.util.concurrent.atomic.AtomicBoolean; this.seekParameters = seekParameters; } - private void setForegroundModeInternal( - boolean foregroundMode, @Nullable AtomicBoolean processedFlag) { - if (this.foregroundMode != foregroundMode) { - this.foregroundMode = foregroundMode; - if (!foregroundMode) { - for (Renderer renderer : renderers) { - if (renderer.getState() == Renderer.STATE_DISABLED) { - renderer.reset(); - } - } - } - } - if (processedFlag != null) { - synchronized (this) { - processedFlag.set(true); - notifyAll(); - } - } - } - - private void stopInternal( - boolean forceResetRenderers, boolean resetPositionAndState, boolean acknowledgeStop) { + private void stopInternal(boolean reset, boolean acknowledgeStop) { resetInternal( - /* resetRenderers= */ forceResetRenderers || !foregroundMode, - /* releaseMediaSource= */ true, - /* resetPosition= */ resetPositionAndState, - /* resetState= */ resetPositionAndState); + /* releaseMediaSource= */ true, /* resetPosition= */ reset, /* resetState= */ reset); playbackInfoUpdate.incrementPendingOperationAcks( pendingPrepareCount + (acknowledgeStop ? 1 : 0)); pendingPrepareCount = 0; @@ -820,10 +750,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private void releaseInternal() { resetInternal( - /* resetRenderers= */ true, - /* releaseMediaSource= */ true, - /* resetPosition= */ true, - /* resetState= */ true); + /* releaseMediaSource= */ true, /* resetPosition= */ true, /* resetState= */ true); loadControl.onReleased(); setState(Player.STATE_IDLE); internalPlaybackThread.quit(); @@ -845,10 +772,7 @@ import java.util.concurrent.atomic.AtomicBoolean; } private void resetInternal( - boolean resetRenderers, - boolean releaseMediaSource, - boolean resetPosition, - boolean resetState) { + boolean releaseMediaSource, boolean resetPosition, boolean resetState) { handler.removeMessages(MSG_DO_SOME_WORK); rebuffering = false; mediaClock.stop(); @@ -858,17 +782,7 @@ import java.util.concurrent.atomic.AtomicBoolean; disableRenderer(renderer); } catch (ExoPlaybackException | RuntimeException e) { // There's nothing we can do. - Log.e(TAG, "Disable failed.", e); - } - } - if (resetRenderers) { - for (Renderer renderer : renderers) { - try { - renderer.reset(); - } catch (RuntimeException e) { - // There's nothing we can do. - Log.e(TAG, "Reset failed.", e); - } + Log.e(TAG, "Stop failed.", e); } } enabledRenderers = new Renderer[0]; @@ -1075,6 +989,7 @@ import java.util.concurrent.atomic.AtomicBoolean; mediaClock.onRendererDisabled(renderer); ensureStopped(renderer); renderer.disable(); + renderer.reset(); } private void reselectTracksInternal() throws ExoPlaybackException { @@ -1379,10 +1294,7 @@ import java.util.concurrent.atomic.AtomicBoolean; setState(Player.STATE_ENDED); // Reset, but retain the source so that it can still be used should a seek occur. resetInternal( - /* resetRenderers= */ false, - /* releaseMediaSource= */ false, - /* resetPosition= */ true, - /* resetState= */ false); + /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); } /** @@ -1727,17 +1639,9 @@ import java.util.concurrent.atomic.AtomicBoolean; throws ExoPlaybackException { enabledRenderers = new Renderer[totalEnabledRendererCount]; int enabledRendererCount = 0; - TrackSelectorResult trackSelectorResult = queue.getPlayingPeriod().getTrackSelectorResult(); - // Reset all disabled renderers before enabling any new ones. This makes sure resources released - // by the disabled renderers will be available to renderers that are being enabled. + MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); for (int i = 0; i < renderers.length; i++) { - if (!trackSelectorResult.isRendererEnabled(i)) { - renderers[i].reset(); - } - } - // Enable the renderers. - for (int i = 0; i < renderers.length; i++) { - if (trackSelectorResult.isRendererEnabled(i)) { + if (playingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)) { enableRenderer(i, rendererWasEnabledFlags[i], enabledRendererCount++); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 4ca6b51ce2..8517556887 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -958,11 +958,6 @@ public class SimpleExoPlayer extends BasePlayer return player.getSeekParameters(); } - @Override - public void setForegroundMode(boolean foregroundMode) { - player.setForegroundMode(foregroundMode); - } - @Override public void stop(boolean reset) { verifyApplicationThread(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 1ac19591c0..156b573df8 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -267,9 +267,4 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { public long getContentBufferedPosition() { throw new UnsupportedOperationException(); } - - @Override - public void setForegroundMode(boolean foregroundMode) { - throw new UnsupportedOperationException(); - } } From 86fdcb08d84de1e04a0f0ffaa0aa9baa55b6c00a Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 17 Oct 2018 05:57:40 -0700 Subject: [PATCH 088/832] Fix documentation confusion in MediaChunkIterator subclasses ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217505571 --- .../exoplayer2/source/chunk/BaseMediaChunkIterator.java | 2 +- .../exoplayer2/source/dash/DefaultDashChunkSource.java | 8 +++++--- .../android/exoplayer2/source/hls/HlsChunkSource.java | 2 +- .../source/smoothstreaming/DefaultSsChunkSource.java | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java index 68dd322449..4c1462babf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java @@ -31,7 +31,7 @@ public abstract class BaseMediaChunkIterator implements MediaChunkIterator { /** * Creates base iterator. * - * @param fromIndex The index at which the iterator will start. + * @param fromIndex The first available index. * @param toIndex The last available index. */ public BaseMediaChunkIterator(long fromIndex, long toIndex) { diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 1ea25ecc36..d989cdf2f7 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -578,12 +578,14 @@ public class DefaultDashChunkSource implements DashChunkSource { * Creates iterator. * * @param representation The {@link RepresentationHolder} to wrap. - * @param segmentNum The number of the segment this iterator will be pointing to initially. + * @param firstAvailableSegmentNum The number of the first available segment. * @param lastAvailableSegmentNum The number of the last available segment. */ public RepresentationSegmentIterator( - RepresentationHolder representation, long segmentNum, long lastAvailableSegmentNum) { - super(/* fromIndex= */ segmentNum, /* toIndex= */ lastAvailableSegmentNum); + RepresentationHolder representation, + long firstAvailableSegmentNum, + long lastAvailableSegmentNum) { + super(/* fromIndex= */ firstAvailableSegmentNum, /* toIndex= */ lastAvailableSegmentNum); this.representationHolder = representation; } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index a8cf1a1437..f000187827 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -631,7 +631,7 @@ import java.util.List; * @param playlist The {@link HlsMediaPlaylist} to wrap. * @param startOfPlaylistInPeriodUs The start time of the playlist in the period, in * microseconds. - * @param chunkIndex The chunk index in the playlist at which the iterator will start. + * @param chunkIndex The index of the first available chunk in the playlist. */ public HlsMediaPlaylistSegmentIterator( HlsMediaPlaylist playlist, long startOfPlaylistInPeriodUs, int chunkIndex) { diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java index 9ac376efad..d50375d4c9 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java @@ -318,7 +318,7 @@ public class DefaultSsChunkSource implements SsChunkSource { * * @param streamElement The {@link StreamElement} to wrap. * @param trackIndex The track index in the stream element. - * @param chunkIndex The chunk index at which the iterator will start. + * @param chunkIndex The index of the first available chunk. */ public StreamElementIterator(StreamElement streamElement, int trackIndex, int chunkIndex) { super(/* fromIndex= */ chunkIndex, /* toIndex= */ streamElement.chunkCount - 1); From 05a98a7b79e255b188ab7c0c40c02c2dfeb4f383 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 18 Oct 2018 04:01:53 -0700 Subject: [PATCH 089/832] Code shrinking doesn't like Class.super.defaultMethodName Just not doing it seems simplier and more obviously correct than suppressing the warnings in our proguard file. Issue: #4890 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217675527 --- .../android/exoplayer2/upstream/DefaultDataSource.java | 5 ++--- .../android/exoplayer2/upstream/cache/CacheDataSource.java | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java index acb2c59e0c..6504562c58 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -261,9 +262,7 @@ public final class DefaultDataSource implements DataSource { @Override public Map> getResponseHeaders() { - return dataSource == null - ? DataSource.super.getResponseHeaders() - : dataSource.getResponseHeaders(); + return dataSource == null ? Collections.emptyMap() : dataSource.getResponseHeaders(); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index 37bc0aca37..eaf72cf7fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -34,6 +34,7 @@ import java.io.InterruptedIOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -367,7 +368,7 @@ public final class CacheDataSource implements DataSource { // TODO: Implement. return isReadingFromUpstream() ? upstreamDataSource.getResponseHeaders() - : DataSource.super.getResponseHeaders(); + : Collections.emptyMap(); } @Override From 1c6abc8b03c6015d226a1ded8f634e4c5693549d Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 18 Oct 2018 04:11:48 -0700 Subject: [PATCH 090/832] Set output on surface in all states setSurface only sets the output surface on the codec if the renderer is enabled or started. This was fine in the past because the codec was always null in other states, however the recent codec reuse work means this is no longer the case. The output surface should always be set if the codec is non-null. If setting the surface requires releasing the codec instance, we should only instantiate the new instance if enabled or started. This is in line with what onDisabled does if flushing requires releasing the codec ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217676434 --- .../exoplayer2/video/MediaCodecVideoRenderer.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 2b2e628017..423a7e32ed 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -415,14 +415,15 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { if (this.surface != surface) { this.surface = surface; @State int state = getState(); - if (state == STATE_ENABLED || state == STATE_STARTED) { - MediaCodec codec = getCodec(); - if (Util.SDK_INT >= 23 && codec != null && surface != null - && !codecNeedsSetOutputSurfaceWorkaround) { + MediaCodec codec = getCodec(); + if (codec != null) { + if (Util.SDK_INT >= 23 && surface != null && !codecNeedsSetOutputSurfaceWorkaround) { setOutputSurfaceV23(codec, surface); } else { releaseCodec(); - maybeInitCodec(); + if (state == STATE_ENABLED || state == STATE_STARTED) { + maybeInitCodec(); + } } } if (surface != null && surface != dummySurface) { From 50dd089794ac70e3aa34873d513cc832b4c009cf Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 18 Oct 2018 08:54:19 -0700 Subject: [PATCH 091/832] Update the cast framework gradle dependency in the Cast extension Issue:#4960 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217707957 --- extensions/cast/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/cast/build.gradle b/extensions/cast/build.gradle index ab14b4034a..f6821d5cd2 100644 --- a/extensions/cast/build.gradle +++ b/extensions/cast/build.gradle @@ -31,7 +31,7 @@ android { } dependencies { - api 'com.google.android.gms:play-services-cast-framework:16.0.1' + api 'com.google.android.gms:play-services-cast-framework:16.0.3' compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion implementation project(modulePrefix + 'library-core') From 80e9c84a9ef9d13fdc3990f4b87b4424e2e0b877 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 18 Oct 2018 08:56:36 -0700 Subject: [PATCH 092/832] Remove RemotePlayer ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217708323 --- .../DefaultReceiverPlayerManager.java | 4 +- .../exoplayer2/ext/cast/CastPlayer.java | 10 ++--- .../exoplayer2/ext/cast/RemotePlayer.java | 45 ------------------- .../ext/cast/SessionAvailabilityListener.java | 26 +++++++++++ 4 files changed, 33 insertions(+), 52 deletions(-) delete mode 100644 extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/RemotePlayer.java create mode 100644 extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/SessionAvailabilityListener.java diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java index f30d3e602f..daaac9486e 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java @@ -33,7 +33,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.ext.cast.CastPlayer; import com.google.android.exoplayer2.ext.cast.MediaItem; -import com.google.android.exoplayer2.ext.cast.RemotePlayer; +import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; @@ -52,7 +52,7 @@ import java.util.ArrayList; /** Manages players and an internal media queue for the ExoPlayer/Cast demo app. */ /* package */ class DefaultReceiverPlayerManager - implements EventListener, RemotePlayer.SessionAvailabilityListener, PlayerManager { + implements EventListener, SessionAvailabilityListener, PlayerManager { private static final String USER_AGENT = "ExoCastDemoPlayer"; private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY = diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index e5832f3b31..584ac68305 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -53,8 +53,8 @@ import java.util.concurrent.CopyOnWriteArraySet; * *

    The behavior of this class depends on the underlying Cast session, which is obtained from the * Cast context passed to {@link #CastPlayer}. To keep track of the session, {@link - * #isCastSessionAvailable()} can be queried and {@link RemotePlayer.SessionAvailabilityListener} - * can be implemented and attached to the player. + * #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be + * implemented and attached to the player. * *

    If no session is available, the player state will remain unchanged and calls to methods that * alter it will be ignored. Querying the player state is possible even when no session is @@ -88,7 +88,7 @@ public final class CastPlayer extends BasePlayer { // Listeners. private final CopyOnWriteArraySet listeners; - private RemotePlayer.SessionAvailabilityListener sessionAvailabilityListener; + private SessionAvailabilityListener sessionAvailabilityListener; // Internal state. private CastTimeline currentTimeline; @@ -257,9 +257,9 @@ public final class CastPlayer extends BasePlayer { /** * Sets a listener for updates on the cast session availability. * - * @param listener The {@link RemotePlayer.SessionAvailabilityListener}. + * @param listener The {@link SessionAvailabilityListener}. */ - public void setSessionAvailabilityListener(RemotePlayer.SessionAvailabilityListener listener) { + public void setSessionAvailabilityListener(SessionAvailabilityListener listener) { sessionAvailabilityListener = listener; } diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/RemotePlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/RemotePlayer.java deleted file mode 100644 index 98d5895db8..0000000000 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/RemotePlayer.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ext.cast; - -import com.google.android.exoplayer2.Player; - -/** A {@link Player} for playing media remotely using the Google Cast framework. */ -public interface RemotePlayer extends Player { - - /** Listener of changes in the cast session availability. */ - interface SessionAvailabilityListener { - - /** Called when a cast session becomes available to the player. */ - void onCastSessionAvailable(); - - /** Called when the cast session becomes unavailable. */ - void onCastSessionUnavailable(); - } - - /** Returns whether a cast session is available. */ - boolean isCastSessionAvailable(); - - /** - * Sets a listener for updates on the cast session availability. - * - * @param listener The {@link SessionAvailabilityListener}. - */ - void setSessionAvailabilityListener(SessionAvailabilityListener listener); - - /** Returns the {@link MediaItemQueue} associated to this player. */ - MediaItemQueue getMediaItemQueue(); -} diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/SessionAvailabilityListener.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/SessionAvailabilityListener.java new file mode 100644 index 0000000000..c686c496c6 --- /dev/null +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/SessionAvailabilityListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.cast; + +/** Listener of changes in the cast session availability. */ +public interface SessionAvailabilityListener { + + /** Called when a cast session becomes available to the player. */ + void onCastSessionAvailable(); + + /** Called when the cast session becomes unavailable. */ + void onCastSessionUnavailable(); +} From 3805717026b1fb2b81b73b20e3496be40d21bbe9 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 18 Oct 2018 09:17:28 -0700 Subject: [PATCH 093/832] Decouple dummySurface lifespan from codec lifespans I don't think there's any particular reason for releasing dummySurface when we release the codec. It seems to make more sense to decouple the two, and treat a dummySurface as a resource that should be released on reset. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217711788 --- .../video/MediaCodecVideoRenderer.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 423a7e32ed..18aeada04b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -381,6 +381,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } } + @Override + protected void onReset() { + try { + super.onReset(); + } finally { + if (dummySurface != null) { + if (surface == dummySurface) { + surface = null; + } + dummySurface.release(); + dummySurface = null; + } + } + } + @Override public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException { if (messageType == C.MSG_SET_SURFACE) { @@ -509,13 +524,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { super.releaseCodec(); } finally { buffersInCodecCount = 0; - if (dummySurface != null) { - if (surface == dummySurface) { - surface = null; - } - dummySurface.release(); - dummySurface = null; - } } } From 1ef3efaa909df3b9ef49133a872d5e3ee39fdf73 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 18 Oct 2018 14:49:51 -0700 Subject: [PATCH 094/832] Automated g4 rollback of changelist 217356705. *** Reason for rollback *** Photos regression is resolved by [] *** Original change description *** Automated g4 rollback of changelist 217189082. *** Reason for rollback *** Broke photos (b/117818304) *** Original change description *** Add ExoPlayer.setForegroundMode Issue: #2826 *** *** ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217773278 --- RELEASENOTES.md | 2 + .../google/android/exoplayer2/ExoPlayer.java | 30 ++++ .../android/exoplayer2/ExoPlayerImpl.java | 9 ++ .../exoplayer2/ExoPlayerImplInternal.java | 132 +++++++++++++++--- .../android/exoplayer2/SimpleExoPlayer.java | 5 + .../exoplayer2/testutil/StubExoPlayer.java | 5 + 6 files changed, 165 insertions(+), 18 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4be871821b..060b5f4daf 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### dev-v2 (not yet released) ### +* Improve decoder re-use between playbacks. TODO: Write and link a blog post + here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). * Improve initial bandwidth meter estimates using the current country and network type. * Do not retry failed loads whose error is `FileNotFoundException`. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 96ddb2eb9c..5ba2394c3f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -209,4 +209,34 @@ public interface ExoPlayer extends Player { /** Returns the currently active {@link SeekParameters} of the player. */ SeekParameters getSeekParameters(); + + /** + * Sets whether the player is allowed to keep holding limited resources such as video decoders, + * even when in the idle state. By doing so, the player may be able to reduce latency when + * starting to play another piece of content for which the same resources are required. + * + *

    This mode should be used with caution, since holding limited resources may prevent other + * players of media components from acquiring them. It should only be enabled when both + * of the following conditions are true: + * + *

      + *
    • The application that owns the player is in the foreground. + *
    • The player is used in a way that may benefit from foreground mode. For this to be true, + * the same player instance must be used to play multiple pieces of content, and there must + * be gaps between the playbacks (i.e. {@link #stop} is called to halt one playback, and + * {@link #prepare} is called some time later to start a new one). + *
    + * + *

    Note that foreground mode is not useful for switching between content without gaps + * between the playbacks. For this use case {@link #stop} does not need to be called, and simply + * calling {@link #prepare} for the new media will cause limited resources to be retained even if + * foreground mode is not enabled. + * + *

    If foreground mode is enabled, it's the application's responsibility to disable it when the + * conditions described above no longer hold. + * + * @param foregroundMode Whether the player is allowed to keep limited resources even when in the + * idle state. + */ + void setForegroundMode(boolean foregroundMode); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index e1f942147d..130bf48921 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -71,6 +71,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private int pendingOperationAcks; private boolean hasPendingPrepare; private boolean hasPendingSeek; + private boolean foregroundMode; private PlaybackParameters playbackParameters; private SeekParameters seekParameters; private @Nullable ExoPlaybackException playbackError; @@ -359,6 +360,14 @@ import java.util.concurrent.CopyOnWriteArraySet; return seekParameters; } + @Override + public void setForegroundMode(boolean foregroundMode) { + if (this.foregroundMode != foregroundMode) { + this.foregroundMode = foregroundMode; + internalPlayer.setForegroundMode(foregroundMode); + } + } + @Override public void stop(boolean reset) { if (reset) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 0c6c3ca202..3034ead032 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -44,6 +44,7 @@ import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; /** Implements the internal behavior of {@link ExoPlayerImpl}. */ /* package */ final class ExoPlayerImplInternal @@ -76,9 +77,10 @@ import java.util.Collections; private static final int MSG_TRACK_SELECTION_INVALIDATED = 11; private static final int MSG_SET_REPEAT_MODE = 12; private static final int MSG_SET_SHUFFLE_ENABLED = 13; - private static final int MSG_SEND_MESSAGE = 14; - private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 15; - private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 16; + private static final int MSG_SET_FOREGROUND_MODE = 14; + private static final int MSG_SEND_MESSAGE = 15; + private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 16; + private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 17; private static final int PREPARING_SOURCE_INTERVAL_MS = 10; private static final int RENDERING_INTERVAL_MS = 10; @@ -115,6 +117,7 @@ import java.util.Collections; private boolean rebuffering; @Player.RepeatMode private int repeatMode; private boolean shuffleModeEnabled; + private boolean foregroundMode; private int pendingPrepareCount; private SeekPosition pendingInitialSeekPosition; @@ -218,6 +221,29 @@ import java.util.Collections; handler.obtainMessage(MSG_SEND_MESSAGE, message).sendToTarget(); } + public synchronized void setForegroundMode(boolean foregroundMode) { + if (foregroundMode) { + handler.obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 1, 0).sendToTarget(); + } else { + AtomicBoolean processedFlag = new AtomicBoolean(); + handler + .obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 0, 0, processedFlag) + .sendToTarget(); + boolean wasInterrupted = false; + while (!processedFlag.get() && !released) { + try { + wait(); + } catch (InterruptedException e) { + wasInterrupted = true; + } + } + if (wasInterrupted) { + // Restore the interrupted status. + Thread.currentThread().interrupt(); + } + } + } + public synchronized void release() { if (released) { return; @@ -311,8 +337,15 @@ import java.util.Collections; case MSG_SET_SEEK_PARAMETERS: setSeekParametersInternal((SeekParameters) msg.obj); break; + case MSG_SET_FOREGROUND_MODE: + setForegroundModeInternal( + /* foregroundMode= */ msg.arg1 != 0, /* processedFlag= */ (AtomicBoolean) msg.obj); + break; case MSG_STOP: - stopInternal(/* reset= */ msg.arg1 != 0, /* acknowledgeStop= */ true); + stopInternal( + /* forceResetRenderers= */ false, + /* resetPositionAndState= */ msg.arg1 != 0, + /* acknowledgeStop= */ true); break; case MSG_PERIOD_PREPARED: handlePeriodPrepared((MediaPeriod) msg.obj); @@ -345,17 +378,26 @@ import java.util.Collections; maybeNotifyPlaybackInfoChanged(); } catch (ExoPlaybackException e) { Log.e(TAG, "Playback error.", e); - stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); + stopInternal( + /* forceResetRenderers= */ true, + /* resetPositionAndState= */ false, + /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget(); maybeNotifyPlaybackInfoChanged(); } catch (IOException e) { Log.e(TAG, "Source error.", e); - stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); + stopInternal( + /* forceResetRenderers= */ false, + /* resetPositionAndState= */ false, + /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForSource(e)).sendToTarget(); maybeNotifyPlaybackInfoChanged(); } catch (RuntimeException e) { Log.e(TAG, "Internal runtime error.", e); - stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); + stopInternal( + /* forceResetRenderers= */ true, + /* resetPositionAndState= */ false, + /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForUnexpected(e)) .sendToTarget(); maybeNotifyPlaybackInfoChanged(); @@ -394,7 +436,8 @@ import java.util.Collections; private void prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) { pendingPrepareCount++; - resetInternal(/* releaseMediaSource= */ true, resetPosition, resetState); + resetInternal( + /* resetRenderers= */ false, /* releaseMediaSource= */ true, resetPosition, resetState); loadControl.onPrepared(); this.mediaSource = mediaSource; setState(Player.STATE_BUFFERING); @@ -631,7 +674,10 @@ import java.util.Collections; // End playback, as we didn't manage to find a valid seek position. setState(Player.STATE_ENDED); resetInternal( - /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); + /* resetRenderers= */ false, + /* releaseMediaSource= */ false, + /* resetPosition= */ true, + /* resetState= */ false); } else { // Execute the seek in the current media periods. long newPeriodPositionUs = periodPositionUs; @@ -738,9 +784,33 @@ import java.util.Collections; this.seekParameters = seekParameters; } - private void stopInternal(boolean reset, boolean acknowledgeStop) { + private void setForegroundModeInternal( + boolean foregroundMode, @Nullable AtomicBoolean processedFlag) { + if (this.foregroundMode != foregroundMode) { + this.foregroundMode = foregroundMode; + if (!foregroundMode) { + for (Renderer renderer : renderers) { + if (renderer.getState() == Renderer.STATE_DISABLED) { + renderer.reset(); + } + } + } + } + if (processedFlag != null) { + synchronized (this) { + processedFlag.set(true); + notifyAll(); + } + } + } + + private void stopInternal( + boolean forceResetRenderers, boolean resetPositionAndState, boolean acknowledgeStop) { resetInternal( - /* releaseMediaSource= */ true, /* resetPosition= */ reset, /* resetState= */ reset); + /* resetRenderers= */ forceResetRenderers || !foregroundMode, + /* releaseMediaSource= */ true, + /* resetPosition= */ resetPositionAndState, + /* resetState= */ resetPositionAndState); playbackInfoUpdate.incrementPendingOperationAcks( pendingPrepareCount + (acknowledgeStop ? 1 : 0)); pendingPrepareCount = 0; @@ -750,7 +820,10 @@ import java.util.Collections; private void releaseInternal() { resetInternal( - /* releaseMediaSource= */ true, /* resetPosition= */ true, /* resetState= */ true); + /* resetRenderers= */ true, + /* releaseMediaSource= */ true, + /* resetPosition= */ true, + /* resetState= */ true); loadControl.onReleased(); setState(Player.STATE_IDLE); internalPlaybackThread.quit(); @@ -772,7 +845,10 @@ import java.util.Collections; } private void resetInternal( - boolean releaseMediaSource, boolean resetPosition, boolean resetState) { + boolean resetRenderers, + boolean releaseMediaSource, + boolean resetPosition, + boolean resetState) { handler.removeMessages(MSG_DO_SOME_WORK); rebuffering = false; mediaClock.stop(); @@ -782,7 +858,17 @@ import java.util.Collections; disableRenderer(renderer); } catch (ExoPlaybackException | RuntimeException e) { // There's nothing we can do. - Log.e(TAG, "Stop failed.", e); + Log.e(TAG, "Disable failed.", e); + } + } + if (resetRenderers) { + for (Renderer renderer : renderers) { + try { + renderer.reset(); + } catch (RuntimeException e) { + // There's nothing we can do. + Log.e(TAG, "Reset failed.", e); + } } } enabledRenderers = new Renderer[0]; @@ -989,7 +1075,6 @@ import java.util.Collections; mediaClock.onRendererDisabled(renderer); ensureStopped(renderer); renderer.disable(); - renderer.reset(); } private void reselectTracksInternal() throws ExoPlaybackException { @@ -1294,7 +1379,10 @@ import java.util.Collections; setState(Player.STATE_ENDED); // Reset, but retain the source so that it can still be used should a seek occur. resetInternal( - /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); + /* resetRenderers= */ false, + /* releaseMediaSource= */ false, + /* resetPosition= */ true, + /* resetState= */ false); } /** @@ -1639,9 +1727,17 @@ import java.util.Collections; throws ExoPlaybackException { enabledRenderers = new Renderer[totalEnabledRendererCount]; int enabledRendererCount = 0; - MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); + TrackSelectorResult trackSelectorResult = queue.getPlayingPeriod().getTrackSelectorResult(); + // Reset all disabled renderers before enabling any new ones. This makes sure resources released + // by the disabled renderers will be available to renderers that are being enabled. for (int i = 0; i < renderers.length; i++) { - if (playingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)) { + if (!trackSelectorResult.isRendererEnabled(i)) { + renderers[i].reset(); + } + } + // Enable the renderers. + for (int i = 0; i < renderers.length; i++) { + if (trackSelectorResult.isRendererEnabled(i)) { enableRenderer(i, rendererWasEnabledFlags[i], enabledRendererCount++); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 8517556887..4ca6b51ce2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -958,6 +958,11 @@ public class SimpleExoPlayer extends BasePlayer return player.getSeekParameters(); } + @Override + public void setForegroundMode(boolean foregroundMode) { + player.setForegroundMode(foregroundMode); + } + @Override public void stop(boolean reset) { verifyApplicationThread(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 156b573df8..1ac19591c0 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -267,4 +267,9 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { public long getContentBufferedPosition() { throw new UnsupportedOperationException(); } + + @Override + public void setForegroundMode(boolean foregroundMode) { + throw new UnsupportedOperationException(); + } } From 861b81694de78b4eaba324121fec4bd997e99f73 Mon Sep 17 00:00:00 2001 From: ogaclejapan Date: Sun, 21 Oct 2018 13:23:53 +0900 Subject: [PATCH 095/832] Allow setting the ad media bitrate in ImaAdsLoader --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 95a3a588b4..581964ae47 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -93,6 +93,7 @@ public final class ImaAdsLoader private @Nullable AdEventListener adEventListener; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; + private int mediaBitrateKbps; private ImaFactory imaFactory; /** @@ -104,6 +105,7 @@ public final class ImaAdsLoader this.context = Assertions.checkNotNull(context); vastLoadTimeoutMs = TIMEOUT_UNSET; mediaLoadTimeoutMs = TIMEOUT_UNSET; + mediaBitrateKbps = BITRATE_UNSET; imaFactory = new DefaultImaFactory(); } @@ -159,6 +161,19 @@ public final class ImaAdsLoader return this; } + /** + * Sets the ad media maximum recommended bitrate, in Kbps. + * + * @param mediaBitrateKbps The ad media maximum recommended bitrate, in Kbps. + * @return This builder, for convenience. + * @see AdsRenderingSettings#setBitrateKbps(int) + */ + public Builder setMediaBitrateKbps(int mediaBitrateKbps) { + Assertions.checkArgument(mediaBitrateKbps > 0); + this.mediaBitrateKbps = mediaBitrateKbps; + return this; + } + // @VisibleForTesting /* package */ Builder setImaFactory(ImaFactory imaFactory) { this.imaFactory = Assertions.checkNotNull(imaFactory); @@ -181,6 +196,7 @@ public final class ImaAdsLoader null, vastLoadTimeoutMs, mediaLoadTimeoutMs, + mediaBitrateKbps, adEventListener, imaFactory); } @@ -200,6 +216,7 @@ public final class ImaAdsLoader adsResponse, vastLoadTimeoutMs, mediaLoadTimeoutMs, + mediaBitrateKbps, adEventListener, imaFactory); } @@ -229,6 +246,7 @@ public final class ImaAdsLoader private static final long MAXIMUM_PRELOAD_DURATION_MS = 8000; private static final int TIMEOUT_UNSET = -1; + private static final int BITRATE_UNSET = -1; /** The state of ad playback. */ @Documented @@ -252,6 +270,7 @@ public final class ImaAdsLoader private final @Nullable String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; + private final int mediaBitrateKbps; private final @Nullable AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; @@ -338,6 +357,7 @@ public final class ImaAdsLoader /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* mediaBitrateKpbs= */ BITRATE_UNSET, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -362,6 +382,7 @@ public final class ImaAdsLoader /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* mediaBitrateKbps= */ BITRATE_UNSET, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -373,6 +394,7 @@ public final class ImaAdsLoader @Nullable String adsResponse, int vastLoadTimeoutMs, int mediaLoadTimeoutMs, + int mediaBitrateKbps, @Nullable AdEventListener adEventListener, ImaFactory imaFactory) { Assertions.checkArgument(adTagUri != null || adsResponse != null); @@ -380,6 +402,7 @@ public final class ImaAdsLoader this.adsResponse = adsResponse; this.vastLoadTimeoutMs = vastLoadTimeoutMs; this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; + this.mediaBitrateKbps = mediaBitrateKbps; this.adEventListener = adEventListener; this.imaFactory = imaFactory; if (imaSdkSettings == null) { @@ -926,6 +949,9 @@ public final class ImaAdsLoader if (mediaLoadTimeoutMs != TIMEOUT_UNSET) { adsRenderingSettings.setLoadVideoTimeout(mediaLoadTimeoutMs); } + if (mediaBitrateKbps != BITRATE_UNSET) { + adsRenderingSettings.setBitrateKbps(mediaBitrateKbps); + } // Set up the ad playback state, skipping ads based on the start position as required. long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints()); From cfcbd2114ba43a7a906577981e383a264d437929 Mon Sep 17 00:00:00 2001 From: ogaclejapan Date: Sun, 21 Oct 2018 13:54:07 +0900 Subject: [PATCH 096/832] Allow setting the ad UI elements to be rendered in ImaAdsLoader --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 581964ae47..e42f2853b2 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -40,6 +40,7 @@ import com.google.ads.interactivemedia.v3.api.AdsRequest; import com.google.ads.interactivemedia.v3.api.CompanionAdSlot; import com.google.ads.interactivemedia.v3.api.ImaSdkFactory; import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; +import com.google.ads.interactivemedia.v3.api.UiElement; import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider; import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate; @@ -69,6 +70,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; /** Loads ads using the IMA SDK. All methods are called on the main thread. */ public final class ImaAdsLoader @@ -91,6 +93,7 @@ public final class ImaAdsLoader private @Nullable ImaSdkSettings imaSdkSettings; private @Nullable AdEventListener adEventListener; + private @Nullable Set adUiElements; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; private int mediaBitrateKbps; @@ -135,6 +138,18 @@ public final class ImaAdsLoader return this; } + /** + * Sets the ad UI elements to be rendered by the IMA SDK. + * + * @param adUiElements The ad UI elements to be rendered by the IMA SDK. + * @return This builder, for convenience. + * @see AdsRenderingSettings#setUiElements(Set) + */ + public Builder setAdUiElements(Set adUiElements) { + this.adUiElements = Assertions.checkNotNull(adUiElements); + return this; + } + /** * Sets the VAST load timeout, in milliseconds. * @@ -197,6 +212,7 @@ public final class ImaAdsLoader vastLoadTimeoutMs, mediaLoadTimeoutMs, mediaBitrateKbps, + adUiElements, adEventListener, imaFactory); } @@ -217,6 +233,7 @@ public final class ImaAdsLoader vastLoadTimeoutMs, mediaLoadTimeoutMs, mediaBitrateKbps, + adUiElements, adEventListener, imaFactory); } @@ -271,6 +288,7 @@ public final class ImaAdsLoader private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; private final int mediaBitrateKbps; + private final @Nullable Set adUiElements; private final @Nullable AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; @@ -358,6 +376,7 @@ public final class ImaAdsLoader /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaBitrateKpbs= */ BITRATE_UNSET, + /* adUiElements= */ null, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -383,6 +402,7 @@ public final class ImaAdsLoader /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaBitrateKbps= */ BITRATE_UNSET, + /* adUiElements= */ null, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -395,6 +415,7 @@ public final class ImaAdsLoader int vastLoadTimeoutMs, int mediaLoadTimeoutMs, int mediaBitrateKbps, + @Nullable Set adUiElements, @Nullable AdEventListener adEventListener, ImaFactory imaFactory) { Assertions.checkArgument(adTagUri != null || adsResponse != null); @@ -403,6 +424,7 @@ public final class ImaAdsLoader this.vastLoadTimeoutMs = vastLoadTimeoutMs; this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; this.mediaBitrateKbps = mediaBitrateKbps; + this.adUiElements = adUiElements; this.adEventListener = adEventListener; this.imaFactory = imaFactory; if (imaSdkSettings == null) { @@ -952,6 +974,9 @@ public final class ImaAdsLoader if (mediaBitrateKbps != BITRATE_UNSET) { adsRenderingSettings.setBitrateKbps(mediaBitrateKbps); } + if (adUiElements != null) { + adsRenderingSettings.setUiElements(adUiElements); + } // Set up the ad playback state, skipping ads based on the start position as required. long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints()); From 95eff3b747dabb7e8eaeee238b40d4812d91d29a Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Mon, 22 Oct 2018 23:02:01 +0200 Subject: [PATCH 097/832] Add Shoutcast Metadata Protocol (ICY) extension Based on the OkHttp extension. Resolves #3735 --- core_settings.gradle | 2 + extensions/icy/README.md | 67 +++ extensions/icy/build.gradle | 52 +++ extensions/icy/proguard-rules.txt | 2 + extensions/icy/src/main/AndroidManifest.xml | 17 + .../exoplayer2/ext/icy/IcyHttpDataSource.java | 387 ++++++++++++++++++ .../ext/icy/IcyHttpDataSourceFactory.java | 85 ++++ .../ext/icy/IcyHttpDataSourceFactoryTest.java | 41 ++ .../ext/icy/IcyHttpDataSourceTest.java | 29 ++ .../exoplayer2/ext/icy/test/Constants.java | 6 + 10 files changed, 688 insertions(+) create mode 100644 extensions/icy/README.md create mode 100644 extensions/icy/build.gradle create mode 100644 extensions/icy/proguard-rules.txt create mode 100644 extensions/icy/src/main/AndroidManifest.xml create mode 100644 extensions/icy/src/main/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSource.java create mode 100644 extensions/icy/src/main/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceFactory.java create mode 100644 extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceFactoryTest.java create mode 100644 extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceTest.java create mode 100644 extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/test/Constants.java diff --git a/core_settings.gradle b/core_settings.gradle index 4d90fa962a..df15edba29 100644 --- a/core_settings.gradle +++ b/core_settings.gradle @@ -28,6 +28,7 @@ include modulePrefix + 'testutils-robolectric' include modulePrefix + 'extension-ffmpeg' include modulePrefix + 'extension-flac' include modulePrefix + 'extension-gvr' +include modulePrefix + 'extension-icy' include modulePrefix + 'extension-ima' include modulePrefix + 'extension-cast' include modulePrefix + 'extension-cronet' @@ -50,6 +51,7 @@ project(modulePrefix + 'testutils-robolectric').projectDir = new File(rootDir, ' project(modulePrefix + 'extension-ffmpeg').projectDir = new File(rootDir, 'extensions/ffmpeg') project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac') project(modulePrefix + 'extension-gvr').projectDir = new File(rootDir, 'extensions/gvr') +project(modulePrefix + 'extension-icy').projectDir = new File(rootDir, 'extensions/icy') project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensions/ima') project(modulePrefix + 'extension-cast').projectDir = new File(rootDir, 'extensions/cast') project(modulePrefix + 'extension-cronet').projectDir = new File(rootDir, 'extensions/cronet') diff --git a/extensions/icy/README.md b/extensions/icy/README.md new file mode 100644 index 0000000000..34ffe762e3 --- /dev/null +++ b/extensions/icy/README.md @@ -0,0 +1,67 @@ +# ExoPlayer Shoutcast Metadata Protocol (ICY) extension # +The Shoutcast Metadata Protocol extension provides **IcyHttpDataSource** and +**IcyHttpDataSourceFactory** which can parse ICY metadata information such as +stream name and genre as well as current song information from a music stream. + +You can find the protocol description here: + +- https://cast.readme.io/v1.0/docs/icy +- http://www.smackfu.com/stuff/programming/shoutcast.html + +## Getting the extension ## + +The easiest way to use the extension is to add it as a gradle dependency: + +```gradle +implementation 'com.google.android.exoplayer:extension-icy:2.X.X' +``` + +where `2.X.X` is the version, which must match the version of the ExoPlayer +library being used. + +Alternatively, you can clone the ExoPlayer repository and depend on the module +locally. Instructions for doing this can be found in ExoPlayer's +[top level README][]. + +[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md + +## Using the extension ## + +To receive information about the current music stream (such as name and genre, +see **IcyHeaders** class) as well as current song information (see +**IcyMetadata** class), pass an instance of **IcyHttpDataSourceFactory** instead +of an **DefaultHttpDataSourceFactory** like this (in Kotlin): + +```kotlin +// ... exoPlayer instance already created + +// Custom HTTP data source factory which requests Icy metadata and parses it if +// the stream server supports it +val client = OkHttpClient.Builder().build() +val icyHttpDataSourceFactory = IcyHttpDataSourceFactory.Builder(client) + .setUserAgent(userAgent) + .setIcyHeadersListener { icyHeaders -> + Log.d("XXX", "onIcyHeaders: %s".format(icyHeaders.toString())) + } + .setIcyMetadataChangeListener { icyMetadata -> + Log.d("XXX", "onIcyMetaData: %s".format(icyMetadata.toString())) + } + .build() + +// Produces DataSource instances through which media data is loaded +val dataSourceFactory = DefaultDataSourceFactory(applicationContext, null, icyHttpDataSourceFactory) + +// The MediaSource represents the media to be played +val mediaSource = ExtractorMediaSource.Factory(dataSourceFactory) + .setExtractorsFactory(DefaultExtractorsFactory()) + .createMediaSource(sourceUri) + +// exoPlayer?.prepare(mediaSource) ... +``` + +## Links ## + +* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ext.icy.*` + belong to this module. + +[Javadoc]: https://google.github.io/ExoPlayer/doc/reference/index.html diff --git a/extensions/icy/build.gradle b/extensions/icy/build.gradle new file mode 100644 index 0000000000..0bff281865 --- /dev/null +++ b/extensions/icy/build.gradle @@ -0,0 +1,52 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +apply from: '../../constants.gradle' +apply plugin: 'com.android.library' + +android { + compileSdkVersion project.ext.compileSdkVersion + buildToolsVersion project.ext.buildToolsVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.targetSdkVersion + consumerProguardFiles 'proguard-rules.txt' + } +} + +dependencies { + implementation project(modulePrefix + 'extension-okhttp') + implementation 'com.android.support:support-annotations:' + supportLibraryVersion + compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion + api 'com.google.android.exoplayer:extension-okhttp:' + releaseVersion + testImplementation 'junit:junit:' + junitVersion + testImplementation project(modulePrefix + 'testutils-robolectric') +} + +ext { + javadocTitle = 'Shoutcast Metadata Protocol (ICY) extension' +} +apply from: '../../javadoc_library.gradle' + +ext { + releaseArtifact = 'extension-icy' + releaseDescription = 'Shoutcast Metadata Protocol (ICY) extension for ExoPlayer.' +} +apply from: '../../publish.gradle' diff --git a/extensions/icy/proguard-rules.txt b/extensions/icy/proguard-rules.txt new file mode 100644 index 0000000000..950555d877 --- /dev/null +++ b/extensions/icy/proguard-rules.txt @@ -0,0 +1,2 @@ +# Proguard rules specific to the Icy extension. + diff --git a/extensions/icy/src/main/AndroidManifest.xml b/extensions/icy/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..4f19052a68 --- /dev/null +++ b/extensions/icy/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + diff --git a/extensions/icy/src/main/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSource.java b/extensions/icy/src/main/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSource.java new file mode 100644 index 0000000000..c6728219c9 --- /dev/null +++ b/extensions/icy/src/main/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSource.java @@ -0,0 +1,387 @@ +package com.google.android.exoplayer2.ext.icy; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.util.Predicate; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import okhttp3.CacheControl; +import okhttp3.Call; + +/** + * https://cast.readme.io/v1.0/docs/icy http://www.smackfu.com/stuff/programming/shoutcast.html + */ +public final class IcyHttpDataSource extends OkHttpDataSource { + + private static final String TAG = IcyHttpDataSource.class.getSimpleName(); + + private static final String REQUEST_HEADER_ICY_METAINT_KEY = "Icy-MetaData"; + private static final String REQUEST_HEADER_ICY_METAINT_VALUE = "1"; + + private static final String RESPONSE_HEADER_ICY_BR_KEY = "icy-br"; + private static final String RESPONSE_HEADER_ICY_GENRE_KEY = "icy-genre"; + private static final String RESPONSE_HEADER_ICY_NAME_KEY = "icy-name"; + private static final String RESPONSE_HEADER_ICY_URL_KEY = "icy-url"; + private static final String RESPONSE_HEADER_ICY_PUB_KEY = "icy-pub"; + private static final String RESPONSE_HEADER_ICY_METAINT_KEY = "icy-metaint"; + + private static final String ICY_METADATA_STREAM_TITLE_KEY = "StreamTitle"; + private static final String ICY_METADATA_STREAM_URL_KEY = "StreamUrl"; + + private IcyHeadersListener icyHeadersListener; + private IcyMetadataListener icyMetadataListener; + private int metaDataIntervalInBytes = -1; + private int remainingStreamDataUntilMetaDataBlock = -1; + private DataSpec dataSpec; + + public interface IcyHeadersListener { + + void onIcyHeaders(IcyHeaders icyHeaders); + } + + public interface IcyMetadataListener { + + void onIcyMetaData(IcyMetadata icyMetadata); + } + + private IcyHttpDataSource( + @NonNull Call.Factory callFactory, + @Nullable final String userAgent, + @Nullable final Predicate contentTypePredicate, + @Nullable CacheControl cacheControl, + @NonNull RequestProperties defaultRequestProperties) { + super(callFactory, userAgent, contentTypePredicate, cacheControl, defaultRequestProperties); + defaultRequestProperties.set(REQUEST_HEADER_ICY_METAINT_KEY, REQUEST_HEADER_ICY_METAINT_VALUE); + + // See class Builder + } + + @Override + public long open(DataSpec dataSpec) throws HttpDataSourceException { + this.dataSpec = dataSpec; + long bytesToRead = super.open(dataSpec); + + Map> responseHeaders = getResponseHeaders(); + if (responseHeaders != null) { + IcyHeaders icyHeaders = new IcyHeaders(); + + Log.d(TAG, "open: responseHeaders=" + responseHeaders.toString()); + List headers = responseHeaders.get(RESPONSE_HEADER_ICY_BR_KEY); + if (headers != null && headers.size() == 1) { + icyHeaders.bitRate = Integer.parseInt(headers.get(0)); + } + headers = responseHeaders.get(RESPONSE_HEADER_ICY_GENRE_KEY); + if (headers != null && headers.size() == 1) { + icyHeaders.genre = headers.get(0); + } + headers = responseHeaders.get(RESPONSE_HEADER_ICY_NAME_KEY); + if (headers != null && headers.size() == 1) { + icyHeaders.name = headers.get(0); + } + headers = responseHeaders.get(RESPONSE_HEADER_ICY_URL_KEY); + if (headers != null && headers.size() == 1) { + icyHeaders.url = headers.get(0); + } + headers = responseHeaders.get(RESPONSE_HEADER_ICY_PUB_KEY); + if (headers != null && headers.size() == 1) { + icyHeaders.isPublic = headers.get(0).equals("1"); + } + headers = responseHeaders.get(RESPONSE_HEADER_ICY_METAINT_KEY); + if (headers != null && headers.size() == 1) { + metaDataIntervalInBytes = Integer.parseInt(headers.get(0)); + remainingStreamDataUntilMetaDataBlock = metaDataIntervalInBytes; + } + + if (icyHeadersListener != null) { + icyHeadersListener.onIcyHeaders(icyHeaders); + } + } + return bytesToRead; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException { + int bytesRead; + + // Only read metadata if the server declared to send it... + if (metaDataIntervalInBytes < 0) { + bytesRead = super.read(buffer, offset, readLength); + } else { + bytesRead = super.read(buffer, offset, + remainingStreamDataUntilMetaDataBlock < readLength ? remainingStreamDataUntilMetaDataBlock + : readLength); + if (remainingStreamDataUntilMetaDataBlock == bytesRead) { + parseIcyMetadata(); + } else { + remainingStreamDataUntilMetaDataBlock -= bytesRead; + } + } + return bytesRead; + } + + private void parseIcyMetadata() throws HttpDataSourceException { + // We hit the metadata block, reset stream data counter + remainingStreamDataUntilMetaDataBlock = metaDataIntervalInBytes; + + byte[] metaDataBuffer = new byte[1]; + int bytesRead = super.read(metaDataBuffer, 0, 1); + if (bytesRead != 1) { + throw new HttpDataSourceException("parseIcyMetadata: Unable to read metadata length!", + dataSpec, HttpDataSourceException.TYPE_READ); + } + int metaDataBlockSize = metaDataBuffer[0]; + if (metaDataBlockSize < 1) { // Either no metadata or end of file + return; + } + metaDataBlockSize <<= 4; // Multiply by 16 to get actual size + + if (metaDataBuffer.length < metaDataBlockSize) { + metaDataBuffer = new byte[metaDataBlockSize]; // Make room for the full metadata block + } + + // Read entire metadata block into buffer + int offset = 0; + int readLength = metaDataBlockSize; + while (readLength > 0 && (bytesRead = super.read(metaDataBuffer, offset, readLength)) != -1) { + offset += bytesRead; + readLength -= bytesRead; + } + metaDataBlockSize = offset; + + // We read the metadata from the stream. Only parse it when we have a listener registered + // to return the contents. + if (icyMetadataListener != null) { + // Find null-terminator + for (int i = 0; i < metaDataBlockSize; i++) { + if (metaDataBuffer[i] == 0) { + metaDataBlockSize = i; + break; + } + } + + try { + final String metaDataString = new String(metaDataBuffer, 0, metaDataBlockSize, "utf-8"); + icyMetadataListener.onIcyMetaData(parseMetadata(metaDataString)); + } catch (Exception e) { + Log.e(TAG, "parseIcyMetadata: Cannot convert bytes to String"); + } + } + } + + private IcyMetadata parseMetadata(final String metaDataString) { + String[] keyAndValuePairs = metaDataString.split(";"); + IcyMetadata icyMetadata = new IcyMetadata(); + + for (String keyValuePair : keyAndValuePairs) { + int equalSignPosition = keyValuePair.indexOf('='); + if (equalSignPosition < 1) { + continue; + } + + boolean isString = equalSignPosition + 1 < keyValuePair.length() + && keyValuePair.charAt(keyValuePair.length() - 1) == '\'' + && keyValuePair.charAt(equalSignPosition + 1) == '\''; + + String key = keyValuePair.substring(0, equalSignPosition); + String value = isString ? + keyValuePair.substring(equalSignPosition + 2, keyValuePair.length() - 1) : + equalSignPosition + 1 < keyValuePair.length() ? + keyValuePair.substring(equalSignPosition + 1) : ""; + + switch (key) { + case ICY_METADATA_STREAM_TITLE_KEY: + icyMetadata.streamTitle = value; + case ICY_METADATA_STREAM_URL_KEY: + icyMetadata.streamUrl = value; + } + + icyMetadata.metadata.put(key, value); + } + + return icyMetadata; + } + + public final static class Builder { + + private Call.Factory callFactory; + private String userAgent; + private Predicate contentTypePredicate; + private CacheControl cacheControl; + private RequestProperties defaultRequestProperties = new RequestProperties(); + private IcyHeadersListener icyHeadersListener; + private IcyMetadataListener icyMetadataListener; + + public Builder(@NonNull Call.Factory callFactory) { + this.callFactory = callFactory; + } + + public Builder setUserAgent(@NonNull final String userAgent) { + this.userAgent = userAgent; + return this; + } + + public Builder setContentTypePredicate(@NonNull final Predicate contentTypePredicate) { + this.contentTypePredicate = contentTypePredicate; + return this; + } + + public Builder setCacheControl(@NonNull final CacheControl cacheControl) { + this.cacheControl = cacheControl; + return this; + } + + public Builder setDefaultRequestProperties( + @NonNull final RequestProperties defaultRequestProperties) { + this.defaultRequestProperties = defaultRequestProperties; + return this; + } + + public Builder setIcyHeadersListener( + @NonNull final IcyHttpDataSource.IcyHeadersListener icyHeadersListener) { + this.icyHeadersListener = icyHeadersListener; + return this; + } + + public Builder setIcyMetadataListener(@NonNull final IcyMetadataListener icyMetadataListener) { + this.icyMetadataListener = icyMetadataListener; + return this; + } + + IcyHttpDataSource build() { + final IcyHttpDataSource dataSource = + new IcyHttpDataSource(callFactory, + userAgent, + contentTypePredicate, + cacheControl, + defaultRequestProperties); + dataSource.icyHeadersListener = icyHeadersListener; + dataSource.icyMetadataListener = icyMetadataListener; + return dataSource; + } + } + + /** + * Container for Icy headers such as stream genre or name. + */ + public final class IcyHeaders { + + /** + * icy-br Bit rate in KB/s + */ + int bitRate; + /** + * icy-genre + */ + String genre; + /** + * icy-name + */ + String name; + /** + * icy-url + */ + String url; + /** + * icy-pub + */ + boolean isPublic; + + /** + * @return The bit rate in kilobits per second (KB/s) + */ + public int getBitRate() { + return bitRate; + } + + /** + * @return The musical genre of the stream + */ + public String getGenre() { + return genre; + } + + /** + * @return The stream name + */ + public String getName() { + return name; + } + + /** + * @return The URL of the music stream (can be a website or artwork) + */ + public String getUrl() { + return url; + } + + /** + * @return Determines if this stream is public or listed in a catalog + */ + public boolean isPublic() { + return isPublic; + } + + @Override + public String toString() { + return "IcyHeaders{" + + "bitRate='" + bitRate + '\'' + + ", genre='" + genre + '\'' + + ", name='" + name + '\'' + + ", url='" + url + '\'' + + ", isPublic=" + isPublic + + '}'; + } + } + + /** + * Container for stream title and URL. + *

    + * The exact contents isn't specified and implementation specific. It's therefore up to the user + * to figure what format a given stream returns. + */ + public final class IcyMetadata { + + String streamTitle; + String streamUrl; + HashMap metadata = new HashMap<>(); + + /** + * @return The song title. + */ + public String getStreamTitle() { + return streamTitle; + } + + /** + * @return Url to album artwork or more information about the current song. + */ + public String getStreamUrl() { + return streamUrl; + } + + /** + * Provides a map of all stream metadata. + * + * @return Complete metadata + */ + public HashMap getMetadata() { + return metadata; + } + + @Override + public String toString() { + return "IcyMetadata{" + + "streamTitle='" + streamTitle + '\'' + + ", streamUrl='" + streamUrl + '\'' + + ", metadata='" + metadata + '\'' + + '}'; + } + } +} diff --git a/extensions/icy/src/main/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceFactory.java b/extensions/icy/src/main/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceFactory.java new file mode 100644 index 0000000000..e396405f44 --- /dev/null +++ b/extensions/icy/src/main/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceFactory.java @@ -0,0 +1,85 @@ +package com.google.android.exoplayer2.ext.icy; + +import android.support.annotation.NonNull; + +import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource; +import com.google.android.exoplayer2.upstream.HttpDataSource; +import com.google.android.exoplayer2.util.Predicate; + +import okhttp3.CacheControl; +import okhttp3.Call; + +/** + * A {@link HttpDataSource.Factory} that produces {@link IcyHttpDataSource} instances. + */ +public final class IcyHttpDataSourceFactory extends OkHttpDataSource.BaseFactory { + + private Call.Factory callFactory; + private String userAgent; + private Predicate contentTypePredicate; + private CacheControl cacheControl; + private IcyHttpDataSource.IcyHeadersListener icyHeadersListener; + private IcyHttpDataSource.IcyMetadataListener icyMetadataListener; + + private IcyHttpDataSourceFactory() { + // See class Builder + } + + /** + * Constructs a IcyHttpDataSourceFactory. + */ + public final static class Builder { + + private final IcyHttpDataSourceFactory factory; + + public Builder(@NonNull Call.Factory callFactory) { + // Apply defaults + factory = new IcyHttpDataSourceFactory(); + factory.callFactory = callFactory; + } + + public Builder setUserAgent(@NonNull final String userAgent) { + factory.userAgent = userAgent; + return this; + } + + public Builder setContentTypePredicate(@NonNull final Predicate contentTypePredicate) { + factory.contentTypePredicate = contentTypePredicate; + return this; + } + + public Builder setCacheControl(@NonNull final CacheControl cacheControl) { + factory.cacheControl = cacheControl; + return this; + } + + public Builder setIcyHeadersListener( + @NonNull final IcyHttpDataSource.IcyHeadersListener icyHeadersListener) { + factory.icyHeadersListener = icyHeadersListener; + return this; + } + + public Builder setIcyMetadataChangeListener( + @NonNull final IcyHttpDataSource.IcyMetadataListener icyMetadataListener) { + factory.icyMetadataListener = icyMetadataListener; + return this; + } + + public IcyHttpDataSourceFactory build() { + return factory; + } + } + + @Override + protected IcyHttpDataSource createDataSourceInternal( + @NonNull HttpDataSource.RequestProperties defaultRequestProperties) { + return new IcyHttpDataSource.Builder(callFactory) + .setUserAgent(userAgent) + .setContentTypePredicate(contentTypePredicate) + .setCacheControl(cacheControl) + .setDefaultRequestProperties(defaultRequestProperties) + .setIcyHeadersListener(icyHeadersListener) + .setIcyMetadataListener(icyMetadataListener) + .build(); + } +} diff --git a/extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceFactoryTest.java b/extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceFactoryTest.java new file mode 100644 index 0000000000..11f5e074d4 --- /dev/null +++ b/extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceFactoryTest.java @@ -0,0 +1,41 @@ +package com.google.android.exoplayer2.ext.icy; + +import com.google.android.exoplayer2.upstream.HttpDataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import okhttp3.OkHttpClient; +import com.google.android.exoplayer2.ext.icy.test.Constants; + +import static org.junit.Assert.assertNotNull; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public final class IcyHttpDataSourceFactoryTest { + + private final IcyHttpDataSource.IcyHeadersListener TEST_ICY_HEADERS_LISTENER = icyHeaders -> { + }; + private final IcyHttpDataSource.IcyMetadataListener TEST_ICY_METADATA_LISTENER = icyMetadata -> { + }; + + @Test + public void createDataSourceViaFactoryFromFactoryBuilder() { + // Arrange + OkHttpClient client = new OkHttpClient.Builder().build(); + IcyHttpDataSourceFactory factory = new IcyHttpDataSourceFactory.Builder(client) + .setUserAgent(Constants.TEST_USER_AGENT) + .setIcyHeadersListener(TEST_ICY_HEADERS_LISTENER) + .setIcyMetadataChangeListener(TEST_ICY_METADATA_LISTENER) + .build(); + HttpDataSource.RequestProperties requestProperties = new HttpDataSource.RequestProperties(); + + // Act + IcyHttpDataSource source = factory.createDataSourceInternal(requestProperties); + + // Assert + assertNotNull(source); + } +} diff --git a/extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceTest.java b/extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceTest.java new file mode 100644 index 0000000000..b06bd78e65 --- /dev/null +++ b/extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/IcyHttpDataSourceTest.java @@ -0,0 +1,29 @@ +package com.google.android.exoplayer2.ext.icy; + +import com.google.android.exoplayer2.ext.icy.IcyHttpDataSource; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import okhttp3.OkHttpClient; +import com.google.android.exoplayer2.ext.icy.test.Constants; + +import static org.junit.Assert.assertNotNull; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public final class IcyHttpDataSourceTest { + + @Test + public void createDataSourceFromBuilder() { + // Arrange, act + OkHttpClient client = new OkHttpClient.Builder().build(); + IcyHttpDataSource source = new IcyHttpDataSource.Builder(client) + .setUserAgent(Constants.TEST_USER_AGENT) + .build(); + + // Assert + assertNotNull(source); + } +} diff --git a/extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/test/Constants.java b/extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/test/Constants.java new file mode 100644 index 0000000000..6dec462829 --- /dev/null +++ b/extensions/icy/src/test/java/com/google/android/exoplayer2/ext/icy/test/Constants.java @@ -0,0 +1,6 @@ +package com.google.android.exoplayer2.ext.icy.test; + +public final class Constants { + + public static final String TEST_USER_AGENT = "test-agent"; +} From 535b40539ad1c94ab98bfca73eb0d8b83aea83ad Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Mon, 22 Oct 2018 23:40:25 +0200 Subject: [PATCH 098/832] Add ICY demo application --- build.gradle | 1 + demos/icy/README.md | 4 + demos/icy/build.gradle | 62 +++++++ demos/icy/src/main/AndroidManifest.xml | 42 +++++ .../exoplayer2/icydemo/MainActivity.kt | 160 +++++++++++++++++ .../drawable-v24/ic_launcher_foreground.xml | 34 ++++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++++ .../res/drawable/ic_play_arrow_black_24dp.xml | 9 + .../main/res/drawable/ic_stop_black_24dp.xml | 9 + .../icy/src/main/res/layout/activity_main.xml | 47 +++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes demos/icy/src/main/res/values/colors.xml | 8 + demos/icy/src/main/res/values/strings.xml | 6 + demos/icy/src/main/res/values/styles.xml | 11 ++ settings.gradle | 2 + 26 files changed, 575 insertions(+) create mode 100644 demos/icy/README.md create mode 100644 demos/icy/build.gradle create mode 100644 demos/icy/src/main/AndroidManifest.xml create mode 100644 demos/icy/src/main/java/com/google/android/exoplayer2/icydemo/MainActivity.kt create mode 100644 demos/icy/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 demos/icy/src/main/res/drawable/ic_launcher_background.xml create mode 100644 demos/icy/src/main/res/drawable/ic_play_arrow_black_24dp.xml create mode 100644 demos/icy/src/main/res/drawable/ic_stop_black_24dp.xml create mode 100644 demos/icy/src/main/res/layout/activity_main.xml create mode 100644 demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 demos/icy/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 demos/icy/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 demos/icy/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 demos/icy/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 demos/icy/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 demos/icy/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 demos/icy/src/main/res/values/colors.xml create mode 100644 demos/icy/src/main/res/values/strings.xml create mode 100644 demos/icy/src/main/res/values/styles.xml diff --git a/build.gradle b/build.gradle index a013f4fb84..f4eb7704a2 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,7 @@ buildscript { classpath 'com.android.tools.build:gradle:3.1.4' classpath 'com.novoda:bintray-release:0.8.1' classpath 'com.google.android.gms:strict-version-matcher-plugin:1.0.3' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.71' } // Workaround for the following test coverage issue. Remove when fixed: // https://code.google.com/p/android/issues/detail?id=226070 diff --git a/demos/icy/README.md b/demos/icy/README.md new file mode 100644 index 0000000000..77a54098e2 --- /dev/null +++ b/demos/icy/README.md @@ -0,0 +1,4 @@ +# ICY demo application # + +This folder contains a demo application that showcases the ExoPlayer Shoutcast +Metadata (ICY) extension. diff --git a/demos/icy/build.gradle b/demos/icy/build.gradle new file mode 100644 index 0000000000..9ae88c445e --- /dev/null +++ b/demos/icy/build.gradle @@ -0,0 +1,62 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +apply from: '../../constants.gradle' +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion project.ext.compileSdkVersion + buildToolsVersion project.ext.buildToolsVersion + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + + defaultConfig { + versionName project.ext.releaseVersion + versionCode project.ext.releaseVersionCode + minSdkVersion 23 + targetSdkVersion project.ext.targetSdkVersion + } + + buildTypes { + release { + shrinkResources true + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt') + } + debug { + jniDebuggable = true + } + } + + lintOptions { + // The demo app does not have translations. + disable 'MissingTranslation' + } +} + +dependencies { + implementation project(modulePrefix + 'extension-icy') + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:design:28.0.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.26.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.26.1' +} + +apply plugin: 'com.google.android.gms.strict-version-matcher-plugin' \ No newline at end of file diff --git a/demos/icy/src/main/AndroidManifest.xml b/demos/icy/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..69d64dbf96 --- /dev/null +++ b/demos/icy/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demos/icy/src/main/java/com/google/android/exoplayer2/icydemo/MainActivity.kt b/demos/icy/src/main/java/com/google/android/exoplayer2/icydemo/MainActivity.kt new file mode 100644 index 0000000000..643ee84ed7 --- /dev/null +++ b/demos/icy/src/main/java/com/google/android/exoplayer2/icydemo/MainActivity.kt @@ -0,0 +1,160 @@ +package com.google.android.exoplayer2.icydemo + +import android.net.Uri +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.util.Log +import com.google.android.exoplayer2.* +import com.google.android.exoplayer2.audio.AudioAttributes +import com.google.android.exoplayer2.ext.icy.IcyHttpDataSourceFactory +import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory +import com.google.android.exoplayer2.source.ExtractorMediaSource +import com.google.android.exoplayer2.source.TrackGroupArray +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector +import com.google.android.exoplayer2.trackselection.TrackSelectionArray +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory +import com.google.android.exoplayer2.util.Util +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.coroutines.experimental.CoroutineStart +import kotlinx.coroutines.experimental.Dispatchers +import kotlinx.coroutines.experimental.GlobalScope +import kotlinx.coroutines.experimental.async +import okhttp3.OkHttpClient + +/** + * Test application, doesn't necessarily show the best way to do things. + */ +class MainActivity : AppCompatActivity() { + private var exoPlayer: SimpleExoPlayer? = null + private val exoPlayerEventListener = ExoPlayerEventListener() + private lateinit var userAgent: String + private var isPlaying = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + stream.setText(DEFAULT_STREAM) + + userAgent = Util.getUserAgent(applicationContext, applicationContext.getString(R.string.app_name)) + + play_pause.setOnClickListener { + if (isPlaying) { + stop() + play_pause.setImageDrawable(resources.getDrawable(R.drawable.ic_play_arrow_black_24dp, null)) + } else { + play() + play_pause.setImageDrawable(resources.getDrawable(R.drawable.ic_stop_black_24dp, null)) + } + } + } + + private fun play() { + GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT, null, { + if (exoPlayer == null) { + exoPlayer = ExoPlayerFactory.newSimpleInstance(applicationContext, + DefaultRenderersFactory(applicationContext), + DefaultTrackSelector(), + DefaultLoadControl() + ) + exoPlayer?.addListener(exoPlayerEventListener) + } + + val audioAttributes = AudioAttributes.Builder() + .setContentType(C.CONTENT_TYPE_MUSIC) + .setUsage(C.USAGE_MEDIA) + .build() + exoPlayer?.audioAttributes = audioAttributes + + // Custom HTTP data source factory which requests Icy metadata and parses it if + // the stream server supports it + val client = OkHttpClient.Builder().build() + val icyHttpDataSourceFactory = IcyHttpDataSourceFactory.Builder(client) + .setUserAgent(userAgent) + .setIcyHeadersListener { icyHeaders -> + Log.d(TAG, "onIcyMetaData: icyHeaders=$icyHeaders") + } + .setIcyMetadataChangeListener { icyMetadata -> + Log.d(TAG, "onIcyMetaData: icyMetadata=$icyMetadata") + } + .build() + + // Produces DataSource instances through which media data is loaded + val dataSourceFactory = DefaultDataSourceFactory( + applicationContext, null, icyHttpDataSourceFactory + ) + // Produces Extractor instances for parsing the media data + val extractorsFactory = DefaultExtractorsFactory() + + // The MediaSource represents the media to be played + val mediaSource = ExtractorMediaSource.Factory(dataSourceFactory) + .setExtractorsFactory(extractorsFactory) + .createMediaSource(Uri.parse(stream.text.toString())) + + // Prepares media to play (happens on background thread) and triggers + // {@code onPlayerStateChanged} callback when the stream is ready to play + exoPlayer?.prepare(mediaSource) + }) + } + + private fun stop() { + releaseResources(true) + isPlaying = false + } + + private fun releaseResources(releasePlayer: Boolean) { + Log.d(TAG, "releaseResources: releasePlayer=$releasePlayer") + + // Stops and releases player (if requested and available). + if (releasePlayer && exoPlayer != null) { + exoPlayer?.release() + exoPlayer?.removeListener(exoPlayerEventListener) + exoPlayer = null + } + } + + private inner class ExoPlayerEventListener : Player.EventListener { + override fun onTimelineChanged(timeline: Timeline, manifest: Any?, reason: Int) { + } + + override fun onTracksChanged(trackGroups: TrackGroupArray, trackSelections: TrackSelectionArray) { + } + + override fun onLoadingChanged(isLoading: Boolean) { + } + + override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { + Log.i(TAG, "onPlayerStateChanged: playWhenReady=$playWhenReady, playbackState=$playbackState") + when (playbackState) { + Player.STATE_IDLE, Player.STATE_BUFFERING, Player.STATE_READY -> + isPlaying = true + Player.STATE_ENDED -> + stop() + } + } + + override fun onPlayerError(error: ExoPlaybackException) { + Log.e(TAG, "onPlayerStateChanged: error=$error") + } + + override fun onPositionDiscontinuity(reason: Int) { + } + + override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) { + } + + override fun onSeekProcessed() { + } + + override fun onRepeatModeChanged(repeatMode: Int) { + } + + override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) { + } + } + + companion object { + private const val TAG = "MainActivity" + private const val DEFAULT_STREAM = "http://ice1.somafm.com/indiepop-128-mp3" + } +} diff --git a/demos/icy/src/main/res/drawable-v24/ic_launcher_foreground.xml b/demos/icy/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000..1f6bb29060 --- /dev/null +++ b/demos/icy/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/demos/icy/src/main/res/drawable/ic_launcher_background.xml b/demos/icy/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..0d025f9bf6 --- /dev/null +++ b/demos/icy/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/icy/src/main/res/drawable/ic_play_arrow_black_24dp.xml b/demos/icy/src/main/res/drawable/ic_play_arrow_black_24dp.xml new file mode 100644 index 0000000000..bf9b895aca --- /dev/null +++ b/demos/icy/src/main/res/drawable/ic_play_arrow_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/demos/icy/src/main/res/drawable/ic_stop_black_24dp.xml b/demos/icy/src/main/res/drawable/ic_stop_black_24dp.xml new file mode 100644 index 0000000000..c428d728dd --- /dev/null +++ b/demos/icy/src/main/res/drawable/ic_stop_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/demos/icy/src/main/res/layout/activity_main.xml b/demos/icy/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..14f3598479 --- /dev/null +++ b/demos/icy/src/main/res/layout/activity_main.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + diff --git a/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..eca70cfe52 --- /dev/null +++ b/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000..eca70cfe52 --- /dev/null +++ b/demos/icy/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/demos/icy/src/main/res/mipmap-hdpi/ic_launcher.png b/demos/icy/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..898f3ed59ac9f3248734a00e5902736c9367d455 GIT binary patch literal 2963 zcmV;E3vBd>P)a+K}1d8+^p? z!e{m!F(8(%L-Or7x3OYORF&;mRAm8a^;km%J=s!AdNyc=+ezQqUM;oHYO18U%`T}O zHf$ra^L^sklEoIeAKmbOvX~v2@Y|vHs<^3JwwH?D$4l*XnPNs zMOqozmbkT?^lZ?$DjQ9%E0x+GsV=1PwZ&39Y}iI-$Fb3d%nsk+qrN@cV=OmQMEdF% z)iHMl(4Yu=cIkixWXtwMIV=>BvDSrHg8?)+vLJKozy*}$iE>&gGGonlG0cJhG&DRv ztzkg-AO(q)B7~G^EwE#tK@nqmJ}!(Bqtf z=eN{I?X#P!Xx=uL)D9cAk=b!~&@H~6S)=a?R4fDdP{-5E5X_!5&FwFJ^7&W2WS z;CnxBCOsSU^v-%(vad;MPukr;&+ciI+F`>sGCPiqHe`1A1|N0p^<|#<+iECwOG@y7 zBF$;;0YAhxtqK7O0SW;M0SW;ckbsQ#9QTYyC*g`2j%bA%1Zh^g9=9l*Cy!I^{_p2$PP2>j_D2AybM$NwY}iJ(ZH9O3 zlM8g4+dw;}V{dlY2EM^Z-Q(AmcmO|Ub1&3EFTS>iuHC#rcNo$wkB3@5c#lSunxsQ) zaA7tLFV3Oxk}X2`9qVL6?4fcq?f>Yk0E0IEcm0~^P5ovLLV$&D9ibbZTOt4ivg_<= zu^#q8tYJktl(egXwj4c3u6N&}S3mj_9pv5y{gQvL;&nM}TeNE{4K3O%_QAdpCAswa z`Ev>!oQREY9uPqL)g(QPVc1U`Q3An`+x_7g8edZ^0zdcpXNv7^!ZsgV{ugB){w+5&3-Wlp}yI7?tN)6*ST)-XSL4g8_rtDVlw+a zE+K|#(tV!KfQE22d-}7B(mLkHukIp4?na@q?%@4Kb%u!@F-ww?o?tn_Ohb zPi3Do`yL?Y$rDPYtEV;|250yzpS^rZT*TflAZ&YqC;by2Ul7NTZHKmC)9NA6Vv+>C%^1XhNlp5*!7zxTTKfHTPhe?@XbH=VzWEuCcmX z@L_&qCB;=(Xi;-D&DvT)kGOiMQ0&YQTezdH&j4D;U@#9&WiZClJThS7w)OHH^fIT| z+jn{&5bhMbynmM$P<0U*%ksp0WUy)=J!n9~WJ&YNn$e3{jMFOW6n~uqMHg+M3FY|#>(q)ZF;RS(xqTh>S1Ez_jfFig z#ivbPnZ26mv{5wdB5SFYrUNM5D?g-OsiZZK?hPof9gqf&7m!5-C=d>yOsw<)(t*G@h5zIY2saaEx|99pU%^#gvdI(Qqf>)zFjf zN}5zm9~oT`PmH~EF012{9eT8?4piYolF(86uiGy`^r#V4yu7SA-c zjm})#d$(Kx2|Yn~i19Fr<)Gs+1XaUIJs~G>kg>3 zkQ$CqUj*cb1ORzHKmZ`Ab2^0!}Qkq&-DC(S~W*1GV zw9}L-zX}y4ZLblxEO1qhqE9Q-IY{NmR+w+RDpB;$@R(PRjCP|D$yJ+BvI$!mIbb<+GQ3MGKxUdIY{N`DOv%} zWA){tEw8M2f!r&ugC6C5AMVXM=w7ej#c_{G;Obab=fD={ut@71RLCd*b?Y1+R_HMR zqYNuWxFqU^Yq9YB)SmxVgNKR;UMH207l5qNItP~xUO*YTsayf1g`)yAJoRV6f2$Fh z|A1cNgyW)@1ZJ!8eBC7gN$MOgAgg|zqX4pYgkw{E4wcr09u#3tt$JW@xgr2dT0piE zfSguooznr3CR>T88cu6RII0io!Z)mN2S3C%toVr+P`0PTJ>8yo4OoHX161h;q+jRY zs$2o2lgirxY2o-j$>c;3w)BT<1fb;PVV(V`cL*zHj5+On;kX@;0)6rF-I?1)gyZtM6}?#ji{u+_Jz`IW9a=87nIA3aK2~3iFMS zzYP&fCXLEibCzR_6R~#sKN@)HB>);Za`ud*QCaKG8jEwqgoknK7rwW`Cq?RYYE5r+ zh-YUqJ082>*;EG`_lhV^vHEM7d+5Y#e$d^rC*jx{U%h3B^nU%7N|*y`o4g{@w;KP-89>&W#h zTBB2vTk*S|My+4jYTPKdk6yR3b?nAfcd`FeC@gttYuGBEl9wuf8`rOD9VP6`bhNxR znvXql-3ssVUSXfvcf^2L5R-^4E-s=g|M$Wm!?BMl!51d{AS*7Ggjwh^YsbK?6jgCA5T=(9$oK{{z$fCe9x5IJ^J=002ov JPDHLkV1g@XpTGbB literal 0 HcmV?d00001 diff --git a/demos/icy/src/main/res/mipmap-hdpi/ic_launcher_round.png b/demos/icy/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..dffca3601eba7bf5f409bdd520820e2eb5122c75 GIT binary patch literal 4905 zcmV+^6V~jBP)sCJ+Khgs=qzz9*aFfTF@MBLc!81jy1$_D*`qMnYCeSOOSS zh~l6kD7e75FgOnvP=_arGNJ+k0uBt2?%a3It*Y+o?&`L?*#fV=?@xECZq+^KuXD~l z_tdQ>JOSF%q}x5h@>Id>gloHZ!fr_@%N)Qad* zI}<}@Poh`#X29>b50CkB%{yWf?z(t0rQf48W{j1a($$IrZ9{N{@#9Wqx}%DM^fL-m z`X#_s9{BwX>^};}KMtudHpmMyRCq34!+|XCtnqeli6}6}7JiE;H+GAtDViHuQ~X9` zP0^{y>Ov~ufreT-w7!yx_c;QOV>|0UxJK{lqSx`7cx`b!OLV*;Ez4q9Y_XdB$PKk4 z+Aq(kmz%WbOV3IpYsa0#_Vd?)>*2Lc zn) zvVw}USbx|rlL2LMl<$^rb@TnK-;J83fd3GKh6#=C5WlXv83lKz{0$(8x1g-%;q}$b z1=&8M<_eQZO4eJk#nshu9TsZZ11Z~hVkpt8oA4831ZP3Fj3C~EG*%gSnciYD-cpkI zj{J=o1Bg-kJrjfz${Js8D?vh>vJwR{=4)c@ZtTqt#tHRR<9b9ew~kVG6oc8(lNE=Pu>)F6HIf=`kIH3oJBkSO2;+SnG--LDU5kx zC0($63w`LN)znoR#GhW@M5n&8!EGBnj_usF!G5qm>{qhQ`sdB#K+CoQF7f-se z?#7!W#vF7jw48A-)Ulxz@0b)?7iKWQI+fE6Ud#Le4H#? z*wIeM>mtaY-X;WO^yfR4Adp*W)N+A4Yv~TqOy)a5g8AjAEfJ4acRWELKhbNNKrc!( z&!ze1YQkhsw=A3()t7B^pu2=1)CJq>k}s1bv-{fV>=i+J^=8Lh=Pn_L(@77X+QqLi zSM!u0YfVL$I)-o^+D$g^8iKevTQlfM$k z8A}@MLX0cd>SIdp0%mtcJaTy&g94$WW9QB?a!}a+T)Rd$eDM!(fgHCnNCsx!svv{S z@9-MjC~sfoKOK+dN>{)_sV(mjhof{qxwvX-7Df1DQTI(g)o z>s6XRhgIhE&g6I!q!Sxz>EW}#SnudH5WeBSekYPp`9~Vp)1-G^r@B46=-SWs(Z;X8 z02evPKG%G)Nf*Dpl|HNSeWdw0`U#|(mpohWGktDRF;Bo`A2K9T}=|{(p(X*E>(aYDag2maC6ay^+ zk7K(%-yfyPJKv6-`qy{#2oNV$%o|*T^A7!TivIn?ahqEKj{ka& z1#*R?@}3aHxtTmO=~U-w(|Xu(B2EmI8B50EvnOk9*GGbcJZK_}E{D#X@`(&j@%hg` zvgc+#V--FuV!3MbUy#-AgE($~;1gULUsw`94gkTgN-nwH+_TiyxD=9t>#{5GHSR=+VC|3HUj>p$m zF=5TOh#WCVpZxG0Mfs)VLU~bclwVS}a)Tud>)$I3M@i?-ZEb;CNQ$OT?W!i>WPgI2K-%bDAV3iV{YFpxIA_D~#F;z7mA_2ToA0 zz;J#$$gz?H{f~tykIYwsN^&ofDHEcc3HtMs_ksmo_H~%=S!trXzdzzq@XJ@P(yd>A zNh?17fF3z>nk9kWDu3|gPt>$~7yTPdOfi9U)o%B9hiOkpO1&hgnGv)+?=lcH(3zlF z)1$73Anp4*+{T@4Fog)rOQR%n2^~~bNRNp!ZBKCK-@noL+ER9Y8^~8Se*UT3c%b7TLtsqf14?X2rJH|pTWGz8-n&h;14Ov z#z`fWWiO*ed){^1em`8ly%A*0PxH#fdX?ndqyYz250dgaflgvo+ zJV{-K7`Kl9diHm3hJcly zengd6QU#LyA&GQLke(wb%#d-6v?HDD3F1f!>{yWg5#|xN?9J0WD7v z;l~T-X%q||!6msgyeyyoVe>kdc~D4&(TwHYfu@{&z(qUzHQHR6u}wE)#*5x&(o-7O zw@7jXJiKu=?N?bq2i6qRnT;Fhz}ixmnKagt?l)w-)BzP^3@k~*Wp97@gTqNpbZPR zy$S@S*a*rO5riY0Ud8DORwP?Adna(v!QOi8<4{14v_(t!#gLwrT(JX4+=L_$A%|pc zXmt?{(xut$cSLlVo(30Y+4jMCjtGY2uwS_m`dG?inGHD{f(#luthNkXB!$a+a>Yn- zK~O4(yi`tCXd{2}Q7v*n=1Z+W<4npgXvmO$@_f~4uO9n2kmNBzD-1S*B*<|l$eA1@ z#7YnNRI?n@&u)dVc}PLoFRSt;=(FF*KZU}pY9KTJIT}LH;AkK9+f+gq?~2G z5#)j#B*jLMG&xp+>KqBOk%JavBS>X$J^3kS)@II(S5WsDjsv%=Is#fvo%C=}VJ79C zu4XlR`eZez2+jdtZkwl~W8jW?O+mCNa{m8IZH0?IgmNQbXlLF4NHs~k~IN5KqX9?a!NuC1W) zYsz_4m;p2B(rNZ|bq7KTK$6gs(A^{fuF@Y|C$u<+ zeYYY3Gn!;AyU4%y;QbOj@OvR}OAX~1e60jYkYi7fGch)Tw9J(lK@#LJf(#;pbZHir zB&II7NTQ;~GF=lByQEr3##lyCO%LAbWBIf<~=H3(^R#^&aTfo7d6DH>o+Z>qt5T4kD_BN0|i~wM{;) zQDk{ivKxY=^BgNdF34d7nZyJ+lfx0Dp`+JSH331CES`Ogv=4}5y2Zs^=PLgRUr*8)xq~v8}M$U zLOie%h{Y~;4ui@DJqJtzG0(xF97ij3CmS@3983s@mls%CJveFs=+cwd>4yDCfvm&e z!5#1cb>BZeo;3I6^_Foju7YH-rfKy08n55>!E;8!9e--mI{HXM9UTG5-bio}4&^qi zE~isoTuo;*ZeZWBo`Vxk8!8zvL!O6k1VIoUEds_IbStzRBxm^3Gm}w=_OY=YZzMUw zCMRKGc;U#1X^+ec$Xs%Pdmk&k3F4CX?~8#O4uI@BY`Kmq!J0Uv+5@a9tSpblLOV))hr-m%u%E*xX4>hBnb`e#B{kyo18?4;4dFUw7M^53Rybu z824~aV-c4}JY7hR>xV*sAg3fy6mLS7LnaNbD2_RfLpjc^aO!{=GM5BGo|C6yB@D9o z>0^ok{idSKZKI>_xtZixNop4pgLk193Gf?Ao}Iaq1y@!>f+5tPYW8ZSJw77VrMS#< zkU%RzE|Nf;cya`#HnR*FQxeQ`<~;c>Y2!DH$r^KWEyp=Wij2g!i9-MbcG4!}i^_bU5@kB8)I8_7rlg4C4#@0J#r1#qtCFoLQJrO9E% zt`s&x4TB&q*Dj{y&(q&hhKJ${y!SHMP)2fle^N(DLRef11H>ps$3G)mFl*0{%0f#} zK?dh~_$b?`;>l7qyL_2N&lj^qc}_^Fh@jk*X2^mq@ZAj7%2fh^%)qQAA zZ3@z-Q#;=6kf<1C_wHkrQ^se@o}KxQJaxedR`bDn4a5ufwojD_f5pWfSc3vWaa8IF z!+Z?HAa-6lxNq{aCuDPGysez_-`RL=-eMvHI(P2D`bHVO)$w1e0^WP&R`mBpOFQKR>_w07I2s zIwmM1dOoD+-D@HOzvDhQc0abkw){E0*){N5cul3$g6n-PcZs4>q4bV;KlnN~%kbn}!V8maBKN?~PDN77Zj6xT>KxccMrJYVYoo)adu8>W% zmv*U9KCo@D{=sCEstjFGl{%?R9Bd_S;`C@G{FNG~X;+5Z0h*dJ1r|5g4wB8=?S#Zy zt3sAsXM@aL)nWAyCYz08&uXYp$}38nkeVvA0^C`|ts22ve2Y2>mf~J~_Til&y|FUz z%#l)O^+i>bDr7NsoiC}@GN^5^{=sAkPSF?VF#7ysBZm@DnF?;le_~|Un-B}Itc2u|IlX``0V1M3jKlcCTY73+_+5_^1 zO|_7<%PEyPhbqxCEnFv#uom}FdO$lY%`OKi#h<5Co8ZPBFZA{I!|wAx!c?aisEfxs z?T$*AUTc9D8_Hpt%L37MoudCVml+QIa-Q{X>F$I{4t=051yd2KXJy7g2ho;dPy9%m z&|3%hK)bgG?)N=_y3^l5BAU(HpEX16sc+%jjdr-wd5e*w`^js6LDPj(u<}q7%axih zoQB@MKIp*y%l0*noe!-3>L8Nvz`X|#;P=}%;m-Yg;Pd%Hg6jXkc0~S4=WWP7_Qlvb zG1>9)E0=~O9SWcSdXd@th$;|?3QV+Z@1bR;tdb%M2ko%(GTA+u#e@F7$5Mb+;mB`4 z!xVgv{Jp95%Y!hpT7-)jrQ~&IJFY@h`L?H{0L^~?0CJaZ z{tZjr)sT1m=#VQw^-Fg;S$l@ofMbuY0uykS+-JWJI=h~`ci}FY$50ATJ+%wA zO77DqVS>075^y6_kJfo$5r(}BH#(lkaYNw(n&Hbh&XQd-lYhgIk-UdHhZ4HzOR6cX9O(7$kLq}D}u9EB; z-dhHFDZZ<8Lc2GP(}(AKLrJ-Oau&a1s?6Nk^&FO z6KSRZhEqx_SQs6S0+Eca!Fb^G1gONmI zC+HbyhfVOuc?OI&h7uoNn}=`c_>iW5NO1q-GUX8K1^!Zxzl z4XfveR)GIBSo>}=cI+IH9~|U>#(X~teA-&84{aZTo0BMk;yjBqEL^gX=_9kDnP=}a z`+sm4^17nldnZj&U`51GznG$gf}Fz|OlbvM2~cNtN6bbO;LjW>4doDpXIHr_#-WEK zTp3oTSyarnG|L?64R(Lh#u7IM@+CF;0?j-dAKR%u-gp$bMThf`Y=V%QniZFqb4;b% z+^sU^c~$y+58W}2ds$fqbXadxS)oD}YcBF8+Kmro`dqK7bh9_jZo>N(2|7ZqH?6u% zs@LZQps|*E)s_+u&N{X0R(-hsYauy#KI0bVpUP;&tcc8vw<4D;UKP1mLj0?AU!cHb ztdAKWi}A~qZL?OzGg+1b@q^keUNsrViJ`HuE@E!RO5*b9*&nDxR@U?Q6pMIaj1kMY qJl2nQa+aK&iDQb84*TpHAJ>1BQ$$nT?9A!_0000+Hy9+Dw zQlg?UKB$_cZ8RBMYcyI%jkQf{#wz1Xr!PxQ>w~B~cKP~!=iIw{_rdOp7tZhwZ1+g(AXy-HL10DFmbXNx@L~ z3H0wQYEpsnp{iIyzhEeKgc((i$;}oAoqHl}Yb`&gx~}ISy|wl# zwdwQ;nvEgzkAnwYj%g}=Nide26RJwsNTUEE)Q2P-5}7cQ3Z84R%7rdvN4sQKhOlPcRnSrOp+WGP}nNJgfkDx!pMkypKGe90p51ezT#4MxAxQ zN3CC+fuRy0nP8u@+)%h}@FHZ>vWFTTCD?*bPf|6Oz4#LAYDsH*sO<_ z+8Vve2|wE19JrkK!TNc*tzkb>2=OxIfDS8-yiLEA$m0k(kQf0ZJlj+Q&+pg*@-o6x zTdEi#&vL>m?`;jX+>v0bbWnM`S<~tiA>-z6^m&Xo6y=iH&}dMDp40vqOvn?CbR0P3 z0YX_`z8klIalWefMaf}lN@-MvK>)C@OTMQsvEFV1j6zbmglN3)tDNw{&IYft@#yp|U;GYg&z^)Rt7d@u#0Bpe zimnOEmq&Tef~aWH7SjqERa#-iBMX%jZKUfNcy71bp|`IOKD_d0nA~D<-XkQV*jewl zx|K$GjP@M*^t)>e04FWS7-Uwy|!6q{ICob5gfvYaErq&g;Btk^VqnotOu zSN-|V;a*P<^rDbv9KD!YExR|ex)jop)as*$VeKa$K-3I_~rZ#$8n0D;V;;rwan!I2{& zEnl34toAlI^wpPe zlye)Ao4ycY%W~JdLaI0e(MHvF%G1SkH=uyAXf{=!ABS!n#lZ@o8CZ4XFmw8#1n{&R zVs(YP+3GCIkwRjs%TCiYQa(?iP=b^m$jib}=-N*{ggXx&44S-zukU>W+LOO#ZOZ!~ zOnukpUM6x&FsRNVXIChVTfbhB(rD_SHz|4}839cXjAmbiVtspfigR#uEFjIMj@si>Ore+Oei$<1cCarcfF2@0*j682U1A9rp; zlE=d6(}XYz#@Cd03QHCwxdi0=G&$N_{=Yy1XfbK~!v(L-Fa7gxu<_$VaOSVq1CpmY z8$Ujb&-~r%UfZSfpfHyQ7GTlb5>~#R>JqSaSxPVhD7~ea?b-3_j}BnQxCvh0zmvuF zfymQ6C7Oj$o(rpg(e8EsF8b6fI~#$e4S@tKotNPf@Ro97lv&dmNB}MOzKDHx{Td^7 z^e>kK&H&X>w(nxk__|+v<^;uhpfq|w0oCgN2n*&Uy98ur#zdLa9sUH2!{g=78$;%} z1L1P#zaX{-%}ARM>G(3`OF*1abzPV`HC~?1g-^B_&(OXN<=~`T0!1J)ouwb`hnx4h z9=m{>-*my^gYQ9FLp5Z*znzJYxJcY)*bL{8bEG_x3mc;?*yV2q=Kg#a+Xvy`pEue zJ2#<55|A&7Ku(lOR2IUxb#E82l~|riL@t>>J=|1!XP{(Gfq7D*RSSuh3Wmux1H9O5 zbzVzIvg#nSb+dS_bpfB9xub!%!Jvc0T8>$5O?a$?#5xXzQ6&nfaS6~B@Yl=oyt`5J zUi|^Lo>^h?bXpN!k$b{#I*o}Gg+L0KqjiNap+>{bdB$Wh1B{gdNt&z zkU*wl;*p0Tp96`fH`Pew34JvBLf)EFl)AaU3W$CXzIJ5}*_hmnyplOlgkJ%5dN1-^ zfYFOQ7f|g*o(nK@@|F3Nh4!=hOBWWfJjm^}QhYrdl{|g|c5+Shdb>Od$s<#GvjwI% znqg*ZJ*3tdIBXmlNOJbhCP>{}#ZfQ82y=FCgS0Is7aB~A{A+vOWk<4kG8-CsBA>N) z2Ro)Vo9)zRim|LCBI$`F-!JxDQG~E+nVNaMkGbGoHB3M|cbfqm?Jyjr6ln%D z61dqAY5B-YX2WN|HS&_#uo&dO1ZLdVcx6-*l>@yGiUd^twKIQ z1myy3dN1;B0z4enBibGcLp_=&v^1A84wc`CetouQG9=$!N7f##SDg2(;-$ z`!;UT3E!5cpgGLm)#4Fpf{Qj}^JF&E4%N%lmmNV4&oVB`hy6ytSLkp=a!l^3{cMD2 zTZ1ifMFW4}K)*?$c>mDR24g)rEZIEGUiM-d`ALieTX6^VNp)73C?Y9z`9d?=c(?d1 zs~_K-`cOc>&%IHK9z-;#Xp`TMv(d*wB}E%mPIu_y`4;N)(a6iqDI;Sfv%{G`Tq?Y? z`XY5qua{3ZRrAk6vM-O$&0Shch^Vh+#oUI{16*NgkrFgmFX!!x!YeN2Yr^QVW|_o)XG(ZcBN)a|R?) zB#;P8w$4loZCthCwyD)Kv~>DA|AHfFa+EnB3aXYkonv5irz&0+e_1c`|f ziIC%^3DMCrgrvlo!j#n640IkHIfLEfbrQs9Mtu8!_VBgvQKZl*M~Z$T%?|zlVT_2; lV%Z2*hu);6rydA(}wUDXPCF_W1vnaRBK zeoR6LNsxyaZGA2++G?*?dRwg0Dq5+E#aFEgnub(`IsNLD^CGWJ)s74L)DOcaT_gD&woh@MDDT7paS^E*rkp>8F->o#K*x;hPkb-{g{@G1-RXg&d5PhrJUf$gT>-Kc2+T~(?$>*Yu zT4h`0W>J$pZ%Azsi;{nVW%G=At*)awy8+_t6`#e`RGh(2zZ43)n*13}cE8;I5R%*` z|5tXk`=>gMs>q*$@(4m8?`JI1Q?{ zRHAd+JgRmHP9yV))rP7q3IO??4XSoJ$5!Su*=~JDub(K$fM<8yf*a-K*Qz zPelO^(`|+V_|-0Wk_vz*qdO0>?1mS)wM$Y29FC;)bEP-uAW0uG0ct9EO#m6#%K0RZ z39?+K6Wk5gE*|+^5I8uFyX{ALNYa2Nz%T`Hn@(}pU9*C57Xtylz}>iUsV2Z#2;ejg zaNoZ2a>iW@1kiDtzFVLPa8^~&DQ^ARm5e)008Ic*fO8jsh19y~Ki*W3-Qpae2p0nv zo(NXL_4n_CukY&uHM^BPt?*wD_pyjn&Gy=Rcfp3fUR68tMLx;5n(a64-U;9T#U52V zit5Q{QE!`~T|s99zY=X$w0cfmaNYW#0DU9B1CnnlE=a4Z9-s@!Y^>p_bSr_8-_-*O#n>*O#n>*O#n>*O#n@Ra~B|fQ*l9(%QQf9xcJEvaY~>ll!7d& zeMy*!>i>NLUU=_aXnXb`eD~hF-~w+IsQDzK^0wEj+D$`WSMKSA3v0K*aIW*wzx){v z|Lq;P{lJ5=b}1e+^O;s(t?biT$yLHOtC&t(07^{x))^Qyf&6nz%;wDIf6##eu8#&sKFHx$9)9f0Z%(CUS$4kJ%h zh7xEzhK3iU_R;u@KbYx|2=~79C&+BFEBd6;PpcBt&P}D2M4-D$&W5VeCtg1)xQ^3! z9dwsT*;DBzpVRTKQar!Iz)wS)Y_}P!pfNfWp?4YK(O3Tre#~%m=I?&-Fr?${tJVhS z>=lrTBvW+|8iS#2`i=IfwE<-R;44R%@X>{!`|u$=e(U6DgfD8a!sD+U6_7w8>_2iC zX4F|kjj91=H`?IFhx(x5cTdB<7oUfx-gpfTz4Im<`TO4(Xq$f9`@-{Je(C_+`S?TZ z4vcpQ8~0gw-iMFABs?!xhr3^RjtMxadO=JCss=`ts28z5FLd@+WjRbPjd{sS);z$b0hGtE^P}he^1i z7>H-yd;^|7eoS~C1QmcUcehUNIDmRU&%AkT#6+Jh?!%J56dPSF5W|cS2~^FD7Wvd} zT-c21)vi6B=%lT`_GJe6+|LDhTUPB z>Kqr7@|jIF1GGeZq0h@xpIiwP1yjb9Y*zKO!2wZMbhJU|{xvrEbS+BPy11i`MdHh_ zU@6%x@Ok(Gv{}~ZjMb!kP=K2@70hm|8K6>-+veseAW{OYUZ4qdx&3t8|MsoFVo&7r zBR|p`^0RB9Ym&QOBA13Klxzr>w7U5`YSn4T7nW@sCeFfg|s|3n!5j{|JLH@6H|aVdjq+q(_^fRXaK3P8tZdo9e@(iRu< zt#-^$ANe`N*~%uK05m~D0gxI2h64{X!b14LJ-fp52WMNa-_Ungz>n!?42H)aRu9tf zZn@BbcY(EZVhL~!%>xXh%jx{h69NHlePI7Nbyew@+aBx-lTRSu!x_l?#;y+Fs_qPn zFzyAQVd36CK07Sp-tGSwzO%a%W;so;wyOnR9>!fGhokSm2Wxk>z$}*;zO!cs^F5s7 zdN4|kx0C?4Z8H;L+zUX*9sl^`u!*Ba_}GaL;N;-QdrRble38%L9&`MolaSM3!@FQJ z6G4Z0_?!g@Oi9v1(0V6LNg6>3G$lEgO-Tm6-~7mZF&SDOz2J<8TOPaz5~@oX5^WXm zRgCN}thFfSJHcV(r^j|mGB%U)4;_7J+>jr_V@F?x)tyaH)Y%AYx|-ou6lC4*?Vr!2 zJS|H}beRSgvSlfiJk7T%A+RjP#kOg-=>Ybx$D05Lj~|1XcHQh<^OqD2_9kucVwoaqihgiFwGD}j~1T8KAq z9 z0*J_$7eGipRXI8<3eY7Ipjr$(pS5fpOv=;6o~r=0)r#cH3Lrr~6QEWsz)#GN7h+$5Xou}0dN}v_c^boY%{;YZ{WV+0(M1QNN9kM;!AOnLO zA!aO<$`pxu4!x90Kzr3RkuIy=J+gW&=9H=qA z_U>+&-|S@9p4AWyTLkr1J{JXz;e*%scI*>vDKlk)jL}tnO0kitDO+6 z?2}J&RYIn-a{R1}qm0E@ZB`_oFkdWy1o&B&jg?@V^{!r@`-SP05aqg;X(mq$fxs-TLGNGl11do^z)ej zbyh|4sl+n@Iva%o$n^8W0w|C#6u>A?ev|-N<5GZdoFLuJoL?^%Ksv}8B7j1W6%fFy zNPbv=Zjk_D@+X75dvA_6E6 zFN6iKm8nL!k^)EsSvqW^!UD*VZ;KXSB0MP{62Yt>fJB5F5ujW(!es*ZyvoB1VF6kp z*=dv~|NIJ2T%dOv2k0&0@pc1G%QTb_ih|Yb=$T%62%3bDw82d2XhH;WDF$Wp8)|TS zO9Yk>O2SA)vS<#MrV(i-iw4q$z#0HWxD;ejKcAgz2+A3z)@+3bosdkEd0g z;D&1#CpZiz#?%|L1R`t^3D6uAKsmytNfdzqGC|f*0VK$e7Qk*e$z8qXvXKiA`1=hV zmpdyx!B&1`%>9K46G0ec(a5T#01`o#KmdgZm-_e-0c6Mz|AmPOGO9|Ba#>%@WZZ2W z>Ho;wdKvvm*|hl5+kCX*InGgW8c#HK{=|ok`9yjeW-XboyKLmQg9WCdk*LNJcD!Wm8!M{^|rzMI;*ms)i5}x+Az2Z&!25I4rWwWL}BX? zEOKufEUd2?%)sM9ARn2w5R42L+weM@-Ge!fsOt>oIm=qnPh6z`_Ydz*&dt4=I7*o{ zE1hu`!$e9>O-f74pc5eSr(Br2T9<$6_jJqiuh$jk6-OgwWnppRih^SC?_wkr78Flg zxdOMJdh#qTEon9)Lx{AD zp})x??JVrlV(c?%q&{ae4u}ilB*0A^Hwr0^^>G9BT>K=*lpq(QLcEr=q$MqBNlRMN c(!@yr22-Ey)4s~&`~Uy|07*qoM6N<$g6%nSQUCw| literal 0 HcmV?d00001 diff --git a/demos/icy/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/demos/icy/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..14ed0af35023e4f1901cf03487b6c524257b8483 GIT binary patch literal 6895 zcmVBruHaWfboaZ^`J@5OTb59uN+UwfO z>5DKPj6xxy*f-15A^38Hcw8gS)fY>m7X^~)>WdY`i-Y7Ev5tB;lGU`#+aci!MOUUM zD}qsF_F|N>IHn{!fdYTV_wX|;<46$x9(d2I{>ArDOEMG+AD^=P{ywF-GrY99`C;pd zTVmI*ebJ{Z?*lK5{2OnL{2bsnz#klb&V^vTF8LL3idsEt+KcA+ISDVmw89n=b3!uh}YH8Am2dcyFwO zP>3sYL|70%XiHU}0Zo+(MxFf$fG{c^GK8Lk0nm!?MOUlH=$7@wQ=P+?afrb30+O<` ziTG*r2zL#G;JREn?w(KwKTW>kAG@~nvD;BDbNA6Sw3X7nOleNtO`EFE_iw7?Nk@V% z2nn}DI|Z-=FUSS{e!iMKGH%z#^FftGb+nGAxybACovek#YjQ#vb&d*p+t1kJZ`xQz z;u|ZlH|p$>-hl#GilOt>$n{u0Xl)T;>j-tlI@@Z?Wzp-=)#G34?74swCQ~ERfdKmc zFhPnTvx5a7>%ShCv+=IbEiP%zhTLzjnoMn+{p#7s56cR+1Ip9!b!Tb z`Sm7~BP+1z^;S0iG7&)FAn@&x7D5ZD8A|Rn^8#NH904lXb|d*p^Im_M3cx}s7!4)T z9gHH`t8+}w++;htxjC@gx{~KPlVjj*{S_ks3$9(+#6u-Jl&IAP3pu!CJwK#M5t6c_ z>9wdD74a&~(E(Zk#1U@ZTtm|Z&dTxVSzAiRZr?zO5>r03qKN!s*CrAGLWn8vUzShH zLj>)tEVfOD(e%jX+M_)bim*#E5_p?Gy16VcdB?_AS3UnYnfh>x4oMP&MNjS{^B>++6>|-QpN0X@X6L&Y0v_nr&QpJ?Nedk76e$t+1QRS1iuh%{F%%f!H-mR|< zQLG8Eng=h6w*&uot15mDdp?pMw_z>mzOGmllD0RJTU#1Lm&egEdG8hyS)~+JzIUCL zOasw+)T%|5zrIFI%imD16;(cBT?v`6d!z2=P1Pi}_cC zaY){_eM2i&Osq}6Oy>Y2JfPjfx74>{k`N|n!sM^n$$Li~8z=DouS%NFPq=6oaadk$ z0*u&FPkPm9z)j6IfM-M)d8(pgV+4M-S4t-d{CpIET*U$q-ZNqpnS{w$epknMM*J)< zPm6>bel7I#uL*$fN%fSIg0yd#CHM7kuV;h_C^iY@0i^Gty9+J2aLrPcO&e_I4V!m|%QLzX;!0D_phPA9;f z54Vuq!_U%`L{EsIT^4|j0x3HRvX(Vc4%<2x@Oh2+Dn;)>o2t)Xj~&>w&Vc`00uyVP z+rjjLt~xt1(^VjmUESy@cLz5nC)L@%fx;yxhQ-ro#ptR%A^-9B0u$XgK)sha_CY+|f}c==vHJ zIsE14R^;ECC&mE-m5-zZK z+8{Cl>U!wJC$s|y>+%=$e8oRsp!aOoBrJ@MF;SPkbU$$FNuOD87#(v%q_;vE<)g{{ z)}HI>svC+uv;Os$twg|H_&AuO>#CKsTo>rM<9BT$m9M@;K7t9+k|;62$@KkG-xKZ2 zhe^_oMi>opdhOmo+KXR&YGro*f{q}Ep3j$aj{uxYnw$E)-`r`v*$LKBT)@uM9ye4J z-Q#1bNUOU9;6>Q;!8^3)TN3u@@%O2>^UtqNkTbvkW<`=Kz-yfT?N{=`iBIXo`W%cP zOF@78`!8CjaFJ~gEr7rbg{*#HA!~+a`8W%{Bz>w?4Y=;y{O2FrCCt!4 zuy^g+qyHvTAKvPoK+M_<8JLnR5|X`g3r*75jg0vjI+5}2Tc>@aBLzSo8U5@X@4sm^ z5-ujt+fn`dMM}KeB4Jx*2>uVv&wPi8j_zvT3~}C%Z`$&>zV&72aX)=W3XlNt!|X?Q zQm^Au32^rJ-)S6xb54f}0OiA!vY*2j%^E_@&@x*=87F{e-s!CjZ|nOe1f`XR>1IGiFlvUuJSK*t=o+=Yf5Tc5TadL2IQF() zEi;A4K7Fc758(rGN!uFr7=1be_I@-cIEM1amN~NnsQVQ zGnAj7{i)NE&jag-b#>GhG`pj=Hqeb+VmN|mT#uW%u2aZ9WP0=nqgD1a!xX1#>7~!l<@*A zoYvP%oqLK3P?~FShX9z1Sqj6ovlDNLrBCj+nMZO-0B}XA0IJ;6%pJ)C?Fk@Zmdxqz ztUAO8CbdHVQ=%<(ai;xq23`ZNh1c{dOsDraC(;Gp_x{_&8?%}28UgCOUzsT>BkT#_$;_WV*qs7k zaPyN$mvj4DM~Poi24V76Q+NQ14?o+kc?17edH8v_RvLR<5W!E8Nw&XzRMg*N-BY$S zuzP*nCBWq5k(6tj0?eD4;4Tw{lUUiyM?|NRtpotF6fZvOQYu;~fC>eGYcU+!A^_gI z>|g&+Jh5H^5!z*f#wXumUx4XTZuC;;xMdO!D9;DmFW!WFarO)uTvuikAf~*Cy!Q2% z?KVMgd~=fYTB|S$Fu1;)-b?J?fAZ6hBmmb%3fCA#XxAj1GG?%S0g^}b05|kYcetUL z-fe4Y`Q-Vtqy|P!>5)U^_~}z_aa-{kcrCnU&C4&rJ`sE|B!wvbkd_OtElu>j6jNVj3Vxd?2fw$+FBYCS|S$=CYSc<5Xi_2*; z&gOy)`=+1ggA3j5q=$gF`8aHR>b`OQ}eQ6h8^930& zTfz6uT#6in{r9oABIe_L$ArY#I_=r^EJ;?q_OB~WfagCwZZ1HRKmdgU5x6DEkfO}< zfwzyo4LP-t+{?-ekO2Z@S_?o$$g;aAA0l1(9&md- z<=AWj7QQA=_Jw~#d#mJ4?b#K9JJqf<0gnCn1538001ANs_@tzj2-yZ49YM<%;c8eY z$FZH)D*9o-^{baHqyo6OF>A<%3Ni|8q&>{r+d^jT-r}%~5L31_lEnvhk3OrL;pn_Wlg^IkA4rJe+-a^UwY7R5qH&49$;zI8q6 zuFa?QWFa#_X%0VCHo0|kEkwel#20?HhOE_Boonzd$ROVHrqv>s49lswR{|TU1x4L9 zYWUdAHK)eyY$D^fHyXs|f^6qRnrJT@3q;P}(?aHg7lc1M1q}7Ow>ObxkL;#qWh{6p zNoJ@q2lV_2;LW5yv5(xor2$M!4PBBnq0SsoCnSIMQwPW-xK9!YXN?9Ewl1gu%s7*t+Bg35~wxOdVL z_!J6maK$|`wmvrlW(J|R4Qp6SZiZ11h`rAlpa;f+xk}ztOG1=6^mika+17v_cwJcm znb@*{glqHQ_Z$<{mdK^Ro{!{5S13qeX|4t2CTLg$Yx3A^XhS&(#Cr%31fKxLk>AE+jwroWIAJqGD8O53ik6ycRr{+uucnefYQ1B=j?lwCZCL0Z!rfHSi)rM z13-u*5X=u3)NR;&OIH(34)$~;+?LI^bTx53U>L*(G1V#y+YdHhk;R@Ll=i?+OkCd- z%3*SEKUbcW_h90>pZQtm|g{tib$ zTp&#%&A4L)t+45A(Dt7dVJl9s;bIyEC|u)|eC+Xd1+WujnF-*8d}{%+%uSDM1z{$R z&7_>g#s<0G`%Nz|CMXD((fWe2kIJa1h~| z1dux=-=+ZA>r1lqv|jhme3Ej-a^{v(vpkqY`fO7a6BRX#kuLv&l7`Q~y7ROYB*UHn z+5!+@oj?G`=>;nRoTL}fw?`M#BtWKv2$vOLIJmo103=_5DFBm)B`<7DKe~FO@{*5NG})#;LV$p z^ny_Ujoc~u*wc9ddR8e}^0QYE$@Iz9$PLF)hny$v0ZvsH#-G7`E%D3)bN6Cny)?Oo z+qSv+;8rB2z(RmV8v@wL?N9-lEd{Wj+o1w%wGhA#`MdzbHr2Go)TqJbTt%3<(;lIm zAUDzU378K1rVR-b78b-Utqt;cXu%;L^r5#m;S(UOxMfca@Vp&7^2Kf$-2R72FCZ2X z4Uz3AJnS1&!MHIBQ6xl$8R)*9=6bq&fnGYy#$XFui~gt_LO97NkaamPlJi zG}q~I`=rPHvkwCoH&ISlZaVxMHavs*`M}$I$W4lzSC%}s2RCQw@i<@HvgZtV*b$z$ z1usHku}*8?kXySDgM-1OS3 zUTf%8r$G=$z>}u%up?*XVrolC&vhjv5k$Ci$41h-vY7O&P;e-=MkR~*S`E2p?^e2R z2iI-Qp)^O8l4dnAv4*)FoLKDvZ9bYE?D@AANMDDx52qZkTzGY)>9HjOKPle;xH&j= z@eBOKOmjv`Hyzps*NFnc=^TJ|TSRUrK%GPVdOzN?a*|%a6f$NpF_~t|=CiIQ=k0*a z_gF9s&CV^f?WRfhqJP7Z2i@Zm5rN+@gx^9pm|1YoJ~}B;5wdmmL}=@&iPu5z8@0Jc zAb{iaf=vM&M7XvE5Rxy|@!k$I=PsOZhtM{&ZTGnpnJdqF)xt#!N9$N6F zgblJ1XdAJum&oim79o@gW2kW(w3Y;Pl=9zrpi`& z!mJaI$>Fh;R0Qh?H=tA~fP;NIicACUUhq}tw&EHtE`c(si%&^rOkR(5#=6rsU|XEx(9YvlOxt7`7r?j;Y@Ha zPS9~Uq=Rp`VM6r6xi!r4g~#X|fyA-jV9L%Fxb&&yzc@|W8V$kHtq`T!J->k$fwT9f zIY8D*dwEf&fqFE>)T?2)4Pu@N7f&9Xf6RBr>&*6g&&!c~>&O}H zr#}qk$lyMl5QDrSl9VKmNn_^Ee2iK3e)M7{i32${3oSk1TC7gGkDd~w?cAO{}c+|2tHX7 zU#BJGcQlcR%3^u|EI#sS6Kjh|H*En;OH2Zj6;&!Hp+#ASkepSggI6tnD`?^Do&Mky z_(gS3!Fy7-66*lojXxVy`EzxYFjw%47oscmr^CW}fN#x@ih)QBU|84q*gJzJCZ~13 zcV=bGip38P%u7EKDP8$aq&)5O$o!1&t}Dv=F{)U027y0E7G!>hpM_^Fehd{2TmRyarwi zugRJiU+!L#tDSf;g80yf8j!fq&|tdLATY2y^~;e|A@Du?49j3d&XV1QyT&!b+bIYy pii9&6o*bz{@b60mWOsVP{|BB8eXZ|AYE1wD002ovPDHLkV1li`I!yoo literal 0 HcmV?d00001 diff --git a/demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b0907cac3bfd8fbfdc46e1108247f0a1055387ec GIT binary patch literal 6387 zcma($WmFVQySpr~^b#u_OG=0|(kva)DP1B+cP_AmARxJ*NC=Wrg0zUl5(`L)gp{N- z(%_OG?|Z*r_s2c=$2@ap&UtF)$(eXP9W_!SdLjS-K&qjxY;ZTH{xb;h@8E{&N(%r$ z+p3|gU=%dFmq%!1q&9_NsUvvk-GvvZjaIJ%uU(o!Ypc=Wv%E8e<<)SFdRM{tz(T@!nKT{;0jT2A&dgKu3 zk|GDUX<&73+f+CnZza0G4g29@hmNkl+2wP#$0yi6=u-4CD#*a8LxJLG9KlkveQ7v} z>E#)-tL=xh89y&5li1I!>Zzc!_i6V~nKP^5-+!69FtnX*f=*tr+cf&UpZtLBY|wv< zJ6r*Z5374 zi$7+B3A@szy#|*$Tb~kkzc_N~h3;oe8q95K$w@e#5FRGcF}wXTR}t#^!OnNc>Z52w zu23YrlIQY7UrLLcFSW5ctMBzwrTz=X-m{1Y!*LWUbO~;u&&q8Lu;wlGFqO2h4olL; z{rpPfr}7f=Z)eZhFw1_ITpft-VzPF1CHv-W>u;OCBJBEOEn$HmTpFjX=xN6-H5#V{ zn6Si;q3V*@lFMd>H8;M}vOp8McQcJ}^bBfV`1xb0g0`9ZZa9(wb+L_RGO6wD&I8ouM<}YVDFU ztMSz*yMDz3AkS0YO)3_lYDarEUyj?A#9s@-ln${-1Op^nD7zREi=%4Hy%V?=YS7G`L@>`3kHM4eAD%)t@F};|C zfj?B^Kox-WuPMuDp2=LPZU3Obgnl7{dD>|>*A`fn-0|^8uAHJz;<)tkTXA8lI&dHt&xG(4Il=e~QNN6o9YD7H{TR?17eM>#Z8#Y@_=7fZ?HkZX8i|mEGs5mR`uBi^ zzFh5AG^3EMyvpx(a*)!eOI1?nPTn?v0Ly$)KlQ16Xfrzh+}+Ua_I!5XU@ciwrAZ>O z<7!MU$n6`x${EB6YH$hWOMuSEw+72Lb~rgO*Yp26LGdNp*;^;HAD@(SAr(Dk;j7w! zQ>!M4rxUFYn7E?v7)2q)2rJ2%PY>A>-1O7bY~nt&n)jYnG$(iR#hvlih1p}c)I+|I zy^C;=uIJImfY zL~pm6t6Zw8FiOIY<1>EBS(<5`Cv8DBcZEpTCQ{@@-|2$Bhi;6H?Pofq1Z%b2@)&at zUA{9iaqi62D1|=T{xTe3Czr|z52P;M7EB|V-ss{qspYc0Cj~hUUURef8?i5H?e;kA z<~qW5`JIc(rCLz_oJ~>x8O2IVR%>+7%}`TBSQt%i+m+4tV?z0(?5cf&1v8cNlz7Lg z%ZS>-e!({r)+sH_1+QJvE5BqOgmfK_$X*P0*x6beoRN|0FV zBu+T9^1E5}1I>g&wC|Bn^{(R$!_A@+E4<}3n|QMU=H|GuQZRAZ+zSZ}SS{MNj&mi0 zRY+fp&8IQn-}zGeIVj+qntrIP-IpXF?2xAoyT|i)X+@HL$+|t{#ZAvBrd?L!=9aLy z%@CY;X7U41O6VpHq<1UBk2vi~afo_h1Xrb{vQ%cE|Fvi8EjFCP^~ zabJnB#=NPyBD*BaNSQW*VI+TbEmlu2&HD<4U_UQNUR_`K~u~XWideSoLc(k)vEtG^CT* zG`Zdarw^M&6C=~oi^6W#WL!BMe{E&Gg9Arbg2gg;cO^sJ#+L$ zWBP!R+lcV(p-B#aK<&Ly>?*3fngF)TwSRSmGJ!zET{Brabip#AUPyChm}S9IFG!l{ z%+I_?Cl?zVm9nbGSU`Ksi%z1{vEPpxnv}!StZLIR4yl9y>GM~KIIbNdVs|xsuCpX=J#rE`8<@v*FO%Lb)=#c`~s7W#9EDhRI!G*VBK(y z5D`)jJo4o1={q}Kg%YGhdH~@PGate(xi{(OiQn~MMSZM;!kHNh*1-e<+YS5-j3b?2 zq7SYPWMn1a!^Gqxr4d1gZ5G`QQ(&4Ag*OcnWO}~9rz5xeE3Ycol5cj$@jggn@8x2* z)UpG-U2|Av7a)Hi=b^@SNp#`PEDfswF$nyx&rD*+4SF}`_U48`=1VnBn}aEm{Funk zSWQuC>r8yUkd_D(dKEqo`7i}}{#+a?O4 zDIg~&^q#d5-Ji>``G%gDDzV<~+=*qePTy_lbVjK?!d`>ygnhxwtyL65_G4A=A}{Dh zq;iS@h|Y-wJdeGj1b{KBTkst|klERM7*Hwy#ZO<~Q$5~GzC~WjZHz>=z3~>oAVbbv zzmgOw2JQ#Kv)GT9dwrXGJKz5(Jw%&rYPjfi;TI|dyVJrvaZ*ivGRT;i>R6}8B>7*j zbJi0%9UfLcYKp+TU9qXLSp`rm`)3(g6YOdHa4cv2Y)-JCPZ&g1Z*%F~T@dw@_HA~- zxeq6NeOi{(yh(ziMZ)4yIfDP6nhTg;)$=9N_-{KO!ZB@c@e$(SVH`%0b3YF`lgX)? zmPOF$H%(2yD*LrQ;d*vDgW=s=2h+1RYg?DCXa2gXNT~W+Hu+pBZ$bO8IlS+nqXw^| zBM2iS@v_S^5P@J5V0gw2hamKs7Wro(xWlv)U$%_D)AA{;Mb;l$7?FOK*2{U?f_M(W z4#aOFFlOC*Grkxzi#w)?qgNP48e=dJ*`EYNKfLm6BlZ-j@VMi+{0T>$Y6e%gC|6;v z4=~J;U-H`Rv(<}l7sEXpm?7;(jXl{O>aLca zP;<5GjkKb?74YTOqJAtFKzq|v(-+j{(@?GPIKVS95tsog!>*S60XwAsnYHqG)dW<#@2UIte}({hi5+*r;^rQeDpKps%Ql|LRink z=CR6^g!&1h1Ks5JplDey{0{E~MNPgvQNeH21%lrCFFh~_7#;b73>@zaFo0B}hXo(J z#OVP*a2!ZeK|x0LfazsE0=vAP5xpQ58{e}Xtzn5B`l%b)PM2PI{UmZ`}XbW%4eE=4-VAbQ|zojxNh6BnLDzTlx-stKQP0|=pi5R7qw0g}ivih_z$ zN`Pc6h9K3P5vFz^s^};EaGwq5yEdpH4Um!3Lju85e*w5hg)|yEkihSklp#pqhWjij zaK_T%_)PG>g`7N9$25qwhR3WB{&pp8G2;J-#qe6%xdFHO2AeceqW`Q#`J1X4*a>V4 z;Y4EVTMA!^vxOA;$ZDCt!CPots~0yn*Erio(G!n)@W*|^D_=Wy;f*k=tF~9Zmr)dn zCzfODoJ@UXXs>1NP-A4#YmmhGXavn<+z_gJ`>cZaGo@Iz2J)=M7{{ zJ;n45y6T86%gls;?`*1bFl=sXf1H<+2AiBU`}H6YM=+eFPoz%Sg=s>Dva{ls1mJO? zTWP*i(U7Ec^3%Z$g`f%l##*mSt_wOa-d&(0A0@(ms#pY$P8SX-ZAVg)> zpsk00`SNH__*AQ#=>~|-wScS`e>RBCs6NsQ18sz`Q({qI(fOQUY10Mt%YO^v{>w>TEBSR zi>oS_n(}3A8W+^iWG~}cr3Bv#s3W>CFUJm0ejS>=V^X>!UmDV@|xH@hWB5yhc zuXagN9&cY%tMFc@?PqIxYmy+OSGU`O5gvK2Yaic7tFAiaz`*T*dLafG4tz~<{L=*n z1iRA9k6#TYhCWcSFW6P4&4yOea4q&Fy6Mbkfl&!{&@KmDXMWs7;2Q2bRU~gBtDs>o zNeUgzt#lWV4oq=C=5{Id0)=a+u5HaCtDZwXnX5u!bO%{LbXF-L40}KeG4lG*uU{E_AOMMd4ch=Q9&rc=;3fB`I@EFBuF!XcuT783*FH`4zO zxZ=AOG#fzwnh^u6!|A7Fqf5u{$IesB&EF?V9g5dyhcmbVh)|M3^!U*}qJEYbGFaK2 z#0I`dWniJzl~+;sJs^jty%7`^Yv#{r+=Q<#CleH22pEWpQ)lwX9b5uv064&fPlS+b zqZM<&o~(2`QgUJ$O29zuo%|4(uP+zAeibd;jfc(zz|+6+9EUrZ?#^|ymX-knV0Dsz zFn=Bg(*p-JjWR}+{_C#CZ~dR&on|-C9&{&ij%~0x9gtgIMPCkr_rc{WE_}pL*bCnZ z3d?M3AYq3)iUS7jPOFD3m9DVG)E&SJ1*`YXzZQib9R(``({n~0aGXEhgZnJU3vy*N zlEAeqef_?@nqICTH{?wuZFw#7F{`&i?NLpf<7G2noyziDxMHBmK=Z&P8jf>~^fSVF zFmD1h)DVg7D8erkb}OkfElv2i`s#7j5-;7~&l>SlgLRqNM90B`oFJ!3Z!I+~g7^$B zkD<7Y^U2QID5DVT!a*uS%0aL5KAD#Lk5^|WCC!!OQcFyxCl$386q*ohKGP#?pNL0_ zG0d|NfxU%N?);5-{u0rA@S7+4>7&sDwppXmJaj`?8D#?9@k90l(a-Vg>E`q1zXh9B zEsyo)21!OKE@yf_^P?a!d>O%I$~z&Bg| z{KuO5lVh07O|keMJh@ks$3EfHm`nFk6qNS&_PxPbKN1c~Ds8?;y>OzV;B0$XVQ=LQx12PJ2~x!&?qm%Tl)eivoas}<)&`&84*`tT{?ou45c+RPjX;imIsuwmXJs;5Klbii3#Q0kSLKcW+Y@xKcRce+GJ-RTlpMp(c)D`xrv zd|#_rj!Bm<&cad=Pq($+uKOY#CGCK-8EXOLAo{LJ2l({+_%87YR(e2EErULI*gm@X z*m6LuczdHTQHH`3=)x;unt9KH-4duW3nu}xk&Cu4-DS4wjNG}S$tO5H_$l1*S3Go6 z0HH1rN4WcDUK${}+a@ICZ(ZC#*`6h6EK7)q2OePook_w)c5%-9AxwoT6E*>!XDxpM zy_C$yP!`aN2TiCVLn_z`_E((J%LUYuw%2%(GBL3Cve+5zmepidD|^#$=@2Wfp!?NR zUpV2SwaMg68}9+`X#n-Ust|TK-Qk@HXu7dM*@>KO~@YA_S!geT; zxLp>TbIo9^WI=ZuT?ErRN;LqRSZX$7)+{MdSSiDnSdSwQ+6Yqb#nF393O_Ow-rRZD z1MtC55vP=~4kwe+$#2C8b3Q6*<^!T_D^X($HS$*Ns2(pd5~m<_QgfsetRt77rwh}yjg#yx`@p|%;RnzvAN8~6i5D;EQg*azSU-+F9W;M>-%sM=r4J zY%}@{t+!2883WSGMgw_85U#I}O75Rr0Q_D5;Du8|l@ zHWBq-r2&(pezi>6+daPx-qwVIQ3A6$h}GxIH72G*;HeRgyXKy?Uf!HvVg$M3Vs?lo j7HB*8-{6~e<}KKy%g|C8?m&3=nE}vH(NX@WXdCq(XawjJ literal 0 HcmV?d00001 diff --git a/demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/demos/icy/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..d8ae03154975f397f8ed1b84f2d4bf9783ecfa26 GIT binary patch literal 10413 zcmV;eC{ovnP){+^kJY@_qlWNt)byXXcl4&di)UgOL4U zf7l=Phy7uH*dML-fsqKMr;DlfM>yz|;&bpF`{OQzgo8jbktkySeg~64fbWuHz_H+% zO2F)JwJEE@HLSkR79_Z#oHbogc3dx%o7^AeCk{b5(&1F_9NvTf!DryJ`XFJT+JS0q z&?sCD-y=8K2W2PRhjJ3<`jzFS2UeBViE9@x1RKUQCZdv7kl1SX?3WZMS(_}*GPxT+MhW0P|fyhZ+Qq30&o zK&_A(Oze8$+U<`PdXPq;v4_f|Urm8qVAY042UnGp45})9cTiQyEh4N`WieG?WwHFJ zL%SQEJASBPNL8tfyeEVAm>Ttneh$6^dT@7TL)6K`4dZuI$Q8$@YC7*NxE8o3xHh;( z)oY%paC7#DbzBq#z7eX{hBSaAFX=&XZgM%%7vkI`tW*yCO_Yg=`yqnAa-v2eeE;?> zc{iKw z56$?22D^!CP)@={l~{!+p^?NV4J00s5s~K!m``K3Z^mK!w_^!uRBfLTqF!aWIQ-yF z+-+mFw$C)OYiVHDrh2UxX&Im_YA#t%&~JYj4^H@@?c?sN*|d{1z)fXCWK#h&a-j`x zMSwIVr!Zx+>*mUE)45>nPAFTm4uSn)0ywG_n3eP}spMCtk;WQXTc!Xa#?G<8~9?@D4_J^SH8;MHSdkm@M;{c4Zl4~|K=yFf32q2}KbIxDWFpb1y zO+OA&=Iq3=s^1(B1GFU0ED0TN)1GUEzJjf&cITr}~_843H9IFf?D zpy-;D=W+{Ha$5$7>!~TGM>3^{(aM!hTwS-Zu6}T3B@Ohtm!x|WXwD0DS$2Sg4MHki zT4wy)C@!)S)O94Q^ENX$IJLgcuiK`aOAMYnR<7i>43I*17(|~2Z^{a28-tFl06j}G z1E(L_b%g+AG(2{IghMo@X493&wrmJ$)etG%R?khj1IO;za&76!!+2C}`5mZmW7T)d zdc5TLAso7|4x4fu(6j?P@#13#aX@*#Nyh;YpF8maDO(w~k+R(hKe!7&`(pji{+WqG zRNJD}1i%xZuq*IN{U@la2#gbNVFCfAchs zIJDcO;{ZH`Z=Jz5RkkxH?-ZOri>KGuU75U|b7#sb@!GV{ltwd6tl0 z`-tj|)YKcR-o#ogdg%auyuQ|?Hi%I3R1^-|ZB z3w@dmquBHyVR{7VswXIVTX$?MPH4+9kb2qjlDK$t-RcV{VoZD69&BtHN{89>gQ~qP zJ3uX1wj2^zXGt+iUU`JHjaZ|tY;IN^;K@-L=fQS>Y@uwVEi&RUN?2Y*+sNids}(cC z+40kwrYD*P3GD#2c-goFwX_(F;ug=ctyz2p&FRs8BZP#KW)rz1wGkz3b++zpGX3NIKL+e&!v|_Kf@T~~axF4tuT$cD=XZI()UWvicEV_jFqjbw^Y;_9AkJsqs?mSQ_V zHd!_~?Uk)r`5Rg=yAOj%Y^~TwjIt7{g{Gt00kYMyk+w^ZgMfMuZBvVP>lJ}>TFiaQ z6}$vw71{x^*|Ko~^_rD(w0N!+0&330f%Q3TNHV+~AX_dQo92j#JW0ofEat`()+cpU zNK-<*Wh>c%oF}ld7(cPM7T>>P3+`N++2#S7TwjYH+FeDL-}5iew@%rhE!V8XXvx!0 zTFweF>(f3j`6XB-!?_??289+P$hL!oDad&d`knUqYw_}zU&NQL{fPhk`)_>p#vk~F zOaH-9ClAxr#e^P5nv&DV0je~`L#5{FGh$URTHx9AYn@Acj8H9 z-fn2Xa=Bbhm#_bhv)?!+_&C~>bovC&J9ipS=gMNVj42zRq^}*vKi$01ti15vyd!%p zUA9JO)5+CkcwA~i2(aSSaRpH~0l2>#}`U$mAt<;*`UUpCUF!4<_g zFf*C<$Rf;^y{H)XiCNlB=(vxmae|1Pqx`~~S}Rm0li_pUevNx<%Eh8q90Q566YDZZYFMh0VeMrAMOVe1 z|Lz;ye`{f@1!x?J0yCotz`^}fMr`Fm4fEt{bxGcZ@CDfQlmg-(RljEY}^PEkElrDm9b@vQz3{qdC=2bx32OI6ixaob7Peg<(shE$A37*Y0*ydf7hWB3l zfOPA%yE6dnF4t(NpuypoFMj$Fe(uB} zYGE`j2L$`WNWctZJGzc_^Y7cZ=&iGKe5Qp4N#!&iijDjXjTz(3xiMo>J=mmazv7G# zF};w)79FkiA@1zpCm-spe1PcGSD#bY2j6kZTSF>x2d*b>5aJ1Q0i#dXZr;STA6&qX z?AfNYN-*H~;g8?zcE?0p{`DpSKBZ+x+2NX#R$#Yh=T4y^j8P-g+?ON+%kpw5Ksi!b zOAq(oLt>AA{_iWD?hG2?wJ$%XV>2K8a2fw~=WnZlqj?=Lg8tUGU(+#}_pV&l`FXI2 z2R{CgjGSMfif5%=Dvs=1Gg5Q<1A2u%ogU0AeaR=a7WglGq9Gm z05rN_()Itp2xw&&&f%Gd_t?ff9{`jo#qQFme-Q@S8}7!~yjOSWsy>00CD&oc8BE zFMG|E_M?KjbKQ9%c|x42azM)$4)-h1zrz4(v;}}*K(PA#cWCU;R^U~Jl3;7>rw{Cu!{8QN zl(B*ZEn!VUSbEKv??13(3(hAM`|DqSwpn--f-*wJC6w9N`i?w)2q&I8VbU?i)Rp5$ zpRbmO?ySVUW0vO8F+m{!u@5;7*qFB&61$hYbWjGt9T07-U^P?#05ata{Vwd{2a}a; z(QWDK-j|R#Z<>+y4)Emu^ECb8n$m7_4%f@(9^8ck*T(DwCIkV5Cej$Fy(m5INbk)B z81_|%Sz$1T#tN3wg#Zy2eKhpDFrV~OEAFZrs~>OtfgjpaWmJ8GEc7e5$ z<-7`0<%3Bl$~A83zX=m=j13)K`E?&RU1#)%u;U-p*j;=g6-ytEUsw>Kreg^;rRu)?wAO})#2n1X6G=;eY zbpY#7JLDu;AE2T%dC;~}?3TFl3JMDHXKYCH0n`pX@o;Z)fS+3mpgvpH+sc<*x z1F}9*_-oA}DzIg@@Ei1s?3sQ04(rg@i;xN56+FJ0yx!{~|Zn%b_xqcb^P%5t(dMXW@Ug}*T&pN4~-o|+0Y3PH&pF}W=|bT0Q%e706_}svCls?Dd?;u zzf`BxSd7-LQcApTHC}%70KMPb((ph|^QvQq=sA_wK%P6L#o@{e=S=Dp9Q*VlcFK&` z3z4}2a!ZM6K#x2yjjU$pQYbW-n|+%|^QNhAEZ%^{+o;|Dp_Dctk{ReEnaG1N7!M zUvln?NB+f`^cqb${^jex;SpPlIV(gVl3I2ghz8NCZ=kUwM+yh%k@0;{mh_r60fM<7 zQyUMG(-U4kq8@)Rcpf7Gs5P<|e4I7+Y4)N_=QfSdz}A0i8M z<9|WJh7HjV5X(eFBM0>$=J8u=0pwnoia*!0$bca|pm_&(<4!rrxI=n8_RLDeAtY}2 z=*KHo>(0ZuLTbvfXLb_qK-^8I+%| zUdG%Cl=sFd>;Oyj@<24U&RhVc(aBVo=p`QzCVUthI@4N3$j=WxTE)7Iqpe%ok|sRnzE-FFFLy4v@Ojy zAh^N;M6&#AA&{i2o>0u#PM074u4E9~0hJ6dw^~A0!+7s~xzzXy*t&$}*`nH~ad24Swg^YQW%SiNd)(;TZ&v!xo_w?$uA?IrfP_|`m zEQFQk^)0w$mv+7L-8Z=N`c!^^cB=rCZUjVG+>M2OQ>B-YZ>N5giD0_7nBKcn9Z(nY zVT8K$EKGZqvp|-)wRvDgk=|8G?b5E#u3g0gVLJp(fT}bAG6o{JwYgv&4v1g=CLIIv zMIDs;tm=7)QDC4e`P->SW@4!&?~R8=%fD+wwQ%fNlz;`*m_7f4lZg zPs+CxK;6mf8GGySjQUzZnze5S&OQAymYz5)_&eH^bn*y2)>B%~UnfXQkL<$*XJ5rj zUfj!-MX2_vYu16CIG-E`Qa)zv+b&q$i!-$Vw2cR#ICW+4KtvPw2|#OCVb?j+tDrN5 z?)7#T8bCM2K|x)hC)UY#!K_emE(FoWtx~UdHXaJ8k-wu&kn8+J-4;A-Q@)_j>(YJY zg?Mu97A%3iAvFK5B_WJYJ=Uk;DLX5%Z$S!1DXUc!tzD^_ios5qQXIOg3I}f~YCb`# zRk6GpUA2J+pg4XtgGkD)Rv#BBbDlJQ4i`ZC2o9iC;vkyV;Ys8tPL2MM0+eN;g~p)} z0w6LgK%2DyWB@z>N{>Q5fDD62D?moT1F($VrU{S^crr8~0`~=JA&cjHO4_~;Wq@Nr zWEemQNj!S?^ny4@yn0cIMFA2Bk;MTr5FUPj42OpoAS2;v4v+wNsNimoCijJ&noYkkmt8oOdws$f#{!w*f?U)Jch8E3A=KN%$ z+~TWqXo1Kw0L2&$j}jo#@V*79M#G~7Xtyqagu%lBw2>bmUGSvS8y4j#ei=rgkL1%f z@7Ap&y`32$qxTGRKt41A?~MHXhN9HfKQK2YxA^)%Jnqcg06k8QB}t7j8Xmm>352H! zplw$Td3)1=B;S71raVS|C4XCE+i!)Y)YsxC zwr{1D2jEFPc?7RGyqCV#udVzd$BRCC0H?lu6o-;y!s{o=UxTz0REZZH+>J9|JAt3s zzmvYE+Eq#889~}zMJ*4&lX>bSjy`sXzE)_;9zIn!*Yltns(4batkeI%Q%T*?_v-l- zwzrm3eQo2^eRVjbFzZgQkn!Qr)?Qv-9>(^*n!7QC+Pie_+=cw@9hkfB2xJx-vh}yA zTVn@TmEvJ#1=R8YJWubbp>9m4%JS)VG&LMlUV!KB-HunhxDSsc$As6z%h&U3vo;k{ zO$HcWI*2C`VCj2X3Q12&RYlshwMk%k0G`!-Fx?$J^uSaSsW%wXr8mn$ z;~AVgF)0R8iD^b{(GvruXp?%J)1xrGDF!ki=FyCE)MFsSVjfM6Au&)Wu}Bi=^k|QH z6l$achszhr(CFcFXd8EPGdXzH1jvCdyxFM(++21qTCwm28srMxgw9+m)jJWN4erJ$ zfHVLZMJ&MMe#UxB{gzxExlj?R><7D^?>gd zIsvP#Th0rRf$)HO7NyhMYMKBt93Bp!1R5YW1IR#lv;!2+Z+#M@Fq;1OKH8?<-rZ>% zn<;qKH8R~3_2@bhB`p7*PXFr}owme&VS;Ayb&TsY1IP$?02pEJib{@y9PbYJ9-F0^9DWM#x0cd9E8d{Nhwu7<=K>8+N^$ZNE0c0dR zf&mgRx77?FBjITdP&~i&$sz#7EWzl}kQ~~U7Pda>u@Fr0w?{q5-~J?^euK+yOKh+@ zK-wS@FtV&4AYl`uO#r1C4No(GOn|2epc(>Df)>{$ZJ_HW%?-am+He4COHWJ0KH7U^ zJ}zBh%m57^@+5I(e{q>?{I1NR0BKHp2%Oha0+beGG(36%GGJC+2~b6`N$@BEs@DQg zX1pBgOSE*}Efmy$I&DJ>^}KXhp?36ES5Hqr^0%LO&a^z*cv>b}Ee=pNt0)6z*0lp< zSV{&gYQPJSfhidrK-D||#TlBCfycn$tyX}D>xy2C#ZNx60osnWp*w3+F|xu#VTHJL zgq)pW3H*WRxp}YA%HipiSp^_NAR?fQ+R6uz;rTqg02z_b!w-<*@IW1C1t<%~d{$u5 ztf~K`ZN{~oH)~6)SfAzrbq8wx0#N79V@ObTnO>*{L{8A*)}e#1H3DaS0kwz1l{q{-VIh)6$u;94s{*9U z5~XMZ$oNb`HGoXWBy0kx#3Xo{0hGz&9?~NdEngrPj~y9BU6+T4KW#fJ1kU3zQ!wON-a=10NQ87wwb%6LRQHnNzVok~O}hUVsF`(;T3r*TuC}N0kXv5o)1FlPiM+Bqt}hut8}4Q~S}Hl}cCEA^@pEl%fTo9TnOE z5;!qR0U`~r9Ux&7qZFX$wE$!QJWT-AasYwrihB-=rayj^whh-tom(<6q$B9d zZUq^P7R@|EduBNavK9kK0a0o+4?xA*0Wx4#9hQ{S4v_F!bx8Vx+?{3s83>O8AUKu; z7R5-2!lIdB=SZ6jp>5M1b)#+7g073t3W?bexF?D1dr=>Y&`=aP=RG=KRF>NSOQy95 zK)et|<53k_05UKoLpwl*rDX5|WCT1=*3s1jpuM#X5*RF;GwnaH88>Ycu5CP3rYl6q zMjop1khimkM{gLVb|XErK`9BJ!`9JjPoHdbLU(bm z;eEj(uqd?P&>oz1`XpVG5SEpLMGg41O+(c*@m(RvVTLqR$Rvb$EPmC{;Fw=5eU(@q zfM-E*{{K4m?)@;dfs>DWA9{;2*ESMcghxGlkqgj#6g@N7fPjz(bJITSk)MJkc}X&3 zx1n||Scj*RSZZ`#x$)as6IUTgi=&nY;DLm932`IpiqozPb@`WM;c2AddJtCz%c<}x zlTT7LK>|GFFhd$DOoH+&LAOZEBO#raL9xrfVDKn#VxV-BG6@wi5acWy8uM^nb<*3C zF2kbP(>^3_>j4H&AJ*e?wdPcXIU#bR%Y(SN^(B7;+qG*q9Lts!hUfDDKvSRB0+0c->J*@QZ2-mV0!U8Bd1526=;cl}bkQ8tzni+Ng#wO^Uu3(L_tPcUJ2^F{|sY8r}6)1CKU{y0Ag40i>Wq#8V$DMynRd zXk`mr#M7(*DR#7h*J;LQ680?4Yz~kS`8@mp>4Aq_pJ?eknRs%@Ca6=I+r!mym(~ss zA4IM+m~%${$kj2BJP&es;J(Eua`v~}s5PX5=yquq0SGoEfnRZ&amirK05UQetT{mO z+VYs?G@CFn3XA4Hby++zco~HU>eLzaW&yLSEe#Z!GbVCj-N~NF)fFHbEb;NWAI%Ow z1wNeH15|rvqs0JH3^oD)2Bu^v0V+y2DU+}Xpi&+1NE_($Rg19bsnD~MPM#C!sK1x% zAX=wf-MX~Km`A83YRASRU?Q&vfoLGi&p=!xesa=!(en8>x#^F@M!Hf~mK6a~LS$G< zhHij_&#Ef{sw!;`4kW-spbWV@OXl1ZKNeC#V@a6X;(mxdSet;y4)0u*1N9VQ6mnIhyQEZyBO%Gb%x{I6!oXH>p9h>Ks5dJOCM%k^un0ed6UHP%Pb8m@^LR*1I5nOkq_hdUc^+S%FHIjIFJs_SQx=R!_ z{|}V3f?1%o4b%2-m&4)?76nK(Cekx8+8iL`lEGk!m8tc$a$f-|$Uu0~PAo}G2sF?{mwdqxbK&cGQ$%gni}UaT%W z>{iFH*vN(TF1pf6baWg*dmhXpN!;AVi65PqEqZ491+;wOpOAS+8#RZ)#91aeU3opr zM1U0TES(RaEFAz5U^3zeEO9c{qvEDbq@;7OZ2q63IpG(?4?U1W%5uNL;yAjv45nq} z!0F2Bz~yd^b&Rz}5@xDhSt1nNKIG>}ewB_*u5Bn$utQM)S>h>^Dn$#P{*b_Qi}v2A zWlB&7DvMeu3e}jpavVlt4oQvyTVrcNloqGbjn8N#ujME$ULBYWcGoQFO`)jyw?y-1 zd?*fmxYA*8|JiWuY&?g$Do4)Z__4Bjv$8v>bkFVZm;oftBGK_9@@pl%lXjej!A!LC zh#}9ohCi{{ZQ-mp-B&KY>P}({57N+{xyjh8FctPfr+T!$Mn30oz09XHQwIB^dljb1 z$^SVOsXW(wZ+)uVGjE;TvtW(PvtX@k@RmZ^+(Uch12(V6o&_nG{11DO9u@4h`w=yp@yLR7+-F_P_1>{dzv%Vc z{4?EWO|R#D_cC>41Q@6rEpfZPY}Qsw(iu+VtM zk?VfLxt-`8D*o)6RH0G0sdlU^c5qq%Bu%TN3R6ec{q<$PcmS#o?ctDy1vk>p({m{8 zE>kOk6c$U>a;ZxBKlm)ODnpQ`%TPxJEO2ZmdS9GBJEt$ZhK?H0Xj&UPI5rAX2R88L z$%0cK7N~Y(7NHkw?B3M1K;whO01!A0WE#NW=*IvFVBhg)$LPV1*_EBco1N2*U4tE( zRtl2?YqWMOIBn0yR9sp7qyVcUb1gnBpzXq7P*oT9KOgqljw+zIvtzojb2zbcN;KS) z9hz1SlqysTupC)~JF~`b&#VTY6#sW--*Hp{MHLo1Fn0-5nsA9VKvNapXEcv<*FF9Z XdJ+W}DiIkV00000NkvXXu0mjfKBlg6 literal 0 HcmV?d00001 diff --git a/demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2c18de9e66108411737e910f5c1972476f03ddbf GIT binary patch literal 9128 zcmb`NcT^K!5btji2)!5SAPPuNq)Ls56s4*38hVo^(nUfO6%ZAH(6N9hNR=iCp@USV zNUs_|I-wKc#ou}5-}laWIcKxU$(_yIot@8o_s%{sGSH@@=As4w(CO-E-X`sF|29fE z>HYT9T?zm$_~>e0H4dIw&!!4C9vSZxNlr9*d^_s#H!1R~WS_6MVYz@X@%G!e zXHz-tb|VivQj`iFZDUWNj>i`*9rwT8VC9f`)ww2)D0tG&WBFX^J|oMigqUy#_eV)Q z<3?;pz6pkr(;Z)thNWZ3Tu^XIU(m2~K2{iFEAS`~Gy5VW_tC>i*Cl0kv`b9xtW+!e zPD_a1*)E4YGCWy+8(ZVrP7}Y9URLg*>8E8fyY^0u;VQCkoBQJ<_5zdXl(d!zb~b;b z)6|dkG)>oK`*erN6Q98nTc z*T4b)onLqyA@?UYxy_MYQjd+D&|e(Pm(0oT&BjWQ4@?kFIoB**?M#(;rSUW9SnG<- zSt-|WaL6iG_P3uZd9eIpr{TtNWC*$Hh2Qz?uBS}bIbRfO#e{zRE!IEy&YexD%F}@N zL-y@k#YdI*GK@^S9Mw$gu9^2z1mSnEkrdxz+MPN|ZNhhS)_oYvhM)cLTYGn3J-&{3 z*gO%dE$+F=!pgEJp;TQOxUvmXY0MZXd)l&aIQ@q%&TOO4FwrA~ak$>;=zXV4zzr%` z=0~OcyNxrVAu`L~2ctf1)jOUXrl5QhI{u_3cR4;2>t?n_c`o(TMz?xA14+Wh$Va%BY0&2$WKO9mM2sYf3h-OCY*=ZOJ$Ngw)1D_iorRZXHQZi4&2K7qT927nQC0Lrg3 z(#lL522bDvLQQ|!4#s}u&v;Yf6v=QytSm1*VR`JzNHPFHGlJ!`WMgHC3lNnE^`=*0 zy?^9tJWsJlLSn+d=%5(DNQYCcv%)omexK}hyZmUHWQF=7JRFKXB_b-*?UD4{x!=dVwazRjll3YN!e1GQ6{ViI{ zhkd)N+MWKT`q_V0)j;tA_oAca{;nI(Y$Pb7t7Zgb7)DUREOEf@igE4Q;TqcgkX-wd zJ;8G+7!?>DALr#bk)GNchOvQs{BBN~iU1F0&RMR&ou$CHl>C|ZrZ@PkAenI@K>Al% zQ7|N8uxRTq4vM*lnm?oa%}HLn-3G$yJC_b75?=65k%LM)%(H@{N`65=i4pdO>Mz+= zLeav25B?f086=X6O6;%!2@%ZP1|;Nvbnj_2aSc+8ZOx$k{x3Drh^ zc*UWh!@lFm$>1}Uo>u2rUqXSar;=W-2Mqo41Pl(rQD;>HWC;@e#W@Z29HUt(caNqC zC&6BqG(7E8;B^rX*m6|Ejm>-6L>RWQs{?%J*!{N&Cn3FMX$DmBS8~(Emio*Dj(^J_ zk~mE@d*561epZk|Er>78iC#q_4Sp0Y3GD6B@JKKrmyoJG4WGBh)HqTZZw>kH>(OJH zlp#iE)N?g*Z@4^*MV+s+H!!1LJlIN*`JxC#o-v0{2|BS}}kDUMqX8%d%;Zo1pF*{G_rVrzNd`M2ya!T0DJTesuRVwL9u7n&PS ze_~l@1G?`(riUCq#<3T)^gi`sw~pk^JSP})C#_iBKTD*{^N7d0$A0wJ3#IRYe;0q4 zA*$YJb_LE1lo-`!M^fB~U00SLiLywh>%-_CXgSb{ju=7v+FzB+78O;y>TeZvRv&RoWxTLP?d+9Zi&Ypua2+{3 z?&P=TOQKt{%~L~p0$j8^;iia9j_>fKovkcwq%sUQ@nh>Z!)%cfJ0$;z4CPrz6I0OU z@+^ZT$qbq`@V*LyaM7l>CZ1ZQo!IplAN5a81(Tt~ztAbYc(d{@u2@?f2YdnGcoX!#60Ixw-Nvix#$k1X*NJg)beTLqL8^6*<{2f@@ns|Q}RjZ!$JIHK8NbS8xrmu#@ z6ulfiVr7xxNb~dV#acSrSX_pQm;bUeyjdV!{OZy#M4(A` zwu81?V`O!?oZ`D{REMi+x!1hB*6Cy(I?k8T%kET=uKQWo39E}=ca$my=uHTEyP8y z54Nz1YH*)(w%#ztIo^C*PQOjte`Hel~gpFN_jZaXoFZnUzuu<)94E6T<5ZU?s4>c zpU3Uo@d?+!hgYmVil!6X(ly;KNm*OwbI8{z3v|%I_4HT>Nt&7^q0@@SPXaA`iAvAR zSr*v1muELwpeL3wqu$P7L5q4m)-N%|J6fE`4!V+xyrOkr+X2!LT$k#tFYksHJH=n z3F!I2Qe4B5pnFmAer;+($yQcgD*uHlDurPx@2dd)1-RjhQe(5`*~SLS`q|S9v+`3~ zQ>IMi+hcTX^%}_YWT=}koWlGSwSH~mOvRNJ&Sfrc>H__ux(6*kTUubhdoQN>V2}J< zR)ymBx4g=I%zlp1J+QjI7joltSLskIt}qG%d@lfB@0(d>+A&l+Glwv&La86NxDmfT zNv>`p7eT?@iBSF8R6M^wCx1D;HRt!F#6s8>2mF;&B-MF;2m~@G4CaiZ!p=4aG-$V0 zYR+PtSNvY$YwW0OPYxL-i+8&!G0&s(?(IcQ&Iv2 z0Nx*-7_~pZT6#2L-so8nF7QMgH5}#22w+dCGMyllm->HAO8q%eYuJ_BHB7343cyG+ zgo9$W05T7{CPl`Zw^P=q+#rx_`T2%M zMCeCJLfZT%fI{csusPnQ7Xv@XSzVNmPU{iX2w134>~=VfgQ82*rq^p^97wA647vgT`a# z85e!NpbSl#8uA*dnopv4RMby4F4MY{UFn^r{Li3l%Ume;QtBh5?8wCixw0*zSQ${* z6)@M`djm|Nz;H2K_j1ACvx90`pqKN#`9b8Cd=@J|$6R{ZYc5yw){(D1GtABWH=Zy` z-HxQuV(8LOB`UjI4iAOJ34LY@KVEmPb@XIC)FfA6m5B&*8T*hQyR{mweAL1#*kA9n z;O}eZUE%DcD;yjrQM!F!8~hPzPrCH2Fvr-ItjJE$$pV*gv9>ye(q2lsB=uQP$h%X% zlekK6q~fP4niGy&O9mR~_I;)G@;?e;L8#rja{}{3_rR(d$+fAsX?PiFx`2ashkOGP zw9A><#);kE3G}H}!W&WxH1$sg*P@*n!{=#L{PK)y~GHI;RsgpA$#8cpY~ zct*9kjG$l!k{*0T43n={dVV!idt6Zw;lPW%!2K;#E>?J>D|V%r^A`&*)MdYZJT>jL z*;x5TTDFevc8OARtqyN`Wyt;0MTTO-DDG|wtNxUqM1$~ye0&&wUtZ&eqI0=0|Y{WT*|Ia1An)J!bjzf9y3P874R^|FamuD zD47YqkS6Zsd3^fEq_zq1i3zN7fM#ldxb7Z@0Y;<&n|qFI`e8q;TO3t$s`geh?U*oK zp&F$0CKJFD-a%BYO^4KA!5J4T1f9rK@Izkpt4qui#^S_s8AE_pvL7$dKQ z*TXfMJYx+MCq$g?pCj@15ZQdjbAm~v`@A?MCg`$$;e!iKvcv423 z^QOF{_mgOGh3-cDZ={Gyr z_&&UYqVw>f(5K`SHp~Mm5XB0N9$~=XOXd$uQNj=bO95ChnZX9K@n&#T?vXPDfqt07xJZVvBuujM>H*4hP6HvbJ~#$K=z-vNQnRCryVz5?3YqR02@1#K{#%aX?h4VQ45b zcmM<+1V?|eCnx}P7(IWh<1mpP1d4*Z4r1WAfB;C4dhrfKPC^**Pz;nD$YOJ0I9i3T zdQ`v*UjtnCM$WL`J8L<$;~1_X+Oyzj(IKG(tLOn!YS8Vny{ z@>lc1XCA-~hhrD7h1@0O)T))gw+GcvsVwxcnaCv{EQzu|qcwKGyiwb`TTP(}njGXHh$KxOryTWq$B1F6I8!hh2O<$rL^FOXZoKME=~3M&0eN93bd- zfpL<(mU)+asMc@#Mvb?Ws^Rw;E;iny$Mb$bu)1ovt0lOm4f(~cAmY<65o0ePN*$EX zrmHUhGI1J_t=@d`{#mmFd?eV^Q&jw>g^;Pf)7JHdLzQB*87{77?Kto0xMvGjC=&M5EOW+c zXpXOY6|Uf)0am19ZLde+hX5J6c11*#mSinvk^A4NWc#m5P)?v~|Bppv*0~T;-^rI9{w3{`~5)bC}`nF?zGx z#@S`#(Q@kl-1Fmze)A@u^#@9=c>MA>$*eslP^G`Zvb5N|sKK{mQ*V?4eX_x+nT?*N zalRRl;P=w1HG57g+d^AJQCZh4&g{?mbJZuj*>jJpGL#!`*C>{MRd4-HML#+BNUG#EHx5`rs8QUMda13u9eMG(lKCYTHCS2gO0L&PIU zkkI-^jv5$aR|blKRsJ6xJ^?au7%A7>eD6+l!ALkEL&*RPl442Nll#UeUv)cn5=YV~ zP)$eQ=SZYMG+hSAy@o*c95}KXP7(~*M%`ovFuZos#RM5t0XkRn?DdjD!7zh+HMGoz6C^Gk*}xdzg{VaE0-2L4An_I# z_)DVjA|u=a+{fkuUkWg+!HA~@f87&ENbQ{u_}}LPin9T}}BZ5K1W#~XT5z0gcc+cy7@$?+tH6Ta*1qVBL@ zBwd%m=LAwRv8~~Cx3MfLmwax@N%=M`ciGYizcDPi#Qug{`#^)V(iZGpR*3ayNFiWv zCT;%Yg?Tn;SO3Pvyu6Dolgt$Pq@8;O(nD{uHM<__6!t9UUP@K#N73GQB){T~9Hpci z<4P6T>Kb;ktBMTne4`e~@)E&sIdENQj5G9OYu`7~bvsRTeRl1z?i^aI{)?VNlekCC zXJKVy+B;Z0|Abe1cpfcW)93y`*4%NW#+1!-OVtut{#3Q5fvBQ-b<*gu4x4f6pmz-x)Q8wc+4G^!kGq??b_{28Zdu9+dS0=wgR`1Va^@f*j96v zE?=;Q{AtjKXi>F3-EkrPfL<`s@S z(Cl$t|NBt^_k;7j{U(%~9iLt{7g5yFfhq?^mE$`_Z>W$9l{seeXUdzmz8$X$3_fz0 zNc_d*naeGkU7&S83}C%)Owd-QTjWCq)4F3puS?Y*tOH3*JX`9t7=HyB%;}BFw)~fX zP3M8Ef?E#|5Tf;EuVktd)#&vh7trJcyxkI{{O|eok{tE^hzi3_4LW$*rN)J?Qmy@$ z@GmJ)5nOLC0(h_C(Ayd(aO3hP5pxuMsRZfvoFgBCNNrsu!(1gLl_W1XDWi)1KiM4& z4TFIN4Z44?71-@F^TGn<^DjNF#jfDTD;qdJ36mB3{oK$>kk1T9x32)H^4{v<&J$?GFZQeeKn zog^e?9JHCkaVAg{99*Xytpn)yWZ-y+!;hT(I=Fwaat_Fckc87LJ*r7!)y;@7k^fUK zxl{eySNWG_U%a8X+L`q+Pwk<%iyJN!iw;Q%=1>$p(4~A8CwtPS13^pt$BA_79TEm3 z!hx@gB4KmstaCTszUdc8*ch3y0f@{;*awP0cxYg(J0u?XLQsFzBA;#(`vHd`I*lBM z;(99!j{626=)R8+$DgEz-MfuzaGI&_b*%9#-BUQaw^>IHgp<=gob@UA0r`@#>-qw0 zpfFP4HZ?#}t^J2jFG?J|6<^ALo3?t>Oz5`IuInteCESw+$NTFo3L77A?}>NbqA$vz z-v81kRTwtLT8^1Hkf#X&iRsn`fKmr-Mu&N{*qwp;$qBXyT}BAQ@L;wB^UWEXX)3_b zh&*ke8czIhFd!IxCi_N!jnrKGIQpfPR2xJo1%*JNF^PvDwB;>G~7@ zQVZ23Q}9_P0C|)?QPY(DS0!&Y!!b^`S|XCy zKNy*Kil!;HIXgI}+mn{ko*V0S7_|JPJm`{p{nOe9Vi^>B;a*toh zNY>_;v-=$AgIA44ebwp@a!75wJN7K9j;+SW z8uoQjVUb03=55d=@#Y_9`Fs=Ut|9xs?0ce>@0mn&q+oSJdb^!tTO8;mb$%l));(4- zKPebA@3lPn z@G1otTd9DCo-AAllf-ruy4anJn=H{RXLG>6j;g|@m(&__Lzek=U-sRZzRO1lOrtOJ zm+5k9slTfFKsku7%a$T6ENphjA3uy9eG=kh6ii90n}D&mc!E$-XY)ycsx6qljq9PY zpDzzbG!`4}xmvrE+7f*Jx351b!!}L5XmvDjt;&0$*g9U$nbVZwscA2!5>S?vG~K*d zPzXIIrnkt|yfEO5^dk>cVc0*&Hh$%zYA8nPL(Hwwk?vVuZpJ+&#LxCsujZ^dalGUq zk8X*2y(traI^+1KZEu-(_j%t<)w?tI>hVd#CUfisw!-|mSM{#>X=67C83>oRW^)Nc z_@hYvV5!q}p#c+`qTV9*kqk5GkA6Z;&)MXHw7m;gzS)ito45k#Ejt_oX>5cfTLfXUX@_N^+#UicK@ zbUwcCAj!Nyi??H{sraN8NiTB?aleSuG-iy_c^*{zg2xn*m1e+7rBnP~o!PuP9z$Gcf(C!4f_G&|`v9JI zHr460gE4qwW4yYiYMyx4c#(d_<1JDCcBZLe=D9DE4fC#q8)2D2Dpnaszf0h1)i*7) zxyKd8y*&dyiKySsH2Uj5(~gfdkoWmaI$)6ycN3CquawfZ+R8$$x+k;L>%Fd*;XYy0 zkq~3{maC~f(~h3ZUsXWo-EodvK!+KO{DW8g|IOnpPq%l@9Ky`Dd0%sz0@6$Ox`Aei I20H400LcNok^lez literal 0 HcmV?d00001 diff --git a/demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/demos/icy/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..beed3cdd2c32af5114a7dc70b9ef5b698eb8797e GIT binary patch literal 15132 zcmZvDWmr_-8||54h>`B@4yC)hOQZ#cM!EzfhmdZRPLWXQlpaz*O1gvrk&^D_^84TW z@jlOq4`=WFp4extwb#3MjEilFPELs0YL1Js)Fn* zzr}qsbfZ_wbNOa4S@vf>;bE~>+%RD!>v%IFV#WTd^7(B=#T|Xno7mV6xS4f=u6692 zQq~7{i;;}Y46D{(Y+R?~SpnS3W=+e#JKDJX-SSUi>9(#}mwE5Tv-r0dn5ZY||9_k1 zWM~Q&Gt=O&6oAqZ3T;9&9$g)JWBOFs0NWF6vYJZJ24_?zn}`jXIHjr$^?F69z!2p< zy%t?XyTRP;!zMXPY^&6kR$$J?UW%?3bCC4XDqr@?ukqAzCEf6lUi%~QE1bZLYf8h# zNIFjy{z&gk+iBasaZQZklPN%Bhl~H-pewWJX`t_4w;I)?=gcrEWq1%u$-pwhg=Fn& zj3nJfbY`j%G4F^8@$CZRg?Lweh*w;b>{2YdOIAi*x9?W^yUNovn|q?NJ#6TPeU_fVowC-#v9#b~gYH6zAw5m28>MUeJ4Tj* znIVgljj#XhW$ zhiz?z_2X4xbgPrk6@%1I-IDPigjXj6D_rk=N!MHKhrgxgN|sX9wAG{r8mKBc5uYx! zD6;oWKPFPVaeKY+;_tfGk8dnA3*mxhD6c6ylsqfXvWFU-T3PF_*(Y_!aR4ycp@UiK zL{0B(1-*H{F=ezF{RJj(g)4PzJx50@A1Bg2>XU|TM&*KjHze0G!vbN}?9#L0`)Mh& zSDg1vm!sTu701b=n&--{Q{n2DpuDb{%No!D^gwg^bAW&J!~L20v4&-T0QrdY*80B?ozklkW% z0rk7=VB9&#oB_RdT&RhUD^ z<%mehua9i+?=)hn7$VmdJdx(xObB8b; zd)9+r z`yz+r{dSM5hDz=4ys1#(+WoWqC+KtBRNG8x2R zkNK+s#C-E*)s>kZCpyIRfB`}hQ6FwUXyKlgYs)!v{kjY>{yEe5^Qr5JEe^d*zcU@; zK#oE%1w&_PZ%A@P#G}S>`1qbU0tkHPO<2-5_Uhe0Y6$FovD9c;Ov~qVD?l$$zpcmn z8BGk}4~3UeEkzOUc<9FqtY1TqoY%qGS&?kSM=O3g}NY85}H(VQS~6J6eJsX=%$ zf%etV-q-i9X(#Qm$6xDNs6>@0-*1b4*6TC?1v|R@FkpbQLy%N<#0-I&1swvEMn?Y( zQKWmqz2#a=uq>R|^cdhnkaB3z*DB@@Q=Jpj%9EBXLuo{WDl~W0E}qH^aARnpD#`Dn zAO=+iepMRRSE1j%9nTDc{=3ACQK(De^37Zvsl54F9`aO8G+M-hmV$3r9l|3HavVov z=cO%-IOVsvo}L%}Jm> zX9gR60KV3P&h$KA;XH%c12K@uFzJy5i9S6?U7BKXLk4&WhD>E$HbfP_Ojp5OF9rfm zT$`)n#dWaGB<22Cl)AZ@Gv7i0;!*>IUJv7##H1X4+Wx!Jki<;jka&jGH6W2$nzJ4> z6yD|%yOMzcBZj~}DSWA5Qj5Q$P>edSrrCzs=X;k&irN=Q9KBAfO4RZ>klxjm*H%`2m5c(y7Pw zcP@DyYA!WftG!MB6T>V!I>_ym+&LEFyikRHI`-j@U5hGl(;JWZbO|orN^1|6{D4+0 z>5k@1pQ`!&UM0WB;(#4ds`}Zu6)B_YebI)X)jZRhJn}_frc0jF4SFi~JHS=t;knPP z&yEu(+8%qK>YIlcGahTfF6Ze^7edgT$J`6#2qm|n26OTFDY|d8s~3hl zpLtuXp@mq2GW8<6|E)D{#yU2)#iuPY!=|5Hmo-<*yo(QYr$3HQqx#%vtHjS|I7NiRxC6lDQq< zTXIalFx_Ncd(TZ(!iRaFymyh~tc4h-VJo_vaMKP(y_b-@V9j{@6aA&=*?g2r3#HBa z-Q(IP$--;P*a%%PO{^%D$`G{5nl&>sUgEN|s^PG}Jh>ISvD%;O|psp}p`-pKAK?pbIHTV?a9?u}(q*GCDRrVm> z0lC9`wd;C96R!Yg%?DnK2`W*_@jf%9IPnwdr@BgGxWS)z)J>cDasy)mt3Y7)p=txP zM)#~H^+!85n&7b%$l{U`iUrdD?1+BT#+yClM)OQek##8!6GFE0paMGl~ znJT5wR_VzqeBv^?U47rJ0!hXwG=8QSN^}EyUNDp2J?(D#FGFgCo^@;lRCMe2zczB^ zM%9XHn3ccHp;wqZ^Uy8mD<>D6R1W$5gqQ>%@AfWuiX0~?SIt2=9&6BS)f-v(V+-C6 zBfbm+ypV$sk2v=A1#JUeO~Sbved*o%-1Huvn%MCF?%m%fP5;xCPP|-(b1@laO;e4- zd6?k_0KN;j`6NXEVgi#X0MXBw38O@O`lZ=y4(f@Vx@QT9*Vpgk{{$@lzYwyh%?NrN zGtU^kn)F6?fKBPA{djTaw^L#(7F&HK0b>+C#os)3 zXBq#MC^QE6lzK^4733pD>UE36G;-{`GpU&0a|`(V-vTwp@G~>2EL6F$*&3YMPp-<3 z$pGu8`_-xR9b-}m{9;+irLXejrTbK_!ep%zGnh;U{^iGo^_=F2)RW>Gnr99OXB*dm zfO+ugGg0L-0>cKR_lG&~a#|_x2{kD1`&ncdCyi6M^Lm931EU`O+-XCCFYRAnjs5f6 zUa^V+z|fk5UB$rN`lRE$u7^I~$Cjw-;Cp6f)HA(2LU;};f)pd4T8-D?I2up+3G(m$&;vg0~+JOD};L`gqqk*eJg+xpbq{T}SE4${0xj>in~=ldQi1rE&?>CiYw2 z#vg0Xtv2hPZfP@t{cR}nkn`imMzN%Ni-Y?Fuhn*~A(k1`mx6vQI)vLRy&;WKU0n}B z@ZJ|)Fn=>TPu!<>B>2~#eYSLuW5D_)A)V?!{Y4XguE!i#eiyl1d{uE|RTBFea zM(g%RB^85qT#!n$qYwxcyR1CEXmt{nlJiLD0Zs8{OI%+d`MxVXSwT?e&2t6`t3 za4o!LrCv}!1now|E(qC6Hf>E@-0qF^3NbW7_qjxU<9CDT$8j)VXDt{8H;2Pzmw@Nb zJ}1NB7;d^GlLw5^EU`sTe0n9Pg~GmQIXwnxEAeh@zS%X#f?&FG!fvUXW1I^%m4Huq zFb9-|D>sEz%pg}Dy}4S#5$%jBg@1FfhQKlNSk?MlP{oDv8s=i*#C%7KTfKRpT((!vAA*0?h5%4doY~|3yq_DA32&6T2RHbNq-AItD)b&W z5)Ng>T|a!hlRxqb6(lwy3n#TR>Q{5$zoTQ(7Yp23btrx0L6lb;lMIld_ZsBm;X65W zhL~-DK~O*?iR1lG`e>ZDti=^0@Hu{22rk-ri$|Mhlfjx zz}x1wtNp{S65T4sftJev1F_{RMAe{B#a1+VB3lE#HN&bH7Rc8 z9d*c27p;2oA4ZYZSk)abazBuwEu8=L?5J?TG~{R3V8o868I?F z#Lt>o_|ohZd7psYl9Vtz6-np(@R&^Q6yKF@# zKK_Phwv=G^eE6%t(B0N4(**az{Z$|8Nab8SLz)m@0bPk@Wo;!3I&BJu}Fl z{}e^!Iy||DQ~DlD9=@%{OB>I8fpV4ZTC})4v8^-k&+wR4`hMI|wtCe3@xtk*M_gV& zT7}a{1ERd3c8RiWPPBvInQ4k+GPxSExF}CJt9v>(EoD>AsA|3ioYaprn4PVQ}7|zFbK2=iyU{SL8K#I2+N-*;IUC zGNwTD;XDPHkYcjzxc(jT?|J#?A9c3l*&Jc_`dkI4Rs7QC{PM6ty6TzkxCMvgm=@WZ zf59SoAflkydVV7?TYoT5`U(N`-HxGa2z_V)YRIz`HRRE3`12J1-lEtmojvMCPtH+1 z)V=IiqG9TR@`K%FOk2#6!1{1OD;*%xRAYo%)EDc|<)I;%EXi}?^()_B6K`pYE*`4Sg)tmZ&*^v8jAGJgK-rh(nO znii&AGyPojK+Ee9+EI?hH-rm&m>=`lAO7{E>D1JKm7n{&r&z%Cwi})WQZ*k0bJ6u=B0Pn1}ek~+ch_lXwn zuc_uu@YRZb$iGWq5BG|g|^Wd_oh(t2hEHAQ>~0CE_L3eNN1(NZ={TZ z*Q&K4gY{whUfZO+x8Pi73^^HTU(N+4u|z~}-7IGjQufEje1K4zazaTk96zyU#Oomt z{bZ_BZ#I(ren>G~3QNkj-ElHS()&+TCR+bjq4vO-*_o`jyU7mwVd?J!edfIxKubK~ znqmum7Gd^m1|fh?4|kW$?Yo6*!cTvq_fNlm%+Olmz3Wf^I(4mQ zO~z#3)9fPojD(VbPK-c6xq)}DM$borMa#X!P?x0&SBqzQG-BST1On6bd~bfeDWpmL zg;dMkgsT6muQ^9L>bR6T?+9!G07EA3XvMR&Q}8^MSfgNeA zEzFXFyts}my(yK#E3|dx>wH+PW-82HFn_p_ z{;sH%Izw2f?je+3ZGMKbJJ%-MUk6I$Q3lW`X#vZ{OC+X9zuDb|vQX4W2a2z2W*Oj)w$<7+lPbGYqEE4!Y z5j4*J(;o`UAc^wryi7M1qZAX{UySopT5y$cT@|8wdo0j-F+*z55(QN4-0X9E2(%0w z->Pj3_BQrPW?JjaUyorsqkqgQ;wow+pkug_qLB3byas`FE+^x`c+_Iv!A2o)GczmY zAV6d5;m~?7FDJ}pHp;5ORZwuDRq(s2BNghbg+aq0nsM$z_3LiUp~h}O&p9WQTkF%8 zM=j%0_<0RSBT*koU?wS=bWkoexJwQclztyKASoPa^=_gN4ebgz`-%PQ4pC%-=4Vq0 zfe#O}LUsDlrtPI4qXRa|3{g~nzfS$+u@EI(83`y$`zM*F4ZrP)V>J3FyYXx}ZGKDg zcnAHvt{Rs*n3G9nWAYgvN_?47{`Qg%8)$u7L&yUCg=`X~0xo?Nm zOT?BaawiXVZT^N9@PB8m9mlRme!pMhW#CUp&O)q1Ff49V5&%z22#hJ2F`M#8APaP0 z$_Rp4aJOUiQWa7(@mp|%WL)nG$d&Zv_rF<$bdOHX?n0#JYw}R-L?73ZR{Dh~d)_hC zut16KfP{BGRQ-I6p%4Q2bsb~&j&!tu<3}y`>iw3ht$>i661@OYn_Xr&XV#5d@S|oP zA@W{))lxW_UJQXd+s5{jYwPj)u*;o$QivH&LtwNF#bMPtindqcy_Sg_0jNOW`lS26z`VMFkJaH+Sv!=ug__rdCdmKpW)`?T6Ob{o>w!vsy+D z-B>}mgAw_|pUbN&6M&;nPF~<=LStpG+Z5n5r71uf?m?gQ-F4dx9x_V$5%CbECK$Gw zzJ2<^i95T446#0C`xOGneN913e!;7o!R%C)^uMCe0=Tn<*P?H{k7Z&~3QPz=NJW=T zj3CEU61-h1U6W|>zbw|;d_CCnt>k5|J0cEO>N_La+8&pSKU3E{M-On-Vw%ehQ{LlX zxIB8%LF!fTxKT!H6<|d62Qh9ehYjV*#xl%&Z~JpAI7ZChyU6I`b9k!^*geM*&r!)0 z`P_*C_$(P{7dfN3zXX2lZVtYo4StL|JW2|=e>3xO1G$K#=;n=dYTEcI0n01mkFdT* zZlxjCcP7Y5aQ>oPVpawo8YKRl#hc>oIaxO{*fKmVk?3H*sQ8bIy$$PNS zm^QUJj;!T<|8X&Tmhjigq?%e(ppMY%uLMndna;mU(!hA{kXVc%0H6AUgIMB;Y2q3as&sY398#kE0 zW83CIlm!|%OO&SzQ41d zS$iN9BrRi!79O=xyI?ngbQV~+RpO` zgt2WYwEdm=V<3qZ)gKkzTAP9Zf$LsE<)l0?cLpV{+UkiYYIQGnS~Bad;H{xUx0IA93P!Z$Ub zRs}&&XlPF1+UESgi+B-d`JNY2Bfq~xE9@Kpnx?;#;mg;m75vQ*?*d4Tztw|nTLS^Y zH-`iqEf>b-r);F3Q~_D`cZH$BGWu)siXg~pRDs3)1|az7kgqJm2#$NR_{p2Y23-4BY)ULyBEa^$KdzDc9uq0^ACB~H-gaD=Y4z@9VVD}V$kHmZY*Zd--RR|Y0w6WlPWsSq`9?!a)pOu312EGz zk4m+W%p>D^0mr(5WfHSjGm4$@-XbLhSU&;M=<@H`iuaG1?)qq49eVAA5|f{k5V){} z8uBYG8s*=a?&=i4q?=aPx<^%phdi8kO`X$JJFg~83BLUMcYF-+MJbGo^^{rW9Z@->vG69q4q3;`%j1PYG2lz1;eHLUAMDldZP&8yIZ=zAT!_W^5Gh_b#n%EiU zZ%Fin+oCFPL;K`A8?8xGtUp%fnKU^o)jCC>R2*P%Cfi#_LmHjMEJxhmc}|a?*)R;# zbyHfgLFFpb00`ZaHUnRQmT#aiiK}x0gu+pd23%n_RUjE4QhiC3{(j_k)DA`~jo|p# z#u5J(u73}=8;tpFvdM1RcA}^T|4=?G_T`x+6LdEhUm=K9erRBQI z%4?gf+wXzRB%6mX!*t}t3Kv1nsQ~!hZbTr0bFyUkaDfV!snDh2##9g(Hhul2EW747 zgi;TxQ%{3b>Mc4N=|y#vIG(4HW=>NnpTpmFun$Rj02m`#o`ex0ONfET z4F{r7@emkC;R~!#dbkG?-M#lhIS+y-buu?tP{T}iowTIQI|Q3D*0|PFM=K&Z8(ngl zIFhy237n_38l?NRLR4+dQiB2V$&rEkfgtk?a6l=H7ExIM41_<)P%KaggZNGFqMZAL zMY&tS8=|yPYSZZFA&!dSI@Tu^@(_*Fml5a%4cZC)7jK+63+eEuZ3PCX_~(AjQOo`= zNPnlQ)GVKn42^BzfT?X|&6O%hoWj^?UbjQVlhMl_0`x{xa=q49T>Mx-$^2R5#O^pn z>2!Sz?&CdJ65j%GFWASd4pIV3tzxpdURHySx^q=6dVRBZ3a7`JP?PSBjkcQPh@?pe)x&( zA66UTKY_1wx3-Ur8yZU zi(!nn?u&oDM9#cLFP7RGZ@liCG@JKro%!fz2GqHc@fk04klM@5*ths6nRZJ%lI|p) ztyuO1VIcggf?H~xX6i7k&p4~V9`G>zjntUEflyoQ^SD~$lBIr*#v)di`!hHHzZ~Wd zJ-QNEBRBq)fz4l2#_xXm8YV8KB%v!-2Is(P`1=|D+zIhS-F?ZUgd{4ZvFP};cKr74 zvi0T|HHv$hL!f3guj8b`g!f?>1v>B0gS~UEbJ?|HOB?fc^jFhtGDY1pfHBHP3X70`g0Pl;1%{(WPrw) zLA={hi)#y_&B|CHDe{&@tUa4*`Gx7EV=fZARJ1+2VgS0L3UZC@{Wc`R>bF^Y|J_=) z6@zu_xnjZE0yN`sSuL5S5%*$tR?_Sn;IN zk+q_-5?}{FkQtG0br0boxa+}qf_r@ocNJU^!H6bY#l--XDfxMU;d>>l#G-kxw=U|n z4oX{wIsAKre7G+PF-;OsE5di0T5MG_-(T zhUl%sTLJ_I(vT32H{#nS1y2{d~Bk*>z;1fMDT#15#7$-u6_Yo!o9QuS!|5#-{ zC0)T!;?6@2clqJa$)sMARqIYV;r+ zk0)L=B>56L%h)=EE^|VE0=oK*K#|t8- zuPFs$^fLQzLGuZ2ZmXe@id)*N@}ZDUnL1)Z8A52hime?+&Bx7u|5)K3ImXEMUQge< zM`(Zo{DDFnt^k6F1jF&@18xC^>12aHE)&2k zs@Nwb?4XI^>w*cbU-d#dTM%R#VlaWL2MW8>deH&l@xZNi1uJB>M`h5y{I|JcKhaAgcz;0;FDw2<~EhliI5igwCTS&^FLFZSoB$eD>H zD10LcRu|WoR}}rm2%pHJGsgh+eOu9q0~qG^b(v)v%8_%bfYg<>q0IYcTAhF-kNC49 zGRJPK;g!YDNi0#B-0xu-ox&gG{wQ(DTXtXWgzKH6KjnvR?85x$A$ZN+G0#8>XkFb9 z9zWb_5-`)TxAZ%jIz@ik!2)usZWY?tyjjOd<;04s^5^fjU8zy`7I$70NYN82zW6h| z$X=NbEUMsfM*!<{`)e40n^{H-)`KJX!(mZdv-cC!9L+JvSVnSO(VKcNP;t?UGtk!b zSPgVYsnD9ejE;FGyPg{6YW6R5Q$rGiy%J(H)2LXP4eT;Slga?wulT3;iy&;Ia=@Rj z!U(jtPyK}8ZWprMhYw6rMgQS66{Y=o_anEEOn1Vj*{8icX-1vaY{+vNoJDFj0{pO( zMG_NH%h3QMU|oF!Z9ocohL5ayn*Z36RiYk>2PU&{vAU1j? zkRdJ8tizF;3llfJ+zh|bK4_O(7pI-9w^Y4gTB0F9sU?J)5ad=AE{p>o;579Jw#@~5OWbag~+3Mnyph?f@wbwu8 z=fB{(_w#nycZtQsdzOuJ=!+1W3GvhPtLJ9m8OpCA&1MCEcLm9=MUSexJUgvMnqDuz zd3!`HT>912mxR#8IDT6FH+LT`QmrCDq@~pdJ?clm$SLSgUD~0uNXRqN&U+KZqw7Df zzDBzgap!mUAGRk7ciu7Jh?&{>=jdQn1ag0rfaz2*?e8k)dfhWih%4+tNn18&)E9RC<4z zeXoG((fW36d;|?kq_y=zW+bjMr=HBC9G6~Oz67sXY9iWf{^(T=lY^M^#K>_LyRTd# zP2auGUqc^`u^ubR5w4Vs@kxf)dChil)2=KRi>a|4o@pNTPdUTmaKG~`#_vwS6!#k6 z{+4VvCc;c#xdy8hCDR;Cl~`TpA&O_}1i*3^LT54QK|MZcr> z_WFbw0$>}L+Ody2Uo6A7WL7!Jjsi|{&4b%5B5BgX4~e|uY}|YIqYsLi98Q<{`IYRM zg6GJnsy+;=)vhXW#}ZcT6Xz)uFQxpe`U{DB-KsDH#Ubr*#odC)p9`{S*v9t${JC%W zNwRP4qvDI=x+u!)g-*90R-vYQbpgwWYEHiCSSi3znGDt6hfK_&?&t8e#l%}MMpBFl zxE>$Q97^qR@(KeM*(xar8JyGv7=1lKpu)}4U@!(Ggn@EP+h#cPr~OUH-`QqXhlhNd zjl-d^u9-i0$Gp!aVs!#8LeIRnr-PZYrSHxBwm7LpU-rGj%`%3{jJ$YGlC;!ih7QtL z?Zt!uX4Po`%PTiH$H>#58o08=3zvG`f%ntyD#+pAjuhI>e65GIil-1!j zY|&2)#*BgVwZTom3H=~rSH4u71~5Evh9-a_APuJ-&g8=GsZ%XZ`qc>;Jya=i6~{(4 zze`0_$3fz?k)M$&6Q&2k9O@)|ms0J}WX+PQI!AD_7a~rK?MmT=*{6>HgTC8@7F?wW zQvP*i_&d*0XyEkG>uvdgHGS``HxH~dcZ(_r(SdxGqHQ%PTNR$W9pbwF`p%+Ykchrg zd;ZKP$e_{BKpcRu)<0Yc9BtI9zz>QDE10>pjI*RY^gW>ul4rjnPF^nE9*z_fjWPsx z;rz(NO!21+*w8E;HQ$iEs5?KQdY&WrS6@)|)f2@QGGUNb`pZ9QAe|~5VNk^MzNK=| z;9mAK2uc9Z4dpSjUqcHr9b7A0l!Z0R|#ihlchp@I~KLoS?6Doh)_ zu=K%3UGOn9lpxZdn;Jp5l_rCG^PfI$I}&ztJSpaMC0Dy0lkx;${plYda`3~ne*P2} z9ns|~NVrt6b{V?dJkGZr?$|N@3Us`o=$|_;^#S3=1iixlG*FRl!;~WTtHWQYrv4vi zfe1%Iyo&Usa1;vcWijV9f7lG3%s-7n>1JhqP#>q+%Q)cm8&5xe%t7J#7D4;Pq!ZrW z*g^ioamw?yQzmW9rs}H{8t5HMq^f8a;yr5&UFlvWAEjU8sr=MHK{6`(@8X=pB5QW2 z)rThuRkfKID&7*$00)V;uz|kjA&u<%qJ(-ftQI~Y0{FUqmAQ!dX>BIlbU4uR1a+&@ zkmj#sFi6@RVdl;od8!Nb$k?GwV+%UZN9AD$I^SFxGhyZiYBo6^FlHMmi!Ic%74vOR zTbAhK$tdDL$9G>b!@nzjgEd46*Yv8FuSvFht22=+*rv|+4$3b zZ!3S9Pw}ln%eG1#?EZ^BG{yxDUxw|9&~c^5s(?Zdx-((jv z13BIiNg7v<)1Ffv6D%?fSr_TBhX^49!*M=iw(6`RQc?jsR0}$}pNjkz<6%^oMiYn`-l$ug_5e zS1DRhObQInw-Hk}ce)nOJZ9INf!2B`WzZ4KR@X3E!~FpiZ)K(=-8Jv@E0_O7vHoC^ z*mjWnD^9@x&n<51a}BtoDA5<;<}xSCC+OaWNZ$ME3m&cIdTfwC4Zm$M?e4xF(O$|$ zrSzuPFiN2WDjj&+{!K)`jnAnWe@$`zFB!7C_VUHc>G-^C$sIK&2Yo??dG8%0cY(-P z1rmXM{)O0gYP&rAn2vYb`0|l9nE3ECc_<5>4C^-IkP5A?DipVEh9TOz&DpiYx%6@C z#Dno^dc`iX8XU-yP(<05{clKW%B~$F$=^>896~*gwp&*&IxfA9fhpjF$7_{qs|GRM zLX+R8N{JxU6-9q%_r?JeOsI^WN_t7?pj&xEkHMow{;zu80jt}tvI zFD>(I?F<}NeZm5#`PrYw0M)P3Kz3*VPJFh2r$Th$n@AOsr`1dhA9WkD|k=MnY0PQDYtoFoJo3AVzoQ(6}uJ5 zwBXm2)hE`7bwu6b&XTa}cPj9p2ZnQpcF_$!1-P{a=mYqW?0lIKJ;w@^$6in|X0*YF`$DQZHSS134zF#>yPW_`4AM znjWs@7CMvwH&w=voOp3Nmp*fLCy%HIhrP5`8tIG_zpnAcnl=|XlAwc5huL$3P(55h z>c_yBe?U^0$VIy65!`OulJGuDnbnWNi(Y(X%(q+=wc|?Q2Wu_JnDJ&$*`0Aw!ZUIi zLNC5ADY4@dQNnc>jc?!5JbOc?nNQyEX>`M5$mfqT$&v=S?+6QQU0tZYtev?)e4p?- zY{z1l6g8L;7w5*j(|auG#MUb~C2FLD6F18@z+LutDU_~ID;*L^^u`B!#;k#f{-zo9?Ko4_oPY}^K;S}Z+?xf&NYM^|v z*pkvo9N^|^q7*<0z0x+Hj+W+}ccPQ$H(-$H-?fpVpC<>uExt9k+(1qEU9M}vo%HvX0RkxaW5 z=KK>pm4^BzfJRm1U%B1g>RZ@jDfLn$`jQ>x1y$v|mymsRDCL?c!YkXHKGa-HgE^c< z&YfRD-oQYl9&jEJOV>1l30cc7hM{sP6OEbF4?M=-nqywL<U9Y?sIr@s$(G5wcSm@dzPD$+RR=zaQD*X%5`4WL^3uN+b)z#*3hP*#P%bC@!UE zZ>`)nYW}1sbTh`W{0WJAY;H1vzX&xGt4PFK9HgIS)leN-3# literal 0 HcmV?d00001 diff --git a/demos/icy/src/main/res/values/colors.xml b/demos/icy/src/main/res/values/colors.xml new file mode 100644 index 0000000000..e7185c4951 --- /dev/null +++ b/demos/icy/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + + #008577 + #00574B + #D81B60 + + diff --git a/demos/icy/src/main/res/values/strings.xml b/demos/icy/src/main/res/values/strings.xml new file mode 100644 index 0000000000..6f87ce75c7 --- /dev/null +++ b/demos/icy/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + Icy-Ext Example + Audio stream + + diff --git a/demos/icy/src/main/res/values/styles.xml b/demos/icy/src/main/res/values/styles.xml new file mode 100644 index 0000000000..5885930df6 --- /dev/null +++ b/demos/icy/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/settings.gradle b/settings.gradle index d4530d67b7..2e0842fde2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,10 +20,12 @@ if (gradle.ext.has('exoplayerModulePrefix')) { include modulePrefix + 'demo' include modulePrefix + 'demo-cast' +include modulePrefix + 'demo-icy' include modulePrefix + 'demo-ima' include modulePrefix + 'playbacktests' project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main') project(modulePrefix + 'demo-cast').projectDir = new File(rootDir, 'demos/cast') +project(modulePrefix + 'demo-icy').projectDir = new File(rootDir, 'demos/icy') project(modulePrefix + 'demo-ima').projectDir = new File(rootDir, 'demos/ima') project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests') From 090a9923326e348aef5074108c1ded38d9225e28 Mon Sep 17 00:00:00 2001 From: ogaclejapan Date: Tue, 23 Oct 2018 11:24:41 +0900 Subject: [PATCH 099/832] Fix setMediaBitrateKbps naming from consistency point of view --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index e42f2853b2..4b35c7545d 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -96,7 +96,7 @@ public final class ImaAdsLoader private @Nullable Set adUiElements; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; - private int mediaBitrateKbps; + private int mediaBitrate; private ImaFactory imaFactory; /** @@ -108,7 +108,7 @@ public final class ImaAdsLoader this.context = Assertions.checkNotNull(context); vastLoadTimeoutMs = TIMEOUT_UNSET; mediaLoadTimeoutMs = TIMEOUT_UNSET; - mediaBitrateKbps = BITRATE_UNSET; + mediaBitrate = BITRATE_UNSET; imaFactory = new DefaultImaFactory(); } @@ -177,15 +177,15 @@ public final class ImaAdsLoader } /** - * Sets the ad media maximum recommended bitrate, in Kbps. + * Sets the ad media maximum recommended bitrate, in bps. * - * @param mediaBitrateKbps The ad media maximum recommended bitrate, in Kbps. + * @param bitrate The ad media maximum recommended bitrate, in bps. * @return This builder, for convenience. * @see AdsRenderingSettings#setBitrateKbps(int) */ - public Builder setMediaBitrateKbps(int mediaBitrateKbps) { - Assertions.checkArgument(mediaBitrateKbps > 0); - this.mediaBitrateKbps = mediaBitrateKbps; + public Builder setMaxMediaBitrate(int bitrate) { + Assertions.checkArgument(bitrate > 0); + this.mediaBitrate = bitrate; return this; } @@ -211,7 +211,7 @@ public final class ImaAdsLoader null, vastLoadTimeoutMs, mediaLoadTimeoutMs, - mediaBitrateKbps, + mediaBitrate, adUiElements, adEventListener, imaFactory); @@ -232,7 +232,7 @@ public final class ImaAdsLoader adsResponse, vastLoadTimeoutMs, mediaLoadTimeoutMs, - mediaBitrateKbps, + mediaBitrate, adUiElements, adEventListener, imaFactory); @@ -287,7 +287,7 @@ public final class ImaAdsLoader private final @Nullable String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; - private final int mediaBitrateKbps; + private final int mediaBitrate; private final @Nullable Set adUiElements; private final @Nullable AdEventListener adEventListener; private final ImaFactory imaFactory; @@ -375,7 +375,7 @@ public final class ImaAdsLoader /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, - /* mediaBitrateKpbs= */ BITRATE_UNSET, + /* mediaBitrate= */ BITRATE_UNSET, /* adUiElements= */ null, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); @@ -401,7 +401,7 @@ public final class ImaAdsLoader /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, - /* mediaBitrateKbps= */ BITRATE_UNSET, + /* mediaBitrate= */ BITRATE_UNSET, /* adUiElements= */ null, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); @@ -414,7 +414,7 @@ public final class ImaAdsLoader @Nullable String adsResponse, int vastLoadTimeoutMs, int mediaLoadTimeoutMs, - int mediaBitrateKbps, + int mediaBitrate, @Nullable Set adUiElements, @Nullable AdEventListener adEventListener, ImaFactory imaFactory) { @@ -423,7 +423,7 @@ public final class ImaAdsLoader this.adsResponse = adsResponse; this.vastLoadTimeoutMs = vastLoadTimeoutMs; this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; - this.mediaBitrateKbps = mediaBitrateKbps; + this.mediaBitrate = mediaBitrate; this.adUiElements = adUiElements; this.adEventListener = adEventListener; this.imaFactory = imaFactory; @@ -971,8 +971,8 @@ public final class ImaAdsLoader if (mediaLoadTimeoutMs != TIMEOUT_UNSET) { adsRenderingSettings.setLoadVideoTimeout(mediaLoadTimeoutMs); } - if (mediaBitrateKbps != BITRATE_UNSET) { - adsRenderingSettings.setBitrateKbps(mediaBitrateKbps); + if (mediaBitrate != BITRATE_UNSET) { + adsRenderingSettings.setBitrateKbps(mediaBitrate / 1000); } if (adUiElements != null) { adsRenderingSettings.setUiElements(adUiElements); From 23666e3ac89ae28ae78679dc5e3300e91822562f Mon Sep 17 00:00:00 2001 From: ogaclejapan Date: Tue, 23 Oct 2018 11:44:53 +0900 Subject: [PATCH 100/832] Fix adUiElements variables even more robust --- .../com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 4b35c7545d..060a3a02a5 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -68,6 +68,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -146,7 +147,7 @@ public final class ImaAdsLoader * @see AdsRenderingSettings#setUiElements(Set) */ public Builder setAdUiElements(Set adUiElements) { - this.adUiElements = Assertions.checkNotNull(adUiElements); + this.adUiElements = new HashSet<>(Assertions.checkNotNull(adUiElements)); return this; } From 9d114b1ccb784fe5d4b5392eb41d30d67eb19e64 Mon Sep 17 00:00:00 2001 From: Yuki312 Date: Tue, 23 Oct 2018 17:13:26 +0900 Subject: [PATCH 101/832] fix checkIdleRequirement --- .../com/google/android/exoplayer2/scheduler/Requirements.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java index 4d6dbd83be..5acd31ee0d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java @@ -187,7 +187,7 @@ public final class Requirements { } PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); return Util.SDK_INT >= 23 - ? !powerManager.isDeviceIdleMode() + ? powerManager.isDeviceIdleMode() : Util.SDK_INT >= 20 ? !powerManager.isInteractive() : !powerManager.isScreenOn(); } From 8c5703e9313db0dde1b37563a1c8c56e0fae2a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20=C3=85kerfeldt?= Date: Tue, 23 Oct 2018 16:25:40 +0200 Subject: [PATCH 102/832] Provide http status message to InvalidResponseCodeException When response code alone is not enough to distinguish error responses, the http status message is helpful. --- .../exoplayer2/ext/cronet/CronetDataSource.java | 5 +++-- .../exoplayer2/ext/okhttp/OkHttpDataSource.java | 2 +- .../exoplayer2/upstream/DefaultHttpDataSource.java | 4 +++- .../android/exoplayer2/upstream/HttpDataSource.java | 10 +++++++++- .../upstream/DefaultLoadErrorHandlingPolicyTest.java | 6 +++--- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 9525983491..d67d8bb107 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -327,7 +327,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { int responseCode = responseInfo.getHttpStatusCode(); if (responseCode < 200 || responseCode > 299) { InvalidResponseCodeException exception = new InvalidResponseCodeException(responseCode, - responseInfo.getAllHeaders(), currentDataSpec); + responseInfo.getHttpStatusText(), responseInfo.getAllHeaders(), currentDataSpec); if (responseCode == 416) { exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); } @@ -611,7 +611,8 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { // The industry standard is to disregard POST redirects when the status code is 307 or 308. if (responseCode == 307 || responseCode == 308) { exception = - new InvalidResponseCodeException(responseCode, info.getAllHeaders(), currentDataSpec); + new InvalidResponseCodeException(responseCode, info.getHttpStatusText(), + info.getAllHeaders(), currentDataSpec); operation.open(); return; } diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 2707f539bc..8091c7c94b 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -173,7 +173,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { Map> headers = response.headers().toMultimap(); closeConnectionQuietly(); InvalidResponseCodeException exception = new InvalidResponseCodeException( - responseCode, headers, dataSpec); + responseCode, response.message(), headers, dataSpec); if (responseCode == 416) { exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index 3673af5540..c6749e6c8f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -283,8 +283,10 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou } int responseCode; + String responseMessage; try { responseCode = connection.getResponseCode(); + responseMessage = connection.getResponseMessage(); } catch (IOException e) { closeConnectionQuietly(); throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e, @@ -296,7 +298,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou Map> headers = connection.getHeaderFields(); closeConnectionQuietly(); InvalidResponseCodeException exception = - new InvalidResponseCodeException(responseCode, headers, dataSpec); + new InvalidResponseCodeException(responseCode, responseMessage, headers, dataSpec); if (responseCode == 416) { exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index 0be7b857df..0ee28b260f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.upstream; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Util; @@ -294,15 +295,22 @@ public interface HttpDataSource extends DataSource { */ public final int responseCode; + /** + * The HTTP status message. + */ + public final @Nullable String responseMessage; + /** * An unmodifiable map of the response header fields and values. */ public final Map> headerFields; - public InvalidResponseCodeException(int responseCode, Map> headerFields, + public InvalidResponseCodeException(int responseCode, @Nullable String responseMessage, + Map> headerFields, DataSpec dataSpec) { super("Response code: " + responseCode, dataSpec, TYPE_OPEN); this.responseCode = responseCode; + this.responseMessage = responseMessage; this.headerFields = headerFields; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java index 0c2b938a64..d656e0dfcc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java @@ -34,7 +34,7 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_blacklist404() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(404, Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException(404, "Not Found", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)) .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } @@ -42,7 +42,7 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_blacklist410() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(410, Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException(410, "Gone", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)) .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } @@ -50,7 +50,7 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_dontBlacklistUnexpectedHttpCodes() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(500, Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException(500, "Internal Server Error", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)).isEqualTo(C.TIME_UNSET); } From 70ccbc6eb8f82488d7163af606e81ef358acd640 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 23 Oct 2018 18:46:52 +0200 Subject: [PATCH 103/832] Fix review comments See https://github.com/google/ExoPlayer/pull/5004 --- .../exoplayer2/ext/cronet/CronetDataSource.java | 15 +++++++++++---- .../exoplayer2/ext/okhttp/OkHttpDataSource.java | 4 ++-- .../exoplayer2/upstream/HttpDataSource.java | 8 +++++--- .../DefaultLoadErrorHandlingPolicyTest.java | 3 ++- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index d67d8bb107..9f381d0053 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -326,8 +326,12 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { // Check for a valid response code. int responseCode = responseInfo.getHttpStatusCode(); if (responseCode < 200 || responseCode > 299) { - InvalidResponseCodeException exception = new InvalidResponseCodeException(responseCode, - responseInfo.getHttpStatusText(), responseInfo.getAllHeaders(), currentDataSpec); + InvalidResponseCodeException exception = + new InvalidResponseCodeException( + responseCode, + responseInfo.getHttpStatusText(), + responseInfo.getAllHeaders(), + currentDataSpec); if (responseCode == 416) { exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); } @@ -611,8 +615,11 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { // The industry standard is to disregard POST redirects when the status code is 307 or 308. if (responseCode == 307 || responseCode == 308) { exception = - new InvalidResponseCodeException(responseCode, info.getHttpStatusText(), - info.getAllHeaders(), currentDataSpec); + new InvalidResponseCodeException( + responseCode, + info.getHttpStatusText(), + info.getAllHeaders(), + currentDataSpec); operation.open(); return; } diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 8091c7c94b..778277fdbc 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -172,8 +172,8 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { if (!response.isSuccessful()) { Map> headers = response.headers().toMultimap(); closeConnectionQuietly(); - InvalidResponseCodeException exception = new InvalidResponseCodeException( - responseCode, response.message(), headers, dataSpec); + InvalidResponseCodeException exception = + new InvalidResponseCodeException(responseCode, response.message(), headers, dataSpec); if (responseCode == 416) { exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index 0ee28b260f..e73901eccb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -298,15 +298,17 @@ public interface HttpDataSource extends DataSource { /** * The HTTP status message. */ - public final @Nullable String responseMessage; + @Nullable public final String responseMessage; /** * An unmodifiable map of the response header fields and values. */ public final Map> headerFields; - public InvalidResponseCodeException(int responseCode, @Nullable String responseMessage, - Map> headerFields, + public InvalidResponseCodeException( + int responseCode, + @Nullable String responseMessage, + Map> headerFields, DataSpec dataSpec) { super("Response code: " + responseCode, dataSpec, TYPE_OPEN); this.responseCode = responseCode; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java index d656e0dfcc..f0f737828c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java @@ -50,7 +50,8 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_dontBlacklistUnexpectedHttpCodes() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(500, "Internal Server Error", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException( + 500, "Internal Server Error", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)).isEqualTo(C.TIME_UNSET); } From 8f3c3e284155ff26a23c29a089305bdb0a6ab7e4 Mon Sep 17 00:00:00 2001 From: eguven Date: Fri, 19 Oct 2018 03:14:26 -0700 Subject: [PATCH 104/832] Add GlUtil createBuffer overload which doesn't copy values ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217846755 --- .../android/exoplayer2/util/GlUtil.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java index 72a782c2ac..df22e6dc2e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java @@ -95,14 +95,23 @@ public final class GlUtil { return program; } - /** Allocates a FloatBuffer with the given data. */ + /** + * Allocates a FloatBuffer with the given data. + * + * @param data Used to initialize the new buffer. + */ public static FloatBuffer createBuffer(float[] data) { - ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * C.BYTES_PER_FLOAT); - byteBuffer.order(ByteOrder.nativeOrder()); - FloatBuffer buffer = byteBuffer.asFloatBuffer(); - buffer.put(data); - buffer.flip(); - return buffer; + return (FloatBuffer) createBuffer(data.length).put(data).flip(); + } + + /** + * Allocates a FloatBuffer. + * + * @param capacity The new buffer's capacity, in floats. + */ + public static FloatBuffer createBuffer(int capacity) { + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(capacity * C.BYTES_PER_FLOAT); + return byteBuffer.order(ByteOrder.nativeOrder()).asFloatBuffer(); } /** From bf57d4402343f214985a330255bc2f044c6707a9 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 19 Oct 2018 08:37:01 -0700 Subject: [PATCH 105/832] Update VR video player sample app ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217874381 --- .../source/ClippingMediaSource.java | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index 5f80725805..8ac9460586 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -116,36 +116,6 @@ public final class ClippingMediaSource extends CompositeMediaSource { /* relativeToDefaultPosition= */ false); } - /** - * Creates a new clipping source that wraps the specified source and provides samples between the - * specified start and end position. - * - * @param mediaSource The single-period source to wrap. - * @param startPositionUs The start position within {@code mediaSource}'s window at which to start - * providing samples, in microseconds. - * @param endPositionUs The end position within {@code mediaSource}'s window at which to stop - * providing samples, in microseconds. Specify {@link C#TIME_END_OF_SOURCE} to provide samples - * from the specified start point up to the end of the source. Specifying a position that - * exceeds the {@code mediaSource}'s duration will also result in the end of the source not - * being clipped. - * @param enableInitialDiscontinuity Whether the initial discontinuity should be enabled. - */ - // TODO: remove this when the new API is public. - @Deprecated - public ClippingMediaSource( - MediaSource mediaSource, - long startPositionUs, - long endPositionUs, - boolean enableInitialDiscontinuity) { - this( - mediaSource, - startPositionUs, - endPositionUs, - enableInitialDiscontinuity, - /* allowDynamicClippingUpdates= */ false, - /* relativeToDefaultPosition= */ false); - } - /** * Creates a new clipping source that wraps the specified source and provides samples from the * default position for the specified duration. From 4c6507e7a0e101468268cce03c062275c5c13b65 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 19 Oct 2018 09:32:07 -0700 Subject: [PATCH 106/832] Remove deprecated MediaSource.prepareSource. This method only ensured backward compatibility while the new version was not public yet. Now ExoPlayer 2.9.0. is public, this workaround can be removed. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217881691 --- .../google/android/exoplayer2/source/BaseMediaSource.java | 6 ------ .../com/google/android/exoplayer2/source/MediaSource.java | 5 ----- 2 files changed, 11 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java index 2feac2978e..3d6e204c9c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java @@ -133,12 +133,6 @@ public abstract class BaseMediaSource implements MediaSource { eventDispatcher.removeEventListener(eventListener); } - @Override - public final void prepareSource( - ExoPlayer player, boolean isTopLevelSource, SourceInfoRefreshListener listener) { - prepareSource(player, isTopLevelSource, listener, /* mediaTransferListener= */ null); - } - @Override public final void prepareSource( ExoPlayer player, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index 6b0f5c8eeb..2a94e884a6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -220,11 +220,6 @@ public interface MediaSource { */ void removeEventListener(MediaSourceEventListener eventListener); - /** @deprecated Will be removed in the next release. */ - @Deprecated - void prepareSource( - ExoPlayer player, boolean isTopLevelSource, SourceInfoRefreshListener listener); - /** * Starts source preparation if not yet started, and adds a listener for timeline and/or manifest * updates. From ca4b5d0a0def1928323b4ec86dd4564b89d4bba4 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 19 Oct 2018 10:05:44 -0700 Subject: [PATCH 107/832] Make context non-optional for DefaultBandwidthMeter - Added ability to override the DefaultBandwidthMeter network type. - These methods currently only work properly if called before the meter is used, but the plan is for them to be handled in the same way as internally detected network changes. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217887019 --- .../flac/src/androidTest/AndroidManifest.xml | 2 + .../exoplayer2/ext/flac/FlacPlaybackTest.java | 2 +- .../opus/src/androidTest/AndroidManifest.xml | 2 + .../exoplayer2/ext/opus/OpusPlaybackTest.java | 2 +- .../vp9/src/androidTest/AndroidManifest.xml | 2 + .../exoplayer2/ext/vp9/VpxPlaybackTest.java | 2 +- .../android/exoplayer2/ExoPlayerFactory.java | 124 ++++++++---------- .../upstream/DefaultBandwidthMeter.java | 78 +++++++---- 8 files changed, 115 insertions(+), 99 deletions(-) diff --git a/extensions/flac/src/androidTest/AndroidManifest.xml b/extensions/flac/src/androidTest/AndroidManifest.xml index cfc90117ac..68ab6fe0c3 100644 --- a/extensions/flac/src/androidTest/AndroidManifest.xml +++ b/extensions/flac/src/androidTest/AndroidManifest.xml @@ -18,6 +18,8 @@ xmlns:tools="http://schemas.android.com/tools" package="com.google.android.exoplayer2.ext.flac.test"> + + diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index 99ddba55c4..2efdde4e58 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -83,7 +83,7 @@ public class FlacPlaybackTest { Looper.prepare(); LibflacAudioRenderer audioRenderer = new LibflacAudioRenderer(); DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); + player = ExoPlayerFactory.newInstance(context, new Renderer[] {audioRenderer}, trackSelector); player.addListener(this); MediaSource mediaSource = new ExtractorMediaSource.Factory( diff --git a/extensions/opus/src/androidTest/AndroidManifest.xml b/extensions/opus/src/androidTest/AndroidManifest.xml index 5ba0f3c0f4..9c1241a512 100644 --- a/extensions/opus/src/androidTest/AndroidManifest.xml +++ b/extensions/opus/src/androidTest/AndroidManifest.xml @@ -18,6 +18,8 @@ xmlns:tools="http://schemas.android.com/tools" package="com.google.android.exoplayer2.ext.opus.test"> + + diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java index c457514c87..5ad864c597 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java @@ -83,7 +83,7 @@ public class OpusPlaybackTest { Looper.prepare(); LibopusAudioRenderer audioRenderer = new LibopusAudioRenderer(); DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - player = ExoPlayerFactory.newInstance(new Renderer[] {audioRenderer}, trackSelector); + player = ExoPlayerFactory.newInstance(context, new Renderer[] {audioRenderer}, trackSelector); player.addListener(this); MediaSource mediaSource = new ExtractorMediaSource.Factory( diff --git a/extensions/vp9/src/androidTest/AndroidManifest.xml b/extensions/vp9/src/androidTest/AndroidManifest.xml index 214427c4f0..aef376237e 100644 --- a/extensions/vp9/src/androidTest/AndroidManifest.xml +++ b/extensions/vp9/src/androidTest/AndroidManifest.xml @@ -18,6 +18,8 @@ xmlns:tools="http://schemas.android.com/tools" package="com.google.android.exoplayer2.ext.vp9.test"> + + diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index 2eb5c87e04..c6d1e667e0 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -116,7 +116,7 @@ public class VpxPlaybackTest { Looper.prepare(); LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(true, 0); DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - player = ExoPlayerFactory.newInstance(new Renderer[] {videoRenderer}, trackSelector); + player = ExoPlayerFactory.newInstance(context, new Renderer[] {videoRenderer}, trackSelector); player.addListener(this); MediaSource mediaSource = new ExtractorMediaSource.Factory( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java index 81f4285a08..c63dbc04d0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java @@ -19,7 +19,6 @@ import android.content.Context; import android.os.Looper; import android.support.annotation.Nullable; import com.google.android.exoplayer2.analytics.AnalyticsCollector; -import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -38,44 +37,6 @@ public final class ExoPlayerFactory { private ExoPlayerFactory() {} - /** - * Creates a {@link SimpleExoPlayer} instance. - * - * @param context A {@link Context}. - * @param trackSelector The {@link TrackSelector} that will be used by the instance. - * @param loadControl The {@link LoadControl} that will be used by the instance. - * @deprecated Use {@link #newSimpleInstance(Context, RenderersFactory, TrackSelector, - * LoadControl)}. - */ - @Deprecated - public static SimpleExoPlayer newSimpleInstance( - Context context, TrackSelector trackSelector, LoadControl loadControl) { - RenderersFactory renderersFactory = new DefaultRenderersFactory(context); - return newSimpleInstance(context, renderersFactory, trackSelector, loadControl); - } - - /** - * Creates a {@link SimpleExoPlayer} instance. Available extension renderers are not used. - * - * @param context A {@link Context}. - * @param trackSelector The {@link TrackSelector} that will be used by the instance. - * @param loadControl The {@link LoadControl} that will be used by the instance. - * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance - * will not be used for DRM protected playbacks. - * @deprecated Use {@link #newSimpleInstance(Context, RenderersFactory, TrackSelector, - * LoadControl)}. - */ - @Deprecated - public static SimpleExoPlayer newSimpleInstance( - Context context, - TrackSelector trackSelector, - LoadControl loadControl, - @Nullable DrmSessionManager drmSessionManager) { - RenderersFactory renderersFactory = new DefaultRenderersFactory(context); - return newSimpleInstance( - context, renderersFactory, trackSelector, loadControl, drmSessionManager); - } - /** * Creates a {@link SimpleExoPlayer} instance. * @@ -88,7 +49,7 @@ public final class ExoPlayerFactory { * extension renderers are used. Note that extensions must be included in the application * build for them to be considered available. * @deprecated Use {@link #newSimpleInstance(Context, RenderersFactory, TrackSelector, - * LoadControl)}. + * LoadControl, DrmSessionManager)}. */ @Deprecated public static SimpleExoPlayer newSimpleInstance( @@ -116,7 +77,7 @@ public final class ExoPlayerFactory { * @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to * seamlessly join an ongoing playback. * @deprecated Use {@link #newSimpleInstance(Context, RenderersFactory, TrackSelector, - * LoadControl)}. + * LoadControl, DrmSessionManager)}. */ @Deprecated public static SimpleExoPlayer newSimpleInstance( @@ -151,23 +112,6 @@ public final class ExoPlayerFactory { return newSimpleInstance(context, new DefaultRenderersFactory(context), trackSelector); } - /** - * Creates a {@link SimpleExoPlayer} instance. - * - * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. - * @param trackSelector The {@link TrackSelector} that will be used by the instance. - * @deprecated Use {@link #newSimpleInstance(Context, RenderersFactory, TrackSelector)}. The use - * of {@link SimpleExoPlayer#setAudioAttributes(AudioAttributes, boolean)} to manage audio - * focus will be unavailable for the {@link SimpleExoPlayer} returned by this method. - */ - @Deprecated - @SuppressWarnings("nullness:argument.type.incompatible") - public static SimpleExoPlayer newSimpleInstance( - RenderersFactory renderersFactory, TrackSelector trackSelector) { - return newSimpleInstance( - /* context= */ null, renderersFactory, trackSelector, new DefaultLoadControl()); - } - /** * Creates a {@link SimpleExoPlayer} instance. * @@ -180,6 +124,38 @@ public final class ExoPlayerFactory { return newSimpleInstance(context, renderersFactory, trackSelector, new DefaultLoadControl()); } + /** + * Creates a {@link SimpleExoPlayer} instance. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + */ + public static SimpleExoPlayer newSimpleInstance( + Context context, TrackSelector trackSelector, LoadControl loadControl) { + RenderersFactory renderersFactory = new DefaultRenderersFactory(context); + return newSimpleInstance(context, renderersFactory, trackSelector, loadControl); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Available extension renderers are not used. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance + * will not be used for DRM protected playbacks. + */ + public static SimpleExoPlayer newSimpleInstance( + Context context, + TrackSelector trackSelector, + LoadControl loadControl, + @Nullable DrmSessionManager drmSessionManager) { + RenderersFactory renderersFactory = new DefaultRenderersFactory(context); + return newSimpleInstance( + context, renderersFactory, trackSelector, loadControl, drmSessionManager); + } + /** * Creates a {@link SimpleExoPlayer} instance. * @@ -355,7 +331,7 @@ public final class ExoPlayerFactory { trackSelector, loadControl, drmSessionManager, - getDefaultBandwidthMeter(), + getDefaultBandwidthMeter(context), analyticsCollectorFactory, looper); } @@ -397,28 +373,32 @@ public final class ExoPlayerFactory { /** * Creates an {@link ExoPlayer} instance. * + * @param context A {@link Context}. * @param renderers The {@link Renderer}s that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. */ - public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector) { - return newInstance(renderers, trackSelector, new DefaultLoadControl()); + public static ExoPlayer newInstance( + Context context, Renderer[] renderers, TrackSelector trackSelector) { + return newInstance(context, renderers, trackSelector, new DefaultLoadControl()); } /** * Creates an {@link ExoPlayer} instance. * + * @param context A {@link Context}. * @param renderers The {@link Renderer}s that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance. */ - public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector, - LoadControl loadControl) { - return newInstance(renderers, trackSelector, loadControl, Util.getLooper()); + public static ExoPlayer newInstance( + Context context, Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) { + return newInstance(context, renderers, trackSelector, loadControl, Util.getLooper()); } /** * Creates an {@link ExoPlayer} instance. * + * @param context A {@link Context}. * @param renderers The {@link Renderer}s that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance. @@ -426,13 +406,19 @@ public final class ExoPlayerFactory { * used to call listeners on. */ public static ExoPlayer newInstance( - Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Looper looper) { - return newInstance(renderers, trackSelector, loadControl, getDefaultBandwidthMeter(), looper); + Context context, + Renderer[] renderers, + TrackSelector trackSelector, + LoadControl loadControl, + Looper looper) { + return newInstance( + context, renderers, trackSelector, loadControl, getDefaultBandwidthMeter(context), looper); } /** * Creates an {@link ExoPlayer} instance. * + * @param context A {@link Context}. * @param renderers The {@link Renderer}s that will be used by the instance. * @param trackSelector The {@link TrackSelector} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance. @@ -440,7 +426,9 @@ public final class ExoPlayerFactory { * @param looper The {@link Looper} which must be used for all calls to the player and which is * used to call listeners on. */ + @SuppressWarnings("unused") public static ExoPlayer newInstance( + Context context, Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, @@ -450,9 +438,9 @@ public final class ExoPlayerFactory { renderers, trackSelector, loadControl, bandwidthMeter, Clock.DEFAULT, looper); } - private static synchronized BandwidthMeter getDefaultBandwidthMeter() { + private static synchronized BandwidthMeter getDefaultBandwidthMeter(Context context) { if (singletonBandwidthMeter == null) { - singletonBandwidthMeter = new DefaultBandwidthMeter.Builder().build(); + singletonBandwidthMeter = new DefaultBandwidthMeter.Builder(context).build(); } return singletonBandwidthMeter; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index 21e2ed4f65..561ecfb024 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -82,18 +82,13 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private int slidingWindowMaxWeight; private Clock clock; - /** @deprecated Use {@link #Builder(Context)} instead. */ - @Deprecated - public Builder() { - this(/* context= */ null); - } - /** * Creates a builder with default parameters and without listener. * * @param context A context. */ - public Builder(@Nullable Context context) { + public Builder(Context context) { + // Handling of null is for backward compatibility only. this.context = context == null ? null : context.getApplicationContext(); initialBitrateEstimates = getInitialBitrateEstimatesForCountry(Util.getCountryCode(context)); slidingWindowMaxWeight = DEFAULT_SLIDING_WINDOW_MAX_WEIGHT; @@ -187,14 +182,9 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList * @return A bandwidth meter with the configured properties. */ public DefaultBandwidthMeter build() { - Long initialBitrateEstimate = - initialBitrateEstimates.get( - context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context)); - if (initialBitrateEstimate == null) { - initialBitrateEstimate = initialBitrateEstimates.get(C.NETWORK_TYPE_UNKNOWN); - } DefaultBandwidthMeter bandwidthMeter = - new DefaultBandwidthMeter(initialBitrateEstimate, slidingWindowMaxWeight, clock); + new DefaultBandwidthMeter( + context, initialBitrateEstimates, slidingWindowMaxWeight, clock); if (eventHandler != null && eventListener != null) { bandwidthMeter.addEventListener(eventHandler, eventListener); } @@ -225,6 +215,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; private static final int BYTES_TRANSFERRED_FOR_ESTIMATE = 512 * 1024; + private final SparseArray initialBitrateEstimates; private final EventDispatcher eventDispatcher; private final SlidingPercentile slidingPercentile; private final Clock clock; @@ -237,34 +228,54 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private long totalBytesTransferred; private long bitrateEstimate; - /** Creates a bandwidth meter with default parameters. */ + /** @deprecated Use {@link Builder} instead. */ + @Deprecated public DefaultBandwidthMeter() { - this(DEFAULT_INITIAL_BITRATE_ESTIMATE, DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, Clock.DEFAULT); + this( + /* context= */ null, + /* initialBitrateEstimates= */ new SparseArray<>(), + DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, + Clock.DEFAULT); } /** @deprecated Use {@link Builder} instead. */ @Deprecated public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener) { - this(DEFAULT_INITIAL_BITRATE_ESTIMATE, DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, Clock.DEFAULT); + this( + /* context= */ null, + /* initialBitrateEstimates= */ new SparseArray<>(), + DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, + Clock.DEFAULT); if (eventHandler != null && eventListener != null) { addEventListener(eventHandler, eventListener); } } - /** @deprecated Use {@link Builder} instead. */ - @Deprecated - public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, int maxWeight) { - this(DEFAULT_INITIAL_BITRATE_ESTIMATE, maxWeight, Clock.DEFAULT); - if (eventHandler != null && eventListener != null) { - addEventListener(eventHandler, eventListener); - } - } - - private DefaultBandwidthMeter(long initialBitrateEstimate, int maxWeight, Clock clock) { + private DefaultBandwidthMeter( + @Nullable Context context, + SparseArray initialBitrateEstimates, + int maxWeight, + Clock clock) { + this.initialBitrateEstimates = initialBitrateEstimates; this.eventDispatcher = new EventDispatcher<>(); this.slidingPercentile = new SlidingPercentile(maxWeight); this.clock = clock; - bitrateEstimate = initialBitrateEstimate; + bitrateEstimate = + getInitialBitrateEstimateForNetworkType( + context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context)); + } + + /** + * Overrides the network type. Handled in the same way as if the meter had detected a change from + * the current network type to the specified network type. + * + *

    Applications should not normally call this method. It is intended for testing purposes. + * + * @param networkType The overriding network type. + */ + public synchronized void setNetworkTypeOverride(@C.NetworkType int networkType) { + // TODO: Handle properly as a network change (in same way as non-external network changes). + bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); } @Override @@ -343,6 +354,17 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList eventDispatcher.dispatch(listener -> listener.onBandwidthSample(elapsedMs, bytes, bitrate)); } + private long getInitialBitrateEstimateForNetworkType(@C.NetworkType int networkType) { + Long initialBitrateEstimate = initialBitrateEstimates.get(networkType); + if (initialBitrateEstimate == null) { + initialBitrateEstimate = initialBitrateEstimates.get(C.NETWORK_TYPE_UNKNOWN); + } + if (initialBitrateEstimate == null) { + initialBitrateEstimate = DEFAULT_INITIAL_BITRATE_ESTIMATE; + } + return initialBitrateEstimate; + } + private static Map createInitialBitrateCountryGroupAssignment() { HashMap countryGroupAssignment = new HashMap<>(); countryGroupAssignment.put("AD", new int[] {1, 0, 0, 0}); From 6977fdbc1d4515f2cc5f16efd2bbee2d17624cd8 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 19 Oct 2018 10:36:35 -0700 Subject: [PATCH 108/832] Properly reset period id and start position in ExoPlayerImpl. This is a no-op change as the respective values are not used so far but the change makes the current state cleaner and is less error-prone. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217892421 --- .../android/exoplayer2/ExoPlayerImpl.java | 18 ++++++++----- .../exoplayer2/ExoPlayerImplInternal.java | 25 ++++++++----------- .../android/exoplayer2/PlaybackInfo.java | 20 ++++++++++++++- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 130bf48921..6124a6c18b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -667,20 +667,26 @@ import java.util.concurrent.CopyOnWriteArraySet; maskingPeriodIndex = getCurrentPeriodIndex(); maskingWindowPositionMs = getCurrentPosition(); } + MediaPeriodId mediaPeriodId = + resetPosition + ? playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window) + : playbackInfo.periodId; + long startPositionUs = resetPosition ? 0 : playbackInfo.positionUs; + long contentPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs; return new PlaybackInfo( resetState ? Timeline.EMPTY : playbackInfo.timeline, resetState ? null : playbackInfo.manifest, - playbackInfo.periodId, - playbackInfo.startPositionUs, - playbackInfo.contentPositionUs, + mediaPeriodId, + startPositionUs, + contentPositionUs, playbackState, /* isLoading= */ false, resetState ? TrackGroupArray.EMPTY : playbackInfo.trackGroups, resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult, - playbackInfo.periodId, - playbackInfo.startPositionUs, + mediaPeriodId, + startPositionUs, /* totalBufferedDurationUs= */ 0, - playbackInfo.startPositionUs); + startPositionUs); } private void updatePlaybackInfo( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 3034ead032..08e1cbfe2b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -648,7 +648,7 @@ import java.util.concurrent.atomic.AtomicBoolean; if (resolvedSeekPosition == null) { // The seek position was valid for the timeline that it was performed into, but the // timeline has changed or is not ready and a suitable seek position could not be resolved. - periodId = getFirstMediaPeriodId(); + periodId = playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window); periodPositionUs = C.TIME_UNSET; contentPositionUs = C.TIME_UNSET; seekPositionAdjusted = true; @@ -833,17 +833,6 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - private MediaPeriodId getFirstMediaPeriodId() { - Timeline timeline = playbackInfo.timeline; - if (timeline.isEmpty()) { - return PlaybackInfo.DUMMY_MEDIA_PERIOD_ID; - } - int firstPeriodIndex = - timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window) - .firstPeriodIndex; - return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex)); - } - private void resetInternal( boolean resetRenderers, boolean releaseMediaSource, @@ -885,8 +874,11 @@ import java.util.concurrent.atomic.AtomicBoolean; pendingMessages.clear(); nextPendingMessageIndex = 0; } + MediaPeriodId mediaPeriodId = + resetPosition + ? playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window) + : playbackInfo.periodId; // Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored. - MediaPeriodId mediaPeriodId = resetPosition ? getFirstMediaPeriodId() : playbackInfo.periodId; long startPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.positionUs; long contentPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs; playbackInfo = @@ -1262,8 +1254,13 @@ import java.util.concurrent.atomic.AtomicBoolean; periodPosition = resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true); } catch (IllegalSeekPositionException e) { + MediaPeriodId firstMediaPeriodId = + playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window); playbackInfo = - playbackInfo.resetToNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET); + playbackInfo.resetToNewPosition( + firstMediaPeriodId, + /* startPositionUs= */ C.TIME_UNSET, + /* contentPositionUs= */ C.TIME_UNSET); throw e; } pendingInitialSeekPosition = null; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 8c73fde3be..4333f51bf7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -30,7 +30,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; * Dummy media period id used while the timeline is empty and no period id is specified. This id * is used when playback infos are created with {@link #createDummy(long, TrackSelectorResult)}. */ - public static final MediaPeriodId DUMMY_MEDIA_PERIOD_ID = + private static final MediaPeriodId DUMMY_MEDIA_PERIOD_ID = new MediaPeriodId(/* periodUid= */ new Object()); /** The current {@link Timeline}. */ @@ -151,6 +151,24 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; this.positionUs = positionUs; } + /** + * Returns dummy media period id for the first-to-be-played period of the current timeline. + * + * @param shuffleModeEnabled Whether shuffle mode is enabled. + * @param window A writable {@link Timeline.Window}. + * @return A dummy media period id for the first-to-be-played period of the current timeline. + */ + public MediaPeriodId getDummyFirstMediaPeriodId( + boolean shuffleModeEnabled, Timeline.Window window) { + if (timeline.isEmpty()) { + return DUMMY_MEDIA_PERIOD_ID; + } + int firstPeriodIndex = + timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window) + .firstPeriodIndex; + return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex)); + } + /** * Copies playback info and resets playing and loading position. * From 0834d128431a8724f4c5c7828c7ab5e9d035c5e9 Mon Sep 17 00:00:00 2001 From: cpaulino Date: Fri, 19 Oct 2018 10:41:10 -0700 Subject: [PATCH 109/832] Make VpxOutputBuffer public Mimics the ExoV1 behavior. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217893212 --- .../google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java index fa0df1cfa9..725d94819b 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer.java @@ -19,10 +19,8 @@ import com.google.android.exoplayer2.decoder.OutputBuffer; import com.google.android.exoplayer2.video.ColorInfo; import java.nio.ByteBuffer; -/** - * Output buffer containing video frame data, populated by {@link VpxDecoder}. - */ -/* package */ final class VpxOutputBuffer extends OutputBuffer { +/** Output buffer containing video frame data, populated by {@link VpxDecoder}. */ +public final class VpxOutputBuffer extends OutputBuffer { public static final int COLORSPACE_UNKNOWN = 0; public static final int COLORSPACE_BT601 = 1; From 42c9b244eee6e5775c91967f6f529b6fb8589d64 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 19 Oct 2018 11:05:50 -0700 Subject: [PATCH 110/832] Make context non-optional in AudioFocusManager ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217897796 --- .../exoplayer2/audio/AudioFocusManager.java | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java index ca4e0c299e..39422c2873 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java @@ -99,7 +99,7 @@ public final class AudioFocusManager { private static final float VOLUME_MULTIPLIER_DUCK = 0.2f; private static final float VOLUME_MULTIPLIER_DEFAULT = 1.0f; - private final @Nullable AudioManager audioManager; + private final AudioManager audioManager; private final AudioFocusListener focusListener; private final PlayerControl playerControl; private @Nullable AudioAttributes audioAttributes; @@ -117,12 +117,9 @@ public final class AudioFocusManager { * @param context The current context. * @param playerControl A {@link PlayerControl} to handle commands from this instance. */ - public AudioFocusManager(@Nullable Context context, PlayerControl playerControl) { + public AudioFocusManager(Context context, PlayerControl playerControl) { this.audioManager = - context == null - ? null - : (AudioManager) - context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE); + (AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE); this.playerControl = playerControl; this.focusListener = new AudioFocusListener(); this.audioFocusState = AUDIO_FOCUS_STATE_NO_FOCUS; @@ -148,8 +145,6 @@ public final class AudioFocusManager { return PLAYER_COMMAND_PLAY_WHEN_READY; } - Assertions.checkNotNull( - audioManager, "SimpleExoPlayer must be created with a context to handle audio focus."); if (!Util.areEqual(this.audioAttributes, audioAttributes)) { this.audioAttributes = audioAttributes; focusGain = convertAudioAttributesToFocusGain(audioAttributes); @@ -177,10 +172,6 @@ public final class AudioFocusManager { * @return A {@link PlayerCommand} to execute on the player. */ public @PlayerCommand int handlePrepare(boolean playWhenReady) { - if (audioManager == null) { - return PLAYER_COMMAND_PLAY_WHEN_READY; - } - return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY; } @@ -192,10 +183,6 @@ public final class AudioFocusManager { * @return A {@link PlayerCommand} to execute on the player. */ public @PlayerCommand int handleSetPlayWhenReady(boolean playWhenReady, int playerState) { - if (audioManager == null) { - return PLAYER_COMMAND_PLAY_WHEN_READY; - } - if (!playWhenReady) { abandonAudioFocus(); return PLAYER_COMMAND_DO_NOT_PLAY; @@ -209,10 +196,6 @@ public final class AudioFocusManager { /** Called by the player as part of {@link ExoPlayer#stop(boolean)}. */ public void handleStop() { - if (audioManager == null) { - return; - } - abandonAudioFocus(/* forceAbandon= */ true); } @@ -271,7 +254,6 @@ public final class AudioFocusManager { } private int requestAudioFocusDefault() { - AudioManager audioManager = Assertions.checkNotNull(this.audioManager); return audioManager.requestAudioFocus( focusListener, Util.getStreamTypeForAudioUsage(Assertions.checkNotNull(audioAttributes).usage), @@ -296,17 +278,17 @@ public final class AudioFocusManager { rebuildAudioFocusRequest = false; } - return Assertions.checkNotNull(audioManager).requestAudioFocus(audioFocusRequest); + return audioManager.requestAudioFocus(audioFocusRequest); } private void abandonAudioFocusDefault() { - Assertions.checkNotNull(audioManager).abandonAudioFocus(focusListener); + audioManager.abandonAudioFocus(focusListener); } @RequiresApi(26) private void abandonAudioFocusV26() { if (audioFocusRequest != null) { - Assertions.checkNotNull(audioManager).abandonAudioFocusRequest(audioFocusRequest); + audioManager.abandonAudioFocusRequest(audioFocusRequest); } } From bb5a9ef8add0217e4d67e4ad000440bfd9c69069 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 19 Oct 2018 11:57:29 -0700 Subject: [PATCH 111/832] Remove extra blank line ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217907544 --- .../com/google/android/exoplayer2/source/hls/HlsMediaChunk.java | 1 - 1 file changed, 1 deletion(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 0cfe31f3b9..a0dd66785d 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -41,7 +41,6 @@ import java.util.concurrent.atomic.AtomicInteger; */ /* package */ final class HlsMediaChunk extends MediaChunk { - private static final String PRIV_TIMESTAMP_FRAME_OWNER = "com.apple.streaming.transportStreamTimestamp"; From f3b4575436d86024e537eb6afa582b752a38e916 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 19 Oct 2018 13:47:45 -0700 Subject: [PATCH 112/832] Temporarily disable codec reuse There are multiple subtle issues with the current implementation: 1. setOperatingRate can cause a codec initialization even if the renderer is disabled. This is not supposed to happen. 2. If the codec is released whilst the renderer is disabled, the renderer can instantiate a new codec using the old format when it's enabled again, only to immediately have to reconfigure or release it if the actual format to be played is different. 3. Codec reuse does not take into account renderer configuration. The specific case where this is problematic is if the video renderer is re-enabled with a different tunneling session id. The reused codec is then not configured correctly. Also moved availableCodecInfos reset into releaseCodec for sanity. Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217924592 --- .../exoplayer2/mediacodec/MediaCodecRenderer.java | 13 +------------ .../exoplayer2/video/MediaCodecVideoRenderer.java | 4 +--- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 27b0d89a3f..a35240668e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -558,18 +558,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Override protected void onDisabled() { - if (drmSession != null || pendingDrmSession != null) { - // TODO: Do something better with this case. - onReset(); - } else { - flushOrReleaseCodec(); - } - } - - @Override - protected void onReset() { format = null; - availableCodecInfos = null; try { releaseCodec(); } finally { @@ -591,6 +580,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } protected void releaseCodec() { + availableCodecInfos = null; codecHotswapDeadlineMs = C.TIME_UNSET; resetInputBuffer(); resetOutputBuffer(); @@ -1307,7 +1297,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * @throws ExoPlaybackException If an error occurs releasing or initializing a codec. */ private void reinitializeCodec(boolean release) throws ExoPlaybackException { - availableCodecInfos = null; if (codecReceivedBuffers) { // Signal end of stream and wait for any final output buffers before re-initialization. codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 18aeada04b..a0e794764a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -436,9 +436,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { setOutputSurfaceV23(codec, surface); } else { releaseCodec(); - if (state == STATE_ENABLED || state == STATE_STARTED) { - maybeInitCodec(); - } + maybeInitCodec(); } } if (surface != null && surface != dummySurface) { From 62759e8dd622177a9959d903d6e91b5e484a2710 Mon Sep 17 00:00:00 2001 From: olly Date: Sat, 20 Oct 2018 06:41:04 -0700 Subject: [PATCH 113/832] Cleanup VR video player sample app ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218003875 --- .../android/exoplayer2/util/GlUtil.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java index df22e6dc2e..9387392ec4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/GlUtil.java @@ -40,18 +40,14 @@ public final class GlUtil { * ExoPlayerLibraryInfo#GL_ASSERTIONS_ENABLED} is true throws a {@link RuntimeException}. */ public static void checkGlError() { - int error = GLES20.glGetError(); - int lastError; - if (error != GLES20.GL_NO_ERROR) { - do { - lastError = error; - Log.e(TAG, "glError " + gluErrorString(lastError)); - error = GLES20.glGetError(); - } while (error != GLES20.GL_NO_ERROR); - - if (ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED) { - throw new RuntimeException("glError " + gluErrorString(lastError)); - } + int lastError = GLES20.GL_NO_ERROR; + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(TAG, "glError " + gluErrorString(lastError)); + lastError = error; + } + if (ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED && lastError != GLES20.GL_NO_ERROR) { + throw new RuntimeException("glError " + gluErrorString(lastError)); } } From 4170c79a4b1e7fe2a1b14d90d605c600e9cd1e9f Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Sun, 21 Oct 2018 02:53:00 -0700 Subject: [PATCH 114/832] Add ACCESS_NETWORK_STATE permission for MH tests ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218058185 --- playbacktests/src/androidTest/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/playbacktests/src/androidTest/AndroidManifest.xml b/playbacktests/src/androidTest/AndroidManifest.xml index d458df55bb..4165a42568 100644 --- a/playbacktests/src/androidTest/AndroidManifest.xml +++ b/playbacktests/src/androidTest/AndroidManifest.xml @@ -18,6 +18,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.google.android.exoplayer2.playbacktests"> + From ea49d39a4d34feb7f6da5d643235eafdb0feed0d Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 22 Oct 2018 02:50:26 -0700 Subject: [PATCH 115/832] Add VR string and icon ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218137830 --- .../ui/src/main/res/drawable-hdpi/exo_icon_vr.png | Bin 0 -> 291 bytes .../ui/src/main/res/drawable-ldpi/exo_icon_vr.png | Bin 0 -> 170 bytes .../ui/src/main/res/drawable-mdpi/exo_icon_vr.png | Bin 0 -> 207 bytes .../src/main/res/drawable-xhdpi/exo_icon_vr.png | Bin 0 -> 375 bytes .../src/main/res/drawable-xxhdpi/exo_icon_vr.png | Bin 0 -> 602 bytes library/ui/src/main/res/values-af/strings.xml | 1 + library/ui/src/main/res/values-am/strings.xml | 1 + library/ui/src/main/res/values-ar/strings.xml | 1 + library/ui/src/main/res/values-az/strings.xml | 1 + .../ui/src/main/res/values-b+sr+Latn/strings.xml | 1 + library/ui/src/main/res/values-be/strings.xml | 1 + library/ui/src/main/res/values-bg/strings.xml | 1 + library/ui/src/main/res/values-bn/strings.xml | 1 + library/ui/src/main/res/values-bs/strings.xml | 1 + library/ui/src/main/res/values-ca/strings.xml | 1 + library/ui/src/main/res/values-cs/strings.xml | 1 + library/ui/src/main/res/values-da/strings.xml | 1 + library/ui/src/main/res/values-de/strings.xml | 1 + library/ui/src/main/res/values-el/strings.xml | 1 + library/ui/src/main/res/values-en-rAU/strings.xml | 1 + library/ui/src/main/res/values-en-rGB/strings.xml | 1 + library/ui/src/main/res/values-en-rIN/strings.xml | 1 + library/ui/src/main/res/values-es-rUS/strings.xml | 1 + library/ui/src/main/res/values-es/strings.xml | 1 + library/ui/src/main/res/values-et/strings.xml | 1 + library/ui/src/main/res/values-eu/strings.xml | 1 + library/ui/src/main/res/values-fa/strings.xml | 1 + library/ui/src/main/res/values-fi/strings.xml | 1 + library/ui/src/main/res/values-fr-rCA/strings.xml | 1 + library/ui/src/main/res/values-fr/strings.xml | 1 + library/ui/src/main/res/values-gl/strings.xml | 1 + library/ui/src/main/res/values-gu/strings.xml | 1 + library/ui/src/main/res/values-hi/strings.xml | 1 + library/ui/src/main/res/values-hr/strings.xml | 1 + library/ui/src/main/res/values-hu/strings.xml | 1 + library/ui/src/main/res/values-hy/strings.xml | 1 + library/ui/src/main/res/values-in/strings.xml | 1 + library/ui/src/main/res/values-is/strings.xml | 1 + library/ui/src/main/res/values-it/strings.xml | 1 + library/ui/src/main/res/values-iw/strings.xml | 1 + library/ui/src/main/res/values-ja/strings.xml | 1 + library/ui/src/main/res/values-ka/strings.xml | 1 + library/ui/src/main/res/values-kk/strings.xml | 1 + library/ui/src/main/res/values-km/strings.xml | 1 + library/ui/src/main/res/values-kn/strings.xml | 1 + library/ui/src/main/res/values-ko/strings.xml | 1 + library/ui/src/main/res/values-ky/strings.xml | 1 + library/ui/src/main/res/values-lo/strings.xml | 1 + library/ui/src/main/res/values-lt/strings.xml | 1 + library/ui/src/main/res/values-lv/strings.xml | 1 + library/ui/src/main/res/values-mk/strings.xml | 1 + library/ui/src/main/res/values-ml/strings.xml | 1 + library/ui/src/main/res/values-mn/strings.xml | 1 + library/ui/src/main/res/values-mr/strings.xml | 1 + library/ui/src/main/res/values-ms/strings.xml | 1 + library/ui/src/main/res/values-my/strings.xml | 1 + library/ui/src/main/res/values-nb/strings.xml | 1 + library/ui/src/main/res/values-ne/strings.xml | 1 + library/ui/src/main/res/values-nl/strings.xml | 1 + library/ui/src/main/res/values-pa/strings.xml | 1 + library/ui/src/main/res/values-pl/strings.xml | 1 + library/ui/src/main/res/values-pt-rPT/strings.xml | 1 + library/ui/src/main/res/values-pt/strings.xml | 1 + library/ui/src/main/res/values-ro/strings.xml | 1 + library/ui/src/main/res/values-ru/strings.xml | 1 + library/ui/src/main/res/values-si/strings.xml | 1 + library/ui/src/main/res/values-sk/strings.xml | 1 + library/ui/src/main/res/values-sl/strings.xml | 1 + library/ui/src/main/res/values-sq/strings.xml | 1 + library/ui/src/main/res/values-sr/strings.xml | 1 + library/ui/src/main/res/values-sv/strings.xml | 1 + library/ui/src/main/res/values-sw/strings.xml | 1 + library/ui/src/main/res/values-ta/strings.xml | 1 + library/ui/src/main/res/values-te/strings.xml | 1 + library/ui/src/main/res/values-th/strings.xml | 1 + library/ui/src/main/res/values-tl/strings.xml | 1 + library/ui/src/main/res/values-tr/strings.xml | 1 + library/ui/src/main/res/values-uk/strings.xml | 1 + library/ui/src/main/res/values-ur/strings.xml | 1 + library/ui/src/main/res/values-uz/strings.xml | 1 + library/ui/src/main/res/values-vi/strings.xml | 1 + library/ui/src/main/res/values-zh-rCN/strings.xml | 1 + library/ui/src/main/res/values-zh-rHK/strings.xml | 1 + library/ui/src/main/res/values-zh-rTW/strings.xml | 1 + library/ui/src/main/res/values-zu/strings.xml | 1 + library/ui/src/main/res/values/strings.xml | 2 ++ 86 files changed, 82 insertions(+) create mode 100644 library/ui/src/main/res/drawable-hdpi/exo_icon_vr.png create mode 100644 library/ui/src/main/res/drawable-ldpi/exo_icon_vr.png create mode 100644 library/ui/src/main/res/drawable-mdpi/exo_icon_vr.png create mode 100644 library/ui/src/main/res/drawable-xhdpi/exo_icon_vr.png create mode 100644 library/ui/src/main/res/drawable-xxhdpi/exo_icon_vr.png diff --git a/library/ui/src/main/res/drawable-hdpi/exo_icon_vr.png b/library/ui/src/main/res/drawable-hdpi/exo_icon_vr.png new file mode 100644 index 0000000000000000000000000000000000000000..90948eb44f23878a6cf8fcca64da973317231928 GIT binary patch literal 291 zcmV+;0o?wHP)#Jt5@A-Lr0l>mDBPro`~|EJMkgz+dpBVqRGlPk%weuv##X_zN;-L1hOOjC6F2 z_S%`t1uZEN5h<>GbtmpJC_SOBAZ2Y%wN9Qjtqbi!bm zHaVuwGQ`A~z5e8hf-6@FCV{|X*fqSC?u*8M*!hp^o}6&$hUE0}aqV6>H5m`ve~?kj p7mjOXUjA<<;38a^ilQirfFI#cK#(TP+w1@U002ovPDHLkV1gM{eZ2qx literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-ldpi/exo_icon_vr.png b/library/ui/src/main/res/drawable-ldpi/exo_icon_vr.png new file mode 100644 index 0000000000000000000000000000000000000000..6e21960ed6eac2fc430a0cdc3b0108cd36b562f4 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iLQfaRkP61P6B}6%7>Kw;TR9zH zt@J|Sq{2y+nF^k6RlN>VwO26gWdC0H=YRf8DIGIDy@LmrG52|ih0Nh&5t_Atd7jHb zGp?6U|CS#pOZV*mFF!&1Xk4U(>z@qLn;{G1~alTC<@pV?tQq< z{to}I_=e`+#~kB3n*Nz=Kct)F&0^wQwrJ(uvhA&{ng{rF3-V>Rf9PPcT~o8$J?{ed z`UbZgDVG;=%`RtruGO-c`$YRTT^pl!*3Y=IfBAvKTQ(Rl%Rl|oT@_XB`4Z?t22WQ%mvv4F FO#lNFPiFuC literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-xhdpi/exo_icon_vr.png b/library/ui/src/main/res/drawable-xhdpi/exo_icon_vr.png new file mode 100644 index 0000000000000000000000000000000000000000..ff243ef56a603c767f8677e4b59c7db94d00fddc GIT binary patch literal 375 zcmV--0f_#IP)pS0a?c zg&?8k=|MG#EI>-*B|t-}0sI6Q=xB{P?ACPnUIX-0{E!(HRPT002ovPDHLkV1faAonrt1 literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-xxhdpi/exo_icon_vr.png b/library/ui/src/main/res/drawable-xxhdpi/exo_icon_vr.png new file mode 100644 index 0000000000000000000000000000000000000000..7c0338842d0bd3efa47d0ad816ed71c0a6e5acd6 GIT binary patch literal 602 zcmV-g0;Tqr?$rj3;+NC0D!!+X9k=N17R4JjI1>)GLV#EU<`~|{>JnVbO5?C04rm# z{eprSU>F-Dm?p?bsqHTR>Dgh9tt*FKuOVK5=d{nYZNI)B9vymwmeQkxXY8xL%$CQd1`>B}HTfaa^ir9#NASJXRFr(g3W_ASfjS1up5@-cl0m+<_LZxJmTqX>7 zX9AK1DOV))RT9ReELKab=3NOacw|6C7hk1I#DGT@Xu_O#As|T^5aMaZBV<6T@6?=k zA~0u6g!gCUix@N4j5+T>pd_KY#C1t1HDk^@5Gd&5FL6G73e6but$^#dQ2QZn;@4>V zL#9WAvB4-|z;EX;Cy`q^%o#*b@}g2An)rV}Qt`mK$^#X$c>;oG5(1TkXGHS^Dn>+j zDiI?ps{$5k6+y-c!B3}pLWZzdt5_(ua<19aXZN@!w^nRfs1`qY;tXG(^*Q5-Pq)>g zdATbo7;*6H^Z_FZqHerhaal alles Skommel Volskermmodus + Exit VR mode Aflaai Aflaaie Laai tans af diff --git a/library/ui/src/main/res/values-am/strings.xml b/library/ui/src/main/res/values-am/strings.xml index 08c7a3180a..cdc6b5fd7b 100644 --- a/library/ui/src/main/res/values-am/strings.xml +++ b/library/ui/src/main/res/values-am/strings.xml @@ -12,6 +12,7 @@ ሁሉንም ድገም በውዝ የሙሉ ማያ ሁነታ + Exit VR mode አውርድ የወረዱ በማውረድ ላይ diff --git a/library/ui/src/main/res/values-ar/strings.xml b/library/ui/src/main/res/values-ar/strings.xml index f79a1cf5db..e64797f7b3 100644 --- a/library/ui/src/main/res/values-ar/strings.xml +++ b/library/ui/src/main/res/values-ar/strings.xml @@ -12,6 +12,7 @@ تكرار الكل ترتيب عشوائي وضع ملء الشاشة + Exit VR mode تنزيل التنزيلات جارٍ التنزيل. diff --git a/library/ui/src/main/res/values-az/strings.xml b/library/ui/src/main/res/values-az/strings.xml index 4191095b4d..31fde9987f 100644 --- a/library/ui/src/main/res/values-az/strings.xml +++ b/library/ui/src/main/res/values-az/strings.xml @@ -12,6 +12,7 @@ Hamısı təkrarlansın Qarışdırın Tam ekran rejimi + Exit VR mode Endirin Endirmələr Endirilir diff --git a/library/ui/src/main/res/values-b+sr+Latn/strings.xml b/library/ui/src/main/res/values-b+sr+Latn/strings.xml index 6acedb205f..890a19b310 100644 --- a/library/ui/src/main/res/values-b+sr+Latn/strings.xml +++ b/library/ui/src/main/res/values-b+sr+Latn/strings.xml @@ -12,6 +12,7 @@ Ponovi sve Pusti nasumično Režim celog ekrana + Exit VR mode Preuzmi Preuzimanja Preuzima se diff --git a/library/ui/src/main/res/values-be/strings.xml b/library/ui/src/main/res/values-be/strings.xml index 63704e66ca..b6c50e4c27 100644 --- a/library/ui/src/main/res/values-be/strings.xml +++ b/library/ui/src/main/res/values-be/strings.xml @@ -12,6 +12,7 @@ Паўтарыць усе Перамяшаць Поўнаэкранны рэжым + Exit VR mode Спампаваць Спампоўкі Спампоўваецца diff --git a/library/ui/src/main/res/values-bg/strings.xml b/library/ui/src/main/res/values-bg/strings.xml index 74bc85313d..da02946695 100644 --- a/library/ui/src/main/res/values-bg/strings.xml +++ b/library/ui/src/main/res/values-bg/strings.xml @@ -12,6 +12,7 @@ Повтаряне на всички Разбъркване Режим на цял екран + Exit VR mode Изтегляне Изтегляния Изтегля се diff --git a/library/ui/src/main/res/values-bn/strings.xml b/library/ui/src/main/res/values-bn/strings.xml index 4e3b00113f..e90d448a85 100644 --- a/library/ui/src/main/res/values-bn/strings.xml +++ b/library/ui/src/main/res/values-bn/strings.xml @@ -12,6 +12,7 @@ সবগুলি আইটেম আবার চালান শাফেল করুন পূর্ণ স্ক্রিন মোড + Exit VR mode ডাউনলোড করুন ডাউনলোড ডাউনলোড হচ্ছে diff --git a/library/ui/src/main/res/values-bs/strings.xml b/library/ui/src/main/res/values-bs/strings.xml index be2f6459f5..3bb68d006a 100644 --- a/library/ui/src/main/res/values-bs/strings.xml +++ b/library/ui/src/main/res/values-bs/strings.xml @@ -12,6 +12,7 @@ Ponovi sve Izmiješaj Način rada preko cijelog ekrana + Exit VR mode Preuzmi Preuzimanja Preuzimanje diff --git a/library/ui/src/main/res/values-ca/strings.xml b/library/ui/src/main/res/values-ca/strings.xml index 10bf259418..530423f212 100644 --- a/library/ui/src/main/res/values-ca/strings.xml +++ b/library/ui/src/main/res/values-ca/strings.xml @@ -12,6 +12,7 @@ Repeteix tot Reprodueix aleatòriament Mode de pantalla completa + Exit VR mode Baixa Baixades S\'està baixant diff --git a/library/ui/src/main/res/values-cs/strings.xml b/library/ui/src/main/res/values-cs/strings.xml index c910fd3483..e1670d6fc4 100644 --- a/library/ui/src/main/res/values-cs/strings.xml +++ b/library/ui/src/main/res/values-cs/strings.xml @@ -12,6 +12,7 @@ Opakovat vše Náhodně Režim celé obrazovky + Exit VR mode Stáhnout Stahování Stahování diff --git a/library/ui/src/main/res/values-da/strings.xml b/library/ui/src/main/res/values-da/strings.xml index 6a25bbf395..93da6a649a 100644 --- a/library/ui/src/main/res/values-da/strings.xml +++ b/library/ui/src/main/res/values-da/strings.xml @@ -12,6 +12,7 @@ Gentag alle Bland Fuld skærm + Exit VR mode Download Downloads Downloader diff --git a/library/ui/src/main/res/values-de/strings.xml b/library/ui/src/main/res/values-de/strings.xml index 6ac92acf9d..410b261b02 100644 --- a/library/ui/src/main/res/values-de/strings.xml +++ b/library/ui/src/main/res/values-de/strings.xml @@ -12,6 +12,7 @@ Alle wiederholen Zufallsmix Vollbildmodus + Exit VR mode Herunterladen Downloads Wird heruntergeladen diff --git a/library/ui/src/main/res/values-el/strings.xml b/library/ui/src/main/res/values-el/strings.xml index 699650d31e..9c1373a77e 100644 --- a/library/ui/src/main/res/values-el/strings.xml +++ b/library/ui/src/main/res/values-el/strings.xml @@ -12,6 +12,7 @@ Επανάληψη όλων Τυχαία αναπαραγωγή Λειτουργία πλήρους οθόνης + Exit VR mode Λήψη Λήψεις Λήψη diff --git a/library/ui/src/main/res/values-en-rAU/strings.xml b/library/ui/src/main/res/values-en-rAU/strings.xml index 2356f0cd94..74b51249e7 100644 --- a/library/ui/src/main/res/values-en-rAU/strings.xml +++ b/library/ui/src/main/res/values-en-rAU/strings.xml @@ -12,6 +12,7 @@ Repeat all Shuffle Full-screen mode + Exit VR mode Download Downloads Downloading diff --git a/library/ui/src/main/res/values-en-rGB/strings.xml b/library/ui/src/main/res/values-en-rGB/strings.xml index 2356f0cd94..74b51249e7 100644 --- a/library/ui/src/main/res/values-en-rGB/strings.xml +++ b/library/ui/src/main/res/values-en-rGB/strings.xml @@ -12,6 +12,7 @@ Repeat all Shuffle Full-screen mode + Exit VR mode Download Downloads Downloading diff --git a/library/ui/src/main/res/values-en-rIN/strings.xml b/library/ui/src/main/res/values-en-rIN/strings.xml index 2356f0cd94..74b51249e7 100644 --- a/library/ui/src/main/res/values-en-rIN/strings.xml +++ b/library/ui/src/main/res/values-en-rIN/strings.xml @@ -12,6 +12,7 @@ Repeat all Shuffle Full-screen mode + Exit VR mode Download Downloads Downloading diff --git a/library/ui/src/main/res/values-es-rUS/strings.xml b/library/ui/src/main/res/values-es-rUS/strings.xml index b7d8facf17..677fddad66 100644 --- a/library/ui/src/main/res/values-es-rUS/strings.xml +++ b/library/ui/src/main/res/values-es-rUS/strings.xml @@ -12,6 +12,7 @@ Repetir todo Reproducir aleatoriamente Modo de pantalla completa + Exit VR mode Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-es/strings.xml b/library/ui/src/main/res/values-es/strings.xml index 7a48245abb..7e36bf86af 100644 --- a/library/ui/src/main/res/values-es/strings.xml +++ b/library/ui/src/main/res/values-es/strings.xml @@ -12,6 +12,7 @@ Repetir todo Reproducir aleatoriamente Modo de pantalla completa + Exit VR mode Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-et/strings.xml b/library/ui/src/main/res/values-et/strings.xml index 0ed5389da7..189c5032db 100644 --- a/library/ui/src/main/res/values-et/strings.xml +++ b/library/ui/src/main/res/values-et/strings.xml @@ -12,6 +12,7 @@ Korda kõiki Esita juhuslikus järjekorras Täisekraani režiim + Exit VR mode Allalaadimine Allalaadimised Allalaadimine diff --git a/library/ui/src/main/res/values-eu/strings.xml b/library/ui/src/main/res/values-eu/strings.xml index 31ec286cfb..2db2ac2131 100644 --- a/library/ui/src/main/res/values-eu/strings.xml +++ b/library/ui/src/main/res/values-eu/strings.xml @@ -12,6 +12,7 @@ Errepikatu guztiak Erreproduzitu ausaz Pantaila osoko modua + Exit VR mode Deskargak Deskargak Deskargatzen diff --git a/library/ui/src/main/res/values-fa/strings.xml b/library/ui/src/main/res/values-fa/strings.xml index 9b0853cee5..e923dae795 100644 --- a/library/ui/src/main/res/values-fa/strings.xml +++ b/library/ui/src/main/res/values-fa/strings.xml @@ -12,6 +12,7 @@ تکرار همه درهم حالت تمام‌صفحه + Exit VR mode بارگیری بارگیری‌ها درحال بارگیری diff --git a/library/ui/src/main/res/values-fi/strings.xml b/library/ui/src/main/res/values-fi/strings.xml index d9b33f0977..89b2ed60bc 100644 --- a/library/ui/src/main/res/values-fi/strings.xml +++ b/library/ui/src/main/res/values-fi/strings.xml @@ -12,6 +12,7 @@ Toista kaikki uudelleen Satunnaistoisto Koko näytön tila + Exit VR mode Lataa Lataukset Ladataan diff --git a/library/ui/src/main/res/values-fr-rCA/strings.xml b/library/ui/src/main/res/values-fr-rCA/strings.xml index b68fab04ed..a694f48c10 100644 --- a/library/ui/src/main/res/values-fr-rCA/strings.xml +++ b/library/ui/src/main/res/values-fr-rCA/strings.xml @@ -12,6 +12,7 @@ Tout lire en boucle Lecture aléatoire Mode Plein écran + Exit VR mode Télécharger Téléchargements Téléchargement en cours… diff --git a/library/ui/src/main/res/values-fr/strings.xml b/library/ui/src/main/res/values-fr/strings.xml index 48c19e30d9..b7c4168bef 100644 --- a/library/ui/src/main/res/values-fr/strings.xml +++ b/library/ui/src/main/res/values-fr/strings.xml @@ -12,6 +12,7 @@ Tout lire en boucle Aléatoire Mode plein écran + Exit VR mode Télécharger Téléchargements Téléchargement… diff --git a/library/ui/src/main/res/values-gl/strings.xml b/library/ui/src/main/res/values-gl/strings.xml index e41b1d1445..ead458f87c 100644 --- a/library/ui/src/main/res/values-gl/strings.xml +++ b/library/ui/src/main/res/values-gl/strings.xml @@ -12,6 +12,7 @@ Repetir todas as pistas Reprodución aleatoria Modo de pantalla completa + Exit VR mode Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-gu/strings.xml b/library/ui/src/main/res/values-gu/strings.xml index 5d8dd31cc5..e00f7cc7fb 100644 --- a/library/ui/src/main/res/values-gu/strings.xml +++ b/library/ui/src/main/res/values-gu/strings.xml @@ -12,6 +12,7 @@ બધાને રિપીટ કરો શફલ કરો પૂર્ણસ્ક્રીન મોડ + Exit VR mode ડાઉનલોડ કરો ડાઉનલોડ ડાઉનલોડ કરી રહ્યાં છીએ diff --git a/library/ui/src/main/res/values-hi/strings.xml b/library/ui/src/main/res/values-hi/strings.xml index 9df542ef52..4ba488e95d 100644 --- a/library/ui/src/main/res/values-hi/strings.xml +++ b/library/ui/src/main/res/values-hi/strings.xml @@ -12,6 +12,7 @@ सभी को दोहराएं शफ़ल करें फ़ुलस्क्रीन मोड + Exit VR mode डाउनलोड करें डाउनलोड की गई मीडिया फ़ाइलें डाउनलोड हो रहा है diff --git a/library/ui/src/main/res/values-hr/strings.xml b/library/ui/src/main/res/values-hr/strings.xml index 7d5de9b189..1cba6e162b 100644 --- a/library/ui/src/main/res/values-hr/strings.xml +++ b/library/ui/src/main/res/values-hr/strings.xml @@ -12,6 +12,7 @@ Ponovi sve Reproduciraj nasumično Prikaz na cijelom zaslonu + Exit VR mode Preuzmi Preuzimanja Preuzimanje diff --git a/library/ui/src/main/res/values-hu/strings.xml b/library/ui/src/main/res/values-hu/strings.xml index bb7d5950e6..d271d741ab 100644 --- a/library/ui/src/main/res/values-hu/strings.xml +++ b/library/ui/src/main/res/values-hu/strings.xml @@ -12,6 +12,7 @@ Összes szám ismétlése Keverés Teljes képernyős mód + Exit VR mode Letöltés Letöltések Letöltés folyamatban diff --git a/library/ui/src/main/res/values-hy/strings.xml b/library/ui/src/main/res/values-hy/strings.xml index 38468b892d..c017a2fc27 100644 --- a/library/ui/src/main/res/values-hy/strings.xml +++ b/library/ui/src/main/res/values-hy/strings.xml @@ -12,6 +12,7 @@ Կրկնել բոլորը Խառնել Լիաէկրան ռեժիմ + Exit VR mode Ներբեռնել Ներբեռնումներ Ներբեռնում diff --git a/library/ui/src/main/res/values-in/strings.xml b/library/ui/src/main/res/values-in/strings.xml index 6cbd6a703a..92c6e055b6 100644 --- a/library/ui/src/main/res/values-in/strings.xml +++ b/library/ui/src/main/res/values-in/strings.xml @@ -12,6 +12,7 @@ Ulangi semua Acak Mode layar penuh + Exit VR mode Download Download Mendownload diff --git a/library/ui/src/main/res/values-is/strings.xml b/library/ui/src/main/res/values-is/strings.xml index cb5f40ef51..77f03e4889 100644 --- a/library/ui/src/main/res/values-is/strings.xml +++ b/library/ui/src/main/res/values-is/strings.xml @@ -12,6 +12,7 @@ Endurtaka allt Stokka Allur skjárinn + Exit VR mode Sækja Niðurhal Sækir diff --git a/library/ui/src/main/res/values-it/strings.xml b/library/ui/src/main/res/values-it/strings.xml index 7e058b4a49..72ea9ee01d 100644 --- a/library/ui/src/main/res/values-it/strings.xml +++ b/library/ui/src/main/res/values-it/strings.xml @@ -12,6 +12,7 @@ Ripeti tutto Riproduzione casuale Modalità a schermo intero + Exit VR mode Scarica Download Download… diff --git a/library/ui/src/main/res/values-iw/strings.xml b/library/ui/src/main/res/values-iw/strings.xml index 9aec8e5834..003597fdee 100644 --- a/library/ui/src/main/res/values-iw/strings.xml +++ b/library/ui/src/main/res/values-iw/strings.xml @@ -12,6 +12,7 @@ חזור על הכול ערבוב מצב מסך מלא + Exit VR mode הורדה הורדות ההורדה מתבצעת diff --git a/library/ui/src/main/res/values-ja/strings.xml b/library/ui/src/main/res/values-ja/strings.xml index 86f204e572..5ea4a3ec07 100644 --- a/library/ui/src/main/res/values-ja/strings.xml +++ b/library/ui/src/main/res/values-ja/strings.xml @@ -12,6 +12,7 @@ 全曲をリピート シャッフル 全画面モード + Exit VR mode ダウンロード ダウンロード ダウンロードしています diff --git a/library/ui/src/main/res/values-ka/strings.xml b/library/ui/src/main/res/values-ka/strings.xml index 5b8c08065e..06b798baf2 100644 --- a/library/ui/src/main/res/values-ka/strings.xml +++ b/library/ui/src/main/res/values-ka/strings.xml @@ -12,6 +12,7 @@ ყველას გამეორება არეულად დაკვრა სრულეკრანიანი რეჟიმი + Exit VR mode ჩამოტვირთვა ჩამოტვირთვები მიმდინარეობს ჩამოტვირთვა diff --git a/library/ui/src/main/res/values-kk/strings.xml b/library/ui/src/main/res/values-kk/strings.xml index 3842263348..1988908ae3 100644 --- a/library/ui/src/main/res/values-kk/strings.xml +++ b/library/ui/src/main/res/values-kk/strings.xml @@ -12,6 +12,7 @@ Барлығын қайталау Араластыру Толық экран режимі + Exit VR mode Жүктеп алу Жүктеп алынғандар Жүктеп алынуда diff --git a/library/ui/src/main/res/values-km/strings.xml b/library/ui/src/main/res/values-km/strings.xml index 89d73605a5..e78efe17d7 100644 --- a/library/ui/src/main/res/values-km/strings.xml +++ b/library/ui/src/main/res/values-km/strings.xml @@ -12,6 +12,7 @@ លេង​ឡើងវិញ​ទាំងអស់ ច្របល់ មុខងារពេញ​អេក្រង់ + Exit VR mode ទាញយក ទាញយក កំពុង​ទាញ​យក diff --git a/library/ui/src/main/res/values-kn/strings.xml b/library/ui/src/main/res/values-kn/strings.xml index 65d65b1d23..1efac2b25f 100644 --- a/library/ui/src/main/res/values-kn/strings.xml +++ b/library/ui/src/main/res/values-kn/strings.xml @@ -12,6 +12,7 @@ ಎಲ್ಲವನ್ನು ಪುನರಾವರ್ತಿಸಿ ಶಫಲ್‌ ಪೂರ್ಣ ಪರದೆ ಮೋಡ್ + Exit VR mode ಡೌನ್‌ಲೋಡ್‌ ಡೌನ್‌ಲೋಡ್‌ಗಳು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ diff --git a/library/ui/src/main/res/values-ko/strings.xml b/library/ui/src/main/res/values-ko/strings.xml index 7714203e5e..92d840374a 100644 --- a/library/ui/src/main/res/values-ko/strings.xml +++ b/library/ui/src/main/res/values-ko/strings.xml @@ -12,6 +12,7 @@ 모두 반복 셔플 전체화면 모드 + Exit VR mode 다운로드 다운로드 다운로드 중 diff --git a/library/ui/src/main/res/values-ky/strings.xml b/library/ui/src/main/res/values-ky/strings.xml index 20a4739a2b..614e5846c4 100644 --- a/library/ui/src/main/res/values-ky/strings.xml +++ b/library/ui/src/main/res/values-ky/strings.xml @@ -12,6 +12,7 @@ Баарын кайталоо Аралаштыруу Толук экран режими + Exit VR mode Жүктөп алуу Жүктөлүп алынгандар Жүктөлүп алынууда diff --git a/library/ui/src/main/res/values-lo/strings.xml b/library/ui/src/main/res/values-lo/strings.xml index 3de5962a42..291dd5f187 100644 --- a/library/ui/src/main/res/values-lo/strings.xml +++ b/library/ui/src/main/res/values-lo/strings.xml @@ -12,6 +12,7 @@ ຫຼິ້ນຊ້ຳທັງໝົດ ຫຼີ້ນແບບສຸ່ມ ໂໝດເຕັມຈໍ + Exit VR mode ດາວໂຫລດ ດາວໂຫລດ ກຳລັງດາວໂຫລດ diff --git a/library/ui/src/main/res/values-lt/strings.xml b/library/ui/src/main/res/values-lt/strings.xml index eaf26aafb6..c3e39edc3c 100644 --- a/library/ui/src/main/res/values-lt/strings.xml +++ b/library/ui/src/main/res/values-lt/strings.xml @@ -12,6 +12,7 @@ Kartoti viską Maišyti Viso ekrano režimas + Exit VR mode Atsisiųsti Atsisiuntimai Atsisiunčiama diff --git a/library/ui/src/main/res/values-lv/strings.xml b/library/ui/src/main/res/values-lv/strings.xml index 708a2143c7..cf3c20e53d 100644 --- a/library/ui/src/main/res/values-lv/strings.xml +++ b/library/ui/src/main/res/values-lv/strings.xml @@ -12,6 +12,7 @@ Atkārtot visu Atskaņot jauktā secībā Pilnekrāna režīms + Exit VR mode Lejupielādēt Lejupielādes Notiek lejupielāde diff --git a/library/ui/src/main/res/values-mk/strings.xml b/library/ui/src/main/res/values-mk/strings.xml index 3e6ae777cc..8b44cd317e 100644 --- a/library/ui/src/main/res/values-mk/strings.xml +++ b/library/ui/src/main/res/values-mk/strings.xml @@ -12,6 +12,7 @@ Повтори ги сите Измешај Режим на цел екран + Exit VR mode Преземи Преземања Се презема diff --git a/library/ui/src/main/res/values-ml/strings.xml b/library/ui/src/main/res/values-ml/strings.xml index acd3828fe2..d5c474bd17 100644 --- a/library/ui/src/main/res/values-ml/strings.xml +++ b/library/ui/src/main/res/values-ml/strings.xml @@ -12,6 +12,7 @@ എല്ലാം ആവർത്തിക്കുക ഇടകലര്‍ത്തുക പൂർണ്ണ സ്‌ക്രീൻ മോഡ് + Exit VR mode ഡൗൺലോഡ് ഡൗൺലോഡുകൾ ഡൗൺലോഡ് ചെയ്യുന്നു diff --git a/library/ui/src/main/res/values-mn/strings.xml b/library/ui/src/main/res/values-mn/strings.xml index 328827f87f..77d493b42f 100644 --- a/library/ui/src/main/res/values-mn/strings.xml +++ b/library/ui/src/main/res/values-mn/strings.xml @@ -12,6 +12,7 @@ Бүгдийг нь дахин тоглуулах Холих Бүтэн дэлгэцийн горим + Exit VR mode Татах Татaлт Татаж байна diff --git a/library/ui/src/main/res/values-mr/strings.xml b/library/ui/src/main/res/values-mr/strings.xml index 45bac7c61f..c88466ca9c 100644 --- a/library/ui/src/main/res/values-mr/strings.xml +++ b/library/ui/src/main/res/values-mr/strings.xml @@ -12,6 +12,7 @@ सर्व रीपीट करा शफल करा फुल स्क्रीन मोड + Exit VR mode डाउनलोड करा डाउनलोड डाउनलोड होत आहे diff --git a/library/ui/src/main/res/values-ms/strings.xml b/library/ui/src/main/res/values-ms/strings.xml index c4a437da3b..df74b68abc 100644 --- a/library/ui/src/main/res/values-ms/strings.xml +++ b/library/ui/src/main/res/values-ms/strings.xml @@ -12,6 +12,7 @@ Ulang semua Rombak Mod skrin penuh + Exit VR mode Muat turun Muat turun Memuat turun diff --git a/library/ui/src/main/res/values-my/strings.xml b/library/ui/src/main/res/values-my/strings.xml index 497ff50416..d7aa70c610 100644 --- a/library/ui/src/main/res/values-my/strings.xml +++ b/library/ui/src/main/res/values-my/strings.xml @@ -12,6 +12,7 @@ အားလုံး ပြန်ကျော့ရန် ရောသမမွှေ မျက်နှာပြင်အပြည့် မုဒ် + Exit VR mode ဒေါင်းလုဒ် လုပ်ရန် ဒေါင်းလုဒ်များ ဒေါင်းလုဒ်လုပ်နေသည် diff --git a/library/ui/src/main/res/values-nb/strings.xml b/library/ui/src/main/res/values-nb/strings.xml index 7e48146084..c73deb4a18 100644 --- a/library/ui/src/main/res/values-nb/strings.xml +++ b/library/ui/src/main/res/values-nb/strings.xml @@ -12,6 +12,7 @@ Gjenta alle Tilfeldig rekkefølge Fullskjermmodus + Exit VR mode Last ned Nedlastinger Laster ned diff --git a/library/ui/src/main/res/values-ne/strings.xml b/library/ui/src/main/res/values-ne/strings.xml index 5011998b87..7daff17e1f 100644 --- a/library/ui/src/main/res/values-ne/strings.xml +++ b/library/ui/src/main/res/values-ne/strings.xml @@ -12,6 +12,7 @@ सबै दोहोर्‍याउनुहोस् मिसाउनुहोस् पूर्ण स्क्रिन मोड + Exit VR mode डाउनलोड गर्नुहोस् डाउनलोडहरू डाउनलोड गरिँदै छ diff --git a/library/ui/src/main/res/values-nl/strings.xml b/library/ui/src/main/res/values-nl/strings.xml index 1a2880ae1f..4749f6bbbb 100644 --- a/library/ui/src/main/res/values-nl/strings.xml +++ b/library/ui/src/main/res/values-nl/strings.xml @@ -12,6 +12,7 @@ Alles herhalen Shuffle Modus \'Volledig scherm\' + Exit VR mode Downloaden Downloads Downloaden diff --git a/library/ui/src/main/res/values-pa/strings.xml b/library/ui/src/main/res/values-pa/strings.xml index effc9250ed..ea5f8f034f 100644 --- a/library/ui/src/main/res/values-pa/strings.xml +++ b/library/ui/src/main/res/values-pa/strings.xml @@ -12,6 +12,7 @@ ਸਾਰਿਆਂ ਨੂੰ ਦੁਹਰਾਓ ਬੇਤਰਤੀਬ ਕਰੋ ਪੂਰੀ-ਸਕ੍ਰੀਨ ਮੋਡ + Exit VR mode ਡਾਊਨਲੋਡ ਕਰੋ ਡਾਊਨਲੋਡ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ diff --git a/library/ui/src/main/res/values-pl/strings.xml b/library/ui/src/main/res/values-pl/strings.xml index 312481fa2b..6e08237bfd 100644 --- a/library/ui/src/main/res/values-pl/strings.xml +++ b/library/ui/src/main/res/values-pl/strings.xml @@ -12,6 +12,7 @@ Powtórz wszystkie Odtwarzanie losowe Tryb pełnoekranowy + Exit VR mode Pobierz Pobieranie Pobieram diff --git a/library/ui/src/main/res/values-pt-rPT/strings.xml b/library/ui/src/main/res/values-pt-rPT/strings.xml index 52eb01e219..6bbfb49c6b 100644 --- a/library/ui/src/main/res/values-pt-rPT/strings.xml +++ b/library/ui/src/main/res/values-pt-rPT/strings.xml @@ -12,6 +12,7 @@ Repetir tudo Reproduzir aleatoriamente Modo de ecrã inteiro + Exit VR mode Transferir Transferências A transferir… diff --git a/library/ui/src/main/res/values-pt/strings.xml b/library/ui/src/main/res/values-pt/strings.xml index 6ea00fed8d..96c27f3d4a 100644 --- a/library/ui/src/main/res/values-pt/strings.xml +++ b/library/ui/src/main/res/values-pt/strings.xml @@ -12,6 +12,7 @@ Repetir tudo Aleatório Modo de tela cheia + Exit VR mode Fazer o download Downloads Fazendo o download diff --git a/library/ui/src/main/res/values-ro/strings.xml b/library/ui/src/main/res/values-ro/strings.xml index 4ea18d4a58..cb50bb6b76 100644 --- a/library/ui/src/main/res/values-ro/strings.xml +++ b/library/ui/src/main/res/values-ro/strings.xml @@ -12,6 +12,7 @@ Repetați-le pe toate Redați aleatoriu Modul Ecran complet + Exit VR mode Descărcați Descărcări Se descarcă diff --git a/library/ui/src/main/res/values-ru/strings.xml b/library/ui/src/main/res/values-ru/strings.xml index 14c8badf99..24659ef1c1 100644 --- a/library/ui/src/main/res/values-ru/strings.xml +++ b/library/ui/src/main/res/values-ru/strings.xml @@ -12,6 +12,7 @@ Повторять все Перемешать Полноэкранный режим + Exit VR mode Скачать Скачивания Скачивание… diff --git a/library/ui/src/main/res/values-si/strings.xml b/library/ui/src/main/res/values-si/strings.xml index 92ae038c4b..80412f5aa5 100644 --- a/library/ui/src/main/res/values-si/strings.xml +++ b/library/ui/src/main/res/values-si/strings.xml @@ -12,6 +12,7 @@ සියල්ල පුනරාවර්තනය කරන්න කලවම් කරන්න සම්පූර්ණ තිර ප්‍රකාරය + Exit VR mode බාගන්න බාගැනීම් බාගනිමින් diff --git a/library/ui/src/main/res/values-sk/strings.xml b/library/ui/src/main/res/values-sk/strings.xml index f4f997b207..99ec680405 100644 --- a/library/ui/src/main/res/values-sk/strings.xml +++ b/library/ui/src/main/res/values-sk/strings.xml @@ -12,6 +12,7 @@ Opakovať všetko Náhodne prehrávať Režim celej obrazovky + Exit VR mode Stiahnuť Stiahnuté Sťahuje sa diff --git a/library/ui/src/main/res/values-sl/strings.xml b/library/ui/src/main/res/values-sl/strings.xml index 83d332103e..ec75cd1d4b 100644 --- a/library/ui/src/main/res/values-sl/strings.xml +++ b/library/ui/src/main/res/values-sl/strings.xml @@ -12,6 +12,7 @@ Ponavljanje vseh Naključno predvajanje Celozaslonski način + Exit VR mode Prenos Prenosi Prenašanje diff --git a/library/ui/src/main/res/values-sq/strings.xml b/library/ui/src/main/res/values-sq/strings.xml index c46524762d..2c139f20cb 100644 --- a/library/ui/src/main/res/values-sq/strings.xml +++ b/library/ui/src/main/res/values-sq/strings.xml @@ -12,6 +12,7 @@ Përsërit të gjitha Përziej Modaliteti me ekran të plotë + Exit VR mode Shkarko Shkarkimet Po shkarkohet diff --git a/library/ui/src/main/res/values-sr/strings.xml b/library/ui/src/main/res/values-sr/strings.xml index 394ab8d36e..82af21ba17 100644 --- a/library/ui/src/main/res/values-sr/strings.xml +++ b/library/ui/src/main/res/values-sr/strings.xml @@ -12,6 +12,7 @@ Понови све Пусти насумично Режим целог екрана + Exit VR mode Преузми Преузимања Преузима се diff --git a/library/ui/src/main/res/values-sv/strings.xml b/library/ui/src/main/res/values-sv/strings.xml index 38daddb507..a99f8409bd 100644 --- a/library/ui/src/main/res/values-sv/strings.xml +++ b/library/ui/src/main/res/values-sv/strings.xml @@ -12,6 +12,7 @@ Upprepa alla Blanda spår Helskärmsläge + Exit VR mode Ladda ned Nedladdningar Laddar ned diff --git a/library/ui/src/main/res/values-sw/strings.xml b/library/ui/src/main/res/values-sw/strings.xml index 2c7268626f..aeefbc2a9f 100644 --- a/library/ui/src/main/res/values-sw/strings.xml +++ b/library/ui/src/main/res/values-sw/strings.xml @@ -12,6 +12,7 @@ Rudia zote Changanya Hali ya skrini nzima + Exit VR mode Pakua Vipakuliwa Inapakua diff --git a/library/ui/src/main/res/values-ta/strings.xml b/library/ui/src/main/res/values-ta/strings.xml index 14a0203e06..655da2f718 100644 --- a/library/ui/src/main/res/values-ta/strings.xml +++ b/library/ui/src/main/res/values-ta/strings.xml @@ -12,6 +12,7 @@ அனைத்தையும் மீண்டும் இயக்கு கலைத்துப் போடு முழுத்திரைப் பயன்முறை + Exit VR mode பதிவிறக்கும் பட்டன் பதிவிறக்கங்கள் பதிவிறக்குகிறது diff --git a/library/ui/src/main/res/values-te/strings.xml b/library/ui/src/main/res/values-te/strings.xml index 7e3f32a039..ed056ef26e 100644 --- a/library/ui/src/main/res/values-te/strings.xml +++ b/library/ui/src/main/res/values-te/strings.xml @@ -12,6 +12,7 @@ అన్నింటినీ పునరావృతం చేయండి షఫుల్ చేయండి పూర్తి స్క్రీన్ మోడ్ + Exit VR mode డౌన్‌లోడ్ చేయి డౌన్‌లోడ్‌లు డౌన్‌లోడ్ చేస్తోంది diff --git a/library/ui/src/main/res/values-th/strings.xml b/library/ui/src/main/res/values-th/strings.xml index 85c4f8cf92..5193aa3b86 100644 --- a/library/ui/src/main/res/values-th/strings.xml +++ b/library/ui/src/main/res/values-th/strings.xml @@ -12,6 +12,7 @@ เล่นซ้ำทั้งหมด สุ่ม โหมดเต็มหน้าจอ + Exit VR mode ดาวน์โหลด ดาวน์โหลด กำลังดาวน์โหลด diff --git a/library/ui/src/main/res/values-tl/strings.xml b/library/ui/src/main/res/values-tl/strings.xml index dfad2c1b08..54b4e9ca6c 100644 --- a/library/ui/src/main/res/values-tl/strings.xml +++ b/library/ui/src/main/res/values-tl/strings.xml @@ -12,6 +12,7 @@ Ulitin lahat I-shuffle Fullscreen mode + Exit VR mode I-download Mga Download Nagda-download diff --git a/library/ui/src/main/res/values-tr/strings.xml b/library/ui/src/main/res/values-tr/strings.xml index cacb60c5a5..6c93d81270 100644 --- a/library/ui/src/main/res/values-tr/strings.xml +++ b/library/ui/src/main/res/values-tr/strings.xml @@ -12,6 +12,7 @@ Tümünü tekrarla Karıştır Tam ekran modu + Exit VR mode İndir İndirilenler İndiriliyor diff --git a/library/ui/src/main/res/values-uk/strings.xml b/library/ui/src/main/res/values-uk/strings.xml index ecf6c8745e..ac1d4c0ce5 100644 --- a/library/ui/src/main/res/values-uk/strings.xml +++ b/library/ui/src/main/res/values-uk/strings.xml @@ -12,6 +12,7 @@ Повторити всі Перемішати Повноекранний режим + Exit VR mode Завантажити Завантаження Завантажується diff --git a/library/ui/src/main/res/values-ur/strings.xml b/library/ui/src/main/res/values-ur/strings.xml index fbc18fa347..bf4e6eddaf 100644 --- a/library/ui/src/main/res/values-ur/strings.xml +++ b/library/ui/src/main/res/values-ur/strings.xml @@ -12,6 +12,7 @@ سبھی کو دہرائیں شفل کریں پوری اسکرین والی وضع + Exit VR mode ڈاؤن لوڈ کریں ڈاؤن لوڈز ڈاؤن لوڈ ہو رہا ہے diff --git a/library/ui/src/main/res/values-uz/strings.xml b/library/ui/src/main/res/values-uz/strings.xml index 5c9a05d259..5275d7bd06 100644 --- a/library/ui/src/main/res/values-uz/strings.xml +++ b/library/ui/src/main/res/values-uz/strings.xml @@ -12,6 +12,7 @@ Hammasini takrorlash Aralash Butun ekran rejimi + Exit VR mode Yuklab olish Yuklanmalar Yuklab olinmoqda diff --git a/library/ui/src/main/res/values-vi/strings.xml b/library/ui/src/main/res/values-vi/strings.xml index 65c9cb52a3..bb9b290bd4 100644 --- a/library/ui/src/main/res/values-vi/strings.xml +++ b/library/ui/src/main/res/values-vi/strings.xml @@ -12,6 +12,7 @@ Lặp lại tất cả Phát ngẫu nhiên Chế độ toàn màn hình + Exit VR mode Tải xuống Tài nguyên đã tải xuống Đang tải xuống diff --git a/library/ui/src/main/res/values-zh-rCN/strings.xml b/library/ui/src/main/res/values-zh-rCN/strings.xml index e75697621c..ece063ff0f 100644 --- a/library/ui/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui/src/main/res/values-zh-rCN/strings.xml @@ -12,6 +12,7 @@ 全部重复播放 随机播放 全屏模式 + Exit VR mode 下载 下载内容 正在下载 diff --git a/library/ui/src/main/res/values-zh-rHK/strings.xml b/library/ui/src/main/res/values-zh-rHK/strings.xml index e65831ad9f..6ccdd01a2a 100644 --- a/library/ui/src/main/res/values-zh-rHK/strings.xml +++ b/library/ui/src/main/res/values-zh-rHK/strings.xml @@ -12,6 +12,7 @@ 全部重複播放 隨機播放 全螢幕模式 + Exit VR mode 下載 下載內容 正在下載 diff --git a/library/ui/src/main/res/values-zh-rTW/strings.xml b/library/ui/src/main/res/values-zh-rTW/strings.xml index b817f189fb..04134d92a6 100644 --- a/library/ui/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui/src/main/res/values-zh-rTW/strings.xml @@ -12,6 +12,7 @@ 重複播放所有項目 隨機播放 全螢幕模式 + Exit VR mode 下載 下載 下載中 diff --git a/library/ui/src/main/res/values-zu/strings.xml b/library/ui/src/main/res/values-zu/strings.xml index 0b78b7d1fa..40a1a6ce29 100644 --- a/library/ui/src/main/res/values-zu/strings.xml +++ b/library/ui/src/main/res/values-zu/strings.xml @@ -12,6 +12,7 @@ Phinda konke Shova Imodi yesikrini esigcwele + Exit VR mode Landa Ukulandwa Iyalanda diff --git a/library/ui/src/main/res/values/strings.xml b/library/ui/src/main/res/values/strings.xml index d3befa2f43..2acbaadeeb 100644 --- a/library/ui/src/main/res/values/strings.xml +++ b/library/ui/src/main/res/values/strings.xml @@ -38,6 +38,8 @@ Shuffle Fullscreen mode + + Exit VR mode Download From 5ce2f1763d061d63f9d2f95e94da0a5441328c20 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 22 Oct 2018 03:31:38 -0700 Subject: [PATCH 116/832] Check if source has been prepared before releasing it. In ConcatenatingMediaSource, the source may be removed before it started preparing (this may happen if lazyPreparation=true). In this case, we shouldn't call releaseSource as the preparation didn't start. Issue:#4986 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218141658 --- RELEASENOTES.md | 3 +++ .../source/ConcatenatingMediaSource.java | 18 ++++++++++++------ .../source/ConcatenatingMediaSourceTest.java | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 060b5f4daf..17e445dd98 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -30,6 +30,9 @@ * IMA extension: * For preroll to live stream transitions, project forward the loading position to avoid being behind the live window. +* Fix issue where a `NullPointerException` is thrown when removing an unprepared + media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` + option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)). ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index fb99eef6e7..830ab52e19 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -502,9 +502,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource Date: Mon, 22 Oct 2018 12:59:24 -0700 Subject: [PATCH 117/832] Remove the Uri suffix from MediaItem.DrmScheme.licenseServerUri Makes it consistent with MediaItem.media. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218217648 --- .../google/android/exoplayer2/ext/cast/MediaItem.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java index f9188e08be..02a8d1b5d4 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java @@ -201,20 +201,20 @@ public final class MediaItem { public final UUID uuid; /** - * A optional {@link UriBundle} for the license server. If no license server is provided, the + * Optional {@link UriBundle} for the license server. If no license server is provided, the * server must be provided by the media. */ - @Nullable public final UriBundle licenseServerUri; + @Nullable public final UriBundle licenseServer; /** * Creates an instance. * * @param uuid See {@link #uuid}. - * @param licenseServerUri See {@link #licenseServerUri}. + * @param licenseServer See {@link #licenseServer}. */ - public DrmScheme(UUID uuid, @Nullable UriBundle licenseServerUri) { + public DrmScheme(UUID uuid, @Nullable UriBundle licenseServer) { this.uuid = uuid; - this.licenseServerUri = licenseServerUri; + this.licenseServer = licenseServer; } } From e1ba9b0086b8510614160d0ab7df08aa509c3181 Mon Sep 17 00:00:00 2001 From: vigneshv Date: Mon, 22 Oct 2018 14:58:21 -0700 Subject: [PATCH 118/832] vp9_extension: Enable fast loop filtering Turn on flag to enable fast loop filtering. This improves multi-threaded decode performance by starting loop filtering early if there are free threads available to do so. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218239442 --- extensions/vp9/src/main/jni/vpx_jni.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/extensions/vp9/src/main/jni/vpx_jni.cc b/extensions/vp9/src/main/jni/vpx_jni.cc index f36c433b22..875e46d40f 100644 --- a/extensions/vp9/src/main/jni/vpx_jni.cc +++ b/extensions/vp9/src/main/jni/vpx_jni.cc @@ -460,6 +460,14 @@ DECODER_FUNC(jlong, vpxInit, jboolean disableLoopFilter, if (err) { LOGE("ERROR: Failed to shut off libvpx loop filter, error = %d.", err); } +#ifdef VPX_CTRL_VP9_SET_LOOP_FILTER_OPT + } else { + err = vpx_codec_control(context->decoder, VP9D_SET_LOOP_FILTER_OPT, true); + if (err) { + LOGE("ERROR: Failed to enable loop filter optimization, error = %d.", + err); + } +#endif } if (enableBufferManager) { err = vpx_codec_set_frame_buffer_functions( From 2d63be096229571508939e8277c0537ac81c0728 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 23 Oct 2018 05:41:48 -0700 Subject: [PATCH 119/832] Swap google() and jcenter() in docs and gradle config. This seems to be more stable in case Bintray has issues updating the ExoPlayer sources. Issue:#4997 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218327350 --- README.md | 2 +- RELEASENOTES.md | 2 ++ build.gradle | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 13dfaddab3..b69be03ae4 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ included in the `build.gradle` file in the root of your project: ```gradle repositories { - jcenter() google() + jcenter() } ``` diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 17e445dd98..84864a35c4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -33,6 +33,8 @@ * Fix issue where a `NullPointerException` is thrown when removing an unprepared media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)). +* Swap recommended order for google() and jcenter() in gradle config + ([#4997](https://github.com/google/ExoPlayer/issues/4997)). ### 2.9.0 ### diff --git a/build.gradle b/build.gradle index a013f4fb84..96eade1aa3 100644 --- a/build.gradle +++ b/build.gradle @@ -13,8 +13,8 @@ // limitations under the License. buildscript { repositories { - jcenter() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.1.4' @@ -32,8 +32,8 @@ buildscript { } allprojects { repositories { - jcenter() google() + jcenter() } project.ext { exoplayerPublishEnabled = true From 2fc122745a1bde8aafe45f87e38be36006660460 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 23 Oct 2018 06:23:50 -0700 Subject: [PATCH 120/832] Give EventDispatcher more predictable behavior If EventDispatcher.removeListener is called to remove a listener, and if the call is made from the same thread that said listener handles events on, then it should be guaranteed that the listener will not be subsequently invoked on that thread. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218331427 --- .../exoplayer2/util/EventDispatcher.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventDispatcher.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventDispatcher.java index 26c02d8ae9..07f278c808 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventDispatcher.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventDispatcher.java @@ -39,22 +39,23 @@ public final class EventDispatcher { /** The list of listeners and handlers. */ private final CopyOnWriteArrayList> listeners; - /** Creates event dispatcher. */ + /** Creates an event dispatcher. */ public EventDispatcher() { listeners = new CopyOnWriteArrayList<>(); } - /** Adds listener to event dispatcher. */ + /** Adds a listener to the event dispatcher. */ public void addListener(Handler handler, T eventListener) { Assertions.checkArgument(handler != null && eventListener != null); removeListener(eventListener); listeners.add(new HandlerAndListener<>(handler, eventListener)); } - /** Removes listener from event dispatcher. */ + /** Removes a listener from the event dispatcher. */ public void removeListener(T eventListener) { for (HandlerAndListener handlerAndListener : listeners) { if (handlerAndListener.listener == eventListener) { + handlerAndListener.release(); listeners.remove(handlerAndListener); } } @@ -67,19 +68,33 @@ public final class EventDispatcher { */ public void dispatch(Event event) { for (HandlerAndListener handlerAndListener : listeners) { - T eventListener = handlerAndListener.listener; - handlerAndListener.handler.post(() -> event.sendTo(eventListener)); + handlerAndListener.dispatch(event); } } private static final class HandlerAndListener { - public final Handler handler; - public final T listener; + private final Handler handler; + private final T listener; + + private boolean released; public HandlerAndListener(Handler handler, T eventListener) { this.handler = handler; this.listener = eventListener; } + + public void release() { + released = true; + } + + public void dispatch(Event event) { + handler.post( + () -> { + if (!released) { + event.sendTo(listener); + } + }); + } } } From 66c508651ad1de2e011a25ce2e935a1b9fea4f2c Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 23 Oct 2018 06:30:52 -0700 Subject: [PATCH 121/832] Cleanup codec init, flush and release Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218332277 --- .../mediacodec/MediaCodecRenderer.java | 158 ++++++++---------- 1 file changed, 72 insertions(+), 86 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index a35240668e..76244dc600 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -475,32 +475,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } try { - if (!initCodecWithFallback(wrappedMediaCrypto, drmSessionRequiresSecureDecoder)) { - // We can't initialize a codec yet. - return; - } + maybeInitCodecWithFallback(wrappedMediaCrypto, drmSessionRequiresSecureDecoder); } catch (DecoderInitializationException e) { throw ExoPlaybackException.createForRenderer(e, getIndex()); } - - String codecName = codecInfo.name; - codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName); - codecNeedsReconfigureWorkaround = codecNeedsReconfigureWorkaround(codecName); - codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); - codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); - codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); - codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); - codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); - codecNeedsEosPropagation = - codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation(); - codecHotswapDeadlineMs = - getState() == STATE_STARTED - ? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) - : C.TIME_UNSET; - resetInputBuffer(); - resetOutputBuffer(); - waitingForFirstSyncFrame = true; - decoderCounters.decoderInitCount++; } protected boolean shouldInitCodec(MediaCodecInfo codecInfo) { @@ -581,31 +559,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { protected void releaseCodec() { availableCodecInfos = null; - codecHotswapDeadlineMs = C.TIME_UNSET; - resetInputBuffer(); - resetOutputBuffer(); - waitingForKeys = false; - shouldSkipOutputBuffer = false; - decodeOnlyPresentationTimestamps.clear(); - resetCodecBuffers(); - codecInfo = null; - codecReconfigured = false; - codecReceivedBuffers = false; - codecNeedsDiscardToSpsWorkaround = false; - codecNeedsFlushWorkaround = false; - codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER; - codecNeedsReconfigureWorkaround = false; - codecNeedsEosFlushWorkaround = false; - codecNeedsMonoChannelCountWorkaround = false; - codecNeedsAdaptationWorkaroundBuffer = false; - shouldSkipAdaptationWorkaroundOutputBuffer = false; - codecNeedsEosPropagation = false; - codecReceivedEos = false; - codecReconfigurationState = RECONFIGURATION_STATE_NONE; - codecReinitializationState = REINITIALIZATION_STATE_NONE; - codecReinitializationIsRelease = false; - codecConfiguredWithOperatingRate = false; if (codec != null) { + resetInputBuffer(); + resetOutputBuffer(); + resetCodecBuffers(); + codecHotswapDeadlineMs = C.TIME_UNSET; + waitingForKeys = false; + decodeOnlyPresentationTimestamps.clear(); + codecInfo = null; + codecReconfigured = false; + codecReconfigurationState = RECONFIGURATION_STATE_NONE; + codecReinitializationState = REINITIALIZATION_STATE_NONE; decoderCounters.decoderReleaseCount++; try { codec.stop(); @@ -708,45 +672,40 @@ public abstract class MediaCodecRenderer extends BaseRenderer { */ protected boolean flushOrReleaseCodec() { if (codec == null) { - // Nothing to do. return false; } - codecHotswapDeadlineMs = C.TIME_UNSET; - resetInputBuffer(); - resetOutputBuffer(); - codecReceivedBuffers = false; - waitingForFirstSyncFrame = true; - waitingForKeys = false; - shouldSkipOutputBuffer = false; - decodeOnlyPresentationTimestamps.clear(); - codecNeedsAdaptationWorkaroundBuffer = false; - shouldSkipAdaptationWorkaroundOutputBuffer = false; - if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) { + if (codecNeedsFlushWorkaround + || (codecNeedsEosFlushWorkaround && codecReceivedEos) + || (codecReinitializationState != REINITIALIZATION_STATE_NONE + && codecReinitializationIsRelease)) { releaseCodec(); return true; - } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { - // We're already waiting to re-initialize the codec. Since we're now flushing, there's no need - // to wait any longer. - if (codecReinitializationIsRelease) { - releaseCodec(); - return true; - } else { - codec.flush(); - codecReinitializationState = REINITIALIZATION_STATE_NONE; - } - } else { - // We can flush and re-use the existing decoder. - codec.flush(); - } - if (codecReconfigured && format != null) { - // Any reconfiguration data that we send shortly before the flush may be discarded. We - // avoid this issue by sending reconfiguration data following every flush. - codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; } + codec.flush(); + + resetInputBuffer(); + resetOutputBuffer(); + codecHotswapDeadlineMs = C.TIME_UNSET; + codecReceivedEos = false; + codecReceivedBuffers = false; + waitingForFirstSyncFrame = true; + codecNeedsAdaptationWorkaroundBuffer = false; + shouldSkipAdaptationWorkaroundOutputBuffer = false; + shouldSkipOutputBuffer = false; + + waitingForKeys = false; + decodeOnlyPresentationTimestamps.clear(); + codecReinitializationState = REINITIALIZATION_STATE_NONE; + // Reconfiguration data sent shortly before the flush may not have been processed by the + // decoder. If the codec has been reconfigured we always send reconfiguration data again to + // guarantee that it's processed. + codecReconfigurationState = + codecReconfigured ? RECONFIGURATION_STATE_WRITE_PENDING : RECONFIGURATION_STATE_NONE; return false; } - private boolean initCodecWithFallback(MediaCrypto crypto, boolean drmSessionRequiresSecureDecoder) + private void maybeInitCodecWithFallback( + MediaCrypto crypto, boolean drmSessionRequiresSecureDecoder) throws DecoderInitializationException { if (availableCodecInfos == null) { try { @@ -770,14 +729,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { DecoderInitializationException.NO_SUITABLE_DECODER_ERROR); } - while (true) { + while (codec == null) { MediaCodecInfo codecInfo = availableCodecInfos.peekFirst(); if (!shouldInitCodec(codecInfo)) { - return false; + return; } try { initCodec(codecInfo, crypto); - return true; } catch (Exception e) { Log.w(TAG, "Failed to initialize decoder: " + codecInfo, e); // This codec failed to initialize, so fall back to the next codec in the list (if any). We @@ -798,6 +756,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } } } + + availableCodecInfos = null; } private List getAvailableCodecInfos(boolean drmSessionRequiresSecureDecoder) @@ -827,13 +787,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { long codecInitializingTimestamp; long codecInitializedTimestamp; MediaCodec codec = null; - String name = codecInfo.name; + String codecName = codecInfo.name; + updateCodecOperatingRate(); boolean configureWithOperatingRate = codecOperatingRate > assumedMinimumCodecOperatingRate; try { codecInitializingTimestamp = SystemClock.elapsedRealtime(); - TraceUtil.beginSection("createCodec:" + name); - codec = MediaCodec.createByCodecName(name); + TraceUtil.beginSection("createCodec:" + codecName); + codec = MediaCodec.createByCodecName(codecName); TraceUtil.endSection(); TraceUtil.beginSection("configureCodec"); configureCodec( @@ -842,7 +803,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { format, crypto, configureWithOperatingRate ? codecOperatingRate : CODEC_OPERATING_RATE_UNSET); - codecConfiguredWithOperatingRate = configureWithOperatingRate; TraceUtil.endSection(); TraceUtil.beginSection("startCodec"); codec.start(); @@ -856,10 +816,36 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } throw e; } + this.codec = codec; this.codecInfo = codecInfo; + codecConfiguredWithOperatingRate = configureWithOperatingRate; + codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName); + codecNeedsReconfigureWorkaround = codecNeedsReconfigureWorkaround(codecName); + codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); + codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); + codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); + codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); + codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); + codecNeedsEosPropagation = + codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation(); + + resetInputBuffer(); + resetOutputBuffer(); + codecHotswapDeadlineMs = + getState() == STATE_STARTED + ? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) + : C.TIME_UNSET; + codecReceivedEos = false; + codecReceivedBuffers = false; + waitingForFirstSyncFrame = true; + codecNeedsAdaptationWorkaroundBuffer = false; + shouldSkipAdaptationWorkaroundOutputBuffer = false; + shouldSkipOutputBuffer = false; + + decoderCounters.decoderInitCount++; long elapsed = codecInitializedTimestamp - codecInitializingTimestamp; - onCodecInitialized(name, codecInitializedTimestamp, elapsed); + onCodecInitialized(codecName, codecInitializedTimestamp, elapsed); } private void getCodecBuffers(MediaCodec codec) { From d56b7ad8117e48ea1dcc4b404be995a41cab55bf Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 23 Oct 2018 08:34:32 -0700 Subject: [PATCH 122/832] Clean up codec reinitialization Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218346327 --- .../mediacodec/MediaCodecRenderer.java | 159 +++++++++--------- 1 file changed, 77 insertions(+), 82 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 76244dc600..3d7287d720 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -228,28 +228,25 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({ - REINITIALIZATION_STATE_NONE, - REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, - REINITIALIZATION_STATE_WAIT_END_OF_STREAM - }) - private @interface ReinitializationState {} - /** - * The codec does not need to be re-initialized. - */ - private static final int REINITIALIZATION_STATE_NONE = 0; - /** - * The input format has changed in a way that requires the codec to be re-initialized, but we - * haven't yet signaled an end of stream to the existing codec. We need to do so in order to - * ensure that it outputs any remaining buffers before we release it. - */ - private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1; - /** - * The input format has changed in a way that requires the codec to be re-initialized, and we've - * signaled an end of stream to the existing codec. We're waiting for the codec to output an end - * of stream signal to indicate that it has output any remaining buffers before we release it. - */ - private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2; + @IntDef({DRAIN_STATE_NONE, DRAIN_STATE_SIGNAL_END_OF_STREAM, DRAIN_STATE_WAIT_END_OF_STREAM}) + private @interface DrainState {} + /** The codec is not being drained. */ + private static final int DRAIN_STATE_NONE = 0; + /** The codec needs to be drained, but we haven't signaled an end of stream to it yet. */ + private static final int DRAIN_STATE_SIGNAL_END_OF_STREAM = 1; + /** The codec needs to be drained, and we're waiting for it to output an end of stream. */ + private static final int DRAIN_STATE_WAIT_END_OF_STREAM = 2; + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({DRAIN_ACTION_NONE, DRAIN_ACTION_FLUSH, DRAIN_ACTION_REINITIALIZE}) + private @interface DrainAction {} + /** No special action should be taken. */ + private static final int DRAIN_ACTION_NONE = 0; + /** The codec should be flushed. */ + private static final int DRAIN_ACTION_FLUSH = 1; + /** The codec should be re-initialized. */ + private static final int DRAIN_ACTION_REINITIALIZE = 2; @Documented @Retention(RetentionPolicy.SOURCE) @@ -283,8 +280,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private static final int ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT = 32; private final MediaCodecSelector mediaCodecSelector; - @Nullable - private final DrmSessionManager drmSessionManager; + @Nullable private final DrmSessionManager drmSessionManager; private final boolean playClearSamplesWithoutKeys; private final float assumedMinimumCodecOperatingRate; private final DecoderInputBuffer buffer; @@ -303,10 +299,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private float rendererOperatingRate; private float codecOperatingRate; private boolean codecConfiguredWithOperatingRate; - private @Nullable ArrayDeque availableCodecInfos; - private @Nullable DecoderInitializationException preferredDecoderInitializationException; - private @Nullable MediaCodecInfo codecInfo; - private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode; + @Nullable private ArrayDeque availableCodecInfos; + @Nullable private DecoderInitializationException preferredDecoderInitializationException; + @Nullable private MediaCodecInfo codecInfo; + @AdaptationWorkaroundMode private int codecAdaptationWorkaroundMode; private boolean codecNeedsReconfigureWorkaround; private boolean codecNeedsDiscardToSpsWorkaround; private boolean codecNeedsFlushWorkaround; @@ -324,9 +320,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private ByteBuffer outputBuffer; private boolean shouldSkipOutputBuffer; private boolean codecReconfigured; - private @ReconfigurationState int codecReconfigurationState; - private @ReinitializationState int codecReinitializationState; - private boolean codecReinitializationIsRelease; + @ReconfigurationState private int codecReconfigurationState; + @DrainState private int codecDrainState; + @DrainAction private int codecDrainAction; private boolean codecReceivedBuffers; private boolean codecReceivedEos; @@ -371,7 +367,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { decodeOnlyPresentationTimestamps = new ArrayList<>(); outputBufferInfo = new MediaCodec.BufferInfo(); codecReconfigurationState = RECONFIGURATION_STATE_NONE; - codecReinitializationState = REINITIALIZATION_STATE_NONE; + codecDrainState = DRAIN_STATE_NONE; + codecDrainAction = DRAIN_ACTION_NONE; codecOperatingRate = CODEC_OPERATING_RATE_UNSET; rendererOperatingRate = 1f; } @@ -567,9 +564,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { waitingForKeys = false; decodeOnlyPresentationTimestamps.clear(); codecInfo = null; - codecReconfigured = false; - codecReconfigurationState = RECONFIGURATION_STATE_NONE; - codecReinitializationState = REINITIALIZATION_STATE_NONE; decoderCounters.decoderReleaseCount++; try { codec.stop(); @@ -674,15 +668,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (codec == null) { return false; } - if (codecNeedsFlushWorkaround - || (codecNeedsEosFlushWorkaround && codecReceivedEos) - || (codecReinitializationState != REINITIALIZATION_STATE_NONE - && codecReinitializationIsRelease)) { + if (codecDrainAction == DRAIN_ACTION_REINITIALIZE + || codecNeedsFlushWorkaround + || (codecNeedsEosFlushWorkaround && codecReceivedEos)) { releaseCodec(); return true; } - codec.flush(); + codec.flush(); resetInputBuffer(); resetOutputBuffer(); codecHotswapDeadlineMs = C.TIME_UNSET; @@ -695,7 +688,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { waitingForKeys = false; decodeOnlyPresentationTimestamps.clear(); - codecReinitializationState = REINITIALIZATION_STATE_NONE; + codecDrainState = DRAIN_STATE_NONE; + codecDrainAction = DRAIN_ACTION_NONE; // Reconfiguration data sent shortly before the flush may not have been processed by the // decoder. If the codec has been reconfigured we always send reconfiguration data again to // guarantee that it's processed. @@ -836,12 +830,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { getState() == STATE_STARTED ? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) : C.TIME_UNSET; + codecReconfigured = false; + codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReceivedEos = false; codecReceivedBuffers = false; - waitingForFirstSyncFrame = true; + codecDrainState = DRAIN_STATE_NONE; + codecDrainAction = DRAIN_ACTION_NONE; codecNeedsAdaptationWorkaroundBuffer = false; shouldSkipAdaptationWorkaroundOutputBuffer = false; shouldSkipOutputBuffer = false; + waitingForFirstSyncFrame = true; decoderCounters.decoderInitCount++; long elapsed = codecInitializedTimestamp - codecInitializingTimestamp; @@ -897,9 +895,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * @throws ExoPlaybackException If an error occurs feeding the input buffer. */ private boolean feedInputBuffer() throws ExoPlaybackException { - if (codec == null || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM - || inputStreamEnded) { - // We need to re-initialize the codec or the input stream has ended. + if (codec == null || codecDrainState == DRAIN_STATE_WAIT_END_OF_STREAM || inputStreamEnded) { return false; } @@ -912,7 +908,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { buffer.clear(); } - if (codecReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) { + if (codecDrainState == DRAIN_STATE_SIGNAL_END_OF_STREAM) { // We need to re-initialize the codec. Send an end of stream signal to the existing codec so // that it outputs any remaining buffers before we release it. if (codecNeedsEosPropagation) { @@ -922,7 +918,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); resetInputBuffer(); } - codecReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM; + codecDrainState = DRAIN_STATE_WAIT_END_OF_STREAM; return false; } @@ -1109,19 +1105,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // We have an existing codec that we may need to reconfigure or re-initialize. If the existing // codec instance is being kept then its operating rate may need to be updated. if (pendingDrmSession != drmSession) { - reinitializeCodec(/* release= */ true); + drainAndReinitializeCodec(); } else { switch (canKeepCodec(codec, codecInfo, oldFormat, format)) { case KEEP_CODEC_RESULT_NO: - reinitializeCodec(/* release= */ true); + drainAndReinitializeCodec(); break; case KEEP_CODEC_RESULT_YES_WITH_FLUSH: - reinitializeCodec(/* release= */ false); + drainAndFlushCodec(); updateCodecOperatingRate(); break; case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION: if (codecNeedsReconfigureWorkaround) { - reinitializeCodec(/* release= */ true); + drainAndReinitializeCodec(); } else { codecReconfigured = true; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; @@ -1253,15 +1249,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } this.codecOperatingRate = codecOperatingRate; - if (codec == null - || (codecReinitializationState != REINITIALIZATION_STATE_NONE - && codecReinitializationIsRelease)) { + if (codec == null || codecDrainAction == DRAIN_ACTION_REINITIALIZE) { // Either no codec, or it's about to be released due to re-initialization anyway. } else if (codecOperatingRate == CODEC_OPERATING_RATE_UNSET && codecConfiguredWithOperatingRate) { // We need to clear the operating rate. The only way to do so is to instantiate a new codec // instance. See [Internal ref: b/71987865]. - reinitializeCodec(/* release= */ true); + drainAndReinitializeCodec(); } else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET && (codecConfiguredWithOperatingRate || codecOperatingRate > assumedMinimumCodecOperatingRate)) { @@ -1274,26 +1268,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } } - /** - * Starts the process of re-initializing the codec. This may occur immediately, or be deferred - * until any final output buffers have been dequeued. - * - * @param release Whether re-initialization requires fully releasing the codec and instantiating a - * new instance, as opposed to flushing and reusing the current instance. - * @throws ExoPlaybackException If an error occurs releasing or initializing a codec. - */ - private void reinitializeCodec(boolean release) throws ExoPlaybackException { + /** Starts draining the codec for flush. */ + private void drainAndFlushCodec() { if (codecReceivedBuffers) { - // Signal end of stream and wait for any final output buffers before re-initialization. - codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; - codecReinitializationIsRelease = release; - return; + codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM; + codecDrainAction = DRAIN_ACTION_FLUSH; } + } - // Nothing has been queued to the decoder. If we need to fully release the codec and instantiate - // a new instance, do so immediately. If only a flush is required then we can do nothing, since - // flushing will be a no-op. - if (release) { + /** + * Starts draining the codec for re-initialization. Re-initialization may occur immediately if no + * buffers have been queued to the codec. + * + * @throws ExoPlaybackException If an error occurs re-initializing a codec. + */ + private void drainAndReinitializeCodec() throws ExoPlaybackException { + if (codecReceivedBuffers) { + codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM; + codecDrainAction = DRAIN_ACTION_REINITIALIZE; + } else { + // Nothing has been queued to the decoder, so we can re-initialize immediately. releaseCodec(); maybeInitCodec(); } @@ -1334,8 +1328,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } /* MediaCodec.INFO_TRY_AGAIN_LATER (-1) or unknown negative return value */ if (codecNeedsEosPropagation - && (inputStreamEnded - || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM)) { + && (inputStreamEnded || codecDrainState == DRAIN_STATE_WAIT_END_OF_STREAM)) { processEndOfStream(); } return false; @@ -1498,17 +1491,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * @throws ExoPlaybackException If an error occurs processing the signal. */ private void processEndOfStream() throws ExoPlaybackException { - if (codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { - // We're waiting to re-initialize the codec, and have now processed all final buffers. - if (codecReinitializationIsRelease) { + switch (codecDrainAction) { + case DRAIN_ACTION_REINITIALIZE: releaseCodec(); maybeInitCodec(); - } else { + break; + case DRAIN_ACTION_FLUSH: flushOrReinitCodec(); - } - } else { - outputStreamEnded = true; - renderToEndOfStream(); + break; + case DRAIN_ACTION_NONE: + default: + outputStreamEnded = true; + renderToEndOfStream(); + break; } } From 66a6921c26acab24b4d31635d450b1c02170c748 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 23 Oct 2018 08:44:40 -0700 Subject: [PATCH 123/832] Remove non-addEventListener ways of adding an event listener ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218347641 --- .../upstream/DefaultBandwidthMeter.java | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index 561ecfb024..0654d2b475 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -76,8 +76,6 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList @Nullable private final Context context; - @Nullable private Handler eventHandler; - @Nullable private EventListener eventListener; private SparseArray initialBitrateEstimates; private int slidingWindowMaxWeight; private Clock clock; @@ -95,21 +93,6 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList clock = Clock.DEFAULT; } - /** - * Sets an event listener for new bandwidth estimates. - * - * @param eventHandler A handler for events. - * @param eventListener A listener of events. - * @return This builder. - * @throws IllegalArgumentException If the event handler or listener are null. - */ - public Builder setEventListener(Handler eventHandler, EventListener eventListener) { - Assertions.checkArgument(eventHandler != null && eventListener != null); - this.eventHandler = eventHandler; - this.eventListener = eventListener; - return this; - } - /** * Sets the maximum weight for the sliding window. * @@ -185,9 +168,6 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter( context, initialBitrateEstimates, slidingWindowMaxWeight, clock); - if (eventHandler != null && eventListener != null) { - bandwidthMeter.addEventListener(eventHandler, eventListener); - } return bandwidthMeter; } @@ -238,19 +218,6 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList Clock.DEFAULT); } - /** @deprecated Use {@link Builder} instead. */ - @Deprecated - public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener) { - this( - /* context= */ null, - /* initialBitrateEstimates= */ new SparseArray<>(), - DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, - Clock.DEFAULT); - if (eventHandler != null && eventListener != null) { - addEventListener(eventHandler, eventListener); - } - } - private DefaultBandwidthMeter( @Nullable Context context, SparseArray initialBitrateEstimates, From 706ce49bd37938dd5b9ae65957ada0717de5c74c Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 23 Oct 2018 09:49:45 -0700 Subject: [PATCH 124/832] Fix handling of MP3s with appended data Issue: #4954 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218357113 --- RELEASENOTES.md | 2 ++ .../extractor/mp3/ConstantBitrateSeeker.java | 5 +++ .../exoplayer2/extractor/mp3/MlltSeeker.java | 4 +++ .../extractor/mp3/Mp3Extractor.java | 32 +++++++++++++++---- .../exoplayer2/extractor/mp3/VbriSeeker.java | 10 ++++-- .../exoplayer2/extractor/mp3/XingSeeker.java | 25 +++++++++++---- 6 files changed, 63 insertions(+), 15 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 84864a35c4..df1423e688 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,6 +24,8 @@ * Audio: * Support seeking based on MLLT metadata ([#3241](https://github.com/google/ExoPlayer/issues/3241)). + * Fix handling of MP3s with appended data + ([#4954](https://github.com/google/ExoPlayer/issues/4954)). * Fix issue where buffered position is not updated correctly when transitioning between periods ([#4899](https://github.com/google/ExoPlayer/issues/4899)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java index bffc43a540..f400720772 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java @@ -39,4 +39,9 @@ import com.google.android.exoplayer2.extractor.MpegAudioHeader; public long getTimeUs(long position) { return getTimeUsAtPosition(position); } + + @Override + public long getDataEndPosition() { + return C.POSITION_UNSET; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java index ff607b9482..868c1d9fbf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java @@ -118,4 +118,8 @@ import com.google.android.exoplayer2.util.Util; } } + @Override + public long getDataEndPosition() { + return C.POSITION_UNSET; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 84f620734b..e8848bf983 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -223,7 +223,7 @@ public final class Mp3Extractor implements Extractor { private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { if (sampleBytesRemaining == 0) { extractorInput.resetPeekPosition(); - if (!extractorInput.peekFully(scratch.data, 0, 4, true)) { + if (peekEndOfStreamOrHeader(extractorInput)) { return RESULT_END_OF_INPUT; } scratch.setPosition(0); @@ -285,9 +285,12 @@ public final class Mp3Extractor implements Extractor { } } while (true) { - if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) { - // We reached the end of the stream but found at least one valid frame. - break; + if (peekEndOfStreamOrHeader(input)) { + if (validFrameCount > 0) { + // We reached the end of the stream but found at least one valid frame. + break; + } + throw new EOFException(); } scratch.setPosition(0); int headerData = scratch.readInt(); @@ -332,6 +335,17 @@ public final class Mp3Extractor implements Extractor { return true; } + /** + * Returns whether the extractor input is peeking the end of the stream. If {@code false}, + * populates the scratch buffer with the next four bytes. + */ + private boolean peekEndOfStreamOrHeader(ExtractorInput extractorInput) + throws IOException, InterruptedException { + return (seeker != null && extractorInput.getPeekPosition() == seeker.getDataEndPosition()) + || !extractorInput.peekFully( + scratch.data, /* offset= */ 0, /* length= */ 4, /* allowEndOfInput= */ true); + } + /** * Consumes the next frame from the {@code input} if it contains VBRI or Xing seeking metadata, * returning a {@link Seeker} if the metadata was present and valid, or {@code null} otherwise. @@ -433,8 +447,9 @@ public final class Mp3Extractor implements Extractor { } /** - * {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be - * used to work out the new sample basis timestamp after seeking and resynchronization. + * {@link SeekMap} that provides the end position of audio data and also allows mapping from + * position (byte offset) back to time, which can be used to work out the new sample basis + * timestamp after seeking and resynchronization. */ /* package */ interface Seeker extends SeekMap { @@ -446,6 +461,11 @@ public final class Mp3Extractor implements Extractor { */ long getTimeUs(long position); + /** + * Returns the position (byte offset) in the stream that is immediately after audio data, or + * {@link C#POSITION_UNSET} if not known. + */ + long getDataEndPosition(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java index 774505f622..15e778115d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java @@ -89,17 +89,19 @@ import com.google.android.exoplayer2.util.Util; if (inputLength != C.LENGTH_UNSET && inputLength != position) { Log.w(TAG, "VBRI data size mismatch: " + inputLength + ", " + position); } - return new VbriSeeker(timesUs, positions, durationUs); + return new VbriSeeker(timesUs, positions, durationUs, /* dataEndPosition= */ position); } private final long[] timesUs; private final long[] positions; private final long durationUs; + private final long dataEndPosition; - private VbriSeeker(long[] timesUs, long[] positions, long durationUs) { + private VbriSeeker(long[] timesUs, long[] positions, long durationUs, long dataEndPosition) { this.timesUs = timesUs; this.positions = positions; this.durationUs = durationUs; + this.dataEndPosition = dataEndPosition; } @Override @@ -129,4 +131,8 @@ import com.google.android.exoplayer2.util.Util; return durationUs; } + @Override + public long getDataEndPosition() { + return dataEndPosition; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java index c2971e47ce..42752e55fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java @@ -75,17 +75,17 @@ import com.google.android.exoplayer2.util.Util; if (inputLength != C.LENGTH_UNSET && inputLength != position + dataSize) { Log.w(TAG, "XING data size mismatch: " + inputLength + ", " + (position + dataSize)); } - return new XingSeeker(position, mpegAudioHeader.frameSize, durationUs, dataSize, - tableOfContents); + return new XingSeeker( + position, mpegAudioHeader.frameSize, durationUs, dataSize, tableOfContents); } private final long dataStartPosition; private final int xingFrameSize; private final long durationUs; - /** - * Data size, including the XING frame. - */ + /** Data size, including the XING frame. */ private final long dataSize; + + private final long dataEndPosition; /** * Entries are in the range [0, 255], but are stored as long integers for convenience. Null if the * table of contents was missing from the header, in which case seeking is not be supported. @@ -93,7 +93,12 @@ import com.google.android.exoplayer2.util.Util; private final @Nullable long[] tableOfContents; private XingSeeker(long dataStartPosition, int xingFrameSize, long durationUs) { - this(dataStartPosition, xingFrameSize, durationUs, C.LENGTH_UNSET, null); + this( + dataStartPosition, + xingFrameSize, + durationUs, + /* dataSize= */ C.LENGTH_UNSET, + /* tableOfContents= */ null); } private XingSeeker( @@ -105,8 +110,9 @@ import com.google.android.exoplayer2.util.Util; this.dataStartPosition = dataStartPosition; this.xingFrameSize = xingFrameSize; this.durationUs = durationUs; - this.dataSize = dataSize; this.tableOfContents = tableOfContents; + this.dataSize = dataSize; + dataEndPosition = dataSize == C.LENGTH_UNSET ? C.POSITION_UNSET : dataStartPosition + dataSize; } @Override @@ -166,6 +172,11 @@ import com.google.android.exoplayer2.util.Util; return durationUs; } + @Override + public long getDataEndPosition() { + return dataEndPosition; + } + /** * Returns the time in microseconds for a given table index. * From 1b6801c0914f51a9e8018dd0814819f5663a990f Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 24 Oct 2018 11:01:58 -0700 Subject: [PATCH 125/832] Simplify setting of codec operating rate Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218540967 --- .../audio/MediaCodecAudioRenderer.java | 2 +- .../mediacodec/MediaCodecRenderer.java | 50 ++++++++----------- .../video/MediaCodecVideoRenderer.java | 2 +- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 59a077dff8..c90b815347 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -388,7 +388,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } @Override - protected float getCodecOperatingRate( + protected float getCodecOperatingRateV23( float operatingRate, Format format, Format[] streamFormats) { // Use the highest known stream sample-rate up front, to avoid having to reconfigure the codec // should an adaptive switch to that stream occur. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 3d7287d720..71ed640f57 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -298,7 +298,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private MediaCodec codec; private float rendererOperatingRate; private float codecOperatingRate; - private boolean codecConfiguredWithOperatingRate; @Nullable private ArrayDeque availableCodecInfos; @Nullable private DecoderInitializationException preferredDecoderInitializationException; @Nullable private MediaCodecInfo codecInfo; @@ -783,20 +782,20 @@ public abstract class MediaCodecRenderer extends BaseRenderer { MediaCodec codec = null; String codecName = codecInfo.name; - updateCodecOperatingRate(); - boolean configureWithOperatingRate = codecOperatingRate > assumedMinimumCodecOperatingRate; + float codecOperatingRate = + Util.SDK_INT < 23 + ? CODEC_OPERATING_RATE_UNSET + : getCodecOperatingRateV23(rendererOperatingRate, format, getStreamFormats()); + if (codecOperatingRate <= assumedMinimumCodecOperatingRate) { + codecOperatingRate = CODEC_OPERATING_RATE_UNSET; + } try { codecInitializingTimestamp = SystemClock.elapsedRealtime(); TraceUtil.beginSection("createCodec:" + codecName); codec = MediaCodec.createByCodecName(codecName); TraceUtil.endSection(); TraceUtil.beginSection("configureCodec"); - configureCodec( - codecInfo, - codec, - format, - crypto, - configureWithOperatingRate ? codecOperatingRate : CODEC_OPERATING_RATE_UNSET); + configureCodec(codecInfo, codec, format, crypto, codecOperatingRate); TraceUtil.endSection(); TraceUtil.beginSection("startCodec"); codec.start(); @@ -813,7 +812,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { this.codec = codec; this.codecInfo = codecInfo; - codecConfiguredWithOperatingRate = configureWithOperatingRate; + this.codecOperatingRate = codecOperatingRate; codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName); codecNeedsReconfigureWorkaround = codecNeedsReconfigureWorkaround(codecName); codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); @@ -1227,7 +1226,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * @return The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if no codec operating * rate should be set. */ - protected float getCodecOperatingRate( + protected float getCodecOperatingRateV23( float operatingRate, Format format, Format[] streamFormats) { return CODEC_OPERATING_RATE_UNSET; } @@ -1238,33 +1237,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * @throws ExoPlaybackException If an error occurs releasing or initializing a codec. */ private void updateCodecOperatingRate() throws ExoPlaybackException { - if (format == null || Util.SDK_INT < 23) { + if (Util.SDK_INT < 23 || codec == null || codecDrainAction == DRAIN_ACTION_REINITIALIZE) { return; } - float codecOperatingRate = - getCodecOperatingRate(rendererOperatingRate, format, getStreamFormats()); - if (this.codecOperatingRate == codecOperatingRate) { - return; - } - - this.codecOperatingRate = codecOperatingRate; - if (codec == null || codecDrainAction == DRAIN_ACTION_REINITIALIZE) { - // Either no codec, or it's about to be released due to re-initialization anyway. - } else if (codecOperatingRate == CODEC_OPERATING_RATE_UNSET - && codecConfiguredWithOperatingRate) { - // We need to clear the operating rate. The only way to do so is to instantiate a new codec - // instance. See [Internal ref: b/71987865]. + float newCodecOperatingRate = + getCodecOperatingRateV23(rendererOperatingRate, format, getStreamFormats()); + if (codecOperatingRate == newCodecOperatingRate) { + // No change. + } else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) { + // The only way to clear the operating rate is to instantiate a new codec instance. See + // [Internal ref: b/71987865]. drainAndReinitializeCodec(); } else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET - && (codecConfiguredWithOperatingRate - || codecOperatingRate > assumedMinimumCodecOperatingRate)) { + || newCodecOperatingRate > assumedMinimumCodecOperatingRate) { // We need to set the operating rate, either because we've set it previously or because it's // above the assumed minimum rate. Bundle codecParameters = new Bundle(); - codecParameters.putFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate); + codecParameters.putFloat(MediaFormat.KEY_OPERATING_RATE, newCodecOperatingRate); codec.setParameters(codecParameters); - codecConfiguredWithOperatingRate = true; + codecOperatingRate = newCodecOperatingRate; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index a0e794764a..a896bc2322 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -536,7 +536,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } @Override - protected float getCodecOperatingRate( + protected float getCodecOperatingRateV23( float operatingRate, Format format, Format[] streamFormats) { // Use the highest known stream frame-rate up front, to avoid having to reconfigure the codec // should an adaptive switch to that stream occur. From 4fe14c76935ab1625c3e2370a6d8fa7934be71de Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 24 Oct 2018 11:56:58 -0700 Subject: [PATCH 126/832] Minor cleanup to BandwidthMeter/DefaultBandwidthMeter - Improve variable naming - In the edge case that bytes are transferred in a sample that has 0 elapsed time, carry the bytes forward into the next sample. If we don't do this then the estimate will be calculated as though those bytes were never transferred. In practice I suspect this very rarely occurs. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218551208 --- .../exoplayer2/upstream/BandwidthMeter.java | 19 +++++++++++-------- .../upstream/DefaultBandwidthMeter.java | 14 +++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java index 470937f02f..85bcda4e32 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java @@ -29,30 +29,33 @@ public interface BandwidthMeter { interface EventListener { /** - * Called periodically to indicate that bytes have been transferred. + * Called periodically to indicate that bytes have been transferred or the estimated bitrate has + * changed. * *

    Note: The estimated bitrate is typically derived from more information than just {@code * bytes} and {@code elapsedMs}. * - * @param elapsedMs The time taken to transfer the bytes, in milliseconds. - * @param bytes The number of bytes transferred. - * @param bitrate The estimated bitrate in bits/sec. + * @param elapsedMs The time taken to transfer {@code bytesTransferred}, in milliseconds. This + * is at most the elapsed time since the last callback, but may be less if there were + * periods during which data was not being transferred. + * @param bytesTransferred The number of bytes transferred since the last callback. + * @param bitrateEstimate The estimated bitrate in bits/sec. */ - void onBandwidthSample(int elapsedMs, long bytes, long bitrate); + void onBandwidthSample(int elapsedMs, long bytesTransferred, long bitrateEstimate); } - /** Returns the estimated bandwidth in bits/sec. */ + /** Returns the estimated bitrate. */ long getBitrateEstimate(); /** * Returns the {@link TransferListener} that this instance uses to gather bandwidth information - * from data transfers. May be null, if no transfer listener is used. + * from data transfers. May be null if the implementation does not listen to data transfers. */ @Nullable TransferListener getTransferListener(); /** - * Adds an {@link EventListener} to be informed of bandwidth samples. + * Adds an {@link EventListener}. * * @param eventHandler A handler for events. * @param eventListener A listener of events. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index 0654d2b475..6be13603ee 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -309,16 +309,16 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList || totalBytesTransferred >= BYTES_TRANSFERRED_FOR_ESTIMATE) { bitrateEstimate = (long) slidingPercentile.getPercentile(0.5f); } - } - notifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); - if (--streamCount > 0) { + notifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); sampleStartTimeMs = nowMs; - } - sampleBytesTransferred = 0; + sampleBytesTransferred = 0; + } // Else any sample bytes transferred will be carried forward into the next sample. + streamCount--; } - private void notifyBandwidthSample(int elapsedMs, long bytes, long bitrate) { - eventDispatcher.dispatch(listener -> listener.onBandwidthSample(elapsedMs, bytes, bitrate)); + private void notifyBandwidthSample(int elapsedMs, long bytesTransferred, long bitrateEstimate) { + eventDispatcher.dispatch( + listener -> listener.onBandwidthSample(elapsedMs, bytesTransferred, bitrateEstimate)); } private long getInitialBitrateEstimateForNetworkType(@C.NetworkType int networkType) { From ede62e97b2d6679fddbb80a056130944300ff505 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 25 Oct 2018 03:41:44 -0700 Subject: [PATCH 127/832] Fix track selection when forceLowestBitrate is enabled: - Only use bitrate when comparing two tracks if their bitrates are actually different. - For audio, prefer to use bitrate over selection flags. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218660886 --- .../trackselection/DefaultTrackSelector.java | 43 +++++++++++-------- library/ui/src/main/res/values-ar/strings.xml | 2 +- library/ui/src/main/res/values-az/strings.xml | 2 +- library/ui/src/main/res/values-bs/strings.xml | 2 +- library/ui/src/main/res/values-fa/strings.xml | 2 +- library/ui/src/main/res/values-hr/strings.xml | 2 +- library/ui/src/main/res/values-in/strings.xml | 2 +- library/ui/src/main/res/values-ka/strings.xml | 2 +- library/ui/src/main/res/values-my/strings.xml | 2 +- library/ui/src/main/res/values-sk/strings.xml | 2 +- library/ui/src/main/res/values-sq/strings.xml | 2 +- library/ui/src/main/res/values-uz/strings.xml | 2 +- library/ui/src/main/res/values-zu/strings.xml | 2 +- 13 files changed, 37 insertions(+), 30 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 4a75b6f722..0f5adab4f5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -1651,9 +1651,10 @@ public class DefaultTrackSelector extends MappingTrackSelector { } boolean selectTrack = trackScore > selectedTrackScore; if (trackScore == selectedTrackScore) { - if (params.forceLowestBitrate) { + int bitrateComparison = compareFormatValues(format.bitrate, selectedBitrate); + if (params.forceLowestBitrate && bitrateComparison != 0) { // Use bitrate as a tie breaker, preferring the lower bitrate. - selectTrack = compareFormatValues(format.bitrate, selectedBitrate) < 0; + selectTrack = bitrateComparison < 0; } else { // Use the pixel count as a tie breaker (or bitrate if pixel counts are tied). If // we're within constraints prefer a higher pixel count (or bitrate), else prefer a @@ -2173,23 +2174,29 @@ public class DefaultTrackSelector extends MappingTrackSelector { if (this.withinRendererCapabilitiesScore != other.withinRendererCapabilitiesScore) { return compareInts(this.withinRendererCapabilitiesScore, other.withinRendererCapabilitiesScore); - } else if (this.matchLanguageScore != other.matchLanguageScore) { - return compareInts(this.matchLanguageScore, other.matchLanguageScore); - } else if (this.defaultSelectionFlagScore != other.defaultSelectionFlagScore) { - return compareInts(this.defaultSelectionFlagScore, other.defaultSelectionFlagScore); - } else if (parameters.forceLowestBitrate) { - return compareInts(other.bitrate, this.bitrate); - } else { - // If the format are within renderer capabilities, prefer higher values of channel count, - // sample rate and bit rate in that order. Otherwise, prefer lower values. - int resultSign = withinRendererCapabilitiesScore == 1 ? 1 : -1; - if (this.channelCount != other.channelCount) { - return resultSign * compareInts(this.channelCount, other.channelCount); - } else if (this.sampleRate != other.sampleRate) { - return resultSign * compareInts(this.sampleRate, other.sampleRate); - } - return resultSign * compareInts(this.bitrate, other.bitrate); } + if (this.matchLanguageScore != other.matchLanguageScore) { + return compareInts(this.matchLanguageScore, other.matchLanguageScore); + } + if (parameters.forceLowestBitrate) { + int bitrateComparison = compareFormatValues(bitrate, other.bitrate); + if (bitrateComparison != 0) { + return bitrateComparison > 0 ? -1 : 1; + } + } + if (this.defaultSelectionFlagScore != other.defaultSelectionFlagScore) { + return compareInts(this.defaultSelectionFlagScore, other.defaultSelectionFlagScore); + } + // If the formats are within renderer capabilities then prefer higher values of channel count, + // sample rate and bit rate in that order. Otherwise, prefer lower values. + int resultSign = withinRendererCapabilitiesScore == 1 ? 1 : -1; + if (this.channelCount != other.channelCount) { + return resultSign * compareInts(this.channelCount, other.channelCount); + } + if (this.sampleRate != other.sampleRate) { + return resultSign * compareInts(this.sampleRate, other.sampleRate); + } + return resultSign * compareInts(this.bitrate, other.bitrate); } } diff --git a/library/ui/src/main/res/values-ar/strings.xml b/library/ui/src/main/res/values-ar/strings.xml index e64797f7b3..9980191621 100644 --- a/library/ui/src/main/res/values-ar/strings.xml +++ b/library/ui/src/main/res/values-ar/strings.xml @@ -12,7 +12,7 @@ تكرار الكل ترتيب عشوائي وضع ملء الشاشة - Exit VR mode + الخروج من وضع VR تنزيل التنزيلات جارٍ التنزيل. diff --git a/library/ui/src/main/res/values-az/strings.xml b/library/ui/src/main/res/values-az/strings.xml index 31fde9987f..d119531c70 100644 --- a/library/ui/src/main/res/values-az/strings.xml +++ b/library/ui/src/main/res/values-az/strings.xml @@ -12,7 +12,7 @@ Hamısı təkrarlansın Qarışdırın Tam ekran rejimi - Exit VR mode + VR rejimdən çıxın Endirin Endirmələr Endirilir diff --git a/library/ui/src/main/res/values-bs/strings.xml b/library/ui/src/main/res/values-bs/strings.xml index 3bb68d006a..34f445bcca 100644 --- a/library/ui/src/main/res/values-bs/strings.xml +++ b/library/ui/src/main/res/values-bs/strings.xml @@ -12,7 +12,7 @@ Ponovi sve Izmiješaj Način rada preko cijelog ekrana - Exit VR mode + Izlazak iz VR načina Preuzmi Preuzimanja Preuzimanje diff --git a/library/ui/src/main/res/values-fa/strings.xml b/library/ui/src/main/res/values-fa/strings.xml index e923dae795..2f36b5fc3b 100644 --- a/library/ui/src/main/res/values-fa/strings.xml +++ b/library/ui/src/main/res/values-fa/strings.xml @@ -12,7 +12,7 @@ تکرار همه درهم حالت تمام‌صفحه - Exit VR mode + خروج از حالت واقعیت مجازی بارگیری بارگیری‌ها درحال بارگیری diff --git a/library/ui/src/main/res/values-hr/strings.xml b/library/ui/src/main/res/values-hr/strings.xml index 1cba6e162b..d76fef9367 100644 --- a/library/ui/src/main/res/values-hr/strings.xml +++ b/library/ui/src/main/res/values-hr/strings.xml @@ -12,7 +12,7 @@ Ponovi sve Reproduciraj nasumično Prikaz na cijelom zaslonu - Exit VR mode + Izlazak iz VR načina Preuzmi Preuzimanja Preuzimanje diff --git a/library/ui/src/main/res/values-in/strings.xml b/library/ui/src/main/res/values-in/strings.xml index 92c6e055b6..39fcfea559 100644 --- a/library/ui/src/main/res/values-in/strings.xml +++ b/library/ui/src/main/res/values-in/strings.xml @@ -12,7 +12,7 @@ Ulangi semua Acak Mode layar penuh - Exit VR mode + Keluar dari mode VR Download Download Mendownload diff --git a/library/ui/src/main/res/values-ka/strings.xml b/library/ui/src/main/res/values-ka/strings.xml index 06b798baf2..2ec70e9c51 100644 --- a/library/ui/src/main/res/values-ka/strings.xml +++ b/library/ui/src/main/res/values-ka/strings.xml @@ -12,7 +12,7 @@ ყველას გამეორება არეულად დაკვრა სრულეკრანიანი რეჟიმი - Exit VR mode + VR რეჟიმიდან გასვლა ჩამოტვირთვა ჩამოტვირთვები მიმდინარეობს ჩამოტვირთვა diff --git a/library/ui/src/main/res/values-my/strings.xml b/library/ui/src/main/res/values-my/strings.xml index d7aa70c610..124b2826c0 100644 --- a/library/ui/src/main/res/values-my/strings.xml +++ b/library/ui/src/main/res/values-my/strings.xml @@ -12,7 +12,7 @@ အားလုံး ပြန်ကျော့ရန် ရောသမမွှေ မျက်နှာပြင်အပြည့် မုဒ် - Exit VR mode + VR မုဒ်မှထွက်ပါ ဒေါင်းလုဒ် လုပ်ရန် ဒေါင်းလုဒ်များ ဒေါင်းလုဒ်လုပ်နေသည် diff --git a/library/ui/src/main/res/values-sk/strings.xml b/library/ui/src/main/res/values-sk/strings.xml index 99ec680405..39b99e215d 100644 --- a/library/ui/src/main/res/values-sk/strings.xml +++ b/library/ui/src/main/res/values-sk/strings.xml @@ -12,7 +12,7 @@ Opakovať všetko Náhodne prehrávať Režim celej obrazovky - Exit VR mode + Ukončiť režim VR Stiahnuť Stiahnuté Sťahuje sa diff --git a/library/ui/src/main/res/values-sq/strings.xml b/library/ui/src/main/res/values-sq/strings.xml index 2c139f20cb..434ef77f58 100644 --- a/library/ui/src/main/res/values-sq/strings.xml +++ b/library/ui/src/main/res/values-sq/strings.xml @@ -12,7 +12,7 @@ Përsërit të gjitha Përziej Modaliteti me ekran të plotë - Exit VR mode + Dil nga modaliteti VR Shkarko Shkarkimet Po shkarkohet diff --git a/library/ui/src/main/res/values-uz/strings.xml b/library/ui/src/main/res/values-uz/strings.xml index 5275d7bd06..a62bc1a2e8 100644 --- a/library/ui/src/main/res/values-uz/strings.xml +++ b/library/ui/src/main/res/values-uz/strings.xml @@ -12,7 +12,7 @@ Hammasini takrorlash Aralash Butun ekran rejimi - Exit VR mode + VR rejimidan chiqish Yuklab olish Yuklanmalar Yuklab olinmoqda diff --git a/library/ui/src/main/res/values-zu/strings.xml b/library/ui/src/main/res/values-zu/strings.xml index 40a1a6ce29..6be1c61f5e 100644 --- a/library/ui/src/main/res/values-zu/strings.xml +++ b/library/ui/src/main/res/values-zu/strings.xml @@ -12,7 +12,7 @@ Phinda konke Shova Imodi yesikrini esigcwele - Exit VR mode + Phuma kumodi ye-VR Landa Ukulandwa Iyalanda From 9ea678c5e6974436f407c7c39dd11b95302fcf16 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 26 Oct 2018 06:45:08 -0700 Subject: [PATCH 128/832] Add ExoCastMessage ExoCastMessage contains all player management messages that the sender app can send to the receiver app. ExoCastMessages can be serialized as JSON strings. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218846977 --- library/ui/src/main/res/values-af/strings.xml | 2 +- library/ui/src/main/res/values-am/strings.xml | 2 +- library/ui/src/main/res/values-cs/strings.xml | 2 +- library/ui/src/main/res/values-el/strings.xml | 2 +- library/ui/src/main/res/values-es/strings.xml | 2 +- library/ui/src/main/res/values-eu/strings.xml | 2 +- library/ui/src/main/res/values-it/strings.xml | 2 +- library/ui/src/main/res/values-ja/strings.xml | 2 +- library/ui/src/main/res/values-km/strings.xml | 2 +- library/ui/src/main/res/values-kn/strings.xml | 2 +- library/ui/src/main/res/values-ko/strings.xml | 2 +- library/ui/src/main/res/values-lo/strings.xml | 2 +- library/ui/src/main/res/values-mr/strings.xml | 2 +- library/ui/src/main/res/values-pa/strings.xml | 2 +- library/ui/src/main/res/values-pl/strings.xml | 2 +- library/ui/src/main/res/values-pt/strings.xml | 2 +- library/ui/src/main/res/values-sl/strings.xml | 2 +- library/ui/src/main/res/values-sw/strings.xml | 2 +- library/ui/src/main/res/values-ta/strings.xml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/library/ui/src/main/res/values-af/strings.xml b/library/ui/src/main/res/values-af/strings.xml index d0190d9202..e20ade1cde 100644 --- a/library/ui/src/main/res/values-af/strings.xml +++ b/library/ui/src/main/res/values-af/strings.xml @@ -12,7 +12,7 @@ Herhaal alles Skommel Volskermmodus - Exit VR mode + Verlaat VR-modus Aflaai Aflaaie Laai tans af diff --git a/library/ui/src/main/res/values-am/strings.xml b/library/ui/src/main/res/values-am/strings.xml index cdc6b5fd7b..d24d681019 100644 --- a/library/ui/src/main/res/values-am/strings.xml +++ b/library/ui/src/main/res/values-am/strings.xml @@ -12,7 +12,7 @@ ሁሉንም ድገም በውዝ የሙሉ ማያ ሁነታ - Exit VR mode + ከቪአር ሁነታ ውጣ አውርድ የወረዱ በማውረድ ላይ diff --git a/library/ui/src/main/res/values-cs/strings.xml b/library/ui/src/main/res/values-cs/strings.xml index e1670d6fc4..97e3c35fb1 100644 --- a/library/ui/src/main/res/values-cs/strings.xml +++ b/library/ui/src/main/res/values-cs/strings.xml @@ -12,7 +12,7 @@ Opakovat vše Náhodně Režim celé obrazovky - Exit VR mode + Ukončit režim VR Stáhnout Stahování Stahování diff --git a/library/ui/src/main/res/values-el/strings.xml b/library/ui/src/main/res/values-el/strings.xml index 9c1373a77e..eff333354b 100644 --- a/library/ui/src/main/res/values-el/strings.xml +++ b/library/ui/src/main/res/values-el/strings.xml @@ -12,7 +12,7 @@ Επανάληψη όλων Τυχαία αναπαραγωγή Λειτουργία πλήρους οθόνης - Exit VR mode + Έξοδος από λειτουργία VR mode Λήψη Λήψεις Λήψη diff --git a/library/ui/src/main/res/values-es/strings.xml b/library/ui/src/main/res/values-es/strings.xml index 7e36bf86af..845210ecfa 100644 --- a/library/ui/src/main/res/values-es/strings.xml +++ b/library/ui/src/main/res/values-es/strings.xml @@ -12,7 +12,7 @@ Repetir todo Reproducir aleatoriamente Modo de pantalla completa - Exit VR mode + Salir del modo RV Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-eu/strings.xml b/library/ui/src/main/res/values-eu/strings.xml index 2db2ac2131..381726a2ea 100644 --- a/library/ui/src/main/res/values-eu/strings.xml +++ b/library/ui/src/main/res/values-eu/strings.xml @@ -12,7 +12,7 @@ Errepikatu guztiak Erreproduzitu ausaz Pantaila osoko modua - Exit VR mode + Irten EB modutik Deskargak Deskargak Deskargatzen diff --git a/library/ui/src/main/res/values-it/strings.xml b/library/ui/src/main/res/values-it/strings.xml index 72ea9ee01d..6611da7595 100644 --- a/library/ui/src/main/res/values-it/strings.xml +++ b/library/ui/src/main/res/values-it/strings.xml @@ -12,7 +12,7 @@ Ripeti tutto Riproduzione casuale Modalità a schermo intero - Exit VR mode + Esci dalla modalità VR Scarica Download Download… diff --git a/library/ui/src/main/res/values-ja/strings.xml b/library/ui/src/main/res/values-ja/strings.xml index 5ea4a3ec07..9f39d59103 100644 --- a/library/ui/src/main/res/values-ja/strings.xml +++ b/library/ui/src/main/res/values-ja/strings.xml @@ -12,7 +12,7 @@ 全曲をリピート シャッフル 全画面モード - Exit VR mode + VR モードを終了します ダウンロード ダウンロード ダウンロードしています diff --git a/library/ui/src/main/res/values-km/strings.xml b/library/ui/src/main/res/values-km/strings.xml index e78efe17d7..80234d89c9 100644 --- a/library/ui/src/main/res/values-km/strings.xml +++ b/library/ui/src/main/res/values-km/strings.xml @@ -12,7 +12,7 @@ លេង​ឡើងវិញ​ទាំងអស់ ច្របល់ មុខងារពេញ​អេក្រង់ - Exit VR mode + ចាកចេញពីមុខងារ​ VR ទាញយក ទាញយក កំពុង​ទាញ​យក diff --git a/library/ui/src/main/res/values-kn/strings.xml b/library/ui/src/main/res/values-kn/strings.xml index 1efac2b25f..0a24dcb001 100644 --- a/library/ui/src/main/res/values-kn/strings.xml +++ b/library/ui/src/main/res/values-kn/strings.xml @@ -12,7 +12,7 @@ ಎಲ್ಲವನ್ನು ಪುನರಾವರ್ತಿಸಿ ಶಫಲ್‌ ಪೂರ್ಣ ಪರದೆ ಮೋಡ್ - Exit VR mode + VR ಮೋಡ್‌ನಿಂದ ನಿರ್ಗಮಿಸಿ ಡೌನ್‌ಲೋಡ್‌ ಡೌನ್‌ಲೋಡ್‌ಗಳು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ diff --git a/library/ui/src/main/res/values-ko/strings.xml b/library/ui/src/main/res/values-ko/strings.xml index 92d840374a..e87a6c1645 100644 --- a/library/ui/src/main/res/values-ko/strings.xml +++ b/library/ui/src/main/res/values-ko/strings.xml @@ -12,7 +12,7 @@ 모두 반복 셔플 전체화면 모드 - Exit VR mode + VR 모드 종료 다운로드 다운로드 다운로드 중 diff --git a/library/ui/src/main/res/values-lo/strings.xml b/library/ui/src/main/res/values-lo/strings.xml index 291dd5f187..025a66ad95 100644 --- a/library/ui/src/main/res/values-lo/strings.xml +++ b/library/ui/src/main/res/values-lo/strings.xml @@ -12,7 +12,7 @@ ຫຼິ້ນຊ້ຳທັງໝົດ ຫຼີ້ນແບບສຸ່ມ ໂໝດເຕັມຈໍ - Exit VR mode + ອອກຈາກໂໝດ VR ດາວໂຫລດ ດາວໂຫລດ ກຳລັງດາວໂຫລດ diff --git a/library/ui/src/main/res/values-mr/strings.xml b/library/ui/src/main/res/values-mr/strings.xml index c88466ca9c..f53b2dc75e 100644 --- a/library/ui/src/main/res/values-mr/strings.xml +++ b/library/ui/src/main/res/values-mr/strings.xml @@ -12,7 +12,7 @@ सर्व रीपीट करा शफल करा फुल स्क्रीन मोड - Exit VR mode + VR मोडमधून बाहेर पडा डाउनलोड करा डाउनलोड डाउनलोड होत आहे diff --git a/library/ui/src/main/res/values-pa/strings.xml b/library/ui/src/main/res/values-pa/strings.xml index ea5f8f034f..1293f58cd6 100644 --- a/library/ui/src/main/res/values-pa/strings.xml +++ b/library/ui/src/main/res/values-pa/strings.xml @@ -12,7 +12,7 @@ ਸਾਰਿਆਂ ਨੂੰ ਦੁਹਰਾਓ ਬੇਤਰਤੀਬ ਕਰੋ ਪੂਰੀ-ਸਕ੍ਰੀਨ ਮੋਡ - Exit VR mode + VR ਮੋਡ ਤੋਂ ਬਾਹਰ ਜਾਓ ਡਾਊਨਲੋਡ ਕਰੋ ਡਾਊਨਲੋਡ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ diff --git a/library/ui/src/main/res/values-pl/strings.xml b/library/ui/src/main/res/values-pl/strings.xml index 6e08237bfd..c6ef64e724 100644 --- a/library/ui/src/main/res/values-pl/strings.xml +++ b/library/ui/src/main/res/values-pl/strings.xml @@ -12,7 +12,7 @@ Powtórz wszystkie Odtwarzanie losowe Tryb pełnoekranowy - Exit VR mode + Wyłącz tryb VR Pobierz Pobieranie Pobieram diff --git a/library/ui/src/main/res/values-pt/strings.xml b/library/ui/src/main/res/values-pt/strings.xml index 96c27f3d4a..7a0592e837 100644 --- a/library/ui/src/main/res/values-pt/strings.xml +++ b/library/ui/src/main/res/values-pt/strings.xml @@ -12,7 +12,7 @@ Repetir tudo Aleatório Modo de tela cheia - Exit VR mode + Sair do modo RV Fazer o download Downloads Fazendo o download diff --git a/library/ui/src/main/res/values-sl/strings.xml b/library/ui/src/main/res/values-sl/strings.xml index ec75cd1d4b..7b32ddcefe 100644 --- a/library/ui/src/main/res/values-sl/strings.xml +++ b/library/ui/src/main/res/values-sl/strings.xml @@ -12,7 +12,7 @@ Ponavljanje vseh Naključno predvajanje Celozaslonski način - Exit VR mode + Izhod iz načina VR Prenos Prenosi Prenašanje diff --git a/library/ui/src/main/res/values-sw/strings.xml b/library/ui/src/main/res/values-sw/strings.xml index aeefbc2a9f..97db67a16e 100644 --- a/library/ui/src/main/res/values-sw/strings.xml +++ b/library/ui/src/main/res/values-sw/strings.xml @@ -12,7 +12,7 @@ Rudia zote Changanya Hali ya skrini nzima - Exit VR mode + Ondoka kwenye hali ya VR Pakua Vipakuliwa Inapakua diff --git a/library/ui/src/main/res/values-ta/strings.xml b/library/ui/src/main/res/values-ta/strings.xml index 655da2f718..0f3c22275c 100644 --- a/library/ui/src/main/res/values-ta/strings.xml +++ b/library/ui/src/main/res/values-ta/strings.xml @@ -12,7 +12,7 @@ அனைத்தையும் மீண்டும் இயக்கு கலைத்துப் போடு முழுத்திரைப் பயன்முறை - Exit VR mode + VRரிலிருந்து வெளியேறும் பதிவிறக்கும் பட்டன் பதிவிறக்கங்கள் பதிவிறக்குகிறது From ed32e2a7f7857e7bbcea79b05dd16a80882ba5eb Mon Sep 17 00:00:00 2001 From: eguven Date: Fri, 26 Oct 2018 10:44:14 -0700 Subject: [PATCH 129/832] Add TrackSelectionUtil.getAverageBitrate method ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218877191 --- .../trackselection/TrackSelectionUtil.java | 57 +++++++ .../TrackSelectionUtilTest.java | 150 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java new file mode 100644 index 0000000000..6f6cd8d80c --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.trackselection; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; + +/** Track selection related utility methods. */ +public final class TrackSelectionUtil { + + private TrackSelectionUtil() {} + + /** + * Returns average bitrate for chunks in bits per second. Chunks are included in average until + * {@code maxDurationMs} or the first unknown length chunk. + * + * @param iterator Iterator for media chunk sequences. + * @param maxDurationUs Maximum duration of chunks to be included in average bitrate, in + * microseconds. + * @return Average bitrate for chunks in bits per second, or {@link C#LENGTH_UNSET} if there are + * no chunks or the first chunk length is unknown. + */ + public static int getAverageBitrate(MediaChunkIterator iterator, long maxDurationUs) { + long totalDurationUs = 0; + long totalLength = 0; + while (iterator.next()) { + long chunkLength = iterator.getDataSpec().length; + if (chunkLength == C.LENGTH_UNSET) { + break; + } + long chunkDurationUs = iterator.getChunkEndTimeUs() - iterator.getChunkStartTimeUs(); + if (totalDurationUs + chunkDurationUs >= maxDurationUs) { + totalLength += chunkLength * (maxDurationUs - totalDurationUs) / chunkDurationUs; + totalDurationUs = maxDurationUs; + break; + } + totalDurationUs += chunkDurationUs; + totalLength += chunkLength; + } + return totalDurationUs == 0 + ? C.LENGTH_UNSET + : (int) (totalLength * C.BITS_PER_BYTE * C.MICROS_PER_SECOND / totalDurationUs); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java new file mode 100644 index 0000000000..df1780c984 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.trackselection; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; +import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; +import com.google.android.exoplayer2.upstream.DataSpec; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** {@link TrackSelectionUtil} tests. */ +@RunWith(RobolectricTestRunner.class) +public class TrackSelectionUtilTest { + + public static final long MAX_DURATION_US = 30 * C.MICROS_PER_SECOND; + + @Test + public void getAverageBitrate_emptyIterator_returnsUnsetLength() { + assertThat(TrackSelectionUtil.getAverageBitrate(MediaChunkIterator.EMPTY, MAX_DURATION_US)) + .isEqualTo(C.LENGTH_UNSET); + } + + @Test + public void getAverageBitrate_oneChunk_returnsChunkBitrate() { + long[] chunkTimeBoundariesSec = {0, 5}; + long[] chunkLengths = {10}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); + int expectedAverageBitrate = + (int) (chunkLengths[0] * C.BITS_PER_BYTE / chunkTimeBoundariesSec[1]); + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) + .isEqualTo(expectedAverageBitrate); + } + + @Test + public void getAverageBitrate_multipleSameDurationChunks_returnsAverageChunkBitrate() { + long[] chunkTimeBoundariesSec = {0, 5, 10}; + long[] chunkLengths = {10, 20}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); + long totalLength = chunkLengths[0] + chunkLengths[1]; + int expectedAverageBitrate = (int) (totalLength * C.BITS_PER_BYTE / chunkTimeBoundariesSec[2]); + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) + .isEqualTo(expectedAverageBitrate); + } + + @Test + public void getAverageBitrate_multipleDifferentDurationChunks_returnsAverageChunkBitrate() { + long[] chunkTimeBoundariesSec = {0, 5, 15, 30}; + long[] chunkLengths = {10, 20, 30}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); + long totalLength = chunkLengths[0] + chunkLengths[1] + chunkLengths[2]; + int expectedAverageBitrate = (int) (totalLength * C.BITS_PER_BYTE / chunkTimeBoundariesSec[3]); + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) + .isEqualTo(expectedAverageBitrate); + } + + @Test + public void getAverageBitrate_firstChunkLengthUnset_returnsUnsetLength() { + long[] chunkTimeBoundariesSec = {0, 5, 15, 30}; + long[] chunkLengths = {C.LENGTH_UNSET, 20, 30}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) + .isEqualTo(C.LENGTH_UNSET); + } + + @Test + public void getAverageBitrate_secondChunkLengthUnset_returnsFirstChunkBitrate() { + long[] chunkTimeBoundariesSec = {0, 5, 15, 30}; + long[] chunkLengths = {10, C.LENGTH_UNSET, 30}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); + int expectedAverageBitrate = + (int) (chunkLengths[0] * C.BITS_PER_BYTE / chunkTimeBoundariesSec[1]); + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) + .isEqualTo(expectedAverageBitrate); + } + + @Test + public void + getAverageBitrate_chunksExceedingMaxDuration_returnsAverageChunkBitrateUpToMaxDuration() { + long[] chunkTimeBoundariesSec = {0, 5, 15, 45, 50}; + long[] chunkLengths = {10, 20, 30, 100}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); + // Just half of the third chunk is in the max duration + long totalLength = chunkLengths[0] + chunkLengths[1] + chunkLengths[2] / 2; + int expectedAverageBitrate = + (int) (totalLength * C.BITS_PER_BYTE * C.MICROS_PER_SECOND / MAX_DURATION_US); + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) + .isEqualTo(expectedAverageBitrate); + } + + @Test + public void getAverageBitrate_zeroMaxDuration_returnsUnsetLength() { + long[] chunkTimeBoundariesSec = {0, 5, 10}; + long[] chunkLengths = {10, 20}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, /* maxDurationUs= */ 0)) + .isEqualTo(C.LENGTH_UNSET); + } + + private static final class FakeIterator extends BaseMediaChunkIterator { + + private final long[] chunkTimeBoundariesSec; + private final long[] chunkLengths; + + public FakeIterator(long[] chunkTimeBoundariesSec, long[] chunkLengths) { + super(/* fromIndex= */ 0, /* toIndex= */ chunkTimeBoundariesSec.length - 2); + this.chunkTimeBoundariesSec = chunkTimeBoundariesSec; + this.chunkLengths = chunkLengths; + } + + @Override + public DataSpec getDataSpec() { + checkInBounds(); + return new DataSpec( + Uri.EMPTY, + /* absoluteStreamPosition= */ 0, + chunkLengths[(int) getCurrentIndex()], + /* key= */ null); + } + + @Override + public long getChunkStartTimeUs() { + checkInBounds(); + return chunkTimeBoundariesSec[(int) getCurrentIndex()] * C.MICROS_PER_SECOND; + } + + @Override + public long getChunkEndTimeUs() { + checkInBounds(); + return chunkTimeBoundariesSec[(int) getCurrentIndex() + 1] * C.MICROS_PER_SECOND; + } + } +} From 194d0f3331aa90d42275ab9a7f55fa6226438616 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 29 Oct 2018 04:43:48 -0700 Subject: [PATCH 130/832] Add a bit of structure DefaultTrackSelector, for sanity ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219109829 --- .../trackselection/DefaultTrackSelector.java | 568 ++++++++++-------- .../DefaultTrackSelectorTest.java | 35 +- .../src/main/res/values-b+sr+Latn/strings.xml | 2 +- library/ui/src/main/res/values-be/strings.xml | 2 +- library/ui/src/main/res/values-bg/strings.xml | 2 +- library/ui/src/main/res/values-bs/strings.xml | 2 +- library/ui/src/main/res/values-ca/strings.xml | 2 +- library/ui/src/main/res/values-da/strings.xml | 2 +- library/ui/src/main/res/values-de/strings.xml | 2 +- .../ui/src/main/res/values-es-rUS/strings.xml | 2 +- library/ui/src/main/res/values-et/strings.xml | 2 +- library/ui/src/main/res/values-fi/strings.xml | 2 +- .../ui/src/main/res/values-fr-rCA/strings.xml | 2 +- library/ui/src/main/res/values-fr/strings.xml | 2 +- library/ui/src/main/res/values-gl/strings.xml | 2 +- library/ui/src/main/res/values-hi/strings.xml | 2 +- library/ui/src/main/res/values-hu/strings.xml | 2 +- library/ui/src/main/res/values-hy/strings.xml | 2 +- library/ui/src/main/res/values-is/strings.xml | 2 +- library/ui/src/main/res/values-iw/strings.xml | 2 +- library/ui/src/main/res/values-kk/strings.xml | 2 +- library/ui/src/main/res/values-ky/strings.xml | 2 +- library/ui/src/main/res/values-lt/strings.xml | 2 +- library/ui/src/main/res/values-lv/strings.xml | 2 +- library/ui/src/main/res/values-mk/strings.xml | 2 +- library/ui/src/main/res/values-mn/strings.xml | 2 +- library/ui/src/main/res/values-ms/strings.xml | 2 +- library/ui/src/main/res/values-nb/strings.xml | 2 +- library/ui/src/main/res/values-ne/strings.xml | 2 +- library/ui/src/main/res/values-nl/strings.xml | 2 +- .../ui/src/main/res/values-pt-rPT/strings.xml | 2 +- library/ui/src/main/res/values-ro/strings.xml | 2 +- library/ui/src/main/res/values-ru/strings.xml | 2 +- library/ui/src/main/res/values-sr/strings.xml | 2 +- library/ui/src/main/res/values-sv/strings.xml | 2 +- library/ui/src/main/res/values-sw/strings.xml | 2 +- library/ui/src/main/res/values-th/strings.xml | 2 +- library/ui/src/main/res/values-tl/strings.xml | 2 +- library/ui/src/main/res/values-tr/strings.xml | 2 +- library/ui/src/main/res/values-uk/strings.xml | 2 +- library/ui/src/main/res/values-ur/strings.xml | 2 +- library/ui/src/main/res/values-vi/strings.xml | 2 +- .../ui/src/main/res/values-zh-rCN/strings.xml | 2 +- .../ui/src/main/res/values-zh-rHK/strings.xml | 2 +- .../ui/src/main/res/values-zh-rTW/strings.xml | 2 +- 45 files changed, 376 insertions(+), 313 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 0f5adab4f5..435b5f1301 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -164,23 +164,27 @@ public class DefaultTrackSelector extends MappingTrackSelector { private final SparseArray> selectionOverrides; private final SparseBooleanArray rendererDisabledFlags; - private @Nullable String preferredAudioLanguage; - private @Nullable String preferredTextLanguage; - private boolean selectUndeterminedTextLanguage; - private int disabledTextTrackSelectionFlags; - private boolean forceLowestBitrate; - private boolean forceHighestSupportedBitrate; - private boolean allowMixedMimeAdaptiveness; - private boolean allowNonSeamlessAdaptiveness; + // Video private int maxVideoWidth; private int maxVideoHeight; private int maxVideoFrameRate; private int maxVideoBitrate; private boolean exceedVideoConstraintsIfNecessary; - private boolean exceedRendererCapabilitiesIfNecessary; private int viewportWidth; private int viewportHeight; private boolean viewportOrientationMayChange; + // Audio + @Nullable private String preferredAudioLanguage; + // Text + @Nullable private String preferredTextLanguage; + private boolean selectUndeterminedTextLanguage; + private int disabledTextTrackSelectionFlags; + // General + private boolean forceLowestBitrate; + private boolean forceHighestSupportedBitrate; + private boolean allowMixedMimeAdaptiveness; + private boolean allowNonSeamlessAdaptiveness; + private boolean exceedRendererCapabilitiesIfNecessary; private int tunnelingAudioSessionId; /** Creates a builder with default initial values. */ @@ -193,109 +197,34 @@ public class DefaultTrackSelector extends MappingTrackSelector { * obtained. */ private ParametersBuilder(Parameters initialValues) { - selectionOverrides = cloneSelectionOverrides(initialValues.selectionOverrides); - rendererDisabledFlags = initialValues.rendererDisabledFlags.clone(); - preferredAudioLanguage = initialValues.preferredAudioLanguage; - preferredTextLanguage = initialValues.preferredTextLanguage; - selectUndeterminedTextLanguage = initialValues.selectUndeterminedTextLanguage; - disabledTextTrackSelectionFlags = initialValues.disabledTextTrackSelectionFlags; - forceLowestBitrate = initialValues.forceLowestBitrate; - forceHighestSupportedBitrate = initialValues.forceHighestSupportedBitrate; - allowMixedMimeAdaptiveness = initialValues.allowMixedMimeAdaptiveness; - allowNonSeamlessAdaptiveness = initialValues.allowNonSeamlessAdaptiveness; + // Video maxVideoWidth = initialValues.maxVideoWidth; maxVideoHeight = initialValues.maxVideoHeight; maxVideoFrameRate = initialValues.maxVideoFrameRate; maxVideoBitrate = initialValues.maxVideoBitrate; exceedVideoConstraintsIfNecessary = initialValues.exceedVideoConstraintsIfNecessary; - exceedRendererCapabilitiesIfNecessary = initialValues.exceedRendererCapabilitiesIfNecessary; viewportWidth = initialValues.viewportWidth; viewportHeight = initialValues.viewportHeight; viewportOrientationMayChange = initialValues.viewportOrientationMayChange; + // Audio + preferredAudioLanguage = initialValues.preferredAudioLanguage; + // Text + preferredTextLanguage = initialValues.preferredTextLanguage; + selectUndeterminedTextLanguage = initialValues.selectUndeterminedTextLanguage; + disabledTextTrackSelectionFlags = initialValues.disabledTextTrackSelectionFlags; + // General + forceLowestBitrate = initialValues.forceLowestBitrate; + forceHighestSupportedBitrate = initialValues.forceHighestSupportedBitrate; + allowMixedMimeAdaptiveness = initialValues.allowMixedMimeAdaptiveness; + allowNonSeamlessAdaptiveness = initialValues.allowNonSeamlessAdaptiveness; + exceedRendererCapabilitiesIfNecessary = initialValues.exceedRendererCapabilitiesIfNecessary; tunnelingAudioSessionId = initialValues.tunnelingAudioSessionId; + // Overrides + selectionOverrides = cloneSelectionOverrides(initialValues.selectionOverrides); + rendererDisabledFlags = initialValues.rendererDisabledFlags.clone(); } - /** - * See {@link Parameters#preferredAudioLanguage}. - * - * @return This builder. - */ - public ParametersBuilder setPreferredAudioLanguage(String preferredAudioLanguage) { - this.preferredAudioLanguage = preferredAudioLanguage; - return this; - } - - /** - * See {@link Parameters#preferredTextLanguage}. - * - * @return This builder. - */ - public ParametersBuilder setPreferredTextLanguage(String preferredTextLanguage) { - this.preferredTextLanguage = preferredTextLanguage; - return this; - } - - /** - * See {@link Parameters#selectUndeterminedTextLanguage}. - * - * @return This builder. - */ - public ParametersBuilder setSelectUndeterminedTextLanguage( - boolean selectUndeterminedTextLanguage) { - this.selectUndeterminedTextLanguage = selectUndeterminedTextLanguage; - return this; - } - - /** - * See {@link Parameters#disabledTextTrackSelectionFlags}. - * - * @return This builder. - */ - public ParametersBuilder setDisabledTextTrackSelectionFlags( - int disabledTextTrackSelectionFlags) { - this.disabledTextTrackSelectionFlags = disabledTextTrackSelectionFlags; - return this; - } - - /** - * See {@link Parameters#forceLowestBitrate}. - * - * @return This builder. - */ - public ParametersBuilder setForceLowestBitrate(boolean forceLowestBitrate) { - this.forceLowestBitrate = forceLowestBitrate; - return this; - } - - /** - * See {@link Parameters#forceHighestSupportedBitrate}. - * - * @return This builder. - */ - public ParametersBuilder setForceHighestSupportedBitrate(boolean forceHighestSupportedBitrate) { - this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; - return this; - } - - /** - * See {@link Parameters#allowMixedMimeAdaptiveness}. - * - * @return This builder. - */ - public ParametersBuilder setAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) { - this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; - return this; - } - - /** - * See {@link Parameters#allowNonSeamlessAdaptiveness}. - * - * @return This builder. - */ - public ParametersBuilder setAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) { - this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; - return this; - } + // Video /** * Equivalent to {@link #setMaxVideoSize setMaxVideoSize(1279, 719)}. @@ -357,17 +286,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } - /** - * See {@link Parameters#exceedRendererCapabilitiesIfNecessary}. - * - * @return This builder. - */ - public ParametersBuilder setExceedRendererCapabilitiesIfNecessary( - boolean exceedRendererCapabilitiesIfNecessary) { - this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; - return this; - } - /** * Equivalent to calling {@link #setViewportSize(int, int, boolean)} with the viewport size * obtained from {@link Util#getPhysicalDisplaySize(Context)}. @@ -384,8 +302,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { } /** - * Equivalent to - * {@link #setViewportSize setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}. + * Equivalent to {@link #setViewportSize setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, + * true)}. * * @return This builder. */ @@ -410,6 +328,127 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } + // Audio + + /** + * See {@link Parameters#preferredAudioLanguage}. + * + * @return This builder. + */ + public ParametersBuilder setPreferredAudioLanguage(String preferredAudioLanguage) { + this.preferredAudioLanguage = preferredAudioLanguage; + return this; + } + + // Text + + /** + * See {@link Parameters#preferredTextLanguage}. + * + * @return This builder. + */ + public ParametersBuilder setPreferredTextLanguage(String preferredTextLanguage) { + this.preferredTextLanguage = preferredTextLanguage; + return this; + } + + /** + * See {@link Parameters#selectUndeterminedTextLanguage}. + * + * @return This builder. + */ + public ParametersBuilder setSelectUndeterminedTextLanguage( + boolean selectUndeterminedTextLanguage) { + this.selectUndeterminedTextLanguage = selectUndeterminedTextLanguage; + return this; + } + + /** + * See {@link Parameters#disabledTextTrackSelectionFlags}. + * + * @return This builder. + */ + public ParametersBuilder setDisabledTextTrackSelectionFlags( + int disabledTextTrackSelectionFlags) { + this.disabledTextTrackSelectionFlags = disabledTextTrackSelectionFlags; + return this; + } + + // General + + /** + * See {@link Parameters#forceLowestBitrate}. + * + * @return This builder. + */ + public ParametersBuilder setForceLowestBitrate(boolean forceLowestBitrate) { + this.forceLowestBitrate = forceLowestBitrate; + return this; + } + + /** + * See {@link Parameters#forceHighestSupportedBitrate}. + * + * @return This builder. + */ + public ParametersBuilder setForceHighestSupportedBitrate(boolean forceHighestSupportedBitrate) { + this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; + return this; + } + + /** + * See {@link Parameters#allowMixedMimeAdaptiveness}. + * + * @return This builder. + */ + public ParametersBuilder setAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) { + this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; + return this; + } + + /** + * See {@link Parameters#allowNonSeamlessAdaptiveness}. + * + * @return This builder. + */ + public ParametersBuilder setAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) { + this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; + return this; + } + + /** + * See {@link Parameters#exceedRendererCapabilitiesIfNecessary}. + * + * @return This builder. + */ + public ParametersBuilder setExceedRendererCapabilitiesIfNecessary( + boolean exceedRendererCapabilitiesIfNecessary) { + this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; + return this; + } + + /** + * See {@link Parameters#tunnelingAudioSessionId}. + * + *

    Enables or disables tunneling. To enable tunneling, pass an audio session id to use when + * in tunneling mode. Session ids can be generated using {@link + * C#generateAudioSessionIdV21(Context)}. To disable tunneling pass {@link + * C#AUDIO_SESSION_ID_UNSET}. Tunneling will only be activated if it's both enabled and + * supported by the audio and video renderers for the selected tracks. + * + * @param tunnelingAudioSessionId The audio session id to use when tunneling, or {@link + * C#AUDIO_SESSION_ID_UNSET} to disable tunneling. + */ + public ParametersBuilder setTunnelingAudioSessionId(int tunnelingAudioSessionId) { + if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) { + this.tunnelingAudioSessionId = tunnelingAudioSessionId; + return this; + } + return this; + } + + // Overrides + /** * Sets whether the renderer at the specified index is disabled. Disabling a renderer prevents * the selector from selecting any tracks for it. @@ -513,51 +552,36 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } - /** - * See {@link Parameters#tunnelingAudioSessionId}. - * - *

    Enables or disables tunneling. To enable tunneling, pass an audio session id to use when - * in tunneling mode. Session ids can be generated using {@link - * C#generateAudioSessionIdV21(Context)}. To disable tunneling pass {@link - * C#AUDIO_SESSION_ID_UNSET}. Tunneling will only be activated if it's both enabled and - * supported by the audio and video renderers for the selected tracks. - * - * @param tunnelingAudioSessionId The audio session id to use when tunneling, or {@link - * C#AUDIO_SESSION_ID_UNSET} to disable tunneling. - */ - public ParametersBuilder setTunnelingAudioSessionId(int tunnelingAudioSessionId) { - if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) { - this.tunnelingAudioSessionId = tunnelingAudioSessionId; - return this; - } - return this; - } - /** * Builds a {@link Parameters} instance with the selected values. */ public Parameters build() { return new Parameters( - selectionOverrides, - rendererDisabledFlags, - preferredAudioLanguage, - preferredTextLanguage, - selectUndeterminedTextLanguage, - disabledTextTrackSelectionFlags, - forceLowestBitrate, - forceHighestSupportedBitrate, - allowMixedMimeAdaptiveness, - allowNonSeamlessAdaptiveness, + // Video maxVideoWidth, maxVideoHeight, maxVideoFrameRate, maxVideoBitrate, exceedVideoConstraintsIfNecessary, - exceedRendererCapabilitiesIfNecessary, viewportWidth, viewportHeight, viewportOrientationMayChange, - tunnelingAudioSessionId); + // Audio + preferredAudioLanguage, + // Text + preferredTextLanguage, + selectUndeterminedTextLanguage, + disabledTextTrackSelectionFlags, + // General + forceLowestBitrate, + forceHighestSupportedBitrate, + allowMixedMimeAdaptiveness, + allowNonSeamlessAdaptiveness, + exceedRendererCapabilitiesIfNecessary, + tunnelingAudioSessionId, + // Overrides + selectionOverrides, + rendererDisabledFlags); } private static SparseArray> cloneSelectionOverrides( @@ -576,37 +600,10 @@ public class DefaultTrackSelector extends MappingTrackSelector { /** An instance with default values. */ public static final Parameters DEFAULT = new Parameters(); - // Per renderer overrides. - + // Overrides private final SparseArray> selectionOverrides; private final SparseBooleanArray rendererDisabledFlags; - // Audio - /** - * The preferred language for audio and forced text tracks, as an ISO 639-2/T tag. {@code null} - * selects the default track, or the first track if there's no default. The default value is - * {@code null}. - */ - public final @Nullable String preferredAudioLanguage; - - // Text - /** - * The preferred language for text tracks as an ISO 639-2/T tag. {@code null} selects the - * default track if there is one, or no track otherwise. The default value is {@code null}. - */ - public final @Nullable String preferredTextLanguage; - /** - * Whether a text track with undetermined language should be selected if no track with {@link - * #preferredTextLanguage} is available, or if {@link #preferredTextLanguage} is unset. The - * default value is {@code false}. - */ - public final boolean selectUndeterminedTextLanguage; - /** - * Bitmask of selection flags that are disabled for text track selections. See {@link - * C.SelectionFlags}. The default value is {@code 0} (i.e. no flags). - */ - public final int disabledTextTrackSelectionFlags; - // Video /** * Maximum allowed video width. The default value is {@link Integer#MAX_VALUE} (i.e. no @@ -660,6 +657,32 @@ public class DefaultTrackSelector extends MappingTrackSelector { */ public final boolean viewportOrientationMayChange; + // Audio + /** + * The preferred language for audio and forced text tracks, as an ISO 639-2/T tag. {@code null} + * selects the default track, or the first track if there's no default. The default value is + * {@code null}. + */ + @Nullable public final String preferredAudioLanguage; + + // Text + /** + * The preferred language for text tracks as an ISO 639-2/T tag. {@code null} selects the + * default track if there is one, or no track otherwise. The default value is {@code null}. + */ + @Nullable public final String preferredTextLanguage; + /** + * Whether a text track with undetermined language should be selected if no track with {@link + * #preferredTextLanguage} is available, or if {@link #preferredTextLanguage} is unset. The + * default value is {@code false}. + */ + public final boolean selectUndeterminedTextLanguage; + /** + * Bitmask of selection flags that are disabled for text track selections. See {@link + * C.SelectionFlags}. The default value is {@code 0} (i.e. no flags). + */ + public final int disabledTextTrackSelectionFlags; + // General /** * Whether to force selection of the single lowest bitrate audio and video tracks that comply @@ -700,92 +723,112 @@ public class DefaultTrackSelector extends MappingTrackSelector { private Parameters() { this( - /* selectionOverrides= */ new SparseArray<>(), - /* rendererDisabledFlags= */ new SparseBooleanArray(), - /* preferredAudioLanguage= */ null, - /* preferredTextLanguage= */ null, - /* selectUndeterminedTextLanguage= */ false, - /* disabledTextTrackSelectionFlags= */ 0, - /* forceLowestBitrate= */ false, - /* forceHighestSupportedBitrate= */ false, - /* allowMixedMimeAdaptiveness= */ false, - /* allowNonSeamlessAdaptiveness= */ true, + // Video /* maxVideoWidth= */ Integer.MAX_VALUE, /* maxVideoHeight= */ Integer.MAX_VALUE, /* maxVideoFrameRate= */ Integer.MAX_VALUE, /* maxVideoBitrate= */ Integer.MAX_VALUE, /* exceedVideoConstraintsIfNecessary= */ true, - /* exceedRendererCapabilitiesIfNecessary= */ true, /* viewportWidth= */ Integer.MAX_VALUE, /* viewportHeight= */ Integer.MAX_VALUE, /* viewportOrientationMayChange= */ true, - /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET); + // Audio + /* preferredAudioLanguage= */ null, + // Text + /* preferredTextLanguage= */ null, + /* selectUndeterminedTextLanguage= */ false, + /* disabledTextTrackSelectionFlags= */ 0, + // General + /* forceLowestBitrate= */ false, + /* forceHighestSupportedBitrate= */ false, + /* allowMixedMimeAdaptiveness= */ false, + /* allowNonSeamlessAdaptiveness= */ true, + /* exceedRendererCapabilitiesIfNecessary= */ true, + /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET, + // Overrides + /* selectionOverrides= */ new SparseArray<>(), + /* rendererDisabledFlags= */ new SparseBooleanArray()); } /* package */ Parameters( - SparseArray> selectionOverrides, - SparseBooleanArray rendererDisabledFlags, - @Nullable String preferredAudioLanguage, - @Nullable String preferredTextLanguage, - boolean selectUndeterminedTextLanguage, - int disabledTextTrackSelectionFlags, - boolean forceLowestBitrate, - boolean forceHighestSupportedBitrate, - boolean allowMixedMimeAdaptiveness, - boolean allowNonSeamlessAdaptiveness, + // Video int maxVideoWidth, int maxVideoHeight, int maxVideoFrameRate, int maxVideoBitrate, boolean exceedVideoConstraintsIfNecessary, - boolean exceedRendererCapabilitiesIfNecessary, int viewportWidth, int viewportHeight, boolean viewportOrientationMayChange, - int tunnelingAudioSessionId) { - this.selectionOverrides = selectionOverrides; - this.rendererDisabledFlags = rendererDisabledFlags; - this.preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); - this.preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage); - this.selectUndeterminedTextLanguage = selectUndeterminedTextLanguage; - this.disabledTextTrackSelectionFlags = disabledTextTrackSelectionFlags; - this.forceLowestBitrate = forceLowestBitrate; - this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; - this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; - this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; + // Audio + @Nullable String preferredAudioLanguage, + // Text + @Nullable String preferredTextLanguage, + boolean selectUndeterminedTextLanguage, + int disabledTextTrackSelectionFlags, + // General + boolean forceLowestBitrate, + boolean forceHighestSupportedBitrate, + boolean allowMixedMimeAdaptiveness, + boolean allowNonSeamlessAdaptiveness, + boolean exceedRendererCapabilitiesIfNecessary, + int tunnelingAudioSessionId, + // Overrides + SparseArray> selectionOverrides, + SparseBooleanArray rendererDisabledFlags) { + // Video this.maxVideoWidth = maxVideoWidth; this.maxVideoHeight = maxVideoHeight; this.maxVideoFrameRate = maxVideoFrameRate; this.maxVideoBitrate = maxVideoBitrate; this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary; - this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; this.viewportWidth = viewportWidth; this.viewportHeight = viewportHeight; this.viewportOrientationMayChange = viewportOrientationMayChange; + // Audio + this.preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); + // Text + this.preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage); + this.selectUndeterminedTextLanguage = selectUndeterminedTextLanguage; + this.disabledTextTrackSelectionFlags = disabledTextTrackSelectionFlags; + // General + this.forceLowestBitrate = forceLowestBitrate; + this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; + this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; + this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; + this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; this.tunnelingAudioSessionId = tunnelingAudioSessionId; + // Overrides + this.selectionOverrides = selectionOverrides; + this.rendererDisabledFlags = rendererDisabledFlags; } /* package */ Parameters(Parcel in) { - this.selectionOverrides = readSelectionOverrides(in); - this.rendererDisabledFlags = in.readSparseBooleanArray(); - this.preferredAudioLanguage = in.readString(); - this.preferredTextLanguage = in.readString(); - this.selectUndeterminedTextLanguage = Util.readBoolean(in); - this.disabledTextTrackSelectionFlags = in.readInt(); - this.forceLowestBitrate = Util.readBoolean(in); - this.forceHighestSupportedBitrate = Util.readBoolean(in); - this.allowMixedMimeAdaptiveness = Util.readBoolean(in); - this.allowNonSeamlessAdaptiveness = Util.readBoolean(in); + // Video this.maxVideoWidth = in.readInt(); this.maxVideoHeight = in.readInt(); this.maxVideoFrameRate = in.readInt(); this.maxVideoBitrate = in.readInt(); this.exceedVideoConstraintsIfNecessary = Util.readBoolean(in); - this.exceedRendererCapabilitiesIfNecessary = Util.readBoolean(in); this.viewportWidth = in.readInt(); this.viewportHeight = in.readInt(); this.viewportOrientationMayChange = Util.readBoolean(in); + // Audio + this.preferredAudioLanguage = in.readString(); + // Text + this.preferredTextLanguage = in.readString(); + this.selectUndeterminedTextLanguage = Util.readBoolean(in); + this.disabledTextTrackSelectionFlags = in.readInt(); + // General + this.forceLowestBitrate = Util.readBoolean(in); + this.forceHighestSupportedBitrate = Util.readBoolean(in); + this.allowMixedMimeAdaptiveness = Util.readBoolean(in); + this.allowNonSeamlessAdaptiveness = Util.readBoolean(in); + this.exceedRendererCapabilitiesIfNecessary = Util.readBoolean(in); this.tunnelingAudioSessionId = in.readInt(); + // Overrides + this.selectionOverrides = readSelectionOverrides(in); + this.rendererDisabledFlags = in.readSparseBooleanArray(); } /** @@ -839,49 +882,59 @@ public class DefaultTrackSelector extends MappingTrackSelector { return false; } Parameters other = (Parameters) obj; - return selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage + return maxVideoWidth == other.maxVideoWidth + && maxVideoHeight == other.maxVideoHeight + && maxVideoFrameRate == other.maxVideoFrameRate + && maxVideoBitrate == other.maxVideoBitrate + && exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary + && viewportOrientationMayChange == other.viewportOrientationMayChange + && viewportWidth == other.viewportWidth + && viewportHeight == other.viewportHeight + // Audio + && TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage) + // Text + && TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage) + && selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage && disabledTextTrackSelectionFlags == other.disabledTextTrackSelectionFlags + // General && forceLowestBitrate == other.forceLowestBitrate && forceHighestSupportedBitrate == other.forceHighestSupportedBitrate && allowMixedMimeAdaptiveness == other.allowMixedMimeAdaptiveness && allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness - && maxVideoWidth == other.maxVideoWidth - && maxVideoHeight == other.maxVideoHeight - && maxVideoFrameRate == other.maxVideoFrameRate - && exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary && exceedRendererCapabilitiesIfNecessary == other.exceedRendererCapabilitiesIfNecessary - && viewportOrientationMayChange == other.viewportOrientationMayChange - && viewportWidth == other.viewportWidth - && viewportHeight == other.viewportHeight - && maxVideoBitrate == other.maxVideoBitrate && tunnelingAudioSessionId == other.tunnelingAudioSessionId - && TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage) - && TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage) + // Overrides && areRendererDisabledFlagsEqual(rendererDisabledFlags, other.rendererDisabledFlags) && areSelectionOverridesEqual(selectionOverrides, other.selectionOverrides); } @Override public int hashCode() { - int result = selectUndeterminedTextLanguage ? 1 : 0; + int result = 1; + // Video + result = 31 * result + maxVideoWidth; + result = 31 * result + maxVideoHeight; + result = 31 * result + maxVideoFrameRate; + result = 31 * result + maxVideoBitrate; + result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0); + result = 31 * result + (viewportOrientationMayChange ? 1 : 0); + result = 31 * result + viewportWidth; + result = 31 * result + viewportHeight; + // Audio + result = + 31 * result + (preferredAudioLanguage == null ? 0 : preferredAudioLanguage.hashCode()); + // Text + result = 31 * result + (preferredTextLanguage == null ? 0 : preferredTextLanguage.hashCode()); + result = 31 * result + (selectUndeterminedTextLanguage ? 1 : 0); result = 31 * result + disabledTextTrackSelectionFlags; + // General result = 31 * result + (forceLowestBitrate ? 1 : 0); result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0); result = 31 * result + (allowMixedMimeAdaptiveness ? 1 : 0); result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0); - result = 31 * result + maxVideoWidth; - result = 31 * result + maxVideoHeight; - result = 31 * result + maxVideoFrameRate; - result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0); result = 31 * result + (exceedRendererCapabilitiesIfNecessary ? 1 : 0); - result = 31 * result + (viewportOrientationMayChange ? 1 : 0); - result = 31 * result + viewportWidth; - result = 31 * result + viewportHeight; - result = 31 * result + maxVideoBitrate; result = 31 * result + tunnelingAudioSessionId; - result = - 31 * result + (preferredAudioLanguage == null ? 0 : preferredAudioLanguage.hashCode()); - result = 31 * result + (preferredTextLanguage == null ? 0 : preferredTextLanguage.hashCode()); + // Overrides (omitted from hashCode). return result; } @@ -894,26 +947,31 @@ public class DefaultTrackSelector extends MappingTrackSelector { @Override public void writeToParcel(Parcel dest, int flags) { - writeSelectionOverridesToParcel(dest, selectionOverrides); - dest.writeSparseBooleanArray(rendererDisabledFlags); - dest.writeString(preferredAudioLanguage); - dest.writeString(preferredTextLanguage); - Util.writeBoolean(dest, selectUndeterminedTextLanguage); - dest.writeInt(disabledTextTrackSelectionFlags); - Util.writeBoolean(dest, forceLowestBitrate); - Util.writeBoolean(dest, forceHighestSupportedBitrate); - Util.writeBoolean(dest, allowMixedMimeAdaptiveness); - Util.writeBoolean(dest, allowNonSeamlessAdaptiveness); + // Video dest.writeInt(maxVideoWidth); dest.writeInt(maxVideoHeight); dest.writeInt(maxVideoFrameRate); dest.writeInt(maxVideoBitrate); Util.writeBoolean(dest, exceedVideoConstraintsIfNecessary); - Util.writeBoolean(dest, exceedRendererCapabilitiesIfNecessary); dest.writeInt(viewportWidth); dest.writeInt(viewportHeight); Util.writeBoolean(dest, viewportOrientationMayChange); + // Audio + dest.writeString(preferredAudioLanguage); + // Text + dest.writeString(preferredTextLanguage); + Util.writeBoolean(dest, selectUndeterminedTextLanguage); + dest.writeInt(disabledTextTrackSelectionFlags); + // General + Util.writeBoolean(dest, forceLowestBitrate); + Util.writeBoolean(dest, forceHighestSupportedBitrate); + Util.writeBoolean(dest, allowMixedMimeAdaptiveness); + Util.writeBoolean(dest, allowNonSeamlessAdaptiveness); + Util.writeBoolean(dest, exceedRendererCapabilitiesIfNecessary); dest.writeInt(tunnelingAudioSessionId); + // Overrides + writeSelectionOverridesToParcel(dest, selectionOverrides); + dest.writeSparseBooleanArray(rendererDisabledFlags); } public static final Parcelable.Creator CREATOR = @@ -2216,7 +2274,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { public final int channelCount; public final int sampleRate; - public final @Nullable String mimeType; + @Nullable public final String mimeType; public AudioConfigurationTuple(int channelCount, int sampleRate, @Nullable String mimeType) { this.channelCount = channelCount; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index ee3d2cf9b0..82fff694de 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -129,26 +129,31 @@ public final class DefaultTrackSelectorTest { Parameters parametersToParcel = new Parameters( - selectionOverrides, - rendererDisabledFlags, + // Video + /* maxVideoWidth= */ 0, + /* maxVideoHeight= */ 1, + /* maxVideoFrameRate= */ 2, + /* maxVideoBitrate= */ 3, + /* exceedVideoConstraintsIfNecessary= */ false, + /* viewportWidth= */ 4, + /* viewportHeight= */ 5, + /* viewportOrientationMayChange= */ true, + // Audio /* preferredAudioLanguage= */ "en", + // Text /* preferredTextLanguage= */ "de", /* selectUndeterminedTextLanguage= */ false, - /* disabledTextTrackSelectionFlags= */ 0, + /* disabledTextTrackSelectionFlags= */ 6, + // General /* forceLowestBitrate= */ true, - /* forceHighestSupportedBitrate= */ true, - /* allowMixedMimeAdaptiveness= */ false, - /* allowNonSeamlessAdaptiveness= */ true, - /* maxVideoWidth= */ 1, - /* maxVideoHeight= */ 2, - /* maxVideoFrameRate= */ 3, - /* maxVideoBitrate= */ 4, - /* exceedVideoConstraintsIfNecessary= */ false, + /* forceHighestSupportedBitrate= */ false, + /* allowMixedMimeAdaptiveness= */ true, + /* allowNonSeamlessAdaptiveness= */ false, /* exceedRendererCapabilitiesIfNecessary= */ true, - /* viewportWidth= */ 5, - /* viewportHeight= */ 6, - /* viewportOrientationMayChange= */ false, - /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET); + /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET, + // Overrides + selectionOverrides, + rendererDisabledFlags); Parcel parcel = Parcel.obtain(); parametersToParcel.writeToParcel(parcel, 0); diff --git a/library/ui/src/main/res/values-b+sr+Latn/strings.xml b/library/ui/src/main/res/values-b+sr+Latn/strings.xml index 890a19b310..f03e6a6a94 100644 --- a/library/ui/src/main/res/values-b+sr+Latn/strings.xml +++ b/library/ui/src/main/res/values-b+sr+Latn/strings.xml @@ -12,7 +12,7 @@ Ponovi sve Pusti nasumično Režim celog ekrana - Exit VR mode + Izađi iz VR režima Preuzmi Preuzimanja Preuzima se diff --git a/library/ui/src/main/res/values-be/strings.xml b/library/ui/src/main/res/values-be/strings.xml index b6c50e4c27..e669739691 100644 --- a/library/ui/src/main/res/values-be/strings.xml +++ b/library/ui/src/main/res/values-be/strings.xml @@ -12,7 +12,7 @@ Паўтарыць усе Перамяшаць Поўнаэкранны рэжым - Exit VR mode + Выйсці з VR-рэжыму Спампаваць Спампоўкі Спампоўваецца diff --git a/library/ui/src/main/res/values-bg/strings.xml b/library/ui/src/main/res/values-bg/strings.xml index da02946695..4146ccf0d8 100644 --- a/library/ui/src/main/res/values-bg/strings.xml +++ b/library/ui/src/main/res/values-bg/strings.xml @@ -12,7 +12,7 @@ Повтаряне на всички Разбъркване Режим на цял екран - Exit VR mode + Изход от режима за VR Изтегляне Изтегляния Изтегля се diff --git a/library/ui/src/main/res/values-bs/strings.xml b/library/ui/src/main/res/values-bs/strings.xml index 34f445bcca..d609f4b7cc 100644 --- a/library/ui/src/main/res/values-bs/strings.xml +++ b/library/ui/src/main/res/values-bs/strings.xml @@ -12,7 +12,7 @@ Ponovi sve Izmiješaj Način rada preko cijelog ekrana - Izlazak iz VR načina + Izađi iz VR načina rada Preuzmi Preuzimanja Preuzimanje diff --git a/library/ui/src/main/res/values-ca/strings.xml b/library/ui/src/main/res/values-ca/strings.xml index 530423f212..0023710ce4 100644 --- a/library/ui/src/main/res/values-ca/strings.xml +++ b/library/ui/src/main/res/values-ca/strings.xml @@ -12,7 +12,7 @@ Repeteix tot Reprodueix aleatòriament Mode de pantalla completa - Exit VR mode + Surt del mode RV Baixa Baixades S\'està baixant diff --git a/library/ui/src/main/res/values-da/strings.xml b/library/ui/src/main/res/values-da/strings.xml index 93da6a649a..79f8bca488 100644 --- a/library/ui/src/main/res/values-da/strings.xml +++ b/library/ui/src/main/res/values-da/strings.xml @@ -12,7 +12,7 @@ Gentag alle Bland Fuld skærm - Exit VR mode + Luk VR-tilstand Download Downloads Downloader diff --git a/library/ui/src/main/res/values-de/strings.xml b/library/ui/src/main/res/values-de/strings.xml index 410b261b02..c49c23b1de 100644 --- a/library/ui/src/main/res/values-de/strings.xml +++ b/library/ui/src/main/res/values-de/strings.xml @@ -12,7 +12,7 @@ Alle wiederholen Zufallsmix Vollbildmodus - Exit VR mode + VR-Modus beenden Herunterladen Downloads Wird heruntergeladen diff --git a/library/ui/src/main/res/values-es-rUS/strings.xml b/library/ui/src/main/res/values-es-rUS/strings.xml index 677fddad66..4980f3c20a 100644 --- a/library/ui/src/main/res/values-es-rUS/strings.xml +++ b/library/ui/src/main/res/values-es-rUS/strings.xml @@ -12,7 +12,7 @@ Repetir todo Reproducir aleatoriamente Modo de pantalla completa - Exit VR mode + Salir del modo RV Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-et/strings.xml b/library/ui/src/main/res/values-et/strings.xml index 189c5032db..bd1bd19926 100644 --- a/library/ui/src/main/res/values-et/strings.xml +++ b/library/ui/src/main/res/values-et/strings.xml @@ -12,7 +12,7 @@ Korda kõiki Esita juhuslikus järjekorras Täisekraani režiim - Exit VR mode + Välju VR-režiimist Allalaadimine Allalaadimised Allalaadimine diff --git a/library/ui/src/main/res/values-fi/strings.xml b/library/ui/src/main/res/values-fi/strings.xml index 89b2ed60bc..65e9f0a7a8 100644 --- a/library/ui/src/main/res/values-fi/strings.xml +++ b/library/ui/src/main/res/values-fi/strings.xml @@ -12,7 +12,7 @@ Toista kaikki uudelleen Satunnaistoisto Koko näytön tila - Exit VR mode + Poistu VR-tilasta Lataa Lataukset Ladataan diff --git a/library/ui/src/main/res/values-fr-rCA/strings.xml b/library/ui/src/main/res/values-fr-rCA/strings.xml index a694f48c10..2454657faf 100644 --- a/library/ui/src/main/res/values-fr-rCA/strings.xml +++ b/library/ui/src/main/res/values-fr-rCA/strings.xml @@ -12,7 +12,7 @@ Tout lire en boucle Lecture aléatoire Mode Plein écran - Exit VR mode + Quitter le mode RV Télécharger Téléchargements Téléchargement en cours… diff --git a/library/ui/src/main/res/values-fr/strings.xml b/library/ui/src/main/res/values-fr/strings.xml index b7c4168bef..c3e32e7946 100644 --- a/library/ui/src/main/res/values-fr/strings.xml +++ b/library/ui/src/main/res/values-fr/strings.xml @@ -12,7 +12,7 @@ Tout lire en boucle Aléatoire Mode plein écran - Exit VR mode + Quitter le mode RV Télécharger Téléchargements Téléchargement… diff --git a/library/ui/src/main/res/values-gl/strings.xml b/library/ui/src/main/res/values-gl/strings.xml index ead458f87c..bd7524e5d3 100644 --- a/library/ui/src/main/res/values-gl/strings.xml +++ b/library/ui/src/main/res/values-gl/strings.xml @@ -12,7 +12,7 @@ Repetir todas as pistas Reprodución aleatoria Modo de pantalla completa - Exit VR mode + Sae do modo de RV Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-hi/strings.xml b/library/ui/src/main/res/values-hi/strings.xml index 4ba488e95d..77264fe109 100644 --- a/library/ui/src/main/res/values-hi/strings.xml +++ b/library/ui/src/main/res/values-hi/strings.xml @@ -12,7 +12,7 @@ सभी को दोहराएं शफ़ल करें फ़ुलस्क्रीन मोड - Exit VR mode + VR मोड से बाहर निकलें डाउनलोड करें डाउनलोड की गई मीडिया फ़ाइलें डाउनलोड हो रहा है diff --git a/library/ui/src/main/res/values-hu/strings.xml b/library/ui/src/main/res/values-hu/strings.xml index d271d741ab..75e5b9e211 100644 --- a/library/ui/src/main/res/values-hu/strings.xml +++ b/library/ui/src/main/res/values-hu/strings.xml @@ -12,7 +12,7 @@ Összes szám ismétlése Keverés Teljes képernyős mód - Exit VR mode + Kilépés a VR-módból Letöltés Letöltések Letöltés folyamatban diff --git a/library/ui/src/main/res/values-hy/strings.xml b/library/ui/src/main/res/values-hy/strings.xml index c017a2fc27..a0a3d92aa5 100644 --- a/library/ui/src/main/res/values-hy/strings.xml +++ b/library/ui/src/main/res/values-hy/strings.xml @@ -12,7 +12,7 @@ Կրկնել բոլորը Խառնել Լիաէկրան ռեժիմ - Exit VR mode + Դուրս գալ VR ռեժիմից Ներբեռնել Ներբեռնումներ Ներբեռնում diff --git a/library/ui/src/main/res/values-is/strings.xml b/library/ui/src/main/res/values-is/strings.xml index 77f03e4889..c9e9212cbe 100644 --- a/library/ui/src/main/res/values-is/strings.xml +++ b/library/ui/src/main/res/values-is/strings.xml @@ -12,7 +12,7 @@ Endurtaka allt Stokka Allur skjárinn - Exit VR mode + Loka sýndarveruleikastillingu Sækja Niðurhal Sækir diff --git a/library/ui/src/main/res/values-iw/strings.xml b/library/ui/src/main/res/values-iw/strings.xml index 003597fdee..b69fa41101 100644 --- a/library/ui/src/main/res/values-iw/strings.xml +++ b/library/ui/src/main/res/values-iw/strings.xml @@ -12,7 +12,7 @@ חזור על הכול ערבוב מצב מסך מלא - Exit VR mode + יציאה ממצב VR הורדה הורדות ההורדה מתבצעת diff --git a/library/ui/src/main/res/values-kk/strings.xml b/library/ui/src/main/res/values-kk/strings.xml index 1988908ae3..8e5677e7f6 100644 --- a/library/ui/src/main/res/values-kk/strings.xml +++ b/library/ui/src/main/res/values-kk/strings.xml @@ -12,7 +12,7 @@ Барлығын қайталау Араластыру Толық экран режимі - Exit VR mode + VR режимінен шығу Жүктеп алу Жүктеп алынғандар Жүктеп алынуда diff --git a/library/ui/src/main/res/values-ky/strings.xml b/library/ui/src/main/res/values-ky/strings.xml index 614e5846c4..efe78b4d23 100644 --- a/library/ui/src/main/res/values-ky/strings.xml +++ b/library/ui/src/main/res/values-ky/strings.xml @@ -12,7 +12,7 @@ Баарын кайталоо Аралаштыруу Толук экран режими - Exit VR mode + VR режиминен чыгуу Жүктөп алуу Жүктөлүп алынгандар Жүктөлүп алынууда diff --git a/library/ui/src/main/res/values-lt/strings.xml b/library/ui/src/main/res/values-lt/strings.xml index c3e39edc3c..4e4da8b79b 100644 --- a/library/ui/src/main/res/values-lt/strings.xml +++ b/library/ui/src/main/res/values-lt/strings.xml @@ -12,7 +12,7 @@ Kartoti viską Maišyti Viso ekrano režimas - Exit VR mode + Išeiti iš VR režimo Atsisiųsti Atsisiuntimai Atsisiunčiama diff --git a/library/ui/src/main/res/values-lv/strings.xml b/library/ui/src/main/res/values-lv/strings.xml index cf3c20e53d..6a7937788e 100644 --- a/library/ui/src/main/res/values-lv/strings.xml +++ b/library/ui/src/main/res/values-lv/strings.xml @@ -12,7 +12,7 @@ Atkārtot visu Atskaņot jauktā secībā Pilnekrāna režīms - Exit VR mode + Iziet no VR režīma Lejupielādēt Lejupielādes Notiek lejupielāde diff --git a/library/ui/src/main/res/values-mk/strings.xml b/library/ui/src/main/res/values-mk/strings.xml index 8b44cd317e..fe5a32409a 100644 --- a/library/ui/src/main/res/values-mk/strings.xml +++ b/library/ui/src/main/res/values-mk/strings.xml @@ -12,7 +12,7 @@ Повтори ги сите Измешај Режим на цел екран - Exit VR mode + Излези од режимот на VR Преземи Преземања Се презема diff --git a/library/ui/src/main/res/values-mn/strings.xml b/library/ui/src/main/res/values-mn/strings.xml index 77d493b42f..4aecfcfc70 100644 --- a/library/ui/src/main/res/values-mn/strings.xml +++ b/library/ui/src/main/res/values-mn/strings.xml @@ -12,7 +12,7 @@ Бүгдийг нь дахин тоглуулах Холих Бүтэн дэлгэцийн горим - Exit VR mode + VR горимоос гарах Татах Татaлт Татаж байна diff --git a/library/ui/src/main/res/values-ms/strings.xml b/library/ui/src/main/res/values-ms/strings.xml index df74b68abc..a94d7be6df 100644 --- a/library/ui/src/main/res/values-ms/strings.xml +++ b/library/ui/src/main/res/values-ms/strings.xml @@ -12,7 +12,7 @@ Ulang semua Rombak Mod skrin penuh - Exit VR mode + Keluar daripada mod VR Muat turun Muat turun Memuat turun diff --git a/library/ui/src/main/res/values-nb/strings.xml b/library/ui/src/main/res/values-nb/strings.xml index c73deb4a18..cbcfff5902 100644 --- a/library/ui/src/main/res/values-nb/strings.xml +++ b/library/ui/src/main/res/values-nb/strings.xml @@ -12,7 +12,7 @@ Gjenta alle Tilfeldig rekkefølge Fullskjermmodus - Exit VR mode + Avslutt VR-modus Last ned Nedlastinger Laster ned diff --git a/library/ui/src/main/res/values-ne/strings.xml b/library/ui/src/main/res/values-ne/strings.xml index 7daff17e1f..bc8510393c 100644 --- a/library/ui/src/main/res/values-ne/strings.xml +++ b/library/ui/src/main/res/values-ne/strings.xml @@ -12,7 +12,7 @@ सबै दोहोर्‍याउनुहोस् मिसाउनुहोस् पूर्ण स्क्रिन मोड - Exit VR mode + VR मोडबाट बाहिरिनुहोस् डाउनलोड गर्नुहोस् डाउनलोडहरू डाउनलोड गरिँदै छ diff --git a/library/ui/src/main/res/values-nl/strings.xml b/library/ui/src/main/res/values-nl/strings.xml index 4749f6bbbb..b8215a3bad 100644 --- a/library/ui/src/main/res/values-nl/strings.xml +++ b/library/ui/src/main/res/values-nl/strings.xml @@ -12,7 +12,7 @@ Alles herhalen Shuffle Modus \'Volledig scherm\' - Exit VR mode + VR-modus sluiten Downloaden Downloads Downloaden diff --git a/library/ui/src/main/res/values-pt-rPT/strings.xml b/library/ui/src/main/res/values-pt-rPT/strings.xml index 6bbfb49c6b..c45b94b144 100644 --- a/library/ui/src/main/res/values-pt-rPT/strings.xml +++ b/library/ui/src/main/res/values-pt-rPT/strings.xml @@ -12,7 +12,7 @@ Repetir tudo Reproduzir aleatoriamente Modo de ecrã inteiro - Exit VR mode + Sair do Modo de RV Transferir Transferências A transferir… diff --git a/library/ui/src/main/res/values-ro/strings.xml b/library/ui/src/main/res/values-ro/strings.xml index cb50bb6b76..ddbfac1406 100644 --- a/library/ui/src/main/res/values-ro/strings.xml +++ b/library/ui/src/main/res/values-ro/strings.xml @@ -12,7 +12,7 @@ Repetați-le pe toate Redați aleatoriu Modul Ecran complet - Exit VR mode + Ieșiți din modul RV Descărcați Descărcări Se descarcă diff --git a/library/ui/src/main/res/values-ru/strings.xml b/library/ui/src/main/res/values-ru/strings.xml index 24659ef1c1..dd08f5f7f9 100644 --- a/library/ui/src/main/res/values-ru/strings.xml +++ b/library/ui/src/main/res/values-ru/strings.xml @@ -12,7 +12,7 @@ Повторять все Перемешать Полноэкранный режим - Exit VR mode + Выйти из VR-режима Скачать Скачивания Скачивание… diff --git a/library/ui/src/main/res/values-sr/strings.xml b/library/ui/src/main/res/values-sr/strings.xml index 82af21ba17..4cc717220c 100644 --- a/library/ui/src/main/res/values-sr/strings.xml +++ b/library/ui/src/main/res/values-sr/strings.xml @@ -12,7 +12,7 @@ Понови све Пусти насумично Режим целог екрана - Exit VR mode + Изађи из ВР режима Преузми Преузимања Преузима се diff --git a/library/ui/src/main/res/values-sv/strings.xml b/library/ui/src/main/res/values-sv/strings.xml index a99f8409bd..56bc4a6731 100644 --- a/library/ui/src/main/res/values-sv/strings.xml +++ b/library/ui/src/main/res/values-sv/strings.xml @@ -12,7 +12,7 @@ Upprepa alla Blanda spår Helskärmsläge - Exit VR mode + Avsluta VR-läget Ladda ned Nedladdningar Laddar ned diff --git a/library/ui/src/main/res/values-sw/strings.xml b/library/ui/src/main/res/values-sw/strings.xml index 97db67a16e..98da2b1d99 100644 --- a/library/ui/src/main/res/values-sw/strings.xml +++ b/library/ui/src/main/res/values-sw/strings.xml @@ -12,7 +12,7 @@ Rudia zote Changanya Hali ya skrini nzima - Ondoka kwenye hali ya VR + Funga hali ya VR Pakua Vipakuliwa Inapakua diff --git a/library/ui/src/main/res/values-th/strings.xml b/library/ui/src/main/res/values-th/strings.xml index 5193aa3b86..ef772e37a2 100644 --- a/library/ui/src/main/res/values-th/strings.xml +++ b/library/ui/src/main/res/values-th/strings.xml @@ -12,7 +12,7 @@ เล่นซ้ำทั้งหมด สุ่ม โหมดเต็มหน้าจอ - Exit VR mode + ออกจากโหมด VR ดาวน์โหลด ดาวน์โหลด กำลังดาวน์โหลด diff --git a/library/ui/src/main/res/values-tl/strings.xml b/library/ui/src/main/res/values-tl/strings.xml index 54b4e9ca6c..95d0e70918 100644 --- a/library/ui/src/main/res/values-tl/strings.xml +++ b/library/ui/src/main/res/values-tl/strings.xml @@ -12,7 +12,7 @@ Ulitin lahat I-shuffle Fullscreen mode - Exit VR mode + Lumabas sa VR mode I-download Mga Download Nagda-download diff --git a/library/ui/src/main/res/values-tr/strings.xml b/library/ui/src/main/res/values-tr/strings.xml index 6c93d81270..1ac912d99c 100644 --- a/library/ui/src/main/res/values-tr/strings.xml +++ b/library/ui/src/main/res/values-tr/strings.xml @@ -12,7 +12,7 @@ Tümünü tekrarla Karıştır Tam ekran modu - Exit VR mode + VR modundan çıkar İndir İndirilenler İndiriliyor diff --git a/library/ui/src/main/res/values-uk/strings.xml b/library/ui/src/main/res/values-uk/strings.xml index ac1d4c0ce5..a3ab4f2ad7 100644 --- a/library/ui/src/main/res/values-uk/strings.xml +++ b/library/ui/src/main/res/values-uk/strings.xml @@ -12,7 +12,7 @@ Повторити всі Перемішати Повноекранний режим - Exit VR mode + Вийти з режиму VR Завантажити Завантаження Завантажується diff --git a/library/ui/src/main/res/values-ur/strings.xml b/library/ui/src/main/res/values-ur/strings.xml index bf4e6eddaf..4f9a84a41b 100644 --- a/library/ui/src/main/res/values-ur/strings.xml +++ b/library/ui/src/main/res/values-ur/strings.xml @@ -12,7 +12,7 @@ سبھی کو دہرائیں شفل کریں پوری اسکرین والی وضع - Exit VR mode + VR موڈ سے باہر نکلیں ڈاؤن لوڈ کریں ڈاؤن لوڈز ڈاؤن لوڈ ہو رہا ہے diff --git a/library/ui/src/main/res/values-vi/strings.xml b/library/ui/src/main/res/values-vi/strings.xml index bb9b290bd4..46adac2451 100644 --- a/library/ui/src/main/res/values-vi/strings.xml +++ b/library/ui/src/main/res/values-vi/strings.xml @@ -12,7 +12,7 @@ Lặp lại tất cả Phát ngẫu nhiên Chế độ toàn màn hình - Exit VR mode + Thoát chế độ thực tế ảo (VR) Tải xuống Tài nguyên đã tải xuống Đang tải xuống diff --git a/library/ui/src/main/res/values-zh-rCN/strings.xml b/library/ui/src/main/res/values-zh-rCN/strings.xml index ece063ff0f..68f3548bf7 100644 --- a/library/ui/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui/src/main/res/values-zh-rCN/strings.xml @@ -12,7 +12,7 @@ 全部重复播放 随机播放 全屏模式 - Exit VR mode + 退出 VR 模式 下载 下载内容 正在下载 diff --git a/library/ui/src/main/res/values-zh-rHK/strings.xml b/library/ui/src/main/res/values-zh-rHK/strings.xml index 6ccdd01a2a..dc3a44eaad 100644 --- a/library/ui/src/main/res/values-zh-rHK/strings.xml +++ b/library/ui/src/main/res/values-zh-rHK/strings.xml @@ -12,7 +12,7 @@ 全部重複播放 隨機播放 全螢幕模式 - Exit VR mode + 結束虛擬現實模式 下載 下載內容 正在下載 diff --git a/library/ui/src/main/res/values-zh-rTW/strings.xml b/library/ui/src/main/res/values-zh-rTW/strings.xml index 04134d92a6..f879f5c857 100644 --- a/library/ui/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui/src/main/res/values-zh-rTW/strings.xml @@ -12,7 +12,7 @@ 重複播放所有項目 隨機播放 全螢幕模式 - Exit VR mode + 結束虛擬實境模式 下載 下載 下載中 From 5de17b9adc0971b79a6e07bf1d5f980c40427ca6 Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 29 Oct 2018 06:43:56 -0700 Subject: [PATCH 131/832] Add support for playing spherical videos on Daydream RELNOTES=true ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219119888 --- RELEASENOTES.md | 1 + extensions/gvr/build.gradle | 4 + .../ui/spherical/BaseGvrPlayerActivity.java | 354 ++++++++++++++++++ extensions/gvr/src/main/res/layout/vr_ui.xml | 27 ++ extensions/gvr/src/main/res/values/styles.xml | 19 + .../exoplayer2/ui/PlayerControlView.java | 46 ++- .../ui/spherical/CanvasRenderer.java | 297 +++++++++++++++ .../exoplayer2/ui/spherical/GlViewGroup.java | 115 ++++++ .../ui/spherical/PointerRenderer.java | 146 ++++++++ .../ui/spherical/SceneRenderer.java | 8 +- .../res/layout/exo_playback_control_view.xml | 3 + library/ui/src/main/res/values-af/strings.xml | 2 +- library/ui/src/main/res/values-am/strings.xml | 2 +- library/ui/src/main/res/values-ar/strings.xml | 2 +- library/ui/src/main/res/values-az/strings.xml | 2 +- .../src/main/res/values-b+sr+Latn/strings.xml | 2 +- library/ui/src/main/res/values-be/strings.xml | 2 +- library/ui/src/main/res/values-bg/strings.xml | 2 +- library/ui/src/main/res/values-bn/strings.xml | 2 +- library/ui/src/main/res/values-bs/strings.xml | 2 +- library/ui/src/main/res/values-ca/strings.xml | 2 +- library/ui/src/main/res/values-cs/strings.xml | 2 +- library/ui/src/main/res/values-da/strings.xml | 2 +- library/ui/src/main/res/values-de/strings.xml | 2 +- library/ui/src/main/res/values-el/strings.xml | 2 +- .../ui/src/main/res/values-en-rAU/strings.xml | 2 +- .../ui/src/main/res/values-en-rGB/strings.xml | 2 +- .../ui/src/main/res/values-en-rIN/strings.xml | 2 +- .../ui/src/main/res/values-es-rUS/strings.xml | 2 +- library/ui/src/main/res/values-es/strings.xml | 2 +- library/ui/src/main/res/values-et/strings.xml | 2 +- library/ui/src/main/res/values-eu/strings.xml | 2 +- library/ui/src/main/res/values-fa/strings.xml | 2 +- library/ui/src/main/res/values-fi/strings.xml | 2 +- .../ui/src/main/res/values-fr-rCA/strings.xml | 2 +- library/ui/src/main/res/values-fr/strings.xml | 2 +- library/ui/src/main/res/values-gl/strings.xml | 2 +- library/ui/src/main/res/values-gu/strings.xml | 2 +- library/ui/src/main/res/values-hi/strings.xml | 2 +- library/ui/src/main/res/values-hr/strings.xml | 2 +- library/ui/src/main/res/values-hu/strings.xml | 2 +- library/ui/src/main/res/values-hy/strings.xml | 2 +- library/ui/src/main/res/values-in/strings.xml | 2 +- library/ui/src/main/res/values-is/strings.xml | 2 +- library/ui/src/main/res/values-it/strings.xml | 2 +- library/ui/src/main/res/values-iw/strings.xml | 2 +- library/ui/src/main/res/values-ja/strings.xml | 2 +- library/ui/src/main/res/values-ka/strings.xml | 2 +- library/ui/src/main/res/values-kk/strings.xml | 2 +- library/ui/src/main/res/values-km/strings.xml | 2 +- library/ui/src/main/res/values-kn/strings.xml | 2 +- library/ui/src/main/res/values-ko/strings.xml | 2 +- library/ui/src/main/res/values-ky/strings.xml | 2 +- library/ui/src/main/res/values-lo/strings.xml | 2 +- library/ui/src/main/res/values-lt/strings.xml | 2 +- library/ui/src/main/res/values-lv/strings.xml | 2 +- library/ui/src/main/res/values-mk/strings.xml | 2 +- library/ui/src/main/res/values-ml/strings.xml | 2 +- library/ui/src/main/res/values-mn/strings.xml | 2 +- library/ui/src/main/res/values-mr/strings.xml | 2 +- library/ui/src/main/res/values-ms/strings.xml | 2 +- library/ui/src/main/res/values-my/strings.xml | 2 +- library/ui/src/main/res/values-nb/strings.xml | 2 +- library/ui/src/main/res/values-ne/strings.xml | 2 +- library/ui/src/main/res/values-nl/strings.xml | 2 +- library/ui/src/main/res/values-pa/strings.xml | 2 +- library/ui/src/main/res/values-pl/strings.xml | 2 +- .../ui/src/main/res/values-pt-rPT/strings.xml | 2 +- library/ui/src/main/res/values-pt/strings.xml | 2 +- library/ui/src/main/res/values-ro/strings.xml | 2 +- library/ui/src/main/res/values-ru/strings.xml | 2 +- library/ui/src/main/res/values-si/strings.xml | 2 +- library/ui/src/main/res/values-sk/strings.xml | 2 +- library/ui/src/main/res/values-sl/strings.xml | 2 +- library/ui/src/main/res/values-sq/strings.xml | 2 +- library/ui/src/main/res/values-sr/strings.xml | 2 +- library/ui/src/main/res/values-sv/strings.xml | 2 +- library/ui/src/main/res/values-sw/strings.xml | 2 +- library/ui/src/main/res/values-ta/strings.xml | 2 +- library/ui/src/main/res/values-te/strings.xml | 2 +- library/ui/src/main/res/values-th/strings.xml | 2 +- library/ui/src/main/res/values-tl/strings.xml | 2 +- library/ui/src/main/res/values-tr/strings.xml | 2 +- library/ui/src/main/res/values-uk/strings.xml | 2 +- library/ui/src/main/res/values-ur/strings.xml | 2 +- library/ui/src/main/res/values-uz/strings.xml | 2 +- library/ui/src/main/res/values-vi/strings.xml | 2 +- .../ui/src/main/res/values-zh-rCN/strings.xml | 2 +- .../ui/src/main/res/values-zh-rHK/strings.xml | 2 +- .../ui/src/main/res/values-zh-rTW/strings.xml | 2 +- library/ui/src/main/res/values-zu/strings.xml | 2 +- library/ui/src/main/res/values/ids.xml | 1 + library/ui/src/main/res/values/strings.xml | 4 +- library/ui/src/main/res/values/styles.xml | 5 + 94 files changed, 1101 insertions(+), 89 deletions(-) create mode 100644 extensions/gvr/src/main/java/com/google/android/exoplayer2/ui/spherical/BaseGvrPlayerActivity.java create mode 100644 extensions/gvr/src/main/res/layout/vr_ui.xml create mode 100644 extensions/gvr/src/main/res/values/styles.xml create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/CanvasRenderer.java create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlViewGroup.java create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/PointerRenderer.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index df1423e688..12e9dbb28d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -32,6 +32,7 @@ * IMA extension: * For preroll to live stream transitions, project forward the loading position to avoid being behind the live window. +* Support for playing spherical videos on Daydream. * Fix issue where a `NullPointerException` is thrown when removing an unprepared media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)). diff --git a/extensions/gvr/build.gradle b/extensions/gvr/build.gradle index af973e1345..c845cb3423 100644 --- a/extensions/gvr/build.gradle +++ b/extensions/gvr/build.gradle @@ -31,8 +31,12 @@ android { dependencies { implementation project(modulePrefix + 'library-core') + implementation project(modulePrefix + 'library-ui') implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.google.vr:sdk-audio:1.80.0' + implementation 'com.google.vr:sdk-controller:1.80.0' + api 'com.google.vr:sdk-base:1.80.0' + compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion } ext { diff --git a/extensions/gvr/src/main/java/com/google/android/exoplayer2/ui/spherical/BaseGvrPlayerActivity.java b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ui/spherical/BaseGvrPlayerActivity.java new file mode 100644 index 0000000000..48acc4a9c8 --- /dev/null +++ b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ui/spherical/BaseGvrPlayerActivity.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.exoplayer2.ui.spherical; + +import android.content.Context; +import android.content.Intent; +import android.graphics.SurfaceTexture; +import android.opengl.Matrix; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.BinderThread; +import android.support.annotation.CallSuper; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.view.ContextThemeWrapper; +import android.view.MotionEvent; +import android.view.Surface; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.ext.gvr.R; +import com.google.android.exoplayer2.ui.PlayerControlView; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; +import com.google.vr.ndk.base.DaydreamApi; +import com.google.vr.sdk.base.AndroidCompat; +import com.google.vr.sdk.base.Eye; +import com.google.vr.sdk.base.GvrActivity; +import com.google.vr.sdk.base.GvrView; +import com.google.vr.sdk.base.HeadTransform; +import com.google.vr.sdk.base.Viewport; +import com.google.vr.sdk.controller.Controller; +import com.google.vr.sdk.controller.ControllerManager; +import javax.microedition.khronos.egl.EGLConfig; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** VR 360 video player base activity class. */ +public abstract class BaseGvrPlayerActivity extends GvrActivity { + private static final String TAG = "GvrPlayerActivity"; + + private static final int EXIT_FROM_VR_REQUEST_CODE = 42; + + private final Handler mainHandler; + + @Nullable private Player player; + @MonotonicNonNull private GlViewGroup glView; + @MonotonicNonNull private ControllerManager controllerManager; + @MonotonicNonNull private SurfaceTexture surfaceTexture; + @MonotonicNonNull private Surface surface; + @MonotonicNonNull private SceneRenderer scene; + @MonotonicNonNull private PlayerControlView playerControl; + + public BaseGvrPlayerActivity() { + mainHandler = new Handler(Looper.getMainLooper()); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setScreenAlwaysOn(true); + + GvrView gvrView = new GvrView(this); + // Since videos typically have fewer pixels per degree than the phones, reducing the render + // target scaling factor reduces the work required to render the scene. + gvrView.setRenderTargetScale(.5f); + + // If a custom theme isn't specified, the Context's theme is used. For VR Activities, this is + // the old Android default theme rather than a modern theme. Override this with a custom theme. + Context theme = new ContextThemeWrapper(this, R.style.VrTheme); + glView = new GlViewGroup(theme, R.layout.vr_ui); + + playerControl = Assertions.checkNotNull(glView.findViewById(R.id.controller)); + playerControl.setShowVrButton(true); + playerControl.setVrButtonListener(v -> exit()); + + PointerRenderer pointerRenderer = new PointerRenderer(); + scene = new SceneRenderer(); + Renderer renderer = new Renderer(scene, glView, pointerRenderer); + + // Attach glView to gvrView in order to properly handle UI events. + gvrView.addView(glView, 0); + + // Standard GvrView configuration + gvrView.setEGLConfigChooser( + 8, 8, 8, 8, // RGBA bits. + 16, // Depth bits. + 0); // Stencil bits. + gvrView.setRenderer(renderer); + setContentView(gvrView); + + // Most Daydream phones can render a 4k video at 60fps in sustained performance mode. These + // options can be tweaked along with the render target scale. + if (gvrView.setAsyncReprojectionEnabled(true)) { + AndroidCompat.setSustainedPerformanceMode(this, true); + } + + // Handle the user clicking on the 'X' in the top left corner. Since this is done when the user + // has taken the headset out of VR, it should launch the app's exit flow directly rather than + // using the transition flow. + gvrView.setOnCloseButtonListener(this::finish); + + ControllerManager.EventListener listener = + new ControllerManager.EventListener() { + @Override + public void onApiStatusChanged(int status) { + // Do nothing. + } + + @Override + public void onRecentered() { + // TODO if in cardboard mode call gvrView.recenterHeadTracker(); + glView.post(() -> Util.castNonNull(playerControl).show()); + } + }; + controllerManager = new ControllerManager(this, listener); + + Controller controller = controllerManager.getController(); + ControllerEventListener controllerEventListener = + new ControllerEventListener(controller, pointerRenderer, glView); + controller.setEventListener(controllerEventListener); + } + + /** + * Sets the {@link Player} to use. + * + * @param newPlayer The {@link Player} to use, or {@code null} to detach the current player. + */ + protected void setPlayer(@Nullable Player newPlayer) { + Assertions.checkNotNull(scene); + if (player == newPlayer) { + return; + } + if (player != null) { + Player.VideoComponent videoComponent = player.getVideoComponent(); + if (videoComponent != null) { + if (surface != null) { + videoComponent.clearVideoSurface(surface); + } + videoComponent.clearVideoFrameMetadataListener(scene); + videoComponent.clearCameraMotionListener(scene); + } + } + player = newPlayer; + if (player != null) { + Player.VideoComponent videoComponent = player.getVideoComponent(); + if (videoComponent != null) { + videoComponent.setVideoFrameMetadataListener(scene); + videoComponent.setCameraMotionListener(scene); + videoComponent.setVideoSurface(surface); + } + } + Assertions.checkNotNull(playerControl).setPlayer(player); + } + + /** + * Sets the default stereo mode. If the played video doesn't contain a stereo mode the default one + * is used. + * + * @param stereoMode A {@link C.StereoMode} value. + */ + protected void setDefaultStereoMode(@C.StereoMode int stereoMode) { + Assertions.checkNotNull(scene).setDefaultStereoMode(stereoMode); + } + + @CallSuper + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent unused) { + if (requestCode == EXIT_FROM_VR_REQUEST_CODE && resultCode == RESULT_OK) { + finish(); + } + } + + @Override + protected void onResume() { + super.onResume(); + Util.castNonNull(controllerManager).start(); + } + + @Override + protected void onPause() { + Util.castNonNull(controllerManager).stop(); + super.onPause(); + } + + @Override + protected void onDestroy() { + setPlayer(null); + releaseSurface(surfaceTexture, surface); + super.onDestroy(); + } + + /** Tries to exit gracefully from VR using a VR transition dialog. */ + @SuppressWarnings("nullness:argument.type.incompatible") + protected void exit() { + // This needs to use GVR's exit transition to avoid disorienting the user. + DaydreamApi api = DaydreamApi.create(this); + if (api != null) { + api.exitFromVr(this, EXIT_FROM_VR_REQUEST_CODE, null); + // Eventually, the Activity's onActivityResult will be called. + api.close(); + } else { + finish(); + } + } + + /** Toggles PlayerControl visibility. */ + @UiThread + protected void togglePlayerControlVisibility() { + if (Assertions.checkNotNull(playerControl).isVisible()) { + playerControl.hide(); + } else { + playerControl.show(); + } + } + + // Called on GL thread. + private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) { + mainHandler.post( + () -> { + SurfaceTexture oldSurfaceTexture = this.surfaceTexture; + Surface oldSurface = this.surface; + this.surfaceTexture = surfaceTexture; + this.surface = new Surface(surfaceTexture); + if (player != null) { + Player.VideoComponent videoComponent = player.getVideoComponent(); + if (videoComponent != null) { + videoComponent.setVideoSurface(surface); + } + } + releaseSurface(oldSurfaceTexture, oldSurface); + }); + } + + private static void releaseSurface( + @Nullable SurfaceTexture oldSurfaceTexture, @Nullable Surface oldSurface) { + if (oldSurfaceTexture != null) { + oldSurfaceTexture.release(); + } + if (oldSurface != null) { + oldSurface.release(); + } + } + + private class Renderer implements GvrView.StereoRenderer { + private static final float Z_NEAR = .1f; + private static final float Z_FAR = 100; + + private final float[] viewProjectionMatrix = new float[16]; + private final SceneRenderer scene; + private final GlViewGroup glView; + private final PointerRenderer pointerRenderer; + + public Renderer(SceneRenderer scene, GlViewGroup glView, PointerRenderer pointerRenderer) { + this.scene = scene; + this.glView = glView; + this.pointerRenderer = pointerRenderer; + } + + @Override + public void onNewFrame(HeadTransform headTransform) {} + + @Override + public void onDrawEye(Eye eye) { + Matrix.multiplyMM( + viewProjectionMatrix, 0, eye.getPerspective(Z_NEAR, Z_FAR), 0, eye.getEyeView(), 0); + scene.drawFrame(viewProjectionMatrix, eye.getType() == Eye.Type.RIGHT); + if (glView.isVisible()) { + glView.getRenderer().draw(viewProjectionMatrix); + pointerRenderer.draw(viewProjectionMatrix); + } + } + + @Override + public void onFinishFrame(Viewport viewport) {} + + @Override + public void onSurfaceCreated(EGLConfig config) { + onSurfaceTextureAvailable(scene.init()); + glView.getRenderer().init(); + pointerRenderer.init(); + } + + @Override + public void onSurfaceChanged(int width, int height) {} + + @Override + public void onRendererShutdown() { + glView.getRenderer().shutdown(); + pointerRenderer.shutdown(); + scene.shutdown(); + } + } + + private class ControllerEventListener extends Controller.EventListener { + + private final Controller controller; + private final PointerRenderer pointerRenderer; + private final GlViewGroup glView; + private final float[] controllerOrientationMatrix; + private boolean clickButtonDown; + private boolean appButtonDown; + + public ControllerEventListener( + Controller controller, PointerRenderer pointerRenderer, GlViewGroup glView) { + this.controller = controller; + this.pointerRenderer = pointerRenderer; + this.glView = glView; + controllerOrientationMatrix = new float[16]; + } + + @Override + @BinderThread + public void onUpdate() { + controller.update(); + controller.orientation.toRotationMatrix(controllerOrientationMatrix); + pointerRenderer.setControllerOrientation(controllerOrientationMatrix); + + if (clickButtonDown || controller.clickButtonState) { + int action; + if (clickButtonDown != controller.clickButtonState) { + clickButtonDown = controller.clickButtonState; + action = clickButtonDown ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP; + } else { + action = MotionEvent.ACTION_MOVE; + } + glView.post( + () -> { + float[] angles = controller.orientation.toYawPitchRollRadians(new float[3]); + boolean clickedOnView = glView.simulateClick(action, angles[0], angles[1]); + if (action == MotionEvent.ACTION_DOWN && !clickedOnView) { + togglePlayerControlVisibility(); + } + }); + } else if (!appButtonDown && controller.appButtonState) { + glView.post(BaseGvrPlayerActivity.this::togglePlayerControlVisibility); + } + appButtonDown = controller.appButtonState; + } + } +} diff --git a/extensions/gvr/src/main/res/layout/vr_ui.xml b/extensions/gvr/src/main/res/layout/vr_ui.xml new file mode 100644 index 0000000000..84e7ac7c6f --- /dev/null +++ b/extensions/gvr/src/main/res/layout/vr_ui.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/extensions/gvr/src/main/res/values/styles.xml b/extensions/gvr/src/main/res/values/styles.xml new file mode 100644 index 0000000000..c79e1dfa60 --- /dev/null +++ b/extensions/gvr/src/main/res/values/styles.xml @@ -0,0 +1,19 @@ + + + + + + + From 4824f352de137f84638289f612f631f08ee7fa39 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 29 Oct 2018 07:37:23 -0700 Subject: [PATCH 132/832] Decouple input format and codec format This paves the way to cleanly fix the first two issues listed in [] onDisabled will null inputFormat, but nulling of codecFormat will remain tied to the codec being released. Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219125458 --- .../mediacodec/MediaCodecRenderer.java | 93 +++++++++++-------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 71ed640f57..74c575c1ef 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -290,13 +290,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private final List decodeOnlyPresentationTimestamps; private final MediaCodec.BufferInfo outputBufferInfo; - private Format format; - private Format pendingFormat; + @Nullable private Format inputFormat; private Format outputFormat; private DrmSession drmSession; private DrmSession pendingDrmSession; - private MediaCodec codec; private float rendererOperatingRate; + @Nullable private MediaCodec codec; + @Nullable private Format codecFormat; private float codecOperatingRate; @Nullable private ArrayDeque availableCodecInfos; @Nullable private DecoderInitializationException preferredDecoderInitializationException; @@ -328,7 +328,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private boolean inputStreamEnded; private boolean outputStreamEnded; private boolean waitingForKeys; - private boolean waitingForFirstSyncFrame; + private boolean waitingForFirstSyncSample; + private boolean waitingForFirstSampleInFormat; protected DecoderCounters decoderCounters; @@ -435,13 +436,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { throws DecoderQueryException; protected final void maybeInitCodec() throws ExoPlaybackException { - if (codec != null || format == null) { + if (codec != null || inputFormat == null) { // We have a codec already, or we don't have a format with which to instantiate one. return; } drmSession = pendingDrmSession; - String mimeType = format.sampleMimeType; + String mimeType = inputFormat.sampleMimeType; MediaCrypto wrappedMediaCrypto = null; boolean drmSessionRequiresSecureDecoder = false; if (drmSession != null) { @@ -527,12 +528,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Override public final void setOperatingRate(float operatingRate) throws ExoPlaybackException { rendererOperatingRate = operatingRate; - updateCodecOperatingRate(); + if (codec != null && codecDrainAction != DRAIN_ACTION_REINITIALIZE) { + updateCodecOperatingRate(); + } } @Override protected void onDisabled() { - format = null; + inputFormat = null; try { releaseCodec(); } finally { @@ -563,6 +566,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { waitingForKeys = false; decodeOnlyPresentationTimestamps.clear(); codecInfo = null; + codecFormat = null; decoderCounters.decoderReleaseCount++; try { codec.stop(); @@ -599,7 +603,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { renderToEndOfStream(); return; } - if (format == null) { + if (inputFormat == null) { // We don't have a format yet, so try and read one. flagsOnlyBuffer.clear(); int result = readSource(formatHolder, flagsOnlyBuffer, true); @@ -680,7 +684,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecHotswapDeadlineMs = C.TIME_UNSET; codecReceivedEos = false; codecReceivedBuffers = false; - waitingForFirstSyncFrame = true; + waitingForFirstSyncSample = true; codecNeedsAdaptationWorkaroundBuffer = false; shouldSkipAdaptationWorkaroundOutputBuffer = false; shouldSkipOutputBuffer = false; @@ -707,7 +711,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { preferredDecoderInitializationException = null; } catch (DecoderQueryException e) { throw new DecoderInitializationException( - format, + inputFormat, e, drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR); @@ -716,7 +720,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (availableCodecInfos.isEmpty()) { throw new DecoderInitializationException( - format, + inputFormat, /* cause= */ null, drmSessionRequiresSecureDecoder, DecoderInitializationException.NO_SUITABLE_DECODER_ERROR); @@ -737,7 +741,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { availableCodecInfos.removeFirst(); DecoderInitializationException exception = new DecoderInitializationException( - format, e, drmSessionRequiresSecureDecoder, codecInfo.name); + inputFormat, e, drmSessionRequiresSecureDecoder, codecInfo.name); if (preferredDecoderInitializationException == null) { preferredDecoderInitializationException = exception; } else { @@ -756,18 +760,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private List getAvailableCodecInfos(boolean drmSessionRequiresSecureDecoder) throws DecoderQueryException { List codecInfos = - getDecoderInfos(mediaCodecSelector, format, drmSessionRequiresSecureDecoder); + getDecoderInfos(mediaCodecSelector, inputFormat, drmSessionRequiresSecureDecoder); if (codecInfos.isEmpty() && drmSessionRequiresSecureDecoder) { // The drm session indicates that a secure decoder is required, but the device does not // have one. Assuming that supportsFormat indicated support for the media being played, we // know that it does not require a secure output path. Most CDM implementations allow // playback to proceed with a non-secure decoder in this case, so we try our luck. - codecInfos = getDecoderInfos(mediaCodecSelector, format, /* requiresSecureDecoder= */ false); + codecInfos = + getDecoderInfos(mediaCodecSelector, inputFormat, /* requiresSecureDecoder= */ false); if (!codecInfos.isEmpty()) { Log.w( TAG, "Drm session requires secure decoder for " - + format.sampleMimeType + + inputFormat.sampleMimeType + ", but no secure decoder available. Trying to proceed with " + codecInfos + "."); @@ -785,7 +790,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { float codecOperatingRate = Util.SDK_INT < 23 ? CODEC_OPERATING_RATE_UNSET - : getCodecOperatingRateV23(rendererOperatingRate, format, getStreamFormats()); + : getCodecOperatingRateV23(rendererOperatingRate, inputFormat, getStreamFormats()); if (codecOperatingRate <= assumedMinimumCodecOperatingRate) { codecOperatingRate = CODEC_OPERATING_RATE_UNSET; } @@ -795,7 +800,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codec = MediaCodec.createByCodecName(codecName); TraceUtil.endSection(); TraceUtil.beginSection("configureCodec"); - configureCodec(codecInfo, codec, format, crypto, codecOperatingRate); + configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate); TraceUtil.endSection(); TraceUtil.beginSection("startCodec"); codec.start(); @@ -813,13 +818,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer { this.codec = codec; this.codecInfo = codecInfo; this.codecOperatingRate = codecOperatingRate; + codecFormat = inputFormat; codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName); codecNeedsReconfigureWorkaround = codecNeedsReconfigureWorkaround(codecName); - codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); + codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, codecFormat); codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); - codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); + codecNeedsMonoChannelCountWorkaround = + codecNeedsMonoChannelCountWorkaround(codecName, codecFormat); codecNeedsEosPropagation = codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation(); @@ -838,7 +845,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecNeedsAdaptationWorkaroundBuffer = false; shouldSkipAdaptationWorkaroundOutputBuffer = false; shouldSkipOutputBuffer = false; - waitingForFirstSyncFrame = true; + waitingForFirstSyncSample = true; decoderCounters.decoderInitCount++; long elapsed = codecInitializedTimestamp - codecInitializingTimestamp; @@ -939,8 +946,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // For adaptive reconfiguration OMX decoders expect all reconfiguration data to be supplied // at the start of the buffer that also contains the first frame in the new format. if (codecReconfigurationState == RECONFIGURATION_STATE_WRITE_PENDING) { - for (int i = 0; i < format.initializationData.size(); i++) { - byte[] data = format.initializationData.get(i); + for (int i = 0; i < codecFormat.initializationData.size(); i++) { + byte[] data = codecFormat.initializationData.get(i); buffer.data.put(data); } codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING; @@ -990,7 +997,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } return false; } - if (waitingForFirstSyncFrame && !buffer.isKeyFrame()) { + if (waitingForFirstSyncSample && !buffer.isKeyFrame()) { buffer.clear(); if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { // The buffer we just cleared contained reconfiguration data. We need to re-write this @@ -999,7 +1006,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } return true; } - waitingForFirstSyncFrame = false; + waitingForFirstSyncSample = false; boolean bufferEncrypted = buffer.isEncrypted(); waitingForKeys = shouldWaitForKeys(bufferEncrypted); if (waitingForKeys) { @@ -1017,9 +1024,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (buffer.isDecodeOnly()) { decodeOnlyPresentationTimestamps.add(presentationTimeUs); } - if (pendingFormat != null) { - formatQueue.add(presentationTimeUs, pendingFormat); - pendingFormat = null; + if (waitingForFirstSampleInFormat) { + formatQueue.add(presentationTimeUs, inputFormat); + waitingForFirstSampleInFormat = false; } buffer.flip(); @@ -1075,19 +1082,20 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}. */ protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { - Format oldFormat = format; - format = newFormat; - pendingFormat = newFormat; + Format oldFormat = inputFormat; + inputFormat = newFormat; + waitingForFirstSampleInFormat = true; boolean drmInitDataChanged = - !Util.areEqual(format.drmInitData, oldFormat == null ? null : oldFormat.drmInitData); + !Util.areEqual(newFormat.drmInitData, oldFormat == null ? null : oldFormat.drmInitData); if (drmInitDataChanged) { - if (format.drmInitData != null) { + if (newFormat.drmInitData != null) { if (drmSessionManager == null) { throw ExoPlaybackException.createForRenderer( new IllegalStateException("Media requires a DrmSessionManager"), getIndex()); } - pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(), format.drmInitData); + pendingDrmSession = + drmSessionManager.acquireSession(Looper.myLooper(), newFormat.drmInitData); if (pendingDrmSession == drmSession) { drmSessionManager.releaseSession(pendingDrmSession); } @@ -1106,12 +1114,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (pendingDrmSession != drmSession) { drainAndReinitializeCodec(); } else { - switch (canKeepCodec(codec, codecInfo, oldFormat, format)) { + switch (canKeepCodec(codec, codecInfo, codecFormat, newFormat)) { case KEEP_CODEC_RESULT_NO: drainAndReinitializeCodec(); break; case KEEP_CODEC_RESULT_YES_WITH_FLUSH: drainAndFlushCodec(); + codecFormat = newFormat; updateCodecOperatingRate(); break; case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION: @@ -1123,12 +1132,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecNeedsAdaptationWorkaroundBuffer = codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS || (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION - && format.width == oldFormat.width - && format.height == oldFormat.height); + && newFormat.width == codecFormat.width + && newFormat.height == codecFormat.height); + codecFormat = newFormat; updateCodecOperatingRate(); } break; case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION: + codecFormat = newFormat; updateCodecOperatingRate(); break; default: @@ -1197,7 +1208,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Override public boolean isReady() { - return format != null + return inputFormat != null && !waitingForKeys && (isSourceReady() || hasOutputBuffer() @@ -1232,17 +1243,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } /** - * Updates the codec operating rate, and the codec itself if necessary. + * Updates the codec operating rate. * * @throws ExoPlaybackException If an error occurs releasing or initializing a codec. */ private void updateCodecOperatingRate() throws ExoPlaybackException { - if (Util.SDK_INT < 23 || codec == null || codecDrainAction == DRAIN_ACTION_REINITIALIZE) { + if (Util.SDK_INT < 23) { return; } float newCodecOperatingRate = - getCodecOperatingRateV23(rendererOperatingRate, format, getStreamFormats()); + getCodecOperatingRateV23(rendererOperatingRate, codecFormat, getStreamFormats()); if (codecOperatingRate == newCodecOperatingRate) { // No change. } else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) { From 038238430b0a70eb1dcf669d337a6a8d6530423c Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 29 Oct 2018 08:19:54 -0700 Subject: [PATCH 133/832] Re-enable codec re-use Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219130576 --- .../mediacodec/MediaCodecRenderer.java | 16 +++++++++++++--- .../video/MediaCodecVideoRenderer.java | 4 ++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 74c575c1ef..efe8959908 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -536,6 +536,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Override protected void onDisabled() { inputFormat = null; + if (drmSession != null || pendingDrmSession != null) { + // TODO: Do something better with this case. + onReset(); + } else { + flushOrReleaseCodec(); + } + } + + @Override + protected void onReset() { try { releaseCodec(); } finally { @@ -559,14 +569,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { protected void releaseCodec() { availableCodecInfos = null; if (codec != null) { + codecInfo = null; + codecFormat = null; resetInputBuffer(); resetOutputBuffer(); resetCodecBuffers(); - codecHotswapDeadlineMs = C.TIME_UNSET; waitingForKeys = false; + codecHotswapDeadlineMs = C.TIME_UNSET; decodeOnlyPresentationTimestamps.clear(); - codecInfo = null; - codecFormat = null; decoderCounters.decoderReleaseCount++; try { codec.stop(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index a896bc2322..0b17cd6338 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -283,8 +283,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override protected void onEnabled(boolean joining) throws ExoPlaybackException { super.onEnabled(joining); + int oldTunnelingAudioSessionId = tunnelingAudioSessionId; tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET; + if (tunnelingAudioSessionId != oldTunnelingAudioSessionId) { + releaseCodec(); + } eventDispatcher.enabled(decoderCounters); frameReleaseTimeHelper.enable(); } From 069e3cbf7df6c582a88b41c4a2275412491f5530 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 29 Oct 2018 09:22:55 -0700 Subject: [PATCH 134/832] Clean up DefaultTrackSelectorTest ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219139356 --- .../DefaultTrackSelectorTest.java | 734 +++++++++--------- 1 file changed, 376 insertions(+), 358 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 82fff694de..06fdaf9218 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -20,9 +20,6 @@ import static com.google.android.exoplayer2.RendererCapabilities.FORMAT_HANDLED; import static com.google.android.exoplayer2.RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE; import static com.google.android.exoplayer2.RendererConfiguration.DEFAULT; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyVararg; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -87,13 +84,13 @@ public final class DefaultTrackSelectorTest { private static final TrackGroup AUDIO_TRACK_GROUP = new TrackGroup(AUDIO_FORMAT); private static final TrackGroupArray TRACK_GROUPS = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP); - private static final TrackSelection[] TRACK_SELECTIONS = new TrackSelection[] { new FixedTrackSelection(VIDEO_TRACK_GROUP, 0), new FixedTrackSelection(AUDIO_TRACK_GROUP, 0) }; private static final TrackSelection[] TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER = new TrackSelection[] {new FixedTrackSelection(VIDEO_TRACK_GROUP, 0), null}; + private static final Timeline TIMELINE = new FakeTimeline(/* windowCount= */ 1); private static MediaPeriodId periodId; @@ -186,15 +183,13 @@ public final class DefaultTrackSelectorTest { /** Tests that a null override clears a track selection. */ @Test public void testSelectTracksWithNullOverride() throws ExoPlaybackException { - DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - trackSelector.init(invalidationListener, bandwidthMeter); trackSelector.setParameters( trackSelector .buildUponParameters() .setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null)); TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); - assertTrackSelections(result, new TrackSelection[] {null, TRACK_SELECTIONS[1]}); + assertSelections(result, new TrackSelection[] {null, TRACK_SELECTIONS[1]}); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {null, DEFAULT}); } @@ -202,8 +197,6 @@ public final class DefaultTrackSelectorTest { /** Tests that a null override can be cleared. */ @Test public void testSelectTracksWithClearedNullOverride() throws ExoPlaybackException { - DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - trackSelector.init(invalidationListener, bandwidthMeter); trackSelector.setParameters( trackSelector .buildUponParameters() @@ -211,7 +204,7 @@ public final class DefaultTrackSelectorTest { .clearSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP))); TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); - assertTrackSelections(result, TRACK_SELECTIONS); + assertSelections(result, TRACK_SELECTIONS); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {DEFAULT, DEFAULT}); } @@ -219,8 +212,6 @@ public final class DefaultTrackSelectorTest { /** Tests that an override is not applied for a different set of available track groups. */ @Test public void testSelectTracksWithNullOverrideForDifferentTracks() throws ExoPlaybackException { - DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - trackSelector.init(invalidationListener, bandwidthMeter); trackSelector.setParameters( trackSelector .buildUponParameters() @@ -231,7 +222,7 @@ public final class DefaultTrackSelectorTest { new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP), periodId, TIMELINE); - assertTrackSelections(result, TRACK_SELECTIONS); + assertSelections(result, TRACK_SELECTIONS); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {DEFAULT, DEFAULT}); } @@ -239,12 +230,10 @@ public final class DefaultTrackSelectorTest { /** Tests disabling a renderer. */ @Test public void testSelectTracksWithDisabledRenderer() throws ExoPlaybackException { - DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - trackSelector.init(invalidationListener, bandwidthMeter); - trackSelector.setParameters(trackSelector.buildUponParameters().setRendererDisabled(1, true)); + trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setRendererDisabled(1, true)); TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); - assertTrackSelections(result, new TrackSelection[] {TRACK_SELECTIONS[0], null}); + assertSelections(result, new TrackSelection[] {TRACK_SELECTIONS[0], null}); assertThat(new RendererConfiguration[] {DEFAULT, null}) .isEqualTo(result.rendererConfigurations); } @@ -252,8 +241,6 @@ public final class DefaultTrackSelectorTest { /** Tests that a disabled renderer can be enabled again. */ @Test public void testSelectTracksWithClearedDisabledRenderer() throws ExoPlaybackException { - DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - trackSelector.init(invalidationListener, bandwidthMeter); trackSelector.setParameters( trackSelector .buildUponParameters() @@ -261,7 +248,7 @@ public final class DefaultTrackSelectorTest { .setRendererDisabled(1, false)); TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS, periodId, TIMELINE); - assertTrackSelections(result, TRACK_SELECTIONS); + assertSelections(result, TRACK_SELECTIONS); assertThat(new RendererConfiguration[] {DEFAULT, DEFAULT}) .isEqualTo(result.rendererConfigurations); } @@ -269,12 +256,10 @@ public final class DefaultTrackSelectorTest { /** Tests a no-sample renderer is enabled without a track selection by default. */ @Test public void testSelectTracksWithNoSampleRenderer() throws ExoPlaybackException { - DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - trackSelector.init(invalidationListener, bandwidthMeter); TrackSelectorResult result = trackSelector.selectTracks( RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS, periodId, TIMELINE); - assertTrackSelections(result, TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER); + assertSelections(result, TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER); assertThat(new RendererConfiguration[] {DEFAULT, DEFAULT}) .isEqualTo(result.rendererConfigurations); } @@ -282,13 +267,11 @@ public final class DefaultTrackSelectorTest { /** Tests disabling a no-sample renderer. */ @Test public void testSelectTracksWithDisabledNoSampleRenderer() throws ExoPlaybackException { - DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - trackSelector.init(invalidationListener, bandwidthMeter); - trackSelector.setParameters(trackSelector.buildUponParameters().setRendererDisabled(1, true)); + trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setRendererDisabled(1, true)); TrackSelectorResult result = trackSelector.selectTracks( RENDERER_CAPABILITIES_WITH_NO_SAMPLE_RENDERER, TRACK_GROUPS, periodId, TIMELINE); - assertTrackSelections(result, TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER); + assertSelections(result, TRACK_SELECTIONS_WITH_NO_SAMPLE_RENDERER); assertThat(new RendererConfiguration[] {DEFAULT, null}) .isEqualTo(result.rendererConfigurations); } @@ -301,8 +284,7 @@ public final class DefaultTrackSelectorTest { @Test public void testSetParameterWithDefaultParametersDoesNotNotifyInvalidationListener() throws Exception { - trackSelector.init(invalidationListener, /* bandwidthMeter= */ null); - + trackSelector.setParameters(Parameters.DEFAULT); verify(invalidationListener, never()).onTrackSelectionsInvalidated(); } @@ -313,10 +295,8 @@ public final class DefaultTrackSelectorTest { @Test public void testSetParameterWithNonDefaultParameterNotifyInvalidationListener() throws Exception { - Parameters parameters = new ParametersBuilder().setPreferredAudioLanguage("eng").build(); - trackSelector.init(invalidationListener, /* bandwidthMeter= */ null); + Parameters parameters = Parameters.DEFAULT.buildUpon().setPreferredAudioLanguage("eng").build(); trackSelector.setParameters(parameters); - verify(invalidationListener).onTrackSelectionsInvalidated(); } @@ -328,11 +308,9 @@ public final class DefaultTrackSelectorTest { @Test public void testSetParameterWithSameParametersDoesNotNotifyInvalidationListenerAgain() throws Exception { - ParametersBuilder builder = new ParametersBuilder().setPreferredAudioLanguage("eng"); - trackSelector.init(invalidationListener, /* bandwidthMeter= */ null); + ParametersBuilder builder = Parameters.DEFAULT.buildUpon().setPreferredAudioLanguage("eng"); trackSelector.setParameters(builder.build()); trackSelector.setParameters(builder.build()); - verify(invalidationListener, times(1)).onTrackSelectionsInvalidated(); } @@ -342,17 +320,21 @@ public final class DefaultTrackSelectorTest { */ @Test public void testSelectTracksSelectTrackWithSelectionFlag() throws Exception { - Format audioFormat = buildAudioFormat("audio", /* language= */ null, /* selectionFlags= */ 0); + Format audioFormat = + buildAudioFormatWithLanguageAndFlags( + "audio", /* language= */ null, /* selectionFlags= */ 0); Format formatWithSelectionFlag = - buildAudioFormat("audio", /* language= */ null, C.SELECTION_FLAG_DEFAULT); + buildAudioFormatWithLanguageAndFlags( + "audio", /* language= */ null, C.SELECTION_FLAG_DEFAULT); + TrackGroupArray trackGroups = wrapFormats(audioFormat, formatWithSelectionFlag); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(formatWithSelectionFlag, audioFormat), + trackGroups, periodId, TIMELINE); - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(formatWithSelectionFlag); + assertFixedSelection(result.selections.get(0), trackGroups, formatWithSelectionFlag); } /** @@ -362,23 +344,23 @@ public final class DefaultTrackSelectorTest { @Test public void testSelectTracksSelectPreferredAudioLanguage() throws Exception { - trackSelector.setParameters(new ParametersBuilder().setPreferredAudioLanguage("eng").build()); - Format frAudioFormat = Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "fra"); Format enAudioFormat = Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "eng"); + TrackGroupArray trackGroups = wrapFormats(frAudioFormat, enAudioFormat); + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setPreferredAudioLanguage("eng").build()); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, wrapFormats(frAudioFormat, enAudioFormat), periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(enAudioFormat); + assertFixedSelection(result.selections.get(0), trackGroups, enAudioFormat); } /** @@ -388,23 +370,23 @@ public final class DefaultTrackSelectorTest { @Test public void testSelectTracksSelectPreferredAudioLanguageOverSelectionFlag() throws Exception { - trackSelector.setParameters(new ParametersBuilder().setPreferredAudioLanguage("eng").build()); - Format frAudioFormat = Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, C.SELECTION_FLAG_DEFAULT, "fra"); Format enAudioFormat = Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "eng"); + TrackGroupArray trackGroups = wrapFormats(frAudioFormat, enAudioFormat); + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setPreferredAudioLanguage("eng").build()); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - wrapFormats(frAudioFormat, enAudioFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(enAudioFormat); + assertFixedSelection(result.selections.get(0), trackGroups, enAudioFormat); } /** @@ -415,6 +397,7 @@ public final class DefaultTrackSelectorTest { public void testSelectTracksPreferTrackWithinCapabilities() throws Exception { Format supportedFormat = buildAudioFormat("supportedFormat"); Format exceededFormat = buildAudioFormat("exceededFormat"); + TrackGroupArray trackGroups = wrapFormats(exceededFormat, supportedFormat); Map mappedCapabilities = new HashMap<>(); mappedCapabilities.put(supportedFormat.id, FORMAT_HANDLED); @@ -425,11 +408,10 @@ public final class DefaultTrackSelectorTest { TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {mappedAudioRendererCapabilities}, - singleTrackGroup(exceededFormat, supportedFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFormat); + assertFixedSelection(result.selections.get(0), trackGroups, supportedFormat); } /** @@ -442,14 +424,15 @@ public final class DefaultTrackSelectorTest { Format audioFormat = Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); + TrackGroupArray trackGroups = singleTrackGroup(audioFormat); + TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(audioFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(audioFormat); + assertFixedSelection(result.selections.get(0), trackGroups, audioFormat); } /** @@ -460,20 +443,20 @@ public final class DefaultTrackSelectorTest { @Test public void testSelectTracksWithNoTrackWithinCapabilitiesAndSetByParamsReturnNoSelection() throws Exception { - trackSelector.setParameters( - new ParametersBuilder().setExceedRendererCapabilitiesIfNecessary(false).build()); - Format audioFormat = Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); + TrackGroupArray trackGroups = singleTrackGroup(audioFormat); + + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setExceedRendererCapabilitiesIfNecessary(false).build()); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(audioFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0)).isNull(); + assertNoSelection(result.selections.get(0)); } /** @@ -483,12 +466,23 @@ public final class DefaultTrackSelectorTest { @Test public void testSelectTracksPreferTrackWithinCapabilitiesOverSelectionFlag() throws Exception { + Format exceededWithSelectionFlagFormat = + Format.createAudioSampleFormat( + "exceededFormat", + MimeTypes.AUDIO_AAC, + null, + Format.NO_VALUE, + Format.NO_VALUE, + 2, + 44100, + null, + null, + C.SELECTION_FLAG_DEFAULT, + null); Format supportedFormat = Format.createAudioSampleFormat("supportedFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); - Format exceededWithSelectionFlagFormat = - Format.createAudioSampleFormat("exceededFormat", MimeTypes.AUDIO_AAC, null, - Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, C.SELECTION_FLAG_DEFAULT, null); + TrackGroupArray trackGroups = wrapFormats(exceededWithSelectionFlagFormat, supportedFormat); Map mappedCapabilities = new HashMap<>(); mappedCapabilities.put(supportedFormat.id, FORMAT_HANDLED); @@ -499,11 +493,10 @@ public final class DefaultTrackSelectorTest { TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {mappedAudioRendererCapabilities}, - singleTrackGroup(exceededWithSelectionFlagFormat, supportedFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFormat); + assertFixedSelection(result.selections.get(0), trackGroups, supportedFormat); } /** @@ -514,14 +507,23 @@ public final class DefaultTrackSelectorTest { @Test public void testSelectTracksPreferTrackWithinCapabilitiesOverPreferredLanguage() throws Exception { - trackSelector.setParameters(new ParametersBuilder().setPreferredAudioLanguage("eng").build()); - + Format exceededEnFormat = + Format.createAudioSampleFormat( + "exceededFormat", + MimeTypes.AUDIO_AAC, + null, + Format.NO_VALUE, + Format.NO_VALUE, + 2, + 44100, + null, + null, + 0, + "eng"); Format supportedFrFormat = Format.createAudioSampleFormat("supportedFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "fra"); - Format exceededEnFormat = - Format.createAudioSampleFormat("exceededFormat", MimeTypes.AUDIO_AAC, null, - Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "eng"); + TrackGroupArray trackGroups = wrapFormats(exceededEnFormat, supportedFrFormat); Map mappedCapabilities = new HashMap<>(); mappedCapabilities.put(exceededEnFormat.id, FORMAT_EXCEEDS_CAPABILITIES); @@ -529,14 +531,15 @@ public final class DefaultTrackSelectorTest { RendererCapabilities mappedAudioRendererCapabilities = new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities); + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setPreferredAudioLanguage("eng").build()); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {mappedAudioRendererCapabilities}, - singleTrackGroup(exceededEnFormat, supportedFrFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFrFormat); + assertFixedSelection(result.selections.get(0), trackGroups, supportedFrFormat); } /** @@ -547,14 +550,23 @@ public final class DefaultTrackSelectorTest { @Test public void testSelectTracksPreferTrackWithinCapabilitiesOverSelectionFlagAndPreferredLanguage() throws Exception { - trackSelector.setParameters(new ParametersBuilder().setPreferredAudioLanguage("eng").build()); - + Format exceededDefaultSelectionEnFormat = + Format.createAudioSampleFormat( + "exceededFormat", + MimeTypes.AUDIO_AAC, + null, + Format.NO_VALUE, + Format.NO_VALUE, + 2, + 44100, + null, + null, + C.SELECTION_FLAG_DEFAULT, + "eng"); Format supportedFrFormat = Format.createAudioSampleFormat("supportedFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "fra"); - Format exceededDefaultSelectionEnFormat = - Format.createAudioSampleFormat("exceededFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, - Format.NO_VALUE, 2, 44100, null, null, C.SELECTION_FLAG_DEFAULT, "eng"); + TrackGroupArray trackGroups = wrapFormats(exceededDefaultSelectionEnFormat, supportedFrFormat); Map mappedCapabilities = new HashMap<>(); mappedCapabilities.put(exceededDefaultSelectionEnFormat.id, FORMAT_EXCEEDS_CAPABILITIES); @@ -562,14 +574,15 @@ public final class DefaultTrackSelectorTest { RendererCapabilities mappedAudioRendererCapabilities = new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities); + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setPreferredAudioLanguage("eng").build()); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {mappedAudioRendererCapabilities}, - singleTrackGroup(exceededDefaultSelectionEnFormat, supportedFrFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFrFormat); + assertFixedSelection(result.selections.get(0), trackGroups, supportedFrFormat); } /** @@ -579,21 +592,31 @@ public final class DefaultTrackSelectorTest { @Test public void testSelectTracksWithinCapabilitiesSelectHigherNumChannel() throws Exception { + Format higherChannelFormat = + Format.createAudioSampleFormat( + "audioFormat", + MimeTypes.AUDIO_AAC, + null, + Format.NO_VALUE, + Format.NO_VALUE, + 6, + 44100, + null, + null, + 0, + null); Format lowerChannelFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); - Format higherChannelFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, - Format.NO_VALUE, 6, 44100, null, null, 0, null); + TrackGroupArray trackGroups = wrapFormats(higherChannelFormat, lowerChannelFormat); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherChannelFormat, lowerChannelFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherChannelFormat); + assertFixedSelection(result.selections.get(0), trackGroups, higherChannelFormat); } /** @@ -609,39 +632,58 @@ public final class DefaultTrackSelectorTest { Format lowerSampleRateFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 22050, null, null, 0, null); + TrackGroupArray trackGroups = wrapFormats(higherSampleRateFormat, lowerSampleRateFormat); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherSampleRateFormat, lowerSampleRateFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherSampleRateFormat); + assertFixedSelection(result.selections.get(0), trackGroups, higherSampleRateFormat); } /** - * Tests that track selector will select audio tracks with higher bit-rate when other factors - * are the same, and tracks are within renderer's capabilities. + * Tests that track selector will select audio tracks with higher bit-rate when other factors are + * the same, and tracks are within renderer's capabilities. */ @Test - public void testSelectTracksWithinCapabilitiesSelectHigherBitrate() - throws Exception { + public void testSelectTracksWithinCapabilitiesSelectHigherBitrate() throws Exception { Format lowerBitrateFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000, - Format.NO_VALUE, 2, 44100, null, null, 0, null); + Format.createAudioSampleFormat( + "audioFormat", + MimeTypes.AUDIO_AAC, + null, + 15000, + Format.NO_VALUE, + 2, + 44100, + null, + null, + 0, + null); Format higherBitrateFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, - Format.NO_VALUE, 2, 44100, null, null, 0, null); + Format.createAudioSampleFormat( + "audioFormat", + MimeTypes.AUDIO_AAC, + null, + 30000, + Format.NO_VALUE, + 2, + 44100, + null, + null, + 0, + null); + TrackGroupArray trackGroups = wrapFormats(lowerBitrateFormat, higherBitrateFormat); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherBitrateFormat); + assertFixedSelection(result.selections.get(0), trackGroups, higherBitrateFormat); } /** @@ -652,23 +694,32 @@ public final class DefaultTrackSelectorTest { @Test public void testSelectTracksPreferHigherNumChannelBeforeSampleRate() throws Exception { + Format higherChannelLowerSampleRateFormat = + Format.createAudioSampleFormat( + "audioFormat", + MimeTypes.AUDIO_AAC, + null, + Format.NO_VALUE, + Format.NO_VALUE, + 6, + 22050, + null, + null, + 0, + null); Format lowerChannelHigherSampleRateFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); - Format higherChannelLowerSampleRateFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, - Format.NO_VALUE, 6, 22050, null, null, 0, null); + TrackGroupArray trackGroups = + wrapFormats(higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup( - higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()) - .isEqualTo(higherChannelLowerSampleRateFormat); + assertFixedSelection(result.selections.get(0), trackGroups, higherChannelLowerSampleRateFormat); } /** @@ -685,17 +736,16 @@ public final class DefaultTrackSelectorTest { Format lowerSampleRateHigherBitrateFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 22050, null, null, 0, null); + TrackGroupArray trackGroups = + wrapFormats(higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup( - higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()) - .isEqualTo(higherSampleRateLowerBitrateFormat); + assertFixedSelection(result.selections.get(0), trackGroups, higherSampleRateLowerBitrateFormat); } /** @@ -705,21 +755,31 @@ public final class DefaultTrackSelectorTest { @Test public void testSelectTracksExceedingCapabilitiesSelectLowerNumChannel() throws Exception { + Format higherChannelFormat = + Format.createAudioSampleFormat( + "audioFormat", + MimeTypes.AUDIO_AAC, + null, + Format.NO_VALUE, + Format.NO_VALUE, + 6, + 44100, + null, + null, + 0, + null); Format lowerChannelFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); - Format higherChannelFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, - Format.NO_VALUE, 6, 44100, null, null, 0, null); + TrackGroupArray trackGroups = wrapFormats(higherChannelFormat, lowerChannelFormat); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherChannelFormat, lowerChannelFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerChannelFormat); + assertFixedSelection(result.selections.get(0), trackGroups, lowerChannelFormat); } /** @@ -735,15 +795,15 @@ public final class DefaultTrackSelectorTest { Format higherSampleRateFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null); + TrackGroupArray trackGroups = wrapFormats(higherSampleRateFormat, lowerSampleRateFormat); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(higherSampleRateFormat, lowerSampleRateFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerSampleRateFormat); + assertFixedSelection(result.selections.get(0), trackGroups, lowerSampleRateFormat); } /** @@ -759,15 +819,15 @@ public final class DefaultTrackSelectorTest { Format higherBitrateFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 44100, null, null, 0, null); + TrackGroupArray trackGroups = wrapFormats(lowerBitrateFormat, higherBitrateFormat); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerBitrateFormat); + assertFixedSelection(result.selections.get(0), trackGroups, lowerBitrateFormat); } /** @@ -784,17 +844,16 @@ public final class DefaultTrackSelectorTest { Format higherChannelLowerSampleRateFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 6, 22050, null, null, 0, null); + TrackGroupArray trackGroups = + wrapFormats(higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup( - higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()) - .isEqualTo(lowerChannelHigherSampleRateFormat); + assertFixedSelection(result.selections.get(0), trackGroups, lowerChannelHigherSampleRateFormat); } /** @@ -811,17 +870,16 @@ public final class DefaultTrackSelectorTest { Format lowerSampleRateHigherBitrateFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 22050, null, null, 0, null); + TrackGroupArray trackGroups = + wrapFormats(higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES}, - singleTrackGroup( - higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()) - .isEqualTo(lowerSampleRateHigherBitrateFormat); + assertFixedSelection(result.selections.get(0), trackGroups, lowerSampleRateHigherBitrateFormat); } /** Tests text track selection flags. */ @@ -838,105 +896,71 @@ public final class DefaultTrackSelectorTest { new RendererCapabilities[] {ALL_TEXT_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}; // There is no text language preference, the first track flagged as default should be selected. + TrackGroupArray trackGroups = wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag); TrackSelectorResult result = - trackSelector.selectTracks( - textRendererCapabilities, - wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag), - periodId, - TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedDefault); + trackSelector.selectTracks(textRendererCapabilities, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, forcedDefault); // Ditto. - result = - trackSelector.selectTracks( - textRendererCapabilities, - wrapFormats(forcedOnly, noFlag, defaultOnly), - periodId, - TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(defaultOnly); + trackGroups = wrapFormats(forcedOnly, noFlag, defaultOnly); + result = trackSelector.selectTracks(textRendererCapabilities, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, defaultOnly); // With no language preference and no text track flagged as default, the first forced should be // selected. - result = - trackSelector.selectTracks( - textRendererCapabilities, wrapFormats(forcedOnly, noFlag), periodId, TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedOnly); + trackGroups = wrapFormats(forcedOnly, noFlag); + result = trackSelector.selectTracks(textRendererCapabilities, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, forcedOnly); + // Default flags are disabled, so the first track flagged as forced should be selected. + trackGroups = wrapFormats(defaultOnly, noFlag, forcedOnly, forcedDefault); trackSelector.setParameters( Parameters.DEFAULT .buildUpon() .setDisabledTextTrackSelectionFlags(C.SELECTION_FLAG_DEFAULT) .build()); - - // Default flags are disabled, so the first track flagged as forced should be selected. - result = - trackSelector.selectTracks( - textRendererCapabilities, - wrapFormats(defaultOnly, noFlag, forcedOnly, forcedDefault), - periodId, - TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedOnly); - - trackSelector.setParameters( - trackSelector.getParameters().buildUpon().setPreferredAudioLanguage("spa").build()); + result = trackSelector.selectTracks(textRendererCapabilities, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, forcedOnly); // Default flags are disabled, but there is a text track flagged as forced whose language // matches the preferred audio language. - result = - trackSelector.selectTracks( - textRendererCapabilities, - wrapFormats(forcedDefault, forcedOnly, defaultOnly, noFlag, forcedOnlySpanish), - periodId, - TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedOnlySpanish); + trackGroups = wrapFormats(forcedDefault, forcedOnly, defaultOnly, noFlag, forcedOnlySpanish); + trackSelector.setParameters( + trackSelector.getParameters().buildUpon().setPreferredAudioLanguage("spa").build()); + result = trackSelector.selectTracks(textRendererCapabilities, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, forcedOnlySpanish); + // All selection flags are disabled and there is no language preference, so nothing should be + // selected. + trackGroups = wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag); trackSelector.setParameters( trackSelector .getParameters() .buildUpon() .setDisabledTextTrackSelectionFlags(C.SELECTION_FLAG_DEFAULT | C.SELECTION_FLAG_FORCED) .build()); - - // All selection flags are disabled and there is no language preference, so nothing should be - // selected. - result = - trackSelector.selectTracks( - textRendererCapabilities, - wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag), - periodId, - TIMELINE); - assertThat(result.selections.get(0)).isNull(); - - trackSelector.setParameters( - Parameters.DEFAULT.buildUpon().setPreferredTextLanguage("eng").build()); + result = trackSelector.selectTracks(textRendererCapabilities, trackGroups, periodId, TIMELINE); + assertNoSelection(result.selections.get(0)); // There is a preferred language, so the first language-matching track flagged as default should // be selected. - result = - trackSelector.selectTracks( - textRendererCapabilities, - wrapFormats(forcedOnly, forcedDefault, defaultOnly, noFlag), - periodId, - TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(forcedDefault); + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setPreferredTextLanguage("eng").build()); + result = trackSelector.selectTracks(textRendererCapabilities, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, forcedDefault); + // Same as above, but the default flag is disabled. If multiple tracks match the preferred + // language, those not flagged as forced are preferred, as they likely include the contents of + // forced subtitles. + trackGroups = wrapFormats(noFlag, forcedOnly, forcedDefault, defaultOnly); trackSelector.setParameters( trackSelector .getParameters() .buildUpon() .setDisabledTextTrackSelectionFlags(C.SELECTION_FLAG_DEFAULT) .build()); - - // Same as above, but the default flag is disabled. If multiple tracks match the preferred - // language, those not flagged as forced are preferred, as they likely include the contents of - // forced subtitles. - result = - trackSelector.selectTracks( - textRendererCapabilities, - wrapFormats(noFlag, forcedOnly, forcedDefault, defaultOnly), - periodId, - TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(noFlag); + result = trackSelector.selectTracks(textRendererCapabilities, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, noFlag); } /** @@ -954,60 +978,37 @@ public final class DefaultTrackSelectorTest { RendererCapabilities[] textRendererCapabilites = new RendererCapabilities[] {ALL_TEXT_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}; + TrackGroupArray trackGroups = wrapFormats(spanish, german, undeterminedUnd, undeterminedNull); TrackSelectorResult result = - trackSelector.selectTracks( - textRendererCapabilites, - wrapFormats(spanish, german, undeterminedUnd, undeterminedNull), - periodId, - TIMELINE); - assertThat(result.selections.get(0)).isNull(); + trackSelector.selectTracks(textRendererCapabilites, trackGroups, periodId, TIMELINE); + assertNoSelection(result.selections.get(0)); trackSelector.setParameters( - new ParametersBuilder().setSelectUndeterminedTextLanguage(true).build()); - result = - trackSelector.selectTracks( - textRendererCapabilites, - wrapFormats(spanish, german, undeterminedUnd, undeterminedNull), - periodId, - TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(undeterminedUnd); + Parameters.DEFAULT.buildUpon().setSelectUndeterminedTextLanguage(true).build()); + result = trackSelector.selectTracks(textRendererCapabilites, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, undeterminedUnd); - ParametersBuilder builder = new ParametersBuilder().setPreferredTextLanguage("spa"); + ParametersBuilder builder = Parameters.DEFAULT.buildUpon().setPreferredTextLanguage("spa"); trackSelector.setParameters(builder.build()); - result = - trackSelector.selectTracks( - textRendererCapabilites, - wrapFormats(spanish, german, undeterminedUnd, undeterminedNull), - periodId, - TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(spanish); + result = trackSelector.selectTracks(textRendererCapabilites, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, spanish); - result = - trackSelector.selectTracks( - textRendererCapabilites, - wrapFormats(german, undeterminedUnd, undeterminedNull), - periodId, - TIMELINE); - assertThat(result.selections.get(0)).isNull(); + trackGroups = wrapFormats(german, undeterminedUnd, undeterminedNull); + + result = trackSelector.selectTracks(textRendererCapabilites, trackGroups, periodId, TIMELINE); + assertNoSelection(result.selections.get(0)); trackSelector.setParameters(builder.setSelectUndeterminedTextLanguage(true).build()); - result = - trackSelector.selectTracks( - textRendererCapabilites, - wrapFormats(german, undeterminedUnd, undeterminedNull), - periodId, - TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(undeterminedUnd); + result = trackSelector.selectTracks(textRendererCapabilites, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, undeterminedUnd); - result = - trackSelector.selectTracks( - textRendererCapabilites, wrapFormats(german, undeterminedNull), periodId, TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(undeterminedNull); + trackGroups = wrapFormats(german, undeterminedNull); + result = trackSelector.selectTracks(textRendererCapabilites, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, undeterminedNull); - result = - trackSelector.selectTracks( - textRendererCapabilites, wrapFormats(german), periodId, TIMELINE); - assertThat(result.selections.get(0)).isNull(); + trackGroups = wrapFormats(german); + result = trackSelector.selectTracks(textRendererCapabilites, trackGroups, periodId, TIMELINE); + assertNoSelection(result.selections.get(0)); } /** Tests audio track selection when there are multiple audio renderers. */ @@ -1032,29 +1033,25 @@ public final class DefaultTrackSelectorTest { RendererCapabilities[] rendererCapabilities = new RendererCapabilities[] {firstRendererCapabilities, secondRendererCapabilities}; + TrackGroupArray trackGroups = wrapFormats(english, german); // Without an explicit language preference, nothing should be selected. TrackSelectorResult result = - trackSelector.selectTracks( - rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); - assertThat(result.selections.get(0)).isNull(); - assertThat(result.selections.get(1)).isNull(); + trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE); + assertNoSelection(result.selections.get(0)); + assertNoSelection(result.selections.get(1)); // Explicit language preference for english. First renderer should be used. - trackSelector.setParameters(trackSelector.buildUponParameters().setPreferredTextLanguage("en")); - result = - trackSelector.selectTracks( - rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(english); - assertThat(result.selections.get(1)).isNull(); + trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setPreferredTextLanguage("en")); + result = trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, english); + assertNoSelection(result.selections.get(1)); // Explicit language preference for German. Second renderer should be used. - trackSelector.setParameters(trackSelector.buildUponParameters().setPreferredTextLanguage("de")); - result = - trackSelector.selectTracks( - rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); - assertThat(result.selections.get(0)).isNull(); - assertThat(result.selections.get(1).getFormat(0)).isSameAs(german); + trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setPreferredTextLanguage("de")); + result = trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE); + assertNoSelection(result.selections.get(0)); + assertFixedSelection(result.selections.get(1), trackGroups, german); } /** @@ -1064,81 +1061,60 @@ public final class DefaultTrackSelectorTest { @Test public void testSelectTracksWithinCapabilitiesAndForceLowestBitrateSelectLowerBitrate() throws Exception { - trackSelector.setParameters(new ParametersBuilder().setForceLowestBitrate(true).build()); - Format lowerBitrateFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000, Format.NO_VALUE, 2, 44100, null, null, 0, null); Format higherBitrateFormat = Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, Format.NO_VALUE, 2, 44100, null, null, 0, null); + TrackGroupArray trackGroups = wrapFormats(lowerBitrateFormat, higherBitrateFormat); + trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setForceLowestBitrate(true).build()); TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, - singleTrackGroup(lowerBitrateFormat, higherBitrateFormat), + trackGroups, periodId, TIMELINE); - - assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerBitrateFormat); + assertFixedSelection(result.selections.get(0), trackGroups, lowerBitrateFormat); } @Test - public void testSelectTracksWithMultipleAudioTracksReturnsAdaptiveTrackSelection() - throws Exception { - TrackSelection adaptiveTrackSelection = mock(TrackSelection.class); - TrackSelection.Factory adaptiveTrackSelectionFactory = mock(TrackSelection.Factory.class); - when(adaptiveTrackSelectionFactory.createTrackSelection(any(), any(), anyVararg())) - .thenReturn(adaptiveTrackSelection); - - trackSelector = new DefaultTrackSelector(adaptiveTrackSelectionFactory); - trackSelector.init(invalidationListener, bandwidthMeter); - - TrackGroupArray trackGroupArray = singleTrackGroup(AUDIO_FORMAT, AUDIO_FORMAT); + public void testSelectTracksWithMultipleAudioTracks() throws Exception { + TrackGroupArray trackGroups = singleTrackGroup(buildAudioFormat("0"), buildAudioFormat("1")); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroupArray, periodId, TIMELINE); + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); - assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); - verify(adaptiveTrackSelectionFactory) - .createTrackSelection(trackGroupArray.get(0), bandwidthMeter, 0, 1); + assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 1); } @Test public void testSelectTracksWithMultipleAudioTracksOverrideReturnsAdaptiveTrackSelection() throws Exception { - TrackSelection adaptiveTrackSelection = mock(TrackSelection.class); - TrackSelection.Factory adaptiveTrackSelectionFactory = mock(TrackSelection.Factory.class); - when(adaptiveTrackSelectionFactory.createTrackSelection(any(), any(), anyVararg())) - .thenReturn(adaptiveTrackSelection); - - trackSelector = new DefaultTrackSelector(adaptiveTrackSelectionFactory); - trackSelector.init(invalidationListener, bandwidthMeter); - - TrackGroupArray trackGroupArray = singleTrackGroup(AUDIO_FORMAT, AUDIO_FORMAT, AUDIO_FORMAT); + TrackGroupArray trackGroups = + singleTrackGroup(buildAudioFormat("0"), buildAudioFormat("1"), buildAudioFormat("2")); trackSelector.setParameters( trackSelector .buildUponParameters() .setSelectionOverride( /* rendererIndex= */ 0, - trackGroupArray, + trackGroups, new SelectionOverride(/* groupIndex= */ 0, /* tracks= */ 1, 2))); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroupArray, periodId, TIMELINE); + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); - assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); - verify(adaptiveTrackSelectionFactory) - .createTrackSelection(trackGroupArray.get(0), bandwidthMeter, 1, 2); + assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 1, 2); } /** Tests audio track selection when there are multiple audio renderers. */ @Test public void testSelectPreferredAudioTrackMultipleRenderers() throws Exception { - Format english = buildAudioFormat("en", "en"); - Format german = buildAudioFormat("de", "de"); + Format english = buildAudioFormatWithLanguage("en", "en"); + Format german = buildAudioFormatWithLanguage("de", "de"); // First renderer handles english. Map firstRendererMappedCapabilities = new HashMap<>(); @@ -1158,89 +1134,104 @@ public final class DefaultTrackSelectorTest { new RendererCapabilities[] {firstRendererCapabilities, secondRendererCapabilities}; // Without an explicit language preference, prefer the first renderer. + TrackGroupArray trackGroups = wrapFormats(english, german); TrackSelectorResult result = - trackSelector.selectTracks( - rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(english); - assertThat(result.selections.get(1)).isNull(); + trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, english); + assertNoSelection(result.selections.get(1)); // Explicit language preference for english. First renderer should be used. - trackSelector.setParameters( - trackSelector.buildUponParameters().setPreferredAudioLanguage("en")); - result = - trackSelector.selectTracks( - rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); - assertThat(result.selections.get(0).getFormat(0)).isSameAs(english); - assertThat(result.selections.get(1)).isNull(); + trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setPreferredAudioLanguage("en")); + result = trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, english); + assertNoSelection(result.selections.get(1)); // Explicit language preference for German. Second renderer should be used. - trackSelector.setParameters( - trackSelector.buildUponParameters().setPreferredAudioLanguage("de")); - result = - trackSelector.selectTracks( - rendererCapabilities, wrapFormats(english, german), periodId, TIMELINE); - assertThat(result.selections.get(0)).isNull(); - assertThat(result.selections.get(1).getFormat(0)).isSameAs(german); + trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setPreferredAudioLanguage("de")); + result = trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE); + assertNoSelection(result.selections.get(0)); + assertFixedSelection(result.selections.get(1), trackGroups, german); } @Test - public void testSelectTracksWithMultipleVideoTracksReturnsAdaptiveTrackSelection() - throws Exception { - TrackSelection adaptiveTrackSelection = mock(TrackSelection.class); - TrackSelection.Factory adaptiveTrackSelectionFactory = mock(TrackSelection.Factory.class); - when(adaptiveTrackSelectionFactory.createTrackSelection(any(), any(), anyVararg())) - .thenReturn(adaptiveTrackSelection); - - trackSelector = new DefaultTrackSelector(adaptiveTrackSelectionFactory); - trackSelector.init(invalidationListener, bandwidthMeter); - - TrackGroupArray trackGroupArray = singleTrackGroup(VIDEO_FORMAT, VIDEO_FORMAT); + public void testSelectTracksWithMultipleVideoTracks() throws Exception { + TrackGroupArray trackGroups = singleTrackGroup(buildVideoFormat("0"), buildVideoFormat("1")); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroupArray, periodId, TIMELINE); + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); - assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); - verify(adaptiveTrackSelectionFactory) - .createTrackSelection(trackGroupArray.get(0), bandwidthMeter, 0, 1); + assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 1); } @Test public void testSelectTracksWithMultipleVideoTracksOverrideReturnsAdaptiveTrackSelection() throws Exception { - TrackSelection adaptiveTrackSelection = mock(TrackSelection.class); - TrackSelection.Factory adaptiveTrackSelectionFactory = mock(TrackSelection.Factory.class); - when(adaptiveTrackSelectionFactory.createTrackSelection(any(), any(), anyVararg())) - .thenReturn(adaptiveTrackSelection); - - trackSelector = new DefaultTrackSelector(adaptiveTrackSelectionFactory); - trackSelector.init(invalidationListener, bandwidthMeter); - - TrackGroupArray trackGroupArray = singleTrackGroup(VIDEO_FORMAT, VIDEO_FORMAT, VIDEO_FORMAT); + TrackGroupArray trackGroups = + singleTrackGroup(buildVideoFormat("0"), buildVideoFormat("1"), buildVideoFormat("2")); trackSelector.setParameters( trackSelector .buildUponParameters() .setSelectionOverride( /* rendererIndex= */ 0, - trackGroupArray, + trackGroups, new SelectionOverride(/* groupIndex= */ 0, /* tracks= */ 1, 2))); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroupArray, periodId, TIMELINE); + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); assertThat(result.length).isEqualTo(1); - assertThat(result.selections.get(0)).isEqualTo(adaptiveTrackSelection); - verify(adaptiveTrackSelectionFactory) - .createTrackSelection(trackGroupArray.get(0), bandwidthMeter, 1, 2); + assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 1, 2); } - private static void assertTrackSelections(TrackSelectorResult result, TrackSelection[] expected) { + private static void assertSelections(TrackSelectorResult result, TrackSelection[] expected) { assertThat(result.length).isEqualTo(expected.length); for (int i = 0; i < expected.length; i++) { assertThat(result.selections.get(i)).isEqualTo(expected[i]); } } + private static void assertFixedSelection( + TrackSelection selection, TrackGroupArray trackGroups, Format expectedFormat) { + int trackGroupIndex = -1; + for (int i = 0; i < trackGroups.length; i++) { + int expectedTrack = trackGroups.get(i).indexOf(expectedFormat); + if (expectedTrack != -1) { + assertThat(trackGroupIndex).isEqualTo(-1); + assertFixedSelection(selection, trackGroups.get(i), expectedTrack); + trackGroupIndex = i; + } + } + // Assert that we found the expected format in a track group + assertThat(trackGroupIndex).isNotEqualTo(-1); + } + + private static void assertFixedSelection( + TrackSelection selection, TrackGroup expectedTrackGroup, int expectedTrack) { + assertThat(selection).isInstanceOf(FixedTrackSelection.class); + assertThat(selection.getTrackGroup()).isEqualTo(expectedTrackGroup); + assertThat(selection.length()).isEqualTo(1); + assertThat(selection.getIndexInTrackGroup(0)).isEqualTo(expectedTrack); + assertThat(selection.getFormat(0)) + .isSameAs(expectedTrackGroup.getFormat(selection.getIndexInTrackGroup(0))); + } + + private static void assertNoSelection(TrackSelection selection) { + assertThat(selection).isNull(); + } + + private static void assertAdaptiveSelection( + TrackSelection selection, TrackGroup expectedTrackGroup, int... expectedTracks) { + assertThat(selection).isInstanceOf(AdaptiveTrackSelection.class); + assertThat(selection.getTrackGroup()).isEqualTo(expectedTrackGroup); + assertThat(selection.length()).isEqualTo(expectedTracks.length); + for (int i = 0; i < expectedTracks.length; i++) { + assertThat(selection.getIndexInTrackGroup(i)).isEqualTo(expectedTracks[i]); + assertThat(selection.getFormat(i)) + .isSameAs(expectedTrackGroup.getFormat(selection.getIndexInTrackGroup(i))); + } + } + private static TrackGroupArray singleTrackGroup(Format... formats) { return new TrackGroupArray(new TrackGroup(formats)); } @@ -1253,10 +1244,10 @@ public final class DefaultTrackSelectorTest { return new TrackGroupArray(trackGroups); } - private static Format buildVideoFormat(String id) { + private static Format buildVideoFormatWithMimeType(String id, String mimeType) { return Format.createVideoSampleFormat( id, - MimeTypes.VIDEO_H264, + mimeType, null, Format.NO_VALUE, Format.NO_VALUE, @@ -1267,23 +1258,50 @@ public final class DefaultTrackSelectorTest { null); } - private static Format buildAudioFormat(String id) { - return buildAudioFormat(id, /* language= */ null); + private static Format buildVideoFormat(String id) { + return buildVideoFormatWithMimeType(id, MimeTypes.VIDEO_H264); } - private static Format buildAudioFormat(String id, String language) { - return buildAudioFormat(id, language, /* selectionFlags= */ 0); + private static Format buildAudioFormatWithLanguage(String id, String language) { + return buildAudioFormatWithLanguageAndFlags(id, language, /* selectionFlags= */ 0); } - private static Format buildAudioFormat(String id, String language, int selectionFlags) { - return Format.createAudioSampleFormat( + private static Format buildAudioFormatWithLanguageAndFlags( + String id, String language, int selectionFlags) { + return buildAudioFormat( id, MimeTypes.AUDIO_AAC, + language, + selectionFlags, + /* channelCount= */ 2, + /* sampleRate= */ 44100); + } + + private static Format buildAudioFormat(String id) { + return buildAudioFormat( + id, + MimeTypes.AUDIO_AAC, + /* language= */ null, + /* selectionFlags= */ 0, + /* channelCount= */ 2, + /* sampleRate= */ 44100); + } + + private static Format buildAudioFormat( + String id, + String mimeType, + String language, + int selectionFlags, + int channelCount, + int sampleRate) { + return Format.createAudioSampleFormat( + id, + mimeType, /* codecs= */ null, /* bitrate= */ Format.NO_VALUE, /* maxInputSize= */ Format.NO_VALUE, - /* channelCount= */ 2, - /* sampleRate= */ 44100, + channelCount, + sampleRate, /* initializationData= */ null, /* drmInitData= */ null, selectionFlags, From 7eeeb40d24d179a074e4a69f802ff6a6bff58b09 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 29 Oct 2018 11:08:22 -0700 Subject: [PATCH 135/832] Add options for controlling audio track selection Issue: #3314 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219158729 --- RELEASENOTES.md | 2 + .../trackselection/DefaultTrackSelector.java | 349 ++++++++++++++---- .../DefaultTrackSelectorTest.java | 245 +++++++++++- 3 files changed, 519 insertions(+), 77 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 12e9dbb28d..4ea4610b84 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,6 +6,8 @@ here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). * Improve initial bandwidth meter estimates using the current country and network type. +* Add options for controlling audio track selections to `DefaultTrackSelector` + ([#3314](https://github.com/google/ExoPlayer/issues/3314)). * Do not retry failed loads whose error is `FileNotFoundException`. * Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext` and `Player.hasPrevious` diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 435b5f1301..d40afa3acc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -170,11 +170,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { private int maxVideoFrameRate; private int maxVideoBitrate; private boolean exceedVideoConstraintsIfNecessary; + private boolean allowVideoMixedMimeTypeAdaptiveness; + private boolean allowVideoNonSeamlessAdaptiveness; private int viewportWidth; private int viewportHeight; private boolean viewportOrientationMayChange; // Audio @Nullable private String preferredAudioLanguage; + private int maxAudioChannelCount; + private int maxAudioBitrate; + private boolean exceedAudioConstraintsIfNecessary; + private boolean allowAudioMixedMimeTypeAdaptiveness; + private boolean allowAudioMixedSampleRateAdaptiveness; // Text @Nullable private String preferredTextLanguage; private boolean selectUndeterminedTextLanguage; @@ -182,8 +189,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General private boolean forceLowestBitrate; private boolean forceHighestSupportedBitrate; - private boolean allowMixedMimeAdaptiveness; - private boolean allowNonSeamlessAdaptiveness; private boolean exceedRendererCapabilitiesIfNecessary; private int tunnelingAudioSessionId; @@ -203,11 +208,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { maxVideoFrameRate = initialValues.maxVideoFrameRate; maxVideoBitrate = initialValues.maxVideoBitrate; exceedVideoConstraintsIfNecessary = initialValues.exceedVideoConstraintsIfNecessary; + allowVideoMixedMimeTypeAdaptiveness = initialValues.allowVideoMixedMimeTypeAdaptiveness; + allowVideoNonSeamlessAdaptiveness = initialValues.allowVideoNonSeamlessAdaptiveness; viewportWidth = initialValues.viewportWidth; viewportHeight = initialValues.viewportHeight; viewportOrientationMayChange = initialValues.viewportOrientationMayChange; // Audio preferredAudioLanguage = initialValues.preferredAudioLanguage; + maxAudioChannelCount = initialValues.maxAudioChannelCount; + maxAudioBitrate = initialValues.maxAudioBitrate; + exceedAudioConstraintsIfNecessary = initialValues.exceedAudioConstraintsIfNecessary; + allowAudioMixedMimeTypeAdaptiveness = initialValues.allowAudioMixedMimeTypeAdaptiveness; + allowAudioMixedSampleRateAdaptiveness = initialValues.allowAudioMixedSampleRateAdaptiveness; // Text preferredTextLanguage = initialValues.preferredTextLanguage; selectUndeterminedTextLanguage = initialValues.selectUndeterminedTextLanguage; @@ -215,8 +227,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General forceLowestBitrate = initialValues.forceLowestBitrate; forceHighestSupportedBitrate = initialValues.forceHighestSupportedBitrate; - allowMixedMimeAdaptiveness = initialValues.allowMixedMimeAdaptiveness; - allowNonSeamlessAdaptiveness = initialValues.allowNonSeamlessAdaptiveness; exceedRendererCapabilitiesIfNecessary = initialValues.exceedRendererCapabilitiesIfNecessary; tunnelingAudioSessionId = initialValues.tunnelingAudioSessionId; // Overrides @@ -286,6 +296,28 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } + /** + * See {@link Parameters#allowVideoMixedMimeTypeAdaptiveness}. + * + * @return This builder. + */ + public ParametersBuilder setAllowVideoMixedMimeTypeAdaptiveness( + boolean allowVideoMixedMimeTypeAdaptiveness) { + this.allowVideoMixedMimeTypeAdaptiveness = allowVideoMixedMimeTypeAdaptiveness; + return this; + } + + /** + * See {@link Parameters#allowVideoNonSeamlessAdaptiveness}. + * + * @return This builder. + */ + public ParametersBuilder setAllowVideoNonSeamlessAdaptiveness( + boolean allowVideoNonSeamlessAdaptiveness) { + this.allowVideoNonSeamlessAdaptiveness = allowVideoNonSeamlessAdaptiveness; + return this; + } + /** * Equivalent to calling {@link #setViewportSize(int, int, boolean)} with the viewport size * obtained from {@link Util#getPhysicalDisplaySize(Context)}. @@ -340,6 +372,59 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } + /** + * See {@link Parameters#maxAudioChannelCount}. + * + * @return This builder. + */ + public ParametersBuilder setMaxAudioChannelCount(int maxAudioChannelCount) { + this.maxAudioChannelCount = maxAudioChannelCount; + return this; + } + + /** + * See {@link Parameters#maxAudioBitrate}. + * + * @return This builder. + */ + public ParametersBuilder setMaxAudioBitrate(int maxAudioBitrate) { + this.maxAudioBitrate = maxAudioBitrate; + return this; + } + + /** + * See {@link Parameters#exceedAudioConstraintsIfNecessary}. + * + * @return This builder. + */ + public ParametersBuilder setExceedAudioConstraintsIfNecessary( + boolean exceedAudioConstraintsIfNecessary) { + this.exceedAudioConstraintsIfNecessary = exceedAudioConstraintsIfNecessary; + return this; + } + + /** + * See {@link Parameters#allowAudioMixedMimeTypeAdaptiveness}. + * + * @return This builder. + */ + public ParametersBuilder setAllowAudioMixedMimeTypeAdaptiveness( + boolean allowAudioMixedMimeTypeAdaptiveness) { + this.allowAudioMixedMimeTypeAdaptiveness = allowAudioMixedMimeTypeAdaptiveness; + return this; + } + + /** + * See {@link Parameters#allowAudioMixedSampleRateAdaptiveness}. + * + * @return This builder. + */ + public ParametersBuilder setAllowAudioMixedSampleRateAdaptiveness( + boolean allowAudioMixedSampleRateAdaptiveness) { + this.allowAudioMixedSampleRateAdaptiveness = allowAudioMixedSampleRateAdaptiveness; + return this; + } + // Text /** @@ -397,23 +482,20 @@ public class DefaultTrackSelector extends MappingTrackSelector { } /** - * See {@link Parameters#allowMixedMimeAdaptiveness}. - * - * @return This builder. + * @deprecated Use {@link #setAllowVideoMixedMimeTypeAdaptiveness(boolean)} and {@link + * #setAllowAudioMixedMimeTypeAdaptiveness(boolean)}. */ + @Deprecated public ParametersBuilder setAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) { - this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; + setAllowAudioMixedMimeTypeAdaptiveness(allowMixedMimeAdaptiveness); + setAllowVideoMixedMimeTypeAdaptiveness(allowMixedMimeAdaptiveness); return this; } - /** - * See {@link Parameters#allowNonSeamlessAdaptiveness}. - * - * @return This builder. - */ + /** @deprecated Use {@link #setAllowVideoNonSeamlessAdaptiveness(boolean)} */ + @Deprecated public ParametersBuilder setAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) { - this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; - return this; + return setAllowVideoNonSeamlessAdaptiveness(allowNonSeamlessAdaptiveness); } /** @@ -563,11 +645,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { maxVideoFrameRate, maxVideoBitrate, exceedVideoConstraintsIfNecessary, + allowVideoMixedMimeTypeAdaptiveness, + allowVideoNonSeamlessAdaptiveness, viewportWidth, viewportHeight, viewportOrientationMayChange, // Audio preferredAudioLanguage, + maxAudioChannelCount, + maxAudioBitrate, + exceedAudioConstraintsIfNecessary, + allowAudioMixedMimeTypeAdaptiveness, + allowAudioMixedSampleRateAdaptiveness, // Text preferredTextLanguage, selectUndeterminedTextLanguage, @@ -575,8 +664,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General forceLowestBitrate, forceHighestSupportedBitrate, - allowMixedMimeAdaptiveness, - allowNonSeamlessAdaptiveness, exceedRendererCapabilitiesIfNecessary, tunnelingAudioSessionId, // Overrides @@ -638,6 +725,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { * {@code true}. */ public final boolean exceedVideoConstraintsIfNecessary; + /** + * Whether to allow adaptive video selections containing mixed mime types. Adaptations between + * different mime types may not be completely seamless, in which case {@link + * #allowVideoNonSeamlessAdaptiveness} also needs to be {@code true} for mixed mime type + * selections to be made. The default value is {@code false}. + */ + public final boolean allowVideoMixedMimeTypeAdaptiveness; + /** + * Whether to allow adaptive video selections where adaptation may not be completely seamless. + * The default value is {@code true}. + */ + public final boolean allowVideoNonSeamlessAdaptiveness; /** * Viewport width in pixels. Constrains video track selections for adaptive content so that only * tracks suitable for the viewport are selected. The default value is {@link Integer#MAX_VALUE} @@ -664,6 +763,30 @@ public class DefaultTrackSelector extends MappingTrackSelector { * {@code null}. */ @Nullable public final String preferredAudioLanguage; + /** + * Maximum allowed audio channel count. The default value is {@link Integer#MAX_VALUE} (i.e. no + * constraint). + */ + public final int maxAudioChannelCount; + /** + * Maximum audio bitrate. The default value is {@link Integer#MAX_VALUE} (i.e. no constraint). + */ + public final int maxAudioBitrate; + /** + * Whether to exceed the {@link #maxAudioChannelCount} and {@link #maxAudioBitrate} constraints + * when no selection can be made otherwise. The default value is {@code true}. + */ + public final boolean exceedAudioConstraintsIfNecessary; + /** + * Whether to allow adaptive audio selections containing mixed mime types. Adaptations between + * different mime types may not be completely seamless. The default value is {@code false}. + */ + public final boolean allowAudioMixedMimeTypeAdaptiveness; + /** + * Whether to allow adaptive audio selections containing mixed sample rates. Adaptations between + * different sample rates may not be completely seamless. The default value is {@code false}. + */ + public final boolean allowAudioMixedSampleRateAdaptiveness; // Text /** @@ -695,15 +818,12 @@ public class DefaultTrackSelector extends MappingTrackSelector { */ public final boolean forceHighestSupportedBitrate; /** - * Whether to allow adaptive selections containing mixed mime types. The default value is {@code - * false}. + * @deprecated Use {@link #allowVideoMixedMimeTypeAdaptiveness} and {@link + * #allowAudioMixedMimeTypeAdaptiveness}. */ - public final boolean allowMixedMimeAdaptiveness; - /** - * Whether to allow adaptive selections where adaptation may not be completely seamless. The - * default value is {@code true}. - */ - public final boolean allowNonSeamlessAdaptiveness; + @Deprecated public final boolean allowMixedMimeAdaptiveness; + /** @deprecated Use {@link #allowVideoNonSeamlessAdaptiveness}. */ + @Deprecated public final boolean allowNonSeamlessAdaptiveness; /** * Whether to exceed renderer capabilities when no selection can be made otherwise. * @@ -729,11 +849,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { /* maxVideoFrameRate= */ Integer.MAX_VALUE, /* maxVideoBitrate= */ Integer.MAX_VALUE, /* exceedVideoConstraintsIfNecessary= */ true, + /* allowVideoMixedMimeTypeAdaptiveness= */ false, + /* allowVideoNonSeamlessAdaptiveness= */ true, /* viewportWidth= */ Integer.MAX_VALUE, /* viewportHeight= */ Integer.MAX_VALUE, /* viewportOrientationMayChange= */ true, // Audio /* preferredAudioLanguage= */ null, + /* maxAudioChannelCount= */ Integer.MAX_VALUE, + /* maxAudioBitrate= */ Integer.MAX_VALUE, + /* exceedAudioConstraintsIfNecessary= */ true, + /* allowAudioMixedMimeTypeAdaptiveness= */ false, + /* allowAudioMixedSampleRateAdaptiveness= */ false, // Text /* preferredTextLanguage= */ null, /* selectUndeterminedTextLanguage= */ false, @@ -741,8 +868,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General /* forceLowestBitrate= */ false, /* forceHighestSupportedBitrate= */ false, - /* allowMixedMimeAdaptiveness= */ false, - /* allowNonSeamlessAdaptiveness= */ true, /* exceedRendererCapabilitiesIfNecessary= */ true, /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET, // Overrides @@ -757,11 +882,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { int maxVideoFrameRate, int maxVideoBitrate, boolean exceedVideoConstraintsIfNecessary, + boolean allowVideoMixedMimeTypeAdaptiveness, + boolean allowVideoNonSeamlessAdaptiveness, int viewportWidth, int viewportHeight, boolean viewportOrientationMayChange, // Audio @Nullable String preferredAudioLanguage, + int maxAudioChannelCount, + int maxAudioBitrate, + boolean exceedAudioConstraintsIfNecessary, + boolean allowAudioMixedMimeTypeAdaptiveness, + boolean allowAudioMixedSampleRateAdaptiveness, // Text @Nullable String preferredTextLanguage, boolean selectUndeterminedTextLanguage, @@ -769,8 +901,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General boolean forceLowestBitrate, boolean forceHighestSupportedBitrate, - boolean allowMixedMimeAdaptiveness, - boolean allowNonSeamlessAdaptiveness, boolean exceedRendererCapabilitiesIfNecessary, int tunnelingAudioSessionId, // Overrides @@ -782,11 +912,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.maxVideoFrameRate = maxVideoFrameRate; this.maxVideoBitrate = maxVideoBitrate; this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary; + this.allowVideoMixedMimeTypeAdaptiveness = allowVideoMixedMimeTypeAdaptiveness; + this.allowVideoNonSeamlessAdaptiveness = allowVideoNonSeamlessAdaptiveness; this.viewportWidth = viewportWidth; this.viewportHeight = viewportHeight; this.viewportOrientationMayChange = viewportOrientationMayChange; // Audio this.preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); + this.maxAudioChannelCount = maxAudioChannelCount; + this.maxAudioBitrate = maxAudioBitrate; + this.exceedAudioConstraintsIfNecessary = exceedAudioConstraintsIfNecessary; + this.allowAudioMixedMimeTypeAdaptiveness = allowAudioMixedMimeTypeAdaptiveness; + this.allowAudioMixedSampleRateAdaptiveness = allowAudioMixedSampleRateAdaptiveness; // Text this.preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage); this.selectUndeterminedTextLanguage = selectUndeterminedTextLanguage; @@ -794,13 +931,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General this.forceLowestBitrate = forceLowestBitrate; this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; - this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; - this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; this.tunnelingAudioSessionId = tunnelingAudioSessionId; // Overrides this.selectionOverrides = selectionOverrides; this.rendererDisabledFlags = rendererDisabledFlags; + // Deprecated fields. + this.allowMixedMimeAdaptiveness = allowVideoMixedMimeTypeAdaptiveness; + this.allowNonSeamlessAdaptiveness = allowVideoNonSeamlessAdaptiveness; } /* package */ Parameters(Parcel in) { @@ -810,11 +948,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.maxVideoFrameRate = in.readInt(); this.maxVideoBitrate = in.readInt(); this.exceedVideoConstraintsIfNecessary = Util.readBoolean(in); + this.allowVideoMixedMimeTypeAdaptiveness = Util.readBoolean(in); + this.allowVideoNonSeamlessAdaptiveness = Util.readBoolean(in); this.viewportWidth = in.readInt(); this.viewportHeight = in.readInt(); this.viewportOrientationMayChange = Util.readBoolean(in); // Audio this.preferredAudioLanguage = in.readString(); + this.maxAudioChannelCount = in.readInt(); + this.maxAudioBitrate = in.readInt(); + this.exceedAudioConstraintsIfNecessary = Util.readBoolean(in); + this.allowAudioMixedMimeTypeAdaptiveness = Util.readBoolean(in); + this.allowAudioMixedSampleRateAdaptiveness = Util.readBoolean(in); // Text this.preferredTextLanguage = in.readString(); this.selectUndeterminedTextLanguage = Util.readBoolean(in); @@ -822,13 +967,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General this.forceLowestBitrate = Util.readBoolean(in); this.forceHighestSupportedBitrate = Util.readBoolean(in); - this.allowMixedMimeAdaptiveness = Util.readBoolean(in); - this.allowNonSeamlessAdaptiveness = Util.readBoolean(in); this.exceedRendererCapabilitiesIfNecessary = Util.readBoolean(in); this.tunnelingAudioSessionId = in.readInt(); // Overrides this.selectionOverrides = readSelectionOverrides(in); this.rendererDisabledFlags = in.readSparseBooleanArray(); + // Deprecated fields. + this.allowMixedMimeAdaptiveness = allowVideoMixedMimeTypeAdaptiveness; + this.allowNonSeamlessAdaptiveness = allowVideoNonSeamlessAdaptiveness; } /** @@ -887,11 +1033,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { && maxVideoFrameRate == other.maxVideoFrameRate && maxVideoBitrate == other.maxVideoBitrate && exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary + && allowVideoMixedMimeTypeAdaptiveness == other.allowVideoMixedMimeTypeAdaptiveness + && allowVideoNonSeamlessAdaptiveness == other.allowVideoNonSeamlessAdaptiveness && viewportOrientationMayChange == other.viewportOrientationMayChange && viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight // Audio && TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage) + && maxAudioChannelCount == other.maxAudioChannelCount + && maxAudioBitrate == other.maxAudioBitrate + && exceedAudioConstraintsIfNecessary == other.exceedAudioConstraintsIfNecessary + && allowAudioMixedMimeTypeAdaptiveness == other.allowAudioMixedMimeTypeAdaptiveness + && allowAudioMixedSampleRateAdaptiveness == other.allowAudioMixedSampleRateAdaptiveness // Text && TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage) && selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage @@ -899,8 +1052,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General && forceLowestBitrate == other.forceLowestBitrate && forceHighestSupportedBitrate == other.forceHighestSupportedBitrate - && allowMixedMimeAdaptiveness == other.allowMixedMimeAdaptiveness - && allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness && exceedRendererCapabilitiesIfNecessary == other.exceedRendererCapabilitiesIfNecessary && tunnelingAudioSessionId == other.tunnelingAudioSessionId // Overrides @@ -917,12 +1068,19 @@ public class DefaultTrackSelector extends MappingTrackSelector { result = 31 * result + maxVideoFrameRate; result = 31 * result + maxVideoBitrate; result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0); + result = 31 * result + (allowVideoMixedMimeTypeAdaptiveness ? 1 : 0); + result = 31 * result + (allowVideoNonSeamlessAdaptiveness ? 1 : 0); result = 31 * result + (viewportOrientationMayChange ? 1 : 0); result = 31 * result + viewportWidth; result = 31 * result + viewportHeight; // Audio result = 31 * result + (preferredAudioLanguage == null ? 0 : preferredAudioLanguage.hashCode()); + result = 31 * result + maxAudioChannelCount; + result = 31 * result + maxAudioBitrate; + result = 31 * result + (exceedAudioConstraintsIfNecessary ? 1 : 0); + result = 31 * result + (allowAudioMixedMimeTypeAdaptiveness ? 1 : 0); + result = 31 * result + (allowAudioMixedSampleRateAdaptiveness ? 1 : 0); // Text result = 31 * result + (preferredTextLanguage == null ? 0 : preferredTextLanguage.hashCode()); result = 31 * result + (selectUndeterminedTextLanguage ? 1 : 0); @@ -930,8 +1088,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General result = 31 * result + (forceLowestBitrate ? 1 : 0); result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0); - result = 31 * result + (allowMixedMimeAdaptiveness ? 1 : 0); - result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0); result = 31 * result + (exceedRendererCapabilitiesIfNecessary ? 1 : 0); result = 31 * result + tunnelingAudioSessionId; // Overrides (omitted from hashCode). @@ -953,11 +1109,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { dest.writeInt(maxVideoFrameRate); dest.writeInt(maxVideoBitrate); Util.writeBoolean(dest, exceedVideoConstraintsIfNecessary); + Util.writeBoolean(dest, allowVideoMixedMimeTypeAdaptiveness); + Util.writeBoolean(dest, allowVideoNonSeamlessAdaptiveness); dest.writeInt(viewportWidth); dest.writeInt(viewportHeight); Util.writeBoolean(dest, viewportOrientationMayChange); // Audio dest.writeString(preferredAudioLanguage); + dest.writeInt(maxAudioChannelCount); + dest.writeInt(maxAudioBitrate); + Util.writeBoolean(dest, exceedAudioConstraintsIfNecessary); + Util.writeBoolean(dest, allowAudioMixedMimeTypeAdaptiveness); + Util.writeBoolean(dest, allowAudioMixedSampleRateAdaptiveness); // Text dest.writeString(preferredTextLanguage); Util.writeBoolean(dest, selectUndeterminedTextLanguage); @@ -965,8 +1128,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General Util.writeBoolean(dest, forceLowestBitrate); Util.writeBoolean(dest, forceHighestSupportedBitrate); - Util.writeBoolean(dest, allowMixedMimeAdaptiveness); - Util.writeBoolean(dest, allowNonSeamlessAdaptiveness); Util.writeBoolean(dest, exceedRendererCapabilitiesIfNecessary); dest.writeInt(tunnelingAudioSessionId); // Overrides @@ -1322,11 +1483,10 @@ public class DefaultTrackSelector extends MappingTrackSelector { rendererTrackGroups.get(override.groupIndex), override.tracks[0]); } else { rendererTrackSelections[i] = - Assertions.checkNotNull(adaptiveTrackSelectionFactory) - .createTrackSelection( - rendererTrackGroups.get(override.groupIndex), - getBandwidthMeter(), - override.tracks); + adaptiveTrackSelectionFactory.createTrackSelection( + rendererTrackGroups.get(override.groupIndex), + getBandwidthMeter(), + override.tracks); } } } @@ -1508,11 +1668,12 @@ public class DefaultTrackSelector extends MappingTrackSelector { TrackSelection.Factory adaptiveTrackSelectionFactory, BandwidthMeter bandwidthMeter) throws ExoPlaybackException { - int requiredAdaptiveSupport = params.allowNonSeamlessAdaptiveness - ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS) - : RendererCapabilities.ADAPTIVE_SEAMLESS; + int requiredAdaptiveSupport = + params.allowVideoNonSeamlessAdaptiveness + ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS) + : RendererCapabilities.ADAPTIVE_SEAMLESS; boolean allowMixedMimeTypes = - params.allowMixedMimeAdaptiveness + params.allowVideoMixedMimeTypeAdaptiveness && (mixedMimeTypeAdaptationSupports & requiredAdaptiveSupport) != 0; for (int i = 0; i < groups.length; i++) { TrackGroup group = groups.get(i); @@ -1530,8 +1691,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { params.viewportHeight, params.viewportOrientationMayChange); if (adaptiveTracks.length > 0) { - return Assertions.checkNotNull(adaptiveTrackSelectionFactory) - .createTrackSelection(group, bandwidthMeter, adaptiveTracks); + return adaptiveTrackSelectionFactory.createTrackSelection( + group, bandwidthMeter, adaptiveTracks); } } return null; @@ -1758,6 +1919,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { * selection was made. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ + @SuppressWarnings("unused") protected @Nullable Pair selectAudioTrack( TrackGroupArray groups, int[][] formatSupports, @@ -1777,6 +1939,10 @@ public class DefaultTrackSelector extends MappingTrackSelector { Format format = trackGroup.getFormat(trackIndex); AudioTrackScore trackScore = new AudioTrackScore(format, params, trackFormatSupport[trackIndex]); + if (!trackScore.isWithinConstraints && !params.exceedAudioConstraintsIfNecessary) { + // Track should not be selected. + continue; + } if (selectedTrackScore == null || trackScore.compareTo(selectedTrackScore) > 0) { selectedGroupIndex = groupIndex; selectedTrackIndex = trackIndex; @@ -1799,7 +1965,10 @@ public class DefaultTrackSelector extends MappingTrackSelector { // If the group of the track with the highest score allows it, try to enable adaptation. int[] adaptiveTracks = getAdaptiveAudioTracks( - selectedGroup, formatSupports[selectedGroupIndex], params.allowMixedMimeAdaptiveness); + selectedGroup, + formatSupports[selectedGroupIndex], + params.allowAudioMixedMimeTypeAdaptiveness, + params.allowAudioMixedSampleRateAdaptiveness); if (adaptiveTracks.length > 0) { selection = adaptiveTrackSelectionFactory.createTrackSelection( @@ -1814,18 +1983,27 @@ public class DefaultTrackSelector extends MappingTrackSelector { return Pair.create(selection, Assertions.checkNotNull(selectedTrackScore)); } - private static int[] getAdaptiveAudioTracks(TrackGroup group, int[] formatSupport, - boolean allowMixedMimeTypes) { + private static int[] getAdaptiveAudioTracks( + TrackGroup group, + int[] formatSupport, + boolean allowMixedMimeTypeAdaptiveness, + boolean allowMixedSampleRateAdaptiveness) { int selectedConfigurationTrackCount = 0; AudioConfigurationTuple selectedConfiguration = null; HashSet seenConfigurationTuples = new HashSet<>(); for (int i = 0; i < group.length; i++) { Format format = group.getFormat(i); - AudioConfigurationTuple configuration = new AudioConfigurationTuple( - format.channelCount, format.sampleRate, - allowMixedMimeTypes ? null : format.sampleMimeType); + AudioConfigurationTuple configuration = + new AudioConfigurationTuple( + format.channelCount, format.sampleRate, format.sampleMimeType); if (seenConfigurationTuples.add(configuration)) { - int configurationCount = getAdaptiveAudioTrackCount(group, formatSupport, configuration); + int configurationCount = + getAdaptiveAudioTrackCount( + group, + formatSupport, + configuration, + allowMixedMimeTypeAdaptiveness, + allowMixedSampleRateAdaptiveness); if (configurationCount > selectedConfigurationTrackCount) { selectedConfiguration = configuration; selectedConfigurationTrackCount = configurationCount; @@ -1838,7 +2016,11 @@ public class DefaultTrackSelector extends MappingTrackSelector { int index = 0; for (int i = 0; i < group.length; i++) { if (isSupportedAdaptiveAudioTrack( - group.getFormat(i), formatSupport[i], Assertions.checkNotNull(selectedConfiguration))) { + group.getFormat(i), + formatSupport[i], + Assertions.checkNotNull(selectedConfiguration), + allowMixedMimeTypeAdaptiveness, + allowMixedSampleRateAdaptiveness)) { adaptiveIndices[index++] = i; } } @@ -1847,23 +2029,41 @@ public class DefaultTrackSelector extends MappingTrackSelector { return NO_TRACKS; } - private static int getAdaptiveAudioTrackCount(TrackGroup group, int[] formatSupport, - AudioConfigurationTuple configuration) { + private static int getAdaptiveAudioTrackCount( + TrackGroup group, + int[] formatSupport, + AudioConfigurationTuple configuration, + boolean allowMixedMimeTypeAdaptiveness, + boolean allowMixedSampleRateAdaptiveness) { int count = 0; for (int i = 0; i < group.length; i++) { - if (isSupportedAdaptiveAudioTrack(group.getFormat(i), formatSupport[i], configuration)) { + if (isSupportedAdaptiveAudioTrack( + group.getFormat(i), + formatSupport[i], + configuration, + allowMixedMimeTypeAdaptiveness, + allowMixedSampleRateAdaptiveness)) { count++; } } return count; } - private static boolean isSupportedAdaptiveAudioTrack(Format format, int formatSupport, - AudioConfigurationTuple configuration) { - return isSupported(formatSupport, false) && format.channelCount == configuration.channelCount - && format.sampleRate == configuration.sampleRate - && (configuration.mimeType == null - || TextUtils.equals(configuration.mimeType, format.sampleMimeType)); + private static boolean isSupportedAdaptiveAudioTrack( + Format format, + int formatSupport, + AudioConfigurationTuple configuration, + boolean allowMixedMimeTypeAdaptiveness, + boolean allowMixedSampleRateAdaptiveness) { + return isSupported(formatSupport, false) + && (format.channelCount != Format.NO_VALUE + && format.channelCount == configuration.channelCount) + && (allowMixedMimeTypeAdaptiveness + || (format.sampleMimeType != null + && TextUtils.equals(format.sampleMimeType, configuration.mimeType))) + && (allowMixedSampleRateAdaptiveness + || (format.sampleRate != Format.NO_VALUE + && format.sampleRate == configuration.sampleRate)); } // Text track selection implementation. @@ -2202,6 +2402,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { /** Represents how well an audio track matches the selection {@link Parameters}. */ protected static final class AudioTrackScore implements Comparable { + public final boolean isWithinConstraints; + private final Parameters parameters; private final int withinRendererCapabilitiesScore; private final int matchLanguageScore; @@ -2218,6 +2420,10 @@ public class DefaultTrackSelector extends MappingTrackSelector { channelCount = format.channelCount; sampleRate = format.sampleRate; bitrate = format.bitrate; + isWithinConstraints = + (format.bitrate == Format.NO_VALUE || format.bitrate <= parameters.maxAudioBitrate) + && (format.channelCount == Format.NO_VALUE + || format.channelCount <= parameters.maxAudioChannelCount); } /** @@ -2236,6 +2442,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { if (this.matchLanguageScore != other.matchLanguageScore) { return compareInts(this.matchLanguageScore, other.matchLanguageScore); } + if (this.isWithinConstraints != other.isWithinConstraints) { + return this.isWithinConstraints ? 1 : -1; + } if (parameters.forceLowestBitrate) { int bitrateComparison = compareFormatValues(bitrate, other.bitrate); if (bitrateComparison != 0) { @@ -2245,9 +2454,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { if (this.defaultSelectionFlagScore != other.defaultSelectionFlagScore) { return compareInts(this.defaultSelectionFlagScore, other.defaultSelectionFlagScore); } - // If the formats are within renderer capabilities then prefer higher values of channel count, - // sample rate and bit rate in that order. Otherwise, prefer lower values. - int resultSign = withinRendererCapabilitiesScore == 1 ? 1 : -1; + // If the formats are within constraints and renderer capabilities then prefer higher values + // of channel count, sample rate and bit rate in that order. Otherwise, prefer lower values. + int resultSign = isWithinConstraints && withinRendererCapabilitiesScore == 1 ? 1 : -1; if (this.channelCount != other.channelCount) { return resultSign * compareInts(this.channelCount, other.channelCount); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 06fdaf9218..92dca1d4dd 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.trackselection; +import static com.google.android.exoplayer2.RendererCapabilities.ADAPTIVE_NOT_SEAMLESS; import static com.google.android.exoplayer2.RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES; import static com.google.android.exoplayer2.RendererCapabilities.FORMAT_HANDLED; import static com.google.android.exoplayer2.RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE; @@ -132,21 +133,26 @@ public final class DefaultTrackSelectorTest { /* maxVideoFrameRate= */ 2, /* maxVideoBitrate= */ 3, /* exceedVideoConstraintsIfNecessary= */ false, + /* allowVideoMixedMimeTypeAdaptiveness= */ true, + /* allowVideoNonSeamlessAdaptiveness= */ false, /* viewportWidth= */ 4, /* viewportHeight= */ 5, /* viewportOrientationMayChange= */ true, // Audio /* preferredAudioLanguage= */ "en", + /* maxAudioChannelCount= */ 6, + /* maxAudioBitrate= */ 7, + /* exceedAudioConstraintsIfNecessary= */ false, + /* allowAudioMixedMimeTypeAdaptiveness= */ true, + /* allowAudioMixedSampleRateAdaptiveness= */ false, // Text /* preferredTextLanguage= */ "de", - /* selectUndeterminedTextLanguage= */ false, - /* disabledTextTrackSelectionFlags= */ 6, + /* selectUndeterminedTextLanguage= */ true, + /* disabledTextTrackSelectionFlags= */ 8, // General - /* forceLowestBitrate= */ true, - /* forceHighestSupportedBitrate= */ false, - /* allowMixedMimeAdaptiveness= */ true, - /* allowNonSeamlessAdaptiveness= */ false, - /* exceedRendererCapabilitiesIfNecessary= */ true, + /* forceLowestBitrate= */ false, + /* forceHighestSupportedBitrate= */ true, + /* exceedRendererCapabilitiesIfNecessary= */ false, /* tunnelingAudioSessionId= */ C.AUDIO_SESSION_ID_UNSET, // Overrides selectionOverrides, @@ -1090,6 +1096,137 @@ public final class DefaultTrackSelectorTest { assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 1); } + @Test + public void testSelectTracksWithMultipleAudioTracksWithMixedSampleRates() throws Exception { + Format highSampleRateAudioFormat = + buildAudioFormatWithSampleRate("44100", /* sampleRate= */ 44100); + Format lowSampleRateAudioFormat = + buildAudioFormatWithSampleRate("22050", /* sampleRate= */ 22050); + + // Should not adapt between mixed sample rates by default, so we expect a fixed selection + // containing the higher sample rate stream. + TrackGroupArray trackGroups = + singleTrackGroup(highSampleRateAudioFormat, lowSampleRateAudioFormat); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, highSampleRateAudioFormat); + + // The same applies if the tracks are provided in the opposite order. + trackGroups = singleTrackGroup(lowSampleRateAudioFormat, highSampleRateAudioFormat); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, highSampleRateAudioFormat); + + // If we explicitly enable mixed sample rate adaptiveness, expect an adaptive selection. + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setAllowAudioMixedSampleRateAdaptiveness(true)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 1); + } + + @Test + public void testSelectTracksWithMultipleAudioTracksWithMixedMimeTypes() throws Exception { + Format aacAudioFormat = buildAudioFormatWithMimeType("aac", MimeTypes.AUDIO_AAC); + Format opusAudioFormat = buildAudioFormatWithMimeType("opus", MimeTypes.AUDIO_OPUS); + + // Should not adapt between mixed mime types by default, so we expect a fixed selection + // containing the first stream. + TrackGroupArray trackGroups = singleTrackGroup(aacAudioFormat, opusAudioFormat); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, aacAudioFormat); + + // The same applies if the tracks are provided in the opposite order. + trackGroups = singleTrackGroup(opusAudioFormat, aacAudioFormat); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, opusAudioFormat); + + // If we explicitly enable mixed mime type adaptiveness, expect an adaptive selection. + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setAllowAudioMixedMimeTypeAdaptiveness(true)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 1); + } + + @Test + public void testSelectTracksWithMultipleAudioTracksWithMixedChannelCounts() throws Exception { + Format stereoAudioFormat = + buildAudioFormatWithChannelCount("2-channels", /* channelCount= */ 2); + Format surroundAudioFormat = + buildAudioFormatWithChannelCount("5-channels", /* channelCount= */ 5); + + // Should not adapt between different channel counts, so we expect a fixed selection containing + // the track with more channels. + TrackGroupArray trackGroups = singleTrackGroup(stereoAudioFormat, surroundAudioFormat); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, surroundAudioFormat); + + // The same applies if the tracks are provided in the opposite order. + trackGroups = singleTrackGroup(surroundAudioFormat, stereoAudioFormat); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, surroundAudioFormat); + + // If we constrain the channel count to 4 we expect a fixed selection containing the track with + // fewer channels. + trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setMaxAudioChannelCount(4)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, stereoAudioFormat); + + // If we constrain the channel count to 2 we expect a fixed selection containing the track with + // fewer channels. + trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setMaxAudioChannelCount(2)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, stereoAudioFormat); + + // If we constrain the channel count to 1 we expect a fixed selection containing the track with + // fewer channels. + trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setMaxAudioChannelCount(1)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, stereoAudioFormat); + + // If we disable exceeding of constraints we expect no selection. + trackSelector.setParameters( + Parameters.DEFAULT + .buildUpon() + .setMaxAudioChannelCount(1) + .setExceedAudioConstraintsIfNecessary(false)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertNoSelection(result.selections.get(0)); + } + @Test public void testSelectTracksWithMultipleAudioTracksOverrideReturnsAdaptiveTrackSelection() throws Exception { @@ -1164,6 +1301,70 @@ public final class DefaultTrackSelectorTest { assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 1); } + @Test + public void testSelectTracksWithMultipleVideoTracksWithNonSeamlessAdaptiveness() + throws Exception { + FakeRendererCapabilities nonSeamlessVideoCapabilities = + new FakeRendererCapabilities(C.TRACK_TYPE_VIDEO, FORMAT_HANDLED | ADAPTIVE_NOT_SEAMLESS); + + // Should do non-seamless adaptiveness by default, so expect an adaptive selection. + TrackGroupArray trackGroups = singleTrackGroup(buildVideoFormat("0"), buildVideoFormat("1")); + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setAllowVideoNonSeamlessAdaptiveness(true)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {nonSeamlessVideoCapabilities}, + trackGroups, + periodId, + TIMELINE); + assertThat(result.length).isEqualTo(1); + assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 1); + + // If we explicitly disable non-seamless adaptiveness, expect a fixed selection. + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setAllowVideoNonSeamlessAdaptiveness(false)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {nonSeamlessVideoCapabilities}, + trackGroups, + periodId, + TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups.get(0), 0); + } + + @Test + public void testSelectTracksWithMultipleVideoTracksWithMixedMimeTypes() throws Exception { + Format h264VideoFormat = buildVideoFormatWithMimeType("h264", MimeTypes.VIDEO_H264); + Format h265VideoFormat = buildVideoFormatWithMimeType("h265", MimeTypes.VIDEO_H265); + + // Should not adapt between mixed mime types by default, so we expect a fixed selection + // containing the first stream. + TrackGroupArray trackGroups = singleTrackGroup(h264VideoFormat, h265VideoFormat); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, h264VideoFormat); + + // The same applies if the tracks are provided in the opposite order. + trackGroups = singleTrackGroup(h265VideoFormat, h264VideoFormat); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups, h265VideoFormat); + + // If we explicitly enable mixed mime type adaptiveness, expect an adaptive selection. + trackSelector.setParameters( + Parameters.DEFAULT.buildUpon().setAllowVideoMixedMimeTypeAdaptiveness(true)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertThat(result.length).isEqualTo(1); + assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 1); + } + @Test public void testSelectTracksWithMultipleVideoTracksOverrideReturnsAdaptiveTrackSelection() throws Exception { @@ -1277,6 +1478,36 @@ public final class DefaultTrackSelectorTest { /* sampleRate= */ 44100); } + private static Format buildAudioFormatWithSampleRate(String id, int sampleRate) { + return buildAudioFormat( + id, + MimeTypes.AUDIO_AAC, + /* language= */ null, + /* selectionFlags= */ 0, + /* channelCount= */ 2, + sampleRate); + } + + private static Format buildAudioFormatWithChannelCount(String id, int channelCount) { + return buildAudioFormat( + id, + MimeTypes.AUDIO_AAC, + /* language= */ null, + /* selectionFlags= */ 0, + channelCount, + /* sampleRate= */ 44100); + } + + private static Format buildAudioFormatWithMimeType(String id, String mimeType) { + return buildAudioFormat( + id, + mimeType, + /* language= */ null, + /* selectionFlags= */ 0, + /* channelCount= */ 2, + /* sampleRate= */ 44100); + } + private static Format buildAudioFormat(String id) { return buildAudioFormat( id, From 053a7bc0b9af79e183ec5b5fed7f0501dbd79348 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 29 Oct 2018 12:25:39 -0700 Subject: [PATCH 136/832] Simplify DefaultDrmSession event handling - Handling EVENT_PROVISION_REQUIRED is unnecessary because we handle it via NotProvisionedException, which was thrown on all API levels. - Handling EVENT_KEY_EXPIRED during playback is unnecessary. All it does is cause an error to be thrown, but an error will be thrown anyway out of the MediaCodec when it tries to use the expired keys. It's currently a race condition where the error gets thrown from, where-as having it always be thrown from one place is preferable. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219172021 --- .../exoplayer2/drm/DefaultDrmSession.java | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java index 514d9da073..faf4b68f10 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java @@ -207,18 +207,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return Arrays.equals(this.sessionId, sessionId); } - @SuppressWarnings("deprecation") public void onMediaDrmEvent(int what) { switch (what) { - case ExoMediaDrm.EVENT_PROVISION_REQUIRED: - onProvisionRequired(); - break; case ExoMediaDrm.EVENT_KEY_REQUIRED: onKeysRequired(); break; - case ExoMediaDrm.EVENT_KEY_EXPIRED: - onKeysExpired(); - break; default: break; } @@ -434,23 +427,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } } - private void onProvisionRequired() { - if (mode == DefaultDrmSessionManager.MODE_PLAYBACK && isOpen()) { - state = STATE_OPENED; - provisioningManager.provisionRequired(this); - } - } - private void onKeysRequired() { - if (mode == DefaultDrmSessionManager.MODE_PLAYBACK && isOpen()) { - doLicense(/* allowRetry= */ false); - } - } - - private void onKeysExpired() { if (mode == DefaultDrmSessionManager.MODE_PLAYBACK && state == STATE_OPENED_WITH_KEYS) { - state = STATE_OPENED; - onError(new KeysExpiredException()); + Util.castNonNull(sessionId); + doLicense(/* allowRetry= */ false); } } From 4b9530c214d9d5ac0e3db7421b10224daf0efd34 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 30 Oct 2018 02:19:08 -0700 Subject: [PATCH 137/832] Let apps specify whether to focus skip button on ATV Issue: #5019 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219267048 --- RELEASENOTES.md | 2 ++ .../exoplayer2/ext/ima/ImaAdsLoader.java | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4ea4610b84..a52a076014 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -34,6 +34,8 @@ * IMA extension: * For preroll to live stream transitions, project forward the loading position to avoid being behind the live window. + * Let apps specify whether to focus the skip button on ATV + ([#5019](https://github.com/google/ExoPlayer/issues/5019)). * Support for playing spherical videos on Daydream. * Fix issue where a `NullPointerException` is thrown when removing an unprepared media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 95a3a588b4..cc621c6218 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -93,6 +93,7 @@ public final class ImaAdsLoader private @Nullable AdEventListener adEventListener; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; + private boolean focusSkipButtonWhenAvailable; private ImaFactory imaFactory; /** @@ -104,6 +105,7 @@ public final class ImaAdsLoader this.context = Assertions.checkNotNull(context); vastLoadTimeoutMs = TIMEOUT_UNSET; mediaLoadTimeoutMs = TIMEOUT_UNSET; + focusSkipButtonWhenAvailable = true; imaFactory = new DefaultImaFactory(); } @@ -159,6 +161,20 @@ public final class ImaAdsLoader return this; } + /** + * Sets whether to focus the skip button (when available) on Android TV devices. The default + * setting is {@code true}. + * + * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on + * Android TV devices. + * @return This builder, for convenience. + * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) + */ + public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvailable) { + this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable; + return this; + } + // @VisibleForTesting /* package */ Builder setImaFactory(ImaFactory imaFactory) { this.imaFactory = Assertions.checkNotNull(imaFactory); @@ -181,6 +197,7 @@ public final class ImaAdsLoader null, vastLoadTimeoutMs, mediaLoadTimeoutMs, + focusSkipButtonWhenAvailable, adEventListener, imaFactory); } @@ -200,6 +217,7 @@ public final class ImaAdsLoader adsResponse, vastLoadTimeoutMs, mediaLoadTimeoutMs, + focusSkipButtonWhenAvailable, adEventListener, imaFactory); } @@ -252,6 +270,7 @@ public final class ImaAdsLoader private final @Nullable String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; + private final boolean focusSkipButtonWhenAvailable; private final @Nullable AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; @@ -338,6 +357,7 @@ public final class ImaAdsLoader /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* focusSkipButtonWhenAvailable= */ true, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -362,6 +382,7 @@ public final class ImaAdsLoader /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* focusSkipButtonWhenAvailable= */ true, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -373,6 +394,7 @@ public final class ImaAdsLoader @Nullable String adsResponse, int vastLoadTimeoutMs, int mediaLoadTimeoutMs, + boolean focusSkipButtonWhenAvailable, @Nullable AdEventListener adEventListener, ImaFactory imaFactory) { Assertions.checkArgument(adTagUri != null || adsResponse != null); @@ -380,6 +402,7 @@ public final class ImaAdsLoader this.adsResponse = adsResponse; this.vastLoadTimeoutMs = vastLoadTimeoutMs; this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; + this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable; this.adEventListener = adEventListener; this.imaFactory = imaFactory; if (imaSdkSettings == null) { @@ -926,6 +949,7 @@ public final class ImaAdsLoader if (mediaLoadTimeoutMs != TIMEOUT_UNSET) { adsRenderingSettings.setLoadVideoTimeout(mediaLoadTimeoutMs); } + adsRenderingSettings.setFocusSkipButtonWhenAvailable(focusSkipButtonWhenAvailable); // Set up the ad playback state, skipping ads based on the start position as required. long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints()); From 1094da2b61a05cacef7036ce1bb81c698bab90f8 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 31 Oct 2018 02:42:14 -0700 Subject: [PATCH 138/832] Fix player state transitions for postroll ads Before this change, the player state would be STATE_ENDED then STATE_BUFFERING (when the postroll ad was marked as played) then STATE_ENDED again. Queueing a final content media period with start point equal to the content duration after a postroll ensures that the player state doesn't change to STATE_ENDED transiently. Switch from using C.TIME_END_OF_SOURCE to C.TIME_UNSET for media periods that should not have an end point and are not followed by an ad. Content media periods before postrolls have end position C.TIME_END_OF_SOURCE. If the postroll ad loads, its content position is set to the content duration, which should be known at the point of loading the postroll, then a final 'empty' content media period with start position equal to its duration is queued. If the postroll fails to load, this empty content media period is queued up directly after the preceding content media period. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219443683 --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 3 +- .../android/exoplayer2/MediaPeriodHolder.java | 53 ++-- .../android/exoplayer2/MediaPeriodInfo.java | 35 ++- .../android/exoplayer2/MediaPeriodQueue.java | 96 ++----- .../google/android/exoplayer2/Timeline.java | 2 +- .../exoplayer2/source/MediaSource.java | 13 +- .../source/ads/AdPlaybackState.java | 20 +- .../exoplayer2/MediaPeriodQueueTest.java | 257 ++++++++++++++++++ library/ui/src/main/res/values-af/strings.xml | 2 +- library/ui/src/main/res/values-am/strings.xml | 2 +- library/ui/src/main/res/values-ar/strings.xml | 2 +- library/ui/src/main/res/values-az/strings.xml | 2 +- .../src/main/res/values-b+sr+Latn/strings.xml | 2 +- library/ui/src/main/res/values-be/strings.xml | 2 +- library/ui/src/main/res/values-bg/strings.xml | 2 +- library/ui/src/main/res/values-bs/strings.xml | 2 +- library/ui/src/main/res/values-ca/strings.xml | 2 +- library/ui/src/main/res/values-cs/strings.xml | 2 +- library/ui/src/main/res/values-da/strings.xml | 2 +- library/ui/src/main/res/values-de/strings.xml | 2 +- library/ui/src/main/res/values-el/strings.xml | 2 +- .../ui/src/main/res/values-es-rUS/strings.xml | 2 +- library/ui/src/main/res/values-es/strings.xml | 2 +- library/ui/src/main/res/values-et/strings.xml | 2 +- library/ui/src/main/res/values-eu/strings.xml | 2 +- library/ui/src/main/res/values-fa/strings.xml | 2 +- library/ui/src/main/res/values-fi/strings.xml | 2 +- .../ui/src/main/res/values-fr-rCA/strings.xml | 2 +- library/ui/src/main/res/values-fr/strings.xml | 2 +- library/ui/src/main/res/values-gl/strings.xml | 2 +- library/ui/src/main/res/values-hi/strings.xml | 2 +- library/ui/src/main/res/values-hr/strings.xml | 2 +- library/ui/src/main/res/values-hu/strings.xml | 2 +- library/ui/src/main/res/values-hy/strings.xml | 2 +- library/ui/src/main/res/values-in/strings.xml | 2 +- library/ui/src/main/res/values-is/strings.xml | 2 +- library/ui/src/main/res/values-it/strings.xml | 2 +- library/ui/src/main/res/values-ja/strings.xml | 2 +- library/ui/src/main/res/values-ka/strings.xml | 2 +- library/ui/src/main/res/values-kk/strings.xml | 2 +- library/ui/src/main/res/values-km/strings.xml | 2 +- library/ui/src/main/res/values-ko/strings.xml | 2 +- library/ui/src/main/res/values-ky/strings.xml | 2 +- library/ui/src/main/res/values-lt/strings.xml | 2 +- library/ui/src/main/res/values-lv/strings.xml | 2 +- library/ui/src/main/res/values-mk/strings.xml | 2 +- library/ui/src/main/res/values-mn/strings.xml | 2 +- library/ui/src/main/res/values-mr/strings.xml | 2 +- library/ui/src/main/res/values-ms/strings.xml | 2 +- library/ui/src/main/res/values-my/strings.xml | 2 +- library/ui/src/main/res/values-nb/strings.xml | 2 +- library/ui/src/main/res/values-nl/strings.xml | 2 +- library/ui/src/main/res/values-pl/strings.xml | 2 +- .../ui/src/main/res/values-pt-rPT/strings.xml | 2 +- library/ui/src/main/res/values-pt/strings.xml | 2 +- library/ui/src/main/res/values-ro/strings.xml | 2 +- library/ui/src/main/res/values-ru/strings.xml | 2 +- library/ui/src/main/res/values-sk/strings.xml | 2 +- library/ui/src/main/res/values-sl/strings.xml | 2 +- library/ui/src/main/res/values-sq/strings.xml | 2 +- library/ui/src/main/res/values-sr/strings.xml | 2 +- library/ui/src/main/res/values-sv/strings.xml | 2 +- library/ui/src/main/res/values-sw/strings.xml | 2 +- library/ui/src/main/res/values-th/strings.xml | 2 +- library/ui/src/main/res/values-tr/strings.xml | 2 +- library/ui/src/main/res/values-uk/strings.xml | 2 +- library/ui/src/main/res/values-uz/strings.xml | 2 +- library/ui/src/main/res/values-vi/strings.xml | 2 +- .../ui/src/main/res/values-zh-rCN/strings.xml | 2 +- .../ui/src/main/res/values-zh-rHK/strings.xml | 2 +- .../ui/src/main/res/values-zh-rTW/strings.xml | 2 +- library/ui/src/main/res/values-zu/strings.xml | 2 +- 72 files changed, 443 insertions(+), 164 deletions(-) create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index cc621c6218..71bc048902 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -666,7 +666,8 @@ public final class ImaAdsLoader // until MAXIMUM_PRELOAD_DURATION_MS before the ad so that an ad group load error delivered // just after an ad group isn't incorrectly attributed to the next ad group. int nextAdGroupIndex = - adPlaybackState.getAdGroupIndexAfterPositionUs(C.msToUs(contentPositionMs)); + adPlaybackState.getAdGroupIndexAfterPositionUs( + C.msToUs(contentPositionMs), C.msToUs(contentDurationMs)); if (nextAdGroupIndex != expectedAdGroupIndex && nextAdGroupIndex != C.INDEX_UNSET) { long nextAdGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[nextAdGroupIndex]); if (nextAdGroupTimeMs == C.TIME_END_OF_SOURCE) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index 4fc21475d9..7becac7b55 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.source.ClippingMediaPeriod; import com.google.android.exoplayer2.source.EmptySampleStream; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelection; @@ -88,16 +89,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; this.info = info; sampleStreams = new SampleStream[rendererCapabilities.length]; mayRetainStreamFlags = new boolean[rendererCapabilities.length]; - MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, allocator); - if (info.id.endPositionUs != C.TIME_END_OF_SOURCE) { - mediaPeriod = - new ClippingMediaPeriod( - mediaPeriod, - /* enableInitialDiscontinuity= */ true, - /* startUs= */ 0, - info.id.endPositionUs); - } - this.mediaPeriod = mediaPeriod; + mediaPeriod = createMediaPeriod(info.id, mediaSource, allocator); } /** @@ -302,16 +294,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; public void release() { disableTrackSelectionsInResult(); trackSelectorResult = null; - try { - if (info.id.endPositionUs != C.TIME_END_OF_SOURCE) { - mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); - } else { - mediaSource.releasePeriod(mediaPeriod); - } - } catch (RuntimeException e) { - // There's nothing we can do. - Log.e(TAG, "Period release failed.", e); - } + releaseMediaPeriod(info.id, mediaSource, mediaPeriod); } /** @@ -413,4 +396,34 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; private boolean isLoadingMediaPeriod() { return next == null; } + + /** Returns a media period corresponding to the given {@code id}. */ + private static MediaPeriod createMediaPeriod( + MediaPeriodId id, MediaSource mediaSource, Allocator allocator) { + MediaPeriod mediaPeriod = mediaSource.createPeriod(id, allocator); + if (id.endPositionUs != C.TIME_UNSET && id.endPositionUs != C.TIME_END_OF_SOURCE) { + mediaPeriod = + new ClippingMediaPeriod( + mediaPeriod, + /* enableInitialDiscontinuity= */ true, + /* startUs= */ 0, + id.endPositionUs); + } + return mediaPeriod; + } + + /** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */ + private static void releaseMediaPeriod( + MediaPeriodId id, MediaSource mediaSource, MediaPeriod mediaPeriod) { + try { + if (id.endPositionUs != C.TIME_UNSET && id.endPositionUs != C.TIME_END_OF_SOURCE) { + mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); + } else { + mediaSource.releasePeriod(mediaPeriod); + } + } catch (RuntimeException e) { + // There's nothing we can do. + Log.e(TAG, "Period release failed.", e); + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java index ba19b54c3f..e57100931e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java @@ -15,8 +15,10 @@ */ package com.google.android.exoplayer2; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; +import com.google.android.exoplayer2.util.Util; /** Stores the information required to load and play a {@link MediaPeriod}. */ /* package */ final class MediaPeriodInfo { @@ -32,8 +34,8 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; public final long contentPositionUs; /** * The duration of the media period, like {@link MediaPeriodId#endPositionUs} but with {@link - * C#TIME_END_OF_SOURCE} resolved to the timeline period duration. May be {@link C#TIME_UNSET} if - * the end position is not known. + * C#TIME_END_OF_SOURCE} and {@link C#TIME_UNSET} resolved to the timeline period duration if + * known. */ public final long durationUs; /** @@ -72,4 +74,33 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; isLastInTimelinePeriod, isFinal); } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MediaPeriodInfo that = (MediaPeriodInfo) o; + return startPositionUs == that.startPositionUs + && contentPositionUs == that.contentPositionUs + && durationUs == that.durationUs + && isLastInTimelinePeriod == that.isLastInTimelinePeriod + && isFinal == that.isFinal + && Util.areEqual(id, that.id); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + id.hashCode(); + result = 31 * result + (int) startPositionUs; + result = 31 * result + (int) contentPositionUs; + result = 31 * result + (int) durationUs; + result = 31 * result + (isLastInTimelinePeriod ? 1 : 0); + result = 31 * result + (isFinal ? 1 : 0); + return result; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 40bc658953..de09fc13a1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -351,17 +351,18 @@ import com.google.android.exoplayer2.util.Assertions; * @return The updated media period info for the current timeline. */ public MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo info) { - boolean isLastInPeriod = isLastInPeriod(info.id); - boolean isLastInTimeline = isLastInTimeline(info.id, isLastInPeriod); + MediaPeriodId id = info.id; + boolean isLastInPeriod = isLastInPeriod(id); + boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); timeline.getPeriodByUid(info.id.periodUid, period); long durationUs = - info.id.isAd() - ? period.getAdDurationUs(info.id.adGroupIndex, info.id.adIndexInAdGroup) - : (info.id.endPositionUs == C.TIME_END_OF_SOURCE + id.isAd() + ? period.getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup) + : (id.endPositionUs == C.TIME_UNSET || id.endPositionUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() - : info.id.endPositionUs); + : id.endPositionUs); return new MediaPeriodInfo( - info.id, + id, info.startPositionUs, info.contentPositionUs, durationUs, @@ -404,7 +405,7 @@ import com.google.android.exoplayer2.util.Assertions; int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs); long endPositionUs = nextAdGroupIndex == C.INDEX_UNSET - ? C.TIME_END_OF_SOURCE + ? C.TIME_UNSET : period.getAdGroupTimeUs(nextAdGroupIndex); return new MediaPeriodId(periodUid, windowSequenceNumber, endPositionUs); } else { @@ -580,7 +581,8 @@ import com.google.android.exoplayer2.util.Assertions; } MediaPeriodId periodId = resolveMediaPeriodIdForAds(nextPeriodUid, startPositionUs, windowSequenceNumber); - return getMediaPeriodInfo(periodId, startPositionUs, startPositionUs); + return getMediaPeriodInfo( + periodId, /* contentPositionUs= */ startPositionUs, startPositionUs); } MediaPeriodId currentPeriodId = mediaPeriodInfo.id; @@ -626,14 +628,14 @@ import com.google.android.exoplayer2.util.Assertions; return getMediaPeriodInfoForContent( currentPeriodId.periodUid, startPositionUs, currentPeriodId.windowSequenceNumber); } - } else if (mediaPeriodInfo.id.endPositionUs != C.TIME_END_OF_SOURCE) { + } else { // Play the next ad group if it's available. int nextAdGroupIndex = period.getAdGroupIndexForPositionUs(mediaPeriodInfo.id.endPositionUs); if (nextAdGroupIndex == C.INDEX_UNSET) { - // The next ad group can't be played. Play content from the ad group position instead. + // The next ad group can't be played. Play content from the previous end position instead. return getMediaPeriodInfoForContent( currentPeriodId.periodUid, - mediaPeriodInfo.id.endPositionUs, + /* startPositionUs= */ mediaPeriodInfo.durationUs, currentPeriodId.windowSequenceNumber); } int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex); @@ -643,30 +645,8 @@ import com.google.android.exoplayer2.util.Assertions; currentPeriodId.periodUid, nextAdGroupIndex, adIndexInAdGroup, - mediaPeriodInfo.id.endPositionUs, + /* contentPositionUs= */ mediaPeriodInfo.durationUs, currentPeriodId.windowSequenceNumber); - } else { - // Check if the postroll ad should be played. - int adGroupCount = period.getAdGroupCount(); - if (adGroupCount == 0) { - return null; - } - int adGroupIndex = adGroupCount - 1; - if (period.getAdGroupTimeUs(adGroupIndex) != C.TIME_END_OF_SOURCE - || period.hasPlayedAdGroup(adGroupIndex)) { - return null; - } - int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); - if (!period.isAdAvailable(adGroupIndex, adIndexInAdGroup)) { - return null; - } - long contentDurationUs = period.getDurationUs(); - return getMediaPeriodInfoForAd( - currentPeriodId.periodUid, - adGroupIndex, - adIndexInAdGroup, - contentDurationUs, - currentPeriodId.windowSequenceNumber); } } @@ -696,8 +676,6 @@ import com.google.android.exoplayer2.util.Assertions; long windowSequenceNumber) { MediaPeriodId id = new MediaPeriodId(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); - boolean isLastInPeriod = isLastInPeriod(id); - boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); long durationUs = timeline .getPeriodByUid(id.periodUid, period) @@ -711,49 +689,35 @@ import com.google.android.exoplayer2.util.Assertions; startPositionUs, contentPositionUs, durationUs, - isLastInPeriod, - isLastInTimeline); + /* isLastInTimelinePeriod= */ false, + /* isFinal= */ false); } private MediaPeriodInfo getMediaPeriodInfoForContent( Object periodUid, long startPositionUs, long windowSequenceNumber) { int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); long endPositionUs = - nextAdGroupIndex == C.INDEX_UNSET - ? C.TIME_END_OF_SOURCE - : period.getAdGroupTimeUs(nextAdGroupIndex); + nextAdGroupIndex != C.INDEX_UNSET + ? period.getAdGroupTimeUs(nextAdGroupIndex) + : C.TIME_UNSET; MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, endPositionUs); - timeline.getPeriodByUid(id.periodUid, period); boolean isLastInPeriod = isLastInPeriod(id); boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); long durationUs = - endPositionUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endPositionUs; + endPositionUs == C.TIME_UNSET || endPositionUs == C.TIME_END_OF_SOURCE + ? period.durationUs + : endPositionUs; return new MediaPeriodInfo( - id, startPositionUs, C.TIME_UNSET, durationUs, isLastInPeriod, isLastInTimeline); + id, + startPositionUs, + /* contentPositionUs= */ C.TIME_UNSET, + durationUs, + isLastInPeriod, + isLastInTimeline); } private boolean isLastInPeriod(MediaPeriodId id) { - int adGroupCount = timeline.getPeriodByUid(id.periodUid, period).getAdGroupCount(); - if (adGroupCount == 0) { - return true; - } - - int lastAdGroupIndex = adGroupCount - 1; - boolean isAd = id.isAd(); - if (period.getAdGroupTimeUs(lastAdGroupIndex) != C.TIME_END_OF_SOURCE) { - // There's no postroll ad. - return !isAd && id.endPositionUs == C.TIME_END_OF_SOURCE; - } - - int postrollAdCount = period.getAdCountInAdGroup(lastAdGroupIndex); - if (postrollAdCount == C.LENGTH_UNSET) { - // We won't know if this is the last ad until we know how many postroll ads there are. - return false; - } - - boolean isLastAd = - isAd && id.adGroupIndex == lastAdGroupIndex && id.adIndexInAdGroup == postrollAdCount - 1; - return isLastAd || (!isAd && period.getFirstAdIndexToPlay(lastAdGroupIndex) == postrollAdCount); + return !id.isAd() && id.endPositionUs == C.TIME_UNSET; } private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index 1639920aaa..61a31f7db5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -441,7 +441,7 @@ public abstract class Timeline { * @return The index of the ad group, or {@link C#INDEX_UNSET}. */ public int getAdGroupIndexAfterPositionUs(long positionUs) { - return adPlaybackState.getAdGroupIndexAfterPositionUs(positionUs); + return adPlaybackState.getAdGroupIndexAfterPositionUs(positionUs, durationUs); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index 2a94e884a6..d8335131f9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -89,11 +89,10 @@ public interface MediaSource { public final long windowSequenceNumber; /** - * The end position of the media to play within the media period, in microseconds, or {@link - * C#TIME_END_OF_SOURCE} if the end position is the end of the media period. - * - *

    Note that this only applies if the media period is for content (i.e., not for an ad) and - * is clipped to the position of the next ad group. + * The end position to which the media period's content is clipped in order to play a following + * ad group, in microseconds, or {@link C#TIME_UNSET} if there is no following ad group or if + * this media period is an ad. The value {@link C#TIME_END_OF_SOURCE} indicates that a postroll + * ad follows at the end of this content media period. */ public final long endPositionUs; @@ -115,7 +114,7 @@ public interface MediaSource { * windows this media period is part of. */ public MediaPeriodId(Object periodUid, long windowSequenceNumber) { - this(periodUid, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber, C.TIME_END_OF_SOURCE); + this(periodUid, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber, C.TIME_UNSET); } /** @@ -143,7 +142,7 @@ public interface MediaSource { */ public MediaPeriodId( Object periodUid, int adGroupIndex, int adIndexInAdGroup, long windowSequenceNumber) { - this(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, C.TIME_END_OF_SOURCE); + this(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, C.TIME_UNSET); } private MediaPeriodId( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 41adb78906..fc1355df6a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -310,7 +310,9 @@ public final class AdPlaybackState { * unplayed. Returns {@link C#INDEX_UNSET} if the ad group at or before {@code positionUs} has no * ads remaining to be played, or if there is no such ad group. * - * @param positionUs The position at or before which to find an ad group, in microseconds. + * @param positionUs The position at or before which to find an ad group, in microseconds, or + * {@link C#TIME_END_OF_SOURCE} for the end of the stream (in which case the index of any + * unplayed postroll ad group will be returned). * @return The index of the ad group, or {@link C#INDEX_UNSET}. */ public int getAdGroupIndexForPositionUs(long positionUs) { @@ -327,10 +329,18 @@ public final class AdPlaybackState { * Returns the index of the next ad group after {@code positionUs} that has ads remaining to be * played. Returns {@link C#INDEX_UNSET} if there is no such ad group. * - * @param positionUs The position after which to find an ad group, in microseconds. + * @param positionUs The position after which to find an ad group, in microseconds, or {@link + * C#TIME_END_OF_SOURCE} for the end of the stream (in which case there can be no ad group + * after the position). + * @param periodDurationUs The duration of the containing period in microseconds, or {@link + * C#TIME_UNSET} if not known. * @return The index of the ad group, or {@link C#INDEX_UNSET}. */ - public int getAdGroupIndexAfterPositionUs(long positionUs) { + public int getAdGroupIndexAfterPositionUs(long positionUs, long periodDurationUs) { + if (positionUs == C.TIME_END_OF_SOURCE + || (periodDurationUs != C.TIME_UNSET && positionUs >= periodDurationUs)) { + return C.INDEX_UNSET; + } // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. // In practice we expect there to be few ad groups so the search shouldn't be expensive. int index = 0; @@ -457,6 +467,10 @@ public final class AdPlaybackState { } private boolean isPositionBeforeAdGroup(long positionUs, int adGroupIndex) { + if (positionUs == C.TIME_END_OF_SOURCE) { + // The end of the content is at (but not before) any postroll ad, and after any other ads. + return false; + } long adGroupPositionUs = adGroupTimesUs[adGroupIndex]; if (adGroupPositionUs == C.TIME_END_OF_SOURCE) { return contentDurationUs == C.TIME_UNSET || positionUs < contentDurationUs; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java new file mode 100644 index 0000000000..e8f43e3fe6 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; + +import android.net.Uri; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; +import com.google.android.exoplayer2.source.SinglePeriodTimeline; +import com.google.android.exoplayer2.source.ads.AdPlaybackState; +import com.google.android.exoplayer2.source.ads.SinglePeriodAdTimeline; +import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.upstream.Allocator; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit tests for {@link MediaPeriodQueue}. */ +@RunWith(RobolectricTestRunner.class) +public final class MediaPeriodQueueTest { + + private static final long CONTENT_DURATION_US = 30 * C.MICROS_PER_SECOND; + private static final long FIRST_AD_START_TIME_US = 10 * C.MICROS_PER_SECOND; + private static final long SECOND_AD_START_TIME_US = 20 * C.MICROS_PER_SECOND; + + private static final Timeline CONTENT_TIMELINE = + new SinglePeriodTimeline(CONTENT_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false); + private static final Uri AD_URI = Uri.EMPTY; + + private MediaPeriodQueue mediaPeriodQueue; + private AdPlaybackState adPlaybackState; + private Timeline timeline; + private Object periodUid; + + private PlaybackInfo playbackInfo; + private RendererCapabilities[] rendererCapabilities; + private TrackSelector trackSelector; + private Allocator allocator; + private MediaSource mediaSource; + + @Before + public void setUp() { + mediaPeriodQueue = new MediaPeriodQueue(); + mediaSource = mock(MediaSource.class); + rendererCapabilities = new RendererCapabilities[0]; + trackSelector = mock(TrackSelector.class); + allocator = mock(Allocator.class); + } + + @Test + public void testGetNextMediaPeriodInfo_withoutAds_returnsLastMediaPeriodInfo() { + setupInitialTimeline(/* initialPositionUs= */ 0); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ 0, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ true); + } + + @Test + public void testGetNextMediaPeriodInfo_withPrerollAd_returnsCorrectMediaPeriodInfos() { + setupInitialTimeline(/* initialPositionUs= */ 0, /* adGroupTimesUs= */ 0); + setAdGroupLoaded(/* adGroupIndex= */ 0); + assertNextMediaPeriodInfoIsAd(/* adGroupIndex= */ 0, /* contentPositionUs= */ 0); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ 0, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ true); + } + + @Test + public void testGetNextMediaPeriodInfo_withMidrollAds_returnsCorrectMediaPeriodInfos() { + setupInitialTimeline( + /* initialPositionUs= */ 0, + /* adGroupTimesUs= */ FIRST_AD_START_TIME_US, + SECOND_AD_START_TIME_US); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ 0, + /* endPositionUs= */ FIRST_AD_START_TIME_US, + /* durationUs= */ FIRST_AD_START_TIME_US, + /* isLast= */ false); + // The next media period info should be null as we haven't loaded the ad yet. + advance(); + assertNull(getNextMediaPeriodInfo()); + setAdGroupLoaded(/* adGroupIndex= */ 0); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ FIRST_AD_START_TIME_US, + /* endPositionUs= */ SECOND_AD_START_TIME_US, + /* durationUs= */ SECOND_AD_START_TIME_US, + /* isLast= */ false); + advance(); + setAdGroupLoaded(/* adGroupIndex= */ 1); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 1, /* contentPositionUs= */ SECOND_AD_START_TIME_US); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ SECOND_AD_START_TIME_US, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ true); + } + + @Test + public void testGetNextMediaPeriodInfo_withMidrollAndPostroll_returnsCorrectMediaPeriodInfos() { + setupInitialTimeline( + /* initialPositionUs= */ 0, + /* adGroupTimesUs= */ FIRST_AD_START_TIME_US, + C.TIME_END_OF_SOURCE); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ 0, + /* endPositionUs= */ FIRST_AD_START_TIME_US, + /* durationUs= */ FIRST_AD_START_TIME_US, + /* isLast= */ false); + advance(); + setAdGroupLoaded(/* adGroupIndex= */ 0); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 0, /* contentPositionUs= */ FIRST_AD_START_TIME_US); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ FIRST_AD_START_TIME_US, + /* endPositionUs= */ C.TIME_END_OF_SOURCE, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ false); + advance(); + setAdGroupLoaded(/* adGroupIndex= */ 1); + assertNextMediaPeriodInfoIsAd( + /* adGroupIndex= */ 1, /* contentPositionUs= */ CONTENT_DURATION_US); + advance(); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ CONTENT_DURATION_US, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ true); + } + + @Test + public void testGetNextMediaPeriodInfo_withPostrollLoadError_returnsEmptyFinalMediaPeriodInfo() { + setupInitialTimeline(/* initialPositionUs= */ 0, /* adGroupTimesUs= */ C.TIME_END_OF_SOURCE); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ 0, + /* endPositionUs= */ C.TIME_END_OF_SOURCE, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ false); + advance(); + setAdGroupFailedToLoad(/* adGroupIndex= */ 0); + assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + /* startPositionUs= */ CONTENT_DURATION_US, + /* endPositionUs= */ C.TIME_UNSET, + /* durationUs= */ CONTENT_DURATION_US, + /* isLast= */ true); + } + + private void setupInitialTimeline(long initialPositionUs, long... adGroupTimesUs) { + adPlaybackState = + new AdPlaybackState(adGroupTimesUs).withContentDurationUs(CONTENT_DURATION_US); + timeline = new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState); + periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0); + mediaPeriodQueue.setTimeline(timeline); + playbackInfo = + new PlaybackInfo( + timeline, + /* manifest= */ null, + mediaPeriodQueue.resolveMediaPeriodIdForAds(periodUid, initialPositionUs), + /* startPositionUs= */ 0, + /* contentPositionUs= */ 0, + Player.STATE_READY, + /* isLoading= */ false, + /* trackGroups= */ null, + /* trackSelectorResult= */ null, + /* loadingMediaPeriodId= */ null, + /* bufferedPositionUs= */ 0, + /* totalBufferedDurationUs= */ 0, + /* positionUs= */ 0); + } + + private void advance() { + mediaPeriodQueue.enqueueNextMediaPeriod( + rendererCapabilities, trackSelector, allocator, mediaSource, getNextMediaPeriodInfo()); + mediaPeriodQueue.advancePlayingPeriod(); + } + + private MediaPeriodInfo getNextMediaPeriodInfo() { + return mediaPeriodQueue.getNextMediaPeriodInfo(/* rendererPositionUs= */ 0, playbackInfo); + } + + private void setAdGroupLoaded(int adGroupIndex) { + adPlaybackState = + adPlaybackState + .withAdCount(adGroupIndex, /* adCount= */ 1) + .withAdUri(adGroupIndex, /* adIndexInAdGroup= */ 0, AD_URI); + updateTimeline(); + } + + private void setAdGroupFailedToLoad(int adGroupIndex) { + adPlaybackState = + adPlaybackState + .withAdCount(adGroupIndex, /* adCount= */ 1) + .withAdLoadError(adGroupIndex, /* adIndexInAdGroup= */ 0); + updateTimeline(); + } + + private void updateTimeline() { + timeline = new SinglePeriodAdTimeline(CONTENT_TIMELINE, adPlaybackState); + mediaPeriodQueue.setTimeline(timeline); + } + + private void assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( + long startPositionUs, long endPositionUs, long durationUs, boolean isLast) { + assertThat(getNextMediaPeriodInfo()) + .isEqualTo( + new MediaPeriodInfo( + new MediaPeriodId(periodUid, /* windowSequenceNumber= */ 0, endPositionUs), + startPositionUs, + /* contentPositionUs= */ C.TIME_UNSET, + durationUs, + /* isLastInTimelinePeriod= */ isLast, + /* isFinal= */ isLast)); + } + + private void assertNextMediaPeriodInfoIsAd(int adGroupIndex, long contentPositionUs) { + assertThat(getNextMediaPeriodInfo()) + .isEqualTo( + new MediaPeriodInfo( + new MediaPeriodId( + periodUid, + adGroupIndex, + /* adIndexInAdGroup= */ 0, + /* windowSequenceNumber= */ 0), + /* startPositionUs= */ 0, + contentPositionUs, + /* durationUs= */ C.TIME_UNSET, + /* isLastInTimelinePeriod= */ false, + /* isFinal= */ false)); + } +} diff --git a/library/ui/src/main/res/values-af/strings.xml b/library/ui/src/main/res/values-af/strings.xml index 5b24387028..729d3b9609 100644 --- a/library/ui/src/main/res/values-af/strings.xml +++ b/library/ui/src/main/res/values-af/strings.xml @@ -12,7 +12,7 @@ Herhaal alles Skommel Volskermmodus - VR mode + VR-modus Aflaai Aflaaie Laai tans af diff --git a/library/ui/src/main/res/values-am/strings.xml b/library/ui/src/main/res/values-am/strings.xml index 933019ac9f..bae3776e35 100644 --- a/library/ui/src/main/res/values-am/strings.xml +++ b/library/ui/src/main/res/values-am/strings.xml @@ -12,7 +12,7 @@ ሁሉንም ድገም በውዝ የሙሉ ማያ ሁነታ - VR mode + የቪአር ሁነታ አውርድ የወረዱ በማውረድ ላይ diff --git a/library/ui/src/main/res/values-ar/strings.xml b/library/ui/src/main/res/values-ar/strings.xml index ec79ce48e9..c78aac1e65 100644 --- a/library/ui/src/main/res/values-ar/strings.xml +++ b/library/ui/src/main/res/values-ar/strings.xml @@ -12,7 +12,7 @@ تكرار الكل ترتيب عشوائي وضع ملء الشاشة - VR mode + وضع VR تنزيل التنزيلات جارٍ التنزيل. diff --git a/library/ui/src/main/res/values-az/strings.xml b/library/ui/src/main/res/values-az/strings.xml index 3a80bab572..c2f208e54e 100644 --- a/library/ui/src/main/res/values-az/strings.xml +++ b/library/ui/src/main/res/values-az/strings.xml @@ -12,7 +12,7 @@ Hamısı təkrarlansın Qarışdırın Tam ekran rejimi - VR mode + VR rejimi Endirin Endirmələr Endirilir diff --git a/library/ui/src/main/res/values-b+sr+Latn/strings.xml b/library/ui/src/main/res/values-b+sr+Latn/strings.xml index 23e1463725..6e90860a9c 100644 --- a/library/ui/src/main/res/values-b+sr+Latn/strings.xml +++ b/library/ui/src/main/res/values-b+sr+Latn/strings.xml @@ -12,7 +12,7 @@ Ponovi sve Pusti nasumično Režim celog ekrana - VR mode + VR režim Preuzmi Preuzimanja Preuzima se diff --git a/library/ui/src/main/res/values-be/strings.xml b/library/ui/src/main/res/values-be/strings.xml index 33ae18711a..f0b28d9b2e 100644 --- a/library/ui/src/main/res/values-be/strings.xml +++ b/library/ui/src/main/res/values-be/strings.xml @@ -12,7 +12,7 @@ Паўтарыць усе Перамяшаць Поўнаэкранны рэжым - VR mode + VR-рэжым Спампаваць Спампоўкі Спампоўваецца diff --git a/library/ui/src/main/res/values-bg/strings.xml b/library/ui/src/main/res/values-bg/strings.xml index 50ffac7b0a..fd2b21efce 100644 --- a/library/ui/src/main/res/values-bg/strings.xml +++ b/library/ui/src/main/res/values-bg/strings.xml @@ -12,7 +12,7 @@ Повтаряне на всички Разбъркване Режим на цял екран - VR mode + режим за VR Изтегляне Изтегляния Изтегля се diff --git a/library/ui/src/main/res/values-bs/strings.xml b/library/ui/src/main/res/values-bs/strings.xml index 942921f28e..fce36435fd 100644 --- a/library/ui/src/main/res/values-bs/strings.xml +++ b/library/ui/src/main/res/values-bs/strings.xml @@ -12,7 +12,7 @@ Ponovi sve Izmiješaj Način rada preko cijelog ekrana - VR mode + VR način rada Preuzmi Preuzimanja Preuzimanje diff --git a/library/ui/src/main/res/values-ca/strings.xml b/library/ui/src/main/res/values-ca/strings.xml index c787ff0194..2875ccfe83 100644 --- a/library/ui/src/main/res/values-ca/strings.xml +++ b/library/ui/src/main/res/values-ca/strings.xml @@ -12,7 +12,7 @@ Repeteix tot Reprodueix aleatòriament Mode de pantalla completa - VR mode + Mode RV Baixa Baixades S\'està baixant diff --git a/library/ui/src/main/res/values-cs/strings.xml b/library/ui/src/main/res/values-cs/strings.xml index 31451856fc..7964068543 100644 --- a/library/ui/src/main/res/values-cs/strings.xml +++ b/library/ui/src/main/res/values-cs/strings.xml @@ -12,7 +12,7 @@ Opakovat vše Náhodně Režim celé obrazovky - VR mode + Režim VR Stáhnout Stahování Stahování diff --git a/library/ui/src/main/res/values-da/strings.xml b/library/ui/src/main/res/values-da/strings.xml index 6a446b0963..03409e1cdd 100644 --- a/library/ui/src/main/res/values-da/strings.xml +++ b/library/ui/src/main/res/values-da/strings.xml @@ -12,7 +12,7 @@ Gentag alle Bland Fuld skærm - VR mode + VR-tilstand Download Downloads Downloader diff --git a/library/ui/src/main/res/values-de/strings.xml b/library/ui/src/main/res/values-de/strings.xml index d2b162ba72..a096211b00 100644 --- a/library/ui/src/main/res/values-de/strings.xml +++ b/library/ui/src/main/res/values-de/strings.xml @@ -12,7 +12,7 @@ Alle wiederholen Zufallsmix Vollbildmodus - VR mode + VR-Modus Herunterladen Downloads Wird heruntergeladen diff --git a/library/ui/src/main/res/values-el/strings.xml b/library/ui/src/main/res/values-el/strings.xml index 6eb9fbc6de..598d1c45dc 100644 --- a/library/ui/src/main/res/values-el/strings.xml +++ b/library/ui/src/main/res/values-el/strings.xml @@ -12,7 +12,7 @@ Επανάληψη όλων Τυχαία αναπαραγωγή Λειτουργία πλήρους οθόνης - VR mode + Λειτουργία VR mode Λήψη Λήψεις Λήψη diff --git a/library/ui/src/main/res/values-es-rUS/strings.xml b/library/ui/src/main/res/values-es-rUS/strings.xml index 92604c5e34..1bfc2513ba 100644 --- a/library/ui/src/main/res/values-es-rUS/strings.xml +++ b/library/ui/src/main/res/values-es-rUS/strings.xml @@ -12,7 +12,7 @@ Repetir todo Reproducir aleatoriamente Modo de pantalla completa - VR mode + Modo RV Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-es/strings.xml b/library/ui/src/main/res/values-es/strings.xml index 4c6fcf7aa7..d424331765 100644 --- a/library/ui/src/main/res/values-es/strings.xml +++ b/library/ui/src/main/res/values-es/strings.xml @@ -12,7 +12,7 @@ Repetir todo Reproducir aleatoriamente Modo de pantalla completa - VR mode + Modo RV Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-et/strings.xml b/library/ui/src/main/res/values-et/strings.xml index d631499a78..38ee173bec 100644 --- a/library/ui/src/main/res/values-et/strings.xml +++ b/library/ui/src/main/res/values-et/strings.xml @@ -12,7 +12,7 @@ Korda kõiki Esita juhuslikus järjekorras Täisekraani režiim - VR mode + VR-režiim Allalaadimine Allalaadimised Allalaadimine diff --git a/library/ui/src/main/res/values-eu/strings.xml b/library/ui/src/main/res/values-eu/strings.xml index efdee2239d..8bc5f0670c 100644 --- a/library/ui/src/main/res/values-eu/strings.xml +++ b/library/ui/src/main/res/values-eu/strings.xml @@ -12,7 +12,7 @@ Errepikatu guztiak Erreproduzitu ausaz Pantaila osoko modua - VR mode + EB modua Deskargak Deskargak Deskargatzen diff --git a/library/ui/src/main/res/values-fa/strings.xml b/library/ui/src/main/res/values-fa/strings.xml index 50e52edd84..bef2aebcfe 100644 --- a/library/ui/src/main/res/values-fa/strings.xml +++ b/library/ui/src/main/res/values-fa/strings.xml @@ -12,7 +12,7 @@ تکرار همه درهم حالت تمام‌صفحه - VR mode + حالت واقعیت مجازی بارگیری بارگیری‌ها درحال بارگیری diff --git a/library/ui/src/main/res/values-fi/strings.xml b/library/ui/src/main/res/values-fi/strings.xml index 72819a376e..3280dff99b 100644 --- a/library/ui/src/main/res/values-fi/strings.xml +++ b/library/ui/src/main/res/values-fi/strings.xml @@ -12,7 +12,7 @@ Toista kaikki uudelleen Satunnaistoisto Koko näytön tila - VR mode + VR-tila Lataa Lataukset Ladataan diff --git a/library/ui/src/main/res/values-fr-rCA/strings.xml b/library/ui/src/main/res/values-fr-rCA/strings.xml index 6b8d459117..ce125f7d06 100644 --- a/library/ui/src/main/res/values-fr-rCA/strings.xml +++ b/library/ui/src/main/res/values-fr-rCA/strings.xml @@ -12,7 +12,7 @@ Tout lire en boucle Lecture aléatoire Mode Plein écran - VR mode + Mode RV Télécharger Téléchargements Téléchargement en cours… diff --git a/library/ui/src/main/res/values-fr/strings.xml b/library/ui/src/main/res/values-fr/strings.xml index 487007f573..b143832ca1 100644 --- a/library/ui/src/main/res/values-fr/strings.xml +++ b/library/ui/src/main/res/values-fr/strings.xml @@ -12,7 +12,7 @@ Tout lire en boucle Aléatoire Mode plein écran - VR mode + Mode RV Télécharger Téléchargements Téléchargement… diff --git a/library/ui/src/main/res/values-gl/strings.xml b/library/ui/src/main/res/values-gl/strings.xml index a5420df692..c321d28bec 100644 --- a/library/ui/src/main/res/values-gl/strings.xml +++ b/library/ui/src/main/res/values-gl/strings.xml @@ -12,7 +12,7 @@ Repetir todas as pistas Reprodución aleatoria Modo de pantalla completa - VR mode + Modo RV Descargar Descargas Descargando diff --git a/library/ui/src/main/res/values-hi/strings.xml b/library/ui/src/main/res/values-hi/strings.xml index a94462b4d9..373c0f4303 100644 --- a/library/ui/src/main/res/values-hi/strings.xml +++ b/library/ui/src/main/res/values-hi/strings.xml @@ -12,7 +12,7 @@ सभी को दोहराएं शफ़ल करें फ़ुलस्क्रीन मोड - VR mode + VR मोड डाउनलोड करें डाउनलोड की गई मीडिया फ़ाइलें डाउनलोड हो रहा है diff --git a/library/ui/src/main/res/values-hr/strings.xml b/library/ui/src/main/res/values-hr/strings.xml index 624d7f04fe..9958f9b4dc 100644 --- a/library/ui/src/main/res/values-hr/strings.xml +++ b/library/ui/src/main/res/values-hr/strings.xml @@ -12,7 +12,7 @@ Ponovi sve Reproduciraj nasumično Prikaz na cijelom zaslonu - VR mode + VR način Preuzmi Preuzimanja Preuzimanje diff --git a/library/ui/src/main/res/values-hu/strings.xml b/library/ui/src/main/res/values-hu/strings.xml index e1f4d77e96..f517ef6212 100644 --- a/library/ui/src/main/res/values-hu/strings.xml +++ b/library/ui/src/main/res/values-hu/strings.xml @@ -12,7 +12,7 @@ Összes szám ismétlése Keverés Teljes képernyős mód - VR mode + VR-mód Letöltés Letöltések Letöltés folyamatban diff --git a/library/ui/src/main/res/values-hy/strings.xml b/library/ui/src/main/res/values-hy/strings.xml index 390d848aff..d3060b06c4 100644 --- a/library/ui/src/main/res/values-hy/strings.xml +++ b/library/ui/src/main/res/values-hy/strings.xml @@ -12,7 +12,7 @@ Կրկնել բոլորը Խառնել Լիաէկրան ռեժիմ - VR mode + VR ռեժիմ Ներբեռնել Ներբեռնումներ Ներբեռնում diff --git a/library/ui/src/main/res/values-in/strings.xml b/library/ui/src/main/res/values-in/strings.xml index 5ac1696c8b..06c55fc020 100644 --- a/library/ui/src/main/res/values-in/strings.xml +++ b/library/ui/src/main/res/values-in/strings.xml @@ -12,7 +12,7 @@ Ulangi semua Acak Mode layar penuh - VR mode + Mode VR Download Download Mendownload diff --git a/library/ui/src/main/res/values-is/strings.xml b/library/ui/src/main/res/values-is/strings.xml index 4f0fbf0975..4a0b84db2f 100644 --- a/library/ui/src/main/res/values-is/strings.xml +++ b/library/ui/src/main/res/values-is/strings.xml @@ -12,7 +12,7 @@ Endurtaka allt Stokka Allur skjárinn - VR mode + sýndarveruleikastilling Sækja Niðurhal Sækir diff --git a/library/ui/src/main/res/values-it/strings.xml b/library/ui/src/main/res/values-it/strings.xml index 6d070c6aa9..84da9905b2 100644 --- a/library/ui/src/main/res/values-it/strings.xml +++ b/library/ui/src/main/res/values-it/strings.xml @@ -12,7 +12,7 @@ Ripeti tutto Riproduzione casuale Modalità a schermo intero - VR mode + Modalità VR Scarica Download Download… diff --git a/library/ui/src/main/res/values-ja/strings.xml b/library/ui/src/main/res/values-ja/strings.xml index eea78b2fb5..e159292348 100644 --- a/library/ui/src/main/res/values-ja/strings.xml +++ b/library/ui/src/main/res/values-ja/strings.xml @@ -12,7 +12,7 @@ 全曲をリピート シャッフル 全画面モード - VR mode + VR モード ダウンロード ダウンロード ダウンロードしています diff --git a/library/ui/src/main/res/values-ka/strings.xml b/library/ui/src/main/res/values-ka/strings.xml index 16f3bea5bf..ff879c5dd8 100644 --- a/library/ui/src/main/res/values-ka/strings.xml +++ b/library/ui/src/main/res/values-ka/strings.xml @@ -12,7 +12,7 @@ ყველას გამეორება არეულად დაკვრა სრულეკრანიანი რეჟიმი - VR mode + VR რეჟიმი ჩამოტვირთვა ჩამოტვირთვები მიმდინარეობს ჩამოტვირთვა diff --git a/library/ui/src/main/res/values-kk/strings.xml b/library/ui/src/main/res/values-kk/strings.xml index 26fe5d6019..f634579932 100644 --- a/library/ui/src/main/res/values-kk/strings.xml +++ b/library/ui/src/main/res/values-kk/strings.xml @@ -12,7 +12,7 @@ Барлығын қайталау Араластыру Толық экран режимі - VR mode + VR режимі Жүктеп алу Жүктеп алынғандар Жүктеп алынуда diff --git a/library/ui/src/main/res/values-km/strings.xml b/library/ui/src/main/res/values-km/strings.xml index ce88214f35..17476b16d1 100644 --- a/library/ui/src/main/res/values-km/strings.xml +++ b/library/ui/src/main/res/values-km/strings.xml @@ -12,7 +12,7 @@ លេង​ឡើងវិញ​ទាំងអស់ ច្របល់ មុខងារពេញ​អេក្រង់ - VR mode + មុខងារ VR ទាញយក ទាញយក កំពុង​ទាញ​យក diff --git a/library/ui/src/main/res/values-ko/strings.xml b/library/ui/src/main/res/values-ko/strings.xml index d61346ff90..a4096711f2 100644 --- a/library/ui/src/main/res/values-ko/strings.xml +++ b/library/ui/src/main/res/values-ko/strings.xml @@ -12,7 +12,7 @@ 모두 반복 셔플 전체화면 모드 - VR mode + 가상 현실 모드 다운로드 다운로드 다운로드 중 diff --git a/library/ui/src/main/res/values-ky/strings.xml b/library/ui/src/main/res/values-ky/strings.xml index ee7a90ede7..c85b70f108 100644 --- a/library/ui/src/main/res/values-ky/strings.xml +++ b/library/ui/src/main/res/values-ky/strings.xml @@ -12,7 +12,7 @@ Баарын кайталоо Аралаштыруу Толук экран режими - VR mode + VR режими Жүктөп алуу Жүктөлүп алынгандар Жүктөлүп алынууда diff --git a/library/ui/src/main/res/values-lt/strings.xml b/library/ui/src/main/res/values-lt/strings.xml index b3d296b4d8..12710e41fd 100644 --- a/library/ui/src/main/res/values-lt/strings.xml +++ b/library/ui/src/main/res/values-lt/strings.xml @@ -12,7 +12,7 @@ Kartoti viską Maišyti Viso ekrano režimas - VR mode + VR režimas Atsisiųsti Atsisiuntimai Atsisiunčiama diff --git a/library/ui/src/main/res/values-lv/strings.xml b/library/ui/src/main/res/values-lv/strings.xml index ed61a7a27f..a55570112f 100644 --- a/library/ui/src/main/res/values-lv/strings.xml +++ b/library/ui/src/main/res/values-lv/strings.xml @@ -12,7 +12,7 @@ Atkārtot visu Atskaņot jauktā secībā Pilnekrāna režīms - VR mode + VR režīms Lejupielādēt Lejupielādes Notiek lejupielāde diff --git a/library/ui/src/main/res/values-mk/strings.xml b/library/ui/src/main/res/values-mk/strings.xml index 4ed15c3913..f4382aaeae 100644 --- a/library/ui/src/main/res/values-mk/strings.xml +++ b/library/ui/src/main/res/values-mk/strings.xml @@ -12,7 +12,7 @@ Повтори ги сите Измешај Режим на цел екран - VR mode + Режим на VR Преземи Преземања Се презема diff --git a/library/ui/src/main/res/values-mn/strings.xml b/library/ui/src/main/res/values-mn/strings.xml index 81c90f34c5..9d8fb29408 100644 --- a/library/ui/src/main/res/values-mn/strings.xml +++ b/library/ui/src/main/res/values-mn/strings.xml @@ -12,7 +12,7 @@ Бүгдийг нь дахин тоглуулах Холих Бүтэн дэлгэцийн горим - VR mode + VR горим Татах Татaлт Татаж байна diff --git a/library/ui/src/main/res/values-mr/strings.xml b/library/ui/src/main/res/values-mr/strings.xml index 7b698e5bcf..d1ee83be30 100644 --- a/library/ui/src/main/res/values-mr/strings.xml +++ b/library/ui/src/main/res/values-mr/strings.xml @@ -12,7 +12,7 @@ सर्व रीपीट करा शफल करा फुल स्क्रीन मोड - VR mode + VR मोड डाउनलोड करा डाउनलोड डाउनलोड होत आहे diff --git a/library/ui/src/main/res/values-ms/strings.xml b/library/ui/src/main/res/values-ms/strings.xml index cfe822f2b8..3b3fc3109f 100644 --- a/library/ui/src/main/res/values-ms/strings.xml +++ b/library/ui/src/main/res/values-ms/strings.xml @@ -12,7 +12,7 @@ Ulang semua Rombak Mod skrin penuh - VR mode + Mod VR Muat turun Muat turun Memuat turun diff --git a/library/ui/src/main/res/values-my/strings.xml b/library/ui/src/main/res/values-my/strings.xml index 34e69c2977..68c1f27056 100644 --- a/library/ui/src/main/res/values-my/strings.xml +++ b/library/ui/src/main/res/values-my/strings.xml @@ -12,7 +12,7 @@ အားလုံး ပြန်ကျော့ရန် ရောသမမွှေ မျက်နှာပြင်အပြည့် မုဒ် - VR mode + VR မုဒ် ဒေါင်းလုဒ် လုပ်ရန် ဒေါင်းလုဒ်များ ဒေါင်းလုဒ်လုပ်နေသည် diff --git a/library/ui/src/main/res/values-nb/strings.xml b/library/ui/src/main/res/values-nb/strings.xml index 5f8e862f5d..e24dcbf959 100644 --- a/library/ui/src/main/res/values-nb/strings.xml +++ b/library/ui/src/main/res/values-nb/strings.xml @@ -12,7 +12,7 @@ Gjenta alle Tilfeldig rekkefølge Fullskjermmodus - VR mode + VR-modus Last ned Nedlastinger Laster ned diff --git a/library/ui/src/main/res/values-nl/strings.xml b/library/ui/src/main/res/values-nl/strings.xml index a3401a0114..18dd96ed9e 100644 --- a/library/ui/src/main/res/values-nl/strings.xml +++ b/library/ui/src/main/res/values-nl/strings.xml @@ -12,7 +12,7 @@ Alles herhalen Shuffle Modus \'Volledig scherm\' - VR mode + VR-modus Downloaden Downloads Downloaden diff --git a/library/ui/src/main/res/values-pl/strings.xml b/library/ui/src/main/res/values-pl/strings.xml index adb81b5779..66eb7d81c7 100644 --- a/library/ui/src/main/res/values-pl/strings.xml +++ b/library/ui/src/main/res/values-pl/strings.xml @@ -12,7 +12,7 @@ Powtórz wszystkie Odtwarzanie losowe Tryb pełnoekranowy - VR mode + Tryb VR Pobierz Pobieranie Pobieram diff --git a/library/ui/src/main/res/values-pt-rPT/strings.xml b/library/ui/src/main/res/values-pt-rPT/strings.xml index f5d8092723..5d199683d9 100644 --- a/library/ui/src/main/res/values-pt-rPT/strings.xml +++ b/library/ui/src/main/res/values-pt-rPT/strings.xml @@ -12,7 +12,7 @@ Repetir tudo Reproduzir aleatoriamente Modo de ecrã inteiro - VR mode + Modo de RV Transferir Transferências A transferir… diff --git a/library/ui/src/main/res/values-pt/strings.xml b/library/ui/src/main/res/values-pt/strings.xml index 2e86dd7acd..151d235554 100644 --- a/library/ui/src/main/res/values-pt/strings.xml +++ b/library/ui/src/main/res/values-pt/strings.xml @@ -12,7 +12,7 @@ Repetir tudo Aleatório Modo de tela cheia - VR mode + Modo RV Fazer o download Downloads Fazendo o download diff --git a/library/ui/src/main/res/values-ro/strings.xml b/library/ui/src/main/res/values-ro/strings.xml index a0bd5a0a16..93f5129d13 100644 --- a/library/ui/src/main/res/values-ro/strings.xml +++ b/library/ui/src/main/res/values-ro/strings.xml @@ -12,7 +12,7 @@ Repetați-le pe toate Redați aleatoriu Modul Ecran complet - VR mode + Mod RV Descărcați Descărcări Se descarcă diff --git a/library/ui/src/main/res/values-ru/strings.xml b/library/ui/src/main/res/values-ru/strings.xml index a45022ffb4..77d96f0bc0 100644 --- a/library/ui/src/main/res/values-ru/strings.xml +++ b/library/ui/src/main/res/values-ru/strings.xml @@ -12,7 +12,7 @@ Повторять все Перемешать Полноэкранный режим - VR mode + VR-режим Скачать Скачивания Скачивание… diff --git a/library/ui/src/main/res/values-sk/strings.xml b/library/ui/src/main/res/values-sk/strings.xml index 2d3997ed89..ae6c0fbb12 100644 --- a/library/ui/src/main/res/values-sk/strings.xml +++ b/library/ui/src/main/res/values-sk/strings.xml @@ -12,7 +12,7 @@ Opakovať všetko Náhodne prehrávať Režim celej obrazovky - VR mode + režim VR Stiahnuť Stiahnuté Sťahuje sa diff --git a/library/ui/src/main/res/values-sl/strings.xml b/library/ui/src/main/res/values-sl/strings.xml index 01fa00320a..16d9a6af03 100644 --- a/library/ui/src/main/res/values-sl/strings.xml +++ b/library/ui/src/main/res/values-sl/strings.xml @@ -12,7 +12,7 @@ Ponavljanje vseh Naključno predvajanje Celozaslonski način - VR mode + Način VR Prenos Prenosi Prenašanje diff --git a/library/ui/src/main/res/values-sq/strings.xml b/library/ui/src/main/res/values-sq/strings.xml index 29007fa20e..fcef9643e8 100644 --- a/library/ui/src/main/res/values-sq/strings.xml +++ b/library/ui/src/main/res/values-sq/strings.xml @@ -12,7 +12,7 @@ Përsërit të gjitha Përziej Modaliteti me ekran të plotë - VR mode + Modaliteti RV Shkarko Shkarkimet Po shkarkohet diff --git a/library/ui/src/main/res/values-sr/strings.xml b/library/ui/src/main/res/values-sr/strings.xml index 659fb113bc..19018f0bac 100644 --- a/library/ui/src/main/res/values-sr/strings.xml +++ b/library/ui/src/main/res/values-sr/strings.xml @@ -12,7 +12,7 @@ Понови све Пусти насумично Режим целог екрана - VR mode + ВР режим Преузми Преузимања Преузима се diff --git a/library/ui/src/main/res/values-sv/strings.xml b/library/ui/src/main/res/values-sv/strings.xml index d9c48aaebe..1f3e7db320 100644 --- a/library/ui/src/main/res/values-sv/strings.xml +++ b/library/ui/src/main/res/values-sv/strings.xml @@ -12,7 +12,7 @@ Upprepa alla Blanda spår Helskärmsläge - VR mode + VR-läge Ladda ned Nedladdningar Laddar ned diff --git a/library/ui/src/main/res/values-sw/strings.xml b/library/ui/src/main/res/values-sw/strings.xml index 83e6b7bc47..b699116dd7 100644 --- a/library/ui/src/main/res/values-sw/strings.xml +++ b/library/ui/src/main/res/values-sw/strings.xml @@ -12,7 +12,7 @@ Rudia zote Changanya Hali ya skrini nzima - VR mode + Hali ya Uhalisia Pepe Pakua Vipakuliwa Inapakua diff --git a/library/ui/src/main/res/values-th/strings.xml b/library/ui/src/main/res/values-th/strings.xml index 454a0f361d..7827c99d93 100644 --- a/library/ui/src/main/res/values-th/strings.xml +++ b/library/ui/src/main/res/values-th/strings.xml @@ -12,7 +12,7 @@ เล่นซ้ำทั้งหมด สุ่ม โหมดเต็มหน้าจอ - VR mode + โหมด VR ดาวน์โหลด ดาวน์โหลด กำลังดาวน์โหลด diff --git a/library/ui/src/main/res/values-tr/strings.xml b/library/ui/src/main/res/values-tr/strings.xml index 43bf4f885f..d47ca1e2aa 100644 --- a/library/ui/src/main/res/values-tr/strings.xml +++ b/library/ui/src/main/res/values-tr/strings.xml @@ -12,7 +12,7 @@ Tümünü tekrarla Karıştır Tam ekran modu - VR mode + VR modu İndir İndirilenler İndiriliyor diff --git a/library/ui/src/main/res/values-uk/strings.xml b/library/ui/src/main/res/values-uk/strings.xml index 0b00dbbe82..5f3097a256 100644 --- a/library/ui/src/main/res/values-uk/strings.xml +++ b/library/ui/src/main/res/values-uk/strings.xml @@ -12,7 +12,7 @@ Повторити всі Перемішати Повноекранний режим - VR mode + Режим віртуальної реальності Завантажити Завантаження Завантажується diff --git a/library/ui/src/main/res/values-uz/strings.xml b/library/ui/src/main/res/values-uz/strings.xml index b5cea1f59e..9427fc4149 100644 --- a/library/ui/src/main/res/values-uz/strings.xml +++ b/library/ui/src/main/res/values-uz/strings.xml @@ -12,7 +12,7 @@ Hammasini takrorlash Aralash Butun ekran rejimi - VR mode + VR rejimi Yuklab olish Yuklanmalar Yuklab olinmoqda diff --git a/library/ui/src/main/res/values-vi/strings.xml b/library/ui/src/main/res/values-vi/strings.xml index d5299d3e6d..ae6abb90f5 100644 --- a/library/ui/src/main/res/values-vi/strings.xml +++ b/library/ui/src/main/res/values-vi/strings.xml @@ -12,7 +12,7 @@ Lặp lại tất cả Phát ngẫu nhiên Chế độ toàn màn hình - VR mode + Chế độ thực tế ảo Tải xuống Tài nguyên đã tải xuống Đang tải xuống diff --git a/library/ui/src/main/res/values-zh-rCN/strings.xml b/library/ui/src/main/res/values-zh-rCN/strings.xml index ad3e4fe68d..cfb9a6fb11 100644 --- a/library/ui/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui/src/main/res/values-zh-rCN/strings.xml @@ -12,7 +12,7 @@ 全部重复播放 随机播放 全屏模式 - VR mode + VR 模式 下载 下载内容 正在下载 diff --git a/library/ui/src/main/res/values-zh-rHK/strings.xml b/library/ui/src/main/res/values-zh-rHK/strings.xml index f538231146..be96b852f5 100644 --- a/library/ui/src/main/res/values-zh-rHK/strings.xml +++ b/library/ui/src/main/res/values-zh-rHK/strings.xml @@ -12,7 +12,7 @@ 全部重複播放 隨機播放 全螢幕模式 - VR mode + 虛擬現實模式 下載 下載內容 正在下載 diff --git a/library/ui/src/main/res/values-zh-rTW/strings.xml b/library/ui/src/main/res/values-zh-rTW/strings.xml index da4b01cc93..c17c9e44ed 100644 --- a/library/ui/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui/src/main/res/values-zh-rTW/strings.xml @@ -12,7 +12,7 @@ 重複播放所有項目 隨機播放 全螢幕模式 - VR mode + 虛擬實境模式 下載 下載 下載中 diff --git a/library/ui/src/main/res/values-zu/strings.xml b/library/ui/src/main/res/values-zu/strings.xml index 4b80136a85..70eccd3c36 100644 --- a/library/ui/src/main/res/values-zu/strings.xml +++ b/library/ui/src/main/res/values-zu/strings.xml @@ -12,7 +12,7 @@ Phinda konke Shova Imodi yesikrini esigcwele - VR mode + Inqubo ye-VR Landa Ukulandwa Iyalanda From 53e06e636df1c63ae8de35a3470861b019b9753e Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 31 Oct 2018 03:06:40 -0700 Subject: [PATCH 139/832] Clean up ExoHostedTest. Some test specific things, like adding an extra listener or adding decoder counters are currently handled by ExoHostedTest. They can be moved to the classes actually using them to clean up ExoHostedTest. This also adds more flexibility to further generic clean-up in onTestFinished. Also, reorder player setup to what ExoPlayerTestRunner is doing. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219446276 --- .../playbacktests/gts/DashTestRunner.java | 6 +--- .../exoplayer2/testutil/ExoHostedTest.java | 35 ++++--------------- 2 files changed, 7 insertions(+), 34 deletions(-) diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java index 1a43a70f25..6ec2a825c3 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java @@ -323,7 +323,7 @@ public final class DashTestRunner { } @Override - protected void logMetrics(DecoderCounters audioCounters, DecoderCounters videoCounters) { + protected void onTestFinished(DecoderCounters audioCounters, DecoderCounters videoCounters) { metricsLogger.logMetric(MetricsLogger.KEY_TEST_NAME, streamName); metricsLogger.logMetric(MetricsLogger.KEY_IS_CDD_LIMITED_RETRY, isCddLimitedRetry); metricsLogger.logMetric(MetricsLogger.KEY_FRAMES_DROPPED_COUNT, @@ -335,10 +335,7 @@ public final class DashTestRunner { metricsLogger.logMetric(MetricsLogger.KEY_FRAMES_RENDERED_COUNT, videoCounters.renderedOutputBufferCount); metricsLogger.close(); - } - @Override - protected void assertPassed(DecoderCounters audioCounters, DecoderCounters videoCounters) { if (fullPlaybackNoSeeking) { // We shouldn't have skipped any output buffers. DecoderCountersUtil @@ -372,7 +369,6 @@ public final class DashTestRunner { } } } - } private static final class DashTestTrackSelector extends DefaultTrackSelector { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java index ad59ca7152..74c0d4bb43 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoHostedTest.java @@ -74,7 +74,6 @@ public abstract class ExoHostedTest implements AnalyticsListener, HostedTest { private SimpleExoPlayer player; private Surface surface; private ExoPlaybackException playerError; - private AnalyticsListener analyticsListener; private boolean playerWasPrepared; private boolean playing; @@ -127,14 +126,6 @@ public abstract class ExoHostedTest implements AnalyticsListener, HostedTest { } } - /** Sets an {@link AnalyticsListener} to listen for events during the test. */ - public final void setAnalyticsListener(AnalyticsListener analyticsListener) { - this.analyticsListener = analyticsListener; - if (player != null) { - player.addAnalyticsListener(analyticsListener); - } - } - // HostedTest implementation @Override @@ -145,19 +136,16 @@ public abstract class ExoHostedTest implements AnalyticsListener, HostedTest { String userAgent = "ExoPlayerPlaybackTests"; DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent); player = buildExoPlayer(host, surface, trackSelector, drmSessionManager); - player.prepare(buildSource(host, Util.getUserAgent(host, userAgent))); + player.setPlayWhenReady(true); player.addAnalyticsListener(this); player.addAnalyticsListener(new EventLogger(trackSelector, tag)); - if (analyticsListener != null) { - player.addAnalyticsListener(analyticsListener); - } - player.setPlayWhenReady(true); - actionHandler = Clock.DEFAULT.createHandler(Looper.myLooper(), /* callback= */ null); // Schedule any pending actions. + actionHandler = Clock.DEFAULT.createHandler(Looper.myLooper(), /* callback= */ null); if (pendingSchedule != null) { pendingSchedule.start(player, trackSelector, surface, actionHandler, /* callback= */ null); pendingSchedule = null; } + player.prepare(buildSource(host, Util.getUserAgent(host, userAgent))); } @Override @@ -172,10 +160,10 @@ public abstract class ExoHostedTest implements AnalyticsListener, HostedTest { @Override public final void onFinished() { + onTestFinished(audioDecoderCounters, videoDecoderCounters); if (failOnPlayerError && playerError != null) { throw new Error(playerError); } - logMetrics(audioDecoderCounters, videoDecoderCounters); if (expectedPlayingTimeMs != EXPECTED_PLAYING_TIME_UNSET) { long playingTimeToAssertMs = expectedPlayingTimeMs == EXPECTED_PLAYING_TIME_MEDIA_DURATION_MS ? sourceDurationMs : expectedPlayingTimeMs; @@ -189,8 +177,6 @@ public abstract class ExoHostedTest implements AnalyticsListener, HostedTest { && totalPlayingTimeMs <= maxAllowedActualPlayingTimeMs) .isTrue(); } - // Make any additional assertions. - assertPassed(audioDecoderCounters, videoDecoderCounters); } // AnalyticsListener @@ -251,12 +237,10 @@ public abstract class ExoHostedTest implements AnalyticsListener, HostedTest { return null; } - @SuppressWarnings("unused") protected DefaultTrackSelector buildTrackSelector(HostActivity host) { return new DefaultTrackSelector(new AdaptiveTrackSelection.Factory()); } - @SuppressWarnings("unused") protected SimpleExoPlayer buildExoPlayer( HostActivity host, Surface surface, @@ -274,20 +258,13 @@ public abstract class ExoHostedTest implements AnalyticsListener, HostedTest { return player; } - @SuppressWarnings("unused") protected abstract MediaSource buildSource(HostActivity host, String userAgent); - @SuppressWarnings("unused") protected void onPlayerErrorInternal(ExoPlaybackException error) { // Do nothing. Interested subclasses may override. } - protected void logMetrics(DecoderCounters audioCounters, DecoderCounters videoCounters) { - // Do nothing. Subclasses may override to log metrics. + protected void onTestFinished(DecoderCounters audioCounters, DecoderCounters videoCounters) { + // Do nothing. Subclasses may override to add clean-up and assertions. } - - protected void assertPassed(DecoderCounters audioCounters, DecoderCounters videoCounters) { - // Do nothing. Subclasses may override to add additional assertions. - } - } From 7d99794bb8f731484416053a0fa471eda2b24f37 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 31 Oct 2018 03:37:24 -0700 Subject: [PATCH 140/832] Allow MP4s with truncated stco to be played ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219448836 --- .../exoplayer2/extractor/mp4/AtomParsers.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 7104630a23..0cda3fafa8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -221,11 +221,22 @@ import java.util.List; for (int i = 0; i < sampleCount; i++) { // Advance to the next chunk if necessary. - while (remainingSamplesInChunk == 0) { - Assertions.checkState(chunkIterator.moveNext()); + boolean chunkDataComplete = true; + while (remainingSamplesInChunk == 0 && (chunkDataComplete = chunkIterator.moveNext())) { offset = chunkIterator.offset; remainingSamplesInChunk = chunkIterator.numSamples; } + if (!chunkDataComplete) { + Log.w(TAG, "Unexpected end of chunk data"); + sampleCount = i; + offsets = Arrays.copyOf(offsets, sampleCount); + sizes = Arrays.copyOf(sizes, sampleCount); + timestamps = Arrays.copyOf(timestamps, sampleCount); + flags = Arrays.copyOf(flags, sampleCount); + remainingSamplesAtTimestampOffset = 0; + remainingTimestampOffsetChanges = 0; + break; + } // Add on the timestamp offset if ctts is present. if (ctts != null) { From 5f6c907c652c70f44869a7a7e7f70573fc88d89c Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 31 Oct 2018 04:58:25 -0700 Subject: [PATCH 141/832] Reset DefaultBandwidthMeter on network type change ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219454931 --- .../upstream/DefaultBandwidthMeter.java | 171 ++++++++++++++++-- .../exoplayer2/util/SlidingPercentile.java | 8 + .../google/android/exoplayer2/util/Util.java | 2 +- .../upstream/DefaultBandwidthMeterTest.java | 74 ++++++-- 4 files changed, 225 insertions(+), 30 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index 6be13603ee..eef86a3aab 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -15,8 +15,13 @@ */ package com.google.android.exoplayer2.upstream; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; import android.os.Handler; +import android.os.Looper; import android.support.annotation.Nullable; import android.util.SparseArray; import com.google.android.exoplayer2.C; @@ -25,9 +30,12 @@ import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.EventDispatcher; import com.google.android.exoplayer2.util.SlidingPercentile; import com.google.android.exoplayer2.util.Util; +import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Estimates bandwidth by listening to data transfers. @@ -79,6 +87,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private SparseArray initialBitrateEstimates; private int slidingWindowMaxWeight; private Clock clock; + private boolean resetOnNetworkTypeChange; /** * Creates a builder with default parameters and without listener. @@ -119,9 +128,8 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } /** - * Sets the initial bitrate estimate in bits per second for a network type that should be - * assumed when a bandwidth estimate is unavailable and the current network connection is of the - * specified type. + * Sets the initial bitrate estimate in bits per second that should be assumed when a bandwidth + * estimate is unavailable and the current network connection is of the specified type. * * @param networkType The {@link C.NetworkType} this initial estimate is for. * @param initialBitrateEstimate The initial bitrate estimate in bits per second. @@ -159,16 +167,29 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList return this; } + /** + * Sets whether to reset if the network type changes. + * + * @param resetOnNetworkTypeChange Whether to reset if the network type changes. + * @return This builder. + */ + public Builder experimental_resetOnNetworkTypeChange(boolean resetOnNetworkTypeChange) { + this.resetOnNetworkTypeChange = resetOnNetworkTypeChange; + return this; + } + /** * Builds the bandwidth meter. * * @return A bandwidth meter with the configured properties. */ public DefaultBandwidthMeter build() { - DefaultBandwidthMeter bandwidthMeter = - new DefaultBandwidthMeter( - context, initialBitrateEstimates, slidingWindowMaxWeight, clock); - return bandwidthMeter; + return new DefaultBandwidthMeter( + context, + initialBitrateEstimates, + slidingWindowMaxWeight, + clock, + resetOnNetworkTypeChange); } private static SparseArray getInitialBitrateEstimatesForCountry(String countryCode) { @@ -195,6 +216,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; private static final int BYTES_TRANSFERRED_FOR_ESTIMATE = 512 * 1024; + @Nullable private final Context context; private final SparseArray initialBitrateEstimates; private final EventDispatcher eventDispatcher; private final SlidingPercentile slidingPercentile; @@ -204,9 +226,14 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private long sampleStartTimeMs; private long sampleBytesTransferred; + @C.NetworkType private int networkType; private long totalElapsedTimeMs; private long totalBytesTransferred; private long bitrateEstimate; + private long lastReportedBitrateEstimate; + + private boolean networkTypeOverrideSet; + @C.NetworkType private int networkTypeOverride; /** @deprecated Use {@link Builder} instead. */ @Deprecated @@ -215,34 +242,44 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList /* context= */ null, /* initialBitrateEstimates= */ new SparseArray<>(), DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, - Clock.DEFAULT); + Clock.DEFAULT, + /* resetOnNetworkTypeChange= */ false); } private DefaultBandwidthMeter( @Nullable Context context, SparseArray initialBitrateEstimates, int maxWeight, - Clock clock) { + Clock clock, + boolean resetOnNetworkTypeChange) { + this.context = context == null ? null : context.getApplicationContext(); this.initialBitrateEstimates = initialBitrateEstimates; this.eventDispatcher = new EventDispatcher<>(); this.slidingPercentile = new SlidingPercentile(maxWeight); this.clock = clock; - bitrateEstimate = - getInitialBitrateEstimateForNetworkType( - context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context)); + // Set the initial network type and bitrate estimate + networkType = context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context); + bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); + // Register to receive connectivity actions if possible. + if (context != null && resetOnNetworkTypeChange) { + ConnectivityActionReceiver connectivityActionReceiver = + ConnectivityActionReceiver.getInstance(context); + connectivityActionReceiver.register(/* bandwidthMeter= */ this); + } } /** * Overrides the network type. Handled in the same way as if the meter had detected a change from - * the current network type to the specified network type. + * the current network type to the specified network type internally. * *

    Applications should not normally call this method. It is intended for testing purposes. * * @param networkType The overriding network type. */ public synchronized void setNetworkTypeOverride(@C.NetworkType int networkType) { - // TODO: Handle properly as a network change (in same way as non-external network changes). - bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); + networkTypeOverride = networkType; + networkTypeOverrideSet = true; + onConnectivityAction(); } @Override @@ -309,14 +346,50 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList || totalBytesTransferred >= BYTES_TRANSFERRED_FOR_ESTIMATE) { bitrateEstimate = (long) slidingPercentile.getPercentile(0.5f); } - notifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); + maybeNotifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); sampleStartTimeMs = nowMs; sampleBytesTransferred = 0; } // Else any sample bytes transferred will be carried forward into the next sample. streamCount--; } - private void notifyBandwidthSample(int elapsedMs, long bytesTransferred, long bitrateEstimate) { + private synchronized void onConnectivityAction() { + int networkType = + networkTypeOverrideSet + ? networkTypeOverride + : (context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context)); + if (this.networkType == networkType) { + return; + } + + this.networkType = networkType; + if (networkType == C.NETWORK_TYPE_OFFLINE + || networkType == C.NETWORK_TYPE_UNKNOWN + || networkType == C.NETWORK_TYPE_OTHER) { + // It's better not to reset the bandwidth meter for these network types. + return; + } + + // Reset the bitrate estimate and report it, along with any bytes transferred. + this.bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); + long nowMs = clock.elapsedRealtime(); + int sampleElapsedTimeMs = streamCount > 0 ? (int) (nowMs - sampleStartTimeMs) : 0; + maybeNotifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); + + // Reset the remainder of the state. + sampleStartTimeMs = nowMs; + sampleBytesTransferred = 0; + totalBytesTransferred = 0; + totalElapsedTimeMs = 0; + slidingPercentile.reset(); + } + + private void maybeNotifyBandwidthSample( + int elapsedMs, long bytesTransferred, long bitrateEstimate) { + if (elapsedMs == 0 && bytesTransferred == 0 && bitrateEstimate == lastReportedBitrateEstimate) { + return; + } + lastReportedBitrateEstimate = bitrateEstimate; eventDispatcher.dispatch( listener -> listener.onBandwidthSample(elapsedMs, bytesTransferred, bitrateEstimate)); } @@ -332,6 +405,70 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList return initialBitrateEstimate; } + /* + * Note: This class only holds a weak reference to DefaultBandwidthMeter instances. It should not + * be made non-static, since doing so adds a strong reference (i.e. DefaultBandwidthMeter.this). + */ + private static class ConnectivityActionReceiver extends BroadcastReceiver { + + @MonotonicNonNull private static ConnectivityActionReceiver staticInstance; + + private final Handler mainHandler; + private final ArrayList> bandwidthMeters; + + public static synchronized ConnectivityActionReceiver getInstance(Context context) { + if (staticInstance == null) { + staticInstance = new ConnectivityActionReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + context.registerReceiver(staticInstance, filter); + } + return staticInstance; + } + + private ConnectivityActionReceiver() { + mainHandler = new Handler(Looper.getMainLooper()); + bandwidthMeters = new ArrayList<>(); + } + + public synchronized void register(DefaultBandwidthMeter bandwidthMeter) { + removeClearedReferences(); + bandwidthMeters.add(new WeakReference<>(bandwidthMeter)); + // Simulate an initial update on the main thread (like the sticky broadcast we'd receive if + // we were to register a separate broadcast receiver for each bandwidth meter). + mainHandler.post(() -> updateBandwidthMeter(bandwidthMeter)); + } + + @Override + public synchronized void onReceive(Context context, Intent intent) { + if (isInitialStickyBroadcast()) { + return; + } + removeClearedReferences(); + for (int i = 0; i < bandwidthMeters.size(); i++) { + WeakReference bandwidthMeterReference = bandwidthMeters.get(i); + DefaultBandwidthMeter bandwidthMeter = bandwidthMeterReference.get(); + if (bandwidthMeter != null) { + updateBandwidthMeter(bandwidthMeter); + } + } + } + + private void updateBandwidthMeter(DefaultBandwidthMeter bandwidthMeter) { + bandwidthMeter.onConnectivityAction(); + } + + private void removeClearedReferences() { + for (int i = bandwidthMeters.size() - 1; i >= 0; i--) { + WeakReference bandwidthMeterReference = bandwidthMeters.get(i); + DefaultBandwidthMeter bandwidthMeter = bandwidthMeterReference.get(); + if (bandwidthMeter == null) { + bandwidthMeters.remove(i); + } + } + } + } + private static Map createInitialBitrateCountryGroupAssignment() { HashMap countryGroupAssignment = new HashMap<>(); countryGroupAssignment.put("AD", new int[] {1, 0, 0, 0}); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java b/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java index f9be1a53b2..c9c21023c3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java @@ -65,6 +65,14 @@ public class SlidingPercentile { currentSortOrder = SORT_ORDER_NONE; } + /** Resets the sliding percentile. */ + public void reset() { + samples.clear(); + currentSortOrder = SORT_ORDER_NONE; + nextSampleIndex = 0; + totalWeight = 0; + } + /** * Adds a new weighted value. * 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 116ad860e0..410e1c174c 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 @@ -1652,7 +1652,7 @@ public final class Util { return getMobileNetworkType(networkInfo); case ConnectivityManager.TYPE_ETHERNET: return C.NETWORK_TYPE_ETHERNET; - default: // Ethernet, VPN, Bluetooth, Dummy. + default: // VPN, Bluetooth, Dummy. return C.NETWORK_TYPE_OTHER; } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java index ebdb45909b..ec5d82e712 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java @@ -15,15 +15,18 @@ */ package com.google.android.exoplayer2.upstream; -import static android.Manifest.permission.ACCESS_NETWORK_STATE; import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; +import android.net.Uri; import android.telephony.TelephonyManager; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.testutil.FakeClock; +import com.google.android.exoplayer2.testutil.FakeDataSource; +import java.util.Random; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,6 +39,7 @@ import org.robolectric.shadows.ShadowNetworkInfo; @RunWith(RobolectricTestRunner.class) public final class DefaultBandwidthMeterTest { + private static final int SIMULATED_TRANSFER_COUNT = 100; private static final String FAST_COUNTRY_ISO = "EE"; private static final String SLOW_COUNTRY_ISO = "PG"; @@ -496,6 +500,44 @@ public final class DefaultBandwidthMeterTest { } @Test + public void networkTypeOverride_updatesBitrateEstimate() { + setActiveNetworkInfo(networkInfoEthernet); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateEthernet = bandwidthMeter.getBitrateEstimate(); + + bandwidthMeter.setNetworkTypeOverride(C.NETWORK_TYPE_2G); + long initialEstimate2g = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimateEthernet).isGreaterThan(initialEstimate2g); + } + + @Test + public void networkTypeOverride_doesFullReset() { + // Simulate transfers for an ethernet connection. + setActiveNetworkInfo(networkInfoEthernet); + FakeClock clock = new FakeClock(/* initialTimeMs= */ 0); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).setClock(clock).build(); + long[] bitrateEstimatesWithNewInstance = simulateTransfers(bandwidthMeter, clock); + + // Create a new instance and seed with some transfers. + setActiveNetworkInfo(networkInfo2g); + bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).setClock(clock).build(); + simulateTransfers(bandwidthMeter, clock); + + // Override the network type to ethernet and simulate transfers again. + bandwidthMeter.setNetworkTypeOverride(C.NETWORK_TYPE_ETHERNET); + long[] bitrateEstimatesAfterReset = simulateTransfers(bandwidthMeter, clock); + + // If overriding the network type fully reset the bandwidth meter, we expect the bitrate + // estimates generated during simulation to be the same. + assertThat(bitrateEstimatesAfterReset).isEqualTo(bitrateEstimatesWithNewInstance); + } + + @Test + @SuppressWarnings("deprecation") public void defaultInitialBitrateEstimate_withoutContext_isReasonable() { DefaultBandwidthMeter bandwidthMeterWithBuilder = new DefaultBandwidthMeter.Builder(/* context= */ null).build(); @@ -510,17 +552,6 @@ public final class DefaultBandwidthMeterTest { assertThat(initialEstimateWithoutBuilder).isLessThan(50_000_000L); } - @Test - public void defaultInitialBitrateEstimate_withoutAccessNetworkStatePermission_isReasonable() { - Shadows.shadowOf(RuntimeEnvironment.application).denyPermissions(ACCESS_NETWORK_STATE); - DefaultBandwidthMeter bandwidthMeter = - new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); - long initialEstimate = bandwidthMeter.getBitrateEstimate(); - - assertThat(initialEstimate).isGreaterThan(100_000L); - assertThat(initialEstimate).isLessThan(50_000_000L); - } - private void setActiveNetworkInfo(NetworkInfo networkInfo) { Shadows.shadowOf(connectivityManager).setActiveNetworkInfo(networkInfo); } @@ -528,4 +559,23 @@ public final class DefaultBandwidthMeterTest { private void setNetworkCountryIso(String countryIso) { Shadows.shadowOf(telephonyManager).setNetworkCountryIso(countryIso); } + + private static long[] simulateTransfers(DefaultBandwidthMeter bandwidthMeter, FakeClock clock) { + long[] bitrateEstimates = new long[SIMULATED_TRANSFER_COUNT]; + Random random = new Random(/* seed= */ 0); + DataSource dataSource = new FakeDataSource(); + DataSpec dataSpec = new DataSpec(Uri.parse("https://dummy.com")); + for (int i = 0; i < SIMULATED_TRANSFER_COUNT; i++) { + bandwidthMeter.onTransferStart(dataSource, dataSpec, /* isNetwork= */ true); + clock.advanceTime(random.nextInt(/* bound= */ 5000)); + bandwidthMeter.onBytesTransferred( + dataSource, + dataSpec, + /* isNetwork= */ true, + /* bytes= */ random.nextInt(5 * 1024 * 1024)); + bandwidthMeter.onTransferEnd(dataSource, dataSpec, /* isNetwork= */ true); + bitrateEstimates[i] = bandwidthMeter.getBitrateEstimate(); + } + return bitrateEstimates; + } } From 58f50ca6555eb60e87e84e717a3c49e0cee17703 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 31 Oct 2018 04:59:04 -0700 Subject: [PATCH 142/832] Clarify Java 8 gradle requirement in developer guide. Issue:#5026 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219454985 --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b69be03ae4..2b6a508aaa 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,15 @@ following will add a dependency to the full library: implementation 'com.google.android.exoplayer:exoplayer:2.X.X' ``` -where `2.X.X` is your preferred version. Alternatively, you can depend on only -the library modules that you actually need. For example the following will add -dependencies on the Core, DASH and UI library modules, as might be required for -an app that plays DASH content: +where `2.X.X` is your preferred version. If not enabled already, you also need +to turn on Java 8 support in all `build.gradle` files depending on ExoPlayer, by +adding `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to the +`android` section. + +As an alternative to the full library, you can depend on only the library +modules that you actually need. For example the following will add dependencies +on the Core, DASH and UI library modules, as might be required for an app that +plays DASH content: ```gradle implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X' From db832d691b0a9279c8c6103ad73d0ac9dadf3d28 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 31 Oct 2018 07:36:49 -0700 Subject: [PATCH 143/832] Add equals and hashCode to MediaItem ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219470655 --- .../exoplayer2/ext/cast/MediaItem.java | 75 +++++++++++++++++++ .../exoplayer2/ext/cast/MediaItemTest.java | 64 ++++++++++++++-- 2 files changed, 133 insertions(+), 6 deletions(-) diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java index 02a8d1b5d4..8ab10e165d 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItem.java @@ -19,6 +19,7 @@ import android.net.Uri; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -189,6 +190,26 @@ public final class MediaItem { this.uri = uri; this.requestHeaders = Collections.unmodifiableMap(new HashMap<>(requestHeaders)); } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + UriBundle uriBundle = (UriBundle) other; + return uri.equals(uriBundle.uri) && requestHeaders.equals(uriBundle.requestHeaders); + } + + @Override + public int hashCode() { + int result = uri.hashCode(); + result = 31 * result + requestHeaders.hashCode(); + return result; + } } /** @@ -216,6 +237,26 @@ public final class MediaItem { this.uuid = uuid; this.licenseServer = licenseServer; } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + DrmScheme drmScheme = (DrmScheme) other; + return uuid.equals(drmScheme.uuid) && Util.areEqual(licenseServer, drmScheme.licenseServer); + } + + @Override + public int hashCode() { + int result = uuid.hashCode(); + result = 31 * result + (licenseServer != null ? licenseServer.hashCode() : 0); + return result; + } } /** @@ -270,6 +311,40 @@ public final class MediaItem { // TODO: Add support for sideloaded tracks, artwork, icon, and subtitle. + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + MediaItem mediaItem = (MediaItem) other; + return startPositionUs == mediaItem.startPositionUs + && endPositionUs == mediaItem.endPositionUs + && uuid.equals(mediaItem.uuid) + && title.equals(mediaItem.title) + && description.equals(mediaItem.description) + && media.equals(mediaItem.media) + && Util.areEqual(attachment, mediaItem.attachment) + && drmSchemes.equals(mediaItem.drmSchemes) + && mimeType.equals(mediaItem.mimeType); + } + + @Override + public int hashCode() { + int result = uuid.hashCode(); + result = 31 * result + title.hashCode(); + result = 31 * result + description.hashCode(); + result = 31 * result + media.hashCode(); + result = 31 * result + (attachment != null ? attachment.hashCode() : 0); + result = 31 * result + drmSchemes.hashCode(); + result = 31 * result + (int) (startPositionUs ^ (startPositionUs >>> 32)); + result = 31 * result + (int) (endPositionUs ^ (endPositionUs >>> 32)); + result = 31 * result + mimeType.hashCode(); + return result; + } + private MediaItem( UUID uuid, String title, diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java index 3c7ec6cab3..f176fe9c6f 100644 --- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java +++ b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java @@ -20,6 +20,9 @@ import static com.google.common.truth.Truth.assertThat; import android.net.Uri; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.MimeTypes; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.UUID; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,18 +50,15 @@ public class MediaItemTest { MediaItem.Builder builder = new MediaItem.Builder(); MediaItem item1 = builder + .setUuid(new UUID(0, 1)) .setMedia("http://example.com") .setTitle("title") .setMimeType(MimeTypes.AUDIO_MP4) .setStartPositionUs(3) .setEndPositionUs(4) .build(); - MediaItem item2 = builder.build(); - assertThat(item1.title).isEqualTo(item2.title); - assertThat(item1.media.uri).isEqualTo(item2.media.uri); - assertThat(item1.mimeType).isEqualTo(item2.mimeType); - assertThat(item1.startPositionUs).isEqualTo(item2.startPositionUs); - assertThat(item1.endPositionUs).isEqualTo(item2.endPositionUs); + MediaItem item2 = builder.setUuid(new UUID(0, 1)).build(); + assertThat(item1).isEqualTo(item2); } @Test @@ -79,6 +79,42 @@ public class MediaItemTest { assertDefaultValues(builder.build()); } + @Test + public void equals_withEqualDrmSchemes_returnsTrue() { + MediaItem.Builder builder = new MediaItem.Builder(); + MediaItem mediaItem1 = + builder + .setUuid(new UUID(0, 1)) + .setMedia("www.google.com") + .setDrmSchemes(createMockDrmSchemes(1)) + .buildAndClear(); + MediaItem mediaItem2 = + builder + .setUuid(new UUID(0, 1)) + .setMedia("www.google.com") + .setDrmSchemes(createMockDrmSchemes(1)) + .buildAndClear(); + assertThat(mediaItem1).isEqualTo(mediaItem2); + } + + @Test + public void equals_withDifferentDrmRequestHeaders_returnsFalse() { + MediaItem.Builder builder = new MediaItem.Builder(); + MediaItem mediaItem1 = + builder + .setUuid(new UUID(0, 1)) + .setMedia("www.google.com") + .setDrmSchemes(createMockDrmSchemes(1)) + .buildAndClear(); + MediaItem mediaItem2 = + builder + .setUuid(new UUID(0, 1)) + .setMedia("www.google.com") + .setDrmSchemes(createMockDrmSchemes(2)) + .buildAndClear(); + assertThat(mediaItem1).isNotEqualTo(mediaItem2); + } + private static void assertDefaultValues(MediaItem item) { assertThat(item.title).isEmpty(); assertThat(item.description).isEmpty(); @@ -89,4 +125,20 @@ public class MediaItemTest { assertThat(item.endPositionUs).isEqualTo(C.TIME_UNSET); assertThat(item.mimeType).isEmpty(); } + + private static List createMockDrmSchemes(int seed) { + HashMap requestHeaders1 = new HashMap<>(); + requestHeaders1.put("key1", "value1"); + requestHeaders1.put("key2", "value1"); + MediaItem.UriBundle uriBundle1 = + new MediaItem.UriBundle(Uri.parse("www.uri1.com"), requestHeaders1); + MediaItem.DrmScheme drmScheme1 = new MediaItem.DrmScheme(C.WIDEVINE_UUID, uriBundle1); + HashMap requestHeaders2 = new HashMap<>(); + requestHeaders2.put("key3", "value3"); + requestHeaders2.put("key4", "valueWithSeed" + seed); + MediaItem.UriBundle uriBundle2 = + new MediaItem.UriBundle(Uri.parse("www.uri2.com"), requestHeaders2); + MediaItem.DrmScheme drmScheme2 = new MediaItem.DrmScheme(C.PLAYREADY_UUID, uriBundle2); + return Arrays.asList(drmScheme1, drmScheme2); + } } From 140d5fc9a96ee8be60138629c98f091fc0be80e0 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 31 Oct 2018 07:50:19 -0700 Subject: [PATCH 144/832] Fix naming in MediaItemTest ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219472219 --- .../android/exoplayer2/ext/cast/MediaItemTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java index f176fe9c6f..98df0d5690 100644 --- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java +++ b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/MediaItemTest.java @@ -67,7 +67,7 @@ public class MediaItemTest { } @Test - public void buildMediaItem_testClear() { + public void buildAndClear_assertDefaultValues() { MediaItem.Builder builder = new MediaItem.Builder(); builder .setMedia("http://example.com") @@ -86,13 +86,13 @@ public class MediaItemTest { builder .setUuid(new UUID(0, 1)) .setMedia("www.google.com") - .setDrmSchemes(createMockDrmSchemes(1)) + .setDrmSchemes(createDummyDrmSchemes(1)) .buildAndClear(); MediaItem mediaItem2 = builder .setUuid(new UUID(0, 1)) .setMedia("www.google.com") - .setDrmSchemes(createMockDrmSchemes(1)) + .setDrmSchemes(createDummyDrmSchemes(1)) .buildAndClear(); assertThat(mediaItem1).isEqualTo(mediaItem2); } @@ -104,13 +104,13 @@ public class MediaItemTest { builder .setUuid(new UUID(0, 1)) .setMedia("www.google.com") - .setDrmSchemes(createMockDrmSchemes(1)) + .setDrmSchemes(createDummyDrmSchemes(1)) .buildAndClear(); MediaItem mediaItem2 = builder .setUuid(new UUID(0, 1)) .setMedia("www.google.com") - .setDrmSchemes(createMockDrmSchemes(2)) + .setDrmSchemes(createDummyDrmSchemes(2)) .buildAndClear(); assertThat(mediaItem1).isNotEqualTo(mediaItem2); } @@ -126,7 +126,7 @@ public class MediaItemTest { assertThat(item.mimeType).isEmpty(); } - private static List createMockDrmSchemes(int seed) { + private static List createDummyDrmSchemes(int seed) { HashMap requestHeaders1 = new HashMap<>(); requestHeaders1.put("key1", "value1"); requestHeaders1.put("key2", "value1"); From 9d947a56f837cd7698c59b229baca9860bb61172 Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 31 Oct 2018 07:57:14 -0700 Subject: [PATCH 145/832] Add MediaChunkListIterator ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219472988 --- .../source/chunk/BaseMediaChunkIterator.java | 4 +- .../source/chunk/MediaChunkListIterator.java | 61 +++++++++++++++ .../chunk/MediaChunkListIteratorTest.java | 76 +++++++++++++++++++ .../AdaptiveTrackSelectionTest.java | 38 +--------- .../exoplayer2/testutil/FakeMediaChunk.java | 58 ++++++++++++++ 5 files changed, 198 insertions(+), 39 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunkListIterator.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/source/chunk/MediaChunkListIteratorTest.java create mode 100644 testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java index 4c1462babf..e506cac675 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java @@ -56,14 +56,14 @@ public abstract class BaseMediaChunkIterator implements MediaChunkIterator { * * @throws NoSuchElementException If the iterator does not point to a valid element. */ - protected void checkInBounds() { + protected final void checkInBounds() { if (currentIndex < fromIndex || currentIndex > toIndex) { throw new NoSuchElementException(); } } /** Returns the current index this iterator is pointing to. */ - protected long getCurrentIndex() { + protected final long getCurrentIndex() { return currentIndex; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunkListIterator.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunkListIterator.java new file mode 100644 index 0000000000..ca64e1affd --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunkListIterator.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source.chunk; + +import com.google.android.exoplayer2.upstream.DataSpec; +import java.util.List; + +/** A {@link MediaChunkIterator} which iterates over a {@link List} of {@link MediaChunk}s. */ +public final class MediaChunkListIterator extends BaseMediaChunkIterator { + + private final List chunks; + private final boolean reverseOrder; + + /** + * Creates iterator. + * + * @param chunks The list of chunks to iterate over. + * @param reverseOrder Whether to iterate in reverse order. + */ + public MediaChunkListIterator(List chunks, boolean reverseOrder) { + super(0, chunks.size() - 1); + this.chunks = chunks; + this.reverseOrder = reverseOrder; + } + + @Override + public DataSpec getDataSpec() { + return getCurrentChunk().dataSpec; + } + + @Override + public long getChunkStartTimeUs() { + return getCurrentChunk().startTimeUs; + } + + @Override + public long getChunkEndTimeUs() { + return getCurrentChunk().endTimeUs; + } + + private MediaChunk getCurrentChunk() { + int index = (int) super.getCurrentIndex(); + if (reverseOrder) { + index = chunks.size() - 1 - index; + } + return chunks.get(index); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/chunk/MediaChunkListIteratorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/chunk/MediaChunkListIteratorTest.java new file mode 100644 index 0000000000..590c06f3d5 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/chunk/MediaChunkListIteratorTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source.chunk; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.testutil.FakeMediaChunk; +import java.util.Arrays; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link MediaChunkListIterator}. */ +@RunWith(RobolectricTestRunner.class) +public class MediaChunkListIteratorTest { + + private static final Format TEST_FORMAT = Format.createSampleFormat(null, null, 0); + + private FakeMediaChunk testChunk1; + private FakeMediaChunk testChunk2; + + @Before + public void setUp() { + testChunk1 = new FakeMediaChunk(TEST_FORMAT, 0, 10); + testChunk2 = new FakeMediaChunk(TEST_FORMAT, 10, 20); + } + + @Test + public void iterator_reverseOrderFalse_returnsItemsInNormalOrder() { + MediaChunkListIterator iterator = + new MediaChunkListIterator( + Arrays.asList(testChunk1, testChunk2), /* reverseOrder= */ false); + assertThat(iterator.isEnded()).isFalse(); + assertThat(iterator.next()).isTrue(); + assertEqual(iterator, testChunk1); + assertThat(iterator.next()).isTrue(); + assertEqual(iterator, testChunk2); + assertThat(iterator.next()).isFalse(); + assertThat(iterator.isEnded()).isTrue(); + } + + @Test + public void iterator_reverseOrderTrue_returnsItemsInReverseOrder() { + MediaChunkListIterator iterator = + new MediaChunkListIterator( + Arrays.asList(testChunk1, testChunk2), /* reverseOrder= */ true); + assertThat(iterator.isEnded()).isFalse(); + assertThat(iterator.next()).isTrue(); + assertEqual(iterator, testChunk2); + assertThat(iterator.next()).isTrue(); + assertEqual(iterator, testChunk1); + assertThat(iterator.next()).isFalse(); + assertThat(iterator.isEnded()).isTrue(); + } + + private static void assertEqual(MediaChunkListIterator iterator, FakeMediaChunk chunk) { + assertThat(iterator.getChunkStartTimeUs()).isEqualTo(chunk.startTimeUs); + assertThat(iterator.getChunkEndTimeUs()).isEqualTo(chunk.endTimeUs); + assertThat(iterator.getDataSpec()).isEqualTo(chunk.dataSpec); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java index 3ddc809ca4..dcad02fef1 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java @@ -23,19 +23,14 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import android.net.Uri; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.TrackGroup; -import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.testutil.FakeClock; +import com.google.android.exoplayer2.testutil.FakeMediaChunk; import com.google.android.exoplayer2.upstream.BandwidthMeter; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.util.MimeTypes; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -409,35 +404,4 @@ public final class AdaptiveTrackSelectionTest { /* drmInitData= */ null); } - private static final class FakeMediaChunk extends MediaChunk { - - private static final DataSource DATA_SOURCE = new DefaultHttpDataSource("TEST_AGENT", null); - - public FakeMediaChunk(Format trackFormat, long startTimeUs, long endTimeUs) { - super( - DATA_SOURCE, - new DataSpec(Uri.EMPTY), - trackFormat, - C.SELECTION_REASON_ADAPTIVE, - null, - startTimeUs, - endTimeUs, - 0); - } - - @Override - public void cancelLoad() { - // Do nothing. - } - - @Override - public void load() throws IOException, InterruptedException { - // Do nothing. - } - - @Override - public boolean isLoadCompleted() { - return true; - } - } } diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java new file mode 100644 index 0000000000..8740e3e57f --- /dev/null +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.testutil; + +import android.net.Uri; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.chunk.MediaChunk; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; +import java.io.IOException; + +/** Fake {@link MediaChunk}. */ +public final class FakeMediaChunk extends MediaChunk { + + private static final DataSource DATA_SOURCE = new DefaultHttpDataSource("TEST_AGENT", null); + + public FakeMediaChunk(Format trackFormat, long startTimeUs, long endTimeUs) { + super( + DATA_SOURCE, + new DataSpec(Uri.EMPTY), + trackFormat, + C.SELECTION_REASON_ADAPTIVE, + /* trackSelectionData= */ null, + startTimeUs, + endTimeUs, + /* chunkIndex= */ 0); + } + + @Override + public void cancelLoad() { + // Do nothing. + } + + @Override + public void load() throws IOException, InterruptedException { + // Do nothing. + } + + @Override + public boolean isLoadCompleted() { + return true; + } +} From 097c045befeeec76d8ed8dab7a97e022ec7d4ed9 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 31 Oct 2018 19:45:32 +0000 Subject: [PATCH 146/832] Resolve conflicts --- .../DefaultTrackSelectorTest.java | 75 ++++++++++++++++--- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 92dca1d4dd..63b1c6d597 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -1061,30 +1061,66 @@ public final class DefaultTrackSelectorTest { } /** - * Tests that track selector will select audio tracks with lower bitrate when {@link Parameters} - * indicate lowest bitrate preference, even when tracks are within capabilities. + * Tests that track selector will select the lowest bitrate supported audio track when {@link + * Parameters#forceLowestBitrate} is set. */ @Test public void testSelectTracksWithinCapabilitiesAndForceLowestBitrateSelectLowerBitrate() throws Exception { - Format lowerBitrateFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000, - Format.NO_VALUE, 2, 44100, null, null, 0, null); - Format higherBitrateFormat = - Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000, - Format.NO_VALUE, 2, 44100, null, null, 0, null); - TrackGroupArray trackGroups = wrapFormats(lowerBitrateFormat, higherBitrateFormat); + Format unsupportedLowBitrateFormat = buildAudioFormatWithBitrate("unsupportedLowBitrate", 5000); + Format lowerBitrateFormat = buildAudioFormatWithBitrate("lowBitrate", 15000); + Format higherBitrateFormat = buildAudioFormatWithBitrate("highBitrate", 30000); + TrackGroupArray trackGroups = + wrapFormats(unsupportedLowBitrateFormat, lowerBitrateFormat, higherBitrateFormat); + + Map mappedCapabilities = new HashMap<>(); + mappedCapabilities.put(unsupportedLowBitrateFormat.id, FORMAT_EXCEEDS_CAPABILITIES); + mappedCapabilities.put(lowerBitrateFormat.id, FORMAT_HANDLED); + mappedCapabilities.put(higherBitrateFormat.id, FORMAT_HANDLED); + RendererCapabilities mappedAudioRendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities); trackSelector.setParameters(Parameters.DEFAULT.buildUpon().setForceLowestBitrate(true).build()); TrackSelectorResult result = trackSelector.selectTracks( - new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + new RendererCapabilities[] {mappedAudioRendererCapabilities}, trackGroups, periodId, TIMELINE); assertFixedSelection(result.selections.get(0), trackGroups, lowerBitrateFormat); } + /** + * Tests that track selector will select the highest bitrate supported audio track when {@link + * Parameters#forceHighestSupportedBitrate} is set. + */ + @Test + public void testSelectTracksWithinCapabilitiesAndForceHighestBitrateSelectHigherBitrate() + throws Exception { + Format lowerBitrateFormat = buildAudioFormatWithBitrate("lowerBitrateFormat", 5000); + Format higherBitrateFormat = buildAudioFormatWithBitrate("higherBitrateFormat", 15000); + Format exceedsBitrateFormat = buildAudioFormatWithBitrate("exceedsBitrateFormat", 30000); + TrackGroupArray trackGroups = + wrapFormats(lowerBitrateFormat, higherBitrateFormat, exceedsBitrateFormat); + + Map mappedCapabilities = new HashMap<>(); + mappedCapabilities.put(lowerBitrateFormat.id, FORMAT_HANDLED); + mappedCapabilities.put(higherBitrateFormat.id, FORMAT_HANDLED); + mappedCapabilities.put(exceedsBitrateFormat.id, FORMAT_EXCEEDS_CAPABILITIES); + RendererCapabilities mappedAudioRendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities); + + trackSelector.setParameters( + new ParametersBuilder().setForceHighestSupportedBitrate(true).build()); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {mappedAudioRendererCapabilities}, + singleTrackGroup(lowerBitrateFormat, higherBitrateFormat, exceedsBitrateFormat), + periodId, + TIMELINE); + assertFixedSelection(result.selections.get(0), trackGroups, higherBitrateFormat); + } + @Test public void testSelectTracksWithMultipleAudioTracks() throws Exception { TrackGroupArray trackGroups = singleTrackGroup(buildAudioFormat("0"), buildAudioFormat("1")); @@ -1472,16 +1508,29 @@ public final class DefaultTrackSelectorTest { return buildAudioFormat( id, MimeTypes.AUDIO_AAC, + /* bitrate= */ Format.NO_VALUE, language, selectionFlags, /* channelCount= */ 2, /* sampleRate= */ 44100); } + private static Format buildAudioFormatWithBitrate(String id, int bitrate) { + return buildAudioFormat( + id, + MimeTypes.AUDIO_AAC, + bitrate, + /* language= */ null, + /* selectionFlags= */ 0, + /* channelCount= */ 2, + /* sampleRate= */ 44100); + } + private static Format buildAudioFormatWithSampleRate(String id, int sampleRate) { return buildAudioFormat( id, MimeTypes.AUDIO_AAC, + /* bitrate= */ Format.NO_VALUE, /* language= */ null, /* selectionFlags= */ 0, /* channelCount= */ 2, @@ -1492,6 +1541,7 @@ public final class DefaultTrackSelectorTest { return buildAudioFormat( id, MimeTypes.AUDIO_AAC, + /* bitrate= */ Format.NO_VALUE, /* language= */ null, /* selectionFlags= */ 0, channelCount, @@ -1502,6 +1552,7 @@ public final class DefaultTrackSelectorTest { return buildAudioFormat( id, mimeType, + /* bitrate= */ Format.NO_VALUE, /* language= */ null, /* selectionFlags= */ 0, /* channelCount= */ 2, @@ -1512,6 +1563,7 @@ public final class DefaultTrackSelectorTest { return buildAudioFormat( id, MimeTypes.AUDIO_AAC, + /* bitrate= */ Format.NO_VALUE, /* language= */ null, /* selectionFlags= */ 0, /* channelCount= */ 2, @@ -1521,6 +1573,7 @@ public final class DefaultTrackSelectorTest { private static Format buildAudioFormat( String id, String mimeType, + int bitrate, String language, int selectionFlags, int channelCount, @@ -1529,7 +1582,7 @@ public final class DefaultTrackSelectorTest { id, mimeType, /* codecs= */ null, - /* bitrate= */ Format.NO_VALUE, + bitrate, /* maxInputSize= */ Format.NO_VALUE, channelCount, sampleRate, From f1d31bdf4f5f6294f2a69f05725d3f175a81f221 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 31 Oct 2018 19:49:24 +0000 Subject: [PATCH 147/832] Fix test --- .../exoplayer2/trackselection/DefaultTrackSelectorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 63b1c6d597..53bc9f4eea 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -1115,7 +1115,7 @@ public final class DefaultTrackSelectorTest { TrackSelectorResult result = trackSelector.selectTracks( new RendererCapabilities[] {mappedAudioRendererCapabilities}, - singleTrackGroup(lowerBitrateFormat, higherBitrateFormat, exceedsBitrateFormat), + trackGroups, periodId, TIMELINE); assertFixedSelection(result.selections.get(0), trackGroups, higherBitrateFormat); From 2dfe7a7ad6a784482b8de094c41b7815a09444b8 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 31 Oct 2018 20:37:59 +0000 Subject: [PATCH 148/832] Misc fixes / stylistic consistency changes for merged pull requests --- .../ext/cronet/CronetDataSource.java | 5 +- .../exoplayer2/upstream/HttpDataSource.java | 11 ++- .../DefaultLoadErrorHandlingPolicyTest.java | 6 +- .../source/dash/manifest/DashManifest.java | 74 ++++++++++++++++--- .../dash/manifest/DashManifestParser.java | 58 +++++++++++---- .../dash/manifest/ProgramInformation.java | 40 ++++------ .../dash/manifest/DashManifestParserTest.java | 12 +-- .../dash/manifest/DashManifestTest.java | 13 +++- .../hls/playlist/HlsPlaylistParser.java | 2 +- 9 files changed, 155 insertions(+), 66 deletions(-) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 9f381d0053..af85401100 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -616,10 +616,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { if (responseCode == 307 || responseCode == 308) { exception = new InvalidResponseCodeException( - responseCode, - info.getHttpStatusText(), - info.getAllHeaders(), - currentDataSpec); + responseCode, info.getHttpStatusText(), info.getAllHeaders(), currentDataSpec); operation.open(); return; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index e73901eccb..e3e93bd6fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -295,9 +295,7 @@ public interface HttpDataSource extends DataSource { */ public final int responseCode; - /** - * The HTTP status message. - */ + /** The http status message. */ @Nullable public final String responseMessage; /** @@ -305,6 +303,13 @@ public interface HttpDataSource extends DataSource { */ public final Map> headerFields; + /** @deprecated Use {@link #InvalidResponseCodeException(int, String, Map, DataSpec)}. */ + @Deprecated + public InvalidResponseCodeException( + int responseCode, Map> headerFields, DataSpec dataSpec) { + this(responseCode, /* responseMessage= */ null, headerFields, dataSpec); + } + public InvalidResponseCodeException( int responseCode, @Nullable String responseMessage, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java index f0f737828c..cf30fce3a6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java @@ -34,7 +34,8 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_blacklist404() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(404, "Not Found", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException( + 404, "Not Found", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)) .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } @@ -42,7 +43,8 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_blacklist410() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(410, "Gone", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException( + 410, "Gone", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)) .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java index 6446909808..3637b80ecb 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.source.dash.manifest; import android.net.Uri; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.FilterableManifest; import com.google.android.exoplayer2.offline.StreamKey; @@ -86,17 +87,56 @@ public class DashManifest implements FilterableManifest { */ public final Uri location; - /** - * The ProgramInformation of this manifest. - */ - public final ProgramInformation programInformation; + /** The {@link ProgramInformation}, or null if not present. */ + @Nullable public final ProgramInformation programInformation; private final List periods; - public DashManifest(long availabilityStartTimeMs, long durationMs, long minBufferTimeMs, - boolean dynamic, long minUpdatePeriodMs, long timeShiftBufferDepthMs, - long suggestedPresentationDelayMs, long publishTimeMs, UtcTimingElement utcTiming, - Uri location, ProgramInformation programInformation, List periods) { + /** + * @deprecated Use {@link #DashManifest(long, long, long, boolean, long, long, long, long, + * ProgramInformation, UtcTimingElement, Uri, List)}. + */ + @Deprecated + public DashManifest( + long availabilityStartTimeMs, + long durationMs, + long minBufferTimeMs, + boolean dynamic, + long minUpdatePeriodMs, + long timeShiftBufferDepthMs, + long suggestedPresentationDelayMs, + long publishTimeMs, + UtcTimingElement utcTiming, + Uri location, + List periods) { + this( + availabilityStartTimeMs, + durationMs, + minBufferTimeMs, + dynamic, + minUpdatePeriodMs, + timeShiftBufferDepthMs, + suggestedPresentationDelayMs, + publishTimeMs, + /* programInformation= */ null, + utcTiming, + location, + periods); + } + + public DashManifest( + long availabilityStartTimeMs, + long durationMs, + long minBufferTimeMs, + boolean dynamic, + long minUpdatePeriodMs, + long timeShiftBufferDepthMs, + long suggestedPresentationDelayMs, + long publishTimeMs, + @Nullable ProgramInformation programInformation, + UtcTimingElement utcTiming, + Uri location, + List periods) { this.availabilityStartTimeMs = availabilityStartTimeMs; this.durationMs = durationMs; this.minBufferTimeMs = minBufferTimeMs; @@ -105,9 +145,9 @@ public class DashManifest implements FilterableManifest { this.timeShiftBufferDepthMs = timeShiftBufferDepthMs; this.suggestedPresentationDelayMs = suggestedPresentationDelayMs; this.publishTimeMs = publishTimeMs; + this.programInformation = programInformation; this.utcTiming = utcTiming; this.location = location; - this.programInformation = programInformation; this.periods = periods == null ? Collections.emptyList() : periods; } @@ -154,9 +194,19 @@ public class DashManifest implements FilterableManifest { } } long newDuration = durationMs != C.TIME_UNSET ? durationMs - shiftMs : C.TIME_UNSET; - return new DashManifest(availabilityStartTimeMs, newDuration, minBufferTimeMs, dynamic, - minUpdatePeriodMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, publishTimeMs, - utcTiming, location, programInformation, copyPeriods); + return new DashManifest( + availabilityStartTimeMs, + newDuration, + minBufferTimeMs, + dynamic, + minUpdatePeriodMs, + timeShiftBufferDepthMs, + suggestedPresentationDelayMs, + publishTimeMs, + programInformation, + utcTiming, + location, + copyPeriods); } private static ArrayList copyAdaptationSets( diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index d048e22b33..f017ae64ad 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -120,9 +120,9 @@ public class DashManifestParser extends DefaultHandler long suggestedPresentationDelayMs = dynamic ? parseDuration(xpp, "suggestedPresentationDelay", C.TIME_UNSET) : C.TIME_UNSET; long publishTimeMs = parseDateTime(xpp, "publishTime", C.TIME_UNSET); + ProgramInformation programInformation = null; UtcTimingElement utcTiming = null; Uri location = null; - ProgramInformation programInformation = null; List periods = new ArrayList<>(); long nextPeriodStartMs = dynamic ? C.TIME_UNSET : 0; @@ -135,12 +135,12 @@ public class DashManifestParser extends DefaultHandler baseUrl = parseBaseUrl(xpp, baseUrl); seenFirstBaseUrl = true; } + } else if (XmlPullParserUtil.isStartTag(xpp, "ProgramInformation")) { + programInformation = parseProgramInformation(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "UTCTiming")) { utcTiming = parseUtcTiming(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "Location")) { location = Uri.parse(xpp.nextText()); - } else if (XmlPullParserUtil.isStartTag(xpp, "ProgramInformation")) { - programInformation = parseProgramInformation(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) { Pair periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs); Period period = periodWithDurationMs.first; @@ -176,18 +176,47 @@ public class DashManifestParser extends DefaultHandler throw new ParserException("No periods found."); } - return buildMediaPresentationDescription(availabilityStartTime, durationMs, minBufferTimeMs, - dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, - publishTimeMs, utcTiming, location, programInformation, periods); + return buildMediaPresentationDescription( + availabilityStartTime, + durationMs, + minBufferTimeMs, + dynamic, + minUpdateTimeMs, + timeShiftBufferDepthMs, + suggestedPresentationDelayMs, + publishTimeMs, + programInformation, + utcTiming, + location, + periods); } - protected DashManifest buildMediaPresentationDescription(long availabilityStartTime, - long durationMs, long minBufferTimeMs, boolean dynamic, long minUpdateTimeMs, - long timeShiftBufferDepthMs, long suggestedPresentationDelayMs, long publishTimeMs, - UtcTimingElement utcTiming, Uri location, ProgramInformation programInformation, List periods) { - return new DashManifest(availabilityStartTime, durationMs, minBufferTimeMs, - dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, - publishTimeMs, utcTiming, location, programInformation, periods); + protected DashManifest buildMediaPresentationDescription( + long availabilityStartTime, + long durationMs, + long minBufferTimeMs, + boolean dynamic, + long minUpdateTimeMs, + long timeShiftBufferDepthMs, + long suggestedPresentationDelayMs, + long publishTimeMs, + ProgramInformation programInformation, + UtcTimingElement utcTiming, + Uri location, + List periods) { + return new DashManifest( + availabilityStartTime, + durationMs, + minBufferTimeMs, + dynamic, + minUpdateTimeMs, + timeShiftBufferDepthMs, + suggestedPresentationDelayMs, + publishTimeMs, + programInformation, + utcTiming, + location, + periods); } protected UtcTimingElement parseUtcTiming(XmlPullParser xpp) { @@ -1001,7 +1030,8 @@ public class DashManifestParser extends DefaultHandler return new RangedUri(urlText, rangeStart, rangeLength); } - protected ProgramInformation parseProgramInformation(XmlPullParser xpp) throws IOException, XmlPullParserException { + protected ProgramInformation parseProgramInformation(XmlPullParser xpp) + throws IOException, XmlPullParserException { String title = null; String source = null; String copyright = null; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java index 71b4af3a21..73e8ef986e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java @@ -17,36 +17,25 @@ package com.google.android.exoplayer2.source.dash.manifest; import com.google.android.exoplayer2.util.Util; -/** - * A parsed ProgramInformation element. - */ +/** A parsed program information element. */ public class ProgramInformation { - /** - * The title for the media presentation. - */ + /** The title for the media presentation. */ public final String title; - /** - * Information about the original source of the media presentation. - */ + /** Information about the original source of the media presentation. */ public final String source; - /** - * A copyright statement for the media presentation. - */ + /** A copyright statement for the media presentation. */ public final String copyright; - /** - * A URL that provides more information about the media presentation. - */ + /** A URL that provides more information about the media presentation. */ public final String moreInformationURL; - /** - * Declares the language code(s) for this ProgramInformation. - */ + /** Declares the language code(s) for this ProgramInformation. */ public final String lang; - public ProgramInformation(String title, String source, String copyright, String moreInformationURL, String lang) { + public ProgramInformation( + String title, String source, String copyright, String moreInformationURL, String lang) { this.title = title; this.source = source; this.copyright = copyright; @@ -56,15 +45,18 @@ public class ProgramInformation { @Override public boolean equals(Object obj) { - if (!(obj instanceof ProgramInformation)) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { return false; } ProgramInformation other = (ProgramInformation) obj; return Util.areEqual(this.title, other.title) - && Util.areEqual(this.source, other.source) - && Util.areEqual(this.copyright, other.copyright) - && Util.areEqual(this.moreInformationURL, other.moreInformationURL) - && Util.areEqual(this.lang, other.lang); + && Util.areEqual(this.source, other.source) + && Util.areEqual(this.copyright, other.copyright) + && Util.areEqual(this.moreInformationURL, other.moreInformationURL) + && Util.areEqual(this.lang, other.lang); } @Override diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java index 3183e3c672..a1693f6985 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java @@ -25,7 +25,6 @@ import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.junit.Test; @@ -156,11 +155,14 @@ public class DashManifestParserTest { @Test public void testParseMediaPresentationDescriptionCanParseProgramInformation() throws IOException { DashManifestParser parser = new DashManifestParser(); - DashManifest mpd = parser.parse(Uri.parse("Https://example.com/test.mpd"), + DashManifest mpd = + parser.parse( + Uri.parse("Https://example.com/test.mpd"), TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_MPD_1)); - ProgramInformation programInformation = new ProgramInformation("MediaTitle", "MediaSource", - "MediaCopyright", "www.example.com", "enUs"); - assertThat(programInformation).isEqualTo(mpd.programInformation); + ProgramInformation expectedProgramInformation = + new ProgramInformation( + "MediaTitle", "MediaSource", "MediaCopyright", "www.example.com", "enUs"); + assertThat(mpd.programInformation).isEqualTo(expectedProgramInformation); } @Test diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java index 17a96fded3..0d08df42e9 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java @@ -219,7 +219,18 @@ public class DashManifestTest { private static DashManifest newDashManifest(int duration, Period... periods) { return new DashManifest( - 0, duration, 1, false, 2, 3, 4, 12345, DUMMY_UTC_TIMING, Uri.EMPTY, null, Arrays.asList(periods)); + /* availabilityStartTimeMs= */ 0, + duration, + /* minBufferTimeMs= */ 1, + /* dynamic= */ false, + /* minUpdatePeriodMs= */ 2, + /* timeShiftBufferDepthMs= */ 3, + /* suggestedPresentationDelayMs= */ 4, + /* publishTimeMs= */ 12345, + /* programInformation= */ null, + DUMMY_UTC_TIMING, + Uri.EMPTY, + Arrays.asList(periods)); } private static Period newPeriod(String id, int startMs, AdaptationSet... adaptationSets) { 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 bd2bea0197..65f4796187 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 @@ -341,7 +341,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser Date: Wed, 31 Oct 2018 20:40:57 +0000 Subject: [PATCH 149/832] Fix nullability --- .../exoplayer2/source/dash/manifest/ProgramInformation.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java index 73e8ef986e..e3072c86bd 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.dash.manifest; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.util.Util; /** A parsed program information element. */ @@ -44,7 +45,7 @@ public class ProgramInformation { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } From 609f10b5eccd37dc991c7bd077ba2089c26a716f Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 31 Oct 2018 14:43:46 -0700 Subject: [PATCH 150/832] Update moe equivalence ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219540864 --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a52a076014..4f2353f1df 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,7 @@ ### dev-v2 (not yet released) ### +* DASH: Parse ProgramInformation element if present in the manifest. * Improve decoder re-use between playbacks. TODO: Write and link a blog post here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). * Improve initial bandwidth meter estimates using the current country and From f76b80c3cbd3ec1dec6faecc2d0e3a6016d1e795 Mon Sep 17 00:00:00 2001 From: ishani Date: Wed, 31 Oct 2018 21:47:57 -0700 Subject: [PATCH 151/832] Automated g4 rollback of changelist 219130576. *** Original change description *** Re-enable codec re-use Issue: #2826 *** ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219585084 --- .../mediacodec/MediaCodecRenderer.java | 16 +++------------- .../video/MediaCodecVideoRenderer.java | 4 ---- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index efe8959908..74c575c1ef 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -536,16 +536,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Override protected void onDisabled() { inputFormat = null; - if (drmSession != null || pendingDrmSession != null) { - // TODO: Do something better with this case. - onReset(); - } else { - flushOrReleaseCodec(); - } - } - - @Override - protected void onReset() { try { releaseCodec(); } finally { @@ -569,14 +559,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { protected void releaseCodec() { availableCodecInfos = null; if (codec != null) { - codecInfo = null; - codecFormat = null; resetInputBuffer(); resetOutputBuffer(); resetCodecBuffers(); - waitingForKeys = false; codecHotswapDeadlineMs = C.TIME_UNSET; + waitingForKeys = false; decodeOnlyPresentationTimestamps.clear(); + codecInfo = null; + codecFormat = null; decoderCounters.decoderReleaseCount++; try { codec.stop(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 0b17cd6338..a896bc2322 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -283,12 +283,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override protected void onEnabled(boolean joining) throws ExoPlaybackException { super.onEnabled(joining); - int oldTunnelingAudioSessionId = tunnelingAudioSessionId; tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET; - if (tunnelingAudioSessionId != oldTunnelingAudioSessionId) { - releaseCodec(); - } eventDispatcher.enabled(decoderCounters); frameReleaseTimeHelper.enable(); } From 36fef95f47de48bd540c0321691f9ac78e43eb23 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 1 Nov 2018 00:58:32 -0700 Subject: [PATCH 152/832] Fix extended service number calculation ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219597894 --- .../google/android/exoplayer2/text/cea/Cea708Decoder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java index a95f1de738..b3be88b851 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java @@ -272,7 +272,10 @@ public final class Cea708Decoder extends CeaDecoder { if (serviceNumber == 7) { // extended service numbers serviceBlockPacket.skipBits(2); - serviceNumber += serviceBlockPacket.readBits(6); + serviceNumber = serviceBlockPacket.readBits(6); + if (serviceNumber < 7) { + Log.w(TAG, "Invalid extended service number: " + serviceNumber); + } } // Ignore packets in which blockSize is 0 From e1c6229cc8e38ee70cc13b10d2d46f3c75e8d6f3 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 1 Nov 2018 03:08:01 -0700 Subject: [PATCH 153/832] Bump version to 2.9.1 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219609471 --- RELEASENOTES.md | 53 +++++++++++-------- constants.gradle | 4 +- .../exoplayer2/ExoPlayerLibraryInfo.java | 6 +-- library/ui/src/main/res/values-bn/strings.xml | 2 +- library/ui/src/main/res/values-gu/strings.xml | 2 +- library/ui/src/main/res/values-iw/strings.xml | 2 +- library/ui/src/main/res/values-kn/strings.xml | 2 +- library/ui/src/main/res/values-lo/strings.xml | 2 +- library/ui/src/main/res/values-ml/strings.xml | 2 +- library/ui/src/main/res/values-ne/strings.xml | 2 +- library/ui/src/main/res/values-pa/strings.xml | 2 +- library/ui/src/main/res/values-si/strings.xml | 2 +- library/ui/src/main/res/values-ta/strings.xml | 2 +- library/ui/src/main/res/values-te/strings.xml | 2 +- library/ui/src/main/res/values-ur/strings.xml | 2 +- 15 files changed, 47 insertions(+), 40 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4f2353f1df..c61b1fecb4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,42 +2,49 @@ ### dev-v2 (not yet released) ### -* DASH: Parse ProgramInformation element if present in the manifest. +* Support for playing spherical videos on Daydream. * Improve decoder re-use between playbacks. TODO: Write and link a blog post here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). -* Improve initial bandwidth meter estimates using the current country and - network type. * Add options for controlling audio track selections to `DefaultTrackSelector` ([#3314](https://github.com/google/ExoPlayer/issues/3314)). * Do not retry failed loads whose error is `FileNotFoundException`. + +### 2.9.1 ### + * Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext` and `Player.hasPrevious` ([#4863](https://github.com/google/ExoPlayer/issues/4863)). -* HLS: - * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload reader - factory flags ([#4861](https://github.com/google/ExoPlayer/issues/4861)). -* Fix an issue with blind seeking to windows with non-zero offset in a - `ConcatenatingMediaSource` - ([#4873](https://github.com/google/ExoPlayer/issues/4873)). -* Fix issue where subtitles have a wrong position if SubtitleView has a non-zero - offset to its parent - ([#4788](https://github.com/google/ExoPlayer/issues/4788)). -* SubRip: Add support for alignment tags, and remove tags from the displayed - captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). -* Audio: - * Support seeking based on MLLT metadata - ([#3241](https://github.com/google/ExoPlayer/issues/3241)). - * Fix handling of MP3s with appended data - ([#4954](https://github.com/google/ExoPlayer/issues/4954)). -* Fix issue where buffered position is not updated correctly when transitioning - between periods - ([#4899](https://github.com/google/ExoPlayer/issues/4899)). +* Improve initial bandwidth meter estimates using the current country and + network type. * IMA extension: * For preroll to live stream transitions, project forward the loading position to avoid being behind the live window. * Let apps specify whether to focus the skip button on ATV ([#5019](https://github.com/google/ExoPlayer/issues/5019)). -* Support for playing spherical videos on Daydream. +* MP3: + * Support seeking based on MLLT metadata + ([#3241](https://github.com/google/ExoPlayer/issues/3241)). + * Fix handling of streams with appended data + ([#4954](https://github.com/google/ExoPlayer/issues/4954)). +* DASH: Parse ProgramInformation element if present in the manifest. +* HLS: Add constructor to `DefaultHlsExtractorFactory` for adding TS payload + reader factory flags + ([#4861](https://github.com/google/ExoPlayer/issues/4861)). +* SubRip: Add support for alignment tags, and remove tags from the displayed + captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). +* Fix issue with blind seeking to windows with non-zero offset in a + `ConcatenatingMediaSource` + ([#4873](https://github.com/google/ExoPlayer/issues/4873)). +* Fix issue where subtitles were positioned incorrectly if `SubtitleView` had a + non-zero position offset to its parent + ([#4788](https://github.com/google/ExoPlayer/issues/4788)). +* Fix issue where the buffered position was not updated correctly when + transitioning between periods + ([#4899](https://github.com/google/ExoPlayer/issues/4899)). +* Suppress a spurious assertion failure on some Samsung devices + ([#4532](https://github.com/google/ExoPlayer/issues/4532)). +* Suppress spurious "references unknown class member" shrinking warning + ([#4890](https://github.com/google/ExoPlayer/issues/4890)). * Fix issue where a `NullPointerException` is thrown when removing an unprepared media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)). diff --git a/constants.gradle b/constants.gradle index 6db6d6310b..dd277c722c 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.9.0' - releaseVersionCode = 2009000 + releaseVersion = '2.9.1' + releaseVersionCode = 2009001 // Important: ExoPlayer specifies a minSdkVersion of 14 because various // components provided by the library may be of use on older devices. // However, please note that the core media playback functionality provided diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index f5ad677d77..c4dda9e957 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo { /** The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - public static final String VERSION = "2.9.0"; + public static final String VERSION = "2.9.1"; /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.0"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.1"; /** * The version of the library expressed as an integer, for example 1002003. @@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final int VERSION_INT = 2009000; + public static final int VERSION_INT = 2009001; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} diff --git a/library/ui/src/main/res/values-bn/strings.xml b/library/ui/src/main/res/values-bn/strings.xml index 0e5efb1b77..c58d70a063 100644 --- a/library/ui/src/main/res/values-bn/strings.xml +++ b/library/ui/src/main/res/values-bn/strings.xml @@ -12,7 +12,7 @@ সবগুলি আইটেম আবার চালান শাফেল করুন পূর্ণ স্ক্রিন মোড - VR mode + ভিআর মোড ডাউনলোড করুন ডাউনলোড ডাউনলোড হচ্ছে diff --git a/library/ui/src/main/res/values-gu/strings.xml b/library/ui/src/main/res/values-gu/strings.xml index 847c5b6d33..a362329138 100644 --- a/library/ui/src/main/res/values-gu/strings.xml +++ b/library/ui/src/main/res/values-gu/strings.xml @@ -12,7 +12,7 @@ બધાને રિપીટ કરો શફલ કરો પૂર્ણસ્ક્રીન મોડ - VR mode + VR મોડ ડાઉનલોડ કરો ડાઉનલોડ ડાઉનલોડ કરી રહ્યાં છીએ diff --git a/library/ui/src/main/res/values-iw/strings.xml b/library/ui/src/main/res/values-iw/strings.xml index 0a471f33e2..b2e1171017 100644 --- a/library/ui/src/main/res/values-iw/strings.xml +++ b/library/ui/src/main/res/values-iw/strings.xml @@ -12,7 +12,7 @@ חזור על הכול ערבוב מצב מסך מלא - VR mode + מצב VR הורדה הורדות ההורדה מתבצעת diff --git a/library/ui/src/main/res/values-kn/strings.xml b/library/ui/src/main/res/values-kn/strings.xml index 9952d859d3..cb46a4f0d8 100644 --- a/library/ui/src/main/res/values-kn/strings.xml +++ b/library/ui/src/main/res/values-kn/strings.xml @@ -12,7 +12,7 @@ ಎಲ್ಲವನ್ನು ಪುನರಾವರ್ತಿಸಿ ಶಫಲ್‌ ಪೂರ್ಣ ಪರದೆ ಮೋಡ್ - VR mode + VR ಮೋಡ್ ಡೌನ್‌ಲೋಡ್‌ ಡೌನ್‌ಲೋಡ್‌ಗಳು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ diff --git a/library/ui/src/main/res/values-lo/strings.xml b/library/ui/src/main/res/values-lo/strings.xml index e2fd29e5d4..a32fabf213 100644 --- a/library/ui/src/main/res/values-lo/strings.xml +++ b/library/ui/src/main/res/values-lo/strings.xml @@ -12,7 +12,7 @@ ຫຼິ້ນຊ້ຳທັງໝົດ ຫຼີ້ນແບບສຸ່ມ ໂໝດເຕັມຈໍ - VR mode + ໂໝດ VR ດາວໂຫລດ ດາວໂຫລດ ກຳລັງດາວໂຫລດ diff --git a/library/ui/src/main/res/values-ml/strings.xml b/library/ui/src/main/res/values-ml/strings.xml index 76b9b3b4c6..9f2c3a702d 100644 --- a/library/ui/src/main/res/values-ml/strings.xml +++ b/library/ui/src/main/res/values-ml/strings.xml @@ -12,7 +12,7 @@ എല്ലാം ആവർത്തിക്കുക ഇടകലര്‍ത്തുക പൂർണ്ണ സ്‌ക്രീൻ മോഡ് - VR mode + VR മോഡ് ഡൗൺലോഡ് ഡൗൺലോഡുകൾ ഡൗൺലോഡ് ചെയ്യുന്നു diff --git a/library/ui/src/main/res/values-ne/strings.xml b/library/ui/src/main/res/values-ne/strings.xml index 4b6ec50903..6ae86f84d2 100644 --- a/library/ui/src/main/res/values-ne/strings.xml +++ b/library/ui/src/main/res/values-ne/strings.xml @@ -12,7 +12,7 @@ सबै दोहोर्‍याउनुहोस् मिसाउनुहोस् पूर्ण स्क्रिन मोड - VR mode + VR मोड डाउनलोड गर्नुहोस् डाउनलोडहरू डाउनलोड गरिँदै छ diff --git a/library/ui/src/main/res/values-pa/strings.xml b/library/ui/src/main/res/values-pa/strings.xml index 1493a62e52..7a03e89faf 100644 --- a/library/ui/src/main/res/values-pa/strings.xml +++ b/library/ui/src/main/res/values-pa/strings.xml @@ -12,7 +12,7 @@ ਸਾਰਿਆਂ ਨੂੰ ਦੁਹਰਾਓ ਬੇਤਰਤੀਬ ਕਰੋ ਪੂਰੀ-ਸਕ੍ਰੀਨ ਮੋਡ - VR mode + VR ਮੋਡ ਡਾਊਨਲੋਡ ਕਰੋ ਡਾਊਨਲੋਡ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ diff --git a/library/ui/src/main/res/values-si/strings.xml b/library/ui/src/main/res/values-si/strings.xml index 241d8336a0..30b63789db 100644 --- a/library/ui/src/main/res/values-si/strings.xml +++ b/library/ui/src/main/res/values-si/strings.xml @@ -12,7 +12,7 @@ සියල්ල පුනරාවර්තනය කරන්න කලවම් කරන්න සම්පූර්ණ තිර ප්‍රකාරය - VR mode + VR ප්‍රකාරය බාගන්න බාගැනීම් බාගනිමින් diff --git a/library/ui/src/main/res/values-ta/strings.xml b/library/ui/src/main/res/values-ta/strings.xml index 828170c101..4608e1beba 100644 --- a/library/ui/src/main/res/values-ta/strings.xml +++ b/library/ui/src/main/res/values-ta/strings.xml @@ -12,7 +12,7 @@ அனைத்தையும் மீண்டும் இயக்கு கலைத்துப் போடு முழுத்திரைப் பயன்முறை - VR mode + VR பயன்முறை பதிவிறக்கும் பட்டன் பதிவிறக்கங்கள் பதிவிறக்குகிறது diff --git a/library/ui/src/main/res/values-te/strings.xml b/library/ui/src/main/res/values-te/strings.xml index 7f77e32b41..46271ddf33 100644 --- a/library/ui/src/main/res/values-te/strings.xml +++ b/library/ui/src/main/res/values-te/strings.xml @@ -12,7 +12,7 @@ అన్నింటినీ పునరావృతం చేయండి షఫుల్ చేయండి పూర్తి స్క్రీన్ మోడ్ - VR mode + వర్చువల్ రియాలిటీ మోడ్ డౌన్‌లోడ్ చేయి డౌన్‌లోడ్‌లు డౌన్‌లోడ్ చేస్తోంది diff --git a/library/ui/src/main/res/values-ur/strings.xml b/library/ui/src/main/res/values-ur/strings.xml index fab6127fc8..ce77007e4d 100644 --- a/library/ui/src/main/res/values-ur/strings.xml +++ b/library/ui/src/main/res/values-ur/strings.xml @@ -12,7 +12,7 @@ سبھی کو دہرائیں شفل کریں پوری اسکرین والی وضع - VR mode + VR موڈ ڈاؤن لوڈ کریں ڈاؤن لوڈز ڈاؤن لوڈ ہو رہا ہے From 76688589d2f49c2681afb8a9a6b1a5c10a7c33df Mon Sep 17 00:00:00 2001 From: eguven Date: Thu, 1 Nov 2018 08:17:18 -0700 Subject: [PATCH 154/832] Add TrackSelectionUtil.getAverageBitrates method ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219636548 --- .../source/chunk/BaseMediaChunkIterator.java | 8 +- .../source/chunk/MediaChunkIterator.java | 8 + .../trackselection/TrackSelectionUtil.java | 78 +++++++++- .../TrackSelectionUtilTest.java | 137 ++++++++++++++---- 4 files changed, 199 insertions(+), 32 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java index e506cac675..274be54889 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkIterator.java @@ -34,10 +34,11 @@ public abstract class BaseMediaChunkIterator implements MediaChunkIterator { * @param fromIndex The first available index. * @param toIndex The last available index. */ + @SuppressWarnings("method.invocation.invalid") public BaseMediaChunkIterator(long fromIndex, long toIndex) { this.fromIndex = fromIndex; this.toIndex = toIndex; - currentIndex = fromIndex - 1; + reset(); } @Override @@ -51,6 +52,11 @@ public abstract class BaseMediaChunkIterator implements MediaChunkIterator { return !isEnded(); } + @Override + public void reset() { + currentIndex = fromIndex - 1; + } + /** * Verifies that the iterator points to a valid element. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunkIterator.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunkIterator.java index 71d8940e26..59ecc03d7a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunkIterator.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunkIterator.java @@ -55,6 +55,11 @@ public interface MediaChunkIterator { public long getChunkEndTimeUs() { throw new NoSuchElementException(); } + + @Override + public void reset() { + // Do nothing. + } }; /** Returns whether the iteration has reached the end of the available data. */ @@ -93,4 +98,7 @@ public interface MediaChunkIterator { * {@link #next()} or when {@link #isEnded()} is true. */ long getChunkEndTimeUs(); + + /** Resets the iterator to the initial position. */ + void reset(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java index 6f6cd8d80c..9c342b6065 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java @@ -16,7 +16,9 @@ package com.google.android.exoplayer2.trackselection; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; +import com.google.android.exoplayer2.util.Assertions; /** Track selection related utility methods. */ public final class TrackSelectionUtil { @@ -30,7 +32,7 @@ public final class TrackSelectionUtil { * @param iterator Iterator for media chunk sequences. * @param maxDurationUs Maximum duration of chunks to be included in average bitrate, in * microseconds. - * @return Average bitrate for chunks in bits per second, or {@link C#LENGTH_UNSET} if there are + * @return Average bitrate for chunks in bits per second, or {@link Format#NO_VALUE} if there are * no chunks or the first chunk length is unknown. */ public static int getAverageBitrate(MediaChunkIterator iterator, long maxDurationUs) { @@ -51,7 +53,79 @@ public final class TrackSelectionUtil { totalLength += chunkLength; } return totalDurationUs == 0 - ? C.LENGTH_UNSET + ? Format.NO_VALUE : (int) (totalLength * C.BITS_PER_BYTE * C.MICROS_PER_SECOND / totalDurationUs); } + + /** + * Returns average bitrate values for a set of tracks whose upcoming media chunk iterators and + * formats are given. If an average bitrate can't be calculated, an estimation is calculated using + * average bitrate of another track and the ratio of the bitrate values defined in the formats of + * the two tracks. + * + * @param iterators An array of {@link MediaChunkIterator}s providing information about the + * sequence of upcoming media chunks for each track. + * @param formats The track formats. + * @param maxDurationUs Maximum duration of chunks to be included in average bitrate values, in + * microseconds. + * @return Average bitrate values for the tracks. If for a track, an average bitrate or an + * estimation can't be calculated, {@link Format#NO_VALUE} is set. + * @see #getAverageBitrate(MediaChunkIterator, long) + */ + public static int[] getAverageBitrates( + MediaChunkIterator[] iterators, Format[] formats, long maxDurationUs) { + int trackCount = iterators.length; + Assertions.checkArgument(trackCount == formats.length); + if (trackCount == 0) { + return new int[0]; + } + + int[] bitrates = new int[trackCount]; + int[] formatBitrates = new int[trackCount]; + float[] bitrateRatios = new float[trackCount]; + boolean needEstimateBitrate = false; + boolean canEstimateBitrate = false; + for (int i = 0; i < trackCount; i++) { + int bitrate = getAverageBitrate(iterators[i], maxDurationUs); + if (bitrate != Format.NO_VALUE) { + int formatBitrate = formats[i].bitrate; + formatBitrates[i] = formatBitrate; + if (formatBitrate != Format.NO_VALUE) { + bitrateRatios[i] = ((float) bitrate) / formatBitrate; + canEstimateBitrate = true; + } + } else { + needEstimateBitrate = true; + formatBitrates[i] = Format.NO_VALUE; + } + bitrates[i] = bitrate; + } + + if (needEstimateBitrate && canEstimateBitrate) { + for (int i = 0; i < trackCount; i++) { + if (bitrates[i] == Format.NO_VALUE) { + int formatBitrate = formats[i].bitrate; + if (formatBitrate != Format.NO_VALUE) { + int closestFormat = findClosestBitrateFormat(formatBitrate, formatBitrates); + bitrates[i] = (int) (bitrateRatios[closestFormat] * formatBitrate); + } + } + } + } + return bitrates; + } + + private static int findClosestBitrateFormat(int formatBitrate, int[] formatBitrates) { + int closestDistance = Integer.MAX_VALUE; + int closestFormat = C.INDEX_UNSET; + for (int j = 0; j < formatBitrates.length; j++) { + if (formatBitrates[j] != Format.NO_VALUE) { + int distance = Math.abs(formatBitrates[j] - formatBitrate); + if (distance < closestDistance) { + closestFormat = j; + } + } + } + return closestFormat; + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java index df1780c984..2d5f2a7c67 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java @@ -18,7 +18,9 @@ package com.google.android.exoplayer2.trackselection; import static com.google.common.truth.Truth.assertThat; import android.net.Uri; +import android.support.annotation.NonNull; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.upstream.DataSpec; @@ -33,62 +35,60 @@ public class TrackSelectionUtilTest { public static final long MAX_DURATION_US = 30 * C.MICROS_PER_SECOND; @Test - public void getAverageBitrate_emptyIterator_returnsUnsetLength() { + public void getAverageBitrate_emptyIterator_returnsNoValue() { assertThat(TrackSelectionUtil.getAverageBitrate(MediaChunkIterator.EMPTY, MAX_DURATION_US)) - .isEqualTo(C.LENGTH_UNSET); + .isEqualTo(Format.NO_VALUE); } @Test public void getAverageBitrate_oneChunk_returnsChunkBitrate() { - long[] chunkTimeBoundariesSec = {0, 5}; + long[] chunkTimeBoundariesSec = {12, 17}; long[] chunkLengths = {10}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); - int expectedAverageBitrate = - (int) (chunkLengths[0] * C.BITS_PER_BYTE / chunkTimeBoundariesSec[1]); - assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) - .isEqualTo(expectedAverageBitrate); + + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)).isEqualTo(16); } @Test public void getAverageBitrate_multipleSameDurationChunks_returnsAverageChunkBitrate() { long[] chunkTimeBoundariesSec = {0, 5, 10}; long[] chunkLengths = {10, 20}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); - long totalLength = chunkLengths[0] + chunkLengths[1]; - int expectedAverageBitrate = (int) (totalLength * C.BITS_PER_BYTE / chunkTimeBoundariesSec[2]); - assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) - .isEqualTo(expectedAverageBitrate); + + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)).isEqualTo(24); } @Test public void getAverageBitrate_multipleDifferentDurationChunks_returnsAverageChunkBitrate() { long[] chunkTimeBoundariesSec = {0, 5, 15, 30}; long[] chunkLengths = {10, 20, 30}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); - long totalLength = chunkLengths[0] + chunkLengths[1] + chunkLengths[2]; - int expectedAverageBitrate = (int) (totalLength * C.BITS_PER_BYTE / chunkTimeBoundariesSec[3]); - assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) - .isEqualTo(expectedAverageBitrate); + + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)).isEqualTo(16); } @Test - public void getAverageBitrate_firstChunkLengthUnset_returnsUnsetLength() { + public void getAverageBitrate_firstChunkLengthUnset_returnsNoValue() { long[] chunkTimeBoundariesSec = {0, 5, 15, 30}; long[] chunkLengths = {C.LENGTH_UNSET, 20, 30}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) - .isEqualTo(C.LENGTH_UNSET); + .isEqualTo(Format.NO_VALUE); } @Test public void getAverageBitrate_secondChunkLengthUnset_returnsFirstChunkBitrate() { long[] chunkTimeBoundariesSec = {0, 5, 15, 30}; long[] chunkLengths = {10, C.LENGTH_UNSET, 30}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); - int expectedAverageBitrate = - (int) (chunkLengths[0] * C.BITS_PER_BYTE / chunkTimeBoundariesSec[1]); - assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) - .isEqualTo(expectedAverageBitrate); + + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)).isEqualTo(16); } @Test @@ -96,22 +96,101 @@ public class TrackSelectionUtilTest { getAverageBitrate_chunksExceedingMaxDuration_returnsAverageChunkBitrateUpToMaxDuration() { long[] chunkTimeBoundariesSec = {0, 5, 15, 45, 50}; long[] chunkLengths = {10, 20, 30, 100}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); - // Just half of the third chunk is in the max duration - long totalLength = chunkLengths[0] + chunkLengths[1] + chunkLengths[2] / 2; - int expectedAverageBitrate = - (int) (totalLength * C.BITS_PER_BYTE * C.MICROS_PER_SECOND / MAX_DURATION_US); - assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)) - .isEqualTo(expectedAverageBitrate); + + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, 30 * C.MICROS_PER_SECOND)) + .isEqualTo(12); } @Test - public void getAverageBitrate_zeroMaxDuration_returnsUnsetLength() { + public void getAverageBitrate_zeroMaxDuration_returnsNoValue() { long[] chunkTimeBoundariesSec = {0, 5, 10}; long[] chunkLengths = {10, 20}; + FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); + assertThat(TrackSelectionUtil.getAverageBitrate(iterator, /* maxDurationUs= */ 0)) - .isEqualTo(C.LENGTH_UNSET); + .isEqualTo(Format.NO_VALUE); + } + + @Test + public void getAverageBitrates_noIterator_returnsEmptyArray() { + assertThat( + TrackSelectionUtil.getAverageBitrates( + new MediaChunkIterator[0], new Format[0], MAX_DURATION_US)) + .hasLength(0); + } + + @Test + public void getAverageBitrates_emptyIterator_returnsNoValue() { + int[] averageBitrates = + TrackSelectionUtil.getAverageBitrates( + new MediaChunkIterator[] {MediaChunkIterator.EMPTY}, + new Format[] {createFormatWithBitrate(10)}, + MAX_DURATION_US); + + assertThat(averageBitrates).asList().containsExactly(Format.NO_VALUE); + } + + @Test + public void getAverageBitrates_twoTracks_returnsAverageChunkBitrates() { + FakeIterator iterator1 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10}); + FakeIterator iterator2 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30}, + /* chunkLengths= */ new long[] {10, 20, 30}); + + int[] averageBitrates = + TrackSelectionUtil.getAverageBitrates( + new MediaChunkIterator[] {iterator1, iterator2}, + new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)}, + MAX_DURATION_US); + + assertThat(averageBitrates).asList().containsExactly(8, 16).inOrder(); + } + + @Test + public void getAverageBitrates_oneEmptyIteratorOneWithChunks_returnsEstimationForEmpty() { + FakeIterator iterator1 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 5}, /* chunkLengths= */ new long[] {10}); + Format format1 = createFormatWithBitrate(10); + MediaChunkIterator iterator2 = MediaChunkIterator.EMPTY; + Format format2 = createFormatWithBitrate(20); + + int[] averageBitrates = + TrackSelectionUtil.getAverageBitrates( + new MediaChunkIterator[] {iterator1, iterator2}, + new Format[] {format1, format2}, + MAX_DURATION_US); + + assertThat(averageBitrates).asList().containsExactly(16, 32).inOrder(); + } + + @Test + public void getAverageBitrates_formatWithoutBitrate_returnsNoValueForEmpty() { + FakeIterator iterator1 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 5}, /* chunkLengths= */ new long[] {10}); + Format format1 = createFormatWithBitrate(10); + MediaChunkIterator iterator2 = MediaChunkIterator.EMPTY; + Format format2 = createFormatWithBitrate(Format.NO_VALUE); + + int[] averageBitrates = + TrackSelectionUtil.getAverageBitrates( + new MediaChunkIterator[] {iterator1, iterator2}, + new Format[] {format1, format2}, + MAX_DURATION_US); + + assertThat(averageBitrates).asList().containsExactly(16, Format.NO_VALUE).inOrder(); + } + + @NonNull + private static Format createFormatWithBitrate(int bitrate) { + return Format.createSampleFormat(null, null, null, bitrate, null); } private static final class FakeIterator extends BaseMediaChunkIterator { From 3b1d0e270bf67567021388ef86625dc236cc5eb3 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 2 Nov 2018 01:36:41 -0700 Subject: [PATCH 155/832] Double the buffer duration for AC3 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219765107 --- .../google/android/exoplayer2/audio/DefaultAudioSink.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 6ad56a78d9..d29659b102 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -172,6 +172,9 @@ public final class DefaultAudioSink implements AudioSink { */ private static final int BUFFER_MULTIPLICATION_FACTOR = 4; + /** To avoid underruns on some devices (e.g., Broadcom 7271), scale up the AC3 buffer duration. */ + private static final int AC3_BUFFER_MULTIPLICATION_FACTOR = 2; + /** * @see AudioTrack#ERROR_BAD_VALUE */ @@ -483,6 +486,9 @@ public final class DefaultAudioSink implements AudioSink { return Util.constrainValue(multipliedBufferSize, minAppBufferSize, maxAppBufferSize); } else { int rate = getMaximumEncodedRateBytesPerSecond(outputEncoding); + if (outputEncoding == C.ENCODING_AC3) { + rate *= AC3_BUFFER_MULTIPLICATION_FACTOR; + } return (int) (PASSTHROUGH_BUFFER_DURATION_US * rate / C.MICROS_PER_SECOND); } } From b0555315cb693ae2d191b2579713be63c18403fa Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 2 Nov 2018 04:25:31 -0700 Subject: [PATCH 156/832] Move all messaging constants to ExoCastConstants Also make ExoCastMessage#mediaItemAsJsonObject protected. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219780620 --- .../exoplayer2/ext/cast/ExoCastConstants.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java new file mode 100644 index 0000000000..c5647dd949 --- /dev/null +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.cast; + +/** Defines constants used by the Cast extension. */ +public final class ExoCastConstants { + + private ExoCastConstants() {} + + public static final int PROTOCOL_VERSION = 0; + + // String representations. + + public static final String STR_STATE_IDLE = "IDLE"; + public static final String STR_STATE_BUFFERING = "BUFFERING"; + public static final String STR_STATE_READY = "READY"; + public static final String STR_STATE_ENDED = "ENDED"; + + public static final String STR_REPEAT_MODE_OFF = "REPEAT_MODE_OFF"; + public static final String STR_REPEAT_MODE_ONE = "REPEAT_MODE_ONE"; + public static final String STR_REPEAT_MODE_ALL = "REPEAT_MODE_ALL"; + + // Methods. + + public static final String METHOD_BASE = "player."; + + public static final String METHOD_ADD_ITEMS = METHOD_BASE + "addItems"; + public static final String METHOD_MOVE_ITEM = METHOD_BASE + "moveItem"; + public static final String METHOD_REMOVE_ITEMS = METHOD_BASE + "removeItems"; + public static final String METHOD_SET_PLAY_WHEN_READY = METHOD_BASE + "setPlayWhenReady"; + public static final String METHOD_SET_REPEAT_MODE = METHOD_BASE + "setRepeatMode"; + public static final String METHOD_SET_SHUFFLE_MODE_ENABLED = + METHOD_BASE + "setShuffleModeEnabled"; + public static final String METHOD_SEEK_TO = METHOD_BASE + "seekTo"; + public static final String METHOD_SET_PLAYBACK_PARAMETERS = METHOD_BASE + "setPlaybackParameters"; + + // JSON message keys. + + public static final String KEY_PROTOCOL_VERSION = "protocolVersion"; + public static final String KEY_METHOD = "method"; + public static final String KEY_SEQUENCE_NUMBER = "sequenceNumber"; + public static final String KEY_INDEX = "index"; + public static final String KEY_ITEMS = "items"; + public static final String KEY_ARGS = "args"; + public static final String KEY_UUID = "uuid"; + public static final String KEY_UUIDS = "uuids"; + public static final String KEY_LICENSE_SERVER = "licenseServer"; + public static final String KEY_TITLE = "title"; + public static final String KEY_URI = "uri"; + public static final String KEY_REQUEST_HEADERS = "requestHeaders"; + public static final String KEY_DESCRIPTION = "description"; + public static final String KEY_MEDIA = "media"; + public static final String KEY_DRM_SCHEMES = "drmSchemes"; + public static final String KEY_START_POSITION_US = "startPositionUs"; + public static final String KEY_END_POSITION_US = "endPositionUs"; + public static final String KEY_MIME_TYPE = "mimeType"; + public static final String KEY_PLAY_WHEN_READY = "playWhenReady"; + public static final String KEY_REPEAT_MODE = "repeatMode"; + public static final String KEY_SHUFFLE_MODE_ENABLED = "shuffleModeEnabled"; + public static final String KEY_POSITION_MS = "positionMs"; + public static final String KEY_SPEED = "speed"; + public static final String KEY_PITCH = "pitch"; + public static final String KEY_SKIP_SILENCE = "skipSilence"; + public static final String KEY_PLAYBACK_STATE = "playbackState"; + public static final String KEY_MEDIA_QUEUE = "mediaQueue"; + public static final String KEY_IS_LOADING = "isLoading"; + public static final String KEY_PLAYBACK_ERROR = "error"; + public static final String KEY_PLAYBACK_POSITION = "playbackPosition"; + public static final String KEY_PLAYBACK_PARAMETERS = "playbackParameters"; +} From 32927bb62ca8d59f8f6af22ae13520e8cb145f16 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 2 Nov 2018 05:28:24 -0700 Subject: [PATCH 157/832] Update the DefaultExtractorInput's peek buffer length on each write This prevents leaving an inconsistent state after a EOF exception. Issue:#5039 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219785275 --- RELEASENOTES.md | 7 +++++-- .../extractor/DefaultExtractorInput.java | 4 ++-- .../extractor/DefaultExtractorInputTest.java | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c61b1fecb4..569e2e555e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -27,8 +27,11 @@ * Fix handling of streams with appended data ([#4954](https://github.com/google/ExoPlayer/issues/4954)). * DASH: Parse ProgramInformation element if present in the manifest. -* HLS: Add constructor to `DefaultHlsExtractorFactory` for adding TS payload - reader factory flags +* HLS: + * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload + reader factory flags + * Fix bug in segment sniffing + ([#5039](https://github.com/google/ExoPlayer/issues/5039)). ([#4861](https://github.com/google/ExoPlayer/issues/4861)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java index c3f6304091..450cca42b0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java @@ -130,16 +130,16 @@ public final class DefaultExtractorInput implements ExtractorInput { public boolean advancePeekPosition(int length, boolean allowEndOfInput) throws IOException, InterruptedException { ensureSpaceForPeek(length); - int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length); + int bytesPeeked = peekBufferLength - peekBufferPosition; while (bytesPeeked < length) { bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked, allowEndOfInput); if (bytesPeeked == C.RESULT_END_OF_INPUT) { return false; } + peekBufferLength = peekBufferPosition + bytesPeeked; } peekBufferPosition += length; - peekBufferLength = Math.max(peekBufferLength, peekBufferPosition); return true; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java index a96dfaf2f8..8b26361578 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java @@ -338,6 +338,23 @@ public class DefaultExtractorInputTest { } } + @Test + public void testPeekFullyAfterEofExceptionPeeksAsExpected() throws Exception { + DefaultExtractorInput input = createDefaultExtractorInput(); + byte[] target = new byte[TEST_DATA.length + 10]; + + try { + input.peekFully(target, /* offset= */ 0, target.length); + fail(); + } catch (EOFException expected) { + // Do nothing. Expected. + } + input.peekFully(target, /* offset= */ 0, /* length= */ TEST_DATA.length); + + assertThat(input.getPeekPosition()).isEqualTo(TEST_DATA.length); + assertThat(Arrays.equals(TEST_DATA, Arrays.copyOf(target, TEST_DATA.length))).isTrue(); + } + @Test public void testResetPeekPosition() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); From 34797651c28ffac03082ceed96b8609063fe14ae Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 2 Nov 2018 07:26:30 -0700 Subject: [PATCH 158/832] Add support for .cmf* extension in DefaultHlsExtractorFactory This makes extractor selection a bit more efficient for some CMAF files. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219795105 --- .../exoplayer2/source/hls/DefaultHlsExtractorFactory.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java index 3d75923553..8a403c3759 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java @@ -48,6 +48,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { public static final String MP4_FILE_EXTENSION = ".mp4"; public static final String M4_FILE_EXTENSION_PREFIX = ".m4"; public static final String MP4_FILE_EXTENSION_PREFIX = ".mp4"; + public static final String CMF_FILE_EXTENSION_PREFIX = ".cmf"; public static final String VTT_FILE_EXTENSION = ".vtt"; public static final String WEBVTT_FILE_EXTENSION = ".webvtt"; @@ -191,7 +192,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { return new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0); } else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION) || lastPathSegment.startsWith(M4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 4) - || lastPathSegment.startsWith(MP4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)) { + || lastPathSegment.startsWith(MP4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5) + || lastPathSegment.startsWith(CMF_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)) { return new FragmentedMp4Extractor( /* flags= */ 0, timestampAdjuster, From 251c4207674aa2f721b7212f618a6d2907916db8 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 2 Nov 2018 10:26:55 -0700 Subject: [PATCH 159/832] Experimental parameter for setting a render time limit in MediaCodecRenderer Currently, MediaCodecRenderer's render loop does not have any time limit. We always try to drain and feed as much buffers as possible. This may lead to a pattern of feeding phase takes too much time from previous loop, which makes the next draining phase drops all buffers, making these buffers available for feeding all at once, and the pattern keeps repeating. This CL adds an experimental parameter for setting a time limit. If the time limit is exceeded, the feeding process stops even if more input could be fed. The default behavior is unchanged ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219820386 --- .../mediacodec/MediaCodecRenderer.java | 24 ++++++++++++++++++- .../upstream/DefaultBandwidthMeter.java | 2 ++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 74c575c1ef..6ba2557ede 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -294,6 +294,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private Format outputFormat; private DrmSession drmSession; private DrmSession pendingDrmSession; + private long renderTimeLimitMs; private float rendererOperatingRate; @Nullable private MediaCodec codec; @Nullable private Format codecFormat; @@ -371,6 +372,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecDrainAction = DRAIN_ACTION_NONE; codecOperatingRate = CODEC_OPERATING_RATE_UNSET; rendererOperatingRate = 1f; + renderTimeLimitMs = C.TIME_UNSET; + } + + /** + * Set a limit on the time a single {@link #render(long, long)} call can spend draining and + * filling the decoder. + * + *

    This method is experimental, and will be renamed or removed in a future release. It should + * only be called before the renderer is used. + * + * @param renderTimeLimitMs The render time limit in milliseconds, or {@link C#TIME_UNSET} for no + * limit. + */ + public void experimental_setRenderTimeLimitMs(long renderTimeLimitMs) { + this.renderTimeLimitMs = renderTimeLimitMs; } @Override @@ -623,9 +639,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // We have a format. maybeInitCodec(); if (codec != null) { + long drainStartTimeMs = SystemClock.elapsedRealtime(); TraceUtil.beginSection("drainAndFeed"); while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {} - while (feedInputBuffer()) {} + while (feedInputBuffer() && shouldContinueFeeding(drainStartTimeMs)) {} TraceUtil.endSection(); } else { decoderCounters.skippedInputBufferCount += skipSource(positionUs); @@ -852,6 +869,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer { onCodecInitialized(codecName, codecInitializedTimestamp, elapsed); } + private boolean shouldContinueFeeding(long drainStartTimeMs) { + return renderTimeLimitMs == C.TIME_UNSET + || SystemClock.elapsedRealtime() - drainStartTimeMs < renderTimeLimitMs; + } + private void getCodecBuffers(MediaCodec codec) { if (Util.SDK_INT < 21) { inputBuffers = codec.getInputBuffers(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index eef86a3aab..d1bda8010f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -170,6 +170,8 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList /** * Sets whether to reset if the network type changes. * + *

    This method is experimental, and will be renamed or removed in a future release. + * * @param resetOnNetworkTypeChange Whether to reset if the network type changes. * @return This builder. */ From c5db69d22cd4daa913e2493a5c76384ad9959530 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 2 Nov 2018 10:32:44 -0700 Subject: [PATCH 160/832] Fix further issues with decoder re-use It's no longer guaranteed that onOutputFormatChanged will always be called when the renderer is re-enabled, since the codec may be kept and its output format may not change. Hence we need to avoid resetting state in onDisabled that we need when the renderer is enabled again. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219821519 --- .../android/exoplayer2/audio/AudioSink.java | 72 +++++++++---------- .../exoplayer2/audio/DefaultAudioSink.java | 18 ++--- .../audio/MediaCodecAudioRenderer.java | 13 +++- .../audio/SimpleDecoderAudioRenderer.java | 4 +- .../video/MediaCodecVideoRenderer.java | 7 +- .../audio/SimpleDecoderAudioRendererTest.java | 2 +- 6 files changed, 59 insertions(+), 57 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java index bb7ef22ef4..bf1dc3ca8a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java @@ -23,29 +23,30 @@ import java.nio.ByteBuffer; /** * A sink that consumes audio data. - *

    - * Before starting playback, specify the input audio format by calling - * {@link #configure(int, int, int, int, int[], int, int)}. - *

    - * Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()} + * + *

    Before starting playback, specify the input audio format by calling {@link #configure(int, + * int, int, int, int[], int, int)}. + * + *

    Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()} * when the data being fed is discontinuous. Call {@link #play()} to start playing the written data. - *

    - * Call {@link #configure(int, int, int, int, int[], int, int)} whenever the input format changes. - * The sink will be reinitialized on the next call to {@link #handleBuffer(ByteBuffer, long)}. - *

    - * Call {@link #reset()} to prepare the sink to receive audio data from a new playback position. - *

    - * Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers will - * be provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #reset()}. Call - * {@link #release()} when the instance is no longer required. - *

    - * The implementation may be backed by a platform {@link AudioTrack}. In this case, - * {@link #setAudioSessionId(int)}, {@link #setAudioAttributes(AudioAttributes)}, - * {@link #enableTunnelingV21(int)} and/or {@link #disableTunneling()} may be called before writing - * data to the sink. These methods may also be called after writing data to the sink, in which case - * it will be reinitialized as required. For implementations that are not based on platform - * {@link AudioTrack}s, calling methods relating to audio sessions, audio attributes, and tunneling - * may have no effect. + * + *

    Call {@link #configure(int, int, int, int, int[], int, int)} whenever the input format + * changes. The sink will be reinitialized on the next call to {@link #handleBuffer(ByteBuffer, + * long)}. + * + *

    Call {@link #flush()} to prepare the sink to receive audio data from a new playback position. + * + *

    Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers + * will be provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #flush()}. + * Call {@link #reset()} when the instance is no longer required. + * + *

    The implementation may be backed by a platform {@link AudioTrack}. In this case, {@link + * #setAudioSessionId(int)}, {@link #setAudioAttributes(AudioAttributes)}, {@link + * #enableTunnelingV21(int)} and/or {@link #disableTunneling()} may be called before writing data to + * the sink. These methods may also be called after writing data to the sink, in which case it will + * be reinitialized as required. For implementations that are not based on platform {@link + * AudioTrack}s, calling methods relating to audio sessions, audio attributes, and tunneling may + * have no effect. */ public interface AudioSink { @@ -197,7 +198,7 @@ public interface AudioSink { * @param trimStartFrames The number of audio frames to trim from the start of data written to the * sink after this call. * @param trimEndFrames The number of audio frames to trim from data written to the sink - * immediately preceding the next call to {@link #reset()} or this method. + * immediately preceding the next call to {@link #flush()} or this method. * @throws ConfigurationException If an error occurs configuring the sink. */ void configure( @@ -223,11 +224,11 @@ public interface AudioSink { * ending at its limit (exclusive). The position of the {@link ByteBuffer} is advanced by the * number of bytes that were handled. {@link Listener#onPositionDiscontinuity()} will be called if * {@code presentationTimeUs} is discontinuous with the last buffer handled since the last reset. - *

    - * Returns whether the data was handled in full. If the data was not handled in full then the same - * {@link ByteBuffer} must be provided to subsequent calls until it has been fully consumed, - * except in the case of an intervening call to {@link #reset()} (or to - * {@link #configure(int, int, int, int, int[], int, int)} that causes the sink to be reset). + * + *

    Returns whether the data was handled in full. If the data was not handled in full then the + * same {@link ByteBuffer} must be provided to subsequent calls until it has been fully consumed, + * except in the case of an intervening call to {@link #flush()} (or to {@link #configure(int, + * int, int, int, int[], int, int)} that causes the sink to be flushed). * * @param buffer The buffer containing audio data. * @param presentationTimeUs The presentation timestamp of the buffer in microseconds. @@ -316,15 +317,12 @@ public interface AudioSink { void pause(); /** - * Resets the sink, after which it is ready to receive buffers from a new playback position. - *

    - * The audio session may remain active until {@link #release()} is called. + * Flushes the sink, after which it is ready to receive buffers from a new playback position. + * + *

    The audio session may remain active until {@link #reset()} is called. */ + void flush(); + + /** Resets the renderer, releasing any resources that it currently holds. */ void reset(); - - /** - * Releases any resources associated with this instance. - */ - void release(); - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index d29659b102..7ba060ad76 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -463,7 +463,7 @@ public final class DefaultAudioSink implements AudioSink { return; } - reset(); + flush(); this.processingEnabled = processingEnabled; outputSampleRate = sampleRate; @@ -681,7 +681,7 @@ public final class DefaultAudioSink implements AudioSink { if (audioTrackPositionTracker.isStalled(getWrittenFrames())) { Log.w(TAG, "Resetting stalled audio track"); - reset(); + flush(); return true; } @@ -871,7 +871,7 @@ public final class DefaultAudioSink implements AudioSink { // The audio attributes are ignored in tunneling mode, so no need to reset. return; } - reset(); + flush(); audioSessionId = C.AUDIO_SESSION_ID_UNSET; } @@ -879,7 +879,7 @@ public final class DefaultAudioSink implements AudioSink { public void setAudioSessionId(int audioSessionId) { if (this.audioSessionId != audioSessionId) { this.audioSessionId = audioSessionId; - reset(); + flush(); } } @@ -907,7 +907,7 @@ public final class DefaultAudioSink implements AudioSink { if (!tunneling || audioSessionId != tunnelingAudioSessionId) { tunneling = true; audioSessionId = tunnelingAudioSessionId; - reset(); + flush(); } } @@ -916,7 +916,7 @@ public final class DefaultAudioSink implements AudioSink { if (tunneling) { tunneling = false; audioSessionId = C.AUDIO_SESSION_ID_UNSET; - reset(); + flush(); } } @@ -947,7 +947,7 @@ public final class DefaultAudioSink implements AudioSink { } @Override - public void reset() { + public void flush() { if (isInitialized()) { submittedPcmBytes = 0; submittedEncodedFrames = 0; @@ -995,8 +995,8 @@ public final class DefaultAudioSink implements AudioSink { } @Override - public void release() { - reset(); + public void reset() { + flush(); releaseKeepSessionIdAudioTrack(); for (AudioProcessor audioProcessor : toIntPcmAvailableAudioProcessors) { audioProcessor.reset(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index c90b815347..dbc25c8d42 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -511,7 +511,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media @Override protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { super.onPositionReset(positionUs, joining); - audioSink.reset(); + audioSink.flush(); currentPositionUs = positionUs; allowFirstBufferPositionDiscontinuity = true; allowPositionDiscontinuity = true; @@ -537,7 +537,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media try { lastInputTimeUs = C.TIME_UNSET; pendingStreamChangeCount = 0; - audioSink.release(); + audioSink.flush(); } finally { try { super.onDisabled(); @@ -548,6 +548,15 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } } + @Override + protected void onReset() { + try { + super.onReset(); + } finally { + audioSink.reset(); + } + } + @Override public boolean isEnded() { return super.isEnded() && audioSink.isEnded(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index cecb17d96c..3df38e3557 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -538,7 +538,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements @Override protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { - audioSink.reset(); + audioSink.flush(); currentPositionUs = positionUs; allowFirstBufferPositionDiscontinuity = true; allowPositionDiscontinuity = true; @@ -567,7 +567,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements waitingForKeys = false; try { releaseDecoder(); - audioSink.release(); + audioSink.reset(); } finally { try { if (drmSession != null) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index a896bc2322..29a75f06e5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -361,18 +361,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override protected void onDisabled() { - currentWidth = Format.NO_VALUE; - currentHeight = Format.NO_VALUE; - currentPixelWidthHeightRatio = Format.NO_VALUE; - pendingPixelWidthHeightRatio = Format.NO_VALUE; - outputStreamOffsetUs = C.TIME_UNSET; lastInputTimeUs = C.TIME_UNSET; + outputStreamOffsetUs = C.TIME_UNSET; pendingOutputStreamOffsetCount = 0; clearReportedVideoSize(); clearRenderedFirstFrame(); frameReleaseTimeHelper.disable(); tunnelingOnFrameRenderedListener = null; - tunneling = false; try { super.onDisabled(); } finally { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java index 48e71c619c..3f28999820 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java @@ -104,7 +104,7 @@ public class SimpleDecoderAudioRendererTest { verify(mockAudioSink, times(1)).playToEndOfStream(); audioRenderer.disable(); audioRenderer.reset(); - verify(mockAudioSink, times(1)).release(); + verify(mockAudioSink, times(1)).reset(); } private static final class FakeDecoder From 37f4540156369891e95f515bd2edf739f067cb85 Mon Sep 17 00:00:00 2001 From: eguven Date: Fri, 2 Nov 2018 11:13:53 -0700 Subject: [PATCH 161/832] Move BaseGvrPlayerActivity to com.google.android.exoplayer2.ext.gvr ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219829614 --- .../{ui/spherical => ext/gvr}/BaseGvrPlayerActivity.java | 6 ++++-- .../android/exoplayer2/ui/spherical/CanvasRenderer.java | 8 ++++---- .../android/exoplayer2/ui/spherical/GlViewGroup.java | 2 +- .../android/exoplayer2/ui/spherical/PointerRenderer.java | 2 +- .../android/exoplayer2/ui/spherical/SceneRenderer.java | 3 +-- 5 files changed, 11 insertions(+), 10 deletions(-) rename extensions/gvr/src/main/java/com/google/android/exoplayer2/{ui/spherical => ext/gvr}/BaseGvrPlayerActivity.java (98%) diff --git a/extensions/gvr/src/main/java/com/google/android/exoplayer2/ui/spherical/BaseGvrPlayerActivity.java b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/BaseGvrPlayerActivity.java similarity index 98% rename from extensions/gvr/src/main/java/com/google/android/exoplayer2/ui/spherical/BaseGvrPlayerActivity.java rename to extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/BaseGvrPlayerActivity.java index 48acc4a9c8..acddae49e9 100644 --- a/extensions/gvr/src/main/java/com/google/android/exoplayer2/ui/spherical/BaseGvrPlayerActivity.java +++ b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/BaseGvrPlayerActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.exoplayer2.ui.spherical; +package com.google.android.exoplayer2.ext.gvr; import android.content.Context; import android.content.Intent; @@ -32,8 +32,10 @@ import android.view.MotionEvent; import android.view.Surface; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.ext.gvr.R; import com.google.android.exoplayer2.ui.PlayerControlView; +import com.google.android.exoplayer2.ui.spherical.GlViewGroup; +import com.google.android.exoplayer2.ui.spherical.PointerRenderer; +import com.google.android.exoplayer2.ui.spherical.SceneRenderer; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import com.google.vr.ndk.base.DaydreamApi; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/CanvasRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/CanvasRenderer.java index 9e51c2ba45..247de5a3d0 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/CanvasRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/CanvasRenderer.java @@ -40,7 +40,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * GL thread before it can be rendered. */ @TargetApi(15) -/* package */ final class CanvasRenderer { +public final class CanvasRenderer { private static final float WIDTH_UNIT = 0.8f; private static final float DISTANCE_UNIT = 1f; @@ -156,7 +156,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } /** Finishes constructing this object on the GL Thread. */ - /* package */ void init() { + public void init() { if (program != 0) { return; } @@ -183,7 +183,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * @param viewProjectionMatrix Array of floats containing the quad's 4x4 perspective matrix in the * {@link android.opengl.Matrix} format. */ - /* package */ void draw(float[] viewProjectionMatrix) { + public void draw(float[] viewProjectionMatrix) { if (displaySurfaceTexture == null) { return; } @@ -237,7 +237,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } /** Frees GL resources. */ - /* package */ void shutdown() { + public void shutdown() { if (program != 0) { GLES20.glDeleteProgram(program); GLES20.glDeleteTextures(1, new int[] {textureId}, 0); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlViewGroup.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlViewGroup.java index 09e2f22207..b3c1a963b8 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlViewGroup.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/GlViewGroup.java @@ -31,7 +31,7 @@ import android.widget.FrameLayout; import com.google.android.exoplayer2.util.Assertions; /** This View uses standard Android APIs to render its child Views to a texture. */ -/* package */ final class GlViewGroup extends FrameLayout { +public final class GlViewGroup extends FrameLayout { private final CanvasRenderer canvasRenderer; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/PointerRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/PointerRenderer.java index d0a4d4c882..fa7118a0fb 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/PointerRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/PointerRenderer.java @@ -24,7 +24,7 @@ import com.google.android.exoplayer2.util.GlUtil; import java.nio.FloatBuffer; /** Renders a pointer. */ -/* package */ final class PointerRenderer { +public final class PointerRenderer { // The pointer quad is 2 * SIZE units. private static final float SIZE = .01f; private static final float DISTANCE = 1; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java index b3b1acb7e4..6793b0c584 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java @@ -36,8 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** Renders a GL Scene. */ -/* package */ final class SceneRenderer - implements VideoFrameMetadataListener, CameraMotionListener { +public final class SceneRenderer implements VideoFrameMetadataListener, CameraMotionListener { private final AtomicBoolean frameAvailable; private final AtomicBoolean resetRotationAtNextFrame; From 894bac617350df582dbf59a832d042fe64979649 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 5 Nov 2018 01:23:47 -0800 Subject: [PATCH 162/832] Tweak dev guide / readme ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220059244 --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2b6a508aaa..37967dd527 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ repository and depend on the modules locally. ### From JCenter ### The easiest way to get started using ExoPlayer is to add it as a gradle -dependency. You need to make sure you have the JCenter and Google repositories +dependency. You need to make sure you have the Google and JCenter repositories included in the `build.gradle` file in the root of your project: ```gradle @@ -47,8 +47,13 @@ implementation 'com.google.android.exoplayer:exoplayer:2.X.X' where `2.X.X` is your preferred version. If not enabled already, you also need to turn on Java 8 support in all `build.gradle` files depending on ExoPlayer, by -adding `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to the -`android` section. +adding the following to the `android` section: + +```gradle +compileOptions { + targetCompatibility JavaVersion.VERSION_1_8 +} +``` As an alternative to the full library, you can depend on only the library modules that you actually need. For example the following will add dependencies From 0afd9c17e64eda74f5c3d52b08c25a653201af38 Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 5 Nov 2018 02:29:53 -0800 Subject: [PATCH 163/832] Add TrackSelectionUtil getBitratesUsingPastInfo ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220066159 --- .../trackselection/TrackSelectionUtil.java | 91 +++++- .../TrackSelectionUtilTest.java | 276 ++++++++++++++++-- .../exoplayer2/testutil/FakeMediaChunk.java | 6 +- 3 files changed, 332 insertions(+), 41 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java index 9c342b6065..fdfd87910e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java @@ -17,8 +17,12 @@ package com.google.android.exoplayer2.trackselection; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; +import com.google.android.exoplayer2.source.chunk.MediaChunkListIterator; import com.google.android.exoplayer2.util.Assertions; +import java.util.Arrays; +import java.util.List; /** Track selection related utility methods. */ public final class TrackSelectionUtil { @@ -58,10 +62,11 @@ public final class TrackSelectionUtil { } /** - * Returns average bitrate values for a set of tracks whose upcoming media chunk iterators and - * formats are given. If an average bitrate can't be calculated, an estimation is calculated using - * average bitrate of another track and the ratio of the bitrate values defined in the formats of - * the two tracks. + * Returns bitrate values for a set of tracks whose upcoming media chunk iterators and formats are + * given. + * + *

    If an average bitrate can't be calculated, an estimation is calculated using average bitrate + * of another track and the ratio of the bitrate values defined in the formats of the two tracks. * * @param iterators An array of {@link MediaChunkIterator}s providing information about the * sequence of upcoming media chunks for each track. @@ -72,7 +77,7 @@ public final class TrackSelectionUtil { * estimation can't be calculated, {@link Format#NO_VALUE} is set. * @see #getAverageBitrate(MediaChunkIterator, long) */ - public static int[] getAverageBitrates( + public static int[] getBitratesUsingFutureInfo( MediaChunkIterator[] iterators, Format[] formats, long maxDurationUs) { int trackCount = iterators.length; Assertions.checkArgument(trackCount == formats.length); @@ -102,26 +107,82 @@ public final class TrackSelectionUtil { } if (needEstimateBitrate && canEstimateBitrate) { - for (int i = 0; i < trackCount; i++) { - if (bitrates[i] == Format.NO_VALUE) { - int formatBitrate = formats[i].bitrate; - if (formatBitrate != Format.NO_VALUE) { - int closestFormat = findClosestBitrateFormat(formatBitrate, formatBitrates); - bitrates[i] = (int) (bitrateRatios[closestFormat] * formatBitrate); - } - } - } + estimateBitrates(bitrates, formats, formatBitrates, bitrateRatios); } return bitrates; } - private static int findClosestBitrateFormat(int formatBitrate, int[] formatBitrates) { + /** + * Returns bitrate values for a set of tracks whose formats are given, using the given queue of + * already buffered {@link MediaChunk} instances. + * + * @param queue The queue of already buffered {@link MediaChunk} instances. Must not be modified. + * @param formats The track formats. + * @param maxDurationUs Maximum duration of chunks to be included in average bitrate values, in + * microseconds. + * @return Bitrate values for the tracks. If for a track, a bitrate value can't be calculated, + * {@link Format#NO_VALUE} is set. + * @see #getBitratesUsingFutureInfo(MediaChunkIterator[], Format[], long) + */ + public static int[] getBitratesUsingPastInfo( + List queue, Format[] formats, long maxDurationUs) { + int[] bitrates = new int[formats.length]; + Arrays.fill(bitrates, Format.NO_VALUE); + int queueAverageBitrate = getAverageQueueBitrate(queue, maxDurationUs); + if (queueAverageBitrate == Format.NO_VALUE) { + return bitrates; + } + int queueFormatBitrate = queue.get(queue.size() - 1).trackFormat.bitrate; + if (queueFormatBitrate != Format.NO_VALUE) { + float queueBitrateRatio = ((float) queueAverageBitrate) / queueFormatBitrate; + estimateBitrates( + bitrates, formats, new int[] {queueFormatBitrate}, new float[] {queueBitrateRatio}); + } + return bitrates; + } + + private static int getAverageQueueBitrate(List queue, long maxDurationUs) { + if (queue.isEmpty()) { + return Format.NO_VALUE; + } + MediaChunkListIterator iterator = + new MediaChunkListIterator(getSingleFormatSubQueue(queue), /* reverseOrder= */ true); + return getAverageBitrate(iterator, maxDurationUs); + } + + private static List getSingleFormatSubQueue( + List queue) { + Format queueFormat = queue.get(queue.size() - 1).trackFormat; + int queueSize = queue.size(); + for (int i = queueSize - 2; i >= 0; i--) { + if (!queue.get(i).trackFormat.equals(queueFormat)) { + return queue.subList(i + 1, queueSize); + } + } + return queue; + } + + private static void estimateBitrates( + int[] bitrates, Format[] formats, int[] formatBitrates, float[] bitrateRatios) { + for (int i = 0; i < bitrates.length; i++) { + if (bitrates[i] == Format.NO_VALUE) { + int formatBitrate = formats[i].bitrate; + if (formatBitrate != Format.NO_VALUE) { + int closestFormat = getClosestBitrateIndex(formatBitrate, formatBitrates); + bitrates[i] = (int) (bitrateRatios[closestFormat] * formatBitrate); + } + } + } + } + + private static int getClosestBitrateIndex(int formatBitrate, int[] formatBitrates) { int closestDistance = Integer.MAX_VALUE; int closestFormat = C.INDEX_UNSET; for (int j = 0; j < formatBitrates.length; j++) { if (formatBitrates[j] != Format.NO_VALUE) { int distance = Math.abs(formatBitrates[j] - formatBitrate); if (distance < closestDistance) { + closestDistance = distance; closestFormat = j; } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java index 2d5f2a7c67..49da9b567a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java @@ -18,12 +18,14 @@ package com.google.android.exoplayer2.trackselection; import static com.google.common.truth.Truth.assertThat; import android.net.Uri; -import android.support.annotation.NonNull; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; +import com.google.android.exoplayer2.testutil.FakeMediaChunk; import com.google.android.exoplayer2.upstream.DataSpec; +import java.util.Arrays; +import java.util.Collections; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -96,11 +98,12 @@ public class TrackSelectionUtilTest { getAverageBitrate_chunksExceedingMaxDuration_returnsAverageChunkBitrateUpToMaxDuration() { long[] chunkTimeBoundariesSec = {0, 5, 15, 45, 50}; long[] chunkLengths = {10, 20, 30, 100}; - FakeIterator iterator = new FakeIterator(chunkTimeBoundariesSec, chunkLengths); - assertThat(TrackSelectionUtil.getAverageBitrate(iterator, 30 * C.MICROS_PER_SECOND)) - .isEqualTo(12); + long maxDurationUs = 30 * C.MICROS_PER_SECOND; + int averageBitrate = TrackSelectionUtil.getAverageBitrate(iterator, maxDurationUs); + + assertThat(averageBitrate).isEqualTo(12); } @Test @@ -115,26 +118,26 @@ public class TrackSelectionUtilTest { } @Test - public void getAverageBitrates_noIterator_returnsEmptyArray() { + public void getBitratesUsingFutureInfo_noIterator_returnsEmptyArray() { assertThat( - TrackSelectionUtil.getAverageBitrates( + TrackSelectionUtil.getBitratesUsingFutureInfo( new MediaChunkIterator[0], new Format[0], MAX_DURATION_US)) .hasLength(0); } @Test - public void getAverageBitrates_emptyIterator_returnsNoValue() { - int[] averageBitrates = - TrackSelectionUtil.getAverageBitrates( + public void getBitratesUsingFutureInfo_emptyIterator_returnsNoValue() { + int[] bitrates = + TrackSelectionUtil.getBitratesUsingFutureInfo( new MediaChunkIterator[] {MediaChunkIterator.EMPTY}, new Format[] {createFormatWithBitrate(10)}, MAX_DURATION_US); - assertThat(averageBitrates).asList().containsExactly(Format.NO_VALUE); + assertThat(bitrates).asList().containsExactly(Format.NO_VALUE); } @Test - public void getAverageBitrates_twoTracks_returnsAverageChunkBitrates() { + public void getBitratesUsingFutureInfo_twoTracksZeroMaxDuration_returnsNoValue() { FakeIterator iterator1 = new FakeIterator( /* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10}); @@ -143,35 +146,62 @@ public class TrackSelectionUtilTest { /* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30}, /* chunkLengths= */ new long[] {10, 20, 30}); - int[] averageBitrates = - TrackSelectionUtil.getAverageBitrates( + int[] bitrates = + TrackSelectionUtil.getBitratesUsingFutureInfo( + new MediaChunkIterator[] {iterator1, iterator2}, + new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)}, + /* maxDurationUs= */ 0); + + assertThat(bitrates).asList().containsExactly(Format.NO_VALUE, Format.NO_VALUE); + } + + @Test + public void getBitratesUsingFutureInfo_twoTracks_returnsBitrates() { + FakeIterator iterator1 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10}); + FakeIterator iterator2 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30}, + /* chunkLengths= */ new long[] {10, 20, 30}); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingFutureInfo( new MediaChunkIterator[] {iterator1, iterator2}, new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)}, MAX_DURATION_US); - assertThat(averageBitrates).asList().containsExactly(8, 16).inOrder(); + assertThat(bitrates).asList().containsExactly(8, 16).inOrder(); } @Test - public void getAverageBitrates_oneEmptyIteratorOneWithChunks_returnsEstimationForEmpty() { + public void getBitratesUsingFutureInfo_emptyIterator_returnsEstimationUsingClosest() { FakeIterator iterator1 = new FakeIterator( /* chunkTimeBoundariesSec= */ new long[] {0, 5}, /* chunkLengths= */ new long[] {10}); Format format1 = createFormatWithBitrate(10); MediaChunkIterator iterator2 = MediaChunkIterator.EMPTY; Format format2 = createFormatWithBitrate(20); + FakeIterator iterator3 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 5}, /* chunkLengths= */ new long[] {50}); + Format format3 = createFormatWithBitrate(25); + FakeIterator iterator4 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 5}, /* chunkLengths= */ new long[] {20}); + Format format4 = createFormatWithBitrate(30); - int[] averageBitrates = - TrackSelectionUtil.getAverageBitrates( - new MediaChunkIterator[] {iterator1, iterator2}, - new Format[] {format1, format2}, + int[] bitrates = + TrackSelectionUtil.getBitratesUsingFutureInfo( + new MediaChunkIterator[] {iterator1, iterator2, iterator3, iterator4}, + new Format[] {format1, format2, format3, format4}, MAX_DURATION_US); - assertThat(averageBitrates).asList().containsExactly(16, 32).inOrder(); + assertThat(bitrates).asList().containsExactly(16, 64, 80, 32).inOrder(); } @Test - public void getAverageBitrates_formatWithoutBitrate_returnsNoValueForEmpty() { + public void getBitratesUsingFutureInfo_formatWithoutBitrate_returnsNoValueForEmpty() { FakeIterator iterator1 = new FakeIterator( /* chunkTimeBoundariesSec= */ new long[] {0, 5}, /* chunkLengths= */ new long[] {10}); @@ -179,16 +209,212 @@ public class TrackSelectionUtilTest { MediaChunkIterator iterator2 = MediaChunkIterator.EMPTY; Format format2 = createFormatWithBitrate(Format.NO_VALUE); - int[] averageBitrates = - TrackSelectionUtil.getAverageBitrates( + int[] bitrates = + TrackSelectionUtil.getBitratesUsingFutureInfo( new MediaChunkIterator[] {iterator1, iterator2}, new Format[] {format1, format2}, MAX_DURATION_US); - assertThat(averageBitrates).asList().containsExactly(16, Format.NO_VALUE).inOrder(); + assertThat(bitrates).asList().containsExactly(16, Format.NO_VALUE).inOrder(); + } + + @Test + public void getBitratesUsingPastInfo_noFormat_returnsEmptyArray() { + FakeMediaChunk chunk = + createChunk( + createFormatWithBitrate(10), + /* length= */ 10, + /* startTimeSec= */ 0, + /* endTimeSec= */ 10); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Collections.singletonList(chunk), new Format[0], MAX_DURATION_US); + + assertThat(bitrates).hasLength(0); + } + + @Test + public void getBitratesUsingPastInfo_emptyQueue_returnsNoValue() { + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Collections.emptyList(), new Format[] {createFormatWithBitrate(10)}, MAX_DURATION_US); + + assertThat(bitrates).asList().containsExactly(Format.NO_VALUE); + } + + @Test + public void getBitratesUsingPastInfo_oneChunkFormatNoBitrate_returnsNoValue() { + Format format = createFormatWithBitrate(Format.NO_VALUE); + FakeMediaChunk chunk = + createChunk(format, /* length= */ 10, /* startTimeSec= */ 0, /* endTimeSec= */ 10); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Collections.singletonList(chunk), new Format[] {format}, MAX_DURATION_US); + + assertThat(bitrates).asList().containsExactly(Format.NO_VALUE); + } + + @Test + public void getBitratesUsingPastInfo_oneChunkNoLength_returnsNoValue() { + Format format = createFormatWithBitrate(10); + FakeMediaChunk chunk = + createChunk( + format, /* length= */ C.LENGTH_UNSET, /* startTimeSec= */ 0, /* endTimeSec= */ 10); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Collections.singletonList(chunk), new Format[] {format}, MAX_DURATION_US); + + assertThat(bitrates).asList().containsExactly(Format.NO_VALUE); + } + + @Test + public void getBitratesUsingPastInfo_oneChunkWithSameFormat_returnsBitrates() { + Format format = createFormatWithBitrate(10); + FakeMediaChunk chunk = + createChunk(format, /* length= */ 10, /* startTimeSec= */ 0, /* endTimeSec= */ 10); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Collections.singletonList(chunk), new Format[] {format}, MAX_DURATION_US); + + assertThat(bitrates).asList().containsExactly(8).inOrder(); + } + + @Test + public void getBitratesUsingPastInfo_zeroMaxDuration_returnsNoValue() { + Format format = createFormatWithBitrate(10); + FakeMediaChunk chunk = + createChunk(format, /* length= */ 10, /* startTimeSec= */ 0, /* endTimeSec= */ 10); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Collections.singletonList(chunk), new Format[] {format}, /* maxDurationUs= */ 0); + + assertThat(bitrates).asList().containsExactly(Format.NO_VALUE).inOrder(); + } + + @Test + public void getBitratesUsingPastInfo_multipleChunkWithSameFormat_returnsAverageBitrate() { + Format format = createFormatWithBitrate(10); + FakeMediaChunk chunk = + createChunk(format, /* length= */ 10, /* startTimeSec= */ 0, /* endTimeSec= */ 10); + FakeMediaChunk chunk2 = + createChunk(format, /* length= */ 20, /* startTimeSec= */ 10, /* endTimeSec= */ 20); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Arrays.asList(chunk, chunk2), new Format[] {format}, MAX_DURATION_US); + + assertThat(bitrates).asList().containsExactly(12).inOrder(); + } + + @Test + public void getBitratesUsingPastInfo_oneChunkWithDifferentFormat_returnsEstimationBitrate() { + FakeMediaChunk chunk = + createChunk( + createFormatWithBitrate(10), + /* length= */ 10, + /* startTimeSec= */ 0, + /* endTimeSec= */ 10); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Collections.singletonList(chunk), + new Format[] {createFormatWithBitrate(20)}, + MAX_DURATION_US); + + assertThat(bitrates).asList().containsExactly(16).inOrder(); + } + + @Test + public void getBitratesUsingPastInfo_trackFormatNoBitrate_returnsNoValue() { + FakeMediaChunk chunk = + createChunk( + createFormatWithBitrate(10), + /* length= */ 10, + /* startTimeSec= */ 0, + /* endTimeSec= */ 10); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Collections.singletonList(chunk), + new Format[] {createFormatWithBitrate(Format.NO_VALUE)}, + MAX_DURATION_US); + + assertThat(bitrates).asList().containsExactly(Format.NO_VALUE); + } + + @Test + public void getBitratesUsingPastInfo_multipleTracks_returnsBitrates() { + FakeMediaChunk chunk = + createChunk( + createFormatWithBitrate(10), + /* length= */ 10, + /* startTimeSec= */ 0, + /* endTimeSec= */ 10); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Collections.singletonList(chunk), + new Format[] {createFormatWithBitrate(20), createFormatWithBitrate(30)}, + MAX_DURATION_US); + + assertThat(bitrates).asList().containsExactly(16, 24).inOrder(); + } + + @Test + public void + getBitratesUsingPastInfo_multipleChunkExceedingMaxDuration_returnsAverageUntilMaxDuration() { + Format format = createFormatWithBitrate(10); + FakeMediaChunk chunk = + createChunk(format, /* length= */ 10, /* startTimeSec= */ 0, /* endTimeSec= */ 20); + FakeMediaChunk chunk2 = + createChunk(format, /* length= */ 40, /* startTimeSec= */ 20, /* endTimeSec= */ 40); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Arrays.asList(chunk, chunk2), + new Format[] {format}, + /* maxDurationUs= */ 30 * C.MICROS_PER_SECOND); + + assertThat(bitrates).asList().containsExactly(12).inOrder(); + } + + @Test + public void + getBitratesUsingPastInfo_chunksWithDifferentFormats_returnsChunkAverageBitrateForLastFormat() { + FakeMediaChunk chunk = + createChunk( + createFormatWithBitrate(10), + /* length= */ 10, + /* startTimeSec= */ 0, + /* endTimeSec= */ 10); + FakeMediaChunk chunk2 = + createChunk( + createFormatWithBitrate(20), + /* length= */ 40, + /* startTimeSec= */ 10, + /* endTimeSec= */ 20); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastInfo( + Arrays.asList(chunk, chunk2), + new Format[] {createFormatWithBitrate(10)}, + MAX_DURATION_US); + + assertThat(bitrates).asList().containsExactly(16).inOrder(); + } + + private static FakeMediaChunk createChunk( + Format format, int length, int startTimeSec, int endTimeSec) { + DataSpec dataSpec = new DataSpec(Uri.EMPTY, 0, length, null, 0); + return new FakeMediaChunk( + dataSpec, format, startTimeSec * C.MICROS_PER_SECOND, endTimeSec * C.MICROS_PER_SECOND); } - @NonNull private static Format createFormatWithBitrate(int bitrate) { return Format.createSampleFormat(null, null, null, bitrate, null); } diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java index 8740e3e57f..6669504c07 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaChunk.java @@ -30,9 +30,13 @@ public final class FakeMediaChunk extends MediaChunk { private static final DataSource DATA_SOURCE = new DefaultHttpDataSource("TEST_AGENT", null); public FakeMediaChunk(Format trackFormat, long startTimeUs, long endTimeUs) { + this(new DataSpec(Uri.EMPTY), trackFormat, startTimeUs, endTimeUs); + } + + public FakeMediaChunk(DataSpec dataSpec, Format trackFormat, long startTimeUs, long endTimeUs) { super( DATA_SOURCE, - new DataSpec(Uri.EMPTY), + dataSpec, trackFormat, C.SELECTION_REASON_ADAPTIVE, /* trackSelectionData= */ null, From 8844694dfd0a033472b98c00e8cbaf023f69d596 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 5 Nov 2018 10:11:27 -0800 Subject: [PATCH 164/832] Automated g4 rollback of changelist 219585084. *** Reason for rollback *** Rolling forward again as [] should fix issue that prompted the rollback *** Original change description *** Automated g4 rollback of changelist 219130576. *** Original change description *** Re-enable codec re-use Issue: #2826 *** *** ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220124362 --- .../mediacodec/MediaCodecRenderer.java | 16 +++++++++++++--- .../video/MediaCodecVideoRenderer.java | 4 ++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 6ba2557ede..2d936afc2a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -552,6 +552,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Override protected void onDisabled() { inputFormat = null; + if (drmSession != null || pendingDrmSession != null) { + // TODO: Do something better with this case. + onReset(); + } else { + flushOrReleaseCodec(); + } + } + + @Override + protected void onReset() { try { releaseCodec(); } finally { @@ -575,14 +585,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { protected void releaseCodec() { availableCodecInfos = null; if (codec != null) { + codecInfo = null; + codecFormat = null; resetInputBuffer(); resetOutputBuffer(); resetCodecBuffers(); - codecHotswapDeadlineMs = C.TIME_UNSET; waitingForKeys = false; + codecHotswapDeadlineMs = C.TIME_UNSET; decodeOnlyPresentationTimestamps.clear(); - codecInfo = null; - codecFormat = null; decoderCounters.decoderReleaseCount++; try { codec.stop(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 29a75f06e5..2851325c75 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -283,8 +283,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override protected void onEnabled(boolean joining) throws ExoPlaybackException { super.onEnabled(joining); + int oldTunnelingAudioSessionId = tunnelingAudioSessionId; tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET; + if (tunnelingAudioSessionId != oldTunnelingAudioSessionId) { + releaseCodec(); + } eventDispatcher.enabled(decoderCounters); frameReleaseTimeHelper.enable(); } From a8d1f04f92e15b2832ba810bc4fb993312f23073 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 5 Nov 2018 10:54:24 -0800 Subject: [PATCH 165/832] Document error case for generateAudioSessionIdV21 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220132865 --- .../core/src/main/java/com/google/android/exoplayer2/C.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 6c72dd8d0a..fac9818d9e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -978,7 +978,10 @@ public final class C { } /** - * Returns a newly generated {@link android.media.AudioTrack} session identifier. + * Returns a newly generated audio session identifier, or {@link AudioManager#ERROR} if an error + * occurred in which case audio playback may fail. + * + * @see AudioManager#generateAudioSessionId() */ @TargetApi(21) public static int generateAudioSessionIdV21(Context context) { From 032883e1d280f1f4f659370007afdce858c065ae Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 6 Nov 2018 00:35:42 -0800 Subject: [PATCH 166/832] Work around non-empty EoS buffers with timestamp 0 Issue: #5045 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220237752 --- RELEASENOTES.md | 3 ++ .../audio/MediaCodecAudioRenderer.java | 30 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 569e2e555e..9f83a40e0f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,9 @@ here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). * Add options for controlling audio track selections to `DefaultTrackSelector` ([#3314](https://github.com/google/ExoPlayer/issues/3314)). +* Work around an issue where a non-empty end-of-stream audio buffer would be + output with timestamp zero, causing the player position to jump backwards + ([#5045](https://github.com/google/ExoPlayer/issues/5045)). * Do not retry failed loads whose error is `FileNotFoundException`. ### 2.9.1 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index dbc25c8d42..d7206cd2c5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -23,6 +23,7 @@ import android.media.MediaCrypto; import android.media.MediaFormat; import android.media.audiofx.Virtualizer; import android.os.Handler; +import android.support.annotation.CallSuper; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -85,6 +86,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media private int codecMaxInputSize; private boolean passthroughEnabled; private boolean codecNeedsDiscardChannelsWorkaround; + private boolean codecNeedsEosBufferTimestampWorkaround; private android.media.MediaFormat passthroughMediaFormat; private @C.Encoding int pcmEncoding; private int channelCount; @@ -344,6 +346,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media float codecOperatingRate) { codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats()); codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); + codecNeedsEosBufferTimestampWorkaround = codecNeedsEosBufferTimestampWorkaround(codecInfo.name); passthroughEnabled = codecInfo.passthrough; String codecMimeType = passthroughEnabled ? MimeTypes.AUDIO_RAW : codecInfo.mimeType; MediaFormat mediaFormat = @@ -599,9 +602,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media lastInputTimeUs = Math.max(buffer.timeUs, lastInputTimeUs); } + @CallSuper @Override protected void onProcessedOutputBuffer(long presentationTimeUs) { - super.onProcessedOutputBuffer(presentationTimeUs); while (pendingStreamChangeCount != 0 && presentationTimeUs >= pendingStreamChangeTimesUs[0]) { audioSink.handleDiscontinuity(); pendingStreamChangeCount--; @@ -626,6 +629,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media boolean shouldSkip, Format format) throws ExoPlaybackException { + if (codecNeedsEosBufferTimestampWorkaround + && bufferPresentationTimeUs == 0 + && (bufferFlags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0 + && lastInputTimeUs != C.TIME_UNSET) { + bufferPresentationTimeUs = lastInputTimeUs; + } + if (passthroughEnabled && (bufferFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // Discard output buffers from the passthrough (raw) decoder containing codec specific data. codec.releaseOutputBuffer(bufferIndex, false); @@ -802,6 +812,24 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media || Util.DEVICE.startsWith("heroqlte")); } + /** + * Returns whether the decoder may output a non-empty buffer with timestamp 0 as the end of stream + * buffer. + * + *

    See GitHub issue #5045. + */ + private static boolean codecNeedsEosBufferTimestampWorkaround(String codecName) { + return Util.SDK_INT < 21 + && "OMX.SEC.mp3.dec".equals(codecName) + && "samsung".equals(Util.MANUFACTURER) + && (Util.DEVICE.startsWith("baffin") + || Util.DEVICE.startsWith("grand") + || Util.DEVICE.startsWith("fortuna") + || Util.DEVICE.startsWith("gprimelte") + || Util.DEVICE.startsWith("j2y18lte") + || Util.DEVICE.startsWith("ms01")); + } + private final class AudioSinkListener implements AudioSink.Listener { @Override From 254a586e27fa800f2fd53e6b30bd4f84525bcfc1 Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Tue, 6 Nov 2018 21:25:08 +0200 Subject: [PATCH 167/832] #1583 - Parsing base64 encoded image data from metadata --- demos/main/src/main/assets/media.exolist.json | 5 ++ .../exoplayer2/text/ttml/TtmlDecoder.java | 34 ++++++++-- .../exoplayer2/text/ttml/TtmlNode.java | 63 ++++++++++++++++--- .../exoplayer2/text/ttml/TtmlSubtitle.java | 7 ++- 4 files changed, 95 insertions(+), 14 deletions(-) diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index c2acf3990b..74b57625d1 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -2,6 +2,11 @@ { "name": "YouTube DASH", "samples": [ + { + "name": "DVB Image sub", + "uri": "https://livesim.dashif.org/dash/vod/testpic_2s/img_subs.mpd", + "extension": "mpd" + }, { "name": "Google Glass (MP4,H264)", "uri": "https://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0", diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java index 2e868077a5..8be5af5a3f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java @@ -68,6 +68,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { private static final String ATTR_END = "end"; private static final String ATTR_STYLE = "style"; private static final String ATTR_REGION = "region"; + private static final String ATTR_IMAGE = "backgroundImage"; + private static final Pattern CLOCK_TIME = Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])" @@ -105,6 +107,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { XmlPullParser xmlParser = xmlParserFactory.newPullParser(); Map globalStyles = new HashMap<>(); Map regionMap = new HashMap<>(); + Map imageMap = new HashMap<>(); regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(null)); ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length); xmlParser.setInput(inputStream, null); @@ -127,7 +130,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName()); unsupportedNodeDepth++; } else if (TtmlNode.TAG_HEAD.equals(name)) { - parseHeader(xmlParser, globalStyles, regionMap, cellResolution); + parseHeader(xmlParser, globalStyles, regionMap, cellResolution, imageMap); } else { try { TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate); @@ -145,7 +148,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { parent.addChild(TtmlNode.buildTextNode(xmlParser.getText())); } else if (eventType == XmlPullParser.END_TAG) { if (xmlParser.getName().equals(TtmlNode.TAG_TT)) { - ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap); + ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap, imageMap); } nodeStack.pop(); } @@ -230,7 +233,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { XmlPullParser xmlParser, Map globalStyles, Map globalRegions, - CellResolution cellResolution) + CellResolution cellResolution, + Map imageMap) throws IOException, XmlPullParserException { do { xmlParser.next(); @@ -250,11 +254,29 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { if (ttmlRegion != null) { globalRegions.put(ttmlRegion.id, ttmlRegion); } + } else if(XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_METADATA)){ + parseMetaData(xmlParser, imageMap); } + } while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD)); return globalStyles; } + public void parseMetaData(XmlPullParser xmlParser, Map imageMap) throws IOException, XmlPullParserException { + do { + xmlParser.next(); + if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_SMPTE_IMAGE)) { + for (int i = 0; i < xmlParser.getAttributeCount(); i++) { + String id = XmlPullParserUtil.getAttributeValue(xmlParser, "id"); + if(id != null){ + String base64 = xmlParser.nextText(); + imageMap.put(id, base64); + } + } + } + } while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_METADATA)); + } + /** * Parses a region declaration. * @@ -457,6 +479,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { long startTime = C.TIME_UNSET; long endTime = C.TIME_UNSET; String regionId = TtmlNode.ANONYMOUS_REGION_ID; + String imageId = ""; String[] styleIds = null; int attributeCount = parser.getAttributeCount(); TtmlStyle style = parseStyleAttributes(parser, null); @@ -487,6 +510,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { regionId = value; } break; + case ATTR_IMAGE: + imageId = value.substring(1); + break; default: // Do nothing. break; @@ -509,7 +535,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { endTime = parent.endTimeUs; } } - return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId); + return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId, imageId); } private static boolean isSupportedTag(String tag) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java index c8b9a59de4..ccea61c498 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java @@ -15,10 +15,16 @@ */ package com.google.android.exoplayer2.text.ttml; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.text.SpannableStringBuilder; +import android.util.Base64; +import android.util.Pair; + import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.util.Assertions; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -44,9 +50,9 @@ import java.util.TreeSet; public static final String TAG_LAYOUT = "layout"; public static final String TAG_REGION = "region"; public static final String TAG_METADATA = "metadata"; - public static final String TAG_SMPTE_IMAGE = "smpte:image"; - public static final String TAG_SMPTE_DATA = "smpte:data"; - public static final String TAG_SMPTE_INFORMATION = "smpte:information"; + public static final String TAG_SMPTE_IMAGE = "image"; + public static final String TAG_SMPTE_DATA = "data"; + public static final String TAG_SMPTE_INFORMATION = "information"; public static final String ANONYMOUS_REGION_ID = ""; public static final String ATTR_ID = "id"; @@ -82,6 +88,7 @@ import java.util.TreeSet; public final long endTimeUs; public final TtmlStyle style; public final String regionId; + public final String imageId; private final String[] styleIds; private final HashMap nodeStartsByRegion; @@ -91,18 +98,19 @@ import java.util.TreeSet; public static TtmlNode buildTextNode(String text) { return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), C.TIME_UNSET, - C.TIME_UNSET, null, null, ANONYMOUS_REGION_ID); + C.TIME_UNSET, null, null, ANONYMOUS_REGION_ID, null); } public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs, - TtmlStyle style, String[] styleIds, String regionId) { - return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds, regionId); + TtmlStyle style, String[] styleIds, String regionId, String imageId) { + return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds, regionId, imageId); } private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs, - TtmlStyle style, String[] styleIds, String regionId) { + TtmlStyle style, String[] styleIds, String regionId, String imageId) { this.tag = tag; this.text = text; + this.imageId = imageId; this.style = style; this.styleIds = styleIds; this.isTextNode = text != null; @@ -172,11 +180,37 @@ import java.util.TreeSet; } public List getCues(long timeUs, Map globalStyles, - Map regionMap) { + Map regionMap, Map imageMap) { + TreeMap regionOutputs = new TreeMap<>(); + List> regionImageList = new ArrayList<>(); + traverseForText(timeUs, false, regionId, regionOutputs); traverseForStyle(timeUs, globalStyles, regionOutputs); + traverseForImage(timeUs, regionId, regionImageList); + List cues = new ArrayList<>(); + + // Create text based cues + for (Pair regionImagePair : regionImageList) { + String base64 = imageMap.get(regionImagePair.second); + byte[] decodedString = Base64.decode(base64, Base64.DEFAULT); + Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length); + TtmlRegion region = regionMap.get(regionImagePair.first); + + cues.add( + new Cue(decodedByte, + region.position, + Cue.TYPE_UNSET, + region.line, + region.lineAnchor, + region.width, + Cue.DIMEN_UNSET + ) + ); + } + + // Create image based cues for (Entry entry : regionOutputs.entrySet()) { TtmlRegion region = regionMap.get(entry.getKey()); cues.add( @@ -195,6 +229,19 @@ import java.util.TreeSet; return cues; } + private void traverseForImage(long timeUs, String inheritedRegion, List> regionImageList) { + // TODO isActive needed? + + String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId; + if (TAG_DIV.equals(tag) && imageId != null) { + regionImageList.add(new Pair<>(resolvedRegionId, imageId)); + } + + for (int i = 0; i < getChildCount(); ++i) { + getChild(i).traverseForImage(timeUs, resolvedRegionId, regionImageList); + } + } + private void traverseForText( long timeUs, boolean descendsPNode, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java index 50916aa841..c92fa876f2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java @@ -32,11 +32,14 @@ import java.util.Map; private final long[] eventTimesUs; private final Map globalStyles; private final Map regionMap; + private final Map imageMap; + public TtmlSubtitle(TtmlNode root, Map globalStyles, - Map regionMap) { + Map regionMap, Map imageMap) { this.root = root; this.regionMap = regionMap; + this.imageMap = imageMap; this.globalStyles = globalStyles != null ? Collections.unmodifiableMap(globalStyles) : Collections.emptyMap(); this.eventTimesUs = root.getEventTimesUs(); @@ -65,7 +68,7 @@ import java.util.Map; @Override public List getCues(long timeUs) { - return root.getCues(timeUs, globalStyles, regionMap); + return root.getCues(timeUs, globalStyles, regionMap, imageMap); } /* @VisibleForTesting */ From eafca5402a9595d4ea160df732f72c02f7b34722 Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Tue, 6 Nov 2018 21:54:34 +0200 Subject: [PATCH 168/832] #1583 - Correcting comment in TtmlNode#getCues() --- .../com/google/android/exoplayer2/text/ttml/TtmlNode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java index ccea61c498..6b2a524e49 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java @@ -191,7 +191,7 @@ import java.util.TreeSet; List cues = new ArrayList<>(); - // Create text based cues + // Create image based cues for (Pair regionImagePair : regionImageList) { String base64 = imageMap.get(regionImagePair.second); byte[] decodedString = Base64.decode(base64, Base64.DEFAULT); @@ -210,7 +210,7 @@ import java.util.TreeSet; ); } - // Create image based cues + // Create text based cues for (Entry entry : regionOutputs.entrySet()) { TtmlRegion region = regionMap.get(entry.getKey()); cues.add( From bc5e12013b793b9772b31cfdfbfa24e0d0e7d9fa Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 6 Nov 2018 06:03:59 -0800 Subject: [PATCH 169/832] Remove executable bit from some resources Copybara propagates this bit on the files, so removing it avoids some unnecessary changes in the first migrated commit. Also losslessly optimize two PNG files. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220268951 --- .../main/res/drawable-xxhdpi/ic_download.png | Bin 303 -> 261 bytes .../main/res/drawable-xxxhdpi/ic_download.png | Bin 304 -> 263 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/demos/main/src/main/res/drawable-xxhdpi/ic_download.png b/demos/main/src/main/res/drawable-xxhdpi/ic_download.png index f02715177ad64ccd13ae3b10298f099f692ae725..4e04a30198d72eedd3a749158ce4fc091a2b0f8b 100644 GIT binary patch delta 233 zcmZ3_)XFqLrCun&C&ZP3f#E+YunSsi1r%i~3GxeOaA^3ia3H(HS_UXG$J50zq=GR? z;#h)650j#*$^wrqW~s?42__1++_v^+NnbTvl^Zg9vXBUia<*}6=c)%6j-~Kql=t~E zurNq)IH?J4YJ9hUHgm^y?VWZtFOm-I47ZRz9yitSi9oXwo1;&c1dE|&lE9HA9gYH% zmDmD#x+Gi-HIf95F6eL+0jj_ds63Wdb#doS_w@hf#^!%2o==z_Tf7eFPzFy|KbLh* G2~7ZayIVp4 delta 276 zcmZo=TF*2=rCv0^C&ZP3f#E*}@Hn5#0+e7c3GxeOaA;uouW%rGQBx#P=#Zz2V@L(# z+bbJ$j|2!XUtE1qHe!*qg0%pPc!BAT|59@_0v05uN&JX-#xMMKU)tu@?;G!ao7uki zlfm5WvWMGvrOn>N->BaGtvLOD{q3##33ji`5(0zWJ9ZxLaJ6+iW9z2#$}s7jVNzhf zU~#`-cU8}f>Yj)z$w%%aA6X=){7g<+?5l)f@f-cRPIU3B5=Pf0Hmx{1W9QKst}@wY zWU_T0MI_&|e#{#^DjWv+R;i913rU`GJA70J0Yf7HdZ(Wfyo{Xm^Fh6({ z_nPtA>pxZT^|vw>_nO_(IRD_a*ip%vc=Os1g?FzWTps**Qn22_*r1(DN<1H4T7bql vcxeelge1npOP|+Xm)v7s>gTe~DWM4f1o3xI literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^6F``W8A#63;L-+CJOMr-u0Z-f3|JhnUIFB@lmz(& zGyG?0c(D0mIFNtd)5S5Qg7NL8g}Kd%Jgyff^!->F`o2}}_}t%%${nijyLSKM`^zAAsKnfb Sjb}E{YYd*QelF{r5}E)O=6ezV From abb7d8a2d501deb6c8537ac64b78301b793de85a Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 6 Nov 2018 07:26:27 -0800 Subject: [PATCH 170/832] Include UI module tests in open source push ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220277660 --- library/ui/src/test/AndroidManifest.xml | 18 +++ .../ui/spherical/CanvasRendererTest.java | 70 ++++++++ .../ui/spherical/TouchTrackerTest.java | 150 ++++++++++++++++++ .../src/test/resources/robolectric.properties | 1 + 4 files changed, 239 insertions(+) create mode 100644 library/ui/src/test/AndroidManifest.xml create mode 100644 library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/CanvasRendererTest.java create mode 100644 library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java create mode 100644 library/ui/src/test/resources/robolectric.properties diff --git a/library/ui/src/test/AndroidManifest.xml b/library/ui/src/test/AndroidManifest.xml new file mode 100644 index 0000000000..1a749dc82c --- /dev/null +++ b/library/ui/src/test/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + diff --git a/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/CanvasRendererTest.java b/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/CanvasRendererTest.java new file mode 100644 index 0000000000..d0503d4a93 --- /dev/null +++ b/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/CanvasRendererTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui.spherical; + +import static com.google.common.truth.Truth.assertThat; + +import android.graphics.PointF; +import android.support.annotation.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link CanvasRenderer}. */ +@RunWith(RobolectricTestRunner.class) +public class CanvasRendererTest { + + private static final float JUST_BELOW_45_DEGREES = (float) (Math.PI / 4 - 1.0E-08); + private static final float JUST_ABOVE_45_DEGREES = (float) (Math.PI / 4 + 1.0E-08); + private static final float TOLERANCE = .00001f; + + @Test + public void testClicksOnCanvas() { + assertClick(translateClick(JUST_BELOW_45_DEGREES, JUST_BELOW_45_DEGREES), 0, 0); + assertClick(translateClick(JUST_BELOW_45_DEGREES, -JUST_BELOW_45_DEGREES), 0, 100); + assertClick(translateClick(0, 0), 50, 50); + assertClick(translateClick(-JUST_BELOW_45_DEGREES, JUST_BELOW_45_DEGREES), 100, 0); + assertClick(translateClick(-JUST_BELOW_45_DEGREES, -JUST_BELOW_45_DEGREES), 100, 100); + } + + @Test + public void testClicksNotOnCanvas() { + assertThat(translateClick(JUST_ABOVE_45_DEGREES, JUST_ABOVE_45_DEGREES)).isNull(); + assertThat(translateClick(JUST_ABOVE_45_DEGREES, -JUST_ABOVE_45_DEGREES)).isNull(); + assertThat(translateClick(-JUST_ABOVE_45_DEGREES, JUST_ABOVE_45_DEGREES)).isNull(); + assertThat(translateClick(-JUST_ABOVE_45_DEGREES, -JUST_ABOVE_45_DEGREES)).isNull(); + assertThat(translateClick((float) (Math.PI / 2), 0)).isNull(); + assertThat(translateClick(0, (float) Math.PI)).isNull(); + } + + private static PointF translateClick(float yaw, float pitch) { + return CanvasRenderer.internalTranslateClick( + yaw, + pitch, + /* xUnit= */ -1, + /* yUnit= */ -1, + /* widthUnit= */ 2, + /* heightUnit= */ 2, + /* widthPixel= */ 100, + /* heightPixel= */ 100); + } + + private static void assertClick(@Nullable PointF actual, float expectedX, float expectedY) { + assertThat(actual).isNotNull(); + assertThat(actual.x).isWithin(TOLERANCE).of(expectedX); + assertThat(actual.y).isWithin(TOLERANCE).of(expectedY); + } +} diff --git a/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java b/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java new file mode 100644 index 0000000000..337fb4c593 --- /dev/null +++ b/library/ui/src/test/java/com/google/android/exoplayer2/ui/spherical/TouchTrackerTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui.spherical; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.view.MotionEvent; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +/** Tests for {@link TouchTracker}. */ +@RunWith(RobolectricTestRunner.class) +public class TouchTrackerTest { + private static final float EPSILON = 0.00001f; + private static final int SWIPE_PX = 100; + private static final float PX_PER_DEGREES = 25; + + private TouchTracker tracker; + private float yaw; + private float pitch; + private float[] dummyMatrix; + + private static void swipe(TouchTracker tracker, float x0, float y0, float x1, float y1) { + tracker.onTouch(null, MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, x0, y0, 0)); + tracker.onTouch(null, MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, x1, y1, 0)); + tracker.onTouch(null, MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, x1, y1, 0)); + } + + @Before + public void setUp() { + Context context = RuntimeEnvironment.application; + tracker = + new TouchTracker( + context, + scrollOffsetDegrees -> { + pitch = scrollOffsetDegrees.y; + yaw = scrollOffsetDegrees.x; + }, + PX_PER_DEGREES); + dummyMatrix = new float[16]; + tracker.onOrientationChange(dummyMatrix, 0); + } + + @Test + public void testTap() { + // Tap is a noop. + swipe(tracker, 0, 0, 0, 0); + assertThat(yaw).isWithin(EPSILON).of(0); + assertThat(pitch).isWithin(EPSILON).of(0); + } + + @Test + public void testBasicYaw() { + swipe(tracker, 0, 0, SWIPE_PX, 0); + assertThat(yaw).isWithin(EPSILON).of(-SWIPE_PX / PX_PER_DEGREES); + assertThat(pitch).isWithin(EPSILON).of(0); + } + + @Test + public void testBigYaw() { + swipe(tracker, 0, 0, -10 * SWIPE_PX, 0); + assertThat(yaw).isEqualTo(10 * SWIPE_PX / PX_PER_DEGREES); + assertThat(pitch).isWithin(EPSILON).of(0); + } + + @Test + public void testYawUnaffectedByPitch() { + swipe(tracker, 0, 0, 0, SWIPE_PX); + assertThat(yaw).isWithin(EPSILON).of(0); + + swipe(tracker, 0, 0, SWIPE_PX, SWIPE_PX); + assertThat(yaw).isWithin(EPSILON).of(-SWIPE_PX / PX_PER_DEGREES); + } + + @Test + public void testBasicPitch() { + swipe(tracker, 0, 0, 0, SWIPE_PX); + assertThat(yaw).isWithin(EPSILON).of(0); + assertThat(pitch).isWithin(EPSILON).of(SWIPE_PX / PX_PER_DEGREES); + } + + @Test + public void testPitchClipped() { + // Big reverse pitch should be clipped. + swipe(tracker, 0, 0, 0, -20 * SWIPE_PX); + assertThat(yaw).isWithin(EPSILON).of(0); + assertThat(pitch).isEqualTo(-TouchTracker.MAX_PITCH_DEGREES); + + // Big forward pitch should be clipped. + swipe(tracker, 0, 0, 0, 50 * SWIPE_PX); + assertThat(yaw).isWithin(EPSILON).of(0); + assertThat(pitch).isEqualTo(TouchTracker.MAX_PITCH_DEGREES); + } + + @Test + public void testWithRoll90() { + tracker.onOrientationChange(dummyMatrix, (float) Math.toRadians(90)); + + // Y-axis should now control yaw. + swipe(tracker, 0, 0, 0, 2 * SWIPE_PX); + assertThat(yaw).isWithin(EPSILON).of(-2 * SWIPE_PX / PX_PER_DEGREES); + + // X-axis should now control reverse pitch. + swipe(tracker, 0, 0, -3 * SWIPE_PX, 0); + assertThat(pitch).isWithin(EPSILON).of(3 * SWIPE_PX / PX_PER_DEGREES); + } + + @Test + public void testWithRoll180() { + tracker.onOrientationChange(dummyMatrix, (float) Math.toRadians(180)); + + // X-axis should now control reverse yaw. + swipe(tracker, 0, 0, -2 * SWIPE_PX, 0); + assertThat(yaw).isWithin(EPSILON).of(-2 * SWIPE_PX / PX_PER_DEGREES); + + // Y-axis should now control reverse pitch. + swipe(tracker, 0, 0, 0, -3 * SWIPE_PX); + assertThat(pitch).isWithin(EPSILON).of(3 * SWIPE_PX / PX_PER_DEGREES); + } + + @Test + public void testWithRoll270() { + tracker.onOrientationChange(dummyMatrix, (float) Math.toRadians(270)); + + // Y-axis should now control reverse yaw. + swipe(tracker, 0, 0, 0, -2 * SWIPE_PX); + assertThat(yaw).isWithin(EPSILON).of(-2 * SWIPE_PX / PX_PER_DEGREES); + + // X-axis should now control pitch. + swipe(tracker, 0, 0, 3 * SWIPE_PX, 0); + assertThat(pitch).isWithin(EPSILON).of(3 * SWIPE_PX / PX_PER_DEGREES); + } +} diff --git a/library/ui/src/test/resources/robolectric.properties b/library/ui/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..2f3210368e --- /dev/null +++ b/library/ui/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +manifest=src/test/AndroidManifest.xml From 0c4d0ee1c11f0649ab55b8cbb5fda61d923b86f5 Mon Sep 17 00:00:00 2001 From: borrelli Date: Tue, 6 Nov 2018 11:18:28 -0800 Subject: [PATCH 171/832] Fix for #5055 - Cannot disable audio focus after enabled. This fixes an issue where disabling audio focus handling while audio focus is held would not release audio focus. A new test was added for this situation. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220316866 --- RELEASENOTES.md | 2 ++ .../exoplayer2/audio/AudioFocusManager.java | 4 --- .../audio/AudioFocusManagerTest.java | 33 ++++++++++++++++++- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9f83a40e0f..02876af90c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### dev-v2 (not yet released) ### +* Fix issue where audio focus handling could not be disabled after enabling + it ([#5055](https://github.com/google/ExoPlayer/issues/5055)). * Support for playing spherical videos on Daydream. * Improve decoder re-use between playbacks. TODO: Write and link a blog post here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java index 39422c2873..09fd1f3f02 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java @@ -141,10 +141,6 @@ public final class AudioFocusManager { */ public @PlayerCommand int setAudioAttributes( @Nullable AudioAttributes audioAttributes, boolean playWhenReady, int playerState) { - if (audioAttributes == null) { - return PLAYER_COMMAND_PLAY_WHEN_READY; - } - if (!Util.areEqual(this.audioAttributes, audioAttributes)) { this.audioAttributes = audioAttributes; focusGain = convertAudioAttributesToFocusGain(audioAttributes); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java index 1cf812559c..b175828d44 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java @@ -58,7 +58,38 @@ public class AudioFocusManagerTest { assertThat( audioFocusManager.setAudioAttributes( /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_IDLE)) + .isEqualTo(PLAYER_COMMAND_WAIT_FOR_CALLBACK); + assertThat( + audioFocusManager.setAudioAttributes( + /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(request).isNull(); + } + + @Test + public void setAudioAttributes_withNullUsage_releasesAudioFocus() { + // Create attributes and request audio focus. + AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build(); + Shadows.shadowOf(audioManager) + .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + assertThat( + audioFocusManager.setAudioAttributes( + media, /* playWhenReady= */ true, Player.STATE_READY)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(request.durationHint).isEqualTo(AudioManager.AUDIOFOCUS_GAIN); + + // Ensure that setting null audio attributes with audio focus releases audio focus. + assertThat( + audioFocusManager.setAudioAttributes( + /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + AudioManager.OnAudioFocusChangeListener lastRequest = + Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener(); + assertThat(lastRequest).isNotNull(); } @Test @@ -296,7 +327,7 @@ public class AudioFocusManagerTest { assertThat( audioFocusManager.setAudioAttributes( /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_READY)) - .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); ShadowAudioManager.AudioFocusRequest request = Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); From d7e23f826b62f167ba27c1c9f8f0b0e83d215621 Mon Sep 17 00:00:00 2001 From: eguven Date: Tue, 6 Nov 2018 11:52:16 -0800 Subject: [PATCH 172/832] Fix package visibility comments ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220323772 --- .../com/google/android/exoplayer2/extractor/mp4/Atom.java | 2 +- .../android/exoplayer2/upstream/cache/CachedContent.java | 6 ++---- .../exoplayer2/upstream/cache/CachedContentIndex.java | 2 +- .../android/exoplayer2/upstream/cache/SimpleCache.java | 2 +- .../android/exoplayer2/upstream/cache/SimpleCacheSpan.java | 6 ++---- .../android/exoplayer2/source/dash/PlayerEmsgHandler.java | 2 +- .../android/exoplayer2/ui/spherical/CanvasRenderer.java | 2 +- 7 files changed, 9 insertions(+), 13 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index 3b85c3d2e3..a20195139f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -23,7 +23,7 @@ import java.util.Arrays; import java.util.List; @SuppressWarnings("ConstantField") -/* package*/ abstract class Atom { +/* package */ abstract class Atom { /** * Size of an atom header, in bytes. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java index 97a7828a22..4d15de5932 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java @@ -23,10 +23,8 @@ import java.io.DataOutputStream; import java.io.IOException; import java.util.TreeSet; -/** - * Defines the cached content for a single stream. - */ -/*package*/ final class CachedContent { +/** Defines the cached content for a single stream. */ +/* package */ final class CachedContent { private static final int VERSION_METADATA_INTRODUCED = 2; private static final int VERSION_MAX = Integer.MAX_VALUE; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java index 3bcfac5053..4220f6c5b4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java @@ -43,7 +43,7 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** Maintains the index of cached content. */ -/*package*/ class CachedContentIndex { +/* package */ class CachedContentIndex { public static final String FILE_NAME = "cached_content_index.exi"; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java index ca2983c891..1fd6dc63bc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java @@ -120,7 +120,7 @@ public final class SimpleCache implements Cache { * @param evictor The evictor to be used. * @param index The CachedContentIndex to be used. */ - /*package*/ SimpleCache(File cacheDir, CacheEvictor evictor, CachedContentIndex index) { + /* package */ SimpleCache(File cacheDir, CacheEvictor evictor, CachedContentIndex index) { if (!lockFolder(cacheDir)) { throw new IllegalStateException("Another SimpleCache instance uses the folder: " + cacheDir); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java index e12d876ce1..82e9f03f4f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java @@ -23,10 +23,8 @@ import java.io.File; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * This class stores span metadata in filename. - */ -/*package*/ final class SimpleCacheSpan extends CacheSpan { +/** This class stores span metadata in filename. */ +/* package */ final class SimpleCacheSpan extends CacheSpan { private static final String SUFFIX = ".v3.exo"; private static final Pattern CACHE_FILE_PATTERN_V1 = Pattern.compile( diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java index e657d126bf..46d70ca714 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java @@ -126,7 +126,7 @@ public final class PlayerEmsgHandler implements Handler.Callback { removePreviouslyExpiredManifestPublishTimeValues(); } - /* package*/ boolean maybeRefreshManifestBeforeLoadingNextChunk(long presentationPositionUs) { + /* package */ boolean maybeRefreshManifestBeforeLoadingNextChunk(long presentationPositionUs) { if (!manifest.dynamic) { return false; } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/CanvasRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/CanvasRenderer.java index 247de5a3d0..5538390d3b 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/CanvasRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/CanvasRenderer.java @@ -271,7 +271,7 @@ public final class CanvasRenderer { } @Nullable - /*package*/ static PointF internalTranslateClick( + /* package */ static PointF internalTranslateClick( float yaw, float pitch, float xUnit, From 7a114cb3746f832c671112aebcba4b1faebce036 Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 7 Nov 2018 02:29:53 -0800 Subject: [PATCH 173/832] Uncomment VisibleForTesting annotations ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220432564 --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 5 ++-- .../extractor/ogg/DefaultOggSeeker.java | 27 +++++++++---------- .../exoplayer2/extractor/ogg/OggPacket.java | 7 ++--- .../extractor/ogg/VorbisReader.java | 15 ++++++----- .../exoplayer2/text/ttml/TtmlSubtitle.java | 5 ++-- .../upstream/cache/CachedContentIndex.java | 3 ++- .../ui/spherical/SphericalSurfaceView.java | 3 ++- .../exoplayer2/ui/spherical/TouchTracker.java | 3 ++- 8 files changed, 37 insertions(+), 31 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 3be28e1b84..40950bceef 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -21,6 +21,7 @@ import android.os.Looper; import android.os.SystemClock; import android.support.annotation.IntDef; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.view.ViewGroup; import com.google.ads.interactivemedia.v3.api.Ad; import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; @@ -206,7 +207,7 @@ public final class ImaAdsLoader return this; } - // @VisibleForTesting + @VisibleForTesting /* package */ Builder setImaFactory(ImaFactory imaFactory) { this.imaFactory = Assertions.checkNotNull(imaFactory); return this; @@ -1346,7 +1347,7 @@ public final class ImaAdsLoader } /** Factory for objects provided by the IMA SDK. */ - // @VisibleForTesting + @VisibleForTesting /* package */ interface ImaFactory { /** @see ImaSdkSettings */ ImaSdkSettings createImaSdkSettings(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java index 93ed00a05b..d251dc1d5d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.ogg; +import android.support.annotation.VisibleForTesting; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.SeekMap; @@ -26,10 +27,8 @@ import java.io.IOException; /** Seeks in an Ogg stream. */ /* package */ final class DefaultOggSeeker implements OggSeeker { - //@VisibleForTesting - public static final int MATCH_RANGE = 72000; - //@VisibleForTesting - public static final int MATCH_BYTE_RANGE = 100000; + @VisibleForTesting public static final int MATCH_RANGE = 72000; + @VisibleForTesting public static final int MATCH_BYTE_RANGE = 100000; private static final int DEFAULT_OFFSET = 30000; private static final int STATE_SEEK_TO_END = 0; @@ -133,7 +132,7 @@ import java.io.IOException; return totalGranules != 0 ? new OggSeekMap() : null; } - //@VisibleForTesting + @VisibleForTesting public void resetSeeking() { start = startPosition; end = endPosition; @@ -150,12 +149,12 @@ import java.io.IOException; * * @param targetGranule the target granule position to seek to. * @param input the {@link ExtractorInput} to read from. - * @return the position to seek the {@link ExtractorInput} to for a next call or - * -(currentGranule + 2) if it's close enough to skip to the target page. + * @return the position to seek the {@link ExtractorInput} to for a next call or -(currentGranule + * + 2) if it's close enough to skip to the target page. * @throws IOException thrown if reading from the input fails. * @throws InterruptedException thrown if interrupted while reading from the input. */ - //@VisibleForTesting + @VisibleForTesting public long getNextSeekPosition(long targetGranule, ExtractorInput input) throws IOException, InterruptedException { if (start == end) { @@ -250,7 +249,7 @@ import java.io.IOException; * @throws InterruptedException If the thread is interrupted. * @throws EOFException If the next page can't be found before the end of the input. */ - // @VisibleForTesting + @VisibleForTesting void skipToNextPage(ExtractorInput input) throws IOException, InterruptedException { if (!skipToNextPage(input, endPosition)) { // Not found until eof. @@ -267,7 +266,7 @@ import java.io.IOException; * @throws IOException thrown if peeking/reading from the input fails. * @throws InterruptedException thrown if interrupted while peeking/reading from the input. */ - // @VisibleForTesting + @VisibleForTesting boolean skipToNextPage(ExtractorInput input, long limit) throws IOException, InterruptedException { limit = Math.min(limit + 3, endPosition); @@ -307,7 +306,7 @@ import java.io.IOException; * @throws IOException If reading from the input fails. * @throws InterruptedException If the thread is interrupted. */ - // @VisibleForTesting + @VisibleForTesting long readGranuleOfLastPage(ExtractorInput input) throws IOException, InterruptedException { skipToNextPage(input); pageHeader.reset(); @@ -319,8 +318,8 @@ import java.io.IOException; } /** - * Skips to the position of the start of the page containing the {@code targetGranule} and - * returns the granule of the page previous to the target page. + * Skips to the position of the start of the page containing the {@code targetGranule} and returns + * the granule of the page previous to the target page. * * @param input the {@link ExtractorInput} to read from. * @param targetGranule the target granule. @@ -331,7 +330,7 @@ import java.io.IOException; * @throws IOException thrown if reading from the input fails. * @throws InterruptedException thrown if interrupted while reading from the input. */ - //@VisibleForTesting + @VisibleForTesting long skipToPageOfGranule(ExtractorInput input, long targetGranule, long currentGranule) throws IOException, InterruptedException { pageHeader.populate(input, false); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java index 482f0c5021..ca79c838de 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.ogg; +import android.support.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.Assertions; @@ -103,14 +104,14 @@ import java.util.Arrays; /** * An OGG Packet may span multiple pages. Returns the {@link OggPageHeader} of the last page read, * or an empty header if the packet has yet to be populated. - *

    - * Note that the returned {@link OggPageHeader} is mutable and may be updated during subsequent + * + *

    Note that the returned {@link OggPageHeader} is mutable and may be updated during subsequent * calls to {@link #populate(ExtractorInput)}. * * @return the {@code PageHeader} of the last page read or an empty header if the packet has yet * to be populated. */ - //@VisibleForTesting + @VisibleForTesting public OggPageHeader getPageHeader() { return pageHeader; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java index 147ad5a20b..3c62f7a824 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.ogg; +import android.support.annotation.VisibleForTesting; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.extractor.ogg.VorbisUtil.Mode; @@ -107,7 +108,7 @@ import java.util.ArrayList; return true; } - //@VisibleForTesting + @VisibleForTesting /* package */ VorbisSetup readSetupHeaders(ParsableByteArray scratch) throws IOException { if (vorbisIdHeader == null) { @@ -133,22 +134,22 @@ import java.util.ArrayList; } /** - * Reads an int of {@code length} bits from {@code src} starting at - * {@code leastSignificantBitIndex}. + * Reads an int of {@code length} bits from {@code src} starting at {@code + * leastSignificantBitIndex}. * * @param src the {@code byte} to read from. * @param length the length in bits of the int to read. * @param leastSignificantBitIndex the index of the least significant bit of the int to read. * @return the int value read. */ - //@VisibleForTesting + @VisibleForTesting /* package */ static int readBits(byte src, int length, int leastSignificantBitIndex) { return (src >> leastSignificantBitIndex) & (255 >>> (8 - length)); } - //@VisibleForTesting - /* package */ static void appendNumberOfSamples(ParsableByteArray buffer, - long packetSampleCount) { + @VisibleForTesting + /* package */ static void appendNumberOfSamples( + ParsableByteArray buffer, long packetSampleCount) { buffer.setLimit(buffer.limit() + 4); // The vorbis decoder expects the number of samples in the packet diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java index 50916aa841..2ac1427e91 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.text.ttml; +import android.support.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Subtitle; @@ -58,7 +59,7 @@ import java.util.Map; return eventTimesUs[index]; } - /* @VisibleForTesting */ + @VisibleForTesting /* package */ TtmlNode getRoot() { return root; } @@ -68,7 +69,7 @@ import java.util.Map; return root.getCues(timeUs, globalStyles, regionMap); } - /* @VisibleForTesting */ + @VisibleForTesting /* package */ Map getGlobalStyles() { return globalStyles; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java index 4220f6c5b4..19160c73d4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.upstream.cache; +import android.support.annotation.VisibleForTesting; import android.util.SparseArray; import com.google.android.exoplayer2.upstream.cache.Cache.CacheException; import com.google.android.exoplayer2.util.Assertions; @@ -336,7 +337,7 @@ import javax.crypto.spec.SecretKeySpec; * than {@link java.lang.Integer#MAX_VALUE} it just returns the next bigger integer. Otherwise it * returns the smallest unused non-negative integer. */ - //@VisibleForTesting + @VisibleForTesting public static int getNewId(SparseArray idToKey) { int size = idToKey.size(); int id = size == 0 ? 0 : (idToKey.keyAt(size - 1) + 1); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java index bae29a6858..8665852de7 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java @@ -30,6 +30,7 @@ import android.support.annotation.AnyThread; import android.support.annotation.BinderThread; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; import android.view.Display; import android.view.Surface; @@ -230,7 +231,7 @@ public final class SphericalSurfaceView extends GLSurfaceView { * Standard GL Renderer implementation. The notable code is the matrix multiplication in * onDrawFrame and updatePitchMatrix. */ - // @VisibleForTesting + @VisibleForTesting /* package */ class Renderer implements GLSurfaceView.Renderer, TouchTracker.Listener, OrientationListener.Listener { private final SceneRenderer scene; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java index 2da913fc32..08c4779cf7 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java @@ -19,6 +19,7 @@ import android.content.Context; import android.graphics.PointF; import android.support.annotation.BinderThread; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; @@ -44,7 +45,7 @@ import android.view.View; * a nicer UI. An even more advanced UI would reproject the user's touch point into 3D and drag the * Mesh as the user moves their finger. However, that requires quaternion interpolation. */ -// @VisibleForTesting +@VisibleForTesting /* package */ class TouchTracker extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener, OrientationListener.Listener { From 5a18a98fbd8ce75786de804f97b3c80722d19fde Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 7 Nov 2018 05:28:34 -0800 Subject: [PATCH 174/832] Add TrackSelectionUtil getBitratesUsingPastAndFutureInfo ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220447459 --- .../trackselection/TrackSelectionUtil.java | 97 +++++++++++++--- .../TrackSelectionUtilTest.java | 108 ++++++++++++++++++ 2 files changed, 190 insertions(+), 15 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java index fdfd87910e..052cf36309 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.trackselection; +import android.support.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.chunk.MediaChunk; @@ -77,7 +78,8 @@ public final class TrackSelectionUtil { * estimation can't be calculated, {@link Format#NO_VALUE} is set. * @see #getAverageBitrate(MediaChunkIterator, long) */ - public static int[] getBitratesUsingFutureInfo( + @VisibleForTesting + /* package */ static int[] getBitratesUsingFutureInfo( MediaChunkIterator[] iterators, Format[] formats, long maxDurationUs) { int trackCount = iterators.length; Assertions.checkArgument(trackCount == formats.length); @@ -86,6 +88,11 @@ public final class TrackSelectionUtil { } int[] bitrates = new int[trackCount]; + if (maxDurationUs == 0) { + Arrays.fill(bitrates, Format.NO_VALUE); + return bitrates; + } + int[] formatBitrates = new int[trackCount]; float[] bitrateRatios = new float[trackCount]; boolean needEstimateBitrate = false; @@ -124,10 +131,14 @@ public final class TrackSelectionUtil { * {@link Format#NO_VALUE} is set. * @see #getBitratesUsingFutureInfo(MediaChunkIterator[], Format[], long) */ - public static int[] getBitratesUsingPastInfo( + @VisibleForTesting + /* package */ static int[] getBitratesUsingPastInfo( List queue, Format[] formats, long maxDurationUs) { int[] bitrates = new int[formats.length]; Arrays.fill(bitrates, Format.NO_VALUE); + if (maxDurationUs == 0) { + return bitrates; + } int queueAverageBitrate = getAverageQueueBitrate(queue, maxDurationUs); if (queueAverageBitrate == Format.NO_VALUE) { return bitrates; @@ -141,6 +152,75 @@ public final class TrackSelectionUtil { return bitrates; } + /** + * Returns bitrate values for a set of tracks whose formats are given, using the given upcoming + * media chunk iterators and the queue of already buffered {@link MediaChunk}s. + * + * @param iterators An array of {@link MediaChunkIterator}s providing information about the + * sequence of upcoming media chunks for each track. + * @param queue The queue of already buffered {@link MediaChunk}s. Must not be modified. + * @param formats The track formats. + * @param maxFutureDurationUs Maximum duration of future chunks to be included in average bitrate + * values, in microseconds. + * @param maxPastDurationUs Maximum duration of past chunks to be included in average bitrate + * values, in microseconds. + * @param useFormatBitrateAsLowerBound Whether to return the estimated bitrate only if it's higher + * than the bitrate of the track's format. + * @return Bitrate values for the tracks. If for a track, a bitrate value can't be calculated, + * {@link Format#NO_VALUE} is set. + */ + public static int[] getBitratesUsingPastAndFutureInfo( + MediaChunkIterator[] iterators, + List queue, + Format[] formats, + long maxFutureDurationUs, + long maxPastDurationUs, + boolean useFormatBitrateAsLowerBound) { + int[] bitrates = getBitratesUsingFutureInfo(iterators, formats, maxFutureDurationUs); + int[] bitratesUsingPastInfo = getBitratesUsingPastInfo(queue, formats, maxPastDurationUs); + for (int i = 0; i < bitrates.length; i++) { + int bitrate = bitrates[i]; + if (bitrate == Format.NO_VALUE) { + bitrate = bitratesUsingPastInfo[i]; + } + if (bitrate == Format.NO_VALUE + || (useFormatBitrateAsLowerBound + && formats[i].bitrate != Format.NO_VALUE + && bitrate < formats[i].bitrate)) { + bitrate = formats[i].bitrate; + } + bitrates[i] = bitrate; + } + return bitrates; + } + + /** + * Fills missing values in the given {@code bitrates} array by calculates an estimation using the + * closest reference bitrate value. + * + * @param bitrates An array of bitrates to be filled with estimations. Missing values are set to + * {@link Format#NO_VALUE}. + * @param formats An array of formats, one for each bitrate. + * @param referenceBitrates An array of reference bitrates which are used to calculate + * estimations. + * @param referenceBitrateRatios An array containing ratio of reference bitrates to their bitrate + * estimates. + */ + private static void estimateBitrates( + int[] bitrates, Format[] formats, int[] referenceBitrates, float[] referenceBitrateRatios) { + for (int i = 0; i < bitrates.length; i++) { + if (bitrates[i] == Format.NO_VALUE) { + int formatBitrate = formats[i].bitrate; + if (formatBitrate != Format.NO_VALUE) { + int closestReferenceBitrateIndex = + getClosestBitrateIndex(formatBitrate, referenceBitrates); + bitrates[i] = + (int) (referenceBitrateRatios[closestReferenceBitrateIndex] * formatBitrate); + } + } + } + } + private static int getAverageQueueBitrate(List queue, long maxDurationUs) { if (queue.isEmpty()) { return Format.NO_VALUE; @@ -162,19 +242,6 @@ public final class TrackSelectionUtil { return queue; } - private static void estimateBitrates( - int[] bitrates, Format[] formats, int[] formatBitrates, float[] bitrateRatios) { - for (int i = 0; i < bitrates.length; i++) { - if (bitrates[i] == Format.NO_VALUE) { - int formatBitrate = formats[i].bitrate; - if (formatBitrate != Format.NO_VALUE) { - int closestFormat = getClosestBitrateIndex(formatBitrate, formatBitrates); - bitrates[i] = (int) (bitrateRatios[closestFormat] * formatBitrate); - } - } - } - } - private static int getClosestBitrateIndex(int formatBitrate, int[] formatBitrates) { int closestDistance = Integer.MAX_VALUE; int closestFormat = C.INDEX_UNSET; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java index 49da9b567a..4596ea5ded 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java @@ -408,6 +408,114 @@ public class TrackSelectionUtilTest { assertThat(bitrates).asList().containsExactly(16).inOrder(); } + @Test + public void getBitratesUsingPastAndFutureInfo_noPastInfo_returnsBitratesUsingOnlyFutureInfo() { + FakeIterator iterator1 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10}); + FakeIterator iterator2 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30}, + /* chunkLengths= */ new long[] {10, 20, 30}); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastAndFutureInfo( + new MediaChunkIterator[] {iterator1, iterator2}, + Collections.emptyList(), + new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)}, + MAX_DURATION_US, + MAX_DURATION_US, + /* useFormatBitrateAsLowerBound= */ false); + + assertThat(bitrates).asList().containsExactly(8, 16).inOrder(); + } + + @Test + public void getBitratesUsingPastAndFutureInfo_noFutureInfo_returnsBitratesUsingOnlyPastInfo() { + FakeMediaChunk chunk = + createChunk( + createFormatWithBitrate(10), + /* length= */ 10, + /* startTimeSec= */ 0, + /* endTimeSec= */ 10); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastAndFutureInfo( + new MediaChunkIterator[] {MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY}, + Collections.singletonList(chunk), + new Format[] {createFormatWithBitrate(20), createFormatWithBitrate(30)}, + MAX_DURATION_US, + MAX_DURATION_US, + /* useFormatBitrateAsLowerBound= */ false); + + assertThat(bitrates).asList().containsExactly(16, 24).inOrder(); + } + + @Test + public void + getBitratesUsingPastAndFutureInfo_pastAndFutureInfo_returnsBitratesUsingOnlyFutureInfo() { + FakeMediaChunk chunk = + createChunk( + createFormatWithBitrate(5), + /* length= */ 10, + /* startTimeSec= */ 0, + /* endTimeSec= */ 10); + FakeIterator iterator1 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10}); + FakeIterator iterator2 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30}, + /* chunkLengths= */ new long[] {10, 20, 30}); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastAndFutureInfo( + new MediaChunkIterator[] {iterator1, iterator2}, + Collections.singletonList(chunk), + new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)}, + MAX_DURATION_US, + MAX_DURATION_US, + /* useFormatBitrateAsLowerBound= */ false); + + assertThat(bitrates).asList().containsExactly(8, 16).inOrder(); + } + + @Test + public void getBitratesUsingPastAndFutureInfo_noPastAndFutureInfo_returnsBitratesOfFormats() { + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastAndFutureInfo( + new MediaChunkIterator[] {MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY}, + Collections.emptyList(), + new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)}, + MAX_DURATION_US, + MAX_DURATION_US, + /* useFormatBitrateAsLowerBound= */ false); + + assertThat(bitrates).asList().containsExactly(10, 20).inOrder(); + } + + @Test + public void + getBitratesUsingPastAndFutureInfo_estimatesLowerAndUseFormatBitrateAsLowerBoundTrue_returnsBitratesOfFormats() { + FakeMediaChunk chunk = + createChunk( + createFormatWithBitrate(10), + /* length= */ 10, + /* startTimeSec= */ 0, + /* endTimeSec= */ 10); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastAndFutureInfo( + new MediaChunkIterator[] {MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY}, + Collections.singletonList(chunk), + new Format[] {createFormatWithBitrate(20), createFormatWithBitrate(30)}, + MAX_DURATION_US, + MAX_DURATION_US, + /* useFormatBitrateAsLowerBound= */ true); + + assertThat(bitrates).asList().containsExactly(20, 30).inOrder(); + } + private static FakeMediaChunk createChunk( Format format, int length, int startTimeSec, int endTimeSec) { DataSpec dataSpec = new DataSpec(Uri.EMPTY, 0, length, null, 0); From 0dfd478a2044b8a2b4bb52c74e783f55dd66e8e7 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 7 Nov 2018 07:47:51 -0800 Subject: [PATCH 175/832] Add missing update on repeat toggle mode change ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220461315 --- .../java/com/google/android/exoplayer2/ui/PlayerControlView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java index 164a49a019..c297e62c07 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java @@ -536,6 +536,7 @@ public class PlayerControlView extends FrameLayout { controlDispatcher.dispatchSetRepeatMode(player, Player.REPEAT_MODE_ALL); } } + updateRepeatModeButton(); } /** Returns whether the shuffle button is shown. */ From 2d606f26f7f0ea04ba04d58ccf4cb2bfad64db78 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 7 Nov 2018 08:09:16 -0800 Subject: [PATCH 176/832] Minor cleanup to mediasession extension - Make units of time clear for seeking - Remove option to specify supported repeat modes in default playback controller. It doesn't seem useful, given we can't control what the session is sent. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220464529 --- .../DefaultPlaybackController.java | 44 +++++++------------ .../mediasession/MediaSessionConnector.java | 14 ++---- .../RepeatModeActionProvider.java | 7 ++- 3 files changed, 26 insertions(+), 39 deletions(-) diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/DefaultPlaybackController.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/DefaultPlaybackController.java index 7d983e14e9..fbc4a39ff2 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/DefaultPlaybackController.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/DefaultPlaybackController.java @@ -20,7 +20,6 @@ import android.os.ResultReceiver; import android.support.v4.media.session.PlaybackStateCompat; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.util.RepeatModeUtil; /** * A default implementation of {@link MediaSessionConnector.PlaybackController}. @@ -45,32 +44,27 @@ public class DefaultPlaybackController implements MediaSessionConnector.Playback protected final long rewindIncrementMs; protected final long fastForwardIncrementMs; - protected final int repeatToggleModes; /** * Creates a new instance. - *

    - * Equivalent to {@code DefaultPlaybackController(DefaultPlaybackController.DEFAULT_REWIND_MS, - * DefaultPlaybackController.DEFAULT_FAST_FORWARD_MS, - * MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES)}. + * + *

    Equivalent to {@code DefaultPlaybackController(DefaultPlaybackController.DEFAULT_REWIND_MS, + * DefaultPlaybackController.DEFAULT_FAST_FORWARD_MS)}. */ public DefaultPlaybackController() { - this(DEFAULT_REWIND_MS, DEFAULT_FAST_FORWARD_MS, - MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES); + this(DEFAULT_REWIND_MS, DEFAULT_FAST_FORWARD_MS); } /** * Creates a new instance with the given fast forward and rewind increments. - * @param rewindIncrementMs The rewind increment in milliseconds. A zero or negative value will + * + * @param rewindIncrementMs The rewind increment in milliseconds. A zero or negative value will * cause the rewind action to be disabled. * @param fastForwardIncrementMs The fast forward increment in milliseconds. A zero or negative - * @param repeatToggleModes The available repeatToggleModes. */ - public DefaultPlaybackController(long rewindIncrementMs, long fastForwardIncrementMs, - @RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { + public DefaultPlaybackController(long rewindIncrementMs, long fastForwardIncrementMs) { this.rewindIncrementMs = rewindIncrementMs; this.fastForwardIncrementMs = fastForwardIncrementMs; - this.repeatToggleModes = repeatToggleModes; } @Override @@ -101,12 +95,12 @@ public class DefaultPlaybackController implements MediaSessionConnector.Playback } @Override - public void onSeekTo(Player player, long position) { + public void onSeekTo(Player player, long positionMs) { long duration = player.getDuration(); if (duration != C.TIME_UNSET) { - position = Math.min(position, duration); + positionMs = Math.min(positionMs, duration); } - player.seekTo(Math.max(position, 0)); + player.seekTo(Math.max(positionMs, 0)); } @Override @@ -137,25 +131,21 @@ public class DefaultPlaybackController implements MediaSessionConnector.Playback } @Override - public void onSetRepeatMode(Player player, int repeatMode) { - int selectedExoPlayerRepeatMode = player.getRepeatMode(); - switch (repeatMode) { + public void onSetRepeatMode(Player player, int mediaSessionRepeatMode) { + int repeatMode; + switch (mediaSessionRepeatMode) { case PlaybackStateCompat.REPEAT_MODE_ALL: case PlaybackStateCompat.REPEAT_MODE_GROUP: - if ((repeatToggleModes & RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL) != 0) { - selectedExoPlayerRepeatMode = Player.REPEAT_MODE_ALL; - } + repeatMode = Player.REPEAT_MODE_ALL; break; case PlaybackStateCompat.REPEAT_MODE_ONE: - if ((repeatToggleModes & RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE) != 0) { - selectedExoPlayerRepeatMode = Player.REPEAT_MODE_ONE; - } + repeatMode = Player.REPEAT_MODE_ONE; break; default: - selectedExoPlayerRepeatMode = Player.REPEAT_MODE_OFF; + repeatMode = Player.REPEAT_MODE_OFF; break; } - player.setRepeatMode(selectedExoPlayerRepeatMode); + player.setRepeatMode(repeatMode); } // CommandReceiver implementation. diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java index c4110a10f7..b24c936f1a 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -39,7 +39,6 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ErrorMessageProvider; -import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; import java.util.Collections; import java.util.HashMap; @@ -76,13 +75,6 @@ public final class MediaSessionConnector { ExoPlayerLibraryInfo.registerModule("goog.exo.mediasession"); } - /** - * The default repeat toggle modes which is the bitmask of {@link - * RepeatModeUtil#REPEAT_TOGGLE_MODE_ONE} and {@link RepeatModeUtil#REPEAT_TOGGLE_MODE_ALL}. - */ - public static final @RepeatModeUtil.RepeatToggleModes int DEFAULT_REPEAT_TOGGLE_MODES = - RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE | RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL; - public static final String EXTRAS_PITCH = "EXO_PITCH"; private static final int BASE_MEDIA_SESSION_FLAGS = MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS @@ -167,7 +159,7 @@ public final class MediaSessionConnector { /** See {@link MediaSessionCompat.Callback#onPause()}. */ void onPause(Player player); /** See {@link MediaSessionCompat.Callback#onSeekTo(long)}. */ - void onSeekTo(Player player, long position); + void onSeekTo(Player player, long positionMs); /** See {@link MediaSessionCompat.Callback#onFastForward()}. */ void onFastForward(Player player); /** See {@link MediaSessionCompat.Callback#onRewind()}. */ @@ -857,9 +849,9 @@ public final class MediaSessionConnector { } @Override - public void onSeekTo(long position) { + public void onSeekTo(long positionMs) { if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_SEEK_TO)) { - playbackController.onSeekTo(player, position); + playbackController.onSeekTo(player, positionMs); } } diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.java index 057f59f62c..b45a690a9d 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/RepeatModeActionProvider.java @@ -26,6 +26,11 @@ import com.google.android.exoplayer2.util.RepeatModeUtil; */ public final class RepeatModeActionProvider implements MediaSessionConnector.CustomActionProvider { + /** The default repeat toggle modes. */ + @RepeatModeUtil.RepeatToggleModes + public static final int DEFAULT_REPEAT_TOGGLE_MODES = + RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE | RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL; + private static final String ACTION_REPEAT_MODE = "ACTION_EXO_REPEAT_MODE"; private final Player player; @@ -45,7 +50,7 @@ public final class RepeatModeActionProvider implements MediaSessionConnector.Cus * @param player The player on which to toggle the repeat mode. */ public RepeatModeActionProvider(Context context, Player player) { - this(context, player, MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES); + this(context, player, DEFAULT_REPEAT_TOGGLE_MODES); } /** From 15dda17905502b99d6fe830895811737ab78476e Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 7 Nov 2018 08:39:05 -0800 Subject: [PATCH 177/832] Make TimelineQueueNavigator shuffle aware Issue: #5065 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220468285 --- RELEASENOTES.md | 2 + .../mediasession/TimelineQueueNavigator.java | 51 +++++++++++-------- .../source/ClippingMediaSource.java | 2 +- .../exoplayer2/ui/PlayerControlView.java | 11 ++-- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 02876af90c..dae0c593aa 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### dev-v2 (not yet released) ### +* Fix logic for enabling next and previous actions in `TimelineQueueNavigator` + ([#5065](https://github.com/google/ExoPlayer/issues/5065)). * Fix issue where audio focus handling could not be disabled after enabling it ([#5055](https://github.com/google/ExoPlayer/issues/5055)). * Support for playing spherical videos on Daydream. diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java index 6671add7e5..d55f8e04f0 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java @@ -39,6 +39,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu public static final int DEFAULT_MAX_QUEUE_SIZE = 10; private final MediaSessionCompat mediaSession; + private final Timeline.Window window; protected final int maxQueueSize; private long activeQueueItemId; @@ -68,6 +69,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu this.mediaSession = mediaSession; this.maxQueueSize = maxQueueSize; activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; + window = new Timeline.Window(); } /** @@ -81,25 +83,24 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu @Override public long getSupportedQueueNavigatorActions(Player player) { - if (player == null || player.getCurrentTimeline().getWindowCount() < 2) { + if (player == null) { return 0; } - if (player.getRepeatMode() != Player.REPEAT_MODE_OFF) { - return PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + Timeline timeline = player.getCurrentTimeline(); + if (timeline.isEmpty() || player.isPlayingAd()) { + return 0; } - - int currentWindowIndex = player.getCurrentWindowIndex(); - long actions; - if (currentWindowIndex == 0) { - actions = PlaybackStateCompat.ACTION_SKIP_TO_NEXT; - } else if (currentWindowIndex == player.getCurrentTimeline().getWindowCount() - 1) { - actions = PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; - } else { - actions = PlaybackStateCompat.ACTION_SKIP_TO_NEXT - | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; + long actions = 0; + if (timeline.getWindowCount() > 1) { + actions |= PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; } - return actions | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + if (window.isSeekable || !window.isDynamic || player.hasPrevious()) { + actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; + } + if (window.isDynamic || player.hasNext()) { + actions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; + } + return actions; } @Override @@ -125,22 +126,25 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu @Override public void onSkipToPrevious(Player player) { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } + int windowIndex = player.getCurrentWindowIndex(); + timeline.getWindow(windowIndex, window); int previousWindowIndex = player.getPreviousWindowIndex(); - if (player.getCurrentPosition() > MAX_POSITION_FOR_SEEK_TO_PREVIOUS - || previousWindowIndex == C.INDEX_UNSET) { - player.seekTo(0); - } else { + if (previousWindowIndex != C.INDEX_UNSET + && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS + || (window.isDynamic && !window.isSeekable))) { player.seekTo(previousWindowIndex, C.TIME_UNSET); + } else { + player.seekTo(0); } } @Override public void onSkipToQueueItem(Player player, long id) { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } int windowIndex = (int) id; @@ -152,12 +156,15 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu @Override public void onSkipToNext(Player player) { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } + int windowIndex = player.getCurrentWindowIndex(); int nextWindowIndex = player.getNextWindowIndex(); if (nextWindowIndex != C.INDEX_UNSET) { player.seekTo(nextWindowIndex, C.TIME_UNSET); + } else if (timeline.getWindow(windowIndex, window).isDynamic) { + player.seekTo(windowIndex, C.TIME_UNSET); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index 8ac9460586..3916d41b61 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -318,7 +318,7 @@ public final class ClippingMediaSource extends CompositeMediaSource { if (timeline.getPeriodCount() != 1) { throw new IllegalClippingException(IllegalClippingException.REASON_INVALID_PERIOD_COUNT); } - Window window = timeline.getWindow(0, new Window(), false); + Window window = timeline.getWindow(0, new Window()); startUs = Math.max(0, startUs); long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : Math.max(0, endUs); if (window.durationUs != C.TIME_UNSET) { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java index c297e62c07..a3d7825f08 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java @@ -668,9 +668,8 @@ public class PlayerControlView extends FrameLayout { int windowIndex = player.getCurrentWindowIndex(); timeline.getWindow(windowIndex, window); isSeekable = window.isSeekable; - enablePrevious = - isSeekable || !window.isDynamic || player.getPreviousWindowIndex() != C.INDEX_UNSET; - enableNext = window.isDynamic || player.getNextWindowIndex() != C.INDEX_UNSET; + enablePrevious = isSeekable || !window.isDynamic || player.hasPrevious(); + enableNext = window.isDynamic || player.hasNext(); } setButtonEnabled(enablePrevious, previousButton); setButtonEnabled(enableNext, nextButton); @@ -865,7 +864,7 @@ public class PlayerControlView extends FrameLayout { private void previous() { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } int windowIndex = player.getCurrentWindowIndex(); @@ -882,14 +881,14 @@ public class PlayerControlView extends FrameLayout { private void next() { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } int windowIndex = player.getCurrentWindowIndex(); int nextWindowIndex = player.getNextWindowIndex(); if (nextWindowIndex != C.INDEX_UNSET) { seekTo(nextWindowIndex, C.TIME_UNSET); - } else if (timeline.getWindow(windowIndex, window, false).isDynamic) { + } else if (timeline.getWindow(windowIndex, window).isDynamic) { seekTo(windowIndex, C.TIME_UNSET); } } From e82d29f47a4b6ce6a7ec1778195ed3fa2c92f656 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 7 Nov 2018 08:49:10 -0800 Subject: [PATCH 178/832] Make BasePlayer.get[Next/Previous]WindowIndex more useful When in REPEAT_MODE_ONE, it's unlikely apps want next/previous methods on the player to keep them in the same window. Music apps in particular tend to implement next/previous functionality as though repeat mode were off when in this mode (i.e. current song loops forever during playback, but next/previous navigation still navigates to next/previous items). ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220469655 --- .../java/com/google/android/exoplayer2/BasePlayer.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java index 6ff0853d9b..f1b54153a1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -79,7 +79,7 @@ public abstract class BasePlayer implements Player { return timeline.isEmpty() ? C.INDEX_UNSET : timeline.getNextWindowIndex( - getCurrentWindowIndex(), getRepeatMode(), getShuffleModeEnabled()); + getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled()); } @Override @@ -88,7 +88,7 @@ public abstract class BasePlayer implements Player { return timeline.isEmpty() ? C.INDEX_UNSET : timeline.getPreviousWindowIndex( - getCurrentWindowIndex(), getRepeatMode(), getShuffleModeEnabled()); + getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled()); } @Override @@ -129,4 +129,10 @@ public abstract class BasePlayer implements Player { ? C.TIME_UNSET : timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); } + + @RepeatMode + private int getRepeatModeForNavigation() { + @RepeatMode int repeatMode = getRepeatMode(); + return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode; + } } From 8d833c319d6be36bf49ad03c99594263a75d1ea1 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 7 Nov 2018 08:53:09 -0800 Subject: [PATCH 179/832] Fix BasePlayer.next() ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220470213 --- .../src/main/java/com/google/android/exoplayer2/BasePlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java index f1b54153a1..eb3bd4f91a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -62,7 +62,7 @@ public abstract class BasePlayer implements Player { @Override public final void next() { - int nextWindowIndex = getPreviousWindowIndex(); + int nextWindowIndex = getNextWindowIndex(); if (nextWindowIndex != C.INDEX_UNSET) { seekToDefaultPosition(nextWindowIndex); } From f895be6b1f7b0202802a3d1a196f36e9131b4db6 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 7 Nov 2018 11:35:23 -0800 Subject: [PATCH 180/832] AudioFocusManager - Treat idle state consistently ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220500268 --- .../exoplayer2/ext/cast/ExoCastConstants.java | 83 ------------------- .../exoplayer2/audio/AudioFocusManager.java | 32 +++---- .../audio/AudioFocusManagerTest.java | 4 +- 3 files changed, 20 insertions(+), 99 deletions(-) delete mode 100644 extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java deleted file mode 100644 index c5647dd949..0000000000 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ext.cast; - -/** Defines constants used by the Cast extension. */ -public final class ExoCastConstants { - - private ExoCastConstants() {} - - public static final int PROTOCOL_VERSION = 0; - - // String representations. - - public static final String STR_STATE_IDLE = "IDLE"; - public static final String STR_STATE_BUFFERING = "BUFFERING"; - public static final String STR_STATE_READY = "READY"; - public static final String STR_STATE_ENDED = "ENDED"; - - public static final String STR_REPEAT_MODE_OFF = "REPEAT_MODE_OFF"; - public static final String STR_REPEAT_MODE_ONE = "REPEAT_MODE_ONE"; - public static final String STR_REPEAT_MODE_ALL = "REPEAT_MODE_ALL"; - - // Methods. - - public static final String METHOD_BASE = "player."; - - public static final String METHOD_ADD_ITEMS = METHOD_BASE + "addItems"; - public static final String METHOD_MOVE_ITEM = METHOD_BASE + "moveItem"; - public static final String METHOD_REMOVE_ITEMS = METHOD_BASE + "removeItems"; - public static final String METHOD_SET_PLAY_WHEN_READY = METHOD_BASE + "setPlayWhenReady"; - public static final String METHOD_SET_REPEAT_MODE = METHOD_BASE + "setRepeatMode"; - public static final String METHOD_SET_SHUFFLE_MODE_ENABLED = - METHOD_BASE + "setShuffleModeEnabled"; - public static final String METHOD_SEEK_TO = METHOD_BASE + "seekTo"; - public static final String METHOD_SET_PLAYBACK_PARAMETERS = METHOD_BASE + "setPlaybackParameters"; - - // JSON message keys. - - public static final String KEY_PROTOCOL_VERSION = "protocolVersion"; - public static final String KEY_METHOD = "method"; - public static final String KEY_SEQUENCE_NUMBER = "sequenceNumber"; - public static final String KEY_INDEX = "index"; - public static final String KEY_ITEMS = "items"; - public static final String KEY_ARGS = "args"; - public static final String KEY_UUID = "uuid"; - public static final String KEY_UUIDS = "uuids"; - public static final String KEY_LICENSE_SERVER = "licenseServer"; - public static final String KEY_TITLE = "title"; - public static final String KEY_URI = "uri"; - public static final String KEY_REQUEST_HEADERS = "requestHeaders"; - public static final String KEY_DESCRIPTION = "description"; - public static final String KEY_MEDIA = "media"; - public static final String KEY_DRM_SCHEMES = "drmSchemes"; - public static final String KEY_START_POSITION_US = "startPositionUs"; - public static final String KEY_END_POSITION_US = "endPositionUs"; - public static final String KEY_MIME_TYPE = "mimeType"; - public static final String KEY_PLAY_WHEN_READY = "playWhenReady"; - public static final String KEY_REPEAT_MODE = "repeatMode"; - public static final String KEY_SHUFFLE_MODE_ENABLED = "shuffleModeEnabled"; - public static final String KEY_POSITION_MS = "positionMs"; - public static final String KEY_SPEED = "speed"; - public static final String KEY_PITCH = "pitch"; - public static final String KEY_SKIP_SILENCE = "skipSilence"; - public static final String KEY_PLAYBACK_STATE = "playbackState"; - public static final String KEY_MEDIA_QUEUE = "mediaQueue"; - public static final String KEY_IS_LOADING = "isLoading"; - public static final String KEY_PLAYBACK_ERROR = "error"; - public static final String KEY_PLAYBACK_POSITION = "playbackPosition"; - public static final String KEY_PLAYBACK_PARAMETERS = "playbackParameters"; -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java index 09fd1f3f02..c20ede439f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java @@ -139,7 +139,8 @@ public final class AudioFocusManager { * @param playerState The current player state; {@link ExoPlayer#getPlaybackState()}. * @return A {@link PlayerCommand} to execute on the player. */ - public @PlayerCommand int setAudioAttributes( + @PlayerCommand + public int setAudioAttributes( @Nullable AudioAttributes audioAttributes, boolean playWhenReady, int playerState) { if (!Util.areEqual(this.audioAttributes, audioAttributes)) { this.audioAttributes = audioAttributes; @@ -154,11 +155,9 @@ public final class AudioFocusManager { } } - if (playerState == Player.STATE_IDLE) { - return PLAYER_COMMAND_WAIT_FOR_CALLBACK; - } else { - return handlePrepare(playWhenReady); - } + return playerState == Player.STATE_IDLE + ? handleIdle(playWhenReady) + : handlePrepare(playWhenReady); } /** @@ -167,7 +166,8 @@ public final class AudioFocusManager { * @param playWhenReady The current state of {@link ExoPlayer#getPlayWhenReady()}. * @return A {@link PlayerCommand} to execute on the player. */ - public @PlayerCommand int handlePrepare(boolean playWhenReady) { + @PlayerCommand + public int handlePrepare(boolean playWhenReady) { return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY; } @@ -178,16 +178,14 @@ public final class AudioFocusManager { * @param playerState The current state of the player. * @return A {@link PlayerCommand} to execute on the player. */ - public @PlayerCommand int handleSetPlayWhenReady(boolean playWhenReady, int playerState) { + @PlayerCommand + public int handleSetPlayWhenReady(boolean playWhenReady, int playerState) { if (!playWhenReady) { abandonAudioFocus(); return PLAYER_COMMAND_DO_NOT_PLAY; - } else if (playerState != Player.STATE_IDLE) { - return requestAudioFocus(); } - return focusGain != C.AUDIOFOCUS_NONE - ? PLAYER_COMMAND_WAIT_FOR_CALLBACK - : PLAYER_COMMAND_PLAY_WHEN_READY; + + return playerState == Player.STATE_IDLE ? handleIdle(playWhenReady) : requestAudioFocus(); } /** Called by the player as part of {@link ExoPlayer#stop(boolean)}. */ @@ -197,7 +195,13 @@ public final class AudioFocusManager { // Internal methods. - private @PlayerCommand int requestAudioFocus() { + @PlayerCommand + private int handleIdle(boolean playWhenReady) { + return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY; + } + + @PlayerCommand + private int requestAudioFocus() { int focusRequestResult; if (focusGain == C.AUDIOFOCUS_NONE) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java index b175828d44..086c4ebc7f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java @@ -58,7 +58,7 @@ public class AudioFocusManagerTest { assertThat( audioFocusManager.setAudioAttributes( /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_IDLE)) - .isEqualTo(PLAYER_COMMAND_WAIT_FOR_CALLBACK); + .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); assertThat( audioFocusManager.setAudioAttributes( /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY)) @@ -148,7 +148,7 @@ public class AudioFocusManagerTest { assertThat( audioFocusManager.setAudioAttributes( media, /* playWhenReady= */ true, Player.STATE_IDLE)) - .isEqualTo(PLAYER_COMMAND_WAIT_FOR_CALLBACK); + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull(); assertThat(audioFocusManager.handlePrepare(/* playWhenReady= */ true)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); From c7f763a3692b1bd24f24e0e9492360bfe937a74f Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Thu, 8 Nov 2018 22:22:23 +0200 Subject: [PATCH 181/832] #1583 - Modify in event time generation of TtmlNode Look for "div" tags when generating the set of times. --- .../exoplayer2/text/ttml/TtmlDecoder.java | 8 ++++++-- .../exoplayer2/text/ttml/TtmlNode.java | 20 ++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java index 8be5af5a3f..6da9502e1b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java @@ -479,7 +479,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { long startTime = C.TIME_UNSET; long endTime = C.TIME_UNSET; String regionId = TtmlNode.ANONYMOUS_REGION_ID; - String imageId = ""; + String imageId = null; String[] styleIds = null; int attributeCount = parser.getAttributeCount(); TtmlStyle style = parseStyleAttributes(parser, null); @@ -511,7 +511,11 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } break; case ATTR_IMAGE: - imageId = value.substring(1); + // Parse URI reference only if refers to an element in the same document (it must start with '#') + // Resolving URIs from external sources is not supported + if (value.startsWith("#")) { + imageId = value.substring(1); + } break; default: // Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java index 6b2a524e49..479d6a1771 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java @@ -159,7 +159,8 @@ import java.util.TreeSet; private void getEventTimes(TreeSet out, boolean descendsPNode) { boolean isPNode = TAG_P.equals(tag); - if (descendsPNode || isPNode) { + boolean isDivNode = TAG_DIV.equals(tag); + if (descendsPNode || isPNode || (isDivNode && imageId != null)) { if (startTimeUs != C.TIME_UNSET) { out.add(startTimeUs); } @@ -180,7 +181,7 @@ import java.util.TreeSet; } public List getCues(long timeUs, Map globalStyles, - Map regionMap, Map imageMap) { + Map regionMap, Map imageMap) { TreeMap regionOutputs = new TreeMap<>(); List> regionImageList = new ArrayList<>(); @@ -204,7 +205,7 @@ import java.util.TreeSet; Cue.TYPE_UNSET, region.line, region.lineAnchor, - region.width, + region.width == Cue.DIMEN_UNSET ? 0.5f : region.width, Cue.DIMEN_UNSET ) ); @@ -229,12 +230,17 @@ import java.util.TreeSet; return cues; } - private void traverseForImage(long timeUs, String inheritedRegion, List> regionImageList) { - // TODO isActive needed? + private void traverseForImage( + long timeUs, + String inheritedRegion, + List> regionImageList) { String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId; - if (TAG_DIV.equals(tag) && imageId != null) { - regionImageList.add(new Pair<>(resolvedRegionId, imageId)); + + if (isActive(timeUs)) { + if (TAG_DIV.equals(tag) && imageId != null) { + regionImageList.add(new Pair<>(resolvedRegionId, imageId)); + } } for (int i = 0; i < getChildCount(); ++i) { From d159050eebc7b93ab816bd4b97a38d8972cb4875 Mon Sep 17 00:00:00 2001 From: Arnold Szabo Date: Sat, 10 Nov 2018 17:07:07 +0200 Subject: [PATCH 182/832] #1583 - Set default region attrs in SubtitlePainter, extend TtmlDecoderTest --- demos/main/src/main/assets/media.exolist.json | 5 -- .../exoplayer2/text/ttml/TtmlNode.java | 4 +- .../src/test/assets/ttml/bitmap_region.xml | 26 ++++++++ .../assets/ttml/bitmap_unsupported_region.xml | 23 +++++++ .../exoplayer2/text/ttml/TtmlDecoderTest.java | 65 +++++++++++++++++++ .../exoplayer2/ui/SubtitlePainter.java | 16 +++++ 6 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 library/core/src/test/assets/ttml/bitmap_region.xml create mode 100644 library/core/src/test/assets/ttml/bitmap_unsupported_region.xml diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 74b57625d1..c2acf3990b 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -2,11 +2,6 @@ { "name": "YouTube DASH", "samples": [ - { - "name": "DVB Image sub", - "uri": "https://livesim.dashif.org/dash/vod/testpic_2s/img_subs.mpd", - "extension": "mpd" - }, { "name": "Google Glass (MP4,H264)", "uri": "https://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0", diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java index 479d6a1771..a20b9df24f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java @@ -202,10 +202,10 @@ import java.util.TreeSet; cues.add( new Cue(decodedByte, region.position, - Cue.TYPE_UNSET, + Cue.ANCHOR_TYPE_MIDDLE, region.line, region.lineAnchor, - region.width == Cue.DIMEN_UNSET ? 0.5f : region.width, + region.width, Cue.DIMEN_UNSET ) ); diff --git a/library/core/src/test/assets/ttml/bitmap_region.xml b/library/core/src/test/assets/ttml/bitmap_region.xml new file mode 100644 index 0000000000..2be95a3932 --- /dev/null +++ b/library/core/src/test/assets/ttml/bitmap_region.xml @@ -0,0 +1,26 @@ + + + + + iVBORw0KGgoAAAANSUhEUgAAAXAAAABICAYAAADvR65LAAAACXBIWXMAAAsTAAALEwEAmpwYAAANIUlEQVR4nO2d/5GjSA+GNVVfALMh4EyucAIXxDiGCWFiwKngukwghA1h74/vxMia/t3qNtjvU7W1u8Ygtbp53agFvL2/v/8hAAAAh+N/j3YAAABAGRBwAAA4KBBwAAA4KC8j4MMwEBHRuq4P9qQ/vdv+yrFOBTECFhxawFNPgnEc6Xw+ExHRPM90u92a+7YXerZ9HEc6nU50Op2IiGhZFprnGSKleOXxCGwxE/BxHLd/uwYkb1+WpeqE1iLBLMuy/XEdX54wRyEW01RCbbeyMQwDnc/nzRYRBfvjWUmN517Ho9V4eDTP0o4YJgLOJ+/pdHLOKMZxpMvlQkRE0zQVn9B8HC3eknmeq2zsBSmI8zw3EUJLG1K85Y/psiyWLu+aHn3WkqP7zzxLO1IwEXB92ezbzsEsQYu3Fge2wX+etcNKaC2iwzDc9cs0TU896wFgL5gKuEug9cldIqxyhk/0c5bNNng7xOMbGYuWcZF9jPgD0IdqAY8JdGx2nkJsYWxdV1rXFcLhoXVcQiktAEA7igScqz+I6MeCotwmt7N4l5ZP+VIntZT4k7NP7Luu7fqKQsc4N3Ytbej+1p9pm67PYrYs4l3SDzlYx7PVeAwdI8f/Hn1Zsm9tP9SOtdz21WYosgVclkAR3QdIpjnkdv77crls4ltaPlU72/N1bKzkTadxUvaRsXItrLrKyfgzPQhLY9fShus4cmzIdms/2Cbvp+NTG2+XDT4Gp3lcNlLbHotDajx7jkcr/3v0Zcm+pf1gNdb02A+NI+mrhO2mjr9sAT+dTj8cldtCAqtn4yVwh5T+ALDvLj9Pp5NTaIdhoMvl4my3r/KGbZ3PZ1qWxbuw6ion89kpzTO3suEbC7IaRbbb98Ovv1cab2lDnsCaZVl+nOjaBlFe6qk0nj3Ho6X/PfqyZN/cdliNtZxxFKqmy52NZws4/0IwoXpWKdhStHMFiG2yLT75SsqE2B9XG/h4evYgO1jvJ39FLXLNXMWhxVHarU0hWdmQcdQlhPrfEletuEyxWcQ71M/yhJPb+XP+k9qfNfFsMR7ZXuo5UeN/bV/6fC3ZN7cd1mONjy3HEJdP8/5sU44/uV8u2QJ+u902Zz4+PojIPe2XjnJgS3N067rSPM/O3JYMXsol2TzPd6I/DAMty7IFWp+4crDI6he5Hw8YCwFf15Wu1+uWS+OT2LK23coGjwV5c1VKX8v+4v/LWbpFvGP9zPblmJEzo9PplJTTJaqLp+V4lNuXZaHr9Rr1vdb/0r6M+Vqyb247rMeaFmk50eRtevLgSjdxW1KoqkKJXR5KR2vFh4/vynHJf8cuH7Wv67pug1BfCukFBtkO/lGR/ozjiEoYig8+LZyMZbxj/ewSDbm9FzXjUZ78epLjmr23ILUvc3zt0c7WY0366JsMuK48mi9iMjIAOemTGm632zawXTnMlEueHF907kzvyyebLwcG3Pgu7y3jbVmp1JKa8ejL70vhaC3gqX2Z42uPdrYea/pHWPooNYy/V9pPxQIuBVrDq7rsrCWy5lteusv8plU6g48nbWtk+3LypsAN4h2G48OTFR0PGb9Hx6fG1x7tbDnWfIKshf3r62ubAJfc9p8s4PohUvJvmUti5Hadd7SaFXAOVua82GavdIbuZNAWxPubI1351fj6qHa2GGvrutI0TbQsy12OnOg7Z9+kjNAl0nKbD520b4Er5wTAM5OSmtxLGqnG1yO1MxVebNV5dpkaJkqraksWcLnSHMofhbbV5HpS/Ou9AEV0/8t8tIF0RBDv/1Nb2dWTGl8f2c6asea6Q1nDQk70fWOPq3IlRLKAy/IcLq/hMhgJp0x4u5x1t+4Ea/HW+Sq9kiwXcvn7oBzEO8yjJikl1Pjao509xlpokVQjq+ykDzHNzFrElHWY7Jg2oJ22EG25KOrLoctLD6vKF70SfT6f70rPdHrIZ9M1EGWbYvSoKOhVtRDCKt57oEU8ZXxC5XMWz0ap9b/GV8t2+tphOdZ4kVXa0Hokt43jGNTOHIpupWeHXY3i7ZYnmMwNuWzLhQAim7pzeSxd6cK29fPJXXWejBbr0JoC0f2g1O2z+mHsYSOXmng/mh7xlPGRN8oxfJ7M85x8I08r/2t8rdk3tR1WY01mHLRNmXom+r5ZjO3IK4GSeBcLuEugLZ79HUM3kn1ipmkyXSzlSxvuJA5+ik3dOVz3mfpL63p8AH+ee3I+0kYONfHeA63j6YsP0f154EoL9Pa/xtfadqa0w3Ks+c5v12STv+9Dp55DFAl4LD1ilcJg5G2orudZsL1QmWLIH+mv63syP6UvjXx3ovF+8upB2tPPEPH5patrSuIaa3utjVj8UvyQlMY7xb6ln759U+LZajzGzoMe/lv5WrNvajtqxpq0JdMxof154it1TB8np+/e3t/f/yR98z94lu0TcIv8W4p9TWzGzy85DT35LNQul+3UqwzffvLzmF+S3Pr21LbX2EiJX8yPmF8p8a7t55R25Prt8qfFeCSyufK18D/lmKXnT+q+OeM6d6yN40hfX19E9D1Lzz2HdMaCKF83swUcAABeHSngn5+fD7vj1eSdmAAAAPoDAQcAgIMCAQcAgIMCAQcAgIMCAQcAgAL2cCcwBBwAADKRVSePfOY6yggBAOCgvP39B/od4p9fvxAgAB7EX79/vz3ahz2DFAoAABwUCDgAABwUCDgAABwUCPhOaP0QsBb08Hnvcdm7f6141XbvDQh4Q1IHOb8Pj4iy3kj9SHr4vPe47N2/Vrxqu/cIBNyYcRzvngvMyGcY+14JR0S7fVGBi5DP/LhRoro62UfFJdX/I/abBa/a7r0BATeEX5cUeuMOvwj6mS89+X2f/D7DPb7+LMTR/QevAwTcCC3erlcpyT+h92cehR4+HzEurwD6ZR9AwA3gGZt8756cZfObN3xv39nLbbk59PD5iHF5BdAv+wECboDrXXhyhr2uK63rGhzsRzwRevh8xLi8AuiXfQABN8KXOkklVLHi25ZbyuX6fk05mO948gdNL+jm2ukRF72vhf8lPliW5vn6JnRs3ifFh979AtxAwI0JLWD6CJVl6W1sw/WW+9DLhF37SH9zy8FcPvNnWgAvl8tmL8dO67j47JX47xP8mA86/Vbit68d7K/2Sy+iy+9LH5Zlcba1d78APxBwY/iEzxXEUFkWb5Mi4bLrqm5JqYzx2S3xWQsB+yavUPYQl5g9fYyY/9qXFB+GYaDL5eK1WVNjLY+p/ZeL6LLiRsM/WqH29uoX4AYCbgDPKHjg8ozKugztdDptthhpU89q9OxOpndcteq1LMtC0zRtbWekvy2qF3Lj4qPG/5K+keKt9+PPa8eObIe8F0GjhViO4VIfrPoF+IGAG7CuK83z7Myd8iC2uGyc5/nuB2EYBlqWhS6Xy2ZTzpakEPC+t9tty/P6Zl6lrOtK1+t1y3XySdp6ppUblxb+1/YN25C2WTyv12t+UP5Djj3+v15gn6Zp+zcR3flQ8yNv1S/ADwTcCB6Irhyq/HfNZbG+fF/XdTtB9YyaRZr3k3a5KsZ6Bv4ocuKyBx9038gfCD0ZqJ2psoiG9tfb2Hei7/FbYn8P/fLsQMANud1u2+DUQk50P6MpEfGc9INv0bL0eHtmD+0o7RseL67jhW78yvErp3ImlLcusQ3aAgE3RtZ8y+oPubBzPp+7XDpKkUCucV9w3/CPuuuuXfn/VuNFVyhZCjhoDwS8Ibfbbcs5E92vzo/jiPwfIKI2C8opyAol1wInRHz/QMA7oPOaODEAk3LjV4tUhBbvaZrurtQ+Pj62xUawXyDgnZCLNwAwehGzF/rGHn01iPz1MYCAd6S3eMsfjNht1KAfe/gxl+sj4LhAwA3gG2aIyFuy5buhphVSJHw3kvQQkNoqikfTwn8up4uVCbZ8doguE9QzcFwpHgMIuAG6bNC1GKTv7GstaLKWl4ju8p1E9TdpxGwzuu1HqIjp4b9cE9F9Q/TdP/M8V93I40Pbkp/pNoP9AgE3Rp/sRPezmWmaur2GikWCxYAfytRjduV6tAB/3kKQrGntP894WbzlA7N0CWGL9Jd8/EPIPtg3EHAD+GTU9d46ZRK6nT6UUolt4+36e/I2adet/UTuhzelEvNLV96UpI1axCXVbor/NT7Iu3ddKbaaxy/E2sxjY1mWH1eP8imCJcdv2S/gnre///x5tA+75p9fv7IC5Mstxy69+SW6vsd3+rZJmyEb8iW97M/5fN5KxT4/P7Pr0lP9kljasIhLiBT/LXxw2alN1cT8cn1X2ib6FvDc2Fv2y1+/f79F3H9pIOARcgX8SIzjSF9fX0RUJuAAtAYCHgYplBcGuU4Ajg0E/ImRl8b6clU/EQ8AcDwg4E+MrECRz2Ym+vnSAIg4AMcDAv7E6OdK88KRrpDBm1EAOCYQ8CeGH6JF9PNFE0Q/X/QAADgWqEKJ8AxVKJzv1nXgR7grErw2qEIJAwGP8AwCDsBRgYCH+RdHEwkWLXE/8gAAAABJRU5ErkJggg== + + + iVBORw0KGgoAAAANSUhEUgAAAaAAAAAkCAIAAABAAnl0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAIBklEQVR4nO1d65GrPAz1nfkKSAu4FWglqeXWQFohrZhW7o8zaBS/kB+QhE/nx86uA5YsnT1+CHb/3G43o1AoFFfEf592QKFQKI6CCpxCobgsVOAUCsVloQL3SxiGwRizruuht5TiBBM/hJ+Lxs85XAQVuJ/BOI7TNBljlmV5vV4H3XKCVxfGz0Xj5xwuRaXAjeOIb7ygoN05J58QqCuOoh7+PyAu8sZULjK3lOIEE5fBB6ORT1MK105fjcANwzBNk7XWU/1xHB+PhzFmnmehPOEWa63X7pxblsU5d8lZpSOQi2maEK4jZoUTTCjaoWmKokbgrLWQJOdc2I74VnRLd6Gfx+OBWUU1jlAXWMWZ0Bx9FeoFzhOyYRhI9Spmj2VZaJGM/jEdoU/VOMOCoNH4WmiOvg3FApcSstSyTg5ODpwmQOCmaVK6ABqH74fm6KsgFTjUkg0TMt5I7VC39sIzWGI3jOMY5Q05kLIVeiL0TXJZx4c2hO3hj5QOnpeObh9qopon8rTmTVTctbtT2R1U46hTDlekqdHDipHWpanuSkAkcFRLNix8tH+kdnx9PB6QucbC8+v1ggkgLNeSS8YY51xYkeAlcPOeeBQxUkShnqPdUo0l31WIYRhQhHHOPZ/PqJ/c1v1+hxUUbbzL8COndSbyIfUlbh9kojqAYbhCi8iXZyLqMPXj1cRS6aBoUAs8D6+JmpNQa3fI0XuL0pRC9/T1SlOR0RAigeOJ4Y3cM6+9y1Hrsiywa60dhoGvXHBCxy+GRS86dDt95X56zIYAhd1aa0mPotfYoKCcAepcfDh8LNQJL1XzKw2r6Idu0OIiE4dMBKLe9jXRHkCTTatzjv+2cxPmnR4IO/LrBSF8ciJa7o8u5aJPXUiolYKE7fI0pXBE+rqkqdSoB5HAQS+5017+SNRI17o84YEOU0rKfaDTuujGAb55Q4DQcNGkINJAaP0IPeLX8N7orrxY0KfoEz9623wSPs7RVHDmeeZzD7kU3iKJwAkmGgMosUhx82pWQB0n4TZ1S9wouj1Prd1OMmwvSlMKjemLClOmZ3maGjkjErjX6wWT9/vdBCtzSj8C3fcBHAicMYZPs+u6zvNsNsmARnhrXQ6UaBGLYRicc9gq8lDyiNPo6MAFhiisy7JgpGQabNudjbEm5Vnk8s2Fj9QtxdF1XZ/PJzlALlVH4AQT7QGUWAQ/6TeTFl9yNfHAucEjIDwPklArg122F6Upher04UqbLgY2pqmRM2VVVJtY91L+Tnu8kBvCJGbeBYKDO4yLvYUh7Qc97V7X1TvRN9u6Bu3rui7Lgq52F0TkTChq9Cn4RLZMp+eqdiNwgoleAdy16JGe2ruM0SPe7i0Sau2iiO11KEofpQnpozk7WgxsSVM7ZwoEjozt7k9PA7mUp++uY5TIzCj4NZ45osIu4XgWMU3xkIIomKDIVhcGn5CaoiBXB7DIYkfAQywZipIioZYQQrbXofp3BJqVmSxbRt3OmQKBIyHjjZigzGFsiy5kxnGkkfdKOfWQCZZl5WPyx1uO5U95vOnXMCHzevPar4H2AH4EfDnz9+9fzEbyN6Ik1MrgCLbXITUQnsru6WvkzI7ARYPr1XSonW+tu6w7UupGR358Mjkz65n5aheOHVHTnIFGbyb8yKL4HLQE8HzgFMxthT9+AN/4LNQuvoHtuzjHqzrO7AhcWJ82QU3aM790/bME3qTB8w3Oof1+v0NeT0BGdyR6RMsBCqPX4eUFrjGAHwEO8vl5EzJo09XDdnwD278E1ZzZETi31Xf5ZjhcKPLGXsdGtPk1QS3ZGDPPM2fVyccxjcXicKWG3kLhO61ocybaA/gpQObM9hTrdPCrhN/AdgmiO62+qObMjsBR+RkzCSq19Cm2pWgnDepFXL4k9NbA3ePID1lSTxLw+kAL+DEc9ex9E/3x19ErgN8AyA1NRZmnTyTUSuGrzmFTA+le8Y8are5hv8hAD56YYE3Bl299J2T+4Dg/0eMn9HxOa/y14ZWgaZqiT9bQNdHKdNErcjxt3uKXtxcNAc4fuixqNNExgB3hMYfvGzjGcYySfxcSaqVQx/aDmJAaSHQh0t1oNWekr2rZ2IMgaO8yMGvt/X53rPRLi3PeOX3PS7c29iZZKfiTNaEhLI/pGjoZoWsQByF9vRFx+Y4KXwb87YiD9rYdTfQKYBd4KabvQy5hunVbIaj0JEFCLYmHebafwIToQGghctCJSiNnCgRu6foH4EIT3rmpe3/QmTdibMS5Lrue1+tlt2Njr2fafWMWRaBD6/I9CM1L5l3saPdqSqK6bG/s0pl3d6XoZaJXALuAS9W0vZQavdJuTyqEH/HDmRQk1Ep5WMT2o5kQpo+cmee5b3UxY9SUcEYqcKl9qHChkULqdrf9yXLPolewt+w1t2jiU53TbMzbn8+ne38HGFdykaXDF+KQ21D0cAzmpdCHlG/54dAsZ4MHFYsikEJHE10CWDGosJHrDh+mCbQMVJzeX0dP+Ry1LqFWiAq2Z9KUQnv6woVIRc+ZW1o48+d2u2U+BrBYC+Wmy7kJP6QEJIsX/q9quKhH/wlOWORKjSj0J/XHW/g18tWW51vKAZOIan44UZ8rIhBFXxONASy1KPTEbe9LRrlk3nctpjBHRkatKIRsrzPRmL5M7jqmKRyakDMigVMoFIpfhP5fVIVCcVmowCkUistCBU6hUFwWKnAKheKyUIFTKBSXhQqcQqG4LFTgFArFZfEPuuTdBr3uWzgAAAAASUVORK5CYII= + + + + From cc153cfca37aa0f0ca96148681cfd80af353fb38 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 13 Feb 2019 11:35:11 +0000 Subject: [PATCH 532/832] Use floating action button in the cast demo app PiperOrigin-RevId: 233723028 --- demos/cast/build.gradle | 1 + .../{ic_add_circle_white_24dp.xml => ic_plus.xml} | 12 ++++++++---- demos/cast/src/main/res/layout/main_activity.xml | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) rename demos/cast/src/main/res/drawable/{ic_add_circle_white_24dp.xml => ic_plus.xml} (59%) diff --git a/demos/cast/build.gradle b/demos/cast/build.gradle index 79cf3d9a0a..7e504b6c43 100644 --- a/demos/cast/build.gradle +++ b/demos/cast/build.gradle @@ -56,6 +56,7 @@ dependencies { implementation project(modulePrefix + 'library-smoothstreaming') implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'extension-cast') + implementation 'com.android.support:design:' + supportLibraryVersion implementation 'com.android.support:support-v4:' + supportLibraryVersion implementation 'com.android.support:appcompat-v7:' + supportLibraryVersion implementation 'com.android.support:recyclerview-v7:' + supportLibraryVersion diff --git a/demos/cast/src/main/res/drawable/ic_add_circle_white_24dp.xml b/demos/cast/src/main/res/drawable/ic_plus.xml similarity index 59% rename from demos/cast/src/main/res/drawable/ic_add_circle_white_24dp.xml rename to demos/cast/src/main/res/drawable/ic_plus.xml index 5f3c8961ef..5a5a5154c9 100644 --- a/demos/cast/src/main/res/drawable/ic_add_circle_white_24dp.xml +++ b/demos/cast/src/main/res/drawable/ic_plus.xml @@ -13,8 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/demos/cast/src/main/res/layout/main_activity.xml b/demos/cast/src/main/res/layout/main_activity.xml index 98efe2095e..b0be69bed3 100644 --- a/demos/cast/src/main/res/layout/main_activity.xml +++ b/demos/cast/src/main/res/layout/main_activity.xml @@ -38,14 +38,14 @@ android:scrollbars="vertical" android:fadeScrollbars="false"/> - From 04e606149870213dd22de3b5e3255a8f41a5c825 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 13 Feb 2019 17:11:27 +0000 Subject: [PATCH 533/832] Reduce the number of parameters required to create an HlsMediaChunk PiperOrigin-RevId: 233765839 --- .../exoplayer2/source/hls/HlsChunkSource.java | 40 +--- .../exoplayer2/source/hls/HlsMediaChunk.java | 187 +++++++++++++----- 2 files changed, 141 insertions(+), 86 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index d01476d004..b188aa74f3 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -290,8 +290,8 @@ import java.util.List; } } - int chunkIndex = (int) (chunkMediaSequence - mediaPlaylist.mediaSequence); - if (chunkIndex >= mediaPlaylist.segments.size()) { + int segmentIndexInPlaylist = (int) (chunkMediaSequence - mediaPlaylist.mediaSequence); + if (segmentIndexInPlaylist >= mediaPlaylist.segments.size()) { if (mediaPlaylist.hasEndTag) { out.endOfStream = true; } else /* Live */ { @@ -306,7 +306,7 @@ import java.util.List; expectedPlaylistUrl = null; // Handle encryption. - HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex); + HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(segmentIndexInPlaylist); // Check if the segment is completely encrypted using the identity key format. if (segment.fullSegmentEncryptionKeyUri != null) { @@ -324,44 +324,20 @@ import java.util.List; clearEncryptionData(); } - DataSpec initDataSpec = null; - Segment initSegment = segment.initializationSegment; - if (initSegment != null) { - Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url); - initDataSpec = new DataSpec(initSegmentUri, initSegment.byterangeOffset, - initSegment.byterangeLength, null); - } - - // Compute start time of the next chunk. - long segmentStartTimeInPeriodUs = startOfPlaylistInPeriodUs + segment.relativeStartTimeUs; - int discontinuitySequence = mediaPlaylist.discontinuitySequence - + segment.relativeDiscontinuitySequence; - TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster( - discontinuitySequence); - - // Configure the data source and spec for the chunk. - Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); - DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, - null); out.chunk = - new HlsMediaChunk( + HlsMediaChunk.createInstance( extractorFactory, mediaDataSource, - dataSpec, - initDataSpec, + startOfPlaylistInPeriodUs, + mediaPlaylist, + segmentIndexInPlaylist, selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), - segmentStartTimeInPeriodUs, - segmentStartTimeInPeriodUs + segment.durationUs, - chunkMediaSequence, - discontinuitySequence, - segment.hasGapTag, isTimestampMaster, - timestampAdjuster, + timestampAdjusterProvider, previous, - segment.drmInitData, encryptionKey, encryptionIv); } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 2995f8b0aa..57f604bb2f 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.hls; +import android.net.Uri; import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -27,10 +28,12 @@ import com.google.android.exoplayer2.metadata.id3.Id3Decoder; import com.google.android.exoplayer2.metadata.id3.PrivFrame; import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; +import com.google.android.exoplayer2.util.UriUtil; import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; @@ -42,6 +45,112 @@ import java.util.concurrent.atomic.AtomicInteger; */ /* package */ final class HlsMediaChunk extends MediaChunk { + /** + * Creates a new instance. + * + * @param extractorFactory A {@link HlsExtractorFactory} from which the HLS media chunk extractor + * is obtained. + * @param dataSource The source from which the data should be loaded. + * @param startOfPlaylistInPeriodUs The position of the playlist in the period in microseconds. + * @param mediaPlaylist The media playlist from which this chunk was obtained. + * @param hlsUrl The url of the playlist from which this chunk was obtained. + * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption + * information is available in the master playlist. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. + * @param timestampAdjusterProvider The provider from which to obtain the {@link + * TimestampAdjuster}. + * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null. + * @param fullSegmentEncryptionKey The key to decrypt the full segment, or null if the segment is + * not fully encrypted. + * @param encryptionIv The AES initialization vector, or null if the segment is not fully + * encrypted. + */ + public static HlsMediaChunk createInstance( + HlsExtractorFactory extractorFactory, + DataSource dataSource, + long startOfPlaylistInPeriodUs, + HlsMediaPlaylist mediaPlaylist, + int segmentIndexInPlaylist, + HlsUrl hlsUrl, + List muxedCaptionFormats, + int trackSelectionReason, + Object trackSelectionData, + boolean isMasterTimestampSource, + TimestampAdjusterProvider timestampAdjusterProvider, + HlsMediaChunk previousChunk, + byte[] fullSegmentEncryptionKey, + byte[] encryptionIv) { + + HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(segmentIndexInPlaylist); + DataSpec dataSpec = + new DataSpec( + UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url), + segment.byterangeOffset, + segment.byterangeLength, + /* key= */ null); + + DataSpec initDataSpec = null; + HlsMediaPlaylist.Segment initSegment = segment.initializationSegment; + if (initSegment != null) { + Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url); + initDataSpec = + new DataSpec( + initSegmentUri, + initSegment.byterangeOffset, + initSegment.byterangeLength, + /* key= */ null); + } + + long segmentStartTimeInPeriodUs = startOfPlaylistInPeriodUs + segment.relativeStartTimeUs; + long segmentEndTimeInPeriodUs = segmentStartTimeInPeriodUs + segment.durationUs; + int discontinuitySequenceNumber = + mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence; + + Extractor previousExtractor = null; + Id3Decoder id3Decoder; + ParsableByteArray scratchId3Data; + boolean shouldSpliceIn; + if (previousChunk != null) { + id3Decoder = previousChunk.id3Decoder; + scratchId3Data = previousChunk.scratchId3Data; + shouldSpliceIn = previousChunk.hlsUrl != hlsUrl || !previousChunk.loadCompleted; + previousExtractor = + previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber || shouldSpliceIn + ? null + : previousChunk.extractor; + } else { + id3Decoder = new Id3Decoder(); + scratchId3Data = new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH); + shouldSpliceIn = false; + } + + return new HlsMediaChunk( + extractorFactory, + dataSource, + dataSpec, + initDataSpec, + hlsUrl, + muxedCaptionFormats, + trackSelectionReason, + trackSelectionData, + segmentStartTimeInPeriodUs, + segmentEndTimeInPeriodUs, + /* chunkMediaSequence= */ mediaPlaylist.mediaSequence + segmentIndexInPlaylist, + discontinuitySequenceNumber, + segment.hasGapTag, + isMasterTimestampSource, + /* timestampAdjuster= */ timestampAdjusterProvider.getAdjuster(discontinuitySequenceNumber), + segment.drmInitData, + previousExtractor, + id3Decoder, + scratchId3Data, + shouldSpliceIn, + fullSegmentEncryptionKey, + encryptionIv); + } + public static final String PRIV_TIMESTAMP_FRAME_OWNER = "com.apple.streaming.transportStreamTimestamp"; @@ -74,7 +183,7 @@ import java.util.concurrent.atomic.AtomicInteger; private final DrmInitData drmInitData; private final Extractor previousExtractor; private final Id3Decoder id3Decoder; - private final ParsableByteArray id3Data; + private final ParsableByteArray scratchId3Data; private Extractor extractor; private HlsSampleStreamWrapper output; @@ -84,32 +193,7 @@ import java.util.concurrent.atomic.AtomicInteger; private volatile boolean loadCanceled; private boolean loadCompleted; - /** - * @param extractorFactory A {@link HlsExtractorFactory} from which the HLS media chunk extractor - * is obtained. - * @param dataSource The source from which the data should be loaded. - * @param dataSpec Defines the data to be loaded. - * @param initDataSpec Defines the initialization data to be fed to new extractors. May be null. - * @param hlsUrl The url of the playlist from which this chunk was obtained. - * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption - * information is available in the master playlist. - * @param trackSelectionReason See {@link #trackSelectionReason}. - * @param trackSelectionData See {@link #trackSelectionData}. - * @param startTimeUs The start time of the chunk in microseconds. - * @param endTimeUs The end time of the chunk in microseconds. - * @param chunkMediaSequence The media sequence number of the chunk. - * @param discontinuitySequenceNumber The discontinuity sequence number of the chunk. - * @param hasGapTag Whether the chunk is tagged with EXT-X-GAP. - * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. - * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. - * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null. - * @param drmInitData A {@link DrmInitData} to sideload to the extractor. - * @param fullSegmentEncryptionKey The key to decrypt the full segment, or null if the segment is - * not fully encrypted. - * @param encryptionIv The AES initialization vector, or null if the segment is not fully - * encrypted. - */ - public HlsMediaChunk( + private HlsMediaChunk( HlsExtractorFactory extractorFactory, DataSource dataSource, DataSpec dataSpec, @@ -125,8 +209,11 @@ import java.util.concurrent.atomic.AtomicInteger; boolean hasGapTag, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster, - HlsMediaChunk previousChunk, DrmInitData drmInitData, + Extractor previousExtractor, + Id3Decoder id3Decoder, + ParsableByteArray scratchId3Data, + boolean shouldSpliceIn, byte[] fullSegmentEncryptionKey, byte[] encryptionIv) { super( @@ -148,19 +235,10 @@ import java.util.concurrent.atomic.AtomicInteger; this.extractorFactory = extractorFactory; this.muxedCaptionFormats = muxedCaptionFormats; this.drmInitData = drmInitData; - Extractor previousExtractor = null; - if (previousChunk != null) { - id3Decoder = previousChunk.id3Decoder; - id3Data = previousChunk.id3Data; - shouldSpliceIn = previousChunk.hlsUrl != hlsUrl || !previousChunk.loadCompleted; - previousExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber - || shouldSpliceIn ? null : previousChunk.extractor; - } else { - id3Decoder = new Id3Decoder(); - id3Data = new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH); - shouldSpliceIn = false; - } this.previousExtractor = previousExtractor; + this.id3Decoder = id3Decoder; + this.scratchId3Data = scratchId3Data; + this.shouldSpliceIn = shouldSpliceIn; initDataSource = dataSource; uid = uidSource.getAndIncrement(); } @@ -314,26 +392,26 @@ import java.util.concurrent.atomic.AtomicInteger; private long peekId3PrivTimestamp(ExtractorInput input) throws IOException, InterruptedException { input.resetPeekPosition(); try { - input.peekFully(id3Data.data, 0, Id3Decoder.ID3_HEADER_LENGTH); + input.peekFully(scratchId3Data.data, 0, Id3Decoder.ID3_HEADER_LENGTH); } catch (EOFException e) { // The input isn't long enough for there to be any ID3 data. return C.TIME_UNSET; } - id3Data.reset(Id3Decoder.ID3_HEADER_LENGTH); - int id = id3Data.readUnsignedInt24(); + scratchId3Data.reset(Id3Decoder.ID3_HEADER_LENGTH); + int id = scratchId3Data.readUnsignedInt24(); if (id != Id3Decoder.ID3_TAG) { return C.TIME_UNSET; } - id3Data.skipBytes(3); // version(2), flags(1). - int id3Size = id3Data.readSynchSafeInt(); + scratchId3Data.skipBytes(3); // version(2), flags(1). + int id3Size = scratchId3Data.readSynchSafeInt(); int requiredCapacity = id3Size + Id3Decoder.ID3_HEADER_LENGTH; - if (requiredCapacity > id3Data.capacity()) { - byte[] data = id3Data.data; - id3Data.reset(requiredCapacity); - System.arraycopy(data, 0, id3Data.data, 0, Id3Decoder.ID3_HEADER_LENGTH); + if (requiredCapacity > scratchId3Data.capacity()) { + byte[] data = scratchId3Data.data; + scratchId3Data.reset(requiredCapacity); + System.arraycopy(data, 0, scratchId3Data.data, 0, Id3Decoder.ID3_HEADER_LENGTH); } - input.peekFully(id3Data.data, Id3Decoder.ID3_HEADER_LENGTH, id3Size); - Metadata metadata = id3Decoder.decode(id3Data.data, id3Size); + input.peekFully(scratchId3Data.data, Id3Decoder.ID3_HEADER_LENGTH, id3Size); + Metadata metadata = id3Decoder.decode(scratchId3Data.data, id3Size); if (metadata == null) { return C.TIME_UNSET; } @@ -343,11 +421,12 @@ import java.util.concurrent.atomic.AtomicInteger; if (frame instanceof PrivFrame) { PrivFrame privFrame = (PrivFrame) frame; if (PRIV_TIMESTAMP_FRAME_OWNER.equals(privFrame.owner)) { - System.arraycopy(privFrame.privateData, 0, id3Data.data, 0, 8 /* timestamp size */); - id3Data.reset(8); + System.arraycopy( + privFrame.privateData, 0, scratchId3Data.data, 0, 8 /* timestamp size */); + scratchId3Data.reset(8); // The top 31 bits should be zeros, but explicitly zero them to wrap in the case that the // streaming provider forgot. See: https://github.com/google/ExoPlayer/pull/3495. - return id3Data.readLong() & 0x1FFFFFFFFL; + return scratchId3Data.readLong() & 0x1FFFFFFFFL; } } } From 5c2aa4bd9e66d55886e27bef304dba1408a244a4 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 13 Feb 2019 19:10:58 +0000 Subject: [PATCH 534/832] Automated g4 rollback of changelist 233073011. *** Reason for rollback *** Pending discussions about cast *** Original change description *** Remove default-receiver-related classes from Cast demo app *** PiperOrigin-RevId: 233790699 --- demos/cast/build.gradle | 16 + demos/cast/src/main/AndroidManifest.xml | 2 +- .../DefaultReceiverPlayerManager.java | 405 +++++++++++++++++ .../exoplayer2/castdemo/MainActivity.java | 31 +- .../exoplayer2/castdemo/PlayerManager.java | 407 ++---------------- 5 files changed, 470 insertions(+), 391 deletions(-) create mode 100644 demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java diff --git a/demos/cast/build.gradle b/demos/cast/build.gradle index 7e504b6c43..49a5e80985 100644 --- a/demos/cast/build.gradle +++ b/demos/cast/build.gradle @@ -47,6 +47,22 @@ android { // The demo app isn't indexed and doesn't have translations. disable 'GoogleAppIndexingWarning','MissingTranslation' } + + flavorDimensions "receiver" + + productFlavors { + exoCast { + dimension "receiver" + manifestPlaceholders = + [castOptionsProvider: "com.google.android.exoplayer2.ext.cast.ExoCastOptionsProvider"] + } + defaultCast { + dimension "receiver" + manifestPlaceholders = + [castOptionsProvider: "com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider"] + } + } + } dependencies { diff --git a/demos/cast/src/main/AndroidManifest.xml b/demos/cast/src/main/AndroidManifest.xml index 69887485cb..856b0b1235 100644 --- a/demos/cast/src/main/AndroidManifest.xml +++ b/demos/cast/src/main/AndroidManifest.xml @@ -25,7 +25,7 @@ android:largeHeap="true" android:allowBackup="false"> + android:value="${castOptionsProvider}" /> mediaQueue; + private final Listener listener; + private final ConcatenatingMediaSource concatenatingMediaSource; + + private boolean castMediaQueueCreationPending; + private int currentItemIndex; + private Player currentPlayer; + + /** + * Creates a new manager for {@link SimpleExoPlayer} and {@link CastPlayer}. + * + * @param listener A {@link Listener} for queue position changes. + * @param localPlayerView The {@link PlayerView} for local playback. + * @param castControlView The {@link PlayerControlView} to control remote playback. + * @param context A {@link Context}. + * @param castContext The {@link CastContext}. + */ + public DefaultReceiverPlayerManager( + Listener listener, + PlayerView localPlayerView, + PlayerControlView castControlView, + Context context, + CastContext castContext) { + this.listener = listener; + this.localPlayerView = localPlayerView; + this.castControlView = castControlView; + mediaQueue = new ArrayList<>(); + currentItemIndex = C.INDEX_UNSET; + concatenatingMediaSource = new ConcatenatingMediaSource(); + + DefaultTrackSelector trackSelector = new DefaultTrackSelector(); + RenderersFactory renderersFactory = new DefaultRenderersFactory(context); + exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector); + exoPlayer.addListener(this); + localPlayerView.setPlayer(exoPlayer); + + castPlayer = new CastPlayer(castContext); + castPlayer.addListener(this); + castPlayer.setSessionAvailabilityListener(this); + castControlView.setPlayer(castPlayer); + + setCurrentPlayer(castPlayer.isCastSessionAvailable() ? castPlayer : exoPlayer); + } + + // Queue manipulation methods. + + /** + * Plays a specified queue item in the current player. + * + * @param itemIndex The index of the item to play. + */ + @Override + public void selectQueueItem(int itemIndex) { + setCurrentItem(itemIndex, C.TIME_UNSET, true); + } + + /** Returns the index of the currently played item. */ + @Override + public int getCurrentItemIndex() { + return currentItemIndex; + } + + /** + * Appends {@code item} to the media queue. + * + * @param item The {@link MediaItem} to append. + */ + @Override + public void addItem(MediaItem item) { + mediaQueue.add(item); + concatenatingMediaSource.addMediaSource(buildMediaSource(item)); + if (currentPlayer == castPlayer) { + castPlayer.addItems(buildMediaQueueItem(item)); + } + } + + /** Returns the size of the media queue. */ + @Override + public int getMediaQueueSize() { + return mediaQueue.size(); + } + + /** + * Returns the item at the given index in the media queue. + * + * @param position The index of the item. + * @return The item at the given index in the media queue. + */ + @Override + public MediaItem getItem(int position) { + return mediaQueue.get(position); + } + + /** + * Removes the item at the given index from the media queue. + * + * @param item The item to remove. + * @return Whether the removal was successful. + */ + @Override + public boolean removeItem(MediaItem item) { + int itemIndex = mediaQueue.indexOf(item); + if (itemIndex == -1) { + return false; + } + concatenatingMediaSource.removeMediaSource(itemIndex); + if (currentPlayer == castPlayer) { + if (castPlayer.getPlaybackState() != Player.STATE_IDLE) { + Timeline castTimeline = castPlayer.getCurrentTimeline(); + if (castTimeline.getPeriodCount() <= itemIndex) { + return false; + } + castPlayer.removeItem((int) castTimeline.getPeriod(itemIndex, new Period()).id); + } + } + mediaQueue.remove(itemIndex); + if (itemIndex == currentItemIndex && itemIndex == mediaQueue.size()) { + maybeSetCurrentItemAndNotify(C.INDEX_UNSET); + } else if (itemIndex < currentItemIndex) { + maybeSetCurrentItemAndNotify(currentItemIndex - 1); + } + return true; + } + + /** + * Moves an item within the queue. + * + * @param item The item to move. + * @param toIndex The target index of the item in the queue. + * @return Whether the item move was successful. + */ + @Override + public boolean moveItem(MediaItem item, int toIndex) { + int fromIndex = mediaQueue.indexOf(item); + if (fromIndex == -1) { + return false; + } + // Player update. + concatenatingMediaSource.moveMediaSource(fromIndex, toIndex); + if (currentPlayer == castPlayer && castPlayer.getPlaybackState() != Player.STATE_IDLE) { + Timeline castTimeline = castPlayer.getCurrentTimeline(); + int periodCount = castTimeline.getPeriodCount(); + if (periodCount <= fromIndex || periodCount <= toIndex) { + return false; + } + int elementId = (int) castTimeline.getPeriod(fromIndex, new Period()).id; + castPlayer.moveItem(elementId, toIndex); + } + + mediaQueue.add(toIndex, mediaQueue.remove(fromIndex)); + + // Index update. + if (fromIndex == currentItemIndex) { + maybeSetCurrentItemAndNotify(toIndex); + } else if (fromIndex < currentItemIndex && toIndex >= currentItemIndex) { + maybeSetCurrentItemAndNotify(currentItemIndex - 1); + } else if (fromIndex > currentItemIndex && toIndex <= currentItemIndex) { + maybeSetCurrentItemAndNotify(currentItemIndex + 1); + } + + return true; + } + + /** + * Dispatches a given {@link KeyEvent} to the corresponding view of the current player. + * + * @param event The {@link KeyEvent}. + * @return Whether the event was handled by the target view. + */ + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (currentPlayer == exoPlayer) { + return localPlayerView.dispatchKeyEvent(event); + } else /* currentPlayer == castPlayer */ { + return castControlView.dispatchKeyEvent(event); + } + } + + /** Releases the manager and the players that it holds. */ + @Override + public void release() { + currentItemIndex = C.INDEX_UNSET; + mediaQueue.clear(); + concatenatingMediaSource.clear(); + castPlayer.setSessionAvailabilityListener(null); + castPlayer.release(); + localPlayerView.setPlayer(null); + exoPlayer.release(); + } + + // Player.EventListener implementation. + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + updateCurrentItemIndex(); + } + + @Override + public void onPositionDiscontinuity(@DiscontinuityReason int reason) { + updateCurrentItemIndex(); + } + + @Override + public void onTimelineChanged( + Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) { + updateCurrentItemIndex(); + if (currentPlayer == castPlayer && timeline.isEmpty()) { + castMediaQueueCreationPending = true; + } + } + + // CastPlayer.SessionAvailabilityListener implementation. + + @Override + public void onCastSessionAvailable() { + setCurrentPlayer(castPlayer); + } + + @Override + public void onCastSessionUnavailable() { + setCurrentPlayer(exoPlayer); + } + + // Internal methods. + + private void updateCurrentItemIndex() { + int playbackState = currentPlayer.getPlaybackState(); + maybeSetCurrentItemAndNotify( + playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED + ? currentPlayer.getCurrentWindowIndex() + : C.INDEX_UNSET); + } + + private void setCurrentPlayer(Player currentPlayer) { + if (this.currentPlayer == currentPlayer) { + return; + } + + // View management. + if (currentPlayer == exoPlayer) { + localPlayerView.setVisibility(View.VISIBLE); + castControlView.hide(); + } else /* currentPlayer == castPlayer */ { + localPlayerView.setVisibility(View.GONE); + castControlView.show(); + } + + // Player state management. + long playbackPositionMs = C.TIME_UNSET; + int windowIndex = C.INDEX_UNSET; + boolean playWhenReady = false; + if (this.currentPlayer != null) { + int playbackState = this.currentPlayer.getPlaybackState(); + if (playbackState != Player.STATE_ENDED) { + playbackPositionMs = this.currentPlayer.getCurrentPosition(); + playWhenReady = this.currentPlayer.getPlayWhenReady(); + windowIndex = this.currentPlayer.getCurrentWindowIndex(); + if (windowIndex != currentItemIndex) { + playbackPositionMs = C.TIME_UNSET; + windowIndex = currentItemIndex; + } + } + this.currentPlayer.stop(true); + } else { + // This is the initial setup. No need to save any state. + } + + this.currentPlayer = currentPlayer; + + // Media queue management. + castMediaQueueCreationPending = currentPlayer == castPlayer; + if (currentPlayer == exoPlayer) { + exoPlayer.prepare(concatenatingMediaSource); + } + + // Playback transition. + if (windowIndex != C.INDEX_UNSET) { + setCurrentItem(windowIndex, playbackPositionMs, playWhenReady); + } + } + + /** + * Starts playback of the item at the given position. + * + * @param itemIndex The index of the item to play. + * @param positionMs The position at which playback should start. + * @param playWhenReady Whether the player should proceed when ready to do so. + */ + private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) { + maybeSetCurrentItemAndNotify(itemIndex); + if (castMediaQueueCreationPending) { + MediaQueueItem[] items = new MediaQueueItem[mediaQueue.size()]; + for (int i = 0; i < items.length; i++) { + items[i] = buildMediaQueueItem(mediaQueue.get(i)); + } + castMediaQueueCreationPending = false; + castPlayer.loadItems(items, itemIndex, positionMs, Player.REPEAT_MODE_OFF); + } else { + currentPlayer.seekTo(itemIndex, positionMs); + currentPlayer.setPlayWhenReady(playWhenReady); + } + } + + private void maybeSetCurrentItemAndNotify(int currentItemIndex) { + if (this.currentItemIndex != currentItemIndex) { + int oldIndex = this.currentItemIndex; + this.currentItemIndex = currentItemIndex; + listener.onQueuePositionChanged(oldIndex, currentItemIndex); + } + } + + private static MediaSource buildMediaSource(MediaItem item) { + Uri uri = item.media.uri; + switch (item.mimeType) { + case DemoUtil.MIME_TYPE_SS: + return new SsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); + case DemoUtil.MIME_TYPE_DASH: + return new DashMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); + case DemoUtil.MIME_TYPE_HLS: + return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); + case DemoUtil.MIME_TYPE_VIDEO_MP4: + return new ProgressiveMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); + default: + { + throw new IllegalStateException("Unsupported type: " + item.mimeType); + } + } + } + + private static MediaQueueItem buildMediaQueueItem(MediaItem item) { + MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); + movieMetadata.putString(MediaMetadata.KEY_TITLE, item.title); + MediaInfo mediaInfo = + new MediaInfo.Builder(item.media.uri.toString()) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setContentType(item.mimeType) + .setMetadata(movieMetadata) + .build(); + return new MediaQueueItem.Builder(mediaInfo).build(); + } +} diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java index a69b32788d..efda40a913 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java @@ -39,6 +39,7 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.ext.cast.MediaItem; import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.ui.PlayerView; +import com.google.android.gms.cast.CastMediaControlIntent; import com.google.android.gms.cast.framework.CastButtonFactory; import com.google.android.gms.cast.framework.CastContext; import com.google.android.gms.dynamite.DynamiteModule; @@ -117,13 +118,29 @@ public class MainActivity extends AppCompatActivity // There is no Cast context to work with. Do nothing. return; } - playerManager = - new PlayerManager( - /* listener= */ this, - localPlayerView, - castControlView, - /* context= */ this, - castContext); + String applicationId = castContext.getCastOptions().getReceiverApplicationId(); + switch (applicationId) { + case CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID: + playerManager = + new DefaultReceiverPlayerManager( + /* listener= */ this, + localPlayerView, + castControlView, + /* context= */ this, + castContext); + break; + case ExoCastOptionsProvider.RECEIVER_ID: + playerManager = + new ExoCastPlayerManager( + /* listener= */ this, + localPlayerView, + castControlView, + /* context= */ this, + castContext); + break; + default: + throw new IllegalStateException("Illegal receiver app id: " + applicationId); + } mediaQueueList.setAdapter(mediaQueueListAdapter); } diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java index 28dd808fdf..c9a728b3ff 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,46 +15,15 @@ */ package com.google.android.exoplayer2.castdemo; -import android.content.Context; -import android.net.Uri; -import android.support.annotation.Nullable; import android.view.KeyEvent; -import android.view.View; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.DefaultRenderersFactory; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.Player.DiscontinuityReason; -import com.google.android.exoplayer2.Player.EventListener; -import com.google.android.exoplayer2.Player.TimelineChangeReason; -import com.google.android.exoplayer2.RenderersFactory; -import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.ext.cast.DefaultCastSessionManager; -import com.google.android.exoplayer2.ext.cast.ExoCastPlayer; import com.google.android.exoplayer2.ext.cast.MediaItem; -import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener; -import com.google.android.exoplayer2.source.ConcatenatingMediaSource; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; -import com.google.android.exoplayer2.source.dash.DashMediaSource; -import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.ui.PlayerControlView; -import com.google.android.exoplayer2.ui.PlayerView; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.Log; -import com.google.android.gms.cast.framework.CastContext; -import java.util.ArrayList; -/** Manages players and an internal media queue for the Cast demo app. */ -/* package */ class PlayerManager implements EventListener, SessionAvailabilityListener { +/** Manages the players in the Cast demo app. */ +/* package */ interface PlayerManager { /** Listener for events. */ - public interface Listener { + interface Listener { /** Called when the currently played item of the media queue changes. */ void onQueuePositionChanged(int previousIndex, int newIndex); @@ -66,361 +35,33 @@ import java.util.ArrayList; void onPlayerError(); } - private static final String TAG = "PlayerManager"; - private static final String USER_AGENT = "ExoCastDemoPlayer"; - private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY = - new DefaultHttpDataSourceFactory(USER_AGENT); + /** Redirects the given {@code keyEvent} to the active player. */ + boolean dispatchKeyEvent(KeyEvent keyEvent); - private final PlayerView localPlayerView; - private final PlayerControlView castControlView; - private final SimpleExoPlayer exoPlayer; - private final ExoCastPlayer exoCastPlayer; - private final ArrayList mediaQueue; - private final Listener listener; - private final ConcatenatingMediaSource concatenatingMediaSource; + /** Appends the given {@link MediaItem} to the media queue. */ + void addItem(MediaItem mediaItem); - private int currentItemIndex; - private Player currentPlayer; + /** Returns the number of items in the media queue. */ + int getMediaQueueSize(); + + /** Selects the item at the given position for playback. */ + void selectQueueItem(int position); /** - * Creates a new manager for {@link SimpleExoPlayer} and {@link ExoCastPlayer}. - * - * @param listener A {@link Listener}. - * @param localPlayerView The {@link PlayerView} for local playback. - * @param castControlView The {@link PlayerControlView} to control remote playback. - * @param context A {@link Context}. - * @param castContext The {@link CastContext}. + * Returns the position of the item currently being played, or {@link C#INDEX_UNSET} if no item is + * being played. */ - public PlayerManager( - Listener listener, - PlayerView localPlayerView, - PlayerControlView castControlView, - Context context, - CastContext castContext) { - this.listener = listener; - this.localPlayerView = localPlayerView; - this.castControlView = castControlView; - mediaQueue = new ArrayList<>(); - currentItemIndex = C.INDEX_UNSET; - concatenatingMediaSource = new ConcatenatingMediaSource(); + int getCurrentItemIndex(); - DefaultTrackSelector trackSelector = new DefaultTrackSelector(); - RenderersFactory renderersFactory = new DefaultRenderersFactory(context); - exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector); - exoPlayer.addListener(this); - localPlayerView.setPlayer(exoPlayer); + /** Returns the {@link MediaItem} at the given {@code position}. */ + MediaItem getItem(int position); - exoCastPlayer = - new ExoCastPlayer( - sessionManagerListener -> - new DefaultCastSessionManager(castContext, sessionManagerListener)); - exoCastPlayer.addListener(this); - exoCastPlayer.setSessionAvailabilityListener(this); - castControlView.setPlayer(exoCastPlayer); + /** Moves the item at position {@code from} to position {@code to}. */ + boolean moveItem(MediaItem item, int to); - setCurrentPlayer(exoCastPlayer.isCastSessionAvailable() ? exoCastPlayer : exoPlayer); - } + /** Removes the item at position {@code index}. */ + boolean removeItem(MediaItem item); - // Queue manipulation methods. - - /** - * Plays a specified queue item in the current player. - * - * @param itemIndex The index of the item to play. - */ - public void selectQueueItem(int itemIndex) { - setCurrentItem(itemIndex, C.TIME_UNSET, true); - } - - /** Returns the index of the currently played item. */ - public int getCurrentItemIndex() { - return currentItemIndex; - } - - /** - * Appends {@code item} to the media queue. - * - * @param item The {@link MediaItem} to append. - */ - public void addItem(MediaItem item) { - mediaQueue.add(item); - concatenatingMediaSource.addMediaSource(buildMediaSource(item)); - if (currentPlayer == exoCastPlayer) { - exoCastPlayer.addItemsToQueue(item); - } - } - - /** Returns the size of the media queue. */ - public int getMediaQueueSize() { - return mediaQueue.size(); - } - - /** - * Returns the item at the given index in the media queue. - * - * @param position The index of the item. - * @return The item at the given index in the media queue. - */ - public MediaItem getItem(int position) { - return mediaQueue.get(position); - } - - /** - * Removes the item at the given index from the media queue. - * - * @param item The item to remove. - * @return Whether the removal was successful. - */ - public boolean removeItem(MediaItem item) { - int itemIndex = mediaQueue.indexOf(item); - if (itemIndex == -1) { - // This may happen if another sender app removes items while this sender app is in "swiping - // an item" state. - return false; - } - concatenatingMediaSource.removeMediaSource(itemIndex); - mediaQueue.remove(itemIndex); - if (currentPlayer == exoCastPlayer) { - exoCastPlayer.removeItemFromQueue(itemIndex); - } - if (itemIndex == currentItemIndex && itemIndex == mediaQueue.size()) { - maybeSetCurrentItemAndNotify(C.INDEX_UNSET); - } else if (itemIndex < currentItemIndex) { - maybeSetCurrentItemAndNotify(currentItemIndex - 1); - } - return true; - } - - /** - * Moves an item within the queue. - * - * @param item The item to move. This method does nothing if {@code item} is not contained in the - * queue. - * @param toIndex The target index of the item in the queue. If {@code toIndex} exceeds the last - * position in the queue, {@code toIndex} is clamped to match the largest possible value. - * @return True if {@code item} was contained in the queue, and {@code toIndex} was a valid - * position. False otherwise. - */ - public boolean moveItem(MediaItem item, int toIndex) { - int indexOfItem = mediaQueue.indexOf(item); - if (indexOfItem == -1) { - // This may happen if another sender app removes items while this sender app is in "dragging - // an item" state. - return false; - } - int clampedToIndex = Math.min(toIndex, mediaQueue.size() - 1); - mediaQueue.add(clampedToIndex, mediaQueue.remove(indexOfItem)); - concatenatingMediaSource.moveMediaSource(indexOfItem, clampedToIndex); - if (currentPlayer == exoCastPlayer) { - exoCastPlayer.moveItemInQueue(indexOfItem, clampedToIndex); - } - // Index update. - maybeSetCurrentItemAndNotify(currentPlayer.getCurrentWindowIndex()); - return clampedToIndex == toIndex; - } - - // Miscellaneous methods. - - public boolean dispatchKeyEvent(KeyEvent event) { - if (currentPlayer == exoPlayer) { - return localPlayerView.dispatchKeyEvent(event); - } else /* currentPlayer == exoCastPlayer */ { - return castControlView.dispatchKeyEvent(event); - } - } - - /** Releases the manager and the players that it holds. */ - public void release() { - currentItemIndex = C.INDEX_UNSET; - mediaQueue.clear(); - concatenatingMediaSource.clear(); - exoCastPlayer.setSessionAvailabilityListener(null); - exoCastPlayer.release(); - localPlayerView.setPlayer(null); - exoPlayer.release(); - } - - // Player.EventListener implementation. - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - updateCurrentItemIndex(); - } - - @Override - public void onPositionDiscontinuity(@DiscontinuityReason int reason) { - updateCurrentItemIndex(); - } - - @Override - public void onTimelineChanged( - Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) { - if (currentPlayer == exoCastPlayer && reason != Player.TIMELINE_CHANGE_REASON_RESET) { - maybeUpdateLocalQueueWithRemoteQueueAndNotify(); - } - updateCurrentItemIndex(); - } - - @Override - public void onPlayerError(ExoPlaybackException error) { - Log.e(TAG, "The player encountered an error.", error); - listener.onPlayerError(); - } - - // CastPlayer.SessionAvailabilityListener implementation. - - @Override - public void onCastSessionAvailable() { - setCurrentPlayer(exoCastPlayer); - } - - @Override - public void onCastSessionUnavailable() { - setCurrentPlayer(exoPlayer); - } - - // Internal methods. - - private void maybeUpdateLocalQueueWithRemoteQueueAndNotify() { - Assertions.checkState(currentPlayer == exoCastPlayer); - boolean mediaQueuesMatch = mediaQueue.size() == exoCastPlayer.getQueueSize(); - for (int i = 0; mediaQueuesMatch && i < mediaQueue.size(); i++) { - mediaQueuesMatch = mediaQueue.get(i).uuid.equals(exoCastPlayer.getQueueItem(i).uuid); - } - if (mediaQueuesMatch) { - // The media queues match. Do nothing. - return; - } - mediaQueue.clear(); - concatenatingMediaSource.clear(); - for (int i = 0; i < exoCastPlayer.getQueueSize(); i++) { - MediaItem item = exoCastPlayer.getQueueItem(i); - mediaQueue.add(item); - concatenatingMediaSource.addMediaSource(buildMediaSource(item)); - } - listener.onQueueContentsExternallyChanged(); - } - - private void updateCurrentItemIndex() { - int playbackState = currentPlayer.getPlaybackState(); - maybeSetCurrentItemAndNotify( - playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED - ? currentPlayer.getCurrentWindowIndex() - : C.INDEX_UNSET); - } - - private void setCurrentPlayer(Player currentPlayer) { - if (this.currentPlayer == currentPlayer) { - return; - } - - // View management. - if (currentPlayer == exoPlayer) { - localPlayerView.setVisibility(View.VISIBLE); - castControlView.hide(); - } else /* currentPlayer == exoCastPlayer */ { - localPlayerView.setVisibility(View.GONE); - castControlView.show(); - } - - // Player state management. - long playbackPositionMs = C.TIME_UNSET; - int windowIndex = C.INDEX_UNSET; - boolean playWhenReady = false; - if (this.currentPlayer != null) { - int playbackState = this.currentPlayer.getPlaybackState(); - if (playbackState != Player.STATE_ENDED) { - playbackPositionMs = this.currentPlayer.getCurrentPosition(); - playWhenReady = this.currentPlayer.getPlayWhenReady(); - windowIndex = this.currentPlayer.getCurrentWindowIndex(); - if (windowIndex != currentItemIndex) { - playbackPositionMs = C.TIME_UNSET; - windowIndex = currentItemIndex; - } - } - this.currentPlayer.stop(true); - } else { - // This is the initial setup. No need to save any state. - } - - this.currentPlayer = currentPlayer; - - // Media queue management. - boolean shouldSeekInNewCurrentPlayer; - if (currentPlayer == exoPlayer) { - exoPlayer.prepare(concatenatingMediaSource); - shouldSeekInNewCurrentPlayer = true; - } else /* currentPlayer == exoCastPlayer */ { - if (exoCastPlayer.getPlaybackState() == Player.STATE_IDLE) { - exoCastPlayer.prepare(); - } - if (mediaQueue.isEmpty()) { - // Casting started with no local queue. We take the receiver app's queue as our own. - maybeUpdateLocalQueueWithRemoteQueueAndNotify(); - shouldSeekInNewCurrentPlayer = false; - } else { - // Casting started when the sender app had no queue. We just load our items into the - // receiver app's queue. If the receiver had no items in its queue, we also seek to wherever - // the sender app was playing. - int currentExoCastPlayerState = exoCastPlayer.getPlaybackState(); - shouldSeekInNewCurrentPlayer = - currentExoCastPlayerState == Player.STATE_IDLE - || currentExoCastPlayerState == Player.STATE_ENDED; - exoCastPlayer.addItemsToQueue(mediaQueue.toArray(new MediaItem[0])); - } - } - - // Playback transition. - if (shouldSeekInNewCurrentPlayer && windowIndex != C.INDEX_UNSET) { - setCurrentItem(windowIndex, playbackPositionMs, playWhenReady); - } else if (getMediaQueueSize() > 0) { - maybeSetCurrentItemAndNotify(currentPlayer.getCurrentWindowIndex()); - } - } - - /** - * Starts playback of the item at the given position. - * - * @param itemIndex The index of the item to play. - * @param positionMs The position at which playback should start. - * @param playWhenReady Whether the player should proceed when ready to do so. - */ - private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) { - maybeSetCurrentItemAndNotify(itemIndex); - currentPlayer.seekTo(itemIndex, positionMs); - if (currentPlayer.getPlaybackState() == Player.STATE_IDLE) { - if (currentPlayer == exoCastPlayer) { - exoCastPlayer.prepare(); - } else { - exoPlayer.prepare(concatenatingMediaSource); - } - } - currentPlayer.setPlayWhenReady(playWhenReady); - } - - private void maybeSetCurrentItemAndNotify(int currentItemIndex) { - if (this.currentItemIndex != currentItemIndex) { - int oldIndex = this.currentItemIndex; - this.currentItemIndex = currentItemIndex; - listener.onQueuePositionChanged(oldIndex, currentItemIndex); - } - } - - private static MediaSource buildMediaSource(MediaItem item) { - Uri uri = item.media.uri; - switch (item.mimeType) { - case DemoUtil.MIME_TYPE_SS: - return new SsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); - case DemoUtil.MIME_TYPE_DASH: - return new DashMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); - case DemoUtil.MIME_TYPE_HLS: - return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); - case DemoUtil.MIME_TYPE_VIDEO_MP4: - return new ProgressiveMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); - default: - { - throw new IllegalStateException("Unsupported type: " + item.mimeType); - } - } - } + /** Releases any acquired resources. */ + void release(); } From 73d9b2d21a3bc28b84403157609368f51dfcedd1 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 13 Feb 2019 19:21:23 +0000 Subject: [PATCH 535/832] Hide ExoCastPlayer components again This CL is effectively a rollback of cr/233072247. PiperOrigin-RevId: 233793032 --- RELEASENOTES.md | 4 ---- demos/cast/build.gradle | 5 ----- 2 files changed, 9 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7a7decfbc0..b65ea50c08 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,10 +2,6 @@ ### dev-v2 (not yet released) ### -* Add `ExoCastPlayer` to cast to custom receiver apps using ExoPlayer's custom - communication protocol - ([#5486](https://github.com/google/ExoPlayer/issues/5486)). Also update the - Cast demo app to work with `ExoCastPlayer`. * HLS: * Form an adaptive track group out of audio renditions with matching name. * DASH: diff --git a/demos/cast/build.gradle b/demos/cast/build.gradle index 49a5e80985..48a9b5da6f 100644 --- a/demos/cast/build.gradle +++ b/demos/cast/build.gradle @@ -51,11 +51,6 @@ android { flavorDimensions "receiver" productFlavors { - exoCast { - dimension "receiver" - manifestPlaceholders = - [castOptionsProvider: "com.google.android.exoplayer2.ext.cast.ExoCastOptionsProvider"] - } defaultCast { dimension "receiver" manifestPlaceholders = From 1c38b226ea547783f63bd1732549e198a56f6878 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 14 Feb 2019 14:35:27 +0000 Subject: [PATCH 536/832] Stop using AlertDialog for TrackSelectionDialog. AlertDialog owns its view, which causes problems with TabLayout on API 21 and 22. Now using AppCompatDialog instead so that we can own the view ourselves. Also: - Renamed layout files from download_xyz to track_selection_xyz. - Added OK and Cancel buttons to the view. - Applied alert dialog style to the "normal" dialog to get a nicer UI. PiperOrigin-RevId: 233944647 --- .../exoplayer2/demo/TrackSelectionDialog.java | 36 +++++++++----- .../src/main/res/layout/download_dialog.xml | 28 ----------- .../res/layout/track_selection_dialog.xml | 48 +++++++++++++++++++ ...tab.xml => track_selection_dialog_tab.xml} | 0 4 files changed, 73 insertions(+), 39 deletions(-) delete mode 100644 demos/main/src/main/res/layout/download_dialog.xml create mode 100644 demos/main/src/main/res/layout/track_selection_dialog.xml rename demos/main/src/main/res/layout/{download_dialog_tab.xml => track_selection_dialog_tab.xml} (100%) diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java index 39e16b2ae1..6be8cf6a77 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionDialog.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.demo; -import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.res.Resources; @@ -27,10 +26,13 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatDialog; import android.util.SparseArray; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -162,11 +164,15 @@ public final class TrackSelectionDialog extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - return new AlertDialog.Builder(getActivity()) - .setTitle(titleId) - .setPositiveButton(android.R.string.ok, onClickListener) - .setNegativeButton(android.R.string.cancel, /* listener= */ null) - .create(); + // We need to own the view to let tab layout work correctly on all API levels. That's why we + // can't use an AlertDialog as it owns its view itself. Using AppCompatDialog instead. We still + // want the "alertDialogTheme" style attribute of the current theme instead of AppCompatDialog's + // default "dialogTheme" style, so obtain that manually. + TypedValue alertDialogStyle = new TypedValue(); + getActivity().getTheme().resolveAttribute(R.attr.alertDialogTheme, alertDialogStyle, true); + AppCompatDialog dialog = new AppCompatDialog(getActivity(), alertDialogStyle.resourceId); + dialog.setTitle(titleId); + return dialog; } @Override @@ -200,12 +206,19 @@ public final class TrackSelectionDialog extends DialogFragment { tabFragments.put(i, tabFragment); tabTitles.add(trackTypeString); } - View dialogView = inflater.inflate(R.layout.download_dialog, container, false); - TabLayout tabLayout = dialogView.findViewById(R.id.download_dialog_tab_layout); - ViewPager viewPager = dialogView.findViewById(R.id.download_dialog_view_pager); + View dialogView = inflater.inflate(R.layout.track_selection_dialog, container, false); + TabLayout tabLayout = dialogView.findViewById(R.id.track_selection_dialog_tab_layout); + ViewPager viewPager = dialogView.findViewById(R.id.track_selection_dialog_view_pager); + Button cancelButton = dialogView.findViewById(R.id.track_selection_dialog_cancel_button); + Button okButton = dialogView.findViewById(R.id.track_selection_dialog_ok_button); viewPager.setAdapter(new FragmentAdapter(getChildFragmentManager())); tabLayout.setupWithViewPager(viewPager); - ((AlertDialog) getDialog()).setView(dialogView); + cancelButton.setOnClickListener(view -> dismiss()); + okButton.setOnClickListener( + view -> { + onClickListener.onClick(getDialog(), DialogInterface.BUTTON_POSITIVE); + dismiss(); + }); return dialogView; } @@ -281,7 +294,8 @@ public final class TrackSelectionDialog extends DialogFragment { @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View rootView = - inflater.inflate(R.layout.download_dialog_tab, container, /* attachToRoot= */ false); + inflater.inflate( + R.layout.track_selection_dialog_tab, container, /* attachToRoot= */ false); trackSelectionView = rootView.findViewById(R.id.download_dialog_track_selection_view); trackSelectionView.setShowDisableOption(true); trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides); diff --git a/demos/main/src/main/res/layout/download_dialog.xml b/demos/main/src/main/res/layout/download_dialog.xml deleted file mode 100644 index 25819c7ff5..0000000000 --- a/demos/main/src/main/res/layout/download_dialog.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - diff --git a/demos/main/src/main/res/layout/track_selection_dialog.xml b/demos/main/src/main/res/layout/track_selection_dialog.xml new file mode 100644 index 0000000000..0a5cd480b3 --- /dev/null +++ b/demos/main/src/main/res/layout/track_selection_dialog.xml @@ -0,0 +1,48 @@ + + + + + + + +