// lib/services/audio_player_handler.dart import 'dart:io'; // ⭐ 添加 import 'dart:typed_data'; // ⭐ 添加 import 'package:flutter/foundation.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'; class AudioPlayerHandler extends audio_service.BaseAudioHandler { final PlayerController _player = PlayerController(); final PlaybackStateManager _state = PlaybackStateManager(); String? _currentId; String? _currentTitle; String? _currentArtist; // ⭐ 唯一的位置变量 Duration _currentPosition = Duration.zero; DateTime _lastPublishTime = DateTime.now(); static const Duration _publishInterval = Duration(milliseconds: 500); AudioPlayerHandler() { _player.playingStream.listen((playing) { _state.updatePlaying(playing); _publishState(); }); // ⭐ 位置更新:直接赋值给 _currentPosition _player.positionStream.listen((position) { _currentPosition = position; _state.updatePosition(position); final now = DateTime.now(); if (now.difference(_lastPublishTime) >= _publishInterval) { _lastPublishTime = now; _updateMediaItemPosition(position); _publishStateOnly(); } }); _player.durationStream.listen((duration) { debugPrint('🎯 [durationStream] duration=$duration'); _state.updateDuration(duration); if (_currentId != null && _currentTitle != null) { _updateMediaItem( id: _currentId!, title: _currentTitle!, artist: _currentArtist!, duration: duration, ); } _publishState(); }); } 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, }, )); } void _publishStateOnly() { final current = playbackState.value; debugPrint( '📡 [publishStateOnly] position=$_currentPosition, playing=${current.playing}', ); playbackState.add( audio_service.PlaybackState( controls: current.controls.isNotEmpty ? current.controls : _state.playbackState.controls, processingState: _state.playbackState.processingState, playing: current.playing, androidCompactActionIndices: current.androidCompactActionIndices, // ⭐ 声明支持 seek systemActions: const { audio_service.MediaAction.seek, }, updatePosition: _currentPosition, updateTime: DateTime.now(), ), ); } void updateNotification({ required String id, required String title, required String artist, Uint8List? artwork, }) { _updateMediaItem( id: id, title: title, artist: artist, artwork: artwork, ); } void _updateMediaItem({ required String id, required String title, required String artist, Duration? duration, Uint8List? artwork, }) { _currentId = id; _currentTitle = title; _currentArtist = artist; final position = _player.position; debugPrint('📢 [handler] updateMediaItem: $title - $artist'); // ⭐ 如果有 artwork,保存为临时文件并设置 artUri Uri? artUri; if (artwork != null && artwork.isNotEmpty) { try { final tempDir = Directory.systemTemp; final artPath = '${tempDir.path}/art_${id.hashCode}.jpg'; final artFile = File(artPath); artFile.writeAsBytesSync(artwork); artUri = Uri.file(artPath); debugPrint('📢 [handler] artwork saved: $artPath'); } catch (e) { debugPrint('⚠️ [handler] save artwork failed: $e'); } } mediaItem.add(audio_service.MediaItem( id: id, title: title, artist: artist, duration: duration ?? _player.duration, artUri: artUri, extras: { 'position': position.inMilliseconds, }, )); } void _updateMediaItemPosition(Duration position) { final currentMediaItem = mediaItem.value; if (currentMediaItem != null) { mediaItem.add(audio_service.MediaItem( id: currentMediaItem.id, title: currentMediaItem.title, artist: currentMediaItem.artist, duration: currentMediaItem.duration, artUri: currentMediaItem.artUri, extras: { 'position': position.inMilliseconds, ...?currentMediaItem.extras, }, )); } } @override Future play() async { debugPrint('▶️ [handler] play() called'); _player.play(); _publishState(); } @override Future pause() async { debugPrint('⏸️ [handler] pause() called'); _player.pause(); _publishState(); } @override Future stop() async { debugPrint('⏹️ [handler] stop() called'); _player.stop(); _publishState(); } @override Future seek(Duration position) async { debugPrint('⏩ [handler] seek() called: $position'); await _player.seek(position); _currentPosition = position; _updateMediaItemPosition(position); _publishStateOnly(); } @override Future skipToNext() async { debugPrint('⏭️ [handler] skipToNext() called'); AudioService().next(); } @override Future skipToPrevious() async { debugPrint('⏮️ [handler] skipToPrevious() called'); AudioService().previous(); } @override Future click( [audio_service.MediaButton button = audio_service.MediaButton.media]) async { debugPrint('🎯 [handler] click() called with button: ${button.name}'); switch (button) { case audio_service.MediaButton.next: await skipToNext(); break; case audio_service.MediaButton.previous: await skipToPrevious(); break; case audio_service.MediaButton.media: if (_player.isPlaying) { await pause(); } else { await play(); } break; default: if (_player.isPlaying) { await pause(); } else { await play(); } } } }