Skip to content

Commit

Permalink
Merge pull request #8582 from google/dev-v2-r2.13.1
Browse files Browse the repository at this point in the history
r2.13.1
  • Loading branch information
ojw28 authored Feb 13, 2021
2 parents b100094 + 5807d2e commit 4b1e0fa
Show file tree
Hide file tree
Showing 18 changed files with 327 additions and 122 deletions.
28 changes: 28 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
# Release notes

### 2.13.1 (2021-02-12)

* Live streaming:
* Fix playback issue for HLS live streams without program date time
information ([#8560](https://github.com/google/ExoPlayer/issues/8560)).
* Fix playback issue for multi-period DASH live streams
([#8537](https://github.com/google/ExoPlayer/issues/8537)).
* Fix playback failures when playing live streams with video tunneling
enabled ([#8570](https://github.com/google/ExoPlayer/issues/8570)).
* IMA extension:
* Fix handling of repeated ad loads, to avoid ads being discarded if the
user seeks away and then back to a preloaded postroll (for example).
* Fix a bug where an assertion would fail if the player started to buffer
an ad media period before the ad URI was known then an ad state update
arrived that didn't set the ad URI.
* Add `ImaAdsLoader.focusSkipButton` to allow apps to request that the
skip button should receive UI focus, if shown
([#8565](https://github.com/google/ExoPlayer/issues/8565)).
* DRM:
* Re-use the previous `DrmSessionManager` instance when playing a playlist
(if possible)
([#8523](https://github.com/google/ExoPlayer/issues/8523)).
* Propagate DRM configuration when creating media sources for ad content
([#8568](https://github.com/google/ExoPlayer/issues/8568)).
* Only release 'keepalive' references to `DrmSession` in
`DefaultDrmSessionManager#release()` if keepalive is enabled
([#8576](https://github.com/google/ExoPlayer/issues/8576)).

### 2.13.0 (2021-02-04)

* Core library:
Expand Down
4 changes: 2 additions & 2 deletions constants.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
// limitations under the License.
project.ext {
// ExoPlayer version and version code.
releaseVersion = '2.13.0'
releaseVersionCode = 2013000
releaseVersion = '2.13.1'
releaseVersionCode = 2013001
minSdkVersion = 16
appTargetSdkVersion = 29
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,16 @@ public void skipAd() {
}
}

/**
* Moves UI focus to the skip button (or other interactive elements), if currently shown. See
* {@link AdsManager#focus()}.
*/
public void focusSkipButton() {
if (adsManager != null) {
adsManager.focus();
}
}

/**
* Starts passing events from this instance (including any pending ad playback state) and
* registers obstructions.
Expand Down Expand Up @@ -879,7 +889,8 @@ private void loadAdInternal(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) {
int adGroupIndex = getAdGroupIndexForAdPod(adPodInfo);
int adIndexInAdGroup = adPodInfo.getAdPosition() - 1;
AdInfo adInfo = new AdInfo(adGroupIndex, adIndexInAdGroup);
adInfoByAdMediaInfo.put(adMediaInfo, adInfo);
// The ad URI may already be known, so force put to update it if needed.
adInfoByAdMediaInfo.forcePut(adMediaInfo, adInfo);
if (configuration.debugModeEnabled) {
Log.d(TAG, "loadAd " + getAdMediaInfoString(adMediaInfo));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,16 @@ public void skipAd() {
}
}

/**
* Moves UI focus to the skip button (or other interactive elements), if currently shown. See
* {@link AdsManager#focus()}.
*/
public void focusSkipButton() {
if (currentAdTagLoader != null) {
currentAdTagLoader.focusSkipButton();
}
}

// AdsLoader implementation.

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,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.13.0";
public static final String VERSION = "2.13.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.13.0";
public static final String VERSION_SLASHY = "ExoPlayerLib/2.13.1";

/**
* The version of the library expressed as an integer, for example 1002003.
Expand All @@ -44,7 +44,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 = 2013000;
public static final int VERSION_INT = 2013001;

/**
* The default user agent for requests made by the library.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,10 @@ public AdGroup withAdState(@AdState int state, int index) {
/** Returns a new instance with the specified ad durations, in microseconds. */
@CheckResult
public AdGroup withAdDurationsUs(long[] durationsUs) {
Assertions.checkArgument(count == C.LENGTH_UNSET || durationsUs.length <= this.uris.length);
if (durationsUs.length < this.uris.length) {
if (durationsUs.length < uris.length) {
durationsUs = copyDurationsUsWithSpaceForAdCount(durationsUs, uris.length);
} else if (count != C.LENGTH_UNSET && durationsUs.length > uris.length) {
durationsUs = Arrays.copyOf(durationsUs, uris.length);
}
return new AdGroup(count, states, uris, durationsUs);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ private void updatePlaybackPositions() throws ExoPlaybackException {
// Adjust live playback speed to new position.
if (playbackInfo.playWhenReady
&& playbackInfo.playbackState == Player.STATE_READY
&& isCurrentPeriodInMovingLiveWindow()
&& shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, playbackInfo.periodId)
&& playbackInfo.playbackParameters.speed == 1f) {
float adjustedSpeed =
livePlaybackSpeedControl.getAdjustedPlaybackSpeed(
Expand Down Expand Up @@ -1051,17 +1051,14 @@ private long getLiveOffsetUs(Timeline timeline, Object periodUid, long periodPos
- (periodPositionUs + period.getPositionInWindowUs());
}

private boolean isCurrentPeriodInMovingLiveWindow() {
return isInMovingLiveWindow(playbackInfo.timeline, playbackInfo.periodId);
}

private boolean isInMovingLiveWindow(Timeline timeline, MediaPeriodId mediaPeriodId) {
private boolean shouldUseLivePlaybackSpeedControl(
Timeline timeline, MediaPeriodId mediaPeriodId) {
if (mediaPeriodId.isAd() || timeline.isEmpty()) {
return false;
}
int windowIndex = timeline.getPeriodByUid(mediaPeriodId.periodUid, period).windowIndex;
timeline.getWindow(windowIndex, window);
return window.isLive() && window.isDynamic;
return window.isLive() && window.isDynamic && window.windowStartTimeMs != C.TIME_UNSET;
}

private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) {
Expand Down Expand Up @@ -1725,7 +1722,7 @@ private boolean shouldTransitionToReadyState(boolean renderersReadyOrEnded) {
}
// Renderers are ready and we're loading. Ask the LoadControl whether to transition.
long targetLiveOffsetUs =
isInMovingLiveWindow(playbackInfo.timeline, queue.getPlayingPeriod().info.id)
shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, queue.getPlayingPeriod().info.id)
? livePlaybackSpeedControl.getTargetLiveOffsetUs()
: C.TIME_UNSET;
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
Expand Down Expand Up @@ -1831,7 +1828,7 @@ private void updateLivePlaybackSpeedControl(
Timeline oldTimeline,
MediaPeriodId oldPeriodId,
long positionForTargetOffsetOverrideUs) {
if (newTimeline.isEmpty() || !isInMovingLiveWindow(newTimeline, newPeriodId)) {
if (newTimeline.isEmpty() || !shouldUseLivePlaybackSpeedControl(newTimeline, newPeriodId)) {
// Live playback speed control is unused.
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,6 @@ public void configure(Format inputFormat, int specifiedBufferSize, @Nullable int
throws ConfigurationException {
int inputPcmFrameSize;
@Nullable AudioProcessor[] availableAudioProcessors;
boolean canApplyPlaybackParameters;

@OutputMode int outputMode;
@C.Encoding int outputEncoding;
Expand All @@ -500,11 +499,10 @@ public void configure(Format inputFormat, int specifiedBufferSize, @Nullable int
Assertions.checkArgument(Util.isEncodingLinearPcm(inputFormat.pcmEncoding));

inputPcmFrameSize = Util.getPcmFrameSize(inputFormat.pcmEncoding, inputFormat.channelCount);
boolean useFloatOutput =
enableFloatOutput && Util.isEncodingHighResolutionPcm(inputFormat.pcmEncoding);
availableAudioProcessors =
useFloatOutput ? toFloatPcmAvailableAudioProcessors : toIntPcmAvailableAudioProcessors;
canApplyPlaybackParameters = !useFloatOutput;
shouldUseFloatOutput(inputFormat.pcmEncoding)
? toFloatPcmAvailableAudioProcessors
: toIntPcmAvailableAudioProcessors;

trimmingAudioProcessor.setTrimFrameCount(
inputFormat.encoderDelay, inputFormat.encoderPadding);
Expand Down Expand Up @@ -541,7 +539,6 @@ public void configure(Format inputFormat, int specifiedBufferSize, @Nullable int
} else {
inputPcmFrameSize = C.LENGTH_UNSET;
availableAudioProcessors = new AudioProcessor[0];
canApplyPlaybackParameters = false;
outputSampleRate = inputFormat.sampleRate;
outputPcmFrameSize = C.LENGTH_UNSET;
if (enableOffload && isOffloadedPlaybackSupported(inputFormat, audioAttributes)) {
Expand Down Expand Up @@ -586,7 +583,6 @@ public void configure(Format inputFormat, int specifiedBufferSize, @Nullable int
outputEncoding,
specifiedBufferSize,
enableAudioTrackPlaybackParams,
canApplyPlaybackParameters,
availableAudioProcessors);
if (isAudioTrackInitialized()) {
this.pendingConfiguration = pendingConfiguration;
Expand Down Expand Up @@ -1336,11 +1332,11 @@ private MediaPositionParameters getMediaPositionParameters() {

private void applyAudioProcessorPlaybackParametersAndSkipSilence(long presentationTimeUs) {
PlaybackParameters playbackParameters =
configuration.canApplyPlaybackParameters
shouldApplyAudioProcessorPlaybackParameters()
? audioProcessorChain.applyPlaybackParameters(getAudioProcessorPlaybackParameters())
: PlaybackParameters.DEFAULT;
boolean skipSilenceEnabled =
configuration.canApplyPlaybackParameters
shouldApplyAudioProcessorPlaybackParameters()
? audioProcessorChain.applySkipSilenceEnabled(getSkipSilenceEnabled())
: DEFAULT_SKIP_SILENCE;
mediaPositionParametersCheckpoints.add(
Expand All @@ -1355,6 +1351,31 @@ private void applyAudioProcessorPlaybackParametersAndSkipSilence(long presentati
}
}

/**
* Returns whether audio processor playback parameters should be applied in the current
* configuration.
*/
private boolean shouldApplyAudioProcessorPlaybackParameters() {
// We don't apply speed/pitch adjustment using an audio processor in the following cases:
// - in tunneling mode, because audio processing can change the duration of audio yet the video
// frame presentation times are currently not modified (see also
// https://github.com/google/ExoPlayer/issues/4803);
// - when playing encoded audio via passthrough/offload, because modifying the audio stream
// would require decoding/re-encoding; and
// - when outputting float PCM audio, because SonicAudioProcessor outputs 16-bit integer PCM.
return !tunneling
&& MimeTypes.AUDIO_RAW.equals(configuration.inputFormat.sampleMimeType)
&& !shouldUseFloatOutput(configuration.inputFormat.pcmEncoding);
}

/**
* Returns whether audio in the specified PCM encoding should be written to the audio track as
* float PCM.
*/
private boolean shouldUseFloatOutput(@C.PcmEncoding int pcmEncoding) {
return enableFloatOutput && Util.isEncodingHighResolutionPcm(pcmEncoding);
}

/**
* Applies and updates media position parameters.
*
Expand Down Expand Up @@ -1897,7 +1918,6 @@ private static final class Configuration {
public final int outputChannelConfig;
@C.Encoding public final int outputEncoding;
public final int bufferSize;
public final boolean canApplyPlaybackParameters;
public final AudioProcessor[] availableAudioProcessors;

public Configuration(
Expand All @@ -1910,7 +1930,6 @@ public Configuration(
int outputEncoding,
int specifiedBufferSize,
boolean enableAudioTrackPlaybackParams,
boolean canApplyPlaybackParameters,
AudioProcessor[] availableAudioProcessors) {
this.inputFormat = inputFormat;
this.inputPcmFrameSize = inputPcmFrameSize;
Expand All @@ -1919,7 +1938,6 @@ public Configuration(
this.outputSampleRate = outputSampleRate;
this.outputChannelConfig = outputChannelConfig;
this.outputEncoding = outputEncoding;
this.canApplyPlaybackParameters = canApplyPlaybackParameters;
this.availableAudioProcessors = availableAudioProcessors;

// Call computeBufferSize() last as it depends on the other configuration values.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -457,12 +457,14 @@ public final void release() {
if (--prepareCallsCount != 0) {
return;
}
// Make a local copy, because sessions are removed from this.sessions during release (via
// callback).
List<DefaultDrmSession> sessions = new ArrayList<>(this.sessions);
for (int i = 0; i < sessions.size(); i++) {
// Release all the keepalive acquisitions.
sessions.get(i).release(/* eventDispatcher= */ null);
// Release all keepalive acquisitions if keepalive is enabled.
if (sessionKeepaliveMs != C.TIME_UNSET) {
// Make a local copy, because sessions are removed from this.sessions during release (via
// callback).
List<DefaultDrmSession> sessions = new ArrayList<>(this.sessions);
for (int i = 0; i < sessions.size(); i++) {
sessions.get(i).release(/* eventDispatcher= */ null);
}
}
Assertions.checkNotNull(exoMediaDrm).release();
exoMediaDrm = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,38 @@
package com.google.android.exoplayer2.drm;

import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;

import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import com.google.common.primitives.Ints;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;

/** Default implementation of {@link DrmSessionManagerProvider}. */
public final class DefaultDrmSessionManagerProvider implements DrmSessionManagerProvider {

private final Object lock;

@GuardedBy("lock")
private MediaItem.@MonotonicNonNull DrmConfiguration drmConfiguration;

@GuardedBy("lock")
private @MonotonicNonNull DrmSessionManager manager;

@Nullable private HttpDataSource.Factory drmHttpDataSourceFactory;
@Nullable private String userAgent;

public DefaultDrmSessionManagerProvider() {
lock = new Object();
}

/**
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
* HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
Expand Down Expand Up @@ -60,12 +75,24 @@ public void setDrmUserAgent(@Nullable String userAgent) {

@Override
public DrmSessionManager get(MediaItem mediaItem) {
Assertions.checkNotNull(mediaItem.playbackProperties);
checkNotNull(mediaItem.playbackProperties);
@Nullable
MediaItem.DrmConfiguration drmConfiguration = mediaItem.playbackProperties.drmConfiguration;
if (drmConfiguration == null || Util.SDK_INT < 18) {
return DrmSessionManager.DRM_UNSUPPORTED;
}

synchronized (lock) {
if (!Util.areEqual(drmConfiguration, this.drmConfiguration)) {
this.drmConfiguration = drmConfiguration;
this.manager = createManager(drmConfiguration);
}
return checkNotNull(this.manager);
}
}

@RequiresApi(18)
private DrmSessionManager createManager(MediaItem.DrmConfiguration drmConfiguration) {
HttpDataSource.Factory dataSourceFactory =
drmHttpDataSourceFactory != null
? drmHttpDataSourceFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,28 @@ private void maybeUpdateAdMediaSources() {
&& adIndexInAdGroup < adPlaybackState.adGroups[adGroupIndex].uris.length) {
@Nullable Uri adUri = adPlaybackState.adGroups[adGroupIndex].uris[adIndexInAdGroup];
if (adUri != null) {
MediaSource adMediaSource =
adMediaSourceFactory.createMediaSource(MediaItem.fromUri(adUri));
MediaItem.Builder adMediaItem = new MediaItem.Builder().setUri(adUri);
// Propagate the content's DRM config into the ad media source.
@Nullable
MediaItem.PlaybackProperties contentPlaybackProperties =
contentMediaSource.getMediaItem().playbackProperties;
if (contentPlaybackProperties != null
&& contentPlaybackProperties.drmConfiguration != null) {
MediaItem.DrmConfiguration drmConfiguration =
contentPlaybackProperties.drmConfiguration;
// TODO(internal b/179984779): Use MediaItem.Builder#setDrmConfiguration() when it's
// available.
adMediaItem.setDrmUuid(drmConfiguration.uuid);
adMediaItem.setDrmKeySetId(drmConfiguration.getKeySetId());
adMediaItem.setDrmLicenseUri(drmConfiguration.licenseUri);
adMediaItem.setDrmForceDefaultLicenseUri(drmConfiguration.forceDefaultLicenseUri);
adMediaItem.setDrmLicenseRequestHeaders(drmConfiguration.requestHeaders);
adMediaItem.setDrmMultiSession(drmConfiguration.multiSession);
adMediaItem.setDrmPlayClearContentWithoutKey(
drmConfiguration.playClearContentWithoutKey);
adMediaItem.setDrmSessionForClearTypes(drmConfiguration.sessionForClearTypes);
}
MediaSource adMediaSource = adMediaSourceFactory.createMediaSource(adMediaItem.build());
adMediaSourceHolder.initializeWithMediaSource(adMediaSource, adUri);
}
}
Expand Down
Loading

0 comments on commit 4b1e0fa

Please sign in to comment.