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(AudioService 稍后初始化) _audioHandler = AudioPlayerHandler(); 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(); } } } // ════════════════════════════════════════════════════════════ // 导航观察者(监听路由变化) // ════════════════════════════════════════════════════════════ class MiniPlayerNavigatorObserver extends NavigatorObserver { void _updateRoute(Route? route) { final name = route?.settings.name ?? '/'; RouteManager().updateRoute(name); } @override void didPush(Route route, Route? previousRoute) { _updateRoute(route); } @override void didPop(Route route, Route? previousRoute) { _updateRoute(previousRoute); } @override void didReplace({Route? newRoute, Route? oldRoute}) { _updateRoute(newRoute); } } // ════════════════════════════════════════════════════════════ // 主应用 // ════════════════════════════════════════════════════════════ class QTPlayerApp extends StatefulWidget { const QTPlayerApp({super.key}); @override State createState() => _QTPlayerAppState(); } class _QTPlayerAppState extends State { // ⭐ build 计数器(诊断用) 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'); // ⭐ 1. 初始化生命周期管理器 lifecycleManager.init(); // ⭐ 2. 绑定生命周期到播放器 _bindLifecycleToPlayback(); // ⭐ 3. 后台初始化 _startBackgroundInitialization(); }); } @override void dispose() { // ⭐ 移除生命周期监听器 if (_lifecycleListener != null) { lifecycleManager.removeListenerFromManager(_lifecycleListener!); } super.dispose(); } // ============================================================ // 生命周期 → 播放器绑定 // ============================================================ void _bindLifecycleToPlayback() { if (_lifecycleListener != null) { lifecycleManager.removeListenerFromManager(_lifecycleListener!); } _lifecycleListener = (status) { final audioService = context.read(); 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 后台初始化开始'); // ----- 1. PlaybackService ----- final t1 = DateTime.now(); try { PlaybackService().init(); debugPrint('⏱️ T3.1 PlaybackService 完成: ${_elapsedMs(t1)}ms'); } catch (e) { debugPrint('❌ PlaybackService 失败: $e'); } // ----- 2. WebDAV(只加载凭证) ----- final t2 = DateTime.now(); try { final hasCred = await WebDAVService.instance.loadCredentials(); debugPrint('⏱️ T3.2 WebDAV 凭证加载完成: ${_elapsedMs(t2)}ms, 有凭证: $hasCred'); } catch (e) { debugPrint('❌ WebDAV 凭证加载失败: $e'); } // ----- 3. ⭐ AudioService(后台播放核心) ----- await _initAudioService(); // ----- 4. 通知权限 ----- 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'); } // ════════════════════════════════════════════════════════════ // ⭐ AudioService 初始化(后台播放核心) // ════════════════════════════════════════════════════════════ Future _initAudioService() async { try { await audio_service.AudioService.init( builder: () => _audioHandler!, config: const audio_service.AudioServiceConfig( androidNotificationChannelId: 'com.lxh.qingting_player.music', androidNotificationChannelName: '清听音乐播放', androidNotificationOngoing: true, androidStopForegroundOnPause: true, ), ); debugPrint('✅ AudioService 初始化完成'); } catch (e) { debugPrint('❌ AudioService 初始化失败: $e'); } } // ════════════════════════════════════════════════════════════ // 辅助方法 // ════════════════════════════════════════════════════════════ String _elapsedMs(DateTime start) { return '${DateTime.now().difference(start).inMilliseconds}'; } // ════════════════════════════════════════════════════════════ // Build // ════════════════════════════════════════════════════════════ @override Widget build(BuildContext context) { _buildCount++; debugPrint( '🎨 [QTPlayerApp] build #$_buildCount ${stopwatch.elapsedMilliseconds}ms'); // 监听歌曲变化,更新通知栏 // main.dart 中 build 方法里的这部分 final audioService = context.watch(); final handler = context.read(); final song = audioService.currentSong; WidgetsBinding.instance.addPostFrameCallback((_) { if (song != null) { // ⭐ song 不为空时才调用 handler.updateNotification( id: song.id, title: song.title, artist: song.artist, ); } else { // 清空通知 handler.mediaItem.add(null); } }); 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()], routes: { '/': (context) => const HomePage(), '/player': (context) => const PlayerPage(), '/playlist': (context) => const PlaylistPage(), }, builder: (context, child) { return Stack( fit: StackFit.expand, children: [ if (child != null) child, AnimatedBuilder( animation: RouteManager(), builder: (context, _) { if (!RouteManager().showMiniPlayer) { return const SizedBox.shrink(); } return const Align( alignment: Alignment.bottomCenter, child: GlobalMiniPlayer(), ); }, ), ], ); }, ); } }