From f224347836831842f95ffdbe0ca892b124546aa1 Mon Sep 17 00:00:00 2001 From: Edison Jwa Date: Tue, 9 Jun 2026 02:15:55 +0900 Subject: [PATCH] fix(ios-audio): add voice join session coordinator --- .../lib/services/voice_join_ordering.dart | 23 ++++++++ .../services/voice_join_ordering_test.dart | 58 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 apps/chanora_flutter/lib/services/voice_join_ordering.dart create mode 100644 apps/chanora_flutter/test/services/voice_join_ordering_test.dart diff --git a/apps/chanora_flutter/lib/services/voice_join_ordering.dart b/apps/chanora_flutter/lib/services/voice_join_ordering.dart new file mode 100644 index 0000000..3122162 --- /dev/null +++ b/apps/chanora_flutter/lib/services/voice_join_ordering.dart @@ -0,0 +1,23 @@ +typedef VoiceJoinCallback = Future Function({ + required BigInt channelId, + required String password, +}); + +typedef IosVoiceSessionActivation = Future Function(); +typedef IosVoiceSessionDeactivation = Future Function(); + +Future joinVoiceChannelWithIosAudioSession({ + required BigInt channelId, + required String password, + required VoiceJoinCallback voiceJoin, + required IosVoiceSessionActivation activateIosAudioSession, + required IosVoiceSessionDeactivation deactivateIosAudioSession, +}) async { + await activateIosAudioSession(); + try { + await voiceJoin(channelId: channelId, password: password); + } catch (_) { + await deactivateIosAudioSession(); + rethrow; + } +} diff --git a/apps/chanora_flutter/test/services/voice_join_ordering_test.dart b/apps/chanora_flutter/test/services/voice_join_ordering_test.dart new file mode 100644 index 0000000..e0ea51d --- /dev/null +++ b/apps/chanora_flutter/test/services/voice_join_ordering_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:chanora_flutter/services/voice_join_ordering.dart'; + +void main() { + group('joinVoiceChannelWithIosAudioSession', () { + test('activates the iOS audio session before Rust voiceJoin', () async { + final calls = []; + + await joinVoiceChannelWithIosAudioSession( + channelId: BigInt.from(42), + password: 'secret', + activateIosAudioSession: () async { + calls.add('activateIosAudioSession'); + }, + deactivateIosAudioSession: () async { + calls.add('deactivateIosAudioSession'); + }, + voiceJoin: ({required channelId, required password}) async { + expect(channelId, BigInt.from(42)); + expect(password, 'secret'); + calls.add('voiceJoin'); + }, + ); + + expect(calls, ['activateIosAudioSession', 'voiceJoin']); + }); + + test('deactivates the iOS audio session when Rust voiceJoin fails', + () async { + final calls = []; + + await expectLater( + joinVoiceChannelWithIosAudioSession( + channelId: BigInt.from(42), + password: '', + activateIosAudioSession: () async { + calls.add('activateIosAudioSession'); + }, + deactivateIosAudioSession: () async { + calls.add('deactivateIosAudioSession'); + }, + voiceJoin: ({required channelId, required password}) async { + calls.add('voiceJoin'); + throw StateError('join rejected'); + }, + ), + throwsStateError, + ); + + expect(calls, [ + 'activateIosAudioSession', + 'voiceJoin', + 'deactivateIosAudioSession', + ]); + }); + }); +}