feat(calls): Щас попробую легонечко с data channel побурмалдить
This commit is contained in:
@@ -84,6 +84,12 @@ class CallSession {
|
||||
static const double _speakLevelOn = 0.05;
|
||||
static const int _speakHoldTicks = 3;
|
||||
|
||||
RTCDataChannel? _probeChannel;
|
||||
bool _peerIsKomet = false;
|
||||
|
||||
static const String _probeQuestion = 'AreYouKomet?';
|
||||
static const String _probeAnswer = 'YesImKomet😎';
|
||||
|
||||
bool get localVideo => _localVideo;
|
||||
bool get localScreen => _localScreen;
|
||||
MediaStream? get localVideoStream => _localVideoStream;
|
||||
@@ -104,6 +110,7 @@ class CallSession {
|
||||
final _state = StreamController<CallSessionState>.broadcast();
|
||||
final _remoteStream = StreamController<MediaStream>.broadcast();
|
||||
final _info = StreamController<void>.broadcast();
|
||||
final _kometDetected = StreamController<void>.broadcast();
|
||||
|
||||
Stream<CallSessionState> get stateStream => _state.stream;
|
||||
Stream<MediaStream> get remoteStreamStream => _remoteStream.stream;
|
||||
@@ -111,6 +118,9 @@ class CallSession {
|
||||
|
||||
Stream<void> get infoUpdates => _info.stream;
|
||||
|
||||
Stream<void> get peerKometDetected => _kometDetected.stream;
|
||||
bool get peerIsKomet => _peerIsKomet;
|
||||
|
||||
bool get isMuted => _muted;
|
||||
bool get peerMuted => _peerMuted;
|
||||
bool get peerVideo => _peerVideo;
|
||||
@@ -482,6 +492,8 @@ class CallSession {
|
||||
init: RTCRtpTransceiverInit(direction: TransceiverDirection.RecvOnly),
|
||||
);
|
||||
|
||||
await _setupKometProbe(pc);
|
||||
|
||||
if (role == CallRole.caller) {
|
||||
_setState(CallSessionState.ringing);
|
||||
await _createAndSendOffer();
|
||||
@@ -499,6 +511,7 @@ class CallSession {
|
||||
});
|
||||
pc.onIceCandidate = _onLocalCandidate;
|
||||
pc.onTrack = (event) => unawaited(_onRemoteTrack(event));
|
||||
pc.onDataChannel = (channel) => _bindProbeChannel(channel, ask: false);
|
||||
pc.onIceConnectionState = (s) => logger.t('[call] ice $s');
|
||||
pc.onConnectionState = (s) {
|
||||
logger.t('[call] pc $s');
|
||||
@@ -534,10 +547,56 @@ class CallSession {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setupKometProbe(RTCPeerConnection pc) async {
|
||||
if (_topology == 'SERVER') return;
|
||||
try {
|
||||
final channel = await pc.createDataChannel(
|
||||
'komet',
|
||||
RTCDataChannelInit()..ordered = true,
|
||||
);
|
||||
_probeChannel = channel;
|
||||
_bindProbeChannel(channel, ask: true);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _bindProbeChannel(RTCDataChannel channel, {required bool ask}) {
|
||||
channel.onMessage = (message) => _onProbeMessage(channel, message);
|
||||
channel.onDataChannelState = (state) {
|
||||
if (ask && state == RTCDataChannelState.RTCDataChannelOpen) {
|
||||
_sendProbe(channel, _probeQuestion);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void _onProbeMessage(RTCDataChannel channel, RTCDataChannelMessage message) {
|
||||
if (message.isBinary) return;
|
||||
final text = message.text;
|
||||
if (text == _probeQuestion) {
|
||||
_sendProbe(channel, _probeAnswer);
|
||||
} else if (text == _probeAnswer) {
|
||||
_markPeerKomet();
|
||||
}
|
||||
}
|
||||
|
||||
void _sendProbe(RTCDataChannel channel, String text) {
|
||||
try {
|
||||
channel.send(RTCDataChannelMessage(text));
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _markPeerKomet() {
|
||||
if (_peerIsKomet) return;
|
||||
_peerIsKomet = true;
|
||||
logger.t('[call] peer is Komet');
|
||||
if (!_kometDetected.isClosed) _kometDetected.add(null);
|
||||
_notifyInfo();
|
||||
}
|
||||
|
||||
Future<void> _setupSfu() async {
|
||||
if (_pc != null) {
|
||||
await _pc!.close();
|
||||
_pc = null;
|
||||
_probeChannel = null;
|
||||
_remoteDescSet = false;
|
||||
_pendingCandidates.clear();
|
||||
for (final track in _localStream?.getTracks() ?? <MediaStreamTrack>[]) {
|
||||
@@ -1018,6 +1077,10 @@ class CallSession {
|
||||
|
||||
Future<void> _dispose() async {
|
||||
_levelTimer?.cancel();
|
||||
try {
|
||||
await _probeChannel?.close();
|
||||
} catch (_) {}
|
||||
_probeChannel = null;
|
||||
for (final track in _localStream?.getTracks() ?? <MediaStreamTrack>[]) {
|
||||
await track.stop();
|
||||
}
|
||||
@@ -1033,6 +1096,7 @@ class CallSession {
|
||||
if (!_state.isClosed) await _state.close();
|
||||
if (!_remoteStream.isClosed) await _remoteStream.close();
|
||||
if (!_info.isClosed) await _info.close();
|
||||
if (!_kometDetected.isClosed) await _kometDetected.close();
|
||||
}
|
||||
|
||||
void _applyConnectionInfo(Map<String, dynamic> msg, List iceServers) {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export 'file_log_output_stub.dart'
|
||||
if (dart.library.io) 'file_log_output_io.dart';
|
||||
@@ -1,61 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class FileLogOutput extends LogOutput {
|
||||
FileLogOutput._();
|
||||
|
||||
static final FileLogOutput instance = FileLogOutput._();
|
||||
|
||||
RandomAccessFile? _raf;
|
||||
String? _path;
|
||||
final List<String> _buffer = [];
|
||||
|
||||
static final RegExp _ansi = RegExp('\x1B\\[[0-9;]*m');
|
||||
|
||||
String? get path => _path;
|
||||
|
||||
Future<void> start() async {
|
||||
if (_raf != null) return;
|
||||
try {
|
||||
final dir = await getApplicationSupportDirectory();
|
||||
final file = File('${dir.path}/komet_calls.log');
|
||||
final raf = await file.open(mode: FileMode.write);
|
||||
_path = file.path;
|
||||
_raf = raf;
|
||||
_write('=== komet log ${file.path} ===');
|
||||
for (final line in _buffer) {
|
||||
_write(line);
|
||||
}
|
||||
_buffer.clear();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _write(String line) {
|
||||
try {
|
||||
_raf?.writeStringSync('$line\n');
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@override
|
||||
void output(OutputEvent event) {
|
||||
if (_raf == null) {
|
||||
for (final line in event.lines) {
|
||||
if (_buffer.length < 20000) _buffer.add(line.replaceAll(_ansi, ''));
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (final line in event.lines) {
|
||||
_write(line.replaceAll(_ansi, ''));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> destroy() async {
|
||||
try {
|
||||
await _raf?.close();
|
||||
} catch (_) {}
|
||||
_raf = null;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
class FileLogOutput extends LogOutput {
|
||||
FileLogOutput._();
|
||||
|
||||
static final FileLogOutput instance = FileLogOutput._();
|
||||
|
||||
String? get path => null;
|
||||
|
||||
Future<void> start() async {}
|
||||
|
||||
@override
|
||||
void output(OutputEvent event) {}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
import 'file_log_output.dart';
|
||||
|
||||
Level _minimumLogLevel() {
|
||||
const raw = String.fromEnvironment('KOMET_LOG_LEVEL', defaultValue: '');
|
||||
switch (raw.toLowerCase()) {
|
||||
@@ -43,13 +41,9 @@ final logger = Logger(
|
||||
filter: _logFilter(),
|
||||
level: _minimumLogLevel(),
|
||||
printer: KometLogPrinter(),
|
||||
output: MultiOutput([ConsoleOutput(), FileLogOutput.instance]),
|
||||
output: ConsoleOutput(),
|
||||
);
|
||||
|
||||
Future<void> startFileLogging() => FileLogOutput.instance.start();
|
||||
|
||||
String? get logFilePath => FileLogOutput.instance.path;
|
||||
|
||||
int _importanceSortKey(Level level) {
|
||||
final v = level.value;
|
||||
if (v >= 5999) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import '../../../core/calls/call_controller.dart';
|
||||
import '../../../core/calls/call_info.dart';
|
||||
import '../../../core/calls/call_session.dart';
|
||||
import '../../../core/utils/format.dart';
|
||||
import '../../widgets/custom_notification.dart';
|
||||
import '../../widgets/glossy_pill.dart';
|
||||
|
||||
const Color _kEndRed = Color(0xFFE5484D);
|
||||
@@ -50,6 +51,7 @@ class _CallScreenState extends State<CallScreen>
|
||||
CallSession? _session;
|
||||
StreamSubscription<CallSessionState>? _stateSub;
|
||||
StreamSubscription<void>? _infoSub;
|
||||
StreamSubscription<void>? _kometSub;
|
||||
StreamSubscription<MediaStream>? _remoteStreamSub;
|
||||
CallSessionState _state = CallSessionState.connecting;
|
||||
bool _incomingPending = false;
|
||||
@@ -212,12 +214,21 @@ class _CallScreenState extends State<CallScreen>
|
||||
setState(() {});
|
||||
});
|
||||
_remoteStreamSub = session.remoteStreamStream.listen(_attachStream);
|
||||
_kometSub = session.peerKometDetected.listen((_) => _showKometBadge());
|
||||
if (session.peerIsKomet) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _showKometBadge());
|
||||
}
|
||||
final existing = session.remoteStream;
|
||||
if (existing != null) _attachStream(existing);
|
||||
_resolveParticipants();
|
||||
_syncVideo();
|
||||
}
|
||||
|
||||
void _showKometBadge() {
|
||||
if (!mounted) return;
|
||||
showCustomNotification(context, 'Этот человек использует Komet! :3');
|
||||
}
|
||||
|
||||
void _resolveParticipants() {
|
||||
final session = _session;
|
||||
if (session == null) return;
|
||||
@@ -337,6 +348,7 @@ class _CallScreenState extends State<CallScreen>
|
||||
void dispose() {
|
||||
_stateSub?.cancel();
|
||||
_infoSub?.cancel();
|
||||
_kometSub?.cancel();
|
||||
_remoteStreamSub?.cancel();
|
||||
_dotsController.dispose();
|
||||
_videoController.dispose();
|
||||
|
||||
@@ -11,7 +11,6 @@ import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'backend/api.dart';
|
||||
import 'core/cache/info_cache.dart';
|
||||
import 'core/utils/logger.dart';
|
||||
import 'core/config/app_accent.dart';
|
||||
import 'core/config/app_amoled.dart';
|
||||
import 'core/config/app_bubble_behavior.dart';
|
||||
@@ -79,8 +78,6 @@ Future<Locale> _loadInitialLocale() async {
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await startFileLogging();
|
||||
logger.i('[log] file: ${logFilePath ?? '—'}');
|
||||
await AppDatabase.init();
|
||||
final activeAccountId = await TokenStorage.getActiveAccountId();
|
||||
if (activeAccountId != null) {
|
||||
|
||||
Reference in New Issue
Block a user