refactor: M2-Call-Pilot – Module call_signaling + voice_channel mit Fassaden (Entscheidung 2)
- CallSignalingService/callSignalingProvider und VoiceChannelService/ voiceChannelProvider als einzige oeffentliche Schnittstellen - voip_manager.dart / livekit_call_manager.dart in ihre Module verschoben, Logik unangetastet (nur Klassen-Koepfe, @override, Imports, Provider-Namen) - Alle Konsumenten entkoppelt; Kompositions-Punkt matrix_client.dart als dokumentierte Ausnahme. analyze sauber, 29 Tests gruen, Windows-Start ok. Call-Klick-/Geraetetest steht aus (PROGRESS.md)
This commit is contained in:
+56
@@ -1333,3 +1333,59 @@
|
|||||||
2026-07-06 12:29 [Edit] C:\Users\nordm\pyramid - Kopie\docs\SQLCIPHER_MIGRATION.md
|
2026-07-06 12:29 [Edit] C:\Users\nordm\pyramid - Kopie\docs\SQLCIPHER_MIGRATION.md
|
||||||
2026-07-06 12:30 [Edit] C:\Users\nordm\pyramid - Kopie\ROADMAP.md
|
2026-07-06 12:30 [Edit] C:\Users\nordm\pyramid - Kopie\ROADMAP.md
|
||||||
2026-07-06 12:31 [Edit] C:\Users\nordm\pyramid - Kopie\PROGRESS.md
|
2026-07-06 12:31 [Edit] C:\Users\nordm\pyramid - Kopie\PROGRESS.md
|
||||||
|
2026-07-06 12:33 [Write] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\call_signaling_service.dart
|
||||||
|
2026-07-06 12:33 [Write] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\voice_channel_service.dart
|
||||||
|
2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart
|
||||||
|
2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart
|
||||||
|
2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart
|
||||||
|
2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart
|
||||||
|
2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart
|
||||||
|
2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart
|
||||||
|
2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart
|
||||||
|
2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart
|
||||||
|
2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart
|
||||||
|
2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart
|
||||||
|
2026-07-06 12:36 [Edit] C:\Users\nordm\pyramid - Kopie\lib\core\app_state.dart
|
||||||
|
2026-07-06 12:36 [Edit] C:\Users\nordm\pyramid - Kopie\lib\core\app_state.dart
|
||||||
|
2026-07-06 12:36 [Edit] C:\Users\nordm\pyramid - Kopie\lib\core\matrix_client.dart
|
||||||
|
2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\layout\app_shell.dart
|
||||||
|
2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\layout\app_shell.dart
|
||||||
|
2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\rooms\rooms_panel.dart
|
||||||
|
2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart
|
||||||
|
2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart
|
||||||
|
2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart
|
||||||
|
2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart
|
||||||
|
2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart
|
||||||
|
2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart
|
||||||
|
2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart
|
||||||
|
2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart
|
||||||
|
2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart
|
||||||
|
2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart
|
||||||
|
2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart
|
||||||
|
2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart
|
||||||
|
2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\chat\view\chat_header.dart
|
||||||
|
2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\chat\view\chat_header.dart
|
||||||
|
2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\chat\chat_view.dart
|
||||||
|
2026-07-06 12:39 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings_modal.dart
|
||||||
|
2026-07-06 12:39 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart
|
||||||
|
2026-07-06 12:39 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart
|
||||||
|
2026-07-06 12:39 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\chat\chat_view.dart
|
||||||
|
2026-07-06 12:39 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\call_signaling_service.dart
|
||||||
|
2026-07-06 12:41 [Edit] C:\Users\nordm\pyramid - Kopie\docs\M2_MODULE_SCHNITT.md
|
||||||
|
2026-07-06 12:41 [Edit] C:\Users\nordm\pyramid - Kopie\ROADMAP.md
|
||||||
|
2026-07-06 12:42 [Edit] C:\Users\nordm\pyramid - Kopie\PROGRESS.md
|
||||||
|
|||||||
+55
@@ -13,6 +13,61 @@ Schritt (und beim Abbruch mitten im Schritt den Zwischenstand). Format:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-06 – M2-Call-Pilot: Module call_signaling + voice_channel mit Fassaden umgesetzt (Entscheidung 2)
|
||||||
|
|
||||||
|
**Erledigt:** Das erste M2-Umbau-Modul nach `docs/M2_MODULE_SCHNITT.md`
|
||||||
|
(Bernds Entscheidung 2: Call-Pilot zuerst, nicht settings_modal):
|
||||||
|
|
||||||
|
- **Zwei neue Module mit schmaler öffentlicher Schnittstelle:**
|
||||||
|
`lib/features/call_signaling/` (`CallSignalingService` +
|
||||||
|
`callSignalingProvider`; Implementierung `voip_manager.dart` hierher
|
||||||
|
verschoben) und `lib/features/voice_channel/` (`VoiceChannelService` +
|
||||||
|
`voiceChannelProvider`; Implementierung `livekit_call_manager.dart`
|
||||||
|
hierher verschoben). Die zwei Call-Mechanismen bleiben wie im Doc
|
||||||
|
begründet GETRENNT (Matrix-1:1-VoIP vs. LiveKit-SFU-Kanäle).
|
||||||
|
- **Alle Konsumenten entkoppelt:** app_shell, Call-UI (`voice_channel.dart`,
|
||||||
|
`mini_call_widget.dart`), Rooms-Panel (Join/Leave, Call-Dock),
|
||||||
|
Chat-Header (Call-Start), Settings (`_VoiceSection` ist jetzt
|
||||||
|
ConsumerStatefulWidget und nutzt die Provider statt der Singletons) –
|
||||||
|
niemand importiert mehr die Implementierungsdateien, repo-weit verifiziert.
|
||||||
|
Einzige dokumentierte Ausnahme: `matrix_client.dart` als Kompositions-
|
||||||
|
Punkt, der `PyramidVoipManager(client)` konstruiert. Die alten Provider
|
||||||
|
`callStateProvider`/`voipStateProvider` in app_state.dart sind ersatzlos
|
||||||
|
durch die Modul-Provider ersetzt (reine Umbenennung + Typ = Interface).
|
||||||
|
- **Bewusste Abweichungen vom Schnitt-Entwurf** (Verhaltensgleichheit vor
|
||||||
|
Schönheit, heiliger Call-Pfad; im Doc-Status-Kopf dokumentiert):
|
||||||
|
ChangeNotifier statt Streams, Methodennamen wie im Bestand
|
||||||
|
(`startCall`/`hangUp` statt `join`/`leave`), `CallSession` und
|
||||||
|
LiveKit-`Room` bleiben in den Schnittstellen sichtbar (UI rendert direkt
|
||||||
|
Renderer/Tracks – für die WebRTC-Renderer sieht das Doc das selbst vor).
|
||||||
|
Kein einziger Handler-/Logik-Block wurde verändert, nur Klassen-Köpfe
|
||||||
|
(`extends` + `@override`), Imports, Provider-Namen und Typ-Annotationen.
|
||||||
|
- **Geprüft:** `flutter analyze` sauber (nur bekannter onUpdate-Hinweis),
|
||||||
|
alle 29 Tests grün, `flutter run -d windows`: App startet, Login/Sync/E2EE
|
||||||
|
laufen, sauberer Quit. (Commit-Hash siehe git log dieses Eintrags.)
|
||||||
|
|
||||||
|
**UNGETESTET (interaktiv/Gerät – deshalb ROADMAP-Punkt „Call-Schicht
|
||||||
|
entwirren" nur annotiert, nicht abgehakt):** echter Voice-Channel-Join per
|
||||||
|
Klick auf Windows und echter 1:1-Call zwischen zwei Geräten. Der Umbau ist
|
||||||
|
bewusst rein mechanisch (Fassade davor, Logik unangetastet), aber die
|
||||||
|
CLAUDE.md-Regel „nach Refactoring Kernflows prüfen" ist für die Call-Flows
|
||||||
|
erst nach einem Klick-/Gerätetest voll erfüllt – steht im PC_TESTPLAN
|
||||||
|
(Abschnitt 8) bzw. braucht Bernd/Uta.
|
||||||
|
|
||||||
|
**Offen/Nächster Schritt:** Härtetest nach `docs/PC_TESTPLAN.md` (so weit
|
||||||
|
ohne Klick-Interaktion möglich); die inneren M2-Aufräumarbeiten der
|
||||||
|
Call-Schicht (UI-State aus den Managern, `call_ui`-Modul, storage-Fassade
|
||||||
|
für die voice_*-Prefs) folgen als eigene Schritte.
|
||||||
|
|
||||||
|
**Stolperfallen:** (1) Bei part-Dateien (settings, rooms_panel, chat_view)
|
||||||
|
hängen die Imports an der EINEN Owner-Datei – wer eine part-Datei umstellt,
|
||||||
|
muss die Imports beim Owner ändern und findet Verwender nur per Repo-grep
|
||||||
|
auf den Provider-Namen. (2) Ein `ChangeNotifierProvider<Interface>` braucht
|
||||||
|
ein Interface, das ChangeNotifier ERWEITERT (abstract class X extends
|
||||||
|
ChangeNotifier), sonst passt der Typparameter nicht.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-07-06 – SQLCipher-Migration UMGESETZT + auf Windows mit echter DB praxisgetestet (M1, Bernds Prio 1)
|
## 2026-07-06 – SQLCipher-Migration UMGESETZT + auf Windows mit echter DB praxisgetestet (M1, Bernds Prio 1)
|
||||||
|
|
||||||
**Erledigt:** Der M1-Punkt „Sichere Speicherung von Access-Token & Krypto-DB"
|
**Erledigt:** Der M1-Punkt „Sichere Speicherung von Access-Token & Krypto-DB"
|
||||||
|
|||||||
+11
@@ -173,6 +173,17 @@ lassen, ohne dass andere Module angefasst werden müssen.
|
|||||||
Bernds Go für den Call-Pilot (Frage in PROGRESS.md).
|
Bernds Go für den Call-Pilot (Frage in PROGRESS.md).
|
||||||
- [ ] Call-Schicht entwirren: EINE klare Zuständigkeit pro Klasse (Signalisierung /
|
- [ ] Call-Schicht entwirren: EINE klare Zuständigkeit pro Klasse (Signalisierung /
|
||||||
LiveKit-Verbindung / UI-State sauber getrennt)
|
LiveKit-Verbindung / UI-State sauber getrennt)
|
||||||
|
**Teilschritt erledigt (2026-07-06, M2-Pilot laut Entscheidung 2):**
|
||||||
|
Module `call_signaling` und `voice_channel` mit Fassaden-Interfaces +
|
||||||
|
Providern existieren (`lib/features/call_signaling/`,
|
||||||
|
`lib/features/voice_channel/`), alle Konsumenten gehen nur noch über
|
||||||
|
`callSignalingProvider`/`voiceChannelProvider` – kein UI-Import der
|
||||||
|
Implementierungen mehr (Details/Abweichungen: Status-Kopf in
|
||||||
|
`docs/M2_MODULE_SCHNITT.md`). App startet, 29 Tests grün, Sync/E2EE
|
||||||
|
laufen. **UNGETESTET:** echter 1:1-Call und Voice-Channel-Join per
|
||||||
|
Klick (braucht Gerät bzw. interaktiven Test) – deshalb nicht abgehakt;
|
||||||
|
außerdem stehen die inneren Aufräumarbeiten (UI-State aus den Managern
|
||||||
|
heraus, `call_ui`-Modul) noch aus.
|
||||||
- [ ] Einheitliches State-Management (Riverpod konsequent, keine Misch-Patterns)
|
- [ ] Einheitliches State-Management (Riverpod konsequent, keine Misch-Patterns)
|
||||||
- [x] Toten Code & Duplikate entfernen, Ordnerstruktur vereinheitlichen
|
- [x] Toten Code & Duplikate entfernen, Ordnerstruktur vereinheitlichen
|
||||||
(PROGRESS.md 2026-07-03: `message_bubble.dart` tot entfernt, doppelte
|
(PROGRESS.md 2026-07-03: `message_bubble.dart` tot entfernt, doppelte
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# M2 – Modul-Schnitt (Entwurf)
|
# M2 – Modul-Schnitt (Entwurf)
|
||||||
|
|
||||||
|
> **STATUS 2026-07-06: Pilot (Schritt 1) UMGESETZT.** Die Module
|
||||||
|
> `call_signaling` (`lib/features/call_signaling/`: `CallSignalingService`
|
||||||
|
> + `callSignalingProvider` + Implementierung `voip_manager.dart`) und
|
||||||
|
> `voice_channel` (`lib/features/voice_channel/`: `VoiceChannelService` +
|
||||||
|
> `voiceChannelProvider` + Implementierung `livekit_call_manager.dart`)
|
||||||
|
> existieren; ALLE Konsumenten (Shell, Call-UI, Rooms-Panel, Chat-Header,
|
||||||
|
> Settings) gehen nur noch über die Provider/Interfaces, kein direkter
|
||||||
|
> Implementierungs-Import mehr (einzige dokumentierte Ausnahme:
|
||||||
|
> Kompositions-Punkt `matrix_client.dart`). Bewusste Abweichungen vom
|
||||||
|
> Entwurf unten (Verhaltensgleichheit vor Schönheit, heiliger Call-Pfad):
|
||||||
|
> ChangeNotifier statt Streams, Methodennamen wie im Bestand
|
||||||
|
> (`startCall`/`hangUp` statt `join`/`leave`), `CallSession`/LiveKit-`Room`
|
||||||
|
> bleiben in den Schnittstellen sichtbar, weil die UI direkt rendert.
|
||||||
|
> Echter Call-/Voice-Channel-Test steht aus (Gerät bzw. Klick-Test) –
|
||||||
|
> siehe PROGRESS.md 2026-07-06. Schritte 2–5 unten sind weiter offen.
|
||||||
|
|
||||||
Stand: 2026-07-03. Reine Schnittstellen-Planung, noch kein Umbau. Grundlage:
|
Stand: 2026-07-03. Reine Schnittstellen-Planung, noch kein Umbau. Grundlage:
|
||||||
Ist-Analyse in `PROGRESS.md` (Eintrag „M2: Ist-Analyse der Architektur
|
Ist-Analyse in `PROGRESS.md` (Eintrag „M2: Ist-Analyse der Architektur
|
||||||
geschrieben"). Leitprinzip (Bernd, `CLAUDE.md`): jedes Modul muss sich in
|
geschrieben"). Leitprinzip (Bernd, `CLAUDE.md`): jedes Modul muss sich in
|
||||||
|
|||||||
+3
-10
@@ -3,8 +3,6 @@ import 'package:matrix/matrix.dart';
|
|||||||
import 'package:pyramid/core/settings_prefs.dart';
|
import 'package:pyramid/core/settings_prefs.dart';
|
||||||
import 'package:pyramid/core/theme.dart';
|
import 'package:pyramid/core/theme.dart';
|
||||||
import 'package:pyramid/core/matrix_client.dart';
|
import 'package:pyramid/core/matrix_client.dart';
|
||||||
import 'package:pyramid/core/livekit_call_manager.dart';
|
|
||||||
import 'package:pyramid/core/voip_manager.dart';
|
|
||||||
import 'package:pyramid/features/rooms/rooms_provider.dart';
|
import 'package:pyramid/features/rooms/rooms_provider.dart';
|
||||||
|
|
||||||
// ─── Theme state ──────────────────────────────────────────────────────────────
|
// ─── Theme state ──────────────────────────────────────────────────────────────
|
||||||
@@ -64,14 +62,9 @@ enum ModalKind { none, settings, search }
|
|||||||
final activeModalProvider = StateProvider<ModalKind>((ref) => ModalKind.none);
|
final activeModalProvider = StateProvider<ModalKind>((ref) => ModalKind.none);
|
||||||
|
|
||||||
// ─── Call state ───────────────────────────────────────────────────────────────
|
// ─── Call state ───────────────────────────────────────────────────────────────
|
||||||
|
// Die Call-Provider leben seit dem M2-Call-Pilot in ihren Modulen:
|
||||||
final callStateProvider = ChangeNotifierProvider<LiveKitCallManager>((ref) {
|
// `callSignalingProvider` (features/call_signaling/call_signaling_service.dart)
|
||||||
return LiveKitCallManager.instance;
|
// und `voiceChannelProvider` (features/voice_channel/voice_channel_service.dart).
|
||||||
});
|
|
||||||
|
|
||||||
final voipStateProvider = ChangeNotifierProvider<PyramidVoipManager>((ref) {
|
|
||||||
return PyramidVoipManager.instance;
|
|
||||||
});
|
|
||||||
|
|
||||||
final incomingCallProvider = StateProvider<CallSession?>((ref) => null);
|
final incomingCallProvider = StateProvider<CallSession?>((ref) => null);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import 'package:matrix/matrix.dart';
|
|||||||
import 'package:pyramid/core/app_database.dart';
|
import 'package:pyramid/core/app_database.dart';
|
||||||
import 'package:pyramid/core/auth_log.dart';
|
import 'package:pyramid/core/auth_log.dart';
|
||||||
import 'package:pyramid/core/soft_logout_guard.dart';
|
import 'package:pyramid/core/soft_logout_guard.dart';
|
||||||
import 'package:pyramid/core/voip_manager.dart';
|
// Kompositions-Punkt: Einzige Stelle außerhalb des call_signaling-Moduls,
|
||||||
|
// die die Implementierung (statt der Fassade) kennen darf – sie erzeugt sie.
|
||||||
|
import 'package:pyramid/features/call_signaling/voip_manager.dart';
|
||||||
|
|
||||||
final matrixClientProvider = FutureProvider<Client>((ref) async {
|
final matrixClientProvider = FutureProvider<Client>((ref) async {
|
||||||
// Factory-Wahl, Verschlüsselung (SQLCipher), Migration und PRAGMAs stecken
|
// Factory-Wahl, Verschlüsselung (SQLCipher), Migration und PRAGMAs stecken
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pyramid/core/app_state.dart';
|
import 'package:pyramid/core/app_state.dart';
|
||||||
import 'package:pyramid/core/theme.dart';
|
import 'package:pyramid/core/theme.dart';
|
||||||
import 'package:pyramid/core/livekit_call_manager.dart';
|
import 'package:pyramid/features/call_signaling/call_signaling_service.dart';
|
||||||
import 'package:pyramid/core/voip_manager.dart';
|
import 'package:pyramid/features/voice_channel/voice_channel_service.dart';
|
||||||
import 'package:pyramid/widgets/screen_share_picker.dart';
|
import 'package:pyramid/widgets/screen_share_picker.dart';
|
||||||
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
|
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
|
||||||
import 'package:livekit_client/livekit_client.dart';
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
|
|
||||||
class MiniCallWidget extends ConsumerStatefulWidget {
|
class MiniCallWidget extends ConsumerStatefulWidget {
|
||||||
final dynamic call; // Can be LiveKitCallManager or PyramidVoipManager
|
final dynamic call; // CallSignalingService (1:1) oder VoiceChannelService
|
||||||
const MiniCallWidget({super.key, required this.call});
|
const MiniCallWidget({super.key, required this.call});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -45,7 +45,7 @@ class _MiniCallWidgetState extends ConsumerState<MiniCallWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final pt = PyramidTheme.of(context);
|
final pt = PyramidTheme.of(context);
|
||||||
final isVoip = widget.call is PyramidVoipManager;
|
final isVoip = widget.call is CallSignalingService;
|
||||||
final call = widget.call;
|
final call = widget.call;
|
||||||
|
|
||||||
final String displayName = isVoip
|
final String displayName = isVoip
|
||||||
@@ -95,7 +95,7 @@ class _MiniCallWidgetState extends ConsumerState<MiniCallWidget> {
|
|||||||
isStreaming ? call.localRenderer : call.remoteRenderer,
|
isStreaming ? call.localRenderer : call.remoteRenderer,
|
||||||
objectFit: rtc.RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
|
objectFit: rtc.RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
|
||||||
)
|
)
|
||||||
: _LiveKitPreview(manager: call as LiveKitCallManager),
|
: _LiveKitPreview(manager: call as VoiceChannelService),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Top bar of preview
|
// Top bar of preview
|
||||||
@@ -259,9 +259,9 @@ class _MiniCallWidgetState extends ConsumerState<MiniCallWidget> {
|
|||||||
pt: pt,
|
pt: pt,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (isVoip) {
|
if (isVoip) {
|
||||||
(call as PyramidVoipManager).toggleScreenSharing(context);
|
(call as CallSignalingService).toggleScreenSharing(context);
|
||||||
} else {
|
} else {
|
||||||
final lk = call as LiveKitCallManager;
|
final lk = call as VoiceChannelService;
|
||||||
if (lk.isScreenSharing) {
|
if (lk.isScreenSharing) {
|
||||||
await lk.stopScreenShare();
|
await lk.stopScreenShare();
|
||||||
} else {
|
} else {
|
||||||
@@ -351,7 +351,7 @@ class _ControlBtnState extends State<_ControlBtn> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LiveKitPreview extends StatelessWidget {
|
class _LiveKitPreview extends StatelessWidget {
|
||||||
final LiveKitCallManager manager;
|
final VoiceChannelService manager;
|
||||||
const _LiveKitPreview({required this.manager});
|
const _LiveKitPreview({required this.manager});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:pyramid/core/app_state.dart';
|
import 'package:pyramid/core/app_state.dart';
|
||||||
import 'package:pyramid/widgets/screen_share_picker.dart';
|
import 'package:pyramid/widgets/screen_share_picker.dart';
|
||||||
import 'package:pyramid/core/theme.dart';
|
import 'package:pyramid/core/theme.dart';
|
||||||
import 'package:pyramid/core/livekit_call_manager.dart';
|
import 'package:pyramid/features/call_signaling/call_signaling_service.dart';
|
||||||
import 'package:pyramid/core/voip_manager.dart';
|
import 'package:pyramid/features/voice_channel/voice_channel_service.dart';
|
||||||
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
|
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
|
||||||
import 'package:pyramid/widgets/hover_region.dart';
|
import 'package:pyramid/widgets/hover_region.dart';
|
||||||
import 'package:pyramid/widgets/pyramid_loader.dart';
|
import 'package:pyramid/widgets/pyramid_loader.dart';
|
||||||
@@ -45,8 +45,8 @@ class VoiceChannelView extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final pt = PyramidTheme.of(context);
|
final pt = PyramidTheme.of(context);
|
||||||
final call = ref.watch(callStateProvider);
|
final call = ref.watch(voiceChannelProvider);
|
||||||
final voip = ref.watch(voipStateProvider);
|
final voip = ref.watch(callSignalingProvider);
|
||||||
|
|
||||||
if (voip.currentCall != null && voip.currentCall!.state != CallState.kEnded) {
|
if (voip.currentCall != null && voip.currentCall!.state != CallState.kEnded) {
|
||||||
return _UnifiedCallView(
|
return _UnifiedCallView(
|
||||||
@@ -101,7 +101,7 @@ class VoiceChannelView extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<_ParticipantData> _getLiveKitParticipants(LiveKitCallManager call) {
|
List<_ParticipantData> _getLiveKitParticipants(VoiceChannelService call) {
|
||||||
final room = call.room;
|
final room = call.room;
|
||||||
if (room == null) return [];
|
if (room == null) return [];
|
||||||
final all = <Participant>[
|
final all = <Participant>[
|
||||||
@@ -142,7 +142,7 @@ class VoiceChannelView extends ConsumerWidget {
|
|||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<_ParticipantData> _getVoipParticipants(PyramidVoipManager voip) {
|
List<_ParticipantData> _getVoipParticipants(CallSignalingService voip) {
|
||||||
final participants = <_ParticipantData>[];
|
final participants = <_ParticipantData>[];
|
||||||
|
|
||||||
double ar(rtc.RTCVideoRenderer r) {
|
double ar(rtc.RTCVideoRenderer r) {
|
||||||
@@ -997,7 +997,7 @@ class _FullscreenVideoPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleLiveKitScreenShare(BuildContext context, LiveKitCallManager call) async {
|
void _handleLiveKitScreenShare(BuildContext context, VoiceChannelService call) async {
|
||||||
if (call.isScreenSharing) {
|
if (call.isScreenSharing) {
|
||||||
await call.stopScreenShare();
|
await call.stopScreenShare();
|
||||||
} else {
|
} else {
|
||||||
@@ -1006,7 +1006,7 @@ void _handleLiveKitScreenShare(BuildContext context, LiveKitCallManager call) as
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showLiveKitQualityMenu(BuildContext context, LiveKitCallManager call, Offset pos) {
|
void _showLiveKitQualityMenu(BuildContext context, VoiceChannelService call, Offset pos) {
|
||||||
final pt = PyramidTheme.of(context);
|
final pt = PyramidTheme.of(context);
|
||||||
showMenu(
|
showMenu(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -1025,7 +1025,7 @@ void _showLiveKitQualityMenu(BuildContext context, LiveKitCallManager call, Offs
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showVoipQualityMenu(BuildContext context, PyramidVoipManager voip, Offset pos) {
|
void _showVoipQualityMenu(BuildContext context, CallSignalingService voip, Offset pos) {
|
||||||
final pt = PyramidTheme.of(context);
|
final pt = PyramidTheme.of(context);
|
||||||
showMenu(
|
showMenu(
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
import 'package:pyramid/features/call_signaling/voip_manager.dart';
|
||||||
|
|
||||||
|
/// Öffentliche Schnittstelle des Moduls `call_signaling` – natives
|
||||||
|
/// Matrix-1:1-VoIP (`m.call.*`-Events, direkte WebRTC-Peer-Connection,
|
||||||
|
/// kein SFU). Schnitt nach `docs/M2_MODULE_SCHNITT.md`.
|
||||||
|
///
|
||||||
|
/// Andere Module (UI, Settings, Shell) konsumieren AUSSCHLIESSLICH diese
|
||||||
|
/// Schnittstelle über [callSignalingProvider] – nie `voip_manager.dart`
|
||||||
|
/// direkt. Einzige Ausnahme ist der Kompositions-Punkt in
|
||||||
|
/// `matrix_client.dart`, der die Implementierung konstruiert.
|
||||||
|
///
|
||||||
|
/// Bewusste Abweichungen vom Entwurf im Doc (Verhaltensgleichheit vor
|
||||||
|
/// Schönheit, heiliger Call-Pfad):
|
||||||
|
/// * ChangeNotifier statt Streams – die gesamte UI hängt heute an
|
||||||
|
/// `ref.watch(...)` mit notifyListeners; ein Stream-Umbau wäre eine
|
||||||
|
/// Verhaltensänderung ohne Not.
|
||||||
|
/// * [currentCall] bleibt als matrix-SDK-`CallSession` sichtbar (die UI
|
||||||
|
/// braucht state/room/answer/reject), ebenso die WebRTC-Renderer –
|
||||||
|
/// das Doc sieht Letzteres ausdrücklich vor.
|
||||||
|
abstract class CallSignalingService extends ChangeNotifier {
|
||||||
|
/// Der laufende 1:1-Call (null = keiner). UI liest state/room und
|
||||||
|
/// beantwortet eingehende Calls über [safeAction] + answer()/reject().
|
||||||
|
CallSession? get currentCall;
|
||||||
|
|
||||||
|
rtc.RTCVideoRenderer get localRenderer;
|
||||||
|
rtc.RTCVideoRenderer get remoteRenderer;
|
||||||
|
|
||||||
|
bool get isMicMuted;
|
||||||
|
bool get isCameraMuted;
|
||||||
|
bool get isScreensharing;
|
||||||
|
bool get isRemoteScreensharing;
|
||||||
|
bool get isDeafened;
|
||||||
|
|
||||||
|
/// Sende-Qualität ('sd' | 'hd' | 'fhd' | '4k').
|
||||||
|
String get currentQualityKey;
|
||||||
|
|
||||||
|
Future<void> startCall(String roomId, {bool video});
|
||||||
|
Future<void> hangup();
|
||||||
|
Future<void> toggleMic();
|
||||||
|
Future<void> toggleCamera();
|
||||||
|
Future<void> toggleDeafen();
|
||||||
|
|
||||||
|
/// Auf Windows öffnet ein [context] den Quellen-Picker fürs Screensharing.
|
||||||
|
Future<void> toggleScreenSharing([BuildContext? context]);
|
||||||
|
|
||||||
|
Future<void> changeQuality(String key);
|
||||||
|
|
||||||
|
/// Wendet die in den Einstellungen gewählte Ausgabelautstärke auf den
|
||||||
|
/// Remote-Stream an. Ohne [value] wird der Pref-Wert gelesen.
|
||||||
|
Future<void> applyOutputVolume([double? value]);
|
||||||
|
|
||||||
|
/// Führt eine Call-Aktion (answer/reject/…) fehlertolerant aus und stößt
|
||||||
|
/// danach die interne Zustands-/Renderer-Aktualisierung an.
|
||||||
|
Future<void> safeAction(Future<dynamic> Function() action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Einziger Zugriffsweg der UI auf das 1:1-VoIP-Modul. Die Instanz wird beim
|
||||||
|
/// Client-Aufbau in `matrix_client.dart` erzeugt (existiert also, sobald die
|
||||||
|
/// eingeloggte Shell steht).
|
||||||
|
final callSignalingProvider =
|
||||||
|
ChangeNotifierProvider<CallSignalingService>((ref) {
|
||||||
|
return PyramidVoipManager.instance;
|
||||||
|
});
|
||||||
@@ -6,17 +6,25 @@ import 'package:matrix/matrix.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:webrtc_interface/webrtc_interface.dart';
|
import 'package:webrtc_interface/webrtc_interface.dart';
|
||||||
import 'package:pyramid/core/ice_servers.dart';
|
import 'package:pyramid/core/ice_servers.dart';
|
||||||
|
import 'package:pyramid/features/call_signaling/call_signaling_service.dart';
|
||||||
import 'package:pyramid/widgets/screen_share_picker.dart';
|
import 'package:pyramid/widgets/screen_share_picker.dart';
|
||||||
|
|
||||||
class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate {
|
/// Implementierung von [CallSignalingService] (Modul `call_signaling`).
|
||||||
|
/// Wird ausschließlich vom Kompositions-Punkt (`matrix_client.dart`)
|
||||||
|
/// konstruiert; alle anderen gehen über `callSignalingProvider`.
|
||||||
|
class PyramidVoipManager extends CallSignalingService
|
||||||
|
implements WebRTCDelegate {
|
||||||
static PyramidVoipManager? _instance;
|
static PyramidVoipManager? _instance;
|
||||||
static PyramidVoipManager get instance => _instance!;
|
static PyramidVoipManager get instance => _instance!;
|
||||||
|
|
||||||
final Client client;
|
final Client client;
|
||||||
late final VoIP voip;
|
late final VoIP voip;
|
||||||
|
@override
|
||||||
CallSession? currentCall;
|
CallSession? currentCall;
|
||||||
|
|
||||||
|
@override
|
||||||
final rtc.RTCVideoRenderer localRenderer = rtc.RTCVideoRenderer();
|
final rtc.RTCVideoRenderer localRenderer = rtc.RTCVideoRenderer();
|
||||||
|
@override
|
||||||
final rtc.RTCVideoRenderer remoteRenderer = rtc.RTCVideoRenderer();
|
final rtc.RTCVideoRenderer remoteRenderer = rtc.RTCVideoRenderer();
|
||||||
bool _renderersInitialized = false;
|
bool _renderersInitialized = false;
|
||||||
|
|
||||||
@@ -29,11 +37,17 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate {
|
|||||||
String? _selectedSourceId;
|
String? _selectedSourceId;
|
||||||
String _currentQualityKey = 'hd';
|
String _currentQualityKey = 'hd';
|
||||||
|
|
||||||
|
@override
|
||||||
bool get isMicMuted => _isMicMuted;
|
bool get isMicMuted => _isMicMuted;
|
||||||
|
@override
|
||||||
bool get isCameraMuted => _isCameraMuted;
|
bool get isCameraMuted => _isCameraMuted;
|
||||||
|
@override
|
||||||
bool get isScreensharing => _isScreensharing;
|
bool get isScreensharing => _isScreensharing;
|
||||||
|
@override
|
||||||
bool get isRemoteScreensharing => _isRemoteScreensharing;
|
bool get isRemoteScreensharing => _isRemoteScreensharing;
|
||||||
|
@override
|
||||||
bool get isDeafened => _isDeafened;
|
bool get isDeafened => _isDeafened;
|
||||||
|
@override
|
||||||
String get currentQualityKey => _currentQualityKey;
|
String get currentQualityKey => _currentQualityKey;
|
||||||
|
|
||||||
PyramidVoipManager(this.client) {
|
PyramidVoipManager(this.client) {
|
||||||
@@ -253,6 +267,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate {
|
|||||||
|
|
||||||
/// Wendet die in den Einstellungen gewählte Ausgabelautstärke auf den
|
/// Wendet die in den Einstellungen gewählte Ausgabelautstärke auf den
|
||||||
/// Remote-Audio-Stream an. Ohne [value] wird der Pref-Wert gelesen.
|
/// Remote-Audio-Stream an. Ohne [value] wird der Pref-Wert gelesen.
|
||||||
|
@override
|
||||||
Future<void> applyOutputVolume([double? value]) async {
|
Future<void> applyOutputVolume([double? value]) async {
|
||||||
var v = value;
|
var v = value;
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
@@ -295,6 +310,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate {
|
|||||||
@override
|
@override
|
||||||
Future<void> registerListeners(CallSession session) async {}
|
Future<void> registerListeners(CallSession session) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> safeAction(Future<dynamic> Function() action) async {
|
Future<void> safeAction(Future<dynamic> Function() action) async {
|
||||||
try {
|
try {
|
||||||
await action();
|
await action();
|
||||||
@@ -306,6 +322,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate {
|
|||||||
_updateRenderers();
|
_updateRenderers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> toggleMic() async {
|
Future<void> toggleMic() async {
|
||||||
if (currentCall == null) return;
|
if (currentCall == null) return;
|
||||||
_isMicMuted = !_isMicMuted;
|
_isMicMuted = !_isMicMuted;
|
||||||
@@ -318,6 +335,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> toggleCamera() async {
|
Future<void> toggleCamera() async {
|
||||||
if (currentCall == null) return;
|
if (currentCall == null) return;
|
||||||
_isCameraMuted = !_isCameraMuted;
|
_isCameraMuted = !_isCameraMuted;
|
||||||
@@ -337,6 +355,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate {
|
|||||||
await _updateRenderers();
|
await _updateRenderers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> toggleScreenSharing([BuildContext? context]) async {
|
Future<void> toggleScreenSharing([BuildContext? context]) async {
|
||||||
if (currentCall == null) return;
|
if (currentCall == null) return;
|
||||||
final call = currentCall!;
|
final call = currentCall!;
|
||||||
@@ -359,11 +378,13 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> toggleDeafen() async {
|
Future<void> toggleDeafen() async {
|
||||||
_isDeafened = !_isDeafened;
|
_isDeafened = !_isDeafened;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> hangup() async {
|
Future<void> hangup() async {
|
||||||
if (currentCall == null) return;
|
if (currentCall == null) return;
|
||||||
await safeAction(() async {
|
await safeAction(() async {
|
||||||
@@ -372,6 +393,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> startCall(String roomId, {bool video = false}) async {
|
Future<void> startCall(String roomId, {bool video = false}) async {
|
||||||
final room = client.getRoomById(roomId);
|
final room = client.getRoomById(roomId);
|
||||||
if (room == null) return;
|
if (room == null) return;
|
||||||
@@ -394,6 +416,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> changeQuality(String key) async {
|
Future<void> changeQuality(String key) async {
|
||||||
_currentQualityKey = key;
|
_currentQualityKey = key;
|
||||||
if (currentCall == null) {
|
if (currentCall == null) {
|
||||||
@@ -22,7 +22,8 @@ import 'package:pyramid/widgets/hover_region.dart';
|
|||||||
import 'package:pyramid/widgets/mxc_image.dart';
|
import 'package:pyramid/widgets/mxc_image.dart';
|
||||||
|
|
||||||
import 'package:pyramid/core/notification_service.dart';
|
import 'package:pyramid/core/notification_service.dart';
|
||||||
import 'package:pyramid/core/voip_manager.dart';
|
import 'package:pyramid/features/call_signaling/call_signaling_service.dart';
|
||||||
|
import 'package:pyramid/features/voice_channel/voice_channel_service.dart';
|
||||||
|
|
||||||
|
|
||||||
part 'view/chat_view_core.dart';
|
part 'view/chat_view_core.dart';
|
||||||
|
|||||||
@@ -167,13 +167,13 @@ class _ChatHeader extends ConsumerWidget {
|
|||||||
if (room.isDirectChat) {
|
if (room.isDirectChat) {
|
||||||
// Use native Matrix VoIP for 1:1 DMs
|
// Use native Matrix VoIP for 1:1 DMs
|
||||||
ref.read(viewModeProvider.notifier).state = ViewMode.voice;
|
ref.read(viewModeProvider.notifier).state = ViewMode.voice;
|
||||||
await PyramidVoipManager.instance.startCall(room.id, video: cam);
|
await ref.read(callSignalingProvider).startCall(room.id, video: cam);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use LiveKit for group voice rooms
|
// Use LiveKit for group voice rooms
|
||||||
ref.read(viewModeProvider.notifier).state = ViewMode.voice;
|
ref.read(viewModeProvider.notifier).state = ViewMode.voice;
|
||||||
await ref.read(callStateProvider).startCall(
|
await ref.read(voiceChannelProvider).startCall(
|
||||||
roomName: roomId,
|
roomName: roomId,
|
||||||
roomDisplayName: room.getLocalizedDisplayname(),
|
roomDisplayName: room.getLocalizedDisplayname(),
|
||||||
identity: 'user_${DateTime.now().millisecondsSinceEpoch}',
|
identity: 'user_${DateTime.now().millisecondsSinceEpoch}',
|
||||||
@@ -576,7 +576,7 @@ class _DmExpandingHeaderState extends ConsumerState<_DmExpandingHeader> {
|
|||||||
|
|
||||||
void _startCall({required bool cam}) async {
|
void _startCall({required bool cam}) async {
|
||||||
ref.read(viewModeProvider.notifier).state = ViewMode.voice;
|
ref.read(viewModeProvider.notifier).state = ViewMode.voice;
|
||||||
await PyramidVoipManager.instance.startCall(widget.room.id, video: cam);
|
await ref.read(callSignalingProvider).startCall(widget.room.id, video: cam);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ class _RoomsPanelState extends ConsumerState<RoomsPanel> {
|
|||||||
final pt = PyramidTheme.of(context);
|
final pt = PyramidTheme.of(context);
|
||||||
final roomsAsync = ref.watch(roomListProvider);
|
final roomsAsync = ref.watch(roomListProvider);
|
||||||
final activeSpaceId = ref.watch(activeSpaceIdProvider);
|
final activeSpaceId = ref.watch(activeSpaceIdProvider);
|
||||||
final call = ref.watch(callStateProvider);
|
final call = ref.watch(voiceChannelProvider);
|
||||||
final voip = ref.watch(voipStateProvider);
|
final voip = ref.watch(callSignalingProvider);
|
||||||
|
|
||||||
final isRealSpace = activeSpaceId != null &&
|
final isRealSpace = activeSpaceId != null &&
|
||||||
activeSpaceId != 'dms' &&
|
activeSpaceId != 'dms' &&
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class _SpaceRoomsListState extends ConsumerState<_SpaceRoomsList> {
|
|||||||
if (jr.isVoice) {
|
if (jr.isVoice) {
|
||||||
// LiveKit only needs the room id + a display name, so connect even if the
|
// LiveKit only needs the room id + a display name, so connect even if the
|
||||||
// matrix room object hasn't synced locally yet.
|
// matrix room object hasn't synced locally yet.
|
||||||
final call = ref.read(callStateProvider);
|
final call = ref.read(voiceChannelProvider);
|
||||||
if (call.isActive) await call.hangUp();
|
if (call.isActive) await call.hangUp();
|
||||||
ref.read(activeVoiceRoomIdProvider.notifier).state = jr.roomId;
|
ref.read(activeVoiceRoomIdProvider.notifier).state = jr.roomId;
|
||||||
await call.startCall(
|
await call.startCall(
|
||||||
@@ -199,7 +199,7 @@ class _SpaceRoomsListState extends ConsumerState<_SpaceRoomsList> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void joinVoiceChannel(Room room) async {
|
void joinVoiceChannel(Room room) async {
|
||||||
final call = ref.read(callStateProvider);
|
final call = ref.read(voiceChannelProvider);
|
||||||
final activeVoice = ref.read(activeVoiceRoomIdProvider);
|
final activeVoice = ref.read(activeVoiceRoomIdProvider);
|
||||||
// Toggle: leave if already in this channel
|
// Toggle: leave if already in this channel
|
||||||
if (call.isVoiceChannel && activeVoice == room.id) {
|
if (call.isVoiceChannel && activeVoice == room.id) {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import 'package:pyramid/core/matrix_client.dart';
|
|||||||
import 'package:pyramid/core/theme.dart';
|
import 'package:pyramid/core/theme.dart';
|
||||||
import 'package:pyramid/features/rooms/rooms_provider.dart';
|
import 'package:pyramid/features/rooms/rooms_provider.dart';
|
||||||
import 'package:pyramid/features/call/mini_call_widget.dart';
|
import 'package:pyramid/features/call/mini_call_widget.dart';
|
||||||
|
import 'package:pyramid/features/call_signaling/call_signaling_service.dart';
|
||||||
|
import 'package:pyramid/features/voice_channel/voice_channel_service.dart';
|
||||||
import 'package:pyramid/features/rooms/user_panel.dart';
|
import 'package:pyramid/features/rooms/user_panel.dart';
|
||||||
import 'package:pyramid/features/spaces/space_admin_dialog.dart';
|
import 'package:pyramid/features/spaces/space_admin_dialog.dart';
|
||||||
import 'package:pyramid/widgets/create_join_dialog.dart';
|
import 'package:pyramid/widgets/create_join_dialog.dart';
|
||||||
|
|||||||
+30
-1
@@ -6,11 +6,14 @@ import 'package:livekit_client/livekit_client.dart';
|
|||||||
import 'package:matrix/matrix.dart' hide Room;
|
import 'package:matrix/matrix.dart' hide Room;
|
||||||
import 'package:pyramid/core/ice_servers.dart';
|
import 'package:pyramid/core/ice_servers.dart';
|
||||||
import 'package:pyramid/core/livekit_token.dart';
|
import 'package:pyramid/core/livekit_token.dart';
|
||||||
|
import 'package:pyramid/features/voice_channel/voice_channel_service.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
const _kVoicePresenceType = 'io.pyramid.voice.presence';
|
const _kVoicePresenceType = 'io.pyramid.voice.presence';
|
||||||
|
|
||||||
class LiveKitCallManager with ChangeNotifier {
|
/// Implementierung von [VoiceChannelService] (Modul `voice_channel`).
|
||||||
|
/// Andere Module gehen ausschließlich über `voiceChannelProvider`.
|
||||||
|
class LiveKitCallManager extends VoiceChannelService {
|
||||||
static final LiveKitCallManager instance = LiveKitCallManager._();
|
static final LiveKitCallManager instance = LiveKitCallManager._();
|
||||||
LiveKitCallManager._();
|
LiveKitCallManager._();
|
||||||
|
|
||||||
@@ -32,17 +35,24 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
Room? _room;
|
Room? _room;
|
||||||
EventsListener<RoomEvent>? _roomEventListener;
|
EventsListener<RoomEvent>? _roomEventListener;
|
||||||
String _displayName = '';
|
String _displayName = '';
|
||||||
|
@override
|
||||||
bool isConnecting = false;
|
bool isConnecting = false;
|
||||||
|
@override
|
||||||
String? error;
|
String? error;
|
||||||
|
@override
|
||||||
bool isMuted = false;
|
bool isMuted = false;
|
||||||
|
@override
|
||||||
bool isCameraOff = false;
|
bool isCameraOff = false;
|
||||||
|
@override
|
||||||
bool isScreenSharing = false;
|
bool isScreenSharing = false;
|
||||||
String _localQualityKey = 'hd';
|
String _localQualityKey = 'hd';
|
||||||
String _subscribeQualityKey = 'auto'; // 'auto' | 'low' | 'medium' | 'high'
|
String _subscribeQualityKey = 'auto'; // 'auto' | 'low' | 'medium' | 'high'
|
||||||
String? _currentScreenShareSourceId;
|
String? _currentScreenShareSourceId;
|
||||||
LocalVideoTrack? _screenShareTrack;
|
LocalVideoTrack? _screenShareTrack;
|
||||||
|
@override
|
||||||
bool isDeafened = false;
|
bool isDeafened = false;
|
||||||
bool _mutedBeforeDeafen = false;
|
bool _mutedBeforeDeafen = false;
|
||||||
|
@override
|
||||||
bool isVoiceChannel = false;
|
bool isVoiceChannel = false;
|
||||||
String? _matrixRoomId;
|
String? _matrixRoomId;
|
||||||
Client? _matrixClient;
|
Client? _matrixClient;
|
||||||
@@ -50,12 +60,18 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
|
|
||||||
Timer? _statsTimer;
|
Timer? _statsTimer;
|
||||||
|
|
||||||
|
@override
|
||||||
bool get isActive => _room != null || isConnecting;
|
bool get isActive => _room != null || isConnecting;
|
||||||
|
@override
|
||||||
Room? get room => _room;
|
Room? get room => _room;
|
||||||
|
@override
|
||||||
String? get currentRoomId => _matrixRoomId;
|
String? get currentRoomId => _matrixRoomId;
|
||||||
|
@override
|
||||||
String get displayName => _displayName;
|
String get displayName => _displayName;
|
||||||
bool get noiseSuppression => false;
|
bool get noiseSuppression => false;
|
||||||
|
@override
|
||||||
String get currentQualityKey => _localQualityKey;
|
String get currentQualityKey => _localQualityKey;
|
||||||
|
@override
|
||||||
String get subscribeQualityKey => _subscribeQualityKey;
|
String get subscribeQualityKey => _subscribeQualityKey;
|
||||||
|
|
||||||
VideoParameters get _currentQuality =>
|
VideoParameters get _currentQuality =>
|
||||||
@@ -122,6 +138,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> startCall({
|
Future<void> startCall({
|
||||||
required String roomName,
|
required String roomName,
|
||||||
required String roomDisplayName,
|
required String roomDisplayName,
|
||||||
@@ -307,6 +324,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
double _outputVolume = 1.0;
|
double _outputVolume = 1.0;
|
||||||
|
|
||||||
/// Live-Anwendung des Lautstärke-Sliders auf alle Remote-Audio-Tracks.
|
/// Live-Anwendung des Lautstärke-Sliders auf alle Remote-Audio-Tracks.
|
||||||
|
@override
|
||||||
Future<void> setOutputVolume(double v) async {
|
Future<void> setOutputVolume(double v) async {
|
||||||
_outputVolume = v;
|
_outputVolume = v;
|
||||||
final room = _room;
|
final room = _room;
|
||||||
@@ -323,6 +341,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> setSubscribeQuality(String key) async {
|
Future<void> setSubscribeQuality(String key) async {
|
||||||
debugPrint('[LiveKit] Viewer changing subscribe quality to: $key');
|
debugPrint('[LiveKit] Viewer changing subscribe quality to: $key');
|
||||||
_subscribeQualityKey = key;
|
_subscribeQualityKey = key;
|
||||||
@@ -340,12 +359,14 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> toggleMute() async {
|
Future<void> toggleMute() async {
|
||||||
isMuted = !isMuted;
|
isMuted = !isMuted;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
await _room?.localParticipant?.setMicrophoneEnabled(!isMuted);
|
await _room?.localParticipant?.setMicrophoneEnabled(!isMuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> toggleDeafen() async {
|
Future<void> toggleDeafen() async {
|
||||||
if (!isDeafened) {
|
if (!isDeafened) {
|
||||||
_mutedBeforeDeafen = isMuted;
|
_mutedBeforeDeafen = isMuted;
|
||||||
@@ -377,6 +398,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> toggleCamera() async {
|
Future<void> toggleCamera() async {
|
||||||
if (isCameraOff) {
|
if (isCameraOff) {
|
||||||
try {
|
try {
|
||||||
@@ -399,6 +421,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> toggleScreenShare() async {
|
Future<void> toggleScreenShare() async {
|
||||||
if (isScreenSharing) {
|
if (isScreenSharing) {
|
||||||
await stopScreenShare();
|
await stopScreenShare();
|
||||||
@@ -426,6 +449,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
const VideoEncoding(maxBitrate: 1_500_000, maxFramerate: 15);
|
const VideoEncoding(maxBitrate: 1_500_000, maxFramerate: 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> startScreenShare(String sourceId) async {
|
Future<void> startScreenShare(String sourceId) async {
|
||||||
if (isScreenSharing) return;
|
if (isScreenSharing) return;
|
||||||
try {
|
try {
|
||||||
@@ -480,6 +504,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> stopScreenShare() async {
|
Future<void> stopScreenShare() async {
|
||||||
final local = _room?.localParticipant;
|
final local = _room?.localParticipant;
|
||||||
if (local != null) {
|
if (local != null) {
|
||||||
@@ -501,6 +526,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> hangUp() async {
|
Future<void> hangUp() async {
|
||||||
_stopStatsLogging();
|
_stopStatsLogging();
|
||||||
_presenceHeartbeat?.cancel();
|
_presenceHeartbeat?.cancel();
|
||||||
@@ -529,6 +555,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> changeQuality(String qualityKey) async {
|
Future<void> changeQuality(String qualityKey) async {
|
||||||
debugPrint('[LiveKit] Streamer changing publish quality to: $qualityKey');
|
debugPrint('[LiveKit] Streamer changing publish quality to: $qualityKey');
|
||||||
_localQualityKey = qualityKey;
|
_localQualityKey = qualityKey;
|
||||||
@@ -562,6 +589,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> changeAudioInput(String deviceId) async {
|
Future<void> changeAudioInput(String deviceId) async {
|
||||||
final participant = _room?.localParticipant;
|
final participant = _room?.localParticipant;
|
||||||
if (participant == null) return;
|
if (participant == null) return;
|
||||||
@@ -575,6 +603,7 @@ class LiveKitCallManager with ChangeNotifier {
|
|||||||
} catch (e) { debugPrint('[Audio] error: $e'); }
|
} catch (e) { debugPrint('[Audio] error: $e'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
void setRemoteVolume(double volume) {
|
void setRemoteVolume(double volume) {
|
||||||
if (_room == null) return;
|
if (_room == null) return;
|
||||||
final mute = volume < 0.05;
|
final mute = volume < 0.05;
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:livekit_client/livekit_client.dart' as lk;
|
||||||
|
import 'package:matrix/matrix.dart' hide Room;
|
||||||
|
import 'package:pyramid/features/voice_channel/livekit_call_manager.dart';
|
||||||
|
|
||||||
|
/// Öffentliche Schnittstelle des Moduls `voice_channel` – LiveKit-SFU-basierte,
|
||||||
|
/// dauerhafte Voice-Channels (Discord-artig: Presence über Matrix-State-Events,
|
||||||
|
/// Screensharing, Mehrpersonen). Schnitt nach `docs/M2_MODULE_SCHNITT.md`;
|
||||||
|
/// bewusst GETRENNT vom 1:1-Modul `call_signaling`, siehe dort.
|
||||||
|
///
|
||||||
|
/// Andere Module konsumieren ausschließlich diese Schnittstelle über
|
||||||
|
/// [voiceChannelProvider] – nie `livekit_call_manager.dart` direkt.
|
||||||
|
///
|
||||||
|
/// Bewusste Abweichungen vom Entwurf im Doc (Verhaltensgleichheit vor
|
||||||
|
/// Schönheit): ChangeNotifier statt `stateStream`, Methodennamen wie im
|
||||||
|
/// Bestand (`startCall`/`hangUp` statt `join`/`leave`), und [room] bleibt
|
||||||
|
/// als LiveKit-Typ sichtbar, weil die Teilnehmer-Kacheln der UI direkt
|
||||||
|
/// LiveKit-Tracks rendern (analog zu den WebRTC-Renderern im Signaling).
|
||||||
|
abstract class VoiceChannelService extends ChangeNotifier {
|
||||||
|
bool get isActive;
|
||||||
|
bool get isConnecting;
|
||||||
|
String? get error;
|
||||||
|
bool get isMuted;
|
||||||
|
bool get isCameraOff;
|
||||||
|
bool get isScreenSharing;
|
||||||
|
bool get isDeafened;
|
||||||
|
bool get isVoiceChannel;
|
||||||
|
|
||||||
|
/// Der verbundene LiveKit-Raum (null = nicht verbunden).
|
||||||
|
lk.Room? get room;
|
||||||
|
|
||||||
|
/// Matrix-Raum-ID des aktiven Voice-Channels.
|
||||||
|
String? get currentRoomId;
|
||||||
|
String get displayName;
|
||||||
|
|
||||||
|
/// Sende-Qualität ('sd' | 'hd' | 'fhd' | '4k').
|
||||||
|
String get currentQualityKey;
|
||||||
|
|
||||||
|
/// Empfangs-Qualität ('auto' | 'low' | 'medium' | 'high').
|
||||||
|
String get subscribeQualityKey;
|
||||||
|
|
||||||
|
Future<void> startCall({
|
||||||
|
required String roomName,
|
||||||
|
required String roomDisplayName,
|
||||||
|
required String identity,
|
||||||
|
bool audioOnly,
|
||||||
|
bool voiceChannel,
|
||||||
|
Client? matrixClient,
|
||||||
|
String? matrixRoomId,
|
||||||
|
});
|
||||||
|
Future<void> hangUp();
|
||||||
|
|
||||||
|
Future<void> toggleMute();
|
||||||
|
Future<void> toggleDeafen();
|
||||||
|
Future<void> toggleCamera();
|
||||||
|
Future<void> toggleScreenShare();
|
||||||
|
Future<void> startScreenShare(String sourceId);
|
||||||
|
Future<void> stopScreenShare();
|
||||||
|
|
||||||
|
Future<void> changeQuality(String qualityKey);
|
||||||
|
Future<void> setSubscribeQuality(String key);
|
||||||
|
|
||||||
|
/// Live-Anwendung des Lautstärke-Sliders auf alle Remote-Audio-Tracks.
|
||||||
|
Future<void> setOutputVolume(double v);
|
||||||
|
Future<void> changeAudioInput(String deviceId);
|
||||||
|
void setRemoteVolume(double volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Einziger Zugriffsweg der UI auf das Voice-Channel-Modul.
|
||||||
|
final voiceChannelProvider =
|
||||||
|
ChangeNotifierProvider<VoiceChannelService>((ref) {
|
||||||
|
return LiveKitCallManager.instance;
|
||||||
|
});
|
||||||
@@ -9,9 +9,10 @@ import 'package:pretty_qr_code/pretty_qr_code.dart';
|
|||||||
import 'package:pyramid/core/app_state.dart';
|
import 'package:pyramid/core/app_state.dart';
|
||||||
import 'package:pyramid/core/matrix_client.dart';
|
import 'package:pyramid/core/matrix_client.dart';
|
||||||
import 'package:pyramid/core/theme.dart';
|
import 'package:pyramid/core/theme.dart';
|
||||||
import 'package:pyramid/core/voip_manager.dart';
|
|
||||||
import 'package:pyramid/features/auth/bootstrap_dialog.dart';
|
import 'package:pyramid/features/auth/bootstrap_dialog.dart';
|
||||||
import 'package:pyramid/features/call/voice_channel.dart';
|
import 'package:pyramid/features/call/voice_channel.dart';
|
||||||
|
import 'package:pyramid/features/call_signaling/call_signaling_service.dart';
|
||||||
|
import 'package:pyramid/features/voice_channel/voice_channel_service.dart';
|
||||||
import 'package:pyramid/features/chat/chat_view.dart';
|
import 'package:pyramid/features/chat/chat_view.dart';
|
||||||
import 'package:pyramid/features/members/members_panel.dart';
|
import 'package:pyramid/features/members/members_panel.dart';
|
||||||
import 'package:pyramid/features/rooms/rooms_panel.dart';
|
import 'package:pyramid/features/rooms/rooms_panel.dart';
|
||||||
@@ -160,7 +161,7 @@ class _AppShellState extends ConsumerState<AppShell>
|
|||||||
// notifyListeners (u.a. Render-Timer im Sekundentakt); ohne Flankenerkennung
|
// notifyListeners (u.a. Render-Timer im Sekundentakt); ohne Flankenerkennung
|
||||||
// würde der Nutzer permanent zurück in die Call-UI gezwungen und könnte
|
// würde der Nutzer permanent zurück in die Call-UI gezwungen und könnte
|
||||||
// sie nie minimieren.
|
// sie nie minimieren.
|
||||||
ref.listen(voipStateProvider, (prev, next) {
|
ref.listen(callSignalingProvider, (prev, next) {
|
||||||
final wasConnected = _wasVoipConnected;
|
final wasConnected = _wasVoipConnected;
|
||||||
final isConnected = next.currentCall != null &&
|
final isConnected = next.currentCall != null &&
|
||||||
next.currentCall!.state == CallState.kConnected;
|
next.currentCall!.state == CallState.kConnected;
|
||||||
@@ -172,7 +173,7 @@ class _AppShellState extends ConsumerState<AppShell>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ref.listen(callStateProvider, (prev, next) {
|
ref.listen(voiceChannelProvider, (prev, next) {
|
||||||
// Only auto-switch to voice view for video/audio calls, NOT voice channels
|
// Only auto-switch to voice view for video/audio calls, NOT voice channels
|
||||||
// (voice channels should stay in background with a mini bar)
|
// (voice channels should stay in background with a mini bar)
|
||||||
final wasActive = _wasLiveKitActive;
|
final wasActive = _wasLiveKitActive;
|
||||||
@@ -200,7 +201,7 @@ class _AppShellState extends ConsumerState<AppShell>
|
|||||||
final activeModal = ref.watch(activeModalProvider);
|
final activeModal = ref.watch(activeModalProvider);
|
||||||
final membersOpen = ref.watch(membersPanelOpenProvider);
|
final membersOpen = ref.watch(membersPanelOpenProvider);
|
||||||
final innerPt = PyramidTheme.of(context);
|
final innerPt = PyramidTheme.of(context);
|
||||||
final voip = ref.watch(voipStateProvider);
|
final voip = ref.watch(callSignalingProvider);
|
||||||
|
|
||||||
final size = MediaQuery.sizeOf(context);
|
final size = MediaQuery.sizeOf(context);
|
||||||
final isMobile = size.width < 900 || size.height < 500;
|
final isMobile = size.width < 900 || size.height < 500;
|
||||||
@@ -332,7 +333,7 @@ class _MobileShell extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final voip = ref.watch(voipStateProvider);
|
final voip = ref.watch(callSignalingProvider);
|
||||||
|
|
||||||
Widget body;
|
Widget body;
|
||||||
if (viewMode == ViewMode.voice) {
|
if (viewMode == ViewMode.voice) {
|
||||||
@@ -456,7 +457,7 @@ class _NotificationBlockedBanner extends ConsumerWidget {
|
|||||||
|
|
||||||
class _IncomingCallOverlay extends ConsumerWidget {
|
class _IncomingCallOverlay extends ConsumerWidget {
|
||||||
final CallSession call;
|
final CallSession call;
|
||||||
final PyramidVoipManager voip;
|
final CallSignalingService voip;
|
||||||
final PyramidTheme pt;
|
final PyramidTheme pt;
|
||||||
|
|
||||||
const _IncomingCallOverlay({
|
const _IncomingCallOverlay({
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
part of '../settings_modal.dart';
|
part of '../settings_modal.dart';
|
||||||
|
|
||||||
class _VoiceSection extends StatefulWidget {
|
class _VoiceSection extends ConsumerStatefulWidget {
|
||||||
final PyramidTheme pt;
|
final PyramidTheme pt;
|
||||||
const _VoiceSection({required this.pt});
|
const _VoiceSection({required this.pt});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_VoiceSection> createState() => _VoiceSectionState();
|
ConsumerState<_VoiceSection> createState() => _VoiceSectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VoiceSectionState extends State<_VoiceSection> {
|
class _VoiceSectionState extends ConsumerState<_VoiceSection> {
|
||||||
List<MediaDeviceInfo> _audioInputs = [];
|
List<MediaDeviceInfo> _audioInputs = [];
|
||||||
List<MediaDeviceInfo> _audioOutputs = [];
|
List<MediaDeviceInfo> _audioOutputs = [];
|
||||||
List<MediaDeviceInfo> _videoInputs = [];
|
List<MediaDeviceInfo> _videoInputs = [];
|
||||||
@@ -89,10 +89,10 @@ class _VoiceSectionState extends State<_VoiceSection> {
|
|||||||
setState(() => _outputVolume = v);
|
setState(() => _outputVolume = v);
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setDouble(_kOutputVolume, v);
|
await prefs.setDouble(_kOutputVolume, v);
|
||||||
// Live auf laufende Calls anwenden.
|
// Live auf laufende Calls anwenden (über die Modul-Fassaden).
|
||||||
LiveKitCallManager.instance.setOutputVolume(v).catchError((_) {});
|
ref.read(voiceChannelProvider).setOutputVolume(v).catchError((_) {});
|
||||||
try {
|
try {
|
||||||
PyramidVoipManager.instance.applyOutputVolume(v).catchError((_) {});
|
ref.read(callSignalingProvider).applyOutputVolume(v).catchError((_) {});
|
||||||
} catch (_) {} // Instanz existiert evtl. noch nicht
|
} catch (_) {} // Instanz existiert evtl. noch nicht
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ import 'package:pyramid/core/app_state.dart';
|
|||||||
import 'package:pyramid/core/auth_log.dart';
|
import 'package:pyramid/core/auth_log.dart';
|
||||||
import 'package:pyramid/core/e2ee_diagnostics.dart';
|
import 'package:pyramid/core/e2ee_diagnostics.dart';
|
||||||
import 'package:pyramid/features/auth/bootstrap_dialog.dart' show isKeyBackupMissing;
|
import 'package:pyramid/features/auth/bootstrap_dialog.dart' show isKeyBackupMissing;
|
||||||
import 'package:pyramid/core/livekit_call_manager.dart';
|
import 'package:pyramid/features/call_signaling/call_signaling_service.dart';
|
||||||
|
import 'package:pyramid/features/voice_channel/voice_channel_service.dart';
|
||||||
import 'package:pyramid/core/matrix_client.dart';
|
import 'package:pyramid/core/matrix_client.dart';
|
||||||
import 'package:pyramid/core/voip_manager.dart';
|
|
||||||
import 'package:pyramid/core/settings_prefs.dart';
|
import 'package:pyramid/core/settings_prefs.dart';
|
||||||
import 'package:pyramid/core/theme.dart';
|
import 'package:pyramid/core/theme.dart';
|
||||||
import 'package:pyramid/core/update_checker.dart';
|
import 'package:pyramid/core/update_checker.dart';
|
||||||
|
|||||||
Reference in New Issue
Block a user