mirror of
https://github.com/samsonjs/media.git
synced 2026-04-22 14:05:55 +00:00
Work around AudioTrack Bluetooth connection issue
If connecting a Bluetooth audio device fails, the AudioTrack may be left in a bad state, where it is not actually playing and its position has jumped back to zero. Detect and work around this case by resetting the track. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=172600912
This commit is contained in:
parent
b8ef1dcc78
commit
eb08e1a5c3
1 changed files with 42 additions and 2 deletions
|
|
@ -293,8 +293,6 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
|
||||
@Override
|
||||
public long getCurrentPositionUs(boolean sourceEnded) {
|
||||
// If the device supports it, use the playback timestamp from AudioTrack.getTimestamp.
|
||||
// Otherwise, derive a smoothed position by sampling the track's frame position.
|
||||
if (!hasCurrentPositionUs()) {
|
||||
return CURRENT_POSITION_NOT_SET;
|
||||
}
|
||||
|
|
@ -303,6 +301,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
maybeSampleSyncParams();
|
||||
}
|
||||
|
||||
// If the device supports it, use the playback timestamp from AudioTrack.getTimestamp.
|
||||
// Otherwise, derive a smoothed position by sampling the track's frame position.
|
||||
long systemClockUs = System.nanoTime() / 1000;
|
||||
long positionUs;
|
||||
if (audioTimestampSet) {
|
||||
|
|
@ -638,6 +638,13 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
inputBuffer = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (audioTrackUtil.needsReset(getWrittenFrames())) {
|
||||
Log.w(TAG, "Resetting stalled audio track");
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1292,6 +1299,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
*/
|
||||
private static class AudioTrackUtil {
|
||||
|
||||
private static final long FORCE_RESET_WORKAROUND_TIMEOUT_MS = 200;
|
||||
|
||||
protected AudioTrack audioTrack;
|
||||
private boolean needsPassthroughWorkaround;
|
||||
private int sampleRate;
|
||||
|
|
@ -1300,6 +1309,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
private long passthroughWorkaroundPauseOffset;
|
||||
|
||||
private long stopTimestampUs;
|
||||
private long forceResetWorkaroundTimeMs;
|
||||
private long stopPlaybackHeadPosition;
|
||||
private long endPlaybackHeadPosition;
|
||||
|
||||
|
|
@ -1314,6 +1324,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
this.audioTrack = audioTrack;
|
||||
this.needsPassthroughWorkaround = needsPassthroughWorkaround;
|
||||
stopTimestampUs = C.TIME_UNSET;
|
||||
forceResetWorkaroundTimeMs = C.TIME_UNSET;
|
||||
lastRawPlaybackHeadPosition = 0;
|
||||
rawPlaybackHeadWrapCount = 0;
|
||||
passthroughWorkaroundPauseOffset = 0;
|
||||
|
|
@ -1348,6 +1359,17 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
audioTrack.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the track is in an invalid state and must be reset.
|
||||
*
|
||||
* @see #getPlaybackHeadPosition()
|
||||
*/
|
||||
public boolean needsReset(long writtenFrames) {
|
||||
return forceResetWorkaroundTimeMs != C.TIME_UNSET && writtenFrames > 0
|
||||
&& SystemClock.elapsedRealtime() - forceResetWorkaroundTimeMs
|
||||
>= FORCE_RESET_WORKAROUND_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AudioTrack#getPlaybackHeadPosition()} returns a value intended to be interpreted as an
|
||||
* unsigned 32 bit integer, which also wraps around periodically. This method returns the
|
||||
|
|
@ -1380,6 +1402,24 @@ public final class DefaultAudioSink implements AudioSink {
|
|||
}
|
||||
rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset;
|
||||
}
|
||||
|
||||
if (Util.SDK_INT <= 26) {
|
||||
if (rawPlaybackHeadPosition == 0 && lastRawPlaybackHeadPosition > 0
|
||||
&& state == PLAYSTATE_PLAYING) {
|
||||
// If connecting a Bluetooth audio device fails, the AudioTrack may be left in a state
|
||||
// where its Java API is in the playing state, but the native track is stopped. When this
|
||||
// happens the playback head position gets stuck at zero. In this case, return the old
|
||||
// playback head position and force the track to be reset after
|
||||
// {@link #FORCE_RESET_WORKAROUND_TIMEOUT_MS} has elapsed.
|
||||
if (forceResetWorkaroundTimeMs == C.TIME_UNSET) {
|
||||
forceResetWorkaroundTimeMs = SystemClock.elapsedRealtime();
|
||||
}
|
||||
return lastRawPlaybackHeadPosition;
|
||||
} else {
|
||||
forceResetWorkaroundTimeMs = C.TIME_UNSET;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastRawPlaybackHeadPosition > rawPlaybackHeadPosition) {
|
||||
// The value must have wrapped around.
|
||||
rawPlaybackHeadWrapCount++;
|
||||
|
|
|
|||
Loading…
Reference in a new issue