未验证 提交 4b1e0fa9 编写于 作者: O Oliver Woodman 提交者: GitHub

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

r2.13.1
# 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.
......
...@@ -281,6 +281,16 @@ import java.util.Map; ...@@ -281,6 +281,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(
...@@ -1355,6 +1351,31 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1355,6 +1351,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.
* *
...@@ -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;
...@@ -8833,6 +8835,42 @@ public final class ExoPlayerTest { ...@@ -8833,6 +8835,42 @@ public final class ExoPlayerTest {
assertThat(liveOffsetAtEnd).isIn(Range.closed(1_900L, 2_100L)); assertThat(liveOffsetAtEnd).isIn(Range.closed(1_900L, 2_100L));
} }
@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 @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;
......
...@@ -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);
} }
......
...@@ -147,6 +147,32 @@ public class DefaultDrmSessionManagerTest { ...@@ -147,6 +147,32 @@ public class DefaultDrmSessionManagerTest {
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED); assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED);
} }
@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) @Test(timeout = 10_000)
public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() throws Exception { public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() throws Exception {
ImmutableList<DrmInitData.SchemeData> secondSchemeDatas = ImmutableList<DrmInitData.SchemeData> secondSchemeDatas =
......
...@@ -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);
}
} }
...@@ -901,77 +901,54 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -901,77 +901,54 @@ public final class DashMediaSource extends BaseMediaSource {
} }
} }
// Update the window. // Update the window.
boolean windowChangingImplicitly = false; Period firstPeriod = manifest.getPeriod(0);
int lastPeriodIndex = manifest.getPeriodCount() - 1; int lastPeriodIndex = manifest.getPeriodCount() - 1;
Period lastPeriod = manifest.getPeriod(lastPeriodIndex); Period lastPeriod = manifest.getPeriod(lastPeriodIndex);
long lastPeriodDurationUs = manifest.getPeriodDurationUs(lastPeriodIndex); long lastPeriodDurationUs = manifest.getPeriodDurationUs(lastPeriodIndex);
long nowUnixTimeUs = C.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs)); long nowUnixTimeUs = C.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs));
// Get the period-relative start/end times. long windowStartTimeInManifestUs =
long currentStartTimeUs = getAvailableStartTimeInManifestUs(
getAvailableStartTimeUs( firstPeriod, manifest.getPeriodDurationUs(0), nowUnixTimeUs);
manifest.getPeriod(0), manifest.getPeriodDurationUs(0), nowUnixTimeUs); long windowEndTimeInManifestUs =
long currentEndTimeUs = getAvailableEndTimeUs(lastPeriod, lastPeriodDurationUs, nowUnixTimeUs); getAvailableEndTimeInManifestUs(lastPeriod, lastPeriodDurationUs, nowUnixTimeUs);
if (manifest.dynamic && !isIndexExplicit(lastPeriod)) { boolean windowChangingImplicitly = manifest.dynamic && !isIndexExplicit(lastPeriod);
// The manifest describes an incomplete live stream. Update the start/end times to reflect the if (windowChangingImplicitly && manifest.timeShiftBufferDepthMs != C.TIME_UNSET) {
// live stream duration and the manifest's time shift buffer depth. // Update the available start time to reflect the manifest's time shift buffer depth.
long liveStreamEndPositionInLastPeriodUs = currentEndTimeUs - C.msToUs(lastPeriod.startMs); long timeShiftBufferStartTimeInManifestUs =
currentEndTimeUs = min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs); windowEndTimeInManifestUs - C.msToUs(manifest.timeShiftBufferDepthMs);
if (manifest.timeShiftBufferDepthMs != C.TIME_UNSET) { windowStartTimeInManifestUs =
long timeShiftBufferDepthUs = C.msToUs(manifest.timeShiftBufferDepthMs); max(windowStartTimeInManifestUs, timeShiftBufferStartTimeInManifestUs);
long offsetInPeriodUs = currentEndTimeUs - timeShiftBufferDepthUs; }
int periodIndex = lastPeriodIndex; long windowDurationUs = windowEndTimeInManifestUs - windowStartTimeInManifestUs;
while (offsetInPeriodUs < 0 && periodIndex > 0) { long windowStartUnixTimeMs = C.TIME_UNSET;
offsetInPeriodUs += manifest.getPeriodDurationUs(--periodIndex); long windowDefaultPositionUs = 0;
}
if (periodIndex == 0) {
currentStartTimeUs = max(currentStartTimeUs, offsetInPeriodUs);
} else {
// The time shift buffer starts after the earliest period.
// TODO: Does this ever happen?
currentStartTimeUs = manifest.getPeriodDurationUs(0);
}
}
windowChangingImplicitly = true;
}
long windowDurationUs = currentEndTimeUs - currentStartTimeUs;
for (int i = 0; i < manifest.getPeriodCount() - 1; i++) {
windowDurationUs += manifest.getPeriodDurationUs(i);
}
long windowStartTimeMs = C.TIME_UNSET;
if (manifest.availabilityStartTimeMs != C.TIME_UNSET) {
windowStartTimeMs =
manifest.availabilityStartTimeMs
+ manifest.getPeriod(0).startMs
+ C.usToMs(currentStartTimeUs);
}
long windowDefaultStartPositionUs = 0;
if (manifest.dynamic) { if (manifest.dynamic) {
updateMediaItemLiveConfiguration( checkState(manifest.availabilityStartTimeMs != C.TIME_UNSET);
/* nowPeriodTimeUs= */ currentStartTimeUs + nowUnixTimeUs - C.msToUs(windowStartTimeMs), long nowInWindowUs =
/* windowStartPeriodTimeUs= */ currentStartTimeUs, nowUnixTimeUs - C.msToUs(manifest.availabilityStartTimeMs) - windowStartTimeInManifestUs;
/* windowEndPeriodTimeUs= */ currentEndTimeUs); updateMediaItemLiveConfiguration(nowInWindowUs, windowDurationUs);
windowDefaultStartPositionUs = windowStartUnixTimeMs =
nowUnixTimeUs - C.msToUs(windowStartTimeMs + liveConfiguration.targetOffsetMs); manifest.availabilityStartTimeMs + C.usToMs(windowStartTimeInManifestUs);
long minimumDefaultStartPositionUs = windowDefaultPositionUs = nowInWindowUs - C.msToUs(liveConfiguration.targetOffsetMs);
long minimumWindowDefaultPositionUs =
min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
if (windowDefaultStartPositionUs < minimumDefaultStartPositionUs) { if (windowDefaultPositionUs < minimumWindowDefaultPositionUs) {
// The default start position is too close to the start of the live window. Set it to the // The default position is too close to the start of the live window. Set it to the minimum
// minimum default start position provided the window is at least twice as big. Else set // default position provided the window is at least twice as big. Else set it to the middle
// it to the middle of the window. // of the window.
windowDefaultStartPositionUs = minimumDefaultStartPositionUs; windowDefaultPositionUs = minimumWindowDefaultPositionUs;
} }
} }
long offsetInFirstPeriodUs = windowStartTimeInManifestUs - C.msToUs(firstPeriod.startMs);
DashTimeline timeline = DashTimeline timeline =
new DashTimeline( new DashTimeline(
manifest.availabilityStartTimeMs, manifest.availabilityStartTimeMs,
windowStartTimeMs, windowStartUnixTimeMs,
elapsedRealtimeOffsetMs, elapsedRealtimeOffsetMs,
firstPeriodId, firstPeriodId,
/* offsetInFirstPeriodUs= */ currentStartTimeUs, offsetInFirstPeriodUs,
windowDurationUs, windowDurationUs,
windowDefaultStartPositionUs, windowDefaultPositionUs,
manifest, manifest,
mediaItem, mediaItem,
manifest.dynamic ? liveConfiguration : null); manifest.dynamic ? liveConfiguration : null);
...@@ -1008,8 +985,7 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1008,8 +985,7 @@ public final class DashMediaSource extends BaseMediaSource {
} }
} }
private void updateMediaItemLiveConfiguration( private void updateMediaItemLiveConfiguration(long nowInWindowUs, long windowDurationUs) {
long nowPeriodTimeUs, long windowStartPeriodTimeUs, long windowEndPeriodTimeUs) {
long maxLiveOffsetMs; long maxLiveOffsetMs;
if (mediaItem.liveConfiguration.maxOffsetMs != C.TIME_UNSET) { if (mediaItem.liveConfiguration.maxOffsetMs != C.TIME_UNSET) {
maxLiveOffsetMs = mediaItem.liveConfiguration.maxOffsetMs; maxLiveOffsetMs = mediaItem.liveConfiguration.maxOffsetMs;
...@@ -1017,7 +993,7 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1017,7 +993,7 @@ public final class DashMediaSource extends BaseMediaSource {
&& manifest.serviceDescription.maxOffsetMs != C.TIME_UNSET) { && manifest.serviceDescription.maxOffsetMs != C.TIME_UNSET) {
maxLiveOffsetMs = manifest.serviceDescription.maxOffsetMs; maxLiveOffsetMs = manifest.serviceDescription.maxOffsetMs;
} else { } else {
maxLiveOffsetMs = C.usToMs(nowPeriodTimeUs - windowStartPeriodTimeUs); maxLiveOffsetMs = C.usToMs(nowInWindowUs);
} }
long minLiveOffsetMs; long minLiveOffsetMs;
if (mediaItem.liveConfiguration.minOffsetMs != C.TIME_UNSET) { if (mediaItem.liveConfiguration.minOffsetMs != C.TIME_UNSET) {
...@@ -1026,7 +1002,7 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1026,7 +1002,7 @@ public final class DashMediaSource extends BaseMediaSource {
&& manifest.serviceDescription.minOffsetMs != C.TIME_UNSET) { && manifest.serviceDescription.minOffsetMs != C.TIME_UNSET) {
minLiveOffsetMs = manifest.serviceDescription.minOffsetMs; minLiveOffsetMs = manifest.serviceDescription.minOffsetMs;
} else { } else {
minLiveOffsetMs = C.usToMs(nowPeriodTimeUs - windowEndPeriodTimeUs); minLiveOffsetMs = C.usToMs(nowInWindowUs - windowDurationUs);
if (minLiveOffsetMs < 0 && maxLiveOffsetMs > 0) { if (minLiveOffsetMs < 0 && maxLiveOffsetMs > 0) {
// The current time is in the window, so assume all clocks are synchronized and set the // The current time is in the window, so assume all clocks are synchronized and set the
// minimum to a live offset of zero. // minimum to a live offset of zero.
...@@ -1052,12 +1028,10 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1052,12 +1028,10 @@ public final class DashMediaSource extends BaseMediaSource {
targetOffsetMs = minLiveOffsetMs; targetOffsetMs = minLiveOffsetMs;
} }
if (targetOffsetMs > maxLiveOffsetMs) { if (targetOffsetMs > maxLiveOffsetMs) {
long windowDurationUs = windowEndPeriodTimeUs - windowStartPeriodTimeUs;
long liveOffsetAtWindowStartUs = nowPeriodTimeUs - windowStartPeriodTimeUs;
long safeDistanceFromWindowStartUs = long safeDistanceFromWindowStartUs =
min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2);
long maxTargetOffsetForSafeDistanceToWindowStartMs = long maxTargetOffsetForSafeDistanceToWindowStartMs =
C.usToMs(liveOffsetAtWindowStartUs - safeDistanceFromWindowStartUs); C.usToMs(nowInWindowUs - safeDistanceFromWindowStartUs);
targetOffsetMs = targetOffsetMs =
Util.constrainValue( Util.constrainValue(
maxTargetOffsetForSafeDistanceToWindowStartMs, minLiveOffsetMs, maxLiveOffsetMs); maxTargetOffsetForSafeDistanceToWindowStartMs, minLiveOffsetMs, maxLiveOffsetMs);
...@@ -1147,9 +1121,10 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1147,9 +1121,10 @@ public final class DashMediaSource extends BaseMediaSource {
return LongMath.divide(intervalUs, 1000, RoundingMode.CEILING); return LongMath.divide(intervalUs, 1000, RoundingMode.CEILING);
} }
private static long getAvailableStartTimeUs( private static long getAvailableStartTimeInManifestUs(
Period period, long periodDurationUs, long nowUnixTimeUs) { Period period, long periodDurationUs, long nowUnixTimeUs) {
long availableStartTimeUs = 0; long periodStartTimeInManifestUs = C.msToUs(period.startMs);
long availableStartTimeInManifestUs = periodStartTimeInManifestUs;
boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period); boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period);
for (int i = 0; i < period.adaptationSets.size(); i++) { for (int i = 0; i < period.adaptationSets.size(); i++) {
AdaptationSet adaptationSet = period.adaptationSets.get(i); AdaptationSet adaptationSet = period.adaptationSets.get(i);
...@@ -1162,23 +1137,26 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1162,23 +1137,26 @@ public final class DashMediaSource extends BaseMediaSource {
} }
@Nullable DashSegmentIndex index = representations.get(0).getIndex(); @Nullable DashSegmentIndex index = representations.get(0).getIndex();
if (index == null) { if (index == null) {
return 0; return periodStartTimeInManifestUs;
} }
int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
if (availableSegmentCount == 0) { if (availableSegmentCount == 0) {
return 0; return periodStartTimeInManifestUs;
} }
long firstAvailableSegmentNum = long firstAvailableSegmentNum =
index.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs); index.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs);
long adaptationSetAvailableStartTimeUs = index.getTimeUs(firstAvailableSegmentNum); long adaptationSetAvailableStartTimeInManifestUs =
availableStartTimeUs = max(availableStartTimeUs, adaptationSetAvailableStartTimeUs); periodStartTimeInManifestUs + index.getTimeUs(firstAvailableSegmentNum);
availableStartTimeInManifestUs =
max(availableStartTimeInManifestUs, adaptationSetAvailableStartTimeInManifestUs);
} }
return availableStartTimeUs; return availableStartTimeInManifestUs;
} }
private static long getAvailableEndTimeUs( private static long getAvailableEndTimeInManifestUs(
Period period, long periodDurationUs, long nowUnixTimeUs) { Period period, long periodDurationUs, long nowUnixTimeUs) {
long availableEndTimeUs = Long.MAX_VALUE; long periodStartTimeInManifestUs = C.msToUs(period.startMs);
long availableEndTimeInManifestUs = Long.MAX_VALUE;
boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period); boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period);
for (int i = 0; i < period.adaptationSets.size(); i++) { for (int i = 0; i < period.adaptationSets.size(); i++) {
AdaptationSet adaptationSet = period.adaptationSets.get(i); AdaptationSet adaptationSet = period.adaptationSets.get(i);
...@@ -1191,21 +1169,23 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1191,21 +1169,23 @@ public final class DashMediaSource extends BaseMediaSource {
} }
@Nullable DashSegmentIndex index = representations.get(0).getIndex(); @Nullable DashSegmentIndex index = representations.get(0).getIndex();
if (index == null) { if (index == null) {
return periodDurationUs; return periodStartTimeInManifestUs + periodDurationUs;
} }
int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
if (availableSegmentCount == 0) { if (availableSegmentCount == 0) {
return 0; return periodStartTimeInManifestUs;
} }
long firstAvailableSegmentNum = long firstAvailableSegmentNum =
index.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs); index.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs);
long lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1; long lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1;
long adaptationSetAvailableEndTimeUs = long adaptationSetAvailableEndTimeInManifestUs =
index.getTimeUs(lastAvailableSegmentNum) periodStartTimeInManifestUs
+ index.getTimeUs(lastAvailableSegmentNum)
+ index.getDurationUs(lastAvailableSegmentNum, periodDurationUs); + index.getDurationUs(lastAvailableSegmentNum, periodDurationUs);
availableEndTimeUs = min(availableEndTimeUs, adaptationSetAvailableEndTimeUs); availableEndTimeInManifestUs =
min(availableEndTimeInManifestUs, adaptationSetAvailableEndTimeInManifestUs);
} }
return availableEndTimeUs; return availableEndTimeInManifestUs;
} }
private static boolean isIndexExplicit(Period period) { private static boolean isIndexExplicit(Period period) {
......
...@@ -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% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册