CoGuestStore and LiveSeatStore.
CoGuestStore and LiveSeatStore support the following mainstream scenarios:liveId (the unique identifier for the live room) to distinguish between different live rooms. You can get it as follows:LiveListStore.shared.createLive(liveInfo).liveInfo in LiveListState.liveList.value, or obtain it by the Anchor sharing the live streaming room ID.applyForSeat method.import 'package:atomic_x_core/atomicxcore.dart';final String liveId = "room ID";late CoGuestStore guestStore;@overridevoid initState() {super.initState();guestStore = CoGuestStore.create(liveId);}// User click "Apply for Mic Connection"Future<void> requestToConnect() async {// seatIndex: The seat index, starting from 0, represents the applied microphone position. 0 means the first seat. View seat status via LiveSeatState.seatList.value and select an idle seat.// timeout: Request timeout (seconds). The audience can call cancelApplication to cancel the request to speak within the timeout period.// The request will expire if it hasn't been processed after reaching the timeout periodfinal result = await guestStore.applyForSeat(seatIndex: 0,timeout: 30,extraInfo: null,);if (result.isSuccess) {debugPrint('Mic connection request sent, waiting for anchor processing...');} else {debugPrint('Failed to send request: ${result.message}');}}
GuestListener, you can receive the host's responselate GuestListener _guestListener;// Subscribe to events when your Widget initializesvoid subscribeGuestEvents() {_guestListener = GuestListener(onGuestApplicationResponded: (isAccept, hostUser) {if (isAccept) {debugPrint('Anchor ${hostUser.userName} granted your request, preparing to join the mic');// 1. Turn on microphone// DeviceStore is the device management module provided by AtomicXCore, used for managing microphones, cameras and other physical devices.// When the host agrees to connect mic, turn on microphone to start collecting audio.DeviceStore.shared.openLocalMicrophone();// 2. Update UI herein, such as disabling the apply button and displaying the mic connection state} else {debugPrint('Anchor ${hostUser.userName} refused your request');// Pop-up prompts user that the application was rejected}},);guestStore.addGuestListener(_guestListener);}@overridevoid dispose() {guestStore.removeGuestListener(_guestListener);super.dispose();}
disConnect to return to regular audience status.// User click "Disconnect" buttonFuture<void> leaveSeat() async {final result = await guestStore.disconnect();if (result.isSuccess) {debugPrint('Disconnected successfully');} else {debugPrint('Failed to leave the seat: ${result.message}');}}
cancelApplication.// While waiting, the user clicks "Cancel Application"Future<void> cancelApplication() async {final result = await guestStore.cancelApplication();if (result.isSuccess) {debugPrint('Request canceled');} else {debugPrint('Failed to cancel request: ${result.message}');}}
HostListener, you can receive immediate notification when a new audience applies and prompt.import 'package:atomic_x_core/atomicxcore.dart';final String liveId = "room ID";late CoGuestStore guestStore;late HostListener _hostListener;@overridevoid initState() {super.initState();guestStore = CoGuestStore.create(liveId);// Subscribe to anchor event_hostListener = HostListener(onGuestApplicationReceived: (guestUser) {debugPrint('Received mic connection request from audience ${guestUser.userName}');// Update the UI here, for example display a red dot on the "application list" button},);guestStore.addHostListener(_hostListener);}@overridevoid dispose() {guestStore.removeHostListener(_hostListener);super.dispose();}
CoGuestStore's coGuestState maintains the current applicant list in real time. You can subscribe to it to update your UI.late final VoidCallback _applicantsListener = _onApplicantsChanged;// Subscribe to status changevoid subscribeApplicants() {guestStore.coGuestState.applicants.addListener(_applicantsListener);}void _onApplicantsChanged() {final applicants = guestStore.coGuestState.applicants.value;debugPrint('Current number of applicants: ${applicants.length}');// Update your "applicant list" UI here}@overridevoid dispose() {guestStore.coGuestState.applicants.removeListener(_applicantsListener);super.dispose();}
// Anchor clicks the "Grant" button and imports the applicant's userIDFuture<void> accept(String userId) async {final result = await guestStore.acceptApplication(userId);if (result.isSuccess) {debugPrint('Approved $userId\\'s request, the other party is joining the mic');}}// Anchor clicks the "Reject" buttonFuture<void> reject(String userId) async {final result = await guestStore.rejectApplication(userId);if (result.isSuccess) {debugPrint('$userId\\'s application denied');}}
inviteToSeat method.// Anchor selects audience and initiates invitationFuture<void> invite(String userId) async {// inviteeID: The ID of the invitee, seatIndex: The seat index, timeout: The invitation timeout durationfinal result = await guestStore.inviteToSeat(inviteeID: userId,seatIndex: 0,timeout: 30,extraInfo: null,);if (result.isSuccess) {debugPrint('Sent invitation to $userId, waiting for peer response...');}}
onHostInvitationResponded event via HostListener.// Add to the HostListener configuration_hostListener = HostListener(onHostInvitationResponded: (isAccept, guestUser) {if (isAccept) {debugPrint('Audience ${guestUser.userName} accepted your invitation');} else {debugPrint('Audience ${guestUser.userName} refused your invitation');}},);
onHostInvitationReceived event via GuestListener.// Add to the GuestListener configuration_guestListener = GuestListener(onHostInvitationReceived: (hostUser) {debugPrint('Received mic connection invitation from Anchor ${hostUser.userName}');// Pop up a dialog box for user selection between "Accept" or "Reject"// showInvitationDialog(hostUser);},);
final String inviterId = "anchor ID who initiates invitation"; // obtain from onHostInvitationReceived event// User clicks "Accept"Future<void> accept() async {final result = await guestStore.acceptInvitation(inviterId);if (result.isSuccess) {// Turn the mic on// DeviceStore is the device management module provided by AtomicXCore, used for managing microphones, cameras and other physical devices.// After accepting the invitation, turn on microphone to start collecting audioDeviceStore.shared.openLocalMicrophone();}}// User click "Reject"Future<void> reject() async {await guestStore.rejectInvitation(inviterId);}
LiveSeatStore, which functions with CoGuestStore.DeviceStore and LiveSeatStore for microphone control:openLocalMicrophone starts up the microphone device to perform audio capture. closeLocalMicrophone stops audio capture and releases the microphone device.muteMicrophone mutes and stops sending local audio stream to the remote end, but the microphone device itself keeps running. unmuteMicrophone unmutes and restores sending audio stream to the remote end.openLocalMicrophone only once to start the device.unmuteMicrophone and muteMicrophone to control the upstream audio stream.disconnect), call closeLocalMicrophone to release the device.LiveSeatStore.muteMicrophone() method. This is a one-way request with no callback.unmuteMicrophone() method.final seatStore = LiveSeatStore.create(liveId);seatStore.muteMicrophone(); // Muteawait seatStore.unmuteMicrophone(); // Unmute
closeRemoteMicrophone to forcibly mute the target user's microphone and lock their mic control. The muted user will receive the onLocalMicrophoneClosedByAdmin event via liveSeatEventPublisher, and their local "Open Microphone" button should become disabled.openRemoteMicrophone—this does not forcibly open the user's mic, but unlocks their mic control, allowing them to unmute themselves. The target user receives the onLocalMicrophoneOpenedByAdmin event, their "Open Microphone" button should become enabled, but they remain muted until they unmute themselves.LiveSeatStore's unmuteMicrophone() to actually unmute and be heard in the room.final seatStore = LiveSeatStore.create(liveId);final String targetUserId = "userD";// 1. Force mute userD and lockfinal result1 = await seatStore.closeRemoteMicrophone(targetUserId);if (result1.isSuccess) {debugPrint('Muted and locked $targetUserId');}// 2. Unlock userD's microphone permission (at this point userD remains muted)final result2 = await seatStore.openRemoteMicrophone(userID: targetUserId,policy: DeviceControlPolicy.unlockOnly,);if (result2.isSuccess) {debugPrint('Unlocked $targetUserId\\'s microphone, user can unmute manually');}
late LiveSeatListener _seatListener;// userD listens to the Anchor's operationvoid subscribeSeatEvents() {_seatListener = LiveSeatListener(onLocalMicrophoneClosedByAdmin: () {debugPrint('Muted by anchor');// muteButton.isEnabled = false;},onLocalMicrophoneOpenedByAdmin: (policy) {debugPrint('Anchor unmuted and unlocked');// muteButton.isEnabled = true;// muteButton.text = "Turn on microphone";},);seatStore.addLiveSeatEventListener(_seatListener);}@overridevoid dispose() {seatStore.removeLiveSeatEventListener(_seatListener);super.dispose();}
Parameter | Type | Description |
userID | String | Operating user userID. |
Parameter | Type | Description |
userID | String | Operating user userID. |
policy | DeviceControlPolicy | Turn on microphone policy. |
kickUserOutOfSeat method to force the specified user off the mic.GuestListener onKickedOffSeat event notification.// Assume "userB" is to be kicked outfinal String targetUserId = "userB";final result = await seatStore.kickUserOutOfSeat(targetUserId);if (result.isSuccess) {debugPrint('Kicked $targetUserId off the mic');} else {debugPrint('Kick user failed: ${result.message}');}// "userB" received the kicked off the mic event_guestListener = GuestListener(onKickedOffSeat: (seatIndex, hostUser) {// Show a toast notificationdebugPrint('You have been kicked off the mic by anchor');},);
Parameter | Type | Description |
userID | String | The user kicked off the mic userID. |
lockSeat method to lock the seat with a specified index. Once locked, audiences cannot use applyForSeat or takeSeat to occupy the seat, but the anchor can invite an audience to the locked seat via inviteToSeat.unlockSeat to unlock the seat. The seat can be occupied again after unlocking.// Lock seat 2final result1 = await seatStore.lockSeat(2);if (result1.isSuccess) {debugPrint('Seat 2 locked');}// Unlock seat 2final result2 = await seatStore.unlockSeat(2);if (result2.isSuccess) {debugPrint('Seat 2 unlocked');}
Parameter | Type | Description |
seatIndex | int | Index of the mic to lock. |
Parameter | Type | Description |
seatIndex | int | Index of the mic to unlock. |
moveUserToSeat to change seat positions.userID is the ID of the target user, targetIndex is the index of the target seat, and the policy parameter specifies the move strategy if the target seat is occupied. For details, see the API parameter description.userID must be the user's own ID, targetIndex is the desired new seat index, and the policy parameter is ignored. If the target seat is occupied, the move fails with an error.final result = await seatStore.moveUserToSeat(userID: "userC",targetIndex: newSeatIndex,policy: MoveSeatPolicy.abortWhenOccupied,);if (result.isSuccess) {debugPrint('Successfully moved to seat $newSeatIndex');} else {debugPrint('Seat change failed, possibly seat is occupied');}
Parameter | Type | Description |
userID | String | The user who needs to move the mic userID. |
targetIndex | int | Index of the target seat. |
policy | MoveSeatPolicy | Processing policy when the target seat is occupied abortWhenOccupied: Abort movement when the target seat is occupied (default policy)forceReplace: Forcibly replace the user on the target seat. The replaced user will be kicked off the mic.swapPosition: Exchange with the user on the target seat. |
Store/Component | Feature Description | API Reference |
CoGuestStore | Audience co-broadcasting management: join microphone application/invite/consent/deny, member permission control (microphone/camera), state synchronization. | |
LiveSeatStore | Seat management: mute/unmute, lock/unlock seats, remove speaker, remotely control Anchor microphone, listen to list status. |
LiveCoreWidget as the main component to render the anchor and guest video streams. The UI emphasizes video layout and sizing, and you can use VideoViewDelegate to add overlays (such as nicknames or placeholders). Both camera and microphone can be enabled.LiveCoreWidget, but instead build a grid UI (such as GridView) based on the liveSeatState of LiveSeatStore (especially seatList). The UI displays each seat’s SeatInfo status in real time: whether it’s occupied, muted, locked, or currently speaking. Only the microphone needs to be enabled.seatList property in LiveSeatState, which is a responsive data of ValueListenable<List<SeatInfo>> data type. It will notify you to re-render the microphone position list whenever the array changes. By traversing this array, you can:seatInfo.userInfo.seatInfo.isLocked.seatInfo.userInfo.microphoneStatus.Feedback