wip: Sicherungs-Commit aller Änderungen seit April + Arbeitsstruktur (CLAUDE.md, ROADMAP.md, PROGRESS.md, Autopilot)
6 Wochen uncommittete Arbeit (Voice-Channels, LiveKit-Manager, Settings-Modal u.v.m.) als ein WIP-Commit gesichert, damit nichts verloren geht und der Pi den aktuellen Stand klonen kann. Thematische Aufarbeitung: siehe ROADMAP M0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
/// Zentrale Quelle für ICE-Server (STUN/TURN), die in ALLE WebRTC-Verbindungen
|
||||
/// injiziert werden (Matrix-1:1-Calls und LiveKit).
|
||||
///
|
||||
/// Hintergrund: Der Heimserver hängt hinter CGNAT (keine Portfreigabe möglich).
|
||||
/// Direkte Verbindungen brauchen deshalb ICE-Hole-Punching mit STUN auf beiden
|
||||
/// Seiten; für harte Fälle (symmetrische Mobilfunk-NAT) können später
|
||||
/// TURN-Server (z. B. Cloudflare TURN, 1 TB/Monat gratis) ergänzt werden —
|
||||
/// dafür muss nur die ice.json auf dem Pi aktualisiert werden, ohne App-Update.
|
||||
class IceServers {
|
||||
/// Wird vom Pi ausgeliefert und kann dort per Cron mit frischen
|
||||
/// TURN-Credentials befüllt werden (siehe /home/steggi/matrix/ice.json).
|
||||
static const _remoteUrl =
|
||||
'https://steggi-matrix.work/.well-known/pyramid/ice.json';
|
||||
|
||||
/// Fallback, falls die ice.json nicht erreichbar ist.
|
||||
static const List<Map<String, dynamic>> _fallback = [
|
||||
{
|
||||
'urls': [
|
||||
'stun:stun.l.google.com:19302',
|
||||
'stun:stun.cloudflare.com:3478',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
static List<Map<String, dynamic>>? _cached;
|
||||
static DateTime? _fetchedAt;
|
||||
|
||||
/// Liefert die ICE-Server als WebRTC-`iceServers`-Maps
|
||||
/// (`{'urls': [...], 'username': ..., 'credential': ...}`).
|
||||
/// Ergebnis wird 30 Minuten gecached; TURN-Credentials in der ice.json
|
||||
/// sollten deutlich länger gültig sein.
|
||||
static Future<List<Map<String, dynamic>>> get() async {
|
||||
final cached = _cached;
|
||||
if (cached != null &&
|
||||
_fetchedAt != null &&
|
||||
DateTime.now().difference(_fetchedAt!) <
|
||||
const Duration(minutes: 30)) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
final resp = await http
|
||||
.get(Uri.parse(_remoteUrl))
|
||||
.timeout(const Duration(seconds: 4));
|
||||
if (resp.statusCode == 200) {
|
||||
final body = jsonDecode(resp.body);
|
||||
final list = (body['ice_servers'] as List)
|
||||
.whereType<Map>()
|
||||
.map((e) => e.cast<String, dynamic>())
|
||||
.toList();
|
||||
if (list.isNotEmpty) {
|
||||
_cached = list;
|
||||
_fetchedAt = DateTime.now();
|
||||
return list;
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
// Offline/Endpoint fehlt — Fallback unten.
|
||||
}
|
||||
return _cached ?? _fallback;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user