// lib/main.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:media_kit/media_kit.dart'; import 'package:audio_service/audio_service.dart' as audio_service; import 'package:permission_handler/permission_handler.dart'; import 'services/audio_service.dart'; import 'services/playback_service.dart'; import 'services/app_lifecycle_service.dart'; import 'services/webdav_service.dart'; import 'services/audio_player_handler.dart'; import 'services/app_lifecycle_manager.dart'; import 'pages/home_page.dart'; import 'pages/player_page.dart'; import 'pages/playlist_page.dart'; import 'widgets/global_mini_player.dart'; final GlobalKey navigatorKey = GlobalKey(); final stopwatch = Stopwatch(); // ⭐ 防止 Hot Restart / 热重载时重复初始化 bool _appInitialized = false; // ⭐ 全局缓存 Handler AudioPlayerHandler? _audioHandler; // ⭐ 全局缓存 LifecycleManager final AppLifecycleManager lifecycleManager = AppLifecycleManager(); void main() async { // ⭐ P0:防止重复初始化 if (_appInitialized) { debugPrint('⏱️ main 已初始化,跳过重复执行'); return; } _appInitialized = true; stopwatch.start(); debugPrint('⏱️ T0 main: ${stopwatch.elapsedMilliseconds}ms'); WidgetsFlutterBinding.ensureInitialized(); debugPrint('⏱️ T0.5 ensureInitialized: ${stopwatch.elapsedMilliseconds}ms'); // ⭐ MediaKit 在 runApp 前初始化(官方要求) try { MediaKit.ensureInitialized(); debugPrint('⏱️ T0.6 MediaKit 初始化完成'); } catch (e) { debugPrint('❌ MediaKit 初始化失败: $e'); } // ⭐ 创建 Handler _audioHandler = AudioPlayerHandler(); // ⭐ 注册切歌回调 AudioService().setOnSongChanged((song) { debugPrint( '📢 [main] song.artwork is ${song.artwork != null ? 'not null' : 'null'}'); _audioHandler!.updateNotification( id: song.id, title: song.title, artist: song.artist, artwork: song.artwork, ); }); runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => AudioService()), ChangeNotifierProvider(create: (_) => AppLifecycleService()), ChangeNotifierProvider( create: (_) => lifecycleManager, ), Provider(create: (_) => _audioHandler!), ], child: const QTPlayerApp(), ), ); debugPrint('⏱️ T0.8 runApp 完成: ${stopwatch.elapsedMilliseconds}ms'); } // ════════════════════════════════════════════════════════════ // 路由管理器(控制 MiniPlayer 显示/隐藏) // ════════════════════════════════════════════════════════════ class RouteManager extends ChangeNotifier { static final RouteManager _instance = RouteManager._internal(); factory RouteManager() => _instance; RouteManager._internal(); String _currentRoute = '/'; String get currentRoute => _currentRoute; bool get showMiniPlayer => _currentRoute != '/player' && _currentRoute != '/playlist'; void updateRoute(String route) { if (_currentRoute != route) { _currentRoute = route; debugPrint('📌 路由更新: $route'); notifyListeners(); } } } // ════════════════════════════════════════════════════════════ // 导航观察者(监听路由变化) // ════════════════════════════════════════════════════════════ // lib/main.dart class MiniPlayerNavigatorObserver extends NavigatorObserver { void _updateRoute(Route? route) { if (route == null) return; // ⭐ 忽略 PopupRoute(菜单、对话框等) if (route is PopupRoute) { return; } final name = route.settings.name ?? '/'; RouteManager().updateRoute(name); } @override void didPush(Route route, Route? previousRoute) { _updateRoute(route); } @override void didPop(Route route, Route? previousRoute) { // ⭐ 返回时,使用上一个路由(previousRoute) if (previousRoute != null && previousRoute is! PopupRoute) { final name = previousRoute.settings.name ?? '/'; RouteManager().updateRoute(name); } else if (previousRoute == null) { RouteManager().updateRoute('/'); } } @override void didReplace({Route? newRoute, Route? oldRoute}) { if (newRoute != null && newRoute is! PopupRoute) { final name = newRoute.settings.name ?? '/'; RouteManager().updateRoute(name); } } } // ════════════════════════════════════════════════════════════ // 主应用 // ════════════════════════════════════════════════════════════ class QTPlayerApp extends StatefulWidget { const QTPlayerApp({super.key}); @override State createState() => _QTPlayerAppState(); } class _QTPlayerAppState extends State { static int _buildCount = 0; bool _isInitializing = false; void Function(AppLifecycleStatus)? _lifecycleListener; @override void initState() { super.initState(); debugPrint('⏱️ T1.5 initState: ${stopwatch.elapsedMilliseconds}ms'); WidgetsBinding.instance.addPostFrameCallback((_) { debugPrint('⏱️ T2 首帧回调: ${stopwatch.elapsedMilliseconds}ms'); lifecycleManager.init(); _bindLifecycleToPlayback(); _startBackgroundInitialization(); }); } @override void dispose() { if (_lifecycleListener != null) { lifecycleManager.removeListenerFromManager(_lifecycleListener!); } super.dispose(); } void _bindLifecycleToPlayback() { if (_lifecycleListener != null) { lifecycleManager.removeListenerFromManager(_lifecycleListener!); } _lifecycleListener = (status) { switch (status) { case AppLifecycleStatus.paused: case AppLifecycleStatus.inactive: debugPrint('🎵 App 进入后台,播放继续'); break; case AppLifecycleStatus.resumed: debugPrint('🎵 App 回到前台'); break; case AppLifecycleStatus.backgroundIdle: debugPrint('🎵 长后台唤醒,检查播放状态'); break; default: break; } }; lifecycleManager.addListenerToManager(_lifecycleListener!); } Future _startBackgroundInitialization() async { if (_isInitializing) return; _isInitializing = true; final tStart = DateTime.now(); debugPrint('⏱️ T3 后台初始化开始'); final t1 = DateTime.now(); try { PlaybackService().init(); debugPrint('⏱️ T3.1 PlaybackService 完成: ${_elapsedMs(t1)}ms'); } catch (e) { debugPrint('❌ PlaybackService 失败: $e'); } final t2 = DateTime.now(); try { final hasCred = await WebDAVService.instance.loadCredentials(); debugPrint('⏱️ T3.2 WebDAV 凭证加载完成: ${_elapsedMs(t2)}ms, 有凭证: $hasCred'); } catch (e) { debugPrint('❌ WebDAV 凭证加载失败: $e'); } await _initAudioService(); final t3 = DateTime.now(); try { if (await Permission.notification.isDenied) { await Permission.notification.request(); debugPrint('⏱️ T3.3 通知权限完成: ${_elapsedMs(t3)}ms'); } else { debugPrint('⏱️ T3.3 通知权限已授予: ${_elapsedMs(t3)}ms'); } } catch (e) { debugPrint('❌ 权限申请失败: $e'); } final tEnd = DateTime.now(); debugPrint('⏱️ T4 全部初始化完成: ${tEnd.difference(tStart).inMilliseconds}ms'); } Future _initAudioService() async { try { await audio_service.AudioService.init( builder: () => _audioHandler!, config: const audio_service.AudioServiceConfig( androidNotificationChannelId: 'com.lxh.qingting_player.music', androidNotificationChannelName: '清听', androidNotificationOngoing: false, androidStopForegroundOnPause: false, ), ); debugPrint('✅ AudioService 初始化完成'); } catch (e) { debugPrint('❌ AudioService 初始化失败: $e'); } } String _elapsedMs(DateTime start) { return '${DateTime.now().difference(start).inMilliseconds}'; } // ⭐ 路由生成(播放页滑入动画,速度 200ms) // lib/main.dart - _onGenerateRoute 方法 Route _onGenerateRoute(RouteSettings settings) { if (settings.name == '/player') { return PageRouteBuilder( settings: settings, pageBuilder: (context, animation, secondaryAnimation) { return const PlayerPage(); }, transitionsBuilder: (context, animation, secondaryAnimation, child) { // ⭐ 应用贝塞尔曲线 final curvedAnimation = CurvedAnimation( parent: animation, curve: Curves.easeOutCubic, // 平滑减速 // curve: Curves.easeOutBack, // 轻微回弹,更灵动 // curve: Curves.fastOutSlowIn, // Material 风格 ); const begin = Offset(0.0, 1.0); const end = Offset.zero; final tween = Tween(begin: begin, end: end); final offsetAnimation = tween.animate(curvedAnimation); return SlideTransition( position: offsetAnimation, child: child, ); }, transitionDuration: const Duration(milliseconds: 300), ); } return MaterialPageRoute( settings: settings, builder: (context) { switch (settings.name) { case '/': return const HomePage(); case '/playlist': return const PlaylistPage(); default: return const HomePage(); } }, ); } @override Widget build(BuildContext context) { _buildCount++; debugPrint( '🎨 [QTPlayerApp] build #$_buildCount ${stopwatch.elapsedMilliseconds}ms'); return MaterialApp( title: '清听', theme: ThemeData( brightness: Brightness.dark, primaryColor: const Color(0xFF7C9A9E), colorScheme: const ColorScheme.dark( primary: Color(0xFF7C9A9E), secondary: Color(0xFFB8D4D0), surface: Color(0xFF1A1F1E), onSurface: Colors.white, ), useMaterial3: true, ), 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( duration: const Duration(milliseconds: 300), switchInCurve: Curves.easeOut, switchOutCurve: Curves.easeIn, transitionBuilder: (child, animation) { // ⭐ 淡入淡出 + 轻微上滑 return FadeTransition( opacity: animation, child: SlideTransition( position: Tween( begin: const Offset(0, 0.1), end: Offset.zero, ).animate(animation), child: child, ), ); }, child: shouldShow ? const Align( key: ValueKey('mini_player_visible'), alignment: Alignment.bottomCenter, child: GlobalMiniPlayer(), ) : const SizedBox.shrink( key: ValueKey('mini_player_hidden'), ), ); }, ), ], ); }, ); } }