Skip to content

feat: show the controls when play/pause comes from outside the controls - #957

Open
Ortes wants to merge 10 commits into
fluttercommunity:masterfrom
Ortes:feat/external-playpause-controls
Open

feat: show the controls when play/pause comes from outside the controls#957
Ortes wants to merge 10 commits into
fluttercommunity:masterfrom
Ortes:feat/external-playpause-controls

Conversation

@Ortes

@Ortes Ortes commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What

When playback is toggled from outside the controls — hardware media keys (▶⏸/F8, handled natively by the browser on web), Android headset/notification controls, the MediaSession API, or the embedding app driving the VideoPlayerController — the video starts/stops but MaterialDesktopControls gives no visual feedback. Pressing space (which goes through the controls' own _playPause) reveals the controls; the media key does not.

With this change any isPlaying flip reveals the controls and restarts the hide timer, so the state change is always visible and the controls auto-hide again after hideControlsTimer.

How

_updateState (already registered as a listener on the VideoPlayerController) compares the incoming controller.value.isPlaying against the previous snapshot _latestValue — which now gets an initializer so its first read can't throw — and calls the existing _cancelAndRestartTimer() on a flip. No new state, no platform-specific code: every platform implementation funnels external play/pause into value.isPlaying (isPlayingStateUpdate), so this covers web media keys, ExoPlayer's onIsPlayingChanged, iOS Control Center, etc.

Tests

test/external_playpause_test.dart drives an externally-mutated controller and covers: reveal + auto-hide on external play, reveal + auto-hide on external pause, and no reveal on position-only updates.

dart format, dart analyze lib test, and the full flutter test suite are clean.

@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.62%. Comparing base (7a4631c) to head (b117ffd).
⚠️ Report is 15 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #957      +/-   ##
==========================================
+ Coverage   51.21%   56.62%   +5.40%     
==========================================
  Files          25       25              
  Lines        1728     1759      +31     
==========================================
+ Hits          885      996     +111     
+ Misses        843      763      -80     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread lib/src/material/material_desktop_controls.dart Outdated
Comment thread lib/src/cupertino/cupertino_controls.dart Outdated
Ortes added a commit to Ortes/chewie that referenced this pull request Aug 12, 2026
@Ortes
Ortes force-pushed the feat/external-playpause-controls branch from f89d717 to 4bfbe00 Compare August 12, 2026 07:25
Ortes added 10 commits August 15, 2026 13:07
Hardware media keys (play/pause handled natively by the browser) and
MediaSession toggle the video element directly: playback changes but the
controls give no visual feedback. Watch for isPlaying flips in
_updateState and mirror _playPause: reveal-and-hold on pause,
reveal-then-autohide on resume.
Compare against _latestValue (now initialized from the controller) instead
of a dedicated field, and drop the pause special-case: the controls reveal
and auto-hide the same way for both directions.
_latestValue no longer carries a field initializer that reads the late
`controller`, which is only resolved in didChangeDependencies. The
storage field is nullable and seeded where `controller` is resolved,
behind a non-null getter that mirrors the existing
_chewieController/chewieController pattern in this class.
VideoPlayerController inherits a public `value` setter from
ValueNotifier, so an external play/pause can be simulated without
subclassing it. The auto-hide waits now derive from
ChewieController.defaultHideControlsTimer instead of a magic 4s.
_updateState treated every isPlaying flip alike and restarted the hide
timer, so a pause auto-hid the controls after hideControlsTimer. That
included pauses coming from the controls themselves: _playPause and the
bottom-bar button deliberately cancel the timer so a paused video keeps
its controls up. External play/pause now mirrors _playPause per
direction: reveal and re-arm the timer on play, reveal and hold on pause.
MaterialControls and CupertinoControls carried the same gap as the
desktop controls: play/pause reaching the VideoPlayerController from
outside them — headset buttons, notification and Control Center
controls, or the embedder driving the controller — changed playback with
no visual feedback. Both now mirror _playPause per direction, with
_latestValue given the same nullable-plus-seeded treatment so the
comparison has a previous value to read on the first update.

The tests run the whole matrix against all three control
implementations.
The declaration goes back to master's `late VideoPlayerValue
_latestValue;` and the field is assigned one line after `controller` is
resolved, so neither the nullable storage field nor the non-null getter
is needed and the existing read sites stay untouched.

Write-before-read is what master already relies on: its first write
happens in _updateState, reached from _initialize, itself called from
didChangeDependencies. Assigning at the top of didChangeDependencies
moves that first write earlier on every path, so the window in which the
field is valid only grows.
@Ortes
Ortes force-pushed the feat/external-playpause-controls branch from 4bfbe00 to b117ffd Compare August 15, 2026 11:13
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.

2 participants