import 'dart:async'; /// Per-screen state for Android display frame-rate matching: the retry /// counter for backends that detect fps only after rendering, whether a /// switch was already applied for the current item, and the MediaSession /// pause-suppression window armed around HDMI renegotiations. /// /// One instance lives on the player screen; the open/reload pipelines call /// [resetForNewItem] before each open and the display-matching paths flip /// [applied]/[retries] as they negotiate. class FrameRateMatcher { /// Retries left for late fps detection (ExoPlayer reports container fps /// only after ~8 rendered frames). int retries = 0; /// Whether a display switch was already applied for the current item — /// the post-first-frame path bails instead of switching twice. bool applied = false; Timer? _mediaPauseSuppressionTimer; /// Whether a MediaSession PauseEvent should be ignored right now because /// the display is (or may still be) renegotiating HDMI. Fire Stick (and /// similar Android TV devices) send onPause() through the MediaSession /// callback when the display mode changes for frame rate matching. bool get suppressesMediaPause => _mediaPauseSuppressionTimer?.isActive ?? false; /// Arm the pause-suppression window around an HDMI renegotiation. The /// window outlasts the switch by a safety margin on top of the user's /// configured extra delay. void beginSuppressWindow(int delaySec) { _mediaPauseSuppressionTimer?.cancel(); _mediaPauseSuppressionTimer = Timer(Duration(seconds: 2 + delaySec + 1), () { _mediaPauseSuppressionTimer = null; }); } /// Reset the per-item negotiation state before opening new media. void resetForNewItem() { retries = 0; applied = false; } /// Cancel any active suppression window when the owning screen is disposed. void dispose() { _mediaPauseSuppressionTimer?.cancel(); _mediaPauseSuppressionTimer = null; } }