Commit 1a5b304b by Oliver Woodman Committed by GitHub

Merge pull request #7517 from google/dev-v2-r2.11.6

r2.11.6
parents 6026b919 67c99e1d
# Release notes #
### 2.11.6 (2020-06-24) ###
* UI: Prevent `PlayerView` from temporarily hiding the video surface when
seeking to an unprepared period within the current window. For example when
seeking over an ad group, or to the next period in a multi-period DASH
stream ([#5507](https://github.com/google/ExoPlayer/issues/5507)).
* IMA extension:
* Add option to skip ads before the start position.
* Catch unexpected errors in `stopAd` to avoid a crash
([#7492](https://github.com/google/ExoPlayer/issues/7492)).
* Fix a bug that caused playback to be stuck buffering on resuming from
the background after all ads had played to the end
([#7508](https://github.com/google/ExoPlayer/issues/7508)).
* Fix a bug where the number of ads in an ad group couldn't change
([#7477](https://github.com/google/ExoPlayer/issues/7477)).
* Work around unexpected `pauseAd`/`stopAd` for ads that have preloaded
on seeking to another position
([#7492](https://github.com/google/ExoPlayer/issues/7492)).
* Fix incorrect rounding of ad cue points.
* Fix handling of postrolls preloading
([#7518](https://github.com/google/ExoPlayer/issues/7518)).
### 2.11.5 (2020-06-05) ###
* Improve the smoothness of video playback immediately after starting, seeking
......@@ -16,10 +38,10 @@
([#7306](https://github.com/google/ExoPlayer/issues/7306)).
* Fix issue in `AudioTrackPositionTracker` that could cause negative positions
to be reported at the start of playback and immediately after seeking
([#7456](https://github.com/google/ExoPlayer/issues/7456).
([#7456](https://github.com/google/ExoPlayer/issues/7456)).
* Fix further cases where downloads would sometimes not resume after their
network requirements are met
([#7453](https://github.com/google/ExoPlayer/issues/7453).
([#7453](https://github.com/google/ExoPlayer/issues/7453)).
* DASH:
* Merge trick play adaptation sets (i.e., adaptation sets marked with
`http://dashif.org/guidelines/trickmode`) into the same `TrackGroup` as
......@@ -99,10 +121,10 @@
`DefaultAudioSink` constructor
([#7134](https://github.com/google/ExoPlayer/issues/7134)).
* Workaround issue that could cause slower than realtime playback of AAC on
Android 10 ([#6671](https://github.com/google/ExoPlayer/issues/6671).
Android 10 ([#6671](https://github.com/google/ExoPlayer/issues/6671)).
* Fix case where another app spuriously holding transient audio focus could
prevent ExoPlayer from acquiring audio focus for an indefinite period of
time ([#7182](https://github.com/google/ExoPlayer/issues/7182).
time ([#7182](https://github.com/google/ExoPlayer/issues/7182)).
* Fix case where the player volume could be permanently ducked if audio focus
was released whilst ducking.
* Fix playback of WAV files with trailing non-media bytes
......@@ -1022,7 +1044,7 @@
([#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).
([#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)).
......@@ -1439,7 +1461,7 @@
resources when the playback thread has quit by the time the loading task has
completed.
* ID3: Better handle malformed ID3 data
([#3792](https://github.com/google/ExoPlayer/issues/3792).
([#3792](https://github.com/google/ExoPlayer/issues/3792)).
* Support 14-bit mode and little endianness in DTS PES packets
([#3340](https://github.com/google/ExoPlayer/issues/3340)).
* Demo app: Add ability to download not DRM protected content.
......
......@@ -13,8 +13,8 @@
// limitations under the License.
project.ext {
// ExoPlayer version and version code.
releaseVersion = '2.11.5'
releaseVersionCode = 2011005
releaseVersion = '2.11.6'
releaseVersionCode = 2011006
minSdkVersion = 16
appTargetSdkVersion = 29
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved
......
......@@ -476,6 +476,11 @@
"name": "VMAP full, empty, full midrolls",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://vastsynthesizer.appspot.com/empty-midroll-2"
},
{
"name": "VMAP midroll at 1765 s",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/frame-counter-one-hour.mp4",
"ad_tag_uri": "https://vastsynthesizer.appspot.com/midroll-large"
}
]
},
......
/*
* Copyright (C) 2020 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.ima;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import java.util.Arrays;
import java.util.List;
/**
* Static utility class for constructing {@link AdPlaybackState} instances from IMA-specific data.
*/
/* package */ final class AdPlaybackStateFactory {
private AdPlaybackStateFactory() {}
/**
* Construct an {@link AdPlaybackState} from the provided {@code cuePoints}.
*
* @param cuePoints The cue points of the ads in seconds.
* @return The {@link AdPlaybackState}.
*/
public static AdPlaybackState fromCuePoints(List<Float> cuePoints) {
if (cuePoints.isEmpty()) {
// If no cue points are specified, there is a preroll ad.
return new AdPlaybackState(/* adGroupTimesUs...= */ 0);
}
int count = cuePoints.size();
long[] adGroupTimesUs = new long[count];
int adGroupIndex = 0;
for (int i = 0; i < count; i++) {
double cuePoint = cuePoints.get(i);
if (cuePoint == -1.0) {
adGroupTimesUs[count - 1] = C.TIME_END_OF_SOURCE;
} else {
adGroupTimesUs[adGroupIndex++] = Math.round(C.MICROS_PER_SECOND * cuePoint);
}
}
// Cue points may be out of order, so sort them.
Arrays.sort(adGroupTimesUs, 0, adGroupIndex);
return new AdPlaybackState(adGroupTimesUs);
}
}
......@@ -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.11.5";
public static final String VERSION = "2.11.6";
/** 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.11.5";
public static final String VERSION_SLASHY = "ExoPlayerLib/2.11.6";
/**
* 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 = 2011005;
public static final int VERSION_INT = 2011006;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
......
......@@ -124,13 +124,9 @@ public final class AdPlaybackState {
return result;
}
/**
* Returns a new instance with the ad count set to {@code count}. This method may only be called
* if this instance's ad count has not yet been specified.
*/
/** Returns a new instance with the ad count set to {@code count}. */
@CheckResult
public AdGroup withAdCount(int count) {
Assertions.checkArgument(this.count == C.LENGTH_UNSET && states.length <= count);
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, count);
long[] durationsUs = copyDurationsUsWithSpaceForAdCount(this.durationsUs, count);
@NullableType Uri[] uris = Arrays.copyOf(this.uris, count);
......@@ -139,17 +135,11 @@ public final class AdPlaybackState {
/**
* Returns a new instance with the specified {@code uri} set for the specified ad, and the ad
* marked as {@link #AD_STATE_AVAILABLE}. The specified ad must currently be in {@link
* #AD_STATE_UNAVAILABLE}, which is the default state.
*
* <p>This instance's ad count may be unknown, in which case {@code index} must be less than the
* ad count specified later. Otherwise, {@code index} must be less than the current ad count.
* marked as {@link #AD_STATE_AVAILABLE}.
*/
@CheckResult
public AdGroup withAdUri(Uri uri, int index) {
Assertions.checkArgument(count == C.LENGTH_UNSET || index < count);
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, index + 1);
Assertions.checkArgument(states[index] == AD_STATE_UNAVAILABLE);
long[] durationsUs =
this.durationsUs.length == states.length
? this.durationsUs
......@@ -280,7 +270,9 @@ public final class AdPlaybackState {
public final AdGroup[] adGroups;
/** The position offset in the first unplayed ad at which to begin playback, in microseconds. */
public final long adResumePositionUs;
/** The content duration in microseconds, if known. {@link C#TIME_UNSET} otherwise. */
/**
* The duration of the content period in microseconds, if known. {@link C#TIME_UNSET} otherwise.
*/
public final long contentDurationUs;
/**
......@@ -489,6 +481,54 @@ public final class AdPlaybackState {
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("AdPlaybackState(adResumePositionUs=");
sb.append(adResumePositionUs);
sb.append(", adGroups=[");
for (int i = 0; i < adGroups.length; i++) {
sb.append("adGroup(timeUs=");
sb.append(adGroupTimesUs[i]);
sb.append(", ads=[");
for (int j = 0; j < adGroups[i].states.length; j++) {
sb.append("ad(state=");
switch (adGroups[i].states[j]) {
case AD_STATE_UNAVAILABLE:
sb.append('_');
break;
case AD_STATE_ERROR:
sb.append('!');
break;
case AD_STATE_AVAILABLE:
sb.append('R');
break;
case AD_STATE_PLAYED:
sb.append('P');
break;
case AD_STATE_SKIPPED:
sb.append('S');
break;
default:
sb.append('?');
break;
}
sb.append(", durationUs=");
sb.append(adGroups[i].durationsUs[j]);
sb.append(')');
if (j < adGroups[i].states.length - 1) {
sb.append(", ");
}
}
sb.append("])");
if (i < adGroups.length - 1) {
sb.append(", ");
}
}
sb.append("])");
return sb.toString();
}
private boolean isPositionBeforeAdGroup(
long positionUs, long periodDurationUs, int adGroupIndex) {
if (positionUs == C.TIME_END_OF_SOURCE) {
......
......@@ -44,23 +44,16 @@ public final class SinglePeriodAdTimeline extends ForwardingTimeline {
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
timeline.getPeriod(periodIndex, period, setIds);
long durationUs =
period.durationUs == C.TIME_UNSET ? adPlaybackState.contentDurationUs : period.durationUs;
period.set(
period.id,
period.uid,
period.windowIndex,
period.durationUs,
durationUs,
period.getPositionInWindowUs(),
adPlaybackState);
return period;
}
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
window = super.getWindow(windowIndex, window, defaultPositionProjectionUs);
if (window.durationUs == C.TIME_UNSET) {
window.durationUs = adPlaybackState.contentDurationUs;
}
return window;
}
}
......@@ -155,12 +155,22 @@ public final class MimeTypes {
if (mimeType == null) {
return false;
}
// TODO: Consider adding additional audio MIME types here.
// TODO: Add additional audio MIME types. Also consider evaluating based on Format rather than
// just MIME type, since in some cases the property is true for a subset of the profiles
// belonging to a single MIME type. If we do this, we should move the method to a different
// class. See [Internal ref: http://go/exo-audio-format-random-access].
switch (mimeType) {
case AUDIO_AAC:
case AUDIO_MPEG:
case AUDIO_MPEG_L1:
case AUDIO_MPEG_L2:
case AUDIO_RAW:
case AUDIO_ALAW:
case AUDIO_MLAW:
case AUDIO_OPUS:
case AUDIO_FLAC:
case AUDIO_AC3:
case AUDIO_E_AC3:
case AUDIO_E_AC3_JOC:
return true;
default:
return false;
......
......@@ -48,6 +48,8 @@ import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlaybackPreparer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.DiscontinuityReason;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.flac.PictureFrame;
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
......@@ -1506,6 +1508,13 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
SingleTapListener,
PlayerControlView.VisibilityListener {
private final Period period;
private @Nullable Object lastPeriodUidWithTracks;
public ComponentListener() {
period = new Period();
}
// TextOutput implementation
@Override
......@@ -1554,6 +1563,29 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
@Override
public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) {
// Suppress the update if transitioning to an unprepared period within the same window. This
// is necessary to avoid closing the shutter when such a transition occurs. See:
// https://github.com/google/ExoPlayer/issues/5507.
Player player = Assertions.checkNotNull(PlayerView.this.player);
Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) {
lastPeriodUidWithTracks = null;
} else if (!player.getCurrentTrackGroups().isEmpty()) {
lastPeriodUidWithTracks =
timeline.getPeriod(player.getCurrentPeriodIndex(), period, /* setIds= */ true).uid;
} else if (lastPeriodUidWithTracks != null) {
int lastPeriodIndexWithTracks = timeline.getIndexOfPeriod(lastPeriodUidWithTracks);
if (lastPeriodIndexWithTracks != C.INDEX_UNSET) {
int lastWindowIndexWithTracks =
timeline.getPeriod(lastPeriodIndexWithTracks, period).windowIndex;
if (player.getCurrentWindowIndex() == lastWindowIndexWithTracks) {
// We're in the same window. Suppress the update.
return;
}
}
lastPeriodUidWithTracks = null;
}
updateForCurrentTrackSelections(/* isNewPlayer= */ false);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment