Pausing during buffering does nothing — playback can resume in the background after leaving the app #6

Closed
opened 2026-08-26 01:53:25 +02:00 by benjamin · 1 comment
Owner

Problem

PlayerController.pause() (PlayerController.kt:97) and toggle() (PlayerController.kt:93) both gate on player.isPlaying. In Media3 isPlaying is false while playbackState == STATE_BUFFERING, even with playWhenReady == true. So during a rebuffer:

  • pause() is a no-op — playWhenReady stays true
  • toggle() takes the else branch and calls resume() instead of pausing

MainActivity.onStop() calls state.player.pause() with the comment "TV-NP: video must not keep playing when the user leaves the app" — but on the buffering path that call does nothing.

Failure scenario

The stream stalls to rebuffer, the user presses Home. pause() no-ops, playWhenReady remains true, buffering completes in the background and audio starts playing behind the launcher. This is exactly the behaviour the TV app quality guideline (TV-NP) forbids, and it burns a provider connection while nobody is watching.

The remote's pause button hits the same dead path whenever it is pressed during a stall.

Suggested fix

Set the intent unconditionally instead of inspecting isPlaying:

fun pause() { player.playWhenReady = false }
fun toggle() { if (player.playWhenReady) pause() else resume() }

Found by multi-agent code review; verified against current PlayerController.kt and MainActivity.kt.

## Problem `PlayerController.pause()` (`PlayerController.kt:97`) and `toggle()` (`PlayerController.kt:93`) both gate on `player.isPlaying`. In Media3 `isPlaying` is `false` while `playbackState == STATE_BUFFERING`, even with `playWhenReady == true`. So during a rebuffer: - `pause()` is a no-op — `playWhenReady` stays `true` - `toggle()` takes the `else` branch and calls `resume()` instead of pausing `MainActivity.onStop()` calls `state.player.pause()` with the comment "TV-NP: video must not keep playing when the user leaves the app" — but on the buffering path that call does nothing. ## Failure scenario The stream stalls to rebuffer, the user presses Home. `pause()` no-ops, `playWhenReady` remains `true`, buffering completes in the background and audio starts playing behind the launcher. This is exactly the behaviour the TV app quality guideline (TV-NP) forbids, and it burns a provider connection while nobody is watching. The remote's pause button hits the same dead path whenever it is pressed during a stall. ## Suggested fix Set the intent unconditionally instead of inspecting `isPlaying`: ```kotlin fun pause() { player.playWhenReady = false } fun toggle() { if (player.playWhenReady) pause() else resume() } ``` Found by multi-agent code review; verified against current `PlayerController.kt` and `MainActivity.kt`.
Author
Owner

Fixed in dd612fc, shipped in v0.9.0.

Both methods now act on the intent flag instead of the derived isPlaying:

fun pause() { player.playWhenReady = false }
fun toggle() { if (player.playWhenReady) pause() else resume() }

playWhenReady is true during buffering, so pausing mid-stall now actually pauses, toggle() no longer flips the wrong way, and onStop() reliably stops playback when the viewer leaves the app — no more audio continuing behind the launcher after a rebuffer.

Fixed in dd612fc, shipped in v0.9.0. Both methods now act on the intent flag instead of the derived `isPlaying`: ```kotlin fun pause() { player.playWhenReady = false } fun toggle() { if (player.playWhenReady) pause() else resume() } ``` `playWhenReady` is `true` during buffering, so pausing mid-stall now actually pauses, `toggle()` no longer flips the wrong way, and `onStop()` reliably stops playback when the viewer leaves the app — no more audio continuing behind the launcher after a rebuffer.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: be-nj/castarr#6