I have implemented a player app with Kotlin (2.3.21), Jetpack Compose (1.11.2) and Media3 ExoPlayer (1.10.1).
The app has a component that displays the waveform of the track being played, given a "zoom" time-window (e.g. 5 seconds). A visual play-head is locked in the middle of the component, always matching the sample corresponding to ExoPlayer.currentPosition - the waveform scrolls left as the track plays.
Quickly said, I decode tracks with 'miniaudio' and 'FFmpeg' (whichever succeeds first) via C++/JNI and, downmix/summarize samples to a cache and then deliver them to the waveform component when it requests frames as currentPosition progresses.
I am finding it challenging to match the position of the play-head to what the user is hearing.
If I pause the player at a silent region of the track, right before a loud region, I can already hear what's on the loud region.
To explain and isolate this a bit better, I tested with a pure PCM/WAV track that I created with Garage Band.
Clicks 6 sec WAV.wav

The track has a duration of 6000 ms (264600 samples @41000 Hz), 1411 kpbs CBR. It contains 2 "tick" 1/4 notes: at 2000 ms and at 4000 ms.
I load the track in my app and play it.
Then I pause it right before the play-head reaches the first note.
ExoPlayer reports a currentPosition of 1909 ms which is right before the "tick" (almost 100 ms ahead of it!), but I can clearly hear the "tick":
Note that the waveform component is rendering the part of the wafeform precisely matching currentPosition.
Next, in order to resume playing, I first exoPlayer.seekTo(1909) then exoPlayer.play(). This time I can hear the "tick" but not entirely. It sounds more like "_ick", like if it had started from 2010 ms loosing the "attack" (but logging is telling us that currentPosition is progressing from 1909).
The reason I call seekTo() is because if I don't, sometimes I observe play() resuming from like 1980 ms (so like 70 ms from where it paused), but I hear what's way past 2000 ms (I hear like "___k").
Thus, It looks like there are at least 100 ms of frames "leaked to the speaker" on exoPlayer.pause(). I am testing with a Sasung Galaxy S23 Ultra. I have observed audio "leakage" of up to 150 ms, but usually it is of about 50~80 ms.
Is there anything that can be done in order to mitigate that?
Ideally, it would be great if ExoPlayer stopped playing audio at the point where it is asked to .pause() and resumed right from where it was paused.
I have implemented a player app with Kotlin (2.3.21), Jetpack Compose (1.11.2) and Media3 ExoPlayer (1.10.1).
The app has a component that displays the waveform of the track being played, given a "zoom" time-window (e.g. 5 seconds). A visual play-head is locked in the middle of the component, always matching the sample corresponding to
ExoPlayer.currentPosition- the waveform scrolls left as the track plays.Quickly said, I decode tracks with 'miniaudio' and 'FFmpeg' (whichever succeeds first) via C++/JNI and, downmix/summarize samples to a cache and then deliver them to the waveform component when it requests frames as
currentPositionprogresses.I am finding it challenging to match the position of the play-head to what the user is hearing.
If I pause the player at a silent region of the track, right before a loud region, I can already hear what's on the loud region.
To explain and isolate this a bit better, I tested with a pure PCM/WAV track that I created with Garage Band.

Clicks 6 sec WAV.wav
The track has a duration of 6000 ms (264600 samples @41000 Hz), 1411 kpbs CBR. It contains 2 "tick" 1/4 notes: at 2000 ms and at 4000 ms.
I load the track in my app and play it.
Then I pause it right before the play-head reaches the first note.
ExoPlayer reports a
currentPositionof1909 mswhich is right before the "tick" (almost 100 ms ahead of it!), but I can clearly hear the "tick":Note that the waveform component is rendering the part of the wafeform precisely matching
currentPosition.Next, in order to resume playing, I first
exoPlayer.seekTo(1909)thenexoPlayer.play(). This time I can hear the "tick" but not entirely. It sounds more like "_ick", like if it had started from 2010 ms loosing the "attack" (but logging is telling us thatcurrentPositionis progressing from 1909).The reason I call
seekTo()is because if I don't, sometimes I observeplay()resuming from like 1980 ms (so like 70 ms from where it paused), but I hear what's way past 2000 ms (I hear like "___k").Thus, It looks like there are at least 100 ms of frames "leaked to the speaker" on
exoPlayer.pause(). I am testing with a Sasung Galaxy S23 Ultra. I have observed audio "leakage" of up to 150 ms, but usually it is of about 50~80 ms.Is there anything that can be done in order to mitigate that?
Ideally, it would be great if ExoPlayer stopped playing audio at the point where it is asked to
.pause()and resumed right from where it was paused.