337 lines
8.2 KiB
Dart
337 lines
8.2 KiB
Dart
// 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,
|
|
});
|
|
}
|
|
|
|
// ============================================================
|
|
// AudioService - 播放状态管理与 UI 数据源
|
|
// ============================================================
|
|
class AudioService extends ChangeNotifier {
|
|
// ---------- 单例 ----------
|
|
static final AudioService _instance = AudioService._internal();
|
|
factory AudioService() => _instance;
|
|
AudioService._internal();
|
|
|
|
// ---------- 播放状态 ----------
|
|
Song? _currentSong;
|
|
bool _isPlaying = false;
|
|
Duration _position = Duration.zero;
|
|
Duration _duration = Duration.zero;
|
|
Duration _bufferedPosition = Duration.zero;
|
|
|
|
// ---------- 播放模式 ----------
|
|
PlayMode _playMode = PlayMode.sequential;
|
|
|
|
// ---------- 播放队列 ----------
|
|
List<Song> _queue = [];
|
|
int _currentIndex = -1;
|
|
|
|
// ---------- 随机播放相关 ----------
|
|
List<int> _shuffledIndices = [];
|
|
int _shuffledIndex = -1;
|
|
|
|
// ---------- 监听控制 ----------
|
|
bool _listening = false;
|
|
final List<StreamSubscription> _subscriptions = [];
|
|
|
|
// ---------- Getter ----------
|
|
Song? get currentSong => _currentSong;
|
|
bool get isPlaying => _isPlaying;
|
|
Duration get position => _position;
|
|
Duration get duration => _duration;
|
|
Duration get bufferedPosition => _bufferedPosition;
|
|
PlayMode get playMode => _playMode;
|
|
List<Song> get queue => List.unmodifiable(_queue);
|
|
int get currentIndex => _currentIndex;
|
|
bool get hasQueue => _queue.isNotEmpty;
|
|
|
|
// ---------- 播放模式图标 ----------
|
|
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 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;
|
|
_position = Duration.zero;
|
|
_duration = Duration.zero;
|
|
_bufferedPosition = Duration.zero;
|
|
|
|
_startListening();
|
|
notifyListeners();
|
|
|
|
if (song.url == null || song.url!.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
// 由 PlaybackService 实际播放,状态由流更新
|
|
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();
|
|
}
|
|
// 状态由 player.stream.playing 更新
|
|
}
|
|
|
|
// ---------- 停止播放 ----------
|
|
void stopPlay() {
|
|
_currentSong = null;
|
|
_isPlaying = false;
|
|
_position = Duration.zero;
|
|
_duration = Duration.zero;
|
|
_bufferedPosition = Duration.zero;
|
|
_stopListening();
|
|
notifyListeners();
|
|
}
|
|
|
|
// ---------- Seek ----------
|
|
void seekTo(Duration position) {
|
|
PlaybackService().seek(position);
|
|
_position = position;
|
|
notifyListeners();
|
|
}
|
|
|
|
// ---------- 处理播放结束 ----------
|
|
void _onPlaybackCompleted() {
|
|
if (_queue.isEmpty) return;
|
|
|
|
// 单曲循环模式
|
|
if (_playMode == PlayMode.repeatOne) {
|
|
_playCurrent();
|
|
return;
|
|
}
|
|
|
|
// 其他模式:播放下一首
|
|
next();
|
|
}
|
|
|
|
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) {
|
|
if (_position != position) {
|
|
_position = position;
|
|
notifyListeners();
|
|
}
|
|
}),
|
|
);
|
|
|
|
_subscriptions.add(
|
|
player.stream.duration.listen((duration) {
|
|
if (_duration != duration) {
|
|
_duration = duration;
|
|
notifyListeners();
|
|
}
|
|
}),
|
|
);
|
|
|
|
_subscriptions.add(
|
|
player.stream.buffer.listen((buffer) {
|
|
if (_bufferedPosition != buffer) {
|
|
_bufferedPosition = buffer;
|
|
notifyListeners();
|
|
}
|
|
}),
|
|
);
|
|
|
|
// ✅ 播放结束监听
|
|
_subscriptions.add(
|
|
player.stream.completed.listen((_) {
|
|
_onPlaybackCompleted();
|
|
}),
|
|
);
|
|
}
|
|
|
|
void _stopListening() {
|
|
_listening = false;
|
|
for (final subscription in _subscriptions) {
|
|
subscription.cancel();
|
|
}
|
|
_subscriptions.clear();
|
|
}
|
|
|
|
// ---------- 资源释放 ----------
|
|
void dispose() {
|
|
_stopListening();
|
|
PlaybackService().dispose();
|
|
super.dispose();
|
|
}
|
|
}
|