顺序播放跳曲与首次启动跳曲问题已部分修复,播放逻辑正确,metadata部分仍需修复
This commit is contained in:
+125
-22
@@ -62,14 +62,17 @@ class AudioService extends ChangeNotifier {
|
|||||||
bool _listening = false;
|
bool _listening = false;
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
|
|
||||||
|
// ---- 播放生命周期标志 ----
|
||||||
bool _handlingCompletion = false;
|
bool _handlingCompletion = false;
|
||||||
void Function(Song)? _onSongChanged;
|
bool _isChangingTrack = false;
|
||||||
|
bool _hasStartedCurrentPlayback = false;
|
||||||
bool _isUserSeeking = false;
|
bool _isUserSeeking = false;
|
||||||
|
|
||||||
// ⭐ 播放代数:每次切歌递增,用于校验异步任务是否过期
|
// ⭐ 播放代数:每次切歌递增,用于校验异步任务是否过期
|
||||||
int _playbackGeneration = 0;
|
int _playbackGeneration = 0;
|
||||||
|
|
||||||
|
void Function(Song)? _onSongChanged;
|
||||||
|
|
||||||
// ---- Repository ----
|
// ---- Repository ----
|
||||||
final SongDatabase _db = SongDatabase();
|
final SongDatabase _db = SongDatabase();
|
||||||
final PlaylistRepository _playlistRepo = PlaylistRepository();
|
final PlaylistRepository _playlistRepo = PlaylistRepository();
|
||||||
@@ -123,7 +126,7 @@ class AudioService extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- 设置播放队列 ----
|
// ---- 设置播放队列 ----
|
||||||
void setQueue(List<Song> queue, {int startIndex = 0}) {
|
Future<void> setQueue(List<Song> queue, {int startIndex = 0}) async {
|
||||||
if (queue.isEmpty) {
|
if (queue.isEmpty) {
|
||||||
_clearQueue();
|
_clearQueue();
|
||||||
return;
|
return;
|
||||||
@@ -132,6 +135,7 @@ class AudioService extends ChangeNotifier {
|
|||||||
_queue = List.from(queue);
|
_queue = List.from(queue);
|
||||||
_currentIndex = startIndex.clamp(0, _queue.length - 1);
|
_currentIndex = startIndex.clamp(0, _queue.length - 1);
|
||||||
|
|
||||||
|
if (_playMode == PlayMode.shuffle) {
|
||||||
_shuffledIndices = List.generate(_queue.length, (i) => i);
|
_shuffledIndices = List.generate(_queue.length, (i) => i);
|
||||||
_shuffledIndices.shuffle();
|
_shuffledIndices.shuffle();
|
||||||
_shuffledIndex = _shuffledIndices.indexOf(_currentIndex);
|
_shuffledIndex = _shuffledIndices.indexOf(_currentIndex);
|
||||||
@@ -139,8 +143,12 @@ class AudioService extends ChangeNotifier {
|
|||||||
_shuffledIndex = 0;
|
_shuffledIndex = 0;
|
||||||
_currentIndex = _shuffledIndices[0];
|
_currentIndex = _shuffledIndices[0];
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
_shuffledIndices = List.generate(_queue.length, (i) => i);
|
||||||
|
_shuffledIndex = _currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
_playCurrent();
|
await _playCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _clearQueue() {
|
void _clearQueue() {
|
||||||
@@ -156,9 +164,9 @@ class AudioService extends ChangeNotifier {
|
|||||||
Future<void> playSong(Song song) async {
|
Future<void> playSong(Song song) async {
|
||||||
_currentPlaylistId = null;
|
_currentPlaylistId = null;
|
||||||
if (_queue.isEmpty || _queue[_currentIndex].id != song.id) {
|
if (_queue.isEmpty || _queue[_currentIndex].id != song.id) {
|
||||||
setQueue([song], startIndex: 0);
|
await setQueue([song], startIndex: 0);
|
||||||
} else {
|
} else {
|
||||||
_playCurrent();
|
await _playCurrent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +206,7 @@ class AudioService extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (songs.isNotEmpty) {
|
if (songs.isNotEmpty) {
|
||||||
setQueue(songs, startIndex: startIndex);
|
await setQueue(songs, startIndex: startIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,15 +220,26 @@ class AudioService extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════
|
||||||
// ⭐ 播放核心(修复:自动添加认证头)
|
// ⭐ 播放核心(完整生命周期控制)
|
||||||
// ════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
void _playCurrent() {
|
Future<void> _playCurrent() async {
|
||||||
|
if (_isChangingTrack) {
|
||||||
|
debugPrint('⚠️ [AudioService] track switch already running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_currentIndex < 0 || _currentIndex >= _queue.length) {
|
if (_currentIndex < 0 || _currentIndex >= _queue.length) {
|
||||||
stopPlay();
|
stopPlay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⭐ 进入切歌状态
|
||||||
|
_isChangingTrack = true;
|
||||||
|
_handlingCompletion = false;
|
||||||
|
_hasStartedCurrentPlayback = false;
|
||||||
|
|
||||||
|
try {
|
||||||
_playbackGeneration++;
|
_playbackGeneration++;
|
||||||
final generation = _playbackGeneration;
|
final generation = _playbackGeneration;
|
||||||
|
|
||||||
@@ -234,14 +253,49 @@ class AudioService extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⭐ 关键修复:播放时自动添加认证头
|
// ⭐ 关键:等待播放器加载完成
|
||||||
_playWithHeaders(song.url!);
|
await _playWithHeaders(song.url!);
|
||||||
|
|
||||||
|
// ⭐ 等待播放真正开始
|
||||||
|
await _waitForPlaybackStarted();
|
||||||
|
|
||||||
_syncPlayerStateDelayed();
|
_syncPlayerStateDelayed();
|
||||||
|
|
||||||
|
// ⭐ 只有真正开始播放后才推送通知
|
||||||
|
if (_hasStartedCurrentPlayback) {
|
||||||
_onSongChanged?.call(song);
|
_onSongChanged?.call(song);
|
||||||
_loadMetadataForCurrentSong(generation);
|
_loadMetadataForCurrentSong(generation);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
_isChangingTrack = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ⭐ 等待播放器真正开始播放
|
||||||
|
Future<void> _waitForPlaybackStarted() async {
|
||||||
|
final player = PlaybackService().player;
|
||||||
|
|
||||||
|
// 如果已经在播放,直接标记
|
||||||
|
if (player.state.playing) {
|
||||||
|
_hasStartedCurrentPlayback = true;
|
||||||
|
debugPrint('🎵 [AudioService] playback already started');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待 playing 变为 true,超时 3 秒
|
||||||
|
try {
|
||||||
|
await player.stream.playing
|
||||||
|
.where((playing) => playing == true)
|
||||||
|
.first
|
||||||
|
.timeout(const Duration(seconds: 3));
|
||||||
|
_hasStartedCurrentPlayback = true;
|
||||||
|
debugPrint('🎵 [AudioService] playback started');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('⚠️ [AudioService] wait for playback timeout: $e');
|
||||||
|
// 超时也标记为已开始,避免永久阻塞
|
||||||
|
_hasStartedCurrentPlayback = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ⭐ 播放带认证头的 URL
|
/// ⭐ 播放带认证头的 URL
|
||||||
Future<void> _playWithHeaders(String url) async {
|
Future<void> _playWithHeaders(String url) async {
|
||||||
@@ -374,13 +428,13 @@ class AudioService extends ChangeNotifier {
|
|||||||
|
|
||||||
if (currentIndex >= 0 && currentIndex < _queue.length) {
|
if (currentIndex >= 0 && currentIndex < _queue.length) {
|
||||||
stopPlay();
|
stopPlay();
|
||||||
_playCurrent();
|
await _playCurrent();
|
||||||
debugPrint('🔄 [AudioService] song reloaded');
|
debugPrint('🔄 [AudioService] song reloaded');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 下一首 ----
|
// ---- 下一首 ----
|
||||||
void next() {
|
Future<void> next() async {
|
||||||
if (_queue.isEmpty) return;
|
if (_queue.isEmpty) return;
|
||||||
|
|
||||||
if (_playMode == PlayMode.shuffle) {
|
if (_playMode == PlayMode.shuffle) {
|
||||||
@@ -388,17 +442,17 @@ class AudioService extends ChangeNotifier {
|
|||||||
final nextIdx = (_shuffledIndex + 1) % _shuffledIndices.length;
|
final nextIdx = (_shuffledIndex + 1) % _shuffledIndices.length;
|
||||||
_shuffledIndex = nextIdx;
|
_shuffledIndex = nextIdx;
|
||||||
_currentIndex = _shuffledIndices[nextIdx];
|
_currentIndex = _shuffledIndices[nextIdx];
|
||||||
_playCurrent();
|
await _playCurrent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final nextIdx = (_currentIndex + 1) % _queue.length;
|
final nextIdx = (_currentIndex + 1) % _queue.length;
|
||||||
_currentIndex = nextIdx;
|
_currentIndex = nextIdx;
|
||||||
_playCurrent();
|
await _playCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 上一首 ----
|
// ---- 上一首 ----
|
||||||
void previous() {
|
Future<void> previous() async {
|
||||||
if (_queue.isEmpty) return;
|
if (_queue.isEmpty) return;
|
||||||
|
|
||||||
if (_playMode == PlayMode.shuffle) {
|
if (_playMode == PlayMode.shuffle) {
|
||||||
@@ -410,7 +464,7 @@ class AudioService extends ChangeNotifier {
|
|||||||
_shuffledIndex = prevIdx;
|
_shuffledIndex = prevIdx;
|
||||||
}
|
}
|
||||||
_currentIndex = _shuffledIndices[_shuffledIndex];
|
_currentIndex = _shuffledIndices[_shuffledIndex];
|
||||||
_playCurrent();
|
await _playCurrent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +474,7 @@ class AudioService extends ChangeNotifier {
|
|||||||
} else {
|
} else {
|
||||||
_currentIndex = prevIdx;
|
_currentIndex = prevIdx;
|
||||||
}
|
}
|
||||||
_playCurrent();
|
await _playCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 播放/暂停 ----
|
// ---- 播放/暂停 ----
|
||||||
@@ -494,6 +548,7 @@ class AudioService extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 监听 media_kit 状态 ----
|
||||||
void _startListening() {
|
void _startListening() {
|
||||||
if (_listening) return;
|
if (_listening) return;
|
||||||
_listening = true;
|
_listening = true;
|
||||||
@@ -534,8 +589,47 @@ class AudioService extends ChangeNotifier {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ⭐ 完整生命周期校验的 completed 监听
|
||||||
_subscriptions.add(
|
_subscriptions.add(
|
||||||
player.stream.completed.listen((_) {
|
player.stream.completed.listen((_) {
|
||||||
|
// 第一层:切歌期间屏蔽
|
||||||
|
if (_isChangingTrack) {
|
||||||
|
debugPrint('⚠️ [AudioService] completed ignored during track switch');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二层:从未真正开始播放,忽略
|
||||||
|
if (!_hasStartedCurrentPlayback) {
|
||||||
|
debugPrint(
|
||||||
|
'⚠️ [AudioService] completed ignored: playback never started');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第三层:防重入
|
||||||
|
if (_handlingCompletion) {
|
||||||
|
debugPrint('⚠️ [service] completed ignored: already handling');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第四层:额外校验 - 当前歌曲必须有有效的 duration
|
||||||
|
final pos = player.state.position;
|
||||||
|
final dur = player.state.duration;
|
||||||
|
if (dur.inMilliseconds <= 0) {
|
||||||
|
debugPrint(
|
||||||
|
'⚠️ [AudioService] completed ignored: invalid duration $dur');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第五层:位置必须接近歌曲末尾(允许 500ms 误差)
|
||||||
|
if (pos.inMilliseconds < dur.inMilliseconds - 500) {
|
||||||
|
debugPrint(
|
||||||
|
'⚠️ [AudioService] completed ignored: position=$pos, duration=$dur, not at end');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint(
|
||||||
|
'🎵 [AudioService] valid completed: index=$_currentIndex, pos=$pos, dur=$dur');
|
||||||
|
_handlingCompletion = true;
|
||||||
_onPlaybackCompleted();
|
_onPlaybackCompleted();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -549,14 +643,23 @@ class AudioService extends ChangeNotifier {
|
|||||||
_subscriptions.clear();
|
_subscriptions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 播放完成处理 ----
|
||||||
void _onPlaybackCompleted() {
|
void _onPlaybackCompleted() {
|
||||||
if (_handlingCompletion) {
|
// 队列状态检查
|
||||||
debugPrint('⚠️ [service] completed ignored: already handling');
|
if (_queue.isEmpty || _currentIndex < 0 || _currentIndex >= _queue.length) {
|
||||||
|
debugPrint('⚠️ [service] completed ignored: invalid queue');
|
||||||
|
_handlingCompletion = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_handlingCompletion = true;
|
|
||||||
|
// 如果是最后一首且不是单曲循环模式,不触发 next
|
||||||
|
if (_currentIndex + 1 >= _queue.length && _playMode != PlayMode.repeatOne) {
|
||||||
|
debugPrint('⚠️ [service] completed ignored: last song in queue');
|
||||||
|
_handlingCompletion = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (_queue.isEmpty) return;
|
|
||||||
if (_playMode == PlayMode.repeatOne) {
|
if (_playMode == PlayMode.repeatOne) {
|
||||||
_playCurrent();
|
_playCurrent();
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user