diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index 6ad9b41..032ae5f 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -109,6 +109,10 @@ class AudioService extends ChangeNotifier { DateTime? _trackStartAt; DateTime? _playReadyAt; int _prefetchedGeneration = -1; + + /// 元数据回退到文件名时置位,播到 80% 再试一次 + bool _metadataNeedsRetry = false; + int _metadataRetryGeneration = -1; bool _firstAudioReported = false; /// 最近一次播放失败的原因,供 UI 提示;成功播放后自动清空。 @@ -319,6 +323,7 @@ class AudioService extends ChangeNotifier { _trackStartAt = DateTime.now(); _playReadyAt = null; _firstAudioReported = false; + _metadataNeedsRetry = false; await _playWithHeaders(song.url!); _playReadyAt = DateTime.now(); } catch (e) { @@ -411,6 +416,7 @@ class AudioService extends ChangeNotifier { final settled = await _waitForPlaybackSettled(epoch); if (!settled || epoch != _switchEpoch) { debugPrint('⏸️ [AudioService] 播放未稳定,跳过本次元数据抓取(避免抢带宽)'); + _metadataNeedsRetry = true; // 留到 80% 再试 return; } await _loadMetadataForCurrentSong(generation); @@ -420,6 +426,25 @@ class AudioService extends ChangeNotifier { }(); } + /// 播到 80% 时,对"元数据回退到文件名"的歌再抓一次。 + /// + /// 原因:反代在等上游首包超时时会直接回 502(实测 10.3s), + /// 起播阶段抓取很容易撞上;到 80% 时链路通常已经热了,重试成功率更高。 + /// 每首歌最多重试一次。 + void _maybeRetryMetadata(Duration position) { + if (!_metadataNeedsRetry) return; + + final duration = durationNotifier.value; + if (duration.inMilliseconds <= 0) return; + if (position.inMilliseconds < duration.inMilliseconds * 0.8) return; + if (_metadataRetryGeneration == _playbackGeneration) return; + + _metadataRetryGeneration = _playbackGeneration; + _metadataNeedsRetry = false; + debugPrint('🔁 [AudioService] 播放到 80%,重试一次元数据抓取'); + _loadMetadataForCurrentSong(_playbackGeneration); + } + /// 播到 60% 时预热下一首(HEAD + Range 0-0,不下载正文)。 /// /// 日志实测起播延迟 2~3s 都在"等首批数据",提前完成 DNS/TLS/反代握手 @@ -654,6 +679,15 @@ class AudioService extends ChangeNotifier { } if (metadata.isNotEmpty) { + // 来源是文件名 = 元数据没读到(常见于反代 502 / 超时) + // 记下来,播到 80% 时再试一次,避免这首歌永远只有文件名 + if (metadata.source == 'filename') { + _metadataNeedsRetry = true; + debugPrint('⚠️ [AudioService] 元数据回退到文件名,将在 80% 处重试一次'); + } else { + _metadataNeedsRetry = false; + } + final updatedSong = Song( id: song.id, title: metadata.title.isNotEmpty ? metadata.title : song.title, @@ -896,6 +930,7 @@ class AudioService extends ChangeNotifier { _playReadyAt = null; } } + _maybeRetryMetadata(position); _maybePrefetchNext(position); positionNotifier.value = position; }),