Commit 4b1e0fa9 by Oliver Woodman Committed by GitHub

Merge pull request #8582 from google/dev-v2-r2.13.1

r2.13.1
parents b1000940 5807d2e0
Showing with 268 additions and 43 deletions
# Release notes # 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) ### 2.13.0 (2021-02-04)
* Core library: * Core library:
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
// limitations under the License. // limitations under the License.
project.ext { project.ext {
// ExoPlayer version and version code. // ExoPlayer version and version code.
releaseVersion = '2.13.0' releaseVersion = '2.13.1'
releaseVersionCode = 2013000 releaseVersionCode = 2013001
minSdkVersion = 16 minSdkVersion = 16
appTargetSdkVersion = 29 appTargetSdkVersion = 29
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest. targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest.
......
...@@ -282,6 +282,16 @@ import java.util.Map; ...@@ -282,6 +282,16 @@ import java.util.Map;
} }
/** /**
* 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 * Starts passing events from this instance (including any pending ad playback state) and
* registers obstructions. * registers obstructions.
*/ */
...@@ -879,7 +889,8 @@ import java.util.Map; ...@@ -879,7 +889,8 @@ import java.util.Map;
int adGroupIndex = getAdGroupIndexForAdPod(adPodInfo); int adGroupIndex = getAdGroupIndexForAdPod(adPodInfo);
int adIndexInAdGroup = adPodInfo.getAdPosition() - 1; int adIndexInAdGroup = adPodInfo.getAdPosition() - 1;
AdInfo adInfo = new AdInfo(adGroupIndex, adIndexInAdGroup); 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) { if (configuration.debugModeEnabled) {
Log.d(TAG, "loadAd " + getAdMediaInfoString(adMediaInfo)); Log.d(TAG, "loadAd " + getAdMediaInfoString(adMediaInfo));
} }
......
...@@ -473,6 +473,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader { ...@@ -473,6 +473,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
} }
} }
/**
* 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. // AdsLoader implementation.
@Override @Override
......
...@@ -30,11 +30,11 @@ public final class ExoPlayerLibraryInfo { ...@@ -30,11 +30,11 @@ public final class ExoPlayerLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3". */ /** 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. // 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}. */ /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // 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. * The version of the library expressed as an integer, for example 1002003.
...@@ -44,7 +44,7 @@ public final class ExoPlayerLibraryInfo { ...@@ -44,7 +44,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006). * integer version 123045006 (123-045-006).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // 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. * The default user agent for requests made by the library.
......
...@@ -182,9 +182,10 @@ public final class AdPlaybackState { ...@@ -182,9 +182,10 @@ public final class AdPlaybackState {
/** Returns a new instance with the specified ad durations, in microseconds. */ /** Returns a new instance with the specified ad durations, in microseconds. */
@CheckResult @CheckResult
public AdGroup withAdDurationsUs(long[] durationsUs) { public AdGroup withAdDurationsUs(long[] durationsUs) {
Assertions.checkArgument(count == C.LENGTH_UNSET || durationsUs.length <= this.uris.length); if (durationsUs.length < uris.length) {
if (durationsUs.length < this.uris.length) {
durationsUs = copyDurationsUsWithSpaceForAdCount(durationsUs, 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); return new AdGroup(count, states, uris, durationsUs);
} }
......
...@@ -880,7 +880,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -880,7 +880,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
// Adjust live playback speed to new position. // Adjust live playback speed to new position.
if (playbackInfo.playWhenReady if (playbackInfo.playWhenReady
&& playbackInfo.playbackState == Player.STATE_READY && playbackInfo.playbackState == Player.STATE_READY
&& isCurrentPeriodInMovingLiveWindow() && shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, playbackInfo.periodId)
&& playbackInfo.playbackParameters.speed == 1f) { && playbackInfo.playbackParameters.speed == 1f) {
float adjustedSpeed = float adjustedSpeed =
livePlaybackSpeedControl.getAdjustedPlaybackSpeed( livePlaybackSpeedControl.getAdjustedPlaybackSpeed(
...@@ -1051,17 +1051,14 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1051,17 +1051,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
- (periodPositionUs + period.getPositionInWindowUs()); - (periodPositionUs + period.getPositionInWindowUs());
} }
private boolean isCurrentPeriodInMovingLiveWindow() { private boolean shouldUseLivePlaybackSpeedControl(
return isInMovingLiveWindow(playbackInfo.timeline, playbackInfo.periodId); Timeline timeline, MediaPeriodId mediaPeriodId) {
}
private boolean isInMovingLiveWindow(Timeline timeline, MediaPeriodId mediaPeriodId) {
if (mediaPeriodId.isAd() || timeline.isEmpty()) { if (mediaPeriodId.isAd() || timeline.isEmpty()) {
return false; return false;
} }
int windowIndex = timeline.getPeriodByUid(mediaPeriodId.periodUid, period).windowIndex; int windowIndex = timeline.getPeriodByUid(mediaPeriodId.periodUid, period).windowIndex;
timeline.getWindow(windowIndex, window); 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) { private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) {
...@@ -1725,7 +1722,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1725,7 +1722,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
// Renderers are ready and we're loading. Ask the LoadControl whether to transition. // Renderers are ready and we're loading. Ask the LoadControl whether to transition.
long targetLiveOffsetUs = long targetLiveOffsetUs =
isInMovingLiveWindow(playbackInfo.timeline, queue.getPlayingPeriod().info.id) shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, queue.getPlayingPeriod().info.id)
? livePlaybackSpeedControl.getTargetLiveOffsetUs() ? livePlaybackSpeedControl.getTargetLiveOffsetUs()
: C.TIME_UNSET; : C.TIME_UNSET;
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod(); MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
...@@ -1831,7 +1828,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1831,7 +1828,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
Timeline oldTimeline, Timeline oldTimeline,
MediaPeriodId oldPeriodId, MediaPeriodId oldPeriodId,
long positionForTargetOffsetOverrideUs) { long positionForTargetOffsetOverrideUs) {
if (newTimeline.isEmpty() || !isInMovingLiveWindow(newTimeline, newPeriodId)) { if (newTimeline.isEmpty() || !shouldUseLivePlaybackSpeedControl(newTimeline, newPeriodId)) {
// Live playback speed control is unused. // Live playback speed control is unused.
return; return;
} }
......
...@@ -488,7 +488,6 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -488,7 +488,6 @@ public final class DefaultAudioSink implements AudioSink {
throws ConfigurationException { throws ConfigurationException {
int inputPcmFrameSize; int inputPcmFrameSize;
@Nullable AudioProcessor[] availableAudioProcessors; @Nullable AudioProcessor[] availableAudioProcessors;
boolean canApplyPlaybackParameters;
@OutputMode int outputMode; @OutputMode int outputMode;
@C.Encoding int outputEncoding; @C.Encoding int outputEncoding;
...@@ -500,11 +499,10 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -500,11 +499,10 @@ public final class DefaultAudioSink implements AudioSink {
Assertions.checkArgument(Util.isEncodingLinearPcm(inputFormat.pcmEncoding)); Assertions.checkArgument(Util.isEncodingLinearPcm(inputFormat.pcmEncoding));
inputPcmFrameSize = Util.getPcmFrameSize(inputFormat.pcmEncoding, inputFormat.channelCount); inputPcmFrameSize = Util.getPcmFrameSize(inputFormat.pcmEncoding, inputFormat.channelCount);
boolean useFloatOutput =
enableFloatOutput && Util.isEncodingHighResolutionPcm(inputFormat.pcmEncoding);
availableAudioProcessors = availableAudioProcessors =
useFloatOutput ? toFloatPcmAvailableAudioProcessors : toIntPcmAvailableAudioProcessors; shouldUseFloatOutput(inputFormat.pcmEncoding)
canApplyPlaybackParameters = !useFloatOutput; ? toFloatPcmAvailableAudioProcessors
: toIntPcmAvailableAudioProcessors;
trimmingAudioProcessor.setTrimFrameCount( trimmingAudioProcessor.setTrimFrameCount(
inputFormat.encoderDelay, inputFormat.encoderPadding); inputFormat.encoderDelay, inputFormat.encoderPadding);
...@@ -541,7 +539,6 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -541,7 +539,6 @@ public final class DefaultAudioSink implements AudioSink {
} else { } else {
inputPcmFrameSize = C.LENGTH_UNSET; inputPcmFrameSize = C.LENGTH_UNSET;
availableAudioProcessors = new AudioProcessor[0]; availableAudioProcessors = new AudioProcessor[0];
canApplyPlaybackParameters = false;
outputSampleRate = inputFormat.sampleRate; outputSampleRate = inputFormat.sampleRate;
outputPcmFrameSize = C.LENGTH_UNSET; outputPcmFrameSize = C.LENGTH_UNSET;
if (enableOffload && isOffloadedPlaybackSupported(inputFormat, audioAttributes)) { if (enableOffload && isOffloadedPlaybackSupported(inputFormat, audioAttributes)) {
...@@ -586,7 +583,6 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -586,7 +583,6 @@ public final class DefaultAudioSink implements AudioSink {
outputEncoding, outputEncoding,
specifiedBufferSize, specifiedBufferSize,
enableAudioTrackPlaybackParams, enableAudioTrackPlaybackParams,
canApplyPlaybackParameters,
availableAudioProcessors); availableAudioProcessors);
if (isAudioTrackInitialized()) { if (isAudioTrackInitialized()) {
this.pendingConfiguration = pendingConfiguration; this.pendingConfiguration = pendingConfiguration;
...@@ -1336,11 +1332,11 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1336,11 +1332,11 @@ public final class DefaultAudioSink implements AudioSink {
private void applyAudioProcessorPlaybackParametersAndSkipSilence(long presentationTimeUs) { private void applyAudioProcessorPlaybackParametersAndSkipSilence(long presentationTimeUs) {
PlaybackParameters playbackParameters = PlaybackParameters playbackParameters =
configuration.canApplyPlaybackParameters shouldApplyAudioProcessorPlaybackParameters()
? audioProcessorChain.applyPlaybackParameters(getAudioProcessorPlaybackParameters()) ? audioProcessorChain.applyPlaybackParameters(getAudioProcessorPlaybackParameters())
: PlaybackParameters.DEFAULT; : PlaybackParameters.DEFAULT;
boolean skipSilenceEnabled = boolean skipSilenceEnabled =
configuration.canApplyPlaybackParameters shouldApplyAudioProcessorPlaybackParameters()
? audioProcessorChain.applySkipSilenceEnabled(getSkipSilenceEnabled()) ? audioProcessorChain.applySkipSilenceEnabled(getSkipSilenceEnabled())
: DEFAULT_SKIP_SILENCE; : DEFAULT_SKIP_SILENCE;
mediaPositionParametersCheckpoints.add( mediaPositionParametersCheckpoints.add(
...@@ -1356,6 +1352,31 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1356,6 +1352,31 @@ public final class DefaultAudioSink implements AudioSink {
} }
/** /**
* 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. * Applies and updates media position parameters.
* *
* @param positionUs The current audio track position, in microseconds. * @param positionUs The current audio track position, in microseconds.
...@@ -1897,7 +1918,6 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1897,7 +1918,6 @@ public final class DefaultAudioSink implements AudioSink {
public final int outputChannelConfig; public final int outputChannelConfig;
@C.Encoding public final int outputEncoding; @C.Encoding public final int outputEncoding;
public final int bufferSize; public final int bufferSize;
public final boolean canApplyPlaybackParameters;
public final AudioProcessor[] availableAudioProcessors; public final AudioProcessor[] availableAudioProcessors;
public Configuration( public Configuration(
...@@ -1910,7 +1930,6 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1910,7 +1930,6 @@ public final class DefaultAudioSink implements AudioSink {
int outputEncoding, int outputEncoding,
int specifiedBufferSize, int specifiedBufferSize,
boolean enableAudioTrackPlaybackParams, boolean enableAudioTrackPlaybackParams,
boolean canApplyPlaybackParameters,
AudioProcessor[] availableAudioProcessors) { AudioProcessor[] availableAudioProcessors) {
this.inputFormat = inputFormat; this.inputFormat = inputFormat;
this.inputPcmFrameSize = inputPcmFrameSize; this.inputPcmFrameSize = inputPcmFrameSize;
...@@ -1919,7 +1938,6 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1919,7 +1938,6 @@ public final class DefaultAudioSink implements AudioSink {
this.outputSampleRate = outputSampleRate; this.outputSampleRate = outputSampleRate;
this.outputChannelConfig = outputChannelConfig; this.outputChannelConfig = outputChannelConfig;
this.outputEncoding = outputEncoding; this.outputEncoding = outputEncoding;
this.canApplyPlaybackParameters = canApplyPlaybackParameters;
this.availableAudioProcessors = availableAudioProcessors; this.availableAudioProcessors = availableAudioProcessors;
// Call computeBufferSize() last as it depends on the other configuration values. // Call computeBufferSize() last as it depends on the other configuration values.
......
...@@ -457,12 +457,14 @@ public class DefaultDrmSessionManager implements DrmSessionManager { ...@@ -457,12 +457,14 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
if (--prepareCallsCount != 0) { if (--prepareCallsCount != 0) {
return; return;
} }
// Make a local copy, because sessions are removed from this.sessions during release (via // Release all keepalive acquisitions if keepalive is enabled.
// callback). if (sessionKeepaliveMs != C.TIME_UNSET) {
List<DefaultDrmSession> sessions = new ArrayList<>(this.sessions); // Make a local copy, because sessions are removed from this.sessions during release (via
for (int i = 0; i < sessions.size(); i++) { // callback).
// Release all the keepalive acquisitions. List<DefaultDrmSession> sessions = new ArrayList<>(this.sessions);
sessions.get(i).release(/* eventDispatcher= */ null); for (int i = 0; i < sessions.size(); i++) {
sessions.get(i).release(/* eventDispatcher= */ null);
}
} }
Assertions.checkNotNull(exoMediaDrm).release(); Assertions.checkNotNull(exoMediaDrm).release();
exoMediaDrm = null; exoMediaDrm = null;
......
...@@ -16,23 +16,38 @@ ...@@ -16,23 +16,38 @@
package com.google.android.exoplayer2.drm; package com.google.android.exoplayer2.drm;
import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK; 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.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import java.util.Map; import java.util.Map;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Default implementation of {@link DrmSessionManagerProvider}. */ /** Default implementation of {@link DrmSessionManagerProvider}. */
public final class DefaultDrmSessionManagerProvider implements 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 HttpDataSource.Factory drmHttpDataSourceFactory;
@Nullable private String userAgent; @Nullable private String userAgent;
public DefaultDrmSessionManagerProvider() {
lock = new Object();
}
/** /**
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback * Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
* HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null} * HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
...@@ -60,12 +75,24 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager ...@@ -60,12 +75,24 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager
@Override @Override
public DrmSessionManager get(MediaItem mediaItem) { public DrmSessionManager get(MediaItem mediaItem) {
Assertions.checkNotNull(mediaItem.playbackProperties); checkNotNull(mediaItem.playbackProperties);
@Nullable @Nullable
MediaItem.DrmConfiguration drmConfiguration = mediaItem.playbackProperties.drmConfiguration; MediaItem.DrmConfiguration drmConfiguration = mediaItem.playbackProperties.drmConfiguration;
if (drmConfiguration == null || Util.SDK_INT < 18) { if (drmConfiguration == null || Util.SDK_INT < 18) {
return DrmSessionManager.DRM_UNSUPPORTED; 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 = HttpDataSource.Factory dataSourceFactory =
drmHttpDataSourceFactory != null drmHttpDataSourceFactory != null
? drmHttpDataSourceFactory ? drmHttpDataSourceFactory
......
...@@ -318,8 +318,28 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { ...@@ -318,8 +318,28 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
&& adIndexInAdGroup < adPlaybackState.adGroups[adGroupIndex].uris.length) { && adIndexInAdGroup < adPlaybackState.adGroups[adGroupIndex].uris.length) {
@Nullable Uri adUri = adPlaybackState.adGroups[adGroupIndex].uris[adIndexInAdGroup]; @Nullable Uri adUri = adPlaybackState.adGroups[adGroupIndex].uris[adIndexInAdGroup];
if (adUri != null) { if (adUri != null) {
MediaSource adMediaSource = MediaItem.Builder adMediaItem = new MediaItem.Builder().setUri(adUri);
adMediaSourceFactory.createMediaSource(MediaItem.fromUri(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); adMediaSourceHolder.initializeWithMediaSource(adMediaSource, adUri);
} }
} }
......
...@@ -71,6 +71,7 @@ import com.google.android.exoplayer2.source.MediaSource; ...@@ -71,6 +71,7 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.SilenceMediaSource; import com.google.android.exoplayer2.source.SilenceMediaSource;
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.AdPlaybackState;
...@@ -83,6 +84,7 @@ import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner; ...@@ -83,6 +84,7 @@ import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
import com.google.android.exoplayer2.testutil.FakeAdaptiveDataSet; import com.google.android.exoplayer2.testutil.FakeAdaptiveDataSet;
import com.google.android.exoplayer2.testutil.FakeAdaptiveMediaSource; import com.google.android.exoplayer2.testutil.FakeAdaptiveMediaSource;
import com.google.android.exoplayer2.testutil.FakeChunkSource; import com.google.android.exoplayer2.testutil.FakeChunkSource;
import com.google.android.exoplayer2.testutil.FakeClock;
import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer; import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
import com.google.android.exoplayer2.testutil.FakeMediaPeriod; import com.google.android.exoplayer2.testutil.FakeMediaPeriod;
...@@ -8834,6 +8836,42 @@ public final class ExoPlayerTest { ...@@ -8834,6 +8836,42 @@ public final class ExoPlayerTest {
} }
@Test @Test
public void targetLiveOffsetInMedia_unknownWindowStartTime_doesNotAdjustLiveOffset()
throws Exception {
FakeClock fakeClock = new AutoAdvancingFakeClock(/* initialTimeMs= */ 987_654_321L);
ExoPlayer player = new TestExoPlayerBuilder(context).setClock(fakeClock).build();
MediaItem mediaItem =
new MediaItem.Builder().setUri(Uri.EMPTY).setLiveTargetOffsetMs(4_000).build();
Timeline liveTimeline =
new SinglePeriodTimeline(
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
/* periodDurationUs= */ 1000 * C.MICROS_PER_SECOND,
/* windowDurationUs= */ 1000 * C.MICROS_PER_SECOND,
/* windowPositionInPeriodUs= */ 0,
/* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* manifest= */ null,
mediaItem,
mediaItem.liveConfiguration);
player.pause();
player.setMediaSource(new FakeMediaSource(liveTimeline));
player.prepare();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY);
long playbackStartTimeMs = fakeClock.elapsedRealtime();
TestPlayerRunHelper.playUntilPosition(player, /* windowIndex= */ 0, /* positionMs= */ 999_000);
long playbackEndTimeMs = fakeClock.elapsedRealtime();
player.release();
// Assert that the time it took to play 999 seconds of media is 999 seconds (asserting that no
// playback speed adjustment was used).
assertThat(playbackEndTimeMs - playbackStartTimeMs).isEqualTo(999_000);
}
@Test
public void noTargetLiveOffsetInMedia_doesNotAdjustLiveOffset() throws Exception { public void noTargetLiveOffsetInMedia_doesNotAdjustLiveOffset() throws Exception {
long windowStartUnixTimeMs = 987_654_321_000L; long windowStartUnixTimeMs = 987_654_321_000L;
long nowUnixTimeMs = windowStartUnixTimeMs + 20_000; long nowUnixTimeMs = windowStartUnixTimeMs + 20_000;
......
...@@ -320,6 +320,20 @@ public final class DefaultAudioSinkTest { ...@@ -320,6 +320,20 @@ public final class DefaultAudioSinkTest {
assertThat(thrown.format).isEqualTo(format); assertThat(thrown.format).isEqualTo(format);
} }
@Test
public void setPlaybackParameters_doesNothingWhenTunnelingIsEnabled() throws Exception {
defaultAudioSink.setAudioSessionId(1);
defaultAudioSink.enableTunnelingV21();
defaultAudioSink.setPlaybackParameters(new PlaybackParameters(2));
configureDefaultAudioSink(/* channelCount= */ 2);
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
/* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND,
/* encodedAccessUnitCount= */ 1);
assertThat(defaultAudioSink.getPlaybackParameters().speed).isEqualTo(1);
}
private void configureDefaultAudioSink(int channelCount) throws AudioSink.ConfigurationException { private void configureDefaultAudioSink(int channelCount) throws AudioSink.ConfigurationException {
configureDefaultAudioSink(channelCount, /* trimStartFrames= */ 0, /* trimEndFrames= */ 0); configureDefaultAudioSink(channelCount, /* trimStartFrames= */ 0, /* trimEndFrames= */ 0);
} }
......
...@@ -148,6 +148,32 @@ public class DefaultDrmSessionManagerTest { ...@@ -148,6 +148,32 @@ public class DefaultDrmSessionManagerTest {
} }
@Test(timeout = 10_000) @Test(timeout = 10_000)
public void managerRelease_keepaliveDisabled_doesntReleaseAnySessions() throws Exception {
FakeExoMediaDrm.LicenseServer licenseServer =
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
DrmSessionManager drmSessionManager =
new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
.setSessionKeepaliveMs(C.TIME_UNSET)
.build(/* mediaDrmCallback= */ licenseServer);
drmSessionManager.prepare();
DrmSession drmSession =
checkNotNull(
drmSessionManager.acquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null,
FORMAT_WITH_DRM_INIT_DATA));
waitForOpenedWithKeys(drmSession);
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
// Release the manager, the session should still be open (though it's unusable because
// the underlying ExoMediaDrm is released).
drmSessionManager.release();
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
}
@Test(timeout = 10_000)
public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() throws Exception { public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() throws Exception {
ImmutableList<DrmInitData.SchemeData> secondSchemeDatas = ImmutableList<DrmInitData.SchemeData> secondSchemeDatas =
ImmutableList.of(DRM_SCHEME_DATAS.get(0).copyWithData(TestUtil.createByteArray(4, 5, 6))); ImmutableList.of(DRM_SCHEME_DATAS.get(0).copyWithData(TestUtil.createByteArray(4, 5, 6)));
......
...@@ -51,4 +51,39 @@ public class DefaultDrmSessionManagerProviderTest { ...@@ -51,4 +51,39 @@ public class DefaultDrmSessionManagerProviderTest {
assertThat(drmSessionManager).isNotEqualTo(DrmSessionManager.DRM_UNSUPPORTED); assertThat(drmSessionManager).isNotEqualTo(DrmSessionManager.DRM_UNSUPPORTED);
} }
@Test
public void create_reusesCachedInstanceWherePossible() {
MediaItem mediaItem1 =
new MediaItem.Builder()
.setUri("https://example.test/content-1")
.setDrmUuid(C.WIDEVINE_UUID)
.build();
// Same DRM info as item1, but different URL to check it doesn't prevent re-using a manager.
MediaItem mediaItem2 =
new MediaItem.Builder()
.setUri("https://example.test/content-2")
.setDrmUuid(C.WIDEVINE_UUID)
.build();
// Different DRM info to 1 and 2, needs a different manager instance.
MediaItem mediaItem3 =
new MediaItem.Builder()
.setUri("https://example.test/content-3")
.setDrmUuid(C.WIDEVINE_UUID)
.setDrmLicenseUri("https://example.test/license")
.build();
DefaultDrmSessionManagerProvider provider = new DefaultDrmSessionManagerProvider();
DrmSessionManager drmSessionManager1 = provider.get(mediaItem1);
DrmSessionManager drmSessionManager2 = provider.get(mediaItem2);
DrmSessionManager drmSessionManager3 = provider.get(mediaItem3);
// Get a manager for the first item again - expect it to be a different instance to last time
// since we only cache one.
DrmSessionManager drmSessionManager4 = provider.get(mediaItem1);
assertThat(drmSessionManager1).isSameInstanceAs(drmSessionManager2);
assertThat(drmSessionManager1).isNotSameInstanceAs(drmSessionManager3);
assertThat(drmSessionManager1).isNotSameInstanceAs(drmSessionManager4);
}
} }
...@@ -30,9 +30,7 @@ public class Period { ...@@ -30,9 +30,7 @@ public class Period {
*/ */
@Nullable public final String id; @Nullable public final String id;
/** /** The start time of the period in milliseconds, relative to the start of the manifest. */
* The start time of the period in milliseconds.
*/
public final long startMs; public final long startMs;
/** /**
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.transformer; package com.google.android.exoplayer2.transformer;
/** A custom interface that determines the speed for media at specific timestamps. */ /** A custom interface that determines the speed for media at specific timestamps. */
public interface SpeedProvider { /* package */ interface SpeedProvider {
/** /**
* Provides the speed that the media should be played at, based on the timeUs. * Provides the speed that the media should be played at, based on the timeUs.
......
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