后台播放能力已调通,介入部分细节优化
This commit is contained in:
+114
-49
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:media_kit/media_kit.dart';
|
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 'services/audio_service.dart';
|
import 'services/audio_service.dart';
|
||||||
@@ -9,6 +9,7 @@ import 'services/playback_service.dart';
|
|||||||
import 'services/app_lifecycle_service.dart';
|
import 'services/app_lifecycle_service.dart';
|
||||||
import 'services/webdav_service.dart';
|
import 'services/webdav_service.dart';
|
||||||
import 'services/audio_player_handler.dart';
|
import 'services/audio_player_handler.dart';
|
||||||
|
import 'services/app_lifecycle_manager.dart';
|
||||||
import 'pages/home_page.dart';
|
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';
|
||||||
@@ -23,6 +24,9 @@ bool _appInitialized = false;
|
|||||||
// ⭐ 全局缓存 Handler
|
// ⭐ 全局缓存 Handler
|
||||||
AudioPlayerHandler? _audioHandler;
|
AudioPlayerHandler? _audioHandler;
|
||||||
|
|
||||||
|
// ⭐ 全局缓存 LifecycleManager
|
||||||
|
final AppLifecycleManager lifecycleManager = AppLifecycleManager();
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
// ⭐ P0:防止重复初始化
|
// ⭐ P0:防止重复初始化
|
||||||
if (_appInitialized) {
|
if (_appInitialized) {
|
||||||
@@ -53,6 +57,10 @@ void main() async {
|
|||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (_) => AudioService()),
|
ChangeNotifierProvider(create: (_) => AudioService()),
|
||||||
ChangeNotifierProvider(create: (_) => AppLifecycleService()),
|
ChangeNotifierProvider(create: (_) => AppLifecycleService()),
|
||||||
|
// ⭐ 生命周期管理器
|
||||||
|
ChangeNotifierProvider<AppLifecycleManager>(
|
||||||
|
create: (_) => lifecycleManager,
|
||||||
|
),
|
||||||
Provider<AudioPlayerHandler>(create: (_) => _audioHandler!),
|
Provider<AudioPlayerHandler>(create: (_) => _audioHandler!),
|
||||||
],
|
],
|
||||||
child: const QTPlayerApp(),
|
child: const QTPlayerApp(),
|
||||||
@@ -121,8 +129,14 @@ class QTPlayerApp extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _QTPlayerAppState extends State<QTPlayerApp> {
|
class _QTPlayerAppState extends State<QTPlayerApp> {
|
||||||
|
// ⭐ build 计数器(诊断用)
|
||||||
|
static int _buildCount = 0;
|
||||||
|
|
||||||
bool _isInitializing = false;
|
bool _isInitializing = false;
|
||||||
|
|
||||||
|
// ⭐ 保存生命周期监听器引用
|
||||||
|
void Function(AppLifecycleStatus)? _lifecycleListener;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 生命周期
|
// 生命周期
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -134,92 +148,143 @@ class _QTPlayerAppState extends State<QTPlayerApp> {
|
|||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
debugPrint('⏱️ T2 首帧回调: ${stopwatch.elapsedMilliseconds}ms');
|
debugPrint('⏱️ T2 首帧回调: ${stopwatch.elapsedMilliseconds}ms');
|
||||||
|
|
||||||
|
// ⭐ 1. 初始化生命周期管理器
|
||||||
|
lifecycleManager.init();
|
||||||
|
|
||||||
|
// ⭐ 2. 绑定生命周期到播放器
|
||||||
|
_bindLifecycleToPlayback();
|
||||||
|
|
||||||
|
// ⭐ 3. 后台初始化
|
||||||
_startBackgroundInitialization();
|
_startBackgroundInitialization();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
// ⭐ 移除生命周期监听器
|
||||||
|
if (_lifecycleListener != null) {
|
||||||
|
lifecycleManager.removeListenerFromManager(_lifecycleListener!);
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 后台初始化(不阻塞首帧)
|
// 生命周期 → 播放器绑定
|
||||||
|
// ============================================================
|
||||||
|
void _bindLifecycleToPlayback() {
|
||||||
|
if (_lifecycleListener != null) {
|
||||||
|
lifecycleManager.removeListenerFromManager(_lifecycleListener!);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lifecycleListener = (status) {
|
||||||
|
final audioService = context.read<AudioService>();
|
||||||
|
|
||||||
|
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<void> _startBackgroundInitialization() async {
|
Future<void> _startBackgroundInitialization() async {
|
||||||
if (_isInitializing) return;
|
if (_isInitializing) return;
|
||||||
_isInitializing = true;
|
_isInitializing = true;
|
||||||
|
|
||||||
|
final tStart = DateTime.now();
|
||||||
debugPrint('⏱️ T3 后台初始化开始');
|
debugPrint('⏱️ T3 后台初始化开始');
|
||||||
|
|
||||||
// 1. PlaybackService(轻量级)
|
// ----- 1. PlaybackService -----
|
||||||
|
final t1 = DateTime.now();
|
||||||
try {
|
try {
|
||||||
PlaybackService().init();
|
PlaybackService().init();
|
||||||
debugPrint('⏱️ T3.1 PlaybackService 初始化完成');
|
debugPrint('⏱️ T3.1 PlaybackService 完成: ${_elapsedMs(t1)}ms');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('❌ PlaybackService 初始化失败: $e');
|
debugPrint('❌ PlaybackService 失败: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. ⭐ AudioService 暂时禁用(等 MainActivity 改为 AudioServiceActivity 后启用)
|
// ----- 2. WebDAV(只加载凭证) -----
|
||||||
// await _initAudioService();
|
final t2 = DateTime.now();
|
||||||
|
|
||||||
// 3. WebDAV
|
|
||||||
await _initWebDAV();
|
|
||||||
|
|
||||||
// 4. 通知权限
|
|
||||||
await _requestPermissions();
|
|
||||||
|
|
||||||
debugPrint('⏱️ T4 全部初始化完成');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════
|
|
||||||
// 子初始化方法
|
|
||||||
// ════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
// ⭐ 暂时禁用,等 MainActivity 改为 AudioServiceActivity 后启用
|
|
||||||
// Future<void> _initAudioService() async {
|
|
||||||
// try {
|
|
||||||
// await audio_service.AudioService.init(
|
|
||||||
// builder: () => _audioHandler!,
|
|
||||||
// config: const audio_service.AudioServiceConfig(
|
|
||||||
// androidNotificationChannelId: 'com.example.qt_player.music',
|
|
||||||
// androidNotificationChannelName: '清听音乐播放',
|
|
||||||
// androidNotificationOngoing: true,
|
|
||||||
// androidStopForegroundOnPause: true,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// debugPrint('✅ AudioService 初始化完成');
|
|
||||||
// } catch (e) {
|
|
||||||
// debugPrint('❌ AudioService 初始化失败: $e');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<void> _initWebDAV() async {
|
|
||||||
try {
|
try {
|
||||||
final hasCred = await WebDAVService.instance.loadCredentials();
|
final hasCred = await WebDAVService.instance.loadCredentials();
|
||||||
debugPrint('✅ WebDAV 加载完成, 已连接: $hasCred');
|
debugPrint('⏱️ T3.2 WebDAV 凭证加载完成: ${_elapsedMs(t2)}ms, 有凭证: $hasCred');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('❌ WebDAV 加载失败: $e');
|
debugPrint('❌ WebDAV 凭证加载失败: $e');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _requestPermissions() async {
|
// ----- 3. ⭐ AudioService(后台播放核心) -----
|
||||||
|
await _initAudioService();
|
||||||
|
|
||||||
|
// ----- 4. 通知权限 -----
|
||||||
|
final t3 = DateTime.now();
|
||||||
try {
|
try {
|
||||||
if (await Permission.notification.isDenied) {
|
if (await Permission.notification.isDenied) {
|
||||||
final status = await Permission.notification.request();
|
await Permission.notification.request();
|
||||||
debugPrint('📢 通知权限状态: $status');
|
debugPrint('⏱️ T3.3 通知权限完成: ${_elapsedMs(t3)}ms');
|
||||||
|
} else {
|
||||||
|
debugPrint('⏱️ T3.3 通知权限已授予: ${_elapsedMs(t3)}ms');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('❌ 权限申请失败: $e');
|
debugPrint('❌ 权限申请失败: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final tEnd = DateTime.now();
|
||||||
|
debugPrint('⏱️ T4 全部初始化完成: ${tEnd.difference(tStart).inMilliseconds}ms');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ════════════════════════════════════════════════════════════
|
||||||
|
// ⭐ AudioService 初始化(后台播放核心)
|
||||||
|
// ════════════════════════════════════════════════════════════
|
||||||
|
Future<void> _initAudioService() async {
|
||||||
|
try {
|
||||||
|
await audio_service.AudioService.init(
|
||||||
|
builder: () => _audioHandler!,
|
||||||
|
config: const audio_service.AudioServiceConfig(
|
||||||
|
androidNotificationChannelId: 'com.example.qt_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
|
// Build
|
||||||
// ============================================================
|
// ════════════════════════════════════════════════════════════
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
debugPrint('⏱️ T1 build: ${stopwatch.elapsedMilliseconds}ms');
|
_buildCount++;
|
||||||
|
debugPrint(
|
||||||
|
'🎨 [QTPlayerApp] build #$_buildCount ${stopwatch.elapsedMilliseconds}ms');
|
||||||
|
|
||||||
// 监听歌曲变化,更新通知栏
|
// 监听歌曲变化,更新通知栏
|
||||||
final audioService = context.watch<AudioService>();
|
final audioService = context.watch<AudioService>();
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
// lib/services/app_lifecycle_manager.dart
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:media_kit/media_kit.dart';
|
||||||
|
|
||||||
|
/// App 生命周期状态(扩展了 Flutter 原生状态)
|
||||||
|
enum AppLifecycleStatus {
|
||||||
|
/// 应用正在启动(初始化阶段)
|
||||||
|
launching,
|
||||||
|
|
||||||
|
/// 应用在前台,用户可交互
|
||||||
|
resumed,
|
||||||
|
|
||||||
|
/// 应用在前台但不可交互(如:来电、系统弹窗)
|
||||||
|
inactive,
|
||||||
|
|
||||||
|
/// 应用在后台
|
||||||
|
paused,
|
||||||
|
|
||||||
|
/// 应用即将被销毁
|
||||||
|
detached,
|
||||||
|
|
||||||
|
/// 应用已进入后台且播放停止(自定义,用于播放策略)
|
||||||
|
backgroundIdle,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// App 生命周期管理器
|
||||||
|
///
|
||||||
|
/// 职责:
|
||||||
|
/// 1. 统一监听 Flutter 的 AppLifecycleState
|
||||||
|
/// 2. 管理播放器在前后台的行为
|
||||||
|
/// 3. 管理渲染调度器的运行状态
|
||||||
|
/// 4. 管理 WebDAV 的缓存策略
|
||||||
|
class AppLifecycleManager extends ChangeNotifier {
|
||||||
|
static final AppLifecycleManager _instance = AppLifecycleManager._internal();
|
||||||
|
factory AppLifecycleManager() => _instance;
|
||||||
|
AppLifecycleManager._internal();
|
||||||
|
|
||||||
|
// ---- 当前状态 ----
|
||||||
|
AppLifecycleStatus _status = AppLifecycleStatus.launching;
|
||||||
|
AppLifecycleStatus get status => _status;
|
||||||
|
|
||||||
|
// ---- 上次进入后台的时间 ----
|
||||||
|
DateTime? _backgroundedAt;
|
||||||
|
DateTime? get backgroundedAt => _backgroundedAt;
|
||||||
|
|
||||||
|
// ---- 是否在后台 ----
|
||||||
|
bool get isInBackground =>
|
||||||
|
_status == AppLifecycleStatus.paused ||
|
||||||
|
_status == AppLifecycleStatus.inactive ||
|
||||||
|
_status == AppLifecycleStatus.detached;
|
||||||
|
|
||||||
|
bool get isInForeground => _status == AppLifecycleStatus.resumed;
|
||||||
|
|
||||||
|
// ---- 后台时长 ----
|
||||||
|
Duration get backgroundDuration {
|
||||||
|
if (_backgroundedAt == null) return Duration.zero;
|
||||||
|
return DateTime.now().difference(_backgroundedAt!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 是否处于"长后台"(超过 5 分钟) ----
|
||||||
|
bool get isLongBackground => backgroundDuration > const Duration(minutes: 5);
|
||||||
|
|
||||||
|
// ---- 监听器 ----
|
||||||
|
final List<void Function(AppLifecycleStatus)> _listeners = [];
|
||||||
|
|
||||||
|
// ---- Flutter 原生生命周期订阅 ----
|
||||||
|
late final WidgetsBindingObserver _observer;
|
||||||
|
bool _isObserving = false;
|
||||||
|
|
||||||
|
// ---- 初始化 ----
|
||||||
|
void init() {
|
||||||
|
if (_isObserving) return;
|
||||||
|
_isObserving = true;
|
||||||
|
|
||||||
|
_observer = _AppLifecycleObserver(this);
|
||||||
|
WidgetsBinding.instance.addObserver(_observer);
|
||||||
|
|
||||||
|
// 获取初始状态
|
||||||
|
_updateStatus(AppLifecycleStatus.resumed);
|
||||||
|
|
||||||
|
debugPrint('📱 AppLifecycleManager 已初始化');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 状态更新 ----
|
||||||
|
void _handleNativeState(AppLifecycleState state) {
|
||||||
|
final newStatus = _convertNativeState(state);
|
||||||
|
|
||||||
|
// 状态变化时更新
|
||||||
|
if (newStatus != _status) {
|
||||||
|
_updateStatus(newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录进入后台的时间
|
||||||
|
if (newStatus == AppLifecycleStatus.paused ||
|
||||||
|
newStatus == AppLifecycleStatus.inactive) {
|
||||||
|
if (_backgroundedAt == null) {
|
||||||
|
_backgroundedAt = DateTime.now();
|
||||||
|
debugPrint('📱 App 进入后台: ${_backgroundedAt}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回到前台时,如果是长后台,触发通知
|
||||||
|
if (newStatus == AppLifecycleStatus.resumed) {
|
||||||
|
if (_backgroundedAt != null) {
|
||||||
|
final duration = backgroundDuration;
|
||||||
|
debugPrint('📱 App 回到前台,后台时长: ${duration.inMinutes}分钟');
|
||||||
|
_backgroundedAt = null;
|
||||||
|
|
||||||
|
// 如果是长后台,通知监听器做重连等操作
|
||||||
|
if (duration > const Duration(minutes: 5)) {
|
||||||
|
_notifyListeners(AppLifecycleStatus.backgroundIdle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateStatus(AppLifecycleStatus newStatus) {
|
||||||
|
final oldStatus = _status;
|
||||||
|
_status = newStatus;
|
||||||
|
debugPrint('📱 生命周期状态变化: $oldStatus → $newStatus');
|
||||||
|
notifyListeners();
|
||||||
|
_notifyListeners(newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLifecycleStatus _convertNativeState(AppLifecycleState state) {
|
||||||
|
switch (state) {
|
||||||
|
case AppLifecycleState.resumed:
|
||||||
|
return AppLifecycleStatus.resumed;
|
||||||
|
case AppLifecycleState.inactive:
|
||||||
|
return AppLifecycleStatus.inactive;
|
||||||
|
case AppLifecycleState.paused:
|
||||||
|
return AppLifecycleStatus.paused;
|
||||||
|
case AppLifecycleState.detached:
|
||||||
|
return AppLifecycleStatus.detached;
|
||||||
|
default:
|
||||||
|
return AppLifecycleStatus.paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 自定义监听器管理 ----
|
||||||
|
void addListenerToManager(void Function(AppLifecycleStatus) listener) {
|
||||||
|
_listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeListenerFromManager(void Function(AppLifecycleStatus) listener) {
|
||||||
|
_listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _notifyListeners(AppLifecycleStatus status) {
|
||||||
|
for (final listener in _listeners) {
|
||||||
|
try {
|
||||||
|
listener(status);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('❌ 生命周期监听器执行失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 检查是否需要重新连接 WebDAV ----
|
||||||
|
bool get shouldRecheckWebDAV {
|
||||||
|
if (!isInForeground) return false;
|
||||||
|
if (_backgroundedAt == null) return false;
|
||||||
|
return isLongBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 释放 ----
|
||||||
|
void dispose() {
|
||||||
|
if (_isObserving) {
|
||||||
|
WidgetsBinding.instance.removeObserver(_observer);
|
||||||
|
_isObserving = false;
|
||||||
|
}
|
||||||
|
_listeners.clear();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 内部观察者(绑定 WidgetsBinding)
|
||||||
|
class _AppLifecycleObserver extends WidgetsBindingObserver {
|
||||||
|
final AppLifecycleManager _manager;
|
||||||
|
|
||||||
|
_AppLifecycleObserver(this._manager);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
_manager._handleNativeState(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user