mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
## Summary - downgrade Mobile Huddle authentication and native media configuration from protocol v3 to the currently deployed relay's v2 contract - restore the released one-byte relay peer prefix while retaining later reconnect, roster, and playout-reset reliability fixes - update Android, iOS, protocol documentation, and focused tests together Protocol v2 does not carry v3's occupancy epoch on audio frames, so it cannot fence the narrow delayed-packet/peer-index-reuse race. This is an intentional compatibility tradeoff until the relay v3 rollout is ready. ### Related issue None found. ### Testing - `just mobile-check` - `just mobile-test` — 1,661 tests passed - Android debug build installed and launched on Pixel 10 as `xyz.block.buzz.mobile.sprout_mobile_profile_settings`; foreground process verified - signed iOS Release build installed and launched on iPhone as `com.buzz.buzzMobile`; running process verified A live two-device Huddle audio call remains a manual verification step. Signed-off-by: kenny lopez <klopez4212@gmail.com>
172 lines
5.5 KiB
Dart
172 lines
5.5 KiB
Dart
import 'package:buzz/shared/huddle/huddle_media.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
const channel = MethodChannel(huddleMediaMethodChannelName);
|
|
final messenger =
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
|
|
|
tearDown(() {
|
|
messenger.setMockMethodCallHandler(channel, null);
|
|
});
|
|
|
|
test(
|
|
'prepares the fixed v2 audio session without overstating codecs',
|
|
() async {
|
|
final calls = <MethodCall>[];
|
|
messenger.setMockMethodCallHandler(channel, (call) async {
|
|
calls.add(call);
|
|
return switch (call.method) {
|
|
'getCapabilities' => {
|
|
'platform': 'android',
|
|
'audioSession': true,
|
|
'microphonePermission': true,
|
|
'capture': false,
|
|
'playback': false,
|
|
'opusEncoding': false,
|
|
'opusDecoding': false,
|
|
},
|
|
'requestMicrophonePermission' => 'granted',
|
|
'prepare' => {'audioSessionPrepared': true},
|
|
'stop' => null,
|
|
_ => throw MissingPluginException(),
|
|
};
|
|
});
|
|
final media = MethodChannelHuddleMedia(channel: channel);
|
|
addTearDown(media.dispose);
|
|
|
|
final capabilities = await media.discoverCapabilities();
|
|
expect(capabilities.supportsAudioSession, isTrue);
|
|
expect(capabilities.supportsFullDuplexOpus, isFalse);
|
|
expect(
|
|
await media.requestMicrophonePermission(),
|
|
HuddleMicrophonePermission.granted,
|
|
);
|
|
await media.prepare();
|
|
|
|
expect(media.state.phase, HuddleMediaPhase.prepared);
|
|
final prepare = calls.singleWhere((call) => call.method == 'prepare');
|
|
expect(prepare.arguments, {
|
|
'protocolVersion': 2,
|
|
'sampleRateHz': 48000,
|
|
'channels': 1,
|
|
'frameSamples': 960,
|
|
});
|
|
await expectLater(
|
|
media.start(),
|
|
throwsA(
|
|
isA<HuddleMediaError>().having(
|
|
(error) => error.code,
|
|
'code',
|
|
HuddleMediaErrorCode.unsupported,
|
|
),
|
|
),
|
|
);
|
|
expect(media.state.phase, HuddleMediaPhase.prepared);
|
|
},
|
|
);
|
|
|
|
test('reports a missing native implementation as unavailable', () async {
|
|
final media = MethodChannelHuddleMedia(channel: channel);
|
|
addTearDown(media.dispose);
|
|
|
|
final capabilities = await media.discoverCapabilities();
|
|
|
|
expect(capabilities, same(HuddleMediaCapabilities.unavailable));
|
|
expect(media.state.phase, HuddleMediaPhase.unavailable);
|
|
});
|
|
|
|
test('opens system settings and reports the native launch result', () async {
|
|
var invoked = 0;
|
|
messenger.setMockMethodCallHandler(channel, (call) async {
|
|
invoked += 1;
|
|
expect(call.method, 'openSystemSettings');
|
|
return true;
|
|
});
|
|
final media = MethodChannelHuddleMedia(channel: channel);
|
|
addTearDown(media.dispose);
|
|
|
|
expect(await media.openSystemSettings(), isTrue);
|
|
expect(invoked, 1);
|
|
});
|
|
|
|
test('missing native settings support degrades to not launched', () async {
|
|
final media = MethodChannelHuddleMedia(channel: channel);
|
|
addTearDown(media.dispose);
|
|
|
|
expect(await media.openSystemSettings(), isFalse);
|
|
});
|
|
|
|
test('starts unmuted and forwards direct mute and speaker changes', () async {
|
|
final calls = <MethodCall>[];
|
|
messenger.setMockMethodCallHandler(channel, (call) async {
|
|
calls.add(call);
|
|
return switch (call.method) {
|
|
'getCapabilities' => {
|
|
'platform': 'android',
|
|
'audioSession': true,
|
|
'microphonePermission': true,
|
|
'capture': true,
|
|
'playback': true,
|
|
'opusEncoding': true,
|
|
'opusDecoding': true,
|
|
},
|
|
'prepare' => {'audioSessionPrepared': true},
|
|
'start' || 'setMuted' || 'setSpeakerEnabled' || 'stop' => null,
|
|
_ => throw MissingPluginException(),
|
|
};
|
|
});
|
|
final media = MethodChannelHuddleMedia(channel: channel);
|
|
addTearDown(media.dispose);
|
|
|
|
await media.discoverCapabilities();
|
|
await media.prepare();
|
|
await media.start();
|
|
expect(media.state.isMuted, isFalse);
|
|
expect(media.state.isSpeakerEnabled, isFalse);
|
|
|
|
await media.setMuted(true);
|
|
|
|
expect(media.state.phase, HuddleMediaPhase.active);
|
|
expect(media.state.isMuted, isTrue);
|
|
expect(calls.singleWhere((call) => call.method == 'setMuted').arguments, {
|
|
'muted': true,
|
|
});
|
|
|
|
await media.setSpeakerEnabled(true);
|
|
|
|
expect(media.state.isSpeakerEnabled, isTrue);
|
|
expect(
|
|
calls.singleWhere((call) => call.method == 'setSpeakerEnabled').arguments,
|
|
{'enabled': true},
|
|
);
|
|
});
|
|
|
|
test('parses aggregate capture diagnostics without audio content', () {
|
|
final diagnostics = HuddleCaptureDiagnostics.fromMap({
|
|
'frameCount': 50,
|
|
'rmsDbovHistogram': {'-42': 30, '-36': 20},
|
|
'peakDbovHistogram': {'-24': 40, '-18': 10},
|
|
'maxPeakDbov': -18,
|
|
'audioSource': 7,
|
|
'audioSourceLabel': 'voice_communication',
|
|
'deviceId': 4,
|
|
'deviceType': 1,
|
|
'deviceLabel': 'Pixel microphone',
|
|
'acousticEchoCancelerAvailable': true,
|
|
'acousticEchoCancelerEnabled': true,
|
|
'noiseSuppressorAvailable': true,
|
|
'noiseSuppressorEnabled': true,
|
|
'automaticGainControlAvailable': true,
|
|
'automaticGainControlEnabled': false,
|
|
});
|
|
|
|
expect(diagnostics.rmsDbovHistogram, {-42: 30, -36: 20});
|
|
expect(diagnostics.peakDbovHistogram, {-24: 40, -18: 10});
|
|
expect(diagnostics.maxPeakDbov, -18);
|
|
expect(diagnostics.debugSummary, contains('voice_communication'));
|
|
});
|
|
}
|