ой в пизду короче
This commit is contained in:
@@ -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) =>
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user