20 changed files with 257 additions and 191 deletions
+1
View File
@@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<application <application
android:name="${applicationName}" android:name="${applicationName}"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,6 +8,8 @@ import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import java.io.File import java.io.File
import android.content.Context
import android.media.AudioManager
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
@@ -32,9 +34,32 @@ class MainActivity : FlutterActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
requestAudioFocus()
setHighRefreshRate() setHighRefreshRate()
} }
private fun requestAudioFocus() {
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
audioManager.requestAudioFocus(
AudioManager.OnAudioFocusChangeListener { focusChange ->
Log.d("MainActivity", "音频焦点变化: $focusChange")
},
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
)
} else {
@Suppress("DEPRECATION")
audioManager.requestAudioFocus(
null,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
)
}
Log.d("MainActivity", "✅ 已请求音频焦点")
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
+1 -1
View File
@@ -20,7 +20,7 @@ pluginManagement {
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "9.0.1" apply false id("com.android.application") version "9.0.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("org.jetbrains.kotlin.android") version "2.3.20" apply false
} }
include(":app") include(":app")
-32
View File
@@ -1,32 +0,0 @@
// lib/audio/player_controller.dart
import '../services/playback_service.dart'; // ⭐ 修正路径
class PlayerController {
static final PlayerController _instance = PlayerController._internal();
factory PlayerController() => _instance;
PlayerController._internal();
final PlaybackService _playback = PlaybackService();
// ---- 状态流(只读) ----
Stream<bool> get playingStream => _playback.player.stream.playing;
Stream<Duration> get positionStream => _playback.player.stream.position;
Stream<Duration> get durationStream => _playback.player.stream.duration;
Stream<void> get completedStream => _playback.player.stream.completed;
// ---- 当前状态快照 ----
bool get isPlaying => _playback.player.state.playing;
Duration get position => _playback.player.state.position;
Duration get duration => _playback.player.state.duration;
// ---- 控制命令 ----
Future<void> play() => _playback.resume();
Future<void> pause() => _playback.pause();
Future<void> stop() => _playback.stop();
Future<void> seek(Duration position) => _playback.seek(position);
// ---- 加载歌曲 ----
void load(String url, {Map<String, String>? headers}) {
_playback.play(url, headers: headers);
}
}
+69 -57
View File
@@ -5,6 +5,7 @@ import 'package:media_kit/media_kit.dart';
import 'package:audio_service/audio_service.dart' as audio_service; import 'package:audio_service/audio_service.dart' as audio_service;
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/services.dart';
import 'services/audio_service.dart'; import 'services/audio_service.dart';
import 'services/playback_service.dart'; import 'services/playback_service.dart';
import 'services/app_lifecycle_service.dart'; import 'services/app_lifecycle_service.dart';
@@ -15,8 +16,8 @@ import 'pages/home_page.dart';
import 'pages/player_page.dart'; import 'pages/player_page.dart';
import 'pages/playlist_page.dart'; import 'pages/playlist_page.dart';
import 'widgets/global_mini_player.dart'; import 'widgets/global_mini_player.dart';
import 'utils/navigation.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
final stopwatch = Stopwatch(); final stopwatch = Stopwatch();
bool _appInitialized = false; bool _appInitialized = false;
@@ -53,6 +54,8 @@ void main() async {
try { try {
_audioHandler = AudioPlayerHandler(); _audioHandler = AudioPlayerHandler();
debugPrint('⏱️ T0.62 AudioPlayerHandler created'); debugPrint('⏱️ T0.62 AudioPlayerHandler created');
// ⭐ 将 Handler 注入到 AudioService
AudioService().setHandler(_audioHandler!);
} catch (e, st) { } catch (e, st) {
debugPrint('❌ AudioPlayerHandler failed: $e'); debugPrint('❌ AudioPlayerHandler failed: $e');
debugPrint('$st'); debugPrint('$st');
@@ -363,65 +366,74 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
debugPrint( debugPrint(
'🎨 [QTPlayerApp] build #$_buildCount ${stopwatch.elapsedMilliseconds}ms'); '🎨 [QTPlayerApp] build #$_buildCount ${stopwatch.elapsedMilliseconds}ms');
return MaterialApp( // ⭐ 使用 AnnotatedRegion 包裹 MaterialApp,统一控制系统栏样式
title: '清听', return AnnotatedRegion<SystemUiOverlayStyle>(
theme: ThemeData( value: SystemUiOverlayStyle(
brightness: Brightness.dark, statusBarColor: Colors.transparent,
primaryColor: const Color(0xFF7C9A9E), statusBarIconBrightness: Brightness.light,
colorScheme: const ColorScheme.dark( systemNavigationBarColor: const Color(0xFF1A1F1E),
primary: Color(0xFF7C9A9E), systemNavigationBarIconBrightness: Brightness.light,
secondary: Color(0xFFB8D4D0),
surface: Color(0xFF1A1F1E),
onSurface: Colors.white,
),
useMaterial3: true,
), ),
navigatorKey: navigatorKey, child: MaterialApp(
navigatorObservers: [MiniPlayerNavigatorObserver()], title: '清听',
onGenerateRoute: _onGenerateRoute, theme: ThemeData(
builder: (context, child) { brightness: Brightness.dark,
return Stack( primaryColor: const Color(0xFF7C9A9E),
fit: StackFit.expand, colorScheme: const ColorScheme.dark(
children: [ primary: Color(0xFF7C9A9E),
if (child != null) child, secondary: Color(0xFFB8D4D0),
AnimatedBuilder( surface: Color(0xFF1A1F1E),
animation: RouteManager(), onSurface: Colors.white,
builder: (context, _) { ),
final routeManager = RouteManager(); useMaterial3: true,
final shouldShow = routeManager.currentRoute != '/player' && ),
routeManager.currentRoute != '/playlist'; navigatorKey: navigatorKey,
navigatorObservers: [MiniPlayerNavigatorObserver()],
onGenerateRoute: _onGenerateRoute,
builder: (context, child) {
return Stack(
fit: StackFit.expand,
children: [
if (child != null) child,
AnimatedBuilder(
animation: RouteManager(),
builder: (context, _) {
final routeManager = RouteManager();
final shouldShow = routeManager.currentRoute != '/player' &&
routeManager.currentRoute != '/playlist';
return AnimatedSwitcher( return AnimatedSwitcher(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
switchInCurve: Curves.easeOut, switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeIn, switchOutCurve: Curves.easeIn,
transitionBuilder: (child, animation) { transitionBuilder: (child, animation) {
return FadeTransition( return FadeTransition(
opacity: animation, opacity: animation,
child: SlideTransition( child: SlideTransition(
position: Tween<Offset>( position: Tween<Offset>(
begin: const Offset(0, 0.1), begin: const Offset(0, 0.1),
end: Offset.zero, end: Offset.zero,
).animate(animation), ).animate(animation),
child: child, child: child,
),
);
},
child: shouldShow
? const Align(
key: ValueKey('mini_player_visible'),
alignment: Alignment.bottomCenter,
child: GlobalMiniPlayer(),
)
: const SizedBox.shrink(
key: ValueKey('mini_player_hidden'),
), ),
); );
}, },
), child: shouldShow
], ? const Align(
); key: ValueKey('mini_player_visible'),
}, alignment: Alignment.bottomCenter,
child: GlobalMiniPlayer(),
)
: const SizedBox.shrink(
key: ValueKey('mini_player_hidden'),
),
);
},
),
],
);
},
),
); );
} }
} }
+138 -93
View File
@@ -4,49 +4,62 @@ import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:audio_service/audio_service.dart' as audio_service; import 'package:audio_service/audio_service.dart' as audio_service;
import '../audio/player_controller.dart';
import '../audio/playback_state_manager.dart';
import '../services/audio_service.dart';
import '../utils/file_provider_utils.dart';
import 'dart:convert';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'playback_service.dart';
import 'audio_service.dart';
import '../utils/file_provider_utils.dart';
/// 音频服务处理器:连接 media_kit 播放引擎与 Android 系统媒体会话
/// 职责:发布播放状态到系统通知栏,接收系统媒体按钮事件
/// 它不拥有 Player 实例,所有播放控制均委托给 PlaybackService
class AudioPlayerHandler extends audio_service.BaseAudioHandler { class AudioPlayerHandler extends audio_service.BaseAudioHandler {
final PlayerController _player = PlayerController(); final PlaybackService _playback = PlaybackService();
final PlaybackStateManager _state = PlaybackStateManager();
// 当前媒体信息
String? _currentId; String? _currentId;
String? _currentTitle; String? _currentTitle;
String? _currentArtist; String? _currentArtist;
Duration _currentPosition = Duration.zero; Duration _currentPosition = Duration.zero;
DateTime _lastPublishTime = DateTime.now(); DateTime _lastPublishTime = DateTime.now();
static const Duration _publishInterval = Duration(milliseconds: 500); static const Duration _publishInterval = Duration(milliseconds: 500);
// ⭐ 缓存 artwork 文件路径,避免重复写入
String? _currentArtworkPath; String? _currentArtworkPath;
bool _isPublishing = false;
AudioPlayerHandler() { AudioPlayerHandler() {
_player.playingStream.listen((playing) { _bindToPlayer();
_state.updatePlaying(playing); }
_publishState();
// ═══════════════════════════════════════════════════════════════
// 绑定播放器状态流
// ═══════════════════════════════════════════════════════════════
void _bindToPlayer() {
final player = _playback.player;
player.stream.playing.listen((playing) {
_publishPlaybackState(
playing: playing,
position: _currentPosition,
duration: player.state.duration,
);
}); });
_player.positionStream.listen((position) { player.stream.position.listen((position) {
_currentPosition = position; _currentPosition = position;
_state.updatePosition(position);
final now = DateTime.now(); final now = DateTime.now();
if (now.difference(_lastPublishTime) >= _publishInterval) { if (now.difference(_lastPublishTime) >= _publishInterval) {
_lastPublishTime = now; _lastPublishTime = now;
// ⭐ 只保留 _publishStateOnly() _publishPlaybackState(
_publishStateOnly(); playing: player.state.playing,
position: position,
duration: player.state.duration,
onlyPosition: true,
);
} }
}); });
_player.durationStream.listen((duration) { player.stream.duration.listen((duration) {
debugPrint('🎯 [durationStream] duration=$duration');
_state.updateDuration(duration);
if (_currentId != null && _currentTitle != null) { if (_currentId != null && _currentTitle != null) {
_updateMediaItem( _updateMediaItem(
id: _currentId!, id: _currentId!,
@@ -55,45 +68,93 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler {
duration: duration, duration: duration,
); );
} }
_publishState(); _publishPlaybackState(
playing: player.state.playing,
position: _currentPosition,
duration: duration,
);
}); });
} }
// ---- 发布状态 ---- // ═══════════════════════════════════════════════════════════════
void _publishState() { // 外部同步接口(由 AudioService 调用)
final state = _state.playbackState; // ═══════════════════════════════════════════════════════════════
playbackState.add(audio_service.PlaybackState( void syncState(Song? song) {
controls: state.controls, if (song == null) {
processingState: state.processingState, _currentId = null;
playing: state.playing, _currentTitle = null;
androidCompactActionIndices: state.androidCompactActionIndices, _currentArtist = null;
updatePosition: _currentPosition, return;
updateTime: DateTime.now(), }
systemActions: const {
audio_service.MediaAction.seek, _updateMediaItem(
}, id: song.id,
)); title: song.title,
artist: song.artist,
artwork: song.artwork,
);
final player = _playback.player;
_publishPlaybackState(
playing: player.state.playing,
position: _currentPosition,
duration: player.state.duration,
);
} }
void _publishStateOnly() { // ═══════════════════════════════════════════════════════════════
final current = playbackState.value; // 状态发布
// ═══════════════════════════════════════════════════════════════
void _publishPlaybackState({
required bool playing,
required Duration position,
required Duration duration,
bool onlyPosition = false,
}) {
if (_isPublishing) return;
_isPublishing = true;
playbackState.add(audio_service.PlaybackState( try {
controls: current.controls.isNotEmpty final controls = _buildControls(playing);
? current.controls final state = audio_service.PlaybackState(
: _state.playbackState.controls, controls: controls,
processingState: _state.playbackState.processingState, processingState: duration.inMilliseconds > 0
playing: current.playing, ? audio_service.AudioProcessingState.ready
androidCompactActionIndices: current.androidCompactActionIndices, : audio_service.AudioProcessingState.idle,
updatePosition: _currentPosition, playing: playing,
updateTime: DateTime.now(), updatePosition: position,
systemActions: const { updateTime: DateTime.now(),
audio_service.MediaAction.seek, systemActions: const {audio_service.MediaAction.seek},
}, );
)); playbackState.add(state);
} finally {
_isPublishing = false;
}
} }
// ---- 更新媒体信息 ---- List<audio_service.MediaControl> _buildControls(bool playing) {
return [
const audio_service.MediaControl(
androidIcon: 'drawable/ic_previous',
label: '上一曲',
action: audio_service.MediaAction.skipToPrevious,
),
audio_service.MediaControl(
androidIcon: playing ? 'drawable/ic_pause' : 'drawable/ic_play',
label: playing ? '暂停' : '播放',
action: audio_service.MediaAction.playPause,
),
const audio_service.MediaControl(
androidIcon: 'drawable/ic_next',
label: '下一曲',
action: audio_service.MediaAction.skipToNext,
),
];
}
// ═══════════════════════════════════════════════════════════════
// 媒体信息更新
// ═══════════════════════════════════════════════════════════════
void _updateMediaItem({ void _updateMediaItem({
required String id, required String id,
required String title, required String title,
@@ -101,21 +162,15 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler {
Duration? duration, Duration? duration,
Uint8List? artwork, Uint8List? artwork,
}) { }) {
debugPrint(
'📢 [handler] _updateMediaItem: artwork is ${artwork != null ? 'not null (${artwork.length} bytes)' : 'null'}');
_currentId = id; _currentId = id;
_currentTitle = title; _currentTitle = title;
_currentArtist = artist; _currentArtist = artist;
final position = _player.position;
debugPrint('📢 [handler] updateMediaItem: $title - $artist'); final position = _playback.player.state.position;
// ⭐ 异步处理 artwork(不阻塞主流程)
_handleArtwork(id, artwork).then((artUri) { _handleArtwork(id, artwork).then((artUri) {
// 如果 artUri 变化,重新推送 MediaItem
final current = mediaItem.value; final current = mediaItem.value;
if (current != null && current.artUri != artUri) { if (current != null && current.artUri != artUri) {
debugPrint('📢 [handler] updating artUri: $artUri');
mediaItem.add(audio_service.MediaItem( mediaItem.add(audio_service.MediaItem(
id: current.id, id: current.id,
title: current.title, title: current.title,
@@ -127,38 +182,28 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler {
} }
}); });
// 先推送不带封面图的 MediaItem(让 UI 尽快显示)
mediaItem.add(audio_service.MediaItem( mediaItem.add(audio_service.MediaItem(
id: id, id: id,
title: title, title: title,
artist: artist, artist: artist,
duration: duration ?? _player.duration, duration: duration ?? _playback.player.state.duration,
extras: {'position': position.inMilliseconds}, extras: {'position': position.inMilliseconds},
)); ));
} }
/// 处理封面图:保存到本地并生成 content URI
Future<Uri?> _handleArtwork(String id, Uint8List? artwork) async { Future<Uri?> _handleArtwork(String id, Uint8List? artwork) async {
if (artwork == null || artwork.isEmpty) { if (artwork == null || artwork.isEmpty) return null;
_currentArtworkPath = null;
return null;
}
try { try {
final dir = await getApplicationDocumentsDirectory(); final dir = await getApplicationDocumentsDirectory();
final artworkDir = Directory('${dir.path}/artworks'); final artworkDir = Directory('${dir.path}/artworks');
if (!await artworkDir.exists()) { if (!await artworkDir.exists()) {
await artworkDir.create(recursive: true); await artworkDir.create(recursive: true);
} }
// 使用 md5 生成安全的文件名
final bytes = utf8.encode(id); final bytes = utf8.encode(id);
final digest = md5.convert(bytes); final digest = md5.convert(bytes);
final fileName = '$digest.jpg'; final fileName = '$digest.jpg';
final path = '${artworkDir.path}/$fileName'; final path = '${artworkDir.path}/$fileName';
final file = File(path); final file = File(path);
// 检查文件是否已存在
if (await file.exists()) { if (await file.exists()) {
final existingBytes = await file.readAsBytes(); final existingBytes = await file.readAsBytes();
if (existingBytes.length == artwork.length && if (existingBytes.length == artwork.length &&
@@ -167,12 +212,8 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler {
return await FileProviderUtils.getContentUri(file); return await FileProviderUtils.getContentUri(file);
} }
} }
// 写入新文件
await file.writeAsBytes(artwork); await file.writeAsBytes(artwork);
_currentArtworkPath = path; _currentArtworkPath = path;
debugPrint('📢 [handler] artwork saved: $path');
return await FileProviderUtils.getContentUri(file); return await FileProviderUtils.getContentUri(file);
} catch (e) { } catch (e) {
debugPrint('⚠️ [handler] artwork handling failed: $e'); debugPrint('⚠️ [handler] artwork handling failed: $e');
@@ -180,50 +221,54 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler {
} }
} }
// ---- 外部接口 ----
void updateNotification({ void updateNotification({
required String id, required String id,
required String title, required String title,
required String artist, required String artist,
Uint8List? artwork, Uint8List? artwork,
}) { }) {
_updateMediaItem( _updateMediaItem(id: id, title: title, artist: artist, artwork: artwork);
id: id, final player = _playback.player;
title: title, _publishPlaybackState(
artist: artist, playing: player.state.playing,
artwork: artwork, position: _currentPosition,
duration: player.state.duration,
); );
_publishState();
} }
// ---- 控制命令 ---- // ═══════════════════════════════════════════════════════════════
// audio_service 控制命令(全部委托给 PlaybackService
// ═══════════════════════════════════════════════════════════════
@override @override
Future<void> play() async { Future<void> play() async {
debugPrint('▶️ [handler] play() called'); debugPrint('▶️ [handler] play() called');
_player.play(); await _playback.resume();
_publishState();
} }
@override @override
Future<void> pause() async { Future<void> pause() async {
debugPrint('⏸️ [handler] pause() called'); debugPrint('⏸️ [handler] pause() called');
_player.pause(); await _playback.pause();
_publishState();
} }
@override @override
Future<void> stop() async { Future<void> stop() async {
debugPrint('⏹️ [handler] stop() called'); debugPrint('⏹️ [handler] stop() called');
_player.stop(); await _playback.stop();
_publishState();
} }
@override @override
Future<void> seek(Duration position) async { Future<void> seek(Duration position) async {
debugPrint('⏩ [handler] seek() called: $position'); debugPrint('⏩ [handler] seek() called: $position');
await _player.seek(position); await _playback.seek(position);
_currentPosition = position; _currentPosition = position;
_publishStateOnly(); final player = _playback.player;
_publishPlaybackState(
playing: player.state.playing,
position: position,
duration: player.state.duration,
onlyPosition: true,
);
} }
@override @override
@@ -251,14 +296,14 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler {
await skipToPrevious(); await skipToPrevious();
break; break;
case audio_service.MediaButton.media: case audio_service.MediaButton.media:
if (_player.isPlaying) { if (_playback.player.state.playing) {
await pause(); await pause();
} else { } else {
await play(); await play();
} }
break; break;
default: default:
if (_player.isPlaying) { if (_playback.player.state.playing) {
await pause(); await pause();
} else { } else {
await play(); await play();
+15 -1
View File
@@ -13,6 +13,7 @@ import '../utils/artwork_helper.dart';
import '../repositories/playlist_repository.dart'; import '../repositories/playlist_repository.dart';
import '../models/playlist.dart'; import '../models/playlist.dart';
import 'webdav_service.dart'; import 'webdav_service.dart';
import 'audio_player_handler.dart';
enum PlayMode { enum PlayMode {
sequential, sequential,
@@ -62,6 +63,9 @@ class AudioService extends ChangeNotifier {
// ---- 当前播放的歌单 ID ---- // ---- 当前播放的歌单 ID ----
String? _currentPlaylistId; String? _currentPlaylistId;
// ⭐ 插入位置:在现有成员变量之后,方法之前
AudioPlayerHandler? _handler; // ⭐ 添加这一行
// ---- 高频进度 ---- // ---- 高频进度 ----
final ValueNotifier<Duration> positionNotifier = ValueNotifier(Duration.zero); final ValueNotifier<Duration> positionNotifier = ValueNotifier(Duration.zero);
final ValueNotifier<Duration> durationNotifier = ValueNotifier(Duration.zero); final ValueNotifier<Duration> durationNotifier = ValueNotifier(Duration.zero);
@@ -115,6 +119,11 @@ class AudioService extends ChangeNotifier {
_onSongChanged = callback; _onSongChanged = callback;
} }
void setHandler(AudioPlayerHandler handler) {
// ⭐ 添加这个方法
_handler = handler;
}
void togglePlayMode() { void togglePlayMode() {
switch (_playMode) { switch (_playMode) {
case PlayMode.sequential: case PlayMode.sequential:
@@ -264,6 +273,8 @@ class AudioService extends ChangeNotifier {
final song = _queue[_currentIndex]; final song = _queue[_currentIndex];
_currentSong = song; _currentSong = song;
_handler?.syncState(song);
_startListening(); _startListening();
notifyListeners(); notifyListeners();
@@ -547,6 +558,8 @@ class AudioService extends ChangeNotifier {
} }
void stopPlay() { void stopPlay() {
// 停止底层播放器
PlaybackService().stop(); // 新增
_currentSong = null; _currentSong = null;
_isPlaying = false; _isPlaying = false;
positionNotifier.value = Duration.zero; positionNotifier.value = Duration.zero;
@@ -554,7 +567,6 @@ class AudioService extends ChangeNotifier {
bufferedNotifier.value = Duration.zero; bufferedNotifier.value = Duration.zero;
_stopListening(); _stopListening();
notifyListeners(); notifyListeners();
// 停止时也保存一次
savePlaybackState(); savePlaybackState();
} }
@@ -821,6 +833,8 @@ class AudioService extends ChangeNotifier {
_currentSong = song; _currentSong = song;
_onSongChanged?.call(song); _onSongChanged?.call(song);
_handler?.syncState(song); // ⭐ 添加这一行
debugPrint( debugPrint(
'♻️ [AudioService] playback state restored: ${song.title} - ${song.artist}'); '♻️ [AudioService] playback state restored: ${song.title} - ${song.artist}');
+3
View File
@@ -0,0 +1,3 @@
import 'package:flutter/material.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
+5 -6
View File
@@ -2,6 +2,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../services/audio_service.dart'; import '../services/audio_service.dart';
import '../utils/navigation.dart'; // ⭐ 导入 navigatorKey
class GlobalMiniPlayer extends StatelessWidget { class GlobalMiniPlayer extends StatelessWidget {
const GlobalMiniPlayer({super.key}); const GlobalMiniPlayer({super.key});
@@ -45,11 +46,9 @@ class GlobalMiniPlayer extends StatelessWidget {
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
// ⭐ 使用 Navigator.of(context) 替代 navigatorKey // ⭐ 使用 navigatorKey 直接跳转
Navigator.of(context, rootNavigator: true) navigatorKey.currentState?.pushNamed('/player');
.pushNamed('/player');
}, },
// ⭐ 替换弃用的 withOpacity 为 withValues
highlightColor: Colors.white.withValues(alpha: 0.05), highlightColor: Colors.white.withValues(alpha: 0.05),
splashColor: Colors.white.withValues(alpha: 0.1), splashColor: Colors.white.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -124,8 +123,8 @@ class GlobalMiniPlayer extends StatelessWidget {
size: 24, size: 24,
), ),
onPressed: () { onPressed: () {
// ⭐ 使用 Navigator.of(context) // ⭐ 使用 navigatorKey 直接跳转
Navigator.of(context).pushNamed('/playlist'); navigatorKey.currentState?.pushNamed('/playlist');
}, },
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
-1
View File
@@ -32,7 +32,6 @@ dependencies:
sdk: flutter sdk: flutter
media_kit: 1.2.6 media_kit: 1.2.6
media_kit_libs_audio: ^1.0.7 media_kit_libs_audio: ^1.0.7
#media_kit_libs_android_audio: 1.3.8
shared_preferences: 2.2.2 shared_preferences: 2.2.2
provider: ^6.1.2 provider: ^6.1.2
dio: ^5.4.0 # HTTP 客户端 dio: ^5.4.0 # HTTP 客户端