part of 'player_page.dart'; extension _PlayerPageRelink on _PlayerPageState { bool _handleStreamFailure() { if (!mounted || _switchInFlight || _engineFallbackInFlight || _relink.pending) { return false; } final s = _session; if (s == null || s.phase.value != PlaybackPhase.playing || !s.engine.state.playing) { return false; } final ticks = ticksFromDuration(s.engine.state.position); if (s.lastFailureTerminal) { AppLogger.warn(_tag, 'stream failure terminal → skip relink, surface error'); _rememberFailurePosition(ticks); s.phase.removeListener(_onSessionPhaseChanged); _attachWakelockSession(null); unawaited(s.dispose()); setState(() { _session = null; _errorMessage = '当前版本源无法播放(服务器拒绝,HTTP 4xx)。请切换其他版本源重试。'; }); _invalidateBottomBar(); return true; } _relink.schedule( onGiveUp: () { AppLogger.warn( _tag, 'relink gave up after ${_relink.attempts} attempts', ); _rememberFailurePosition(ticks); s.phase.removeListener(_onSessionPhaseChanged); _attachWakelockSession(null); unawaited(s.dispose()); setState(() { _session = null; _errorMessage = '播放链接已失效,多次重连失败'; }); _invalidateBottomBar(); }, onRelink: (delay) { AppLogger.debug( _tag, 'relink scheduled attempt=${_relink.attempts} ' 'delay=${delay.inSeconds}s posTicks=$ticks', ); final base = _lastRequest; if (!mounted || base == null) return; final req = base.copyWith( startPositionTicks: ticks > 0 ? ticks : null, playFromStart: false, ); unawaited(_switchTo(req)); }, ); return true; } void _rememberFailurePosition(int ticks) { final base = _lastRequest; if (base == null || ticks <= 0) return; _lastRequest = base.copyWith( startPositionTicks: ticks, playFromStart: false, ); } void _armRelinkSustain() { _relink.armSustain(); } }