Files
QingTing-Player/lib/services/audio_service.dart
T

325 lines
7.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// lib/services/audio_service.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:media_kit/media_kit.dart';
import 'playback_service.dart';
enum PlayMode {
sequential,
repeatOne,
shuffle,
}
class Song {
final String id;
final String title;
final String artist;
final String? url;
Song({
required this.id,
required this.title,
required this.artist,
this.url,
});
}
class AudioService extends ChangeNotifier {
static final AudioService _instance = AudioService._internal();
factory AudioService() => _instance;
AudioService._internal();
// ---- 基础状态(低频,触发 UI 重建) ----
Song? _currentSong;
bool _isPlaying = false;
PlayMode _playMode = PlayMode.sequential;
// ---- 播放队列 ----
List<Song> _queue = [];
int _currentIndex = -1;
List<int> _shuffledIndices = [];
int _shuffledIndex = -1;
// ---- ⭐ 高频进度(用 ValueNotifier,不触发全局重建) ----
final ValueNotifier<Duration> positionNotifier = ValueNotifier(Duration.zero);
final ValueNotifier<Duration> durationNotifier = ValueNotifier(Duration.zero);
final ValueNotifier<Duration> bufferedNotifier = ValueNotifier(Duration.zero);
bool _listening = false;
final List<StreamSubscription> _subscriptions = [];
// ⭐ 防重入标志
bool _handlingCompletion = false;
// ⭐ 歌曲切换回调(用于通知 Handler 更新 MediaItem
void Function(Song)? _onSongChanged;
// ---- Getter(高频字段不走 ChangeNotifier ----
Song? get currentSong => _currentSong;
bool get isPlaying => _isPlaying;
PlayMode get playMode => _playMode;
List<Song> get queue => List.unmodifiable(_queue);
int get currentIndex => _currentIndex;
bool get hasQueue => _queue.isNotEmpty;
// ---- 兼容旧代码:提供 getter 返回 ValueNotifier 的值 ----
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<Song> 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<void> 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;
// ⭐ 切歌时触发回调(通知 Handler 更新 MediaItem
_onSongChanged?.call(song);
// ⭐ 重置进度(用 ValueNotifier
positionNotifier.value = Duration.zero;
durationNotifier.value = Duration.zero;
bufferedNotifier.value = Duration.zero;
_startListening();
notifyListeners();
if (song.url == null || song.url!.isEmpty) {
return;
}
PlaybackService().play(song.url!);
}
// ---- 下一首 ----
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) {
PlaybackService().seek(position);
positionNotifier.value = position;
}
void clearQueue() {
_queue.clear();
_currentIndex = -1;
_shuffledIndices.clear();
_shuffledIndex = -1;
stopPlay();
notifyListeners();
}
// ---- 监听 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) {
positionNotifier.value = position;
}),
);
_subscriptions.add(
player.stream.duration.listen((duration) {
durationNotifier.value = duration;
}),
);
_subscriptions.add(
player.stream.buffer.listen((buffer) {
bufferedNotifier.value = buffer;
}),
);
// ⭐ 唯一监听 completed 的地方(带防重入)
_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();
}
}