From 40d4af322bd66e42cddc0cf64f9bde89f0aca10a Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Wed, 2 Sep 2026 21:42:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=A7=8B=E8=BF=9B=E5=85=A5=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=98=B6=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/audio/player_controller.dart | 32 ---- lib/main.dart | 2 + lib/services/audio_player_handler.dart | 231 +++++++++++++++---------- lib/services/audio_service.dart | 16 +- 4 files changed, 155 insertions(+), 126 deletions(-) delete mode 100644 lib/audio/player_controller.dart diff --git a/lib/audio/player_controller.dart b/lib/audio/player_controller.dart deleted file mode 100644 index df77931..0000000 --- a/lib/audio/player_controller.dart +++ /dev/null @@ -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 get playingStream => _playback.player.stream.playing; - Stream get positionStream => _playback.player.stream.position; - Stream get durationStream => _playback.player.stream.duration; - Stream 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 play() => _playback.resume(); - Future pause() => _playback.pause(); - Future stop() => _playback.stop(); - Future seek(Duration position) => _playback.seek(position); - - // ---- 加载歌曲 ---- - void load(String url, {Map? headers}) { - _playback.play(url, headers: headers); - } -} diff --git a/lib/main.dart b/lib/main.dart index bfed8d8..55e3880 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -54,6 +54,8 @@ void main() async { try { _audioHandler = AudioPlayerHandler(); debugPrint('⏱️ T0.62 AudioPlayerHandler created'); + // ⭐ 将 Handler 注入到 AudioService + AudioService().setHandler(_audioHandler!); } catch (e, st) { debugPrint('❌ AudioPlayerHandler failed: $e'); debugPrint('$st'); diff --git a/lib/services/audio_player_handler.dart b/lib/services/audio_player_handler.dart index 39dca2f..a993daf 100644 --- a/lib/services/audio_player_handler.dart +++ b/lib/services/audio_player_handler.dart @@ -4,49 +4,62 @@ import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:path_provider/path_provider.dart'; 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 '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 { - final PlayerController _player = PlayerController(); - final PlaybackStateManager _state = PlaybackStateManager(); + final PlaybackService _playback = PlaybackService(); + // 当前媒体信息 String? _currentId; String? _currentTitle; String? _currentArtist; - Duration _currentPosition = Duration.zero; DateTime _lastPublishTime = DateTime.now(); static const Duration _publishInterval = Duration(milliseconds: 500); - - // ⭐ 缓存 artwork 文件路径,避免重复写入 String? _currentArtworkPath; + bool _isPublishing = false; AudioPlayerHandler() { - _player.playingStream.listen((playing) { - _state.updatePlaying(playing); - _publishState(); + _bindToPlayer(); + } + + // ═══════════════════════════════════════════════════════════════ + // 绑定播放器状态流 + // ═══════════════════════════════════════════════════════════════ + 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; - _state.updatePosition(position); - final now = DateTime.now(); if (now.difference(_lastPublishTime) >= _publishInterval) { _lastPublishTime = now; - // ⭐ 只保留 _publishStateOnly() - _publishStateOnly(); + _publishPlaybackState( + playing: player.state.playing, + position: position, + duration: player.state.duration, + onlyPosition: true, + ); } }); - _player.durationStream.listen((duration) { - debugPrint('🎯 [durationStream] duration=$duration'); - _state.updateDuration(duration); + player.stream.duration.listen((duration) { if (_currentId != null && _currentTitle != null) { _updateMediaItem( id: _currentId!, @@ -55,45 +68,93 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler { duration: duration, ); } - _publishState(); + _publishPlaybackState( + playing: player.state.playing, + position: _currentPosition, + duration: duration, + ); }); } - // ---- 发布状态 ---- - void _publishState() { - final state = _state.playbackState; - playbackState.add(audio_service.PlaybackState( - controls: state.controls, - processingState: state.processingState, - playing: state.playing, - androidCompactActionIndices: state.androidCompactActionIndices, - updatePosition: _currentPosition, - updateTime: DateTime.now(), - systemActions: const { - audio_service.MediaAction.seek, - }, - )); + // ═══════════════════════════════════════════════════════════════ + // 外部同步接口(由 AudioService 调用) + // ═══════════════════════════════════════════════════════════════ + void syncState(Song? song) { + if (song == null) { + _currentId = null; + _currentTitle = null; + _currentArtist = null; + return; + } + + _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( - controls: current.controls.isNotEmpty - ? current.controls - : _state.playbackState.controls, - processingState: _state.playbackState.processingState, - playing: current.playing, - androidCompactActionIndices: current.androidCompactActionIndices, - updatePosition: _currentPosition, - updateTime: DateTime.now(), - systemActions: const { - audio_service.MediaAction.seek, - }, - )); + try { + final controls = _buildControls(playing); + final state = audio_service.PlaybackState( + controls: controls, + processingState: duration.inMilliseconds > 0 + ? audio_service.AudioProcessingState.ready + : audio_service.AudioProcessingState.idle, + playing: playing, + updatePosition: position, + updateTime: DateTime.now(), + systemActions: const {audio_service.MediaAction.seek}, + ); + playbackState.add(state); + } finally { + _isPublishing = false; + } } - // ---- 更新媒体信息 ---- + List _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({ required String id, required String title, @@ -101,21 +162,15 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler { Duration? duration, Uint8List? artwork, }) { - debugPrint( - '📢 [handler] _updateMediaItem: artwork is ${artwork != null ? 'not null (${artwork.length} bytes)' : 'null'}'); _currentId = id; _currentTitle = title; _currentArtist = artist; - final position = _player.position; - debugPrint('📢 [handler] updateMediaItem: $title - $artist'); + final position = _playback.player.state.position; - // ⭐ 异步处理 artwork(不阻塞主流程) _handleArtwork(id, artwork).then((artUri) { - // 如果 artUri 变化,重新推送 MediaItem final current = mediaItem.value; if (current != null && current.artUri != artUri) { - debugPrint('📢 [handler] updating artUri: $artUri'); mediaItem.add(audio_service.MediaItem( id: current.id, title: current.title, @@ -127,38 +182,28 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler { } }); - // 先推送不带封面图的 MediaItem(让 UI 尽快显示) mediaItem.add(audio_service.MediaItem( id: id, title: title, artist: artist, - duration: duration ?? _player.duration, + duration: duration ?? _playback.player.state.duration, extras: {'position': position.inMilliseconds}, )); } - /// 处理封面图:保存到本地并生成 content URI Future _handleArtwork(String id, Uint8List? artwork) async { - if (artwork == null || artwork.isEmpty) { - _currentArtworkPath = null; - return null; - } - + if (artwork == null || artwork.isEmpty) return null; try { final dir = await getApplicationDocumentsDirectory(); final artworkDir = Directory('${dir.path}/artworks'); if (!await artworkDir.exists()) { await artworkDir.create(recursive: true); } - - // 使用 md5 生成安全的文件名 final bytes = utf8.encode(id); final digest = md5.convert(bytes); final fileName = '$digest.jpg'; final path = '${artworkDir.path}/$fileName'; final file = File(path); - - // 检查文件是否已存在 if (await file.exists()) { final existingBytes = await file.readAsBytes(); if (existingBytes.length == artwork.length && @@ -167,12 +212,8 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler { return await FileProviderUtils.getContentUri(file); } } - - // 写入新文件 await file.writeAsBytes(artwork); _currentArtworkPath = path; - debugPrint('📢 [handler] artwork saved: $path'); - return await FileProviderUtils.getContentUri(file); } catch (e) { debugPrint('⚠️ [handler] artwork handling failed: $e'); @@ -180,50 +221,54 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler { } } - // ---- 外部接口 ---- void updateNotification({ required String id, required String title, required String artist, Uint8List? artwork, }) { - _updateMediaItem( - id: id, - title: title, - artist: artist, - artwork: artwork, + _updateMediaItem(id: id, title: title, artist: artist, artwork: artwork); + final player = _playback.player; + _publishPlaybackState( + playing: player.state.playing, + position: _currentPosition, + duration: player.state.duration, ); - _publishState(); } - // ---- 控制命令 ---- + // ═══════════════════════════════════════════════════════════════ + // audio_service 控制命令(全部委托给 PlaybackService) + // ═══════════════════════════════════════════════════════════════ @override Future play() async { debugPrint('▶️ [handler] play() called'); - _player.play(); - _publishState(); + await _playback.resume(); } @override Future pause() async { debugPrint('⏸️ [handler] pause() called'); - _player.pause(); - _publishState(); + await _playback.pause(); } @override Future stop() async { debugPrint('⏹️ [handler] stop() called'); - _player.stop(); - _publishState(); + await _playback.stop(); } @override Future seek(Duration position) async { debugPrint('⏩ [handler] seek() called: $position'); - await _player.seek(position); + await _playback.seek(position); _currentPosition = position; - _publishStateOnly(); + final player = _playback.player; + _publishPlaybackState( + playing: player.state.playing, + position: position, + duration: player.state.duration, + onlyPosition: true, + ); } @override @@ -251,14 +296,14 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler { await skipToPrevious(); break; case audio_service.MediaButton.media: - if (_player.isPlaying) { + if (_playback.player.state.playing) { await pause(); } else { await play(); } break; default: - if (_player.isPlaying) { + if (_playback.player.state.playing) { await pause(); } else { await play(); diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index 8a2bc3b..dffe4b3 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -13,6 +13,7 @@ import '../utils/artwork_helper.dart'; import '../repositories/playlist_repository.dart'; import '../models/playlist.dart'; import 'webdav_service.dart'; +import 'audio_player_handler.dart'; enum PlayMode { sequential, @@ -62,6 +63,9 @@ class AudioService extends ChangeNotifier { // ---- 当前播放的歌单 ID ---- String? _currentPlaylistId; + // ⭐ 插入位置:在现有成员变量之后,方法之前 + AudioPlayerHandler? _handler; // ⭐ 添加这一行 + // ---- 高频进度 ---- final ValueNotifier positionNotifier = ValueNotifier(Duration.zero); final ValueNotifier durationNotifier = ValueNotifier(Duration.zero); @@ -115,6 +119,11 @@ class AudioService extends ChangeNotifier { _onSongChanged = callback; } + void setHandler(AudioPlayerHandler handler) { + // ⭐ 添加这个方法 + _handler = handler; + } + void togglePlayMode() { switch (_playMode) { case PlayMode.sequential: @@ -264,6 +273,8 @@ class AudioService extends ChangeNotifier { final song = _queue[_currentIndex]; _currentSong = song; + _handler?.syncState(song); + _startListening(); notifyListeners(); @@ -547,6 +558,8 @@ class AudioService extends ChangeNotifier { } void stopPlay() { + // 停止底层播放器 + PlaybackService().stop(); // 新增 _currentSong = null; _isPlaying = false; positionNotifier.value = Duration.zero; @@ -554,7 +567,6 @@ class AudioService extends ChangeNotifier { bufferedNotifier.value = Duration.zero; _stopListening(); notifyListeners(); - // 停止时也保存一次 savePlaybackState(); } @@ -821,6 +833,8 @@ class AudioService extends ChangeNotifier { _currentSong = song; _onSongChanged?.call(song); + _handler?.syncState(song); // ⭐ 添加这一行 + debugPrint( '♻️ [AudioService] playback state restored: ${song.title} - ${song.artist}');