import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; /// Anruf-Vordergrunddienst auf Android (android/.../CallService.kt). /// /// * Solange ein Anruf läuft, hält der Dienst das Mikrofon auch im /// Hintergrund aktiv (Android schaltet es sonst stumm) und zeigt eine /// „Anruf läuft“-Benachrichtigung, über die man zurück in die App kommt. /// * Für eine Bildschirmübertragung MUSS ab Android 14 vorher ein Dienst vom /// Typ mediaProjection laufen – und der darf erst NACH der Zustimmung des /// Nutzers starten. [prepareScreenCapture] erledigt beides in der richtigen /// Reihenfolge; flutter_webrtc benutzt danach die gespeicherte Zustimmung. /// /// Beide Anrufwege (1:1 über Matrix, Sprachkanäle über LiveKit) melden sich /// mit eigenem Namen an; der Dienst endet erst, wenn keiner mehr läuft. /// Auf anderen Plattformen passiert nichts. class CallForeground { CallForeground._(); static const _channel = MethodChannel('chat.pyramid.pyramid/call'); static final _owners = {}; static final _screenOwners = {}; static String _title = 'Pyramid'; static bool get _android => !kIsWeb && Platform.isAndroid; /// true = Dienst läuft (bzw. wurde beendet) wie gewünscht. static Future _apply() async { if (!_android) return true; try { if (_owners.isEmpty) { await _channel.invokeMethod('stop'); return true; } final ok = await _channel.invokeMethod('start', { 'title': _title, 'screen': _screenOwners.isNotEmpty, }); return ok ?? false; } catch (e) { debugPrint('[CallForeground] $e'); return false; } } /// Anruf von [owner] läuft (z. B. 'voip', 'livekit'). static Future callStarted(String owner, {required String title}) async { _owners.add(owner); _title = title; await _apply(); } /// Anruf von [owner] ist beendet. static Future callEnded(String owner) async { final changed = _owners.remove(owner) | _screenOwners.remove(owner); if (changed) await _apply(); } /// Vor einer Bildschirmübertragung auf Android: Zustimmung einholen, dann /// den Dienst um „mediaProjection“ erweitern. false = Nutzer hat abgelehnt. static Future prepareScreenCapture(String owner) async { if (!_android) return true; final granted = await rtc.Helper.requestCapturePermission(); if (!granted) return false; _owners.add(owner); _screenOwners.add(owner); if (!await _apply()) { // Ohne laufenden mediaProjection-Dienst würde der Mitschnitt die App // abstürzen lassen – dann lieber gar nicht übertragen. _screenOwners.remove(owner); await _apply(); return false; } return true; } /// Übertragung von [owner] beendet – Dienst wieder ohne mediaProjection. static Future screenCaptureStopped(String owner) async { if (_screenOwners.remove(owner)) await _apply(); } }