Skip to content

Commit bc37acd

Browse files
toniheiicbaker
authored andcommitted
Fix missing onPositionAdvancing callback after AudioTrack is stopped
It's currently not triggered at all if the advancing state is not detected before stopping the AudioTrack. This can be fixed by also triggering the callback once stopped, but with fewer checks, as the position is simulated anyway at this point. PiperOrigin-RevId: 785817439 (cherry picked from commit c3e8fc5)
1 parent c653dce commit bc37acd

File tree

3 files changed

+181
-13
lines changed

3 files changed

+181
-13
lines changed

RELEASENOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ This release includes the following changes since the
1616
to call this method on the same thread as ExoPlayer's playback thread or
1717
use a different instance than the one used for playback
1818
([#1191](https://github.com/androidx/media/issues/1191)).
19+
* Audio
20+
* Fix bug where `AnalyticsListener.onAudioPositionAdvancing` is not called
21+
when the audio playback is started very close to the end of the media.
1922
* Session:
2023
* Fix bug where connections from third-party non-priviledged Media3
2124
controllers are ignored.

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioTrackPositionTracker.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -303,21 +303,12 @@ public long getCurrentPositionUs() {
303303
? audioTimestampPoller.getTimestampPositionUs(systemTimeUs, audioTrackPlaybackSpeed)
304304
: getPlaybackHeadPositionEstimateUs(systemTimeUs);
305305

306-
if (audioTrack.getPlayState() == PLAYSTATE_PLAYING) {
307-
if (enableOnAudioPositionAdvancingFix
308-
&& onPositionAdvancingFromPositionUs != C.TIME_UNSET
309-
&& positionUs >= onPositionAdvancingFromPositionUs
310-
&& (useGetTimestampMode || !audioTimestampPoller.isWaitingForAdvancingTimestamp())) {
306+
int audioTrackPlayState = audioTrack.getPlayState();
307+
if (audioTrackPlayState == PLAYSTATE_PLAYING) {
308+
if (useGetTimestampMode || !audioTimestampPoller.isWaitingForAdvancingTimestamp()) {
311309
// Assume the new position is reliable to estimate the playout start time once we have an
312310
// advancing timestamp from the AudioTimestampPoller, or we stopped waiting for it.
313-
long mediaDurationSinceResumeUs = positionUs - onPositionAdvancingFromPositionUs;
314-
long playoutDurationSinceLastPositionUs =
315-
Util.getPlayoutDurationForMediaDuration(
316-
mediaDurationSinceResumeUs, audioTrackPlaybackSpeed);
317-
long playoutStartSystemTimeMs =
318-
clock.currentTimeMillis() - Util.usToMs(playoutDurationSinceLastPositionUs);
319-
onPositionAdvancingFromPositionUs = C.TIME_UNSET;
320-
listener.onPositionAdvancing(playoutStartSystemTimeMs);
311+
maybeTriggerOnPositionAdvancingCallback(positionUs);
321312
}
322313

323314
if (lastSystemTimeUs != C.TIME_UNSET) {
@@ -357,6 +348,10 @@ public long getCurrentPositionUs() {
357348

358349
lastSystemTimeUs = systemTimeUs;
359350
lastPositionUs = positionUs;
351+
} else if (audioTrackPlayState == PLAYSTATE_STOPPED) {
352+
// Once stopped, the position is simulated anyway and we don't need to wait for the timestamp
353+
// poller to produce reliable data.
354+
maybeTriggerOnPositionAdvancingCallback(positionUs);
360355
}
361356

362357
return positionUs;
@@ -506,6 +501,22 @@ private boolean hasPendingAudioTrackUnderruns() {
506501
return result;
507502
}
508503

504+
private void maybeTriggerOnPositionAdvancingCallback(long positionUs) {
505+
if (!enableOnAudioPositionAdvancingFix
506+
|| onPositionAdvancingFromPositionUs == C.TIME_UNSET
507+
|| positionUs < onPositionAdvancingFromPositionUs) {
508+
return;
509+
}
510+
long mediaDurationSinceResumeUs = positionUs - onPositionAdvancingFromPositionUs;
511+
long playoutDurationSinceLastPositionUs =
512+
Util.getPlayoutDurationForMediaDuration(
513+
mediaDurationSinceResumeUs, audioTrackPlaybackSpeed);
514+
long playoutStartSystemTimeMs =
515+
clock.currentTimeMillis() - Util.usToMs(playoutDurationSinceLastPositionUs);
516+
onPositionAdvancingFromPositionUs = C.TIME_UNSET;
517+
listener.onPositionAdvancing(playoutStartSystemTimeMs);
518+
}
519+
509520
private void maybeSampleSyncParams() {
510521
long systemTimeUs = clock.nanoTime() / 1000;
511522
if (systemTimeUs - lastPlayheadSampleTimeUs >= MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US) {

libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/AudioTrackPositionTrackerTest.java

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
package androidx.media3.exoplayer.audio;
1717

1818
import static com.google.common.truth.Truth.assertThat;
19+
import static org.mockito.ArgumentMatchers.anyLong;
1920
import static org.mockito.Mockito.mock;
21+
import static org.mockito.Mockito.never;
22+
import static org.mockito.Mockito.times;
23+
import static org.mockito.Mockito.verify;
2024

2125
import android.media.AudioFormat;
2226
import android.media.AudioTrack;
@@ -264,6 +268,156 @@ public void getCurrentPositionUs_afterHandleEndOfStreamWithPausePlay_returnsCorr
264268
assertThat(audioTrackPositionTracker.getCurrentPositionUs()).isEqualTo(2_000_000L);
265269
}
266270

271+
@Test
272+
public void onPositionAdvancing_isTriggeredWhenPlaying() {
273+
AudioTrackPositionTracker.Listener listener = mock(AudioTrackPositionTracker.Listener.class);
274+
AudioTrackPositionTracker audioTrackPositionTracker = new AudioTrackPositionTracker(listener);
275+
audioTrackPositionTracker.setClock(clock);
276+
audioTrackPositionTracker.setAudioTrack(
277+
audioTrack,
278+
/* isPassthrough= */ false,
279+
C.ENCODING_PCM_16BIT,
280+
OUTPUT_PCM_FRAME_SIZE,
281+
MIN_BUFFER_SIZE,
282+
/* enableOnAudioPositionAdvancingFix= */ true);
283+
// Start the tracker to set the initial position for advancing check.
284+
audioTrackPositionTracker.start();
285+
audioTrack.play();
286+
// Write data to advance the position.
287+
writeBytesAndAdvanceTime(audioTrack);
288+
289+
// Call getCurrentPositionUs() to request an update.
290+
audioTrackPositionTracker.getCurrentPositionUs();
291+
292+
verify(listener).onPositionAdvancing(anyLong());
293+
}
294+
295+
@Test
296+
public void onPositionAdvancing_isNotTriggeredWhenPaused() {
297+
AudioTrackPositionTracker.Listener listener = mock(AudioTrackPositionTracker.Listener.class);
298+
AudioTrackPositionTracker audioTrackPositionTracker = new AudioTrackPositionTracker(listener);
299+
audioTrackPositionTracker.setClock(clock);
300+
audioTrackPositionTracker.setAudioTrack(
301+
audioTrack,
302+
/* isPassthrough= */ false,
303+
C.ENCODING_PCM_16BIT,
304+
OUTPUT_PCM_FRAME_SIZE,
305+
MIN_BUFFER_SIZE,
306+
/* enableOnAudioPositionAdvancingFix= */ true);
307+
// Start the tracker to set the initial position for advancing check.
308+
audioTrackPositionTracker.start();
309+
audioTrack.play();
310+
// Write data to advance the position.
311+
writeBytesAndAdvanceTime(audioTrack);
312+
// Pause the tracker and audio track.
313+
audioTrackPositionTracker.pause();
314+
audioTrack.pause();
315+
316+
// Call getCurrentPositionUs() while stopped to request an update.
317+
audioTrackPositionTracker.getCurrentPositionUs();
318+
319+
verify(listener, never()).onPositionAdvancing(anyLong());
320+
}
321+
322+
@Test
323+
public void onPositionAdvancing_isTriggeredAgainAfterPauseAndResume() {
324+
AudioTrackPositionTracker.Listener listener = mock(AudioTrackPositionTracker.Listener.class);
325+
AudioTrackPositionTracker audioTrackPositionTracker = new AudioTrackPositionTracker(listener);
326+
audioTrackPositionTracker.setClock(clock);
327+
audioTrackPositionTracker.setAudioTrack(
328+
audioTrack,
329+
/* isPassthrough= */ false,
330+
C.ENCODING_PCM_16BIT,
331+
OUTPUT_PCM_FRAME_SIZE,
332+
MIN_BUFFER_SIZE,
333+
/* enableOnAudioPositionAdvancingFix= */ true);
334+
// Start the tracker to set the initial position for advancing check.
335+
audioTrackPositionTracker.start();
336+
audioTrack.play();
337+
// Write data to advance the position.
338+
writeBytesAndAdvanceTime(audioTrack);
339+
// Call getCurrentPositionUs() to request an initial update.
340+
audioTrackPositionTracker.getCurrentPositionUs();
341+
342+
// Pause the tracker and audio track.
343+
audioTrackPositionTracker.pause();
344+
audioTrack.pause();
345+
// Call getCurrentPositionUs() while paused to request an update.
346+
audioTrackPositionTracker.getCurrentPositionUs();
347+
// Write some more data to advance the position again.
348+
writeBytesAndAdvanceTime(audioTrack);
349+
// Start the tracker again.
350+
audioTrackPositionTracker.start();
351+
audioTrack.play();
352+
// Call getCurrentPositionUs() to request another update.
353+
audioTrackPositionTracker.getCurrentPositionUs();
354+
355+
verify(listener, times(2)).onPositionAdvancing(anyLong());
356+
}
357+
358+
@Test
359+
public void onPositionAdvancing_isTriggeredWhenStopped() {
360+
AudioTrackPositionTracker.Listener listener = mock(AudioTrackPositionTracker.Listener.class);
361+
AudioTrackPositionTracker audioTrackPositionTracker = new AudioTrackPositionTracker(listener);
362+
audioTrackPositionTracker.setClock(clock);
363+
audioTrackPositionTracker.setAudioTrack(
364+
audioTrack,
365+
/* isPassthrough= */ false,
366+
C.ENCODING_PCM_16BIT,
367+
OUTPUT_PCM_FRAME_SIZE,
368+
MIN_BUFFER_SIZE,
369+
/* enableOnAudioPositionAdvancingFix= */ true);
370+
// Start the tracker to set the initial position for advancing check.
371+
audioTrackPositionTracker.start();
372+
audioTrack.play();
373+
// Write data to advance the position.
374+
writeBytesAndAdvanceTime(audioTrack);
375+
// Simulate stopping the track before the advancing callback is triggered.
376+
audioTrackPositionTracker.handleEndOfStream(/* writtenFrames= */ SAMPLE_RATE);
377+
audioTrack.stop();
378+
verify(listener, never()).onPositionAdvancing(anyLong());
379+
380+
// Call getCurrentPositionUs() while the track is stopped to request an update.
381+
audioTrackPositionTracker.getCurrentPositionUs();
382+
383+
verify(listener).onPositionAdvancing(anyLong());
384+
}
385+
386+
@Test
387+
public void onPositionAdvancing_isTriggeredAgainAfterStoppedPauseAndResume() {
388+
AudioTrackPositionTracker.Listener listener = mock(AudioTrackPositionTracker.Listener.class);
389+
AudioTrackPositionTracker audioTrackPositionTracker = new AudioTrackPositionTracker(listener);
390+
audioTrackPositionTracker.setClock(clock);
391+
audioTrackPositionTracker.setAudioTrack(
392+
audioTrack,
393+
/* isPassthrough= */ false,
394+
C.ENCODING_PCM_16BIT,
395+
OUTPUT_PCM_FRAME_SIZE,
396+
MIN_BUFFER_SIZE,
397+
/* enableOnAudioPositionAdvancingFix= */ true);
398+
// Start the tracker to set the initial position for advancing check.
399+
audioTrackPositionTracker.start();
400+
audioTrack.play();
401+
// Write data to advance the position.
402+
writeBytesAndAdvanceTime(audioTrack);
403+
// Simulate stopping the track before the advancing callback is triggered.
404+
audioTrackPositionTracker.handleEndOfStream(/* writtenFrames= */ SAMPLE_RATE);
405+
audioTrack.stop();
406+
// Call getCurrentPositionUs() to request an initial update.
407+
audioTrackPositionTracker.getCurrentPositionUs();
408+
409+
// Pause the tracker.
410+
audioTrackPositionTracker.pause();
411+
// Call getCurrentPositionUs() while paused to request an update.
412+
audioTrackPositionTracker.getCurrentPositionUs();
413+
// Start the tracker again.
414+
audioTrackPositionTracker.start();
415+
// Call getCurrentPositionUs() to request another update.
416+
audioTrackPositionTracker.getCurrentPositionUs();
417+
418+
verify(listener, times(2)).onPositionAdvancing(anyLong());
419+
}
420+
267421
private void writeBytesAndAdvanceTime(AudioTrack audioTrack) {
268422
ByteBuffer byteBuffer = createDefaultSilenceBuffer();
269423
int bytesRemaining = byteBuffer.remaining();

0 commit comments

Comments
 (0)