自定义线路与线路优化
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
|
||||
import '../core/constants/storage_keys.dart';
|
||||
import '../core/utils/app_logger.dart';
|
||||
import 'host_mapping_proxy_service.dart';
|
||||
import 'server_service.dart';
|
||||
import 'storage_service.dart';
|
||||
|
||||
class CustomLineService {
|
||||
CustomLineService._();
|
||||
|
||||
static final CustomLineService instance = CustomLineService._();
|
||||
|
||||
HostMappingProxyEndpoint? get currentProxyEndpoint =>
|
||||
HostMappingProxyService.instance.endpoint;
|
||||
|
||||
Future<SelectedCustomLineNode?> getSelectedNode() async {
|
||||
final raw = await StorageService.instance.getString(
|
||||
StorageKeys.selectedCustomLineNode,
|
||||
);
|
||||
if (raw == null || raw.isEmpty) return null;
|
||||
|
||||
try {
|
||||
final decoded = jsonDecode(raw);
|
||||
if (decoded is! Map<String, dynamic>) return null;
|
||||
final node = SelectedCustomLineNode.fromJson(decoded);
|
||||
if (!node.canRewriteDownloadUrl) return null;
|
||||
return node;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setSelectedNode(SelectedCustomLineNode node) async {
|
||||
if (!node.canRewriteDownloadUrl) {
|
||||
throw ArgumentError('The selected custom line node has no usable IP.');
|
||||
}
|
||||
await StorageService.instance.setString(
|
||||
StorageKeys.selectedCustomLineNode,
|
||||
jsonEncode(node.toJson()),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> clearSelectedNode() async {
|
||||
await StorageService.instance.remove(StorageKeys.selectedCustomLineNode);
|
||||
await HostMappingProxyService.instance.stop();
|
||||
}
|
||||
|
||||
Future<CustomLineDownloadRoute> routeDownloadUrl(String url) async {
|
||||
if (!_canUseCustomLine()) {
|
||||
await HostMappingProxyService.instance.stop();
|
||||
return CustomLineDownloadRoute(url: url);
|
||||
}
|
||||
|
||||
final node = await getSelectedNode();
|
||||
if (node == null) return CustomLineDownloadRoute(url: url);
|
||||
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri == null || uri.host.isEmpty) {
|
||||
return CustomLineDownloadRoute(url: url);
|
||||
}
|
||||
|
||||
final endpoint = await activateSelectedNodeForHost(uri.host);
|
||||
if (endpoint == null) {
|
||||
return CustomLineDownloadRoute(url: url);
|
||||
}
|
||||
|
||||
return CustomLineDownloadRoute(
|
||||
url: url,
|
||||
nodeName: node.name,
|
||||
originalHost: uri.host,
|
||||
proxy: endpoint,
|
||||
);
|
||||
}
|
||||
|
||||
Future<HostMappingProxyEndpoint?> activateSelectedNodeForHost(String host) {
|
||||
return activateSelectedNodeForHosts([host]);
|
||||
}
|
||||
|
||||
Future<HostMappingProxyEndpoint?> activateSelectedNodeForHosts(
|
||||
Iterable<String> hosts,
|
||||
) async {
|
||||
if (!_canUseCustomLine()) {
|
||||
await HostMappingProxyService.instance.stop();
|
||||
return null;
|
||||
}
|
||||
|
||||
final node = await getSelectedNode();
|
||||
if (node == null) {
|
||||
await HostMappingProxyService.instance.stop();
|
||||
return null;
|
||||
}
|
||||
|
||||
final hostMap = <String, String>{};
|
||||
void addHost(String value) {
|
||||
final host = value.trim();
|
||||
if (host.isNotEmpty) hostMap[host] = node.ip;
|
||||
}
|
||||
|
||||
addHost(node.host);
|
||||
for (final host in hosts) {
|
||||
addHost(host);
|
||||
}
|
||||
|
||||
if (hostMap.isEmpty) {
|
||||
await HostMappingProxyService.instance.stop();
|
||||
return null;
|
||||
}
|
||||
return HostMappingProxyService.instance.configure(hostMap);
|
||||
}
|
||||
|
||||
Future<HostMappingProxyEndpoint?> activateSelectedNodeForUrl(String url) {
|
||||
final uri = Uri.tryParse(url);
|
||||
return activateSelectedNodeForHost(uri?.host ?? '');
|
||||
}
|
||||
|
||||
Future<HostMappingProxyEndpoint?> activateSelectedNodeForUrls(
|
||||
Iterable<String> urls,
|
||||
) {
|
||||
final hosts = urls
|
||||
.map((url) => Uri.tryParse(url)?.host ?? '')
|
||||
.where((host) => host.trim().isNotEmpty);
|
||||
return activateSelectedNodeForHosts(hosts);
|
||||
}
|
||||
|
||||
Future<void> configureMediaPlayerProxy(Player player, String url) async {
|
||||
final endpoint = await activateSelectedNodeForUrl(url);
|
||||
final platform = player.platform;
|
||||
if (platform is! NativePlayer) return;
|
||||
|
||||
try {
|
||||
await platform.setProperty('http-proxy', endpoint?.proxyUri ?? '');
|
||||
AppLogger.d('媒体播放器自定义线路代理已配置: ${endpoint?.proxyUri ?? 'DIRECT'}');
|
||||
} catch (e) {
|
||||
AppLogger.d('媒体播放器自定义线路代理配置失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
bool _canUseCustomLine() {
|
||||
final user = ServerService.instance.currentServer?.user;
|
||||
final groupName = user?.group?.name.trim().toLowerCase();
|
||||
return user != null &&
|
||||
groupName != null &&
|
||||
groupName.isNotEmpty &&
|
||||
groupName != 'user';
|
||||
}
|
||||
}
|
||||
|
||||
class SelectedCustomLineNode {
|
||||
final String id;
|
||||
final String name;
|
||||
final String ip;
|
||||
final String host;
|
||||
final String protocol;
|
||||
final int? port;
|
||||
|
||||
const SelectedCustomLineNode({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.ip,
|
||||
required this.host,
|
||||
this.protocol = '',
|
||||
this.port,
|
||||
});
|
||||
|
||||
bool get canRewriteDownloadUrl =>
|
||||
ip.trim().isNotEmpty && host.trim().isNotEmpty;
|
||||
|
||||
String routeScheme(String fallback) {
|
||||
final value = protocol.trim().toLowerCase();
|
||||
return value == 'http' || value == 'https' ? value : fallback;
|
||||
}
|
||||
|
||||
int? get routePort => port != null && port! > 0 ? port : null;
|
||||
|
||||
factory SelectedCustomLineNode.fromJson(Map<String, dynamic> json) {
|
||||
return SelectedCustomLineNode(
|
||||
id: json['id']?.toString() ?? '',
|
||||
name: json['name']?.toString() ?? '',
|
||||
ip: json['ip']?.toString() ?? '',
|
||||
host: json['host']?.toString() ?? '',
|
||||
protocol: json['protocol']?.toString() ?? '',
|
||||
port: _asNullableInt(json['port']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'ip': ip,
|
||||
'host': host,
|
||||
if (protocol.trim().isNotEmpty) 'protocol': protocol,
|
||||
if (routePort != null) 'port': routePort,
|
||||
};
|
||||
}
|
||||
|
||||
static int? _asNullableInt(Object? value) {
|
||||
if (value is int) return value;
|
||||
if (value is num) return value.round();
|
||||
return int.tryParse(value?.toString() ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
class CustomLineDownloadRoute {
|
||||
final String url;
|
||||
final Map<String, String> headers;
|
||||
final String? nodeName;
|
||||
final String? originalHost;
|
||||
final HostMappingProxyEndpoint? proxy;
|
||||
|
||||
const CustomLineDownloadRoute({
|
||||
required this.url,
|
||||
this.headers = const {},
|
||||
this.nodeName,
|
||||
this.originalHost,
|
||||
this.proxy,
|
||||
});
|
||||
|
||||
bool get changed => proxy != null || headers.isNotEmpty;
|
||||
}
|
||||
Reference in New Issue
Block a user