Files
QingTing-Player/lib/services/network_watcher.dart
T
lxh2875931338 b517c75cdd feat(playback): 网络切换后自动重连当前歌曲
mpv 的 HTTP 长连接不会跟随网络迁移:换 Wi-Fi / Wi-Fi<->移动数据
会换掉出口 IP,旧连接失效但不一定报错,表现为进度停住、一直缓冲。

- 新增 NetworkWatcher:每 10s 枚举 IPv4 接口,签名变化即视为网络切换
  (不依赖插件和额外权限,也能覆盖"同类型网络换 AP"的情况)
- AudioService 收到变化后 3s 去抖,然后强制重连当前歌曲并 seek 回原位置
  (force 参数跳过 2 分钟节流,因为这是明确的外部事件)

真机验证(公网链路 + 移动数据):
  21:43:23.123  检测到网络变化
  21:43:26.124  主动重连(3s 去抖)
  21:43:26.289  自动恢复完成(165ms)
  随后 position 35.5s -> 41.7s 正常推进
2026-10-23 21:44:55 +08:00

69 lines
2.2 KiB
Dart

// lib/services/network_watcher.dart
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
/// 监听本机网络接口的地址变化。
///
/// 为什么不直接依赖 mpv 自己恢复:HTTP 流是长连接,Wi-Fi 切换 / 移动数据切换
/// 会换掉出口 IP,旧连接直接失效。mpv 不一定会重连,通常表现为
/// "进度不动、缓冲一直转"。所以在网络变化时由上层主动重开当前歌曲。
///
/// 实现方式:每 10s 枚举一次 IPv4 地址,签名变化即视为网络切换。
/// 相比平台 Channel 的方案,它不需要新增权限或插件,
/// 且能覆盖"同一个 Wi-Fi 换了 AP / 换了出口"这类 transport 类型不变的情况。
class NetworkWatcher {
static final NetworkWatcher _instance = NetworkWatcher._internal();
factory NetworkWatcher() => _instance;
NetworkWatcher._internal();
Timer? _timer;
String? _lastSignature;
bool _ticking = false;
/// 网络签名变化时回调(已在内部做过去抖)
void Function()? onChanged;
void start({Duration interval = const Duration(seconds: 10)}) {
if (_timer != null) return;
_timer = Timer.periodic(interval, (_) => _tick());
_tick();
debugPrint('🌐 [NetworkWatcher] 已启动(每 ${interval.inSeconds}s 检查一次)');
}
void stop() {
_timer?.cancel();
_timer = null;
_lastSignature = null;
debugPrint('🌐 [NetworkWatcher] 已停止');
}
Future<void> _tick() async {
if (_ticking) return;
_ticking = true;
try {
final interfaces = await NetworkInterface.list(
type: InternetAddressType.IPv4,
includeLoopback: false,
);
final signature = interfaces
.map((i) => '${i.name}:${i.addresses.map((a) => a.address).join(',')}')
.join('|');
final previous = _lastSignature;
_lastSignature = signature;
// 首次只记录基线,不触发
if (previous != null && previous != signature) {
debugPrint('🌐 [NetworkWatcher] 检测到网络变化');
onChanged?.call();
}
} catch (e) {
debugPrint('⚠️ [NetworkWatcher] 枚举网络接口失败: $e');
} finally {
_ticking = false;
}
}
}