feat: звонки 1:1 (аудио) — WebRTC + ws2-сигналинг
Реализованы голосовые звонки 1:1 поверх инфраструктуры VK/OK
(flutter_webrtc + сигналинг ws2 wss://videowebrtc.okcdn.ru). Протокол
восстановлен реверсом захваченных пакетов и декомпиляцией
ru.ok.android.externcalls.sdk.
Ядро (lib/core/calls/):
- conversation_params.dart — декодер vcp (LZ4-блок), с unit-тестом
- ws2_signaling.dart — клиент сигналинга ws2 (transmit-data, ICE,
accept-call, hangup, change-media-settings, ping/pong)
- call_session.dart — WebRTC-контроллер для звонящего и вызываемого
- call_controller.dart — глобальный оркестратор: пуш opcode 137,
исходящие, приём/отклонение, управление активной сессией
Бэкенд:
- CallsModule.initiateCall — инициация исходящего (opcode 78)
- общий LZ4-block декодер вынесен в core/protocol/lz4_block.dart
(используется и транспортом, и vcp)
UI / обвязка:
- CallScreen переписан под живой CallSession (+ кнопка «свернуть»
без завершения звонка, возврат повторным нажатием 📞)
- кнопка звонка в шапке диалога
- инициализация CallController на логине, показ входящего по пушу
- дев-тумблер: принудительный сигнал состояния микрофона без
изменения реального аудиотрека
Прочее:
- зависимость flutter_webrtc ^0.12.5
- Android: RECORD_AUDIO / MODIFY_AUDIO_SETTINGS / BLUETOOTH
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
/// LZ4 block декомпрессия (без frame-заголовка).
|
||||
///
|
||||
/// Сервер шлёт block-формат как в транспорте (payload пакетов), так и в
|
||||
/// `vcp`-параметрах звонка. dart_lz4 поддерживает только frame-формат, поэтому
|
||||
/// block распаковывается вручную.
|
||||
Uint8List lz4BlockDecompress(Uint8List src, int maxSize) {
|
||||
var out = Uint8List(1024);
|
||||
int outLen = 0;
|
||||
int pos = 0;
|
||||
|
||||
void ensure(int extra) {
|
||||
if (outLen + extra > maxSize) throw StateError('LZ4: превышен лимит');
|
||||
if (outLen + extra <= out.length) return;
|
||||
var newCap = out.length * 2;
|
||||
while (newCap < outLen + extra) {
|
||||
newCap *= 2;
|
||||
}
|
||||
if (newCap > maxSize) newCap = maxSize;
|
||||
final grown = Uint8List(newCap);
|
||||
grown.setRange(0, outLen, out);
|
||||
out = grown;
|
||||
}
|
||||
|
||||
while (pos < src.length) {
|
||||
final token = src[pos++];
|
||||
var litLen = token >> 4;
|
||||
|
||||
if (litLen == 15) {
|
||||
while (pos < src.length) {
|
||||
final b = src[pos++];
|
||||
litLen += b;
|
||||
if (b != 255) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (litLen > 0) {
|
||||
ensure(litLen);
|
||||
out.setRange(outLen, outLen + litLen, src, pos);
|
||||
outLen += litLen;
|
||||
pos += litLen;
|
||||
}
|
||||
|
||||
if (pos >= src.length) break;
|
||||
|
||||
if (pos + 1 >= src.length) throw StateError('LZ4: unexpected end of input');
|
||||
final offset = src[pos] | (src[pos + 1] << 8);
|
||||
pos += 2;
|
||||
if (offset == 0) throw StateError('LZ4: offset = 0');
|
||||
|
||||
var matchLen = (token & 0x0F) + 4;
|
||||
if ((token & 0x0F) == 0x0F) {
|
||||
while (pos < src.length) {
|
||||
final b = src[pos++];
|
||||
matchLen += b;
|
||||
if (b != 255) break;
|
||||
}
|
||||
}
|
||||
|
||||
ensure(matchLen);
|
||||
final start = outLen - offset;
|
||||
if (start < 0) throw StateError('LZ4: offset за пределами вывода');
|
||||
for (var i = 0; i < matchLen; i++) {
|
||||
out[outLen + i] = out[start + i];
|
||||
}
|
||||
outLen += matchLen;
|
||||
}
|
||||
|
||||
return Uint8List.sublistView(out, 0, outLen);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'dart:isolate';
|
||||
import 'package:dart_lz4/dart_lz4.dart';
|
||||
import 'package:libcompress/libcompress.dart';
|
||||
import 'package:msgpack_dart/msgpack_dart.dart' as msgpack;
|
||||
import 'lz4_block.dart';
|
||||
|
||||
/// ver(1) + cmd(1) + seq(2) + opcode(2) + packedLen(4) = 10
|
||||
const int headerSize = 10;
|
||||
@@ -203,75 +204,9 @@ Uint8List _decompressPayload(Uint8List src) {
|
||||
|
||||
// По умолчанию — LZ4 block (без magic)
|
||||
try {
|
||||
return _lz4BlockDecompress(src, _maxDecompressedSize);
|
||||
return lz4BlockDecompress(src, _maxDecompressedSize);
|
||||
} catch (e) {
|
||||
throw Exception('LZ4 block decompression error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// LZ4 block декомпрессия (без frame-заголовка).
|
||||
/// Сервер шлёт именно block-формат, dart_lz4 его не поддерживает.
|
||||
Uint8List _lz4BlockDecompress(Uint8List src, int maxSize) {
|
||||
var out = Uint8List(1024);
|
||||
int outLen = 0;
|
||||
int pos = 0;
|
||||
|
||||
void ensure(int extra) {
|
||||
if (outLen + extra > maxSize) throw StateError('LZ4: превышен лимит');
|
||||
if (outLen + extra <= out.length) return;
|
||||
var newCap = out.length * 2;
|
||||
while (newCap < outLen + extra) {
|
||||
newCap *= 2;
|
||||
}
|
||||
if (newCap > maxSize) newCap = maxSize;
|
||||
final grown = Uint8List(newCap);
|
||||
grown.setRange(0, outLen, out);
|
||||
out = grown;
|
||||
}
|
||||
|
||||
while (pos < src.length) {
|
||||
final token = src[pos++];
|
||||
var litLen = token >> 4;
|
||||
|
||||
if (litLen == 15) {
|
||||
while (pos < src.length) {
|
||||
final b = src[pos++];
|
||||
litLen += b;
|
||||
if (b != 255) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (litLen > 0) {
|
||||
ensure(litLen);
|
||||
out.setRange(outLen, outLen + litLen, src, pos);
|
||||
outLen += litLen;
|
||||
pos += litLen;
|
||||
}
|
||||
|
||||
if (pos >= src.length) break;
|
||||
|
||||
if (pos + 1 >= src.length) throw StateError('LZ4: unexpected end of input');
|
||||
final offset = src[pos] | (src[pos + 1] << 8);
|
||||
pos += 2;
|
||||
if (offset == 0) throw StateError('LZ4: offset = 0');
|
||||
|
||||
var matchLen = (token & 0x0F) + 4;
|
||||
if ((token & 0x0F) == 0x0F) {
|
||||
while (pos < src.length) {
|
||||
final b = src[pos++];
|
||||
matchLen += b;
|
||||
if (b != 255) break;
|
||||
}
|
||||
}
|
||||
|
||||
ensure(matchLen);
|
||||
final start = outLen - offset;
|
||||
if (start < 0) throw StateError('LZ4: offset за пределами вывода');
|
||||
for (var i = 0; i < matchLen; i++) {
|
||||
out[outLen + i] = out[start + i];
|
||||
}
|
||||
outLen += matchLen;
|
||||
}
|
||||
|
||||
return Uint8List.sublistView(out, 0, outLen);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user