ой в пизду короче

This commit is contained in:
Jganenokk
2026-08-08 17:34:46 +07:00
parent 824beacabf
commit 474923328b
12 changed files with 462 additions and 83 deletions
+2 -20
View File
@@ -160,27 +160,9 @@ class CallController {
return session;
}
Future<({CallSession session, String? joinLink})> createGroupCall({
bool isVideo = false,
}) async {
Future<CreatedCall> createConference() async {
if (_active != null) throw StateError('уже идёт звонок');
final out = await _calls!.startGroupCall(isVideo: isVideo);
final joinLink = await _calls!.createJoinLink(out.conversationId);
final config = Ws2Config.fromEndpoint(
out.endpoint,
userId: out.callsUserId,
device: _api?.callsDevice,
osVersion: _api?.callsOsVersion,
);
final session = CallSession(
ws2Config: config,
role: CallRole.caller,
isGroup: true,
);
_bind(session);
await session.start();
CallBridge.instance.notifyAccepted();
return (session: session, joinLink: joinLink);
return _calls!.createConference();
}
Future<CallLinkPreview?> previewCallLink(String url) =>
+16
View File
@@ -1,10 +1,26 @@
class CallLink {
static const String base = 'https://max.ru/joincall/';
static final RegExp _pattern = RegExp(
r'^https?://(?:[^/\s]+\.)?max\.ru/joincall/([A-Za-z0-9_-]+)',
caseSensitive: false,
);
static final RegExp _rawPattern = RegExp(
r'^(?:joincall/)?([A-Za-z0-9_-]+)$',
caseSensitive: false,
);
static bool isCallLink(String url) => token(url) != null;
static String? token(String url) => _pattern.firstMatch(url.trim())?.group(1);
static String? normalizeToken(String raw) {
final value = raw.trim();
return token(value) ?? _rawPattern.firstMatch(value)?.group(1);
}
static String url(String token) => '$base$token';
static String path(String token) => 'joincall/$token';
}
+53 -1
View File
@@ -179,7 +179,8 @@ class CallSession {
Stream<int> get participantStreamUpdates => _participantStreamUpdates.stream;
MediaStream? streamOf(int participantId) => _participantStreams[participantId];
MediaStream? streamOf(int participantId) =>
_participantStreams[participantId];
int get participantCount => _participants.length;
@@ -251,6 +252,8 @@ class CallSession {
signaling.done.then((_) => _onSignalingLost());
await signaling.connect();
logger.i('[call] signaling connected to ${ws2Config.uri.host}');
logger.i('[call] ws2 url ${_maskedUrl()}');
unawaited(_wakeSignalingIfSilent(signaling));
Timer(const Duration(seconds: 10), () {
if (_ended || _gotConnection) return;
logger.w(
@@ -260,6 +263,55 @@ class CallSession {
});
}
String _maskedUrl() {
final token = ws2Config.uri.queryParameters['token'];
if (token == null || token.length < 12) return ws2Config.uri.toString();
final masked =
'${token.substring(0, 4)}${token.substring(token.length - 6)}';
return ws2Config.uri.toString().replaceAll(
Uri.encodeQueryComponent(token),
masked,
);
}
/// Кадры ws2 приходят в broadcast-канал Rust-ядра, а подписка на него
/// создаётся уже после того, как сокет открыт: нотификацию `connection`,
/// присланную сразу после хэндшейка, ядро выбрасывает. Если её нет — толкаем
/// сервер командой (ответы идут по sequence и гонке не подвержены).
Future<void> _wakeSignalingIfSilent(Ws2Signaling signaling) async {
await Future<void>.delayed(const Duration(milliseconds: 1200));
if (_ended || _gotConnection || _signaling != signaling) return;
logger.w('[call] "connection" не пришла за 1.2 с — бужу ws2');
try {
final response = await signaling.sendCommand(
'change-media-settings',
extra: {
'mediaSettings': {
'isVideoEnabled': _localVideo,
'isAudioEnabled': !_muted,
'isScreenSharingEnabled': _localScreen,
'isAnimojiEnabled': false,
},
},
);
logger.i('[call] ws2 wake ok: $response');
} catch (e) {
logger.w('[call] ws2 wake failed: $e');
return;
}
await Future<void>.delayed(const Duration(milliseconds: 1200));
if (_ended || _gotConnection || _signaling != signaling) return;
logger.w('[call] всё ещё тихо — шлю accept-call вслепую');
try {
await accept(activate: false);
} catch (e) {
logger.w('[call] accept-call failed: $e');
}
}
void _onSignalingLost() {
if (_ended || _reconnecting) return;
logger.w('[call] signaling lost, reconnecting');
+5 -2
View File
@@ -52,8 +52,10 @@ class Ws2Config {
return Ws2Config(uri: uri, userId: userId);
}
/// Исходящий звонок: `endpoint` из ответа opcode 78 уже содержит токен и
/// conversationId/userId в query — дописываем клиентские параметры.
/// Исходящий звонок или вход в конференцию: `endpoint` из ответа opcode 78 /
/// 166 уже содержит токен и conversationId/userId в query — дописываем
/// клиентские параметры. Без `tgt=start` медиасервер принимает сокет, но не
/// поднимает разговор и не шлёт нотификацию `connection`.
factory Ws2Config.fromEndpoint(
String endpoint, {
required int userId,
@@ -72,6 +74,7 @@ class Ws2Config {
'clientType': 'ONE_ME',
'appVersion': _appVersion,
'osVersion': osVersion ?? defaultOsVersion,
'tgt': 'start',
},
);
return Ws2Config(uri: uri, userId: userId);