// lib/services/audio_service.dart import 'dart:async'; import 'package:flutter/material.dart'; import 'package:media_kit/media_kit.dart'; import 'playback_service.dart'; import '../metadata/metadata_service.dart'; import 'dart:typed_data'; enum PlayMode { sequential, repeatOne, shuffle, } class Song { final String id; final String title; final String artist; final String? url; final Uint8List? artwork; Song({ required this.id, required this.title, required this.artist, this.url, this.artwork, }); } class AudioService extends ChangeNotifier { static final AudioService _instance = AudioService._internal(); factory AudioService() => _instance; AudioService._internal(); // ---- 基础状态 ---- Song? _currentSong; bool _isPlaying = false; PlayMode _playMode = PlayMode.sequential; // ---- 播放队列 ---- List _queue = []; int _currentIndex = -1; List _shuffledIndices = []; int _shuffledIndex = -1; // ---- 高频进度(ValueNotifier,不触发全局重建) ---- final ValueNotifier positionNotifier = ValueNotifier(Duration.zero); final ValueNotifier durationNotifier = ValueNotifier(Duration.zero); final ValueNotifier bufferedNotifier = ValueNotifier(Duration.zero); bool _listening = false; final List _subscriptions = []; bool _handlingCompletion = false; void Function(Song)? _onSongChanged; // ⭐ 防 seek 覆盖标志 bool _isUserSeeking = false; // ---- Getter ---- Song? get currentSong => _currentSong; bool get isPlaying => _isPlaying; PlayMode get playMode => _playMode; List get queue => List.unmodifiable(_queue); int get currentIndex => _currentIndex; bool get hasQueue => _queue.isNotEmpty; Duration get position => positionNotifier.value; Duration get duration => durationNotifier.value; Duration get bufferedPosition => bufferedNotifier.value; IconData get playModeIcon { switch (_playMode) { case PlayMode.sequential: return Icons.repeat; case PlayMode.repeatOne: return Icons.repeat_one; case PlayMode.shuffle: return Icons.shuffle; } } // ---- 注册回调 ---- void setOnSongChanged(void Function(Song) callback) { _onSongChanged = callback; } // ---- 切换播放模式 ---- void togglePlayMode() { switch (_playMode) { case PlayMode.sequential: _playMode = PlayMode.repeatOne; break; case PlayMode.repeatOne: _playMode = PlayMode.shuffle; break; case PlayMode.shuffle: _playMode = PlayMode.sequential; break; } notifyListeners(); } // ---- 设置播放队列 ---- void setQueue(List queue, {int startIndex = 0}) { if (queue.isEmpty) { _clearQueue(); return; } _queue = List.from(queue); _currentIndex = startIndex.clamp(0, _queue.length - 1); _shuffledIndices = List.generate(_queue.length, (i) => i); _shuffledIndices.shuffle(); _shuffledIndex = _shuffledIndices.indexOf(_currentIndex); if (_shuffledIndex == -1) { _shuffledIndex = 0; _currentIndex = _shuffledIndices[0]; } _playCurrent(); } void _clearQueue() { _queue.clear(); _currentIndex = -1; _shuffledIndices.clear(); _shuffledIndex = -1; stopPlay(); } // ---- 播放指定歌曲 ---- Future playSong(Song song) async { if (_queue.isEmpty || _queue[_currentIndex].id != song.id) { setQueue([song], startIndex: 0); } else { _playCurrent(); } } // ---- 核心:播放当前歌曲 ---- void _playCurrent() { if (_currentIndex < 0 || _currentIndex >= _queue.length) { stopPlay(); return; } final song = _queue[_currentIndex]; _currentSong = song; _startListening(); notifyListeners(); if (song.url == null || song.url!.isEmpty) { return; } PlaybackService().play(song.url!); // 延迟同步兜底(解决首次加载时 stream 未推送的问题) _syncPlayerStateDelayed(); _loadMetadataForCurrentSong(); } // ---- 加载 metadata ---- Future _loadMetadataForCurrentSong() async { if (_currentIndex < 0 || _currentIndex >= _queue.length) return; final song = _queue[_currentIndex]; if (song.url == null || song.url!.isEmpty) return; try { final metadata = await MetadataService().getMetadata( url: song.url!, fileName: song.title, fileId: song.id, ); if (metadata.isNotEmpty) { final updatedSong = Song( id: song.id, title: metadata.title.isNotEmpty ? metadata.title : song.title, artist: metadata.artist.isNotEmpty ? metadata.artist : song.artist, url: song.url, artwork: metadata.artwork, // ⭐ 如果需要传递 artwork,可以在这里添加字段 ); _queue[_currentIndex] = updatedSong; _currentSong = updatedSong; notifyListeners(); _onSongChanged?.call(updatedSong); } } catch (e) { debugPrint('⚠️ [AudioService] metadata load failed: $e'); } } // ---- 下一首 ---- void next() { if (_queue.isEmpty) return; if (_playMode == PlayMode.shuffle) { if (_shuffledIndices.isEmpty) return; final nextIdx = (_shuffledIndex + 1) % _shuffledIndices.length; _shuffledIndex = nextIdx; _currentIndex = _shuffledIndices[nextIdx]; _playCurrent(); return; } final nextIdx = (_currentIndex + 1) % _queue.length; _currentIndex = nextIdx; _playCurrent(); } // ---- 上一首 ---- void previous() { if (_queue.isEmpty) return; if (_playMode == PlayMode.shuffle) { if (_shuffledIndices.isEmpty) return; final prevIdx = (_shuffledIndex - 1) % _shuffledIndices.length; if (prevIdx < 0) { _shuffledIndex = _shuffledIndices.length - 1; } else { _shuffledIndex = prevIdx; } _currentIndex = _shuffledIndices[_shuffledIndex]; _playCurrent(); return; } final prevIdx = (_currentIndex - 1) % _queue.length; if (prevIdx < 0) { _currentIndex = _queue.length - 1; } else { _currentIndex = prevIdx; } _playCurrent(); } // ---- 播放/暂停 ---- void togglePlay() { if (_currentSong == null) return; if (_isPlaying) { PlaybackService().pause(); } else { PlaybackService().resume(); } } // ---- 停止播放 ---- void stopPlay() { _currentSong = null; _isPlaying = false; positionNotifier.value = Duration.zero; durationNotifier.value = Duration.zero; bufferedNotifier.value = Duration.zero; _stopListening(); notifyListeners(); } // ---- 跳转 ---- void seekTo(Duration position) { _isUserSeeking = true; PlaybackService().seek(position); positionNotifier.value = position; // 800ms 后重置标志(覆盖 media_kit 的 positionStream 推送窗口) Future.delayed(const Duration(milliseconds: 800), () { _isUserSeeking = false; }); } // ---- 清空队列 ---- void clearQueue() { _queue.clear(); _currentIndex = -1; _shuffledIndices.clear(); _shuffledIndex = -1; stopPlay(); notifyListeners(); } // ---- 主动状态同步(供 PlayerPage 调用) ---- void syncPlayerStateNow() { final player = PlaybackService().player; final pos = player.state.position; final dur = player.state.duration; final buf = player.state.buffer; debugPrint( '🎯 [AudioService] syncPlayerStateNow: pos=$pos, dur=$dur, buf=$buf'); if (pos.inMilliseconds >= 0) { positionNotifier.value = pos; } if (dur.inMilliseconds > 0) { durationNotifier.value = dur; } if (buf.inMilliseconds >= 0) { bufferedNotifier.value = buf; } } // ---- 延迟同步(兜底) ---- void _syncPlayerStateDelayed() { syncPlayerStateNow(); Future.delayed(const Duration(milliseconds: 200), () { syncPlayerStateNow(); }); Future.delayed(const Duration(milliseconds: 500), () { syncPlayerStateNow(); }); } // ---- 监听 media_kit 状态 ---- void _startListening() { if (_listening) return; _listening = true; final player = PlaybackService().player; _subscriptions.add( player.stream.playing.listen((playing) { if (_isPlaying != playing) { _isPlaying = playing; notifyListeners(); } }), ); _subscriptions.add( player.stream.position.listen((position) { // ⭐ 如果是用户主动 seek,忽略这次推送(避免覆盖) if (_isUserSeeking) { debugPrint('🎯 [AudioService] positionStream ignored: user seeking'); return; } positionNotifier.value = position; }), ); _subscriptions.add( player.stream.duration.listen((duration) { debugPrint('🎯 [AudioService] durationStream: $duration'); if (duration.inMilliseconds > 0) { durationNotifier.value = duration; } }), ); _subscriptions.add( player.stream.buffer.listen((buffer) { bufferedNotifier.value = buffer; }), ); _subscriptions.add( player.stream.completed.listen((_) { _onPlaybackCompleted(); }), ); } void _stopListening() { _listening = false; for (final subscription in _subscriptions) { subscription.cancel(); } _subscriptions.clear(); } // ---- 防重入的完成事件处理 ---- void _onPlaybackCompleted() { if (_handlingCompletion) { debugPrint('⚠️ [service] completed ignored: already handling'); return; } _handlingCompletion = true; try { if (_queue.isEmpty) return; if (_playMode == PlayMode.repeatOne) { _playCurrent(); return; } next(); } finally { _handlingCompletion = false; } } @override void dispose() { _stopListening(); positionNotifier.dispose(); durationNotifier.dispose(); bufferedNotifier.dispose(); PlaybackService().dispose(); super.dispose(); } }