From a08514315fdc8bd07bbf5fb6e510380461a0a928 Mon Sep 17 00:00:00 2001 From: kimvde Date: Mon, 8 Feb 2021 10:34:55 +0000 Subject: [PATCH 01/11] Transformer: remove SpeedProvider interface from Javadoc #minor-release PiperOrigin-RevId: 356221487 --- .../google/android/exoplayer2/transformer/SpeedProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SpeedProvider.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SpeedProvider.java index f8109e031c..ad2b307750 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SpeedProvider.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/SpeedProvider.java @@ -16,7 +16,7 @@ package com.google.android.exoplayer2.transformer; /** 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. From 5211f06dc2da11aa91af997235c77e7cb17413cb Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 8 Feb 2021 11:26:35 +0000 Subject: [PATCH 02/11] Don't apply speed adjustment if windowStartTime is unknown. This may happen for HLS live streams without program date time information. Issue: #8560 PiperOrigin-RevId: 356227729 --- RELEASENOTES.md | 6 +++ .../exoplayer2/ExoPlayerImplInternal.java | 15 +++----- .../android/exoplayer2/ExoPlayerTest.java | 38 +++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f5353a24e1..6f45ddcf93 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,11 @@ # Release notes +### 2.13.1 (not yet released) + +* Core library: + * Fix playback issue for HLS live streams without program date time + information ([#8560](https://github.com/google/ExoPlayer/issues/8560)). + ### 2.13.0 (2021-02-04) * Core library: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 755d7511c4..f791d05b13 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -880,7 +880,7 @@ import java.util.concurrent.atomic.AtomicBoolean; // 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( @@ -1051,17 +1051,14 @@ import java.util.concurrent.atomic.AtomicBoolean; - (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) { @@ -1725,7 +1722,7 @@ import java.util.concurrent.atomic.AtomicBoolean; } // 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(); @@ -1831,7 +1828,7 @@ import java.util.concurrent.atomic.AtomicBoolean; 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; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 008d8c6b53..e743858875 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -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.MediaSourceEventListener; 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.TrackGroupArray; import com.google.android.exoplayer2.source.ads.AdPlaybackState; @@ -83,6 +84,7 @@ import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner; import com.google.android.exoplayer2.testutil.FakeAdaptiveDataSet; import com.google.android.exoplayer2.testutil.FakeAdaptiveMediaSource; 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.FakeMediaClockRenderer; import com.google.android.exoplayer2.testutil.FakeMediaPeriod; @@ -8833,6 +8835,42 @@ public final class ExoPlayerTest { 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 public void noTargetLiveOffsetInMedia_doesNotAdjustLiveOffset() throws Exception { long windowStartUnixTimeMs = 987_654_321_000L; From 56feb96fc917f5894308c1bfb83dd92e0d7bf090 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 8 Feb 2021 14:23:29 +0000 Subject: [PATCH 03/11] Handle loading the same ad more than once Also allow the player's prepared ad media period durations array to exceed the length of the loaded ad URIs array, as it's possible for the player to buffer an ad media period fully at the point where it's known that an ad is coming up but its URI is still unknown. PiperOrigin-RevId: 356249284 --- RELEASENOTES.md | 6 ++++++ .../com/google/android/exoplayer2/ext/ima/AdTagLoader.java | 3 ++- .../android/exoplayer2/source/ads/AdPlaybackState.java | 5 +++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6f45ddcf93..004b72cdcb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,6 +5,12 @@ * Core library: * Fix playback issue for HLS live streams without program date time information ([#8560](https://github.com/google/ExoPlayer/issues/8560)). +* 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. ### 2.13.0 (2021-02-04) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java index 4ce0610fb6..624a009bb7 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java @@ -879,7 +879,8 @@ import java.util.Map; 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)); } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index a50fcd7d1d..b70dd10c38 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -182,9 +182,10 @@ public final class AdPlaybackState { /** 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); } From 19ab087c61f8dcc4142c2be975eb245ff89973b3 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 10 Feb 2021 09:48:04 +0000 Subject: [PATCH 04/11] Cache the last DrmSessionManager instance inside the default provider Without this a new manager is instantiated for every item in a playlist, meaning the impact of caching improvements to DefaultDrmSessionManager are reduced (since the cache doesn't persist across playlist items). With this change, playlists of items with identical DRM config will use the same manager instance (and thus share existing sessions). Issue: #8523 PiperOrigin-RevId: 356690852 --- RELEASENOTES.md | 4 +++ .../drm/DefaultDrmSessionManagerProvider.java | 31 ++++++++++++++-- .../DefaultDrmSessionManagerProviderTest.java | 35 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 004b72cdcb..214b635a48 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -11,6 +11,10 @@ * 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. +* DRM: + * Re-use the previous `DrmSessionManager` instance when playing a playlist + (if possible) + ([#8523](https://github.com/google/ExoPlayer/issues/8523)). ### 2.13.0 (2021-02-04) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerProvider.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerProvider.java index 10bd2953d5..18f668cc3d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerProvider.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerProvider.java @@ -16,23 +16,39 @@ 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}. */ +@RequiresApi(18) 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} @@ -60,12 +76,23 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager @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); + } + } + + private DrmSessionManager createManager(MediaItem.DrmConfiguration drmConfiguration) { HttpDataSource.Factory dataSourceFactory = drmHttpDataSourceFactory != null ? drmHttpDataSourceFactory diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultDrmSessionManagerProviderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultDrmSessionManagerProviderTest.java index 4e597b6371..0c830ca5ab 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultDrmSessionManagerProviderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/DefaultDrmSessionManagerProviderTest.java @@ -51,4 +51,39 @@ public class DefaultDrmSessionManagerProviderTest { 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); + } } From d0dd33e5d339216326d8aa1e7c127b334b8c7ef1 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 10 Feb 2021 09:53:13 +0000 Subject: [PATCH 05/11] Add a method to focus the 'skip ad' button, if shown Issue: #8565 PiperOrigin-RevId: 356691251 --- RELEASENOTES.md | 3 +++ .../google/android/exoplayer2/ext/ima/AdTagLoader.java | 10 ++++++++++ .../android/exoplayer2/ext/ima/ImaAdsLoader.java | 10 ++++++++++ 3 files changed, 23 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 214b635a48..3f9dffbe13 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -11,6 +11,9 @@ * 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) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java index 624a009bb7..9908e4940c 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java @@ -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 * registers obstructions. diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index e2adbaf2d0..336a560042 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -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. @Override From ed3d1c62836b72983f0c5b847e6600bb0c478758 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 11 Feb 2021 09:34:27 +0000 Subject: [PATCH 06/11] Don't set playback parameters when using tunneling Issue: #4803 PiperOrigin-RevId: 356923345 --- RELEASENOTES.md | 3 ++ .../exoplayer2/audio/DefaultAudioSink.java | 42 +++++++++++++------ .../audio/DefaultAudioSinkTest.java | 14 +++++++ 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3f9dffbe13..4e46be344f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,6 +5,9 @@ * Core library: * Fix playback issue for HLS live streams without program date time information ([#8560](https://github.com/google/ExoPlayer/issues/8560)). + * Fix a bug where setting playback parameters while using video tunneling + would cause an error to be thrown + ([#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). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 8f16df115a..826839b3d2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -488,7 +488,6 @@ public final class DefaultAudioSink implements AudioSink { throws ConfigurationException { int inputPcmFrameSize; @Nullable AudioProcessor[] availableAudioProcessors; - boolean canApplyPlaybackParameters; @OutputMode int outputMode; @C.Encoding int outputEncoding; @@ -500,11 +499,10 @@ public final class DefaultAudioSink implements AudioSink { 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); @@ -541,7 +539,6 @@ public final class DefaultAudioSink implements AudioSink { } else { inputPcmFrameSize = C.LENGTH_UNSET; availableAudioProcessors = new AudioProcessor[0]; - canApplyPlaybackParameters = false; outputSampleRate = inputFormat.sampleRate; outputPcmFrameSize = C.LENGTH_UNSET; if (enableOffload && isOffloadedPlaybackSupported(inputFormat, audioAttributes)) { @@ -586,7 +583,6 @@ public final class DefaultAudioSink implements AudioSink { outputEncoding, specifiedBufferSize, enableAudioTrackPlaybackParams, - canApplyPlaybackParameters, availableAudioProcessors); if (isAudioTrackInitialized()) { this.pendingConfiguration = pendingConfiguration; @@ -1336,11 +1332,11 @@ public final class DefaultAudioSink implements AudioSink { 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( @@ -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. * @@ -1897,7 +1918,6 @@ public final class DefaultAudioSink implements AudioSink { public final int outputChannelConfig; @C.Encoding public final int outputEncoding; public final int bufferSize; - public final boolean canApplyPlaybackParameters; public final AudioProcessor[] availableAudioProcessors; public Configuration( @@ -1910,7 +1930,6 @@ public final class DefaultAudioSink implements AudioSink { int outputEncoding, int specifiedBufferSize, boolean enableAudioTrackPlaybackParams, - boolean canApplyPlaybackParameters, AudioProcessor[] availableAudioProcessors) { this.inputFormat = inputFormat; this.inputPcmFrameSize = inputPcmFrameSize; @@ -1919,7 +1938,6 @@ public final class DefaultAudioSink implements AudioSink { 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. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java index 7b9e639cd6..13c62c96b9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java @@ -320,6 +320,20 @@ public final class DefaultAudioSinkTest { 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 { configureDefaultAudioSink(channelCount, /* trimStartFrames= */ 0, /* trimEndFrames= */ 0); } From d48d59bda1c18587ea10fd92acddbc0bb4ab5b1d Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 11 Feb 2021 13:13:30 +0000 Subject: [PATCH 07/11] Propagate DRM config when creating ad media sources The `DrmConfiguration.sessionForClearTypes` property is often used to ensure a secure decoder is used for clear ads played in encrypted content. This is because some devices show black frames when switching decoders. Before this change the DRM config isn't propagated down when constructing the ad media source, meaning `DrmSessionManager.DRM_UNSUPPORTED` is always used, which will cause playback to switch from secure to clear decoder when transitioning to an ad break (ignoring the MediaItem `sessionForClearTypes` option. Issue: #8568 PiperOrigin-RevId: 356951124 --- RELEASENOTES.md | 2 ++ .../exoplayer2/source/ads/AdsMediaSource.java | 24 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4e46be344f..07f9a1b010 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -21,6 +21,8 @@ * 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)). ### 2.13.0 (2021-02-04) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 4f2617e868..be1ab81cd3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -318,8 +318,28 @@ public final class AdsMediaSource extends CompositeMediaSource { && 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); } } From 295e8bacb366bc62093662cc9fc54e72ee339039 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 11 Feb 2021 13:47:43 +0000 Subject: [PATCH 08/11] Check if keepalive is enabled before releasing sessions in DDSM.release If keepalive is disabled the existing code over-eagerly releases DrmSession instances. This is arguably OK since a (Default)DrmSession should be released before its (Default)Manager is released (since the underlying MediaDrm instance might be released when the manager is released). And if all sessions are released before the manager is released then `sessions` is empty, so the loop is a no-op. Issue: #8576 PiperOrigin-RevId: 356955308 --- RELEASENOTES.md | 3 +++ .../drm/DefaultDrmSessionManager.java | 14 +++++----- .../drm/DefaultDrmSessionManagerTest.java | 26 +++++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 07f9a1b010..ec1174a244 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -23,6 +23,9 @@ ([#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) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index 67cb095b8d..d24d5ce847 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -457,12 +457,14 @@ public class DefaultDrmSessionManager implements DrmSessionManager { if (--prepareCallsCount != 0) { return; } - // Make a local copy, because sessions are removed from this.sessions during release (via - // callback). - List 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 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; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java index 5ac26a76b3..c0b83e7a65 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java @@ -147,6 +147,32 @@ public class DefaultDrmSessionManagerTest { 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) public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() throws Exception { ImmutableList secondSchemeDatas = From 01f57c3c0bc068a2ab66efcfec62e5b242df4903 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 11 Feb 2021 18:10:39 +0000 Subject: [PATCH 09/11] Fix available end time calculation for multi-period DASH live streams The available end time was accidentally substracted by the start time of the last period. To avoid similar time reference confusion in the future, also renaming many variables and methods to clearly reflect the time reference point. And to avoid constant conversion, the processManifest method also attempts to converge to time relative to the start of the window as quickly as possible. Issue: #8537 PiperOrigin-RevId: 357001624 --- RELEASENOTES.md | 3 + .../source/dash/DashMediaSource.java | 136 ++++++++---------- .../source/dash/manifest/Period.java | 4 +- 3 files changed, 62 insertions(+), 81 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ec1174a244..5cb76ec3f6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,6 +8,9 @@ * Fix a bug where setting playback parameters while using video tunneling would cause an error to be thrown ([#8570](https://github.com/google/ExoPlayer/issues/8570)). +* DASH: + * Fix playback issue for multi-period DASH live streams + ([#8537](https://github.com/google/ExoPlayer/issues/8537)). * 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). diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 258ebf3270..d63295adde 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -901,77 +901,54 @@ public final class DashMediaSource extends BaseMediaSource { } } // Update the window. - boolean windowChangingImplicitly = false; + Period firstPeriod = manifest.getPeriod(0); int lastPeriodIndex = manifest.getPeriodCount() - 1; Period lastPeriod = manifest.getPeriod(lastPeriodIndex); long lastPeriodDurationUs = manifest.getPeriodDurationUs(lastPeriodIndex); long nowUnixTimeUs = C.msToUs(Util.getNowUnixTimeMs(elapsedRealtimeOffsetMs)); - // Get the period-relative start/end times. - long currentStartTimeUs = - getAvailableStartTimeUs( - manifest.getPeriod(0), manifest.getPeriodDurationUs(0), nowUnixTimeUs); - long currentEndTimeUs = getAvailableEndTimeUs(lastPeriod, lastPeriodDurationUs, nowUnixTimeUs); - if (manifest.dynamic && !isIndexExplicit(lastPeriod)) { - // The manifest describes an incomplete live stream. Update the start/end times to reflect the - // live stream duration and the manifest's time shift buffer depth. - long liveStreamEndPositionInLastPeriodUs = currentEndTimeUs - C.msToUs(lastPeriod.startMs); - currentEndTimeUs = min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs); - if (manifest.timeShiftBufferDepthMs != C.TIME_UNSET) { - long timeShiftBufferDepthUs = C.msToUs(manifest.timeShiftBufferDepthMs); - long offsetInPeriodUs = currentEndTimeUs - timeShiftBufferDepthUs; - int periodIndex = lastPeriodIndex; - while (offsetInPeriodUs < 0 && periodIndex > 0) { - offsetInPeriodUs += manifest.getPeriodDurationUs(--periodIndex); - } - 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 windowStartTimeInManifestUs = + getAvailableStartTimeInManifestUs( + firstPeriod, manifest.getPeriodDurationUs(0), nowUnixTimeUs); + long windowEndTimeInManifestUs = + getAvailableEndTimeInManifestUs(lastPeriod, lastPeriodDurationUs, nowUnixTimeUs); + boolean windowChangingImplicitly = manifest.dynamic && !isIndexExplicit(lastPeriod); + if (windowChangingImplicitly && manifest.timeShiftBufferDepthMs != C.TIME_UNSET) { + // Update the available start time to reflect the manifest's time shift buffer depth. + long timeShiftBufferStartTimeInManifestUs = + windowEndTimeInManifestUs - C.msToUs(manifest.timeShiftBufferDepthMs); + windowStartTimeInManifestUs = + max(windowStartTimeInManifestUs, timeShiftBufferStartTimeInManifestUs); } - 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; + long windowDurationUs = windowEndTimeInManifestUs - windowStartTimeInManifestUs; + long windowStartUnixTimeMs = C.TIME_UNSET; + long windowDefaultPositionUs = 0; if (manifest.dynamic) { - updateMediaItemLiveConfiguration( - /* nowPeriodTimeUs= */ currentStartTimeUs + nowUnixTimeUs - C.msToUs(windowStartTimeMs), - /* windowStartPeriodTimeUs= */ currentStartTimeUs, - /* windowEndPeriodTimeUs= */ currentEndTimeUs); - windowDefaultStartPositionUs = - nowUnixTimeUs - C.msToUs(windowStartTimeMs + liveConfiguration.targetOffsetMs); - long minimumDefaultStartPositionUs = + checkState(manifest.availabilityStartTimeMs != C.TIME_UNSET); + long nowInWindowUs = + nowUnixTimeUs - C.msToUs(manifest.availabilityStartTimeMs) - windowStartTimeInManifestUs; + updateMediaItemLiveConfiguration(nowInWindowUs, windowDurationUs); + windowStartUnixTimeMs = + manifest.availabilityStartTimeMs + C.usToMs(windowStartTimeInManifestUs); + windowDefaultPositionUs = nowInWindowUs - C.msToUs(liveConfiguration.targetOffsetMs); + long minimumWindowDefaultPositionUs = min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); - if (windowDefaultStartPositionUs < minimumDefaultStartPositionUs) { - // The default start position is too close to the start of the live window. Set it to the - // minimum default start position provided the window is at least twice as big. Else set - // it to the middle of the window. - windowDefaultStartPositionUs = minimumDefaultStartPositionUs; + if (windowDefaultPositionUs < minimumWindowDefaultPositionUs) { + // The default position is too close to the start of the live window. Set it to the minimum + // default position provided the window is at least twice as big. Else set it to the middle + // of the window. + windowDefaultPositionUs = minimumWindowDefaultPositionUs; } } + long offsetInFirstPeriodUs = windowStartTimeInManifestUs - C.msToUs(firstPeriod.startMs); DashTimeline timeline = new DashTimeline( manifest.availabilityStartTimeMs, - windowStartTimeMs, + windowStartUnixTimeMs, elapsedRealtimeOffsetMs, firstPeriodId, - /* offsetInFirstPeriodUs= */ currentStartTimeUs, + offsetInFirstPeriodUs, windowDurationUs, - windowDefaultStartPositionUs, + windowDefaultPositionUs, manifest, mediaItem, manifest.dynamic ? liveConfiguration : null); @@ -1008,8 +985,7 @@ public final class DashMediaSource extends BaseMediaSource { } } - private void updateMediaItemLiveConfiguration( - long nowPeriodTimeUs, long windowStartPeriodTimeUs, long windowEndPeriodTimeUs) { + private void updateMediaItemLiveConfiguration(long nowInWindowUs, long windowDurationUs) { long maxLiveOffsetMs; if (mediaItem.liveConfiguration.maxOffsetMs != C.TIME_UNSET) { maxLiveOffsetMs = mediaItem.liveConfiguration.maxOffsetMs; @@ -1017,7 +993,7 @@ public final class DashMediaSource extends BaseMediaSource { && manifest.serviceDescription.maxOffsetMs != C.TIME_UNSET) { maxLiveOffsetMs = manifest.serviceDescription.maxOffsetMs; } else { - maxLiveOffsetMs = C.usToMs(nowPeriodTimeUs - windowStartPeriodTimeUs); + maxLiveOffsetMs = C.usToMs(nowInWindowUs); } long minLiveOffsetMs; if (mediaItem.liveConfiguration.minOffsetMs != C.TIME_UNSET) { @@ -1026,7 +1002,7 @@ public final class DashMediaSource extends BaseMediaSource { && manifest.serviceDescription.minOffsetMs != C.TIME_UNSET) { minLiveOffsetMs = manifest.serviceDescription.minOffsetMs; } else { - minLiveOffsetMs = C.usToMs(nowPeriodTimeUs - windowEndPeriodTimeUs); + minLiveOffsetMs = C.usToMs(nowInWindowUs - windowDurationUs); if (minLiveOffsetMs < 0 && maxLiveOffsetMs > 0) { // The current time is in the window, so assume all clocks are synchronized and set the // minimum to a live offset of zero. @@ -1052,12 +1028,10 @@ public final class DashMediaSource extends BaseMediaSource { targetOffsetMs = minLiveOffsetMs; } if (targetOffsetMs > maxLiveOffsetMs) { - long windowDurationUs = windowEndPeriodTimeUs - windowStartPeriodTimeUs; - long liveOffsetAtWindowStartUs = nowPeriodTimeUs - windowStartPeriodTimeUs; long safeDistanceFromWindowStartUs = min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); long maxTargetOffsetForSafeDistanceToWindowStartMs = - C.usToMs(liveOffsetAtWindowStartUs - safeDistanceFromWindowStartUs); + C.usToMs(nowInWindowUs - safeDistanceFromWindowStartUs); targetOffsetMs = Util.constrainValue( maxTargetOffsetForSafeDistanceToWindowStartMs, minLiveOffsetMs, maxLiveOffsetMs); @@ -1147,9 +1121,10 @@ public final class DashMediaSource extends BaseMediaSource { return LongMath.divide(intervalUs, 1000, RoundingMode.CEILING); } - private static long getAvailableStartTimeUs( + private static long getAvailableStartTimeInManifestUs( Period period, long periodDurationUs, long nowUnixTimeUs) { - long availableStartTimeUs = 0; + long periodStartTimeInManifestUs = C.msToUs(period.startMs); + long availableStartTimeInManifestUs = periodStartTimeInManifestUs; boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period); for (int i = 0; i < period.adaptationSets.size(); i++) { AdaptationSet adaptationSet = period.adaptationSets.get(i); @@ -1162,23 +1137,26 @@ public final class DashMediaSource extends BaseMediaSource { } @Nullable DashSegmentIndex index = representations.get(0).getIndex(); if (index == null) { - return 0; + return periodStartTimeInManifestUs; } int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); if (availableSegmentCount == 0) { - return 0; + return periodStartTimeInManifestUs; } long firstAvailableSegmentNum = index.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs); - long adaptationSetAvailableStartTimeUs = index.getTimeUs(firstAvailableSegmentNum); - availableStartTimeUs = max(availableStartTimeUs, adaptationSetAvailableStartTimeUs); + long adaptationSetAvailableStartTimeInManifestUs = + 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) { - long availableEndTimeUs = Long.MAX_VALUE; + long periodStartTimeInManifestUs = C.msToUs(period.startMs); + long availableEndTimeInManifestUs = Long.MAX_VALUE; boolean haveAudioVideoAdaptationSets = hasVideoOrAudioAdaptationSets(period); for (int i = 0; i < period.adaptationSets.size(); i++) { AdaptationSet adaptationSet = period.adaptationSets.get(i); @@ -1191,21 +1169,23 @@ public final class DashMediaSource extends BaseMediaSource { } @Nullable DashSegmentIndex index = representations.get(0).getIndex(); if (index == null) { - return periodDurationUs; + return periodStartTimeInManifestUs + periodDurationUs; } int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs); if (availableSegmentCount == 0) { - return 0; + return periodStartTimeInManifestUs; } long firstAvailableSegmentNum = index.getFirstAvailableSegmentNum(periodDurationUs, nowUnixTimeUs); long lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1; - long adaptationSetAvailableEndTimeUs = - index.getTimeUs(lastAvailableSegmentNum) + long adaptationSetAvailableEndTimeInManifestUs = + periodStartTimeInManifestUs + + index.getTimeUs(lastAvailableSegmentNum) + index.getDurationUs(lastAvailableSegmentNum, periodDurationUs); - availableEndTimeUs = min(availableEndTimeUs, adaptationSetAvailableEndTimeUs); + availableEndTimeInManifestUs = + min(availableEndTimeInManifestUs, adaptationSetAvailableEndTimeInManifestUs); } - return availableEndTimeUs; + return availableEndTimeInManifestUs; } private static boolean isIndexExplicit(Period period) { diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java index b472aed50c..b5b852ed7e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java @@ -30,9 +30,7 @@ public class Period { */ @Nullable public final String id; - /** - * The start time of the period in milliseconds. - */ + /** The start time of the period in milliseconds, relative to the start of the manifest. */ public final long startMs; /** From 26371ae40f944a0eeba85ba5e6ca620003bf4b62 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 12 Feb 2021 18:29:21 +0000 Subject: [PATCH 10/11] Bump version to 2.13.1 PiperOrigin-RevId: 357219132 --- RELEASENOTES.md | 10 ++++------ constants.gradle | 4 ++-- .../android/exoplayer2/ExoPlayerLibraryInfo.java | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5cb76ec3f6..b2ac9f0197 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,16 +1,14 @@ # Release notes -### 2.13.1 (not yet released) +### 2.13.1 (2021-02-12) -* Core library: +* Live streaming: * Fix playback issue for HLS live streams without program date time information ([#8560](https://github.com/google/ExoPlayer/issues/8560)). - * Fix a bug where setting playback parameters while using video tunneling - would cause an error to be thrown - ([#8570](https://github.com/google/ExoPlayer/issues/8570)). -* DASH: * 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). diff --git a/constants.gradle b/constants.gradle index bb775e7050..7b73235144 100644 --- a/constants.gradle +++ b/constants.gradle @@ -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. diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index 21f352590c..0315cfc9cd 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -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. @@ -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. From 5807d2e0beacfd157ba32ddb47879ae7c31df2e7 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 12 Feb 2021 22:52:27 +0000 Subject: [PATCH 11/11] Fix RequiresApi annotation #minor-release PiperOrigin-RevId: 357273184 --- .../exoplayer2/drm/DefaultDrmSessionManagerProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerProvider.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerProvider.java index 18f668cc3d..d8a16fc077 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerProvider.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerProvider.java @@ -31,7 +31,6 @@ import java.util.Map; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** Default implementation of {@link DrmSessionManagerProvider}. */ -@RequiresApi(18) public final class DefaultDrmSessionManagerProvider implements DrmSessionManagerProvider { private final Object lock; @@ -92,6 +91,7 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager } } + @RequiresApi(18) private DrmSessionManager createManager(MediaItem.DrmConfiguration drmConfiguration) { HttpDataSource.Factory dataSourceFactory = drmHttpDataSourceFactory != null