mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-28 23:08:59 +02:00
fix(player): pause motion header video when its screen is hidden
The banner only reacted to app lifecycle, so the HEVC decoder and HLS stream kept running behind covered routes and inactive tabs. It now follows TickerMode: the Navigator already disables it for covered routes, and the shell disables it for non-current tabs (which also mutes stray animations on kept-alive pages).
This commit is contained in:
@@ -594,9 +594,15 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
itemCount: tabs.length,
|
||||
onPageChanged: _onPageChanged,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
// TickerMode mutes animations and lets visibility-aware widgets
|
||||
// (e.g. MotionHeaderBanner) pause when their tab is hidden —
|
||||
// kept-alive pages otherwise keep running offscreen.
|
||||
itemBuilder: (context, index) => _KeepAliveTabPage(
|
||||
key: ValueKey('page-$index'),
|
||||
child: tabs[index],
|
||||
child: TickerMode(
|
||||
enabled: index == _currentIndex,
|
||||
child: tabs[index],
|
||||
),
|
||||
),
|
||||
),
|
||||
builder: (context, child) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/foundation.dart' show ValueListenable;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart';
|
||||
@@ -27,6 +28,7 @@ class _MotionHeaderBannerState extends State<MotionHeaderBanner>
|
||||
VideoPlayerController? _controller;
|
||||
bool _ready = false;
|
||||
bool _failed = false;
|
||||
ValueListenable<TickerModeData>? _tickerMode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -35,6 +37,38 @@ class _MotionHeaderBannerState extends State<MotionHeaderBanner>
|
||||
_initialize();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
// TickerMode is off while this subtree is hidden: covered by an opaque
|
||||
// route (Navigator offstages it) or on an inactive shell tab. Follow it
|
||||
// so the decoder doesn't keep running behind other screens.
|
||||
final notifier = TickerMode.getValuesNotifier(context);
|
||||
if (!identical(notifier, _tickerMode)) {
|
||||
_tickerMode?.removeListener(_syncPlayback);
|
||||
_tickerMode = notifier;
|
||||
notifier.addListener(_syncPlayback);
|
||||
}
|
||||
_syncPlayback();
|
||||
}
|
||||
|
||||
bool get _visible {
|
||||
final lifecycle = WidgetsBinding.instance.lifecycleState;
|
||||
final appVisible =
|
||||
lifecycle == null || lifecycle == AppLifecycleState.resumed;
|
||||
return appVisible && (_tickerMode?.value.enabled ?? true);
|
||||
}
|
||||
|
||||
void _syncPlayback() {
|
||||
final controller = _controller;
|
||||
if (controller == null || !_ready) return;
|
||||
if (_visible) {
|
||||
controller.play();
|
||||
} else {
|
||||
controller.pause();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(MotionHeaderBanner oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
@@ -67,8 +101,8 @@ class _MotionHeaderBannerState extends State<MotionHeaderBanner>
|
||||
}
|
||||
await controller.setVolume(0);
|
||||
await controller.setLooping(true);
|
||||
await controller.play();
|
||||
setState(() => _ready = true);
|
||||
_syncPlayback();
|
||||
} catch (e) {
|
||||
_log.w('Failed to play motion banner: $e');
|
||||
if (!mounted) return;
|
||||
@@ -84,18 +118,12 @@ class _MotionHeaderBannerState extends State<MotionHeaderBanner>
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
final controller = _controller;
|
||||
if (controller == null || !_ready) return;
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
controller.play();
|
||||
} else if (state == AppLifecycleState.paused ||
|
||||
state == AppLifecycleState.inactive) {
|
||||
controller.pause();
|
||||
}
|
||||
_syncPlayback();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tickerMode?.removeListener(_syncPlayback);
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_disposeController();
|
||||
super.dispose();
|
||||
|
||||
Reference in New Issue
Block a user