diff --git a/lib/pages/player_page.dart b/lib/pages/player_page.dart index 847df0e..c3f82bb 100644 --- a/lib/pages/player_page.dart +++ b/lib/pages/player_page.dart @@ -11,6 +11,8 @@ class PlayerPage extends StatefulWidget { } class _PlayerPageState extends State { + double? _dragProgress; // 拖动时的临时进度 + String _formatDuration(Duration d) { if (d.inMilliseconds < 0) return '00:00'; final minutes = d.inMinutes; @@ -38,10 +40,20 @@ class _PlayerPageState extends State { final isPlaying = service.isPlaying; final position = service.position; final duration = service.duration; + final bufferedPosition = service.bufferedPosition; + final progress = duration.inMilliseconds > 0 ? position.inMilliseconds / duration.inMilliseconds : 0.0; + final bufferProgress = duration.inMilliseconds > 0 + ? (bufferedPosition.inMilliseconds / duration.inMilliseconds) + .clamp(0.0, 1.0) + : 0.0; + + // 拖动时显示拖动的进度,否则显示实际播放进度 + final displayProgress = _dragProgress ?? progress; + return Scaffold( backgroundColor: const Color(0xFF0E1211), appBar: AppBar( @@ -125,22 +137,95 @@ class _PlayerPageState extends State { ), const SizedBox(height: 40), - // ---- 进度条 ---- +// ---- 双层进度条(视觉与交互分离) ---- Column( children: [ - Slider( - value: progress.clamp(0.0, 1.0), - onChanged: (value) { - final newPosition = Duration( - milliseconds: (value * duration.inMilliseconds).round(), - ); - service.seekTo(newPosition); - }, - activeColor: const Color(0xFFB8D4D0), - inactiveColor: Colors.grey[800], - min: 0.0, - max: 1.0, + SizedBox( + height: 20, + child: LayoutBuilder( + builder: (context, constraints) { + final width = constraints.maxWidth; + + final bufferWidth = + width * bufferProgress.clamp(0.0, 1.0); + final progressWidth = width * progress.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), + ), + ), + + // ② 缓冲层(深青色) + 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, + ), + ), + child: Slider( + value: progress.clamp(0.0, 1.0), + min: 0.0, + max: 1.0, + onChanged: (value) { + // 拖动时只更新本地状态,不触发 seek + setState(() { + _dragProgress = value; + }); + }, + onChangeEnd: (value) { + final newPosition = Duration( + milliseconds: + (value * duration.inMilliseconds) + .round(), + ); + service.seekTo(newPosition); + setState(() { + _dragProgress = null; + }); + }, + ), + ), + ), + ], + ); + }, + ), ), + // 时间显示 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index ba3153d..6f2453a 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -1,5 +1,5 @@ // lib/services/audio_service.dart -import 'dart:async'; // ⬅️ 添加这个导入 +import 'dart:async'; import 'package:flutter/material.dart'; import 'playback_service.dart'; @@ -22,6 +22,7 @@ class AudioService extends ChangeNotifier { bool _isPlaying = false; Duration _position = Duration.zero; Duration _duration = Duration.zero; + Duration _bufferedPosition = Duration.zero; // ✅ 新增:缓冲位置 bool _listening = false; final List _subscriptions = []; @@ -30,12 +31,13 @@ class AudioService extends ChangeNotifier { bool get isPlaying => _isPlaying; Duration get position => _position; Duration get duration => _duration; + Duration get bufferedPosition => _bufferedPosition; // ✅ 新增 getter - // ✅ 完整的播放入口 Future playSong(Song song) async { _currentSong = song; _position = Duration.zero; _duration = Duration.zero; + _bufferedPosition = Duration.zero; _startListening(); notifyListeners(); @@ -44,11 +46,9 @@ class AudioService extends ChangeNotifier { return; } - // 播放由 PlaybackService 执行,状态由 stream 更新 await PlaybackService().play(song.url!); } - // ✅ 只调用播放器方法,不手动修改 _isPlaying void togglePlay() { if (_currentSong == null) return; @@ -57,7 +57,6 @@ class AudioService extends ChangeNotifier { } else { PlaybackService().resume(); } - // _isPlaying 由 player.stream.playing 更新 } void stopPlay() { @@ -65,6 +64,7 @@ class AudioService extends ChangeNotifier { _isPlaying = false; _position = Duration.zero; _duration = Duration.zero; + _bufferedPosition = Duration.zero; _stopListening(); notifyListeners(); } @@ -75,7 +75,6 @@ class AudioService extends ChangeNotifier { notifyListeners(); } - // ✅ 直接监听 media_kit 的三个独立 Stream void _startListening() { if (_listening) return; _listening = true; @@ -108,6 +107,16 @@ class AudioService extends ChangeNotifier { } }), ); + + // ✅ 新增:监听缓冲位置 + _subscriptions.add( + player.stream.buffer.listen((buffer) { + if (_bufferedPosition != buffer) { + _bufferedPosition = buffer; + notifyListeners(); + } + }), + ); } void _stopListening() {