Skip to content

fix(presence): read the Discord card's play state from playback intent, not the raw render flag - #314

Merged
ajisth69 merged 1 commit into
mainfrom
fix/discord-presence-play-state
Oct 13, 2026
Merged

ajisth69 merged 1 commit into
mainfrom
fix/discord-presence-play-state

Conversation

@ajisth69

Copy link
Copy Markdown
Member

What

The Discord card told everyone you were Paused while music was playing.

Root cause

MusicPlayerState.isPlaying is ExoPlayer's raw flag, and ExoPlayer reports it false on every stall — the first buffer of a fresh track, a mid-track network hiccup, the re-prepare behind a quality upgrade — while playWhenReady stays true. The player publishes exactly that as isBuffering (which is playWhenReady-gated). The presence publisher pushed a card on every signature change, and the signature carried the raw flag — so every stall on a streaming connection published "Paused" to Discord, flip-flopping back on resume. On a hi-res stream, stalls are most of the listen.

Fix

The card's play state is now isPlaying || isBuffering — the user's intent at the state boundary (isBuffering only ever rides with playWhenReady, and both flags are cleared together by pause(), a fatal error, and cast teardown):

  • buildActivity — timestamps stay up and the asset says play/"Playing" through a stall, so the countdown bar never reads as a frozen paused card mid-buffer.
  • signature — a stall shares the playing signature, so a resume repushes nothing (nothing visible changed) and a stall cannot become a frame on its own.
  • A genuine pause still reads Paused with no timestamps, and its signature still differs so the pause repushes.

Also logs one line per frame actually handed to Discord, so a card that never appears is diagnosable as no pushes at all, refused pushes (existing WARN), or Discord dropping them (rate limit / app side).

Tests

  • bufferingWhileTheUserWantsPlaybackStillReadsAsPlaying — stall card reads Playing, bar up.
  • aStallSharesThePlayingSignatureSoItCannotChurnOrBeSwallowed
  • aRealPauseStillReadsAsPausedAndRepushes

Run: ./gradlew :app:testDebugUnitTest --tests "com.lastwave.app.presence.DiscordPresenceTest"

Not yet run — if the "sometimes isn't even detecting" symptom persists after this, logcat with -s DiscordPresence DiscordIpcTransport while reproducing will show whether frames stop flowing or Discord refuses them.

…t, not the raw render flag

MusicPlayerState.isPlaying is ExoPlayer's raw flag, and ExoPlayer reports it
false on every stall - the first buffer of a fresh track, a mid-track network
hiccup, the re-prepare behind a quality upgrade - while playWhenReady stays
true. The player publishes exactly that as isBuffering, which is
playWhenReady-gated. The presence publisher pushed a card on every signature
change and the signature carried the raw flag, so every stall on a streaming
connection told Discord "Paused", flip-flopping back on resume. On a hi-res
stream that is most of the listen.

The card's play state is now isPlaying || isBuffering - the user's intent at
the state boundary (isBuffering only ever rides with playWhenReady, and both
flags are cleared together by pause(), a fatal error and cast teardown). Both
buildActivity (timestamps + play/pause asset) and signature read it, so a stall
shares the playing signature: a resume repushes nothing, and neither can a
stall become a frame on its own. A genuine pause still reads Paused with no
timestamps, and its signature still differs so the pause repushes.

Also logs one line per frame actually handed to Discord, so a card that never
appears is diagnosable as no pushes at all, refused pushes (the existing WARN),
or Discord dropping them.

Tests: buffering-while-intending-playback reads Playing with the countdown bar
up, a stall shares the playing signature, and a real pause reads Paused and
repushes.
@ajisth69
ajisth69 merged commit 4adc28f into main Oct 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant