From f6eb1b108ac6aa16acb90eb2dc309d11d031e306 Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Sun, 23 Aug 2026 21:42:37 +0800 Subject: [PATCH] =?UTF-8?q?=E7=8E=B0=E5=9C=A8=E6=92=AD=E6=94=BE=E9=A1=B5?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E6=97=B6=E6=95=88=E6=9E=9C=E6=9C=89=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/player_page.dart | 255 +++++++++++++++++++------------- lib/services/audio_service.dart | 65 ++++---- 2 files changed, 178 insertions(+), 142 deletions(-) diff --git a/lib/pages/player_page.dart b/lib/pages/player_page.dart index 32386c4..68475e9 100644 --- a/lib/pages/player_page.dart +++ b/lib/pages/player_page.dart @@ -43,6 +43,41 @@ class _PlayerPageState extends State { } } + @override + void initState() { + super.initState(); + // ⭐ 页面加载时主动拉取状态(多次重试) + _syncStateWithRetry(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // ⭐ 依赖变化时(如从后台返回)重新同步 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + context.read().syncPlayerStateNow(); + } + }); + } + + void _syncStateWithRetry() { + final service = context.read(); + + // 立即同步 + service.syncPlayerStateNow(); + + // 延迟重试 + const delays = [200, 500, 800, 1200]; + for (final delay in delays) { + Future.delayed(Duration(milliseconds: delay), () { + if (mounted) { + service.syncPlayerStateNow(); + } + }); + } + } + @override Widget build(BuildContext context) { final service = context.watch(); @@ -75,7 +110,6 @@ class _PlayerPageState extends State { } final isPlaying = service.isPlaying; - final duration = service.duration; return Scaffold( backgroundColor: const Color(0xFF0E1211), @@ -156,7 +190,7 @@ class _PlayerPageState extends State { ], ), const SizedBox(height: 40), - _buildProgressSection(service, duration), + _buildProgressSection(service), const SizedBox(height: 32), _buildControlButtons(service, isPlaying), const SizedBox(height: 16), @@ -166,120 +200,137 @@ class _PlayerPageState extends State { ); } - Widget _buildProgressSection(AudioService service, Duration duration) { - debugPrint('🎯 [PlayerPage] duration=$duration'); + Widget _buildProgressSection(AudioService service) { return ValueListenableBuilder( valueListenable: service.positionNotifier, builder: (context, position, _) { - debugPrint('🎯 [PlayerPage] position=$position, duration=$duration'); - final progress = duration.inMilliseconds > 0 - ? position.inMilliseconds / duration.inMilliseconds - : 0.0; + return ValueListenableBuilder( + valueListenable: service.durationNotifier, + builder: (context, duration, _) { + // ⭐ 如果 duration 为 0,显示加载指示器 + if (duration.inMilliseconds == 0) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: LinearProgressIndicator( + color: Color(0xFFB8D4D0), + backgroundColor: Color(0xFF2A3332), + ), + ); + } - final bufferProgress = duration.inMilliseconds > 0 - ? (service.bufferedNotifier.value.inMilliseconds / - duration.inMilliseconds) - .clamp(0.0, 1.0) - : 0.0; + final progress = duration.inMilliseconds > 0 + ? position.inMilliseconds / duration.inMilliseconds + : 0.0; - final displayProgress = _dragProgress ?? progress; + final bufferProgress = duration.inMilliseconds > 0 + ? (service.bufferedNotifier.value.inMilliseconds / + duration.inMilliseconds) + .clamp(0.0, 1.0) + : 0.0; - return Column( - children: [ - SizedBox( - height: 20, - child: LayoutBuilder( - builder: (context, constraints) { - final width = constraints.maxWidth; - final bufferWidth = width * bufferProgress.clamp(0.0, 1.0); - final progressWidth = width * displayProgress.clamp(0.0, 1.0); + final displayProgress = _dragProgress ?? progress; - return Stack( - alignment: Alignment.centerLeft, - children: [ - Container( - width: width, - height: 4, - decoration: BoxDecoration( - color: Colors.grey[800], - borderRadius: BorderRadius.circular(2), - ), - ), - Container( - width: bufferWidth, - height: 4, - decoration: BoxDecoration( - color: const Color(0xFF4A7A7A), - borderRadius: BorderRadius.circular(2), - ), - ), - Container( - width: progressWidth, - height: 4, - decoration: BoxDecoration( - color: const Color(0xFFB8D4D0), - borderRadius: BorderRadius.circular(2), - ), - ), - Positioned.fill( - child: SliderTheme( - data: SliderTheme.of(context).copyWith( - trackHeight: 0, - activeTrackColor: Colors.transparent, - inactiveTrackColor: Colors.transparent, - thumbColor: Colors.transparent, - overlayColor: Colors.transparent, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 0, + return Column( + children: [ + SizedBox( + height: 20, + child: LayoutBuilder( + builder: (context, constraints) { + final width = constraints.maxWidth; + final bufferWidth = + width * bufferProgress.clamp(0.0, 1.0); + final progressWidth = + width * displayProgress.clamp(0.0, 1.0); + + return Stack( + alignment: Alignment.centerLeft, + children: [ + Container( + width: width, + height: 4, + decoration: BoxDecoration( + color: Colors.grey[800], + borderRadius: BorderRadius.circular(2), ), ), - child: Slider( - value: displayProgress.clamp(0.0, 1.0), - min: 0.0, - max: 1.0, - onChanged: (value) { - setState(() { - _dragProgress = value; - }); - }, - onChangeEnd: (value) { - final newPosition = Duration( - milliseconds: - (value * duration.inMilliseconds).round(), - ); - service.seekTo(newPosition); - setState(() { - _dragProgress = null; - }); - }, + Container( + width: bufferWidth, + height: 4, + decoration: BoxDecoration( + color: const Color(0xFF4A7A7A), + borderRadius: BorderRadius.circular(2), + ), ), - ), - ), - ], - ); - }, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - _formatDuration(position), - style: TextStyle( - fontSize: 12, - color: Colors.grey[500], + Container( + width: progressWidth, + height: 4, + decoration: BoxDecoration( + color: const Color(0xFFB8D4D0), + borderRadius: BorderRadius.circular(2), + ), + ), + Positioned.fill( + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + trackHeight: 0, + activeTrackColor: Colors.transparent, + inactiveTrackColor: Colors.transparent, + thumbColor: Colors.transparent, + overlayColor: Colors.transparent, + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 0, + ), + ), + child: Slider( + value: displayProgress.clamp(0.0, 1.0), + min: 0.0, + max: 1.0, + onChanged: (value) { + setState(() { + _dragProgress = value; + }); + }, + onChangeEnd: (value) { + final newPosition = Duration( + milliseconds: + (value * duration.inMilliseconds) + .round(), + ); + service.seekTo(newPosition); + setState(() { + _dragProgress = null; + }); + }, + ), + ), + ), + ], + ); + }, ), ), - Text( - _formatDuration(duration), - style: TextStyle( - fontSize: 12, - color: Colors.grey[500], - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _formatDuration(position), + style: TextStyle( + fontSize: 12, + color: Colors.grey[500], + ), + ), + Text( + _formatDuration(duration), + style: TextStyle( + fontSize: 12, + color: Colors.grey[500], + ), + ), + ], ), ], - ), - ], + ); + }, ); }, ); diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index b69d33b..329af3e 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -130,9 +130,6 @@ class AudioService extends ChangeNotifier { } } - // ============================================================ - // 核心播放逻辑 - // ============================================================ void _playCurrent() { if (_currentIndex < 0 || _currentIndex >= _queue.length) { stopPlay(); @@ -142,9 +139,6 @@ class AudioService extends ChangeNotifier { final song = _queue[_currentIndex]; _currentSong = song; - // ⭐ 不重置任何 Notifier,完全依赖 stream 推送 - // position / duration / buffered 由播放器自然更新 - _startListening(); notifyListeners(); @@ -154,15 +148,12 @@ class AudioService extends ChangeNotifier { PlaybackService().play(song.url!); - // ⭐ 兜底:主动同步一次播放器状态(解决首次加载时 stream 未推送的问题) + // ⭐ 延迟同步兜底 _syncPlayerStateDelayed(); _loadMetadataForCurrentSong(); } - // ============================================================ - // Metadata 加载 - // ============================================================ Future _loadMetadataForCurrentSong() async { if (_currentIndex < 0 || _currentIndex >= _queue.length) return; final song = _queue[_currentIndex]; @@ -192,9 +183,6 @@ class AudioService extends ChangeNotifier { } } - // ============================================================ - // 播放控制 - // ============================================================ void next() { if (_queue.isEmpty) return; @@ -271,38 +259,39 @@ class AudioService extends ChangeNotifier { notifyListeners(); } - // ============================================================ - // 播放器状态同步(兜底机制) - // ============================================================ - void _syncPlayerStateDelayed() { - _syncPlayerStateNow(); - Future.delayed(const Duration(milliseconds: 200), () { - _syncPlayerStateNow(); - }); - Future.delayed(const Duration(milliseconds: 500), () { - _syncPlayerStateNow(); - }); - } - - void _syncPlayerStateNow() { + // ⭐ 公开方法:供 PlayerPage 主动同步 + void syncPlayerStateNow() { final player = PlaybackService().player; + final pos = player.state.position; final dur = player.state.duration; final buf = player.state.buffer; - if (dur.inMilliseconds > 0 && durationNotifier.value.inMilliseconds == 0) { - durationNotifier.value = dur; - debugPrint('🎯 [AudioService] sync duration: $dur'); - } + debugPrint( + '🎯 [AudioService] syncPlayerStateNow: pos=$pos, dur=$dur, buf=$buf'); - if (buf.inMilliseconds > 0 && bufferedNotifier.value.inMilliseconds == 0) { + // ⭐ 无条件更新(不要只判断 == 0) + if (pos.inMilliseconds >= 0) { + positionNotifier.value = pos; + } + if (dur.inMilliseconds > 0) { + durationNotifier.value = dur; + } + if (buf.inMilliseconds >= 0) { bufferedNotifier.value = buf; - debugPrint('🎯 [AudioService] sync buffer: $buf'); } } - // ============================================================ - // media_kit 状态监听 - // ============================================================ + void _syncPlayerStateDelayed() { + syncPlayerStateNow(); + Future.delayed(const Duration(milliseconds: 200), () { + syncPlayerStateNow(); + }); + Future.delayed(const Duration(milliseconds: 500), () { + syncPlayerStateNow(); + }); + } + + // ---- 监听 ---- void _startListening() { if (_listening) return; _listening = true; @@ -327,7 +316,6 @@ class AudioService extends ChangeNotifier { _subscriptions.add( player.stream.duration.listen((duration) { debugPrint('🎯 [AudioService] durationStream: $duration'); - // 只接受 > 0 的值,避免 0 覆盖正确值 if (duration.inMilliseconds > 0) { durationNotifier.value = duration; } @@ -355,9 +343,6 @@ class AudioService extends ChangeNotifier { _subscriptions.clear(); } - // ============================================================ - // 播放完成处理 - // ============================================================ void _onPlaybackCompleted() { if (_handlingCompletion) { debugPrint('⚠️ [service] completed ignored: already handling');