From ee611a1ab8c683e67e0c0d6da0b88a8b5c98f670 Mon Sep 17 00:00:00 2001 From: kimvde Date: Thu, 3 Apr 2025 07:06:10 -0700 Subject: [PATCH] Access finalAudioSink from playback thread The finalAudioSink was accessed from the main thread but it should only be accessed from the playback thread. PiperOrigin-RevId: 743548450 --- .../media3/transformer/CompositionPlayer.java | 20 ++++------ .../CompositionPlayerInternal.java | 38 +++++++++++++++---- .../PlaybackAudioGraphWrapper.java | 21 +++++++++- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java index 990c1bc19c..35f5445e83 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java @@ -336,7 +336,6 @@ public final class CompositionPlayer extends SimpleBasePlayer private LivePositionSupplier positionSupplier; private LivePositionSupplier bufferedPositionSupplier; private LivePositionSupplier totalBufferedDurationSupplier; - private boolean isSeeking; // "this" reference for position suppliers. @SuppressWarnings("initialization:methodref.receiver.bound.invalid") @@ -506,9 +505,9 @@ public final class CompositionPlayer extends SimpleBasePlayer playWhenReadyChangeReason = PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST; if (playbackState == STATE_READY) { if (playWhenReady) { - finalAudioSink.play(); + checkStateNotNull(compositionPlayerInternal).startRendering(); } else { - finalAudioSink.pause(); + checkStateNotNull(compositionPlayerInternal).stopRendering(); } for (int i = 0; i < players.size(); i++) { players.get(i).setPlayWhenReady(playWhenReady); @@ -587,7 +586,9 @@ public final class CompositionPlayer extends SimpleBasePlayer @Override protected ListenableFuture handleSetVolume(float volume) { this.volume = Util.constrainValue(volume, /* min= */ 0.0f, /* max= */ 1.0f); - finalAudioSink.setVolume(this.volume); + if (compositionPlayerInternal != null) { + compositionPlayerInternal.setVolume(this.volume); + } return Futures.immediateVoidFuture(); } @@ -597,7 +598,6 @@ public final class CompositionPlayer extends SimpleBasePlayer resetLivePositionSuppliers(); CompositionPlayerInternal compositionPlayerInternal = checkStateNotNull(this.compositionPlayerInternal); - isSeeking = true; compositionPlayerInternal.startSeek(positionMs); for (int i = 0; i < players.size(); i++) { players.get(i).seekTo(positionMs); @@ -683,22 +683,17 @@ public final class CompositionPlayer extends SimpleBasePlayer for (int i = 0; i < players.size(); i++) { players.get(i).setPlayWhenReady(false); } - if (!isSeeking) { - // The finalAudioSink cannot be paused more than once. The audio pipeline pauses it during - // a seek, so don't pause here when seeking. - finalAudioSink.pause(); - } + checkStateNotNull(compositionPlayerInternal).stopRendering(); } } else if (endedCount == players.size()) { playbackState = STATE_ENDED; } else { playbackState = STATE_READY; - isSeeking = false; if (oldPlaybackState != STATE_READY && playWhenReady) { for (int i = 0; i < players.size(); i++) { players.get(i).setPlayWhenReady(true); } - finalAudioSink.play(); + checkStateNotNull(compositionPlayerInternal).startRendering(); } } } @@ -799,6 +794,7 @@ public final class CompositionPlayer extends SimpleBasePlayer playbackVideoGraphWrapper, /* listener= */ this, compositionInternalListenerHandler); + compositionPlayerInternal.setVolume(volume); } private void setPrimaryPlayerSequence(ExoPlayer player, EditedMediaItemSequence sequence) { diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayerInternal.java b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayerInternal.java index 910bb23134..07969e70e8 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayerInternal.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayerInternal.java @@ -47,11 +47,14 @@ import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper; } private static final String TAG = "CompPlayerInternal"; - private static final int MSG_SET_OUTPUT_SURFACE_INFO = 1; - private static final int MSG_CLEAR_OUTPUT_SURFACE = 2; - private static final int MSG_START_SEEK = 3; - private static final int MSG_END_SEEK = 4; - private static final int MSG_RELEASE = 5; + private static final int MSG_START_RENDERING = 1; + private static final int MSG_STOP_RENDERING = 2; + private static final int MSG_SET_VOLUME = 3; + private static final int MSG_SET_OUTPUT_SURFACE_INFO = 4; + private static final int MSG_CLEAR_OUTPUT_SURFACE = 5; + private static final int MSG_START_SEEK = 6; + private static final int MSG_END_SEEK = 7; + private static final int MSG_RELEASE = 8; private final Clock clock; private final HandlerWrapper handler; @@ -94,6 +97,18 @@ import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper; // Public methods + public void startRendering() { + handler.sendEmptyMessage(MSG_START_RENDERING); + } + + public void stopRendering() { + handler.sendEmptyMessage(MSG_STOP_RENDERING); + } + + public void setVolume(float volume) { + handler.obtainMessage(MSG_SET_VOLUME, volume).sendToTarget(); + } + /** Sets the output surface information on the video pipeline. */ public void setOutputSurfaceInfo(Surface surface, Size size) { handler @@ -103,7 +118,7 @@ import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper; /** Clears the output surface from the video pipeline. */ public void clearOutputSurface() { - handler.obtainMessage(MSG_CLEAR_OUTPUT_SURFACE).sendToTarget(); + handler.sendEmptyMessage(MSG_CLEAR_OUTPUT_SURFACE); } public void startSeek(long positionMs) { @@ -111,7 +126,7 @@ import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper; } public void endSeek() { - handler.obtainMessage(MSG_END_SEEK).sendToTarget(); + handler.sendEmptyMessage(MSG_END_SEEK); } /** @@ -139,6 +154,15 @@ import androidx.media3.exoplayer.video.PlaybackVideoGraphWrapper; public boolean handleMessage(Message message) { try { switch (message.what) { + case MSG_START_RENDERING: + playbackAudioGraphWrapper.startRendering(); + break; + case MSG_STOP_RENDERING: + playbackAudioGraphWrapper.stopRendering(); + break; + case MSG_SET_VOLUME: + playbackAudioGraphWrapper.setVolume(/* volume= */ (float) message.obj); + break; case MSG_SET_OUTPUT_SURFACE_INFO: setOutputSurfaceInfoOnInternalThread( /* outputSurfaceInfo= */ (OutputSurfaceInfo) message.obj); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/PlaybackAudioGraphWrapper.java b/libraries/transformer/src/main/java/androidx/media3/transformer/PlaybackAudioGraphWrapper.java index fca8afcbf4..09d600ac33 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/PlaybackAudioGraphWrapper.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/PlaybackAudioGraphWrapper.java @@ -47,6 +47,7 @@ import java.util.Objects; private AudioFormat outputAudioFormat; private long outputFramesWritten; private long seekPositionUs; + private boolean isRenderingStarted; /** * Creates an instance. @@ -135,6 +136,24 @@ import java.util.Objects; + sampleCountToDurationUs(outputFramesWritten, outputAudioFormat.sampleRate); } + public void startRendering() { + finalAudioSink.play(); + isRenderingStarted = true; + } + + public void stopRendering() { + if (!isRenderingStarted) { + // The finalAudioSink cannot be paused more than once. + return; + } + finalAudioSink.pause(); + isRenderingStarted = false; + } + + public void setVolume(float volume) { + finalAudioSink.setVolume(volume); + } + /** * Handles the steps that need to be executed for a seek before seeking the upstream players. * @@ -144,7 +163,7 @@ import java.util.Objects; if (positionUs == C.TIME_UNSET) { positionUs = 0; } - finalAudioSink.pause(); + stopRendering(); audioGraph.blockInput(); audioGraph.setPendingStartTimeUs(positionUs); audioGraph.flush();