From 73d9728f6a5933cd4b21e5d4a9c064988ea4ce48 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 27 Jan 2022 09:27:18 +0000 Subject: [PATCH] Enforce unit speed for ad playback Ad playback shouldn't be affected by manual speed adjustments set by the user. This change enforces unit speed for ad playback. Issue: google/ExoPlayer#9018 PiperOrigin-RevId: 424546258 --- RELEASENOTES.md | 4 +- .../exoplayer2/ExoPlayerImplInternal.java | 9 ++- .../android/exoplayer2/ExoPlayerTest.java | 79 +++++++++++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6b80621e8e..6ee2c0ebeb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -70,10 +70,12 @@ When a `DrmSessionManager` is used by an app in a custom `MediaSource`, the `playbackLooper` needs to be passed to `DrmSessionManager.setPlayer` instead. -* IMA: +* Ad playback / IMA: * Add a method to `AdPlaybackState` to allow resetting an ad group so that it can be played again ([#9615](https://github.com/google/ExoPlayer/issues/9615)). + * Enforce playback speed of 1.0 during ad playback + ([#9018](https://github.com/google/ExoPlayer/issues/9018)). * DASH: * Support the `forced-subtitle` track role ([#9727](https://github.com/google/ExoPlayer/issues/9727)). 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 0485b19826..7d48272bdd 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 @@ -1913,9 +1913,12 @@ import java.util.concurrent.atomic.AtomicBoolean; MediaPeriodId oldPeriodId, long positionForTargetOffsetOverrideUs) { if (!shouldUseLivePlaybackSpeedControl(newTimeline, newPeriodId)) { - // Live playback speed control is unused for the current period, reset speed if adjusted. - if (!mediaClock.getPlaybackParameters().equals(playbackInfo.playbackParameters)) { - mediaClock.setPlaybackParameters(playbackInfo.playbackParameters); + // Live playback speed control is unused for the current period, reset speed to user-defined + // playback parameters or 1.0 for ad playback. + PlaybackParameters targetPlaybackParameters = + newPeriodId.isAd() ? PlaybackParameters.DEFAULT : playbackInfo.playbackParameters; + if (!mediaClock.getPlaybackParameters().equals(targetPlaybackParameters)) { + mediaClock.setPlaybackParameters(targetPlaybackParameters); } 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 886d3c8ed9..923311db17 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 @@ -9324,6 +9324,85 @@ public final class ExoPlayerTest { .onPlaybackParametersChanged(new PlaybackParameters(/* speed= */ 2, /* pitch= */ 2)); } + @Test + public void setPlaybackSpeed_withAdPlayback_onlyAppliesToContent() throws Exception { + // Create renderer with media clock to listen to playback parameter changes. + ArrayList playbackParameters = new ArrayList<>(); + FakeMediaClockRenderer audioRenderer = + new FakeMediaClockRenderer(C.TRACK_TYPE_AUDIO) { + private long positionUs; + + @Override + protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { + this.positionUs = offsetUs; + } + + @Override + public long getPositionUs() { + // Continuously increase position to let playback progress. + positionUs += 10_000; + return positionUs; + } + + @Override + public void setPlaybackParameters(PlaybackParameters parameters) { + playbackParameters.add(parameters); + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return playbackParameters.isEmpty() + ? PlaybackParameters.DEFAULT + : Iterables.getLast(playbackParameters); + } + }; + ExoPlayer player = new TestExoPlayerBuilder(context).setRenderers(audioRenderer).build(); + AdPlaybackState adPlaybackState = + FakeTimeline.createAdPlaybackState( + /* adsPerAdGroup= */ 1, + /* adGroupTimesUs...= */ 0, + 7 * C.MICROS_PER_SECOND, + C.TIME_END_OF_SOURCE); + TimelineWindowDefinition adTimelineDefinition = + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* isLive= */ false, + /* isPlaceholder= */ false, + /* durationUs= */ 10 * C.MICROS_PER_SECOND, + /* defaultPositionUs= */ 0, + /* windowOffsetInFirstPeriodUs= */ 0, + adPlaybackState); + player.setMediaSource( + new FakeMediaSource( + new FakeTimeline(adTimelineDefinition), ExoPlayerTestRunner.AUDIO_FORMAT)); + Player.Listener mockListener = mock(Player.Listener.class); + player.addListener(mockListener); + + player.setPlaybackSpeed(5f); + player.prepare(); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); + player.release(); + + // Assert that the renderer received the playback speed updates at each ad/content boundary. + assertThat(playbackParameters) + .containsExactly( + /* preroll ad */ new PlaybackParameters(1f), + /* content after preroll */ new PlaybackParameters(5f), + /* midroll ad */ new PlaybackParameters(1f), + /* content after midroll */ new PlaybackParameters(5f), + /* postroll ad */ new PlaybackParameters(1f), + /* content after postroll */ new PlaybackParameters(5f)) + .inOrder(); + + // Assert that user-set speed was reported, but none of the ad overrides. + verify(mockListener).onPlaybackParametersChanged(any()); + verify(mockListener).onPlaybackParametersChanged(new PlaybackParameters(5.0f)); + } + @Test public void targetLiveOffsetInMedia_withSetPlaybackParameters_usesPlaybackParameterSpeed() throws Exception {