This guides you through building a voice chat room application with host broadcasting and audience participation features using the AtomicXCore SDK's LiveListStore and LiveSeatStore.
Core Concepts
The following table introduces core concepts of LiveSeatStore:
|
| class
| The core of seat management is responsible for managing ALL seat information and related operations in the room. Expose the real-time microphone position list data stream through liveSeatState.seatList. |
| class
| Represent the current status of the microphone position. seatList is a ValueListenable<List<SeatInfo>> type that stores the real-time status of the seat position list;
speakingUsers signifies the person currently speaking and the corresponding volume.
|
| class
| The data model of one microphone position. The seat position list (seatList) pushed by LiveSeatStore consists of multiple SeatInfo objects. Key field: index (the index of the seat position).
isLocked (whether the seat position is locked).
userInfo (user information on the seat position. If the seat position is empty, this field is also an empty object).
|
| class
| The detailed data model of the on-mic user. When a user successfully gets on mic, the userInfo field in SeatInfo will be filled with this user. Key field: userID (user's unique ID)
userName (user's nickname)
avatarURL (user's profile photo URL)
microphoneStatus (Mic status)
cameraStatus (Camera status).
|
Preparations
Step 1: Activate the Services
See Activate Service to obtain either the trial or paid version of the SDK.Then, go to the Console for application management, and get the following: SDKAppID: Application identifier (required). Tencent Cloud uses SDKAppID for billing and details.
SDKSecretKey: Application secret key, used to initialize the configuration file with secret information.
Step 2: Import AtomicXCore into Your Project
1. Install component: Please add atomic_x_core dependency in your pubspec.yaml file, then execute flutter pub get.
dependencies:
atomic_x_core: ^3.6.0
2. Configure project permissions: Android and iOS projects all are needed to configure permissions.
Add permission to use microphone in file android/app/src/main/AndroidManifest.xml.
<uses-permission android:name="android.permission.RECORD_AUDIO" />
Add permission to use microphone in file ios directory Podfile and ios/Runner directory Info.plist.
Podfile
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_MICROPHONE=1',
]
end
end
end
Info.plist
<key>NSMicrophoneUsageDescription</key>
<string>TUILiveKit needs to access your microphone permission. Recorded video will have sound when enabled</string>
Step 3: Implement Login Logic
Call LoginStore.shared.login in the project to complete login. This is the key premise for using AtomicXCore all functions.
Important:
We recommend calling LoginStore.shared.login after successful log-in to your App's user account to ensure clear and consistent login service logic.
import 'package:flutter/material.dart';
import 'package:atomic_x_core/atomicxcore.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final result = await LoginStore.shared.login(
sdkAppID: 1400000001,
userID: "test_001",
userSig: "xxxxxxxxxxx",
);
if (result.isSuccess) {
debugPrint("login success");
} else {
debugPrint("login failed code: ${result.code}, message: ${result.message}");
}
runApp(const MyApp());
}
Login API parameters:
Parameter | Type | Description |
sdkAppID
| Int
| |
userID
| String
| The unique ID for the current user. Must contain only English letters, numbers, hyphens, and underscores. |
userSig
| String
| A credential for TRTC authentication: Development Environment: You can use the local GenerateTestUserSig.genTestSig function to generate a UserSig or generate a temporary UserSig via the UserSig Generation Tool. |
Build a Basic Voice Chat Room
Step 1: Host Creates a Voice Chat Room
Follow these steps to quickly set up a voice chat room as the host.
1. Initialize LiveSeatStore
On your anchor page, create a LiveSeatStore instance. You need to listen to changes in liveSeatStore.liveSeatState for real-time seat updates and UI rendering.
import 'package:flutter/material.dart';
import 'package:atomic_x_core/atomicxcore.dart';
class YourAnchorPage extends StatefulWidget {
const YourAnchorPage({super.key});
@override
State<YourAnchorPage> createState() => _YourAnchorPageState();
}
class _YourAnchorPageState extends State<YourAnchorPage> {
final _liveListStore = LiveListStore.shared;
final _deviceStore = DeviceStore.shared;
final String _liveID = "test_voice_room_001";
late final LiveSeatStore _liveSeatStore;
late final VoidCallback _seatListListener = _onSeatListChanged;
@override
void initState() {
super.initState();
_liveSeatStore = LiveSeatStore.create(_liveID);
_observeSeatList();
}
void _observeSeatList() {
_liveSeatStore.liveSeatState.seatList.addListener(_seatListListener);
}
void _onSeatListChanged() {
final seatInfoList = _liveSeatStore.liveSeatState.seatList.value;
debugPrint("Seat list updated: ${seatInfoList.length} seats");
setState(() {});
}
@override
void dispose() {
_liveSeatStore.liveSeatState.seatList.removeListener(_seatListListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
2. Turn on Microphone
Turn on the microphone by calling the DeviceStore openLocalMicrophone API:
class _YourAnchorPageState extends State<YourAnchorPage> {
void _openDevices() {
DeviceStore.shared.openLocalMicrophone();
}
}
3. Start Voice Chat
By calling the LiveListStore createLive API to start a voice chat room live stream:
class _YourAnchorPageState extends State<YourAnchorPage> {
final String _liveID = "test_voice_room_001";
@override
void initState() {
super.initState();
_startLive();
}
Future<void> _startLive() async {
final liveInfo = LiveInfo(
liveID: _liveID,
liveName: "test voice chat room",
isSeatEnabled: true,
keepOwnerOnSeat: true,
seatLayoutTemplateID: 70,
seatMode: TakeSeatMode.apply,
maxSeatCount: 10,
);
final result = await _liveListStore.createLive(liveInfo);
if (result.isSuccess) {
debugPrint("Response startLive onSuccess");
_liveSeatStore.unmuteMicrophone();
} else {
debugPrint("Response startLive onError: ${result.message}");
}
}
}
LiveInfo parameters:
|
liveID
| String
| Required | Unique identifier of the live streaming room. |
liveName
| String
| Optional. | Title of the live streaming room. |
notice
| String
| Optional. | Announcement information of the live streaming room. |
isMessageDisable
| bool
| Optional. | Whether to mute chat (true: yes, false: no). |
isPublicVisible
| bool
| Optional. | Make room public (true: yes, false: no) |
isSeatEnabled
| bool
| Optional. | Whether to enable the seat feature (true: yes, false: no). |
keepOwnerOnSeat
| bool
| Optional. | Whether to keep the room owner on the seat. |
maxSeatCount
| int
| Optional. | Maximum number of microphones. |
seatMode
| TakeSeatMode
| Optional. | Microphone mode (free: free to take seat, apply: apply to take seat). |
seatLayoutTemplateID
| int
| Required | Mic position layout template ID. |
coverURL
| String
| Optional. | Cover image URL for the live room. |
backgroundURL
| String
| Optional. | Background image URL of the live streaming room. |
categoryList
| List<int>
| Optional. | Category tag list of the live streaming room. |
activityStatus
| int
| Optional. | Live stream status. |
isGiftEnabled
| bool
| Optional. | Whether to enable the gift function (true: yes, false: no). |
4. Build a Seat UI
Note:
For the business code of the Mic Seat UI effect, see the seat_grid_widget.dart file in the TUILiveKit open-source project to learn about the complete implementation logic. Through the LiveSeatStore instance, listen to changes in liveSeatState.seatList to get seat data in real time for rendering your UI. You can listen to the data on the page (such as YourAnchorPage or YourAudiencePage) in the following ways:
class _YourAnchorPageState extends State<YourAnchorPage> {
late final LiveSeatStore _liveSeatStore;
late final VoidCallback _seatListListener = _onSeatListChanged;
@override
void initState() {
super.initState();
_liveSeatStore = LiveSeatStore.create("your_live_id");
_observeSeatList();
}
void _observeSeatList() {
_liveSeatStore.liveSeatState.seatList.addListener(_seatListListener);
}
void _onSeatListChanged() {
final seatInfoList = _liveSeatStore.liveSeatState.seatList.value;
debugPrint("Seat list updated: ${seatInfoList.length} seats");
setState(() {});
}
@override
void dispose() {
_liveSeatStore.liveSeatState.seatList.removeListener(_seatListListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<List<SeatInfo>>(
valueListenable: _liveSeatStore.liveSeatState.seatList,
builder: (context, seatList, child) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
itemCount: seatList.length,
itemBuilder: (context, index) {
final seat = seatList[index];
return _buildSeatItem(seat);
},
);
},
);
}
Widget _buildSeatItem(SeatInfo seat) {
return Container();
}
}
5. End Voice Chat
After the voice chat, the room owner can call the LiveListStore's endLive API to end the chat. The SDK will handle the logic of stopping streaming and terminating the room.
class _YourAnchorPageState extends State<YourAnchorPage> {
Future<void> _stopLive() async {
final result = await _liveListStore.endLive();
if (result.isSuccess) {
debugPrint("endLive success");
} else {
debugPrint("endLive error: ${result.message}");
}
}
}
Step 2: Audience Joins the Voice Chat Room
Enable audience members to join the voice chat room with the following steps.
1. Initialize the LiveSeatStore
On your audience page, create a LiveSeatStore instance and listen to changes in liveSeatState.seatList to render the mic seat UI.
import 'package:flutter/material.dart';
import 'package:atomic_x_core/atomicxcore.dart';
class YourAudiencePage extends StatefulWidget {
const YourAudiencePage({super.key});
@override
State<YourAudiencePage> createState() => _YourAudiencePageState();
}
class _YourAudiencePageState extends State<YourAudiencePage> {
final _liveListStore = LiveListStore.shared;
final String _liveID = "test_voice_room_001";
late final LiveSeatStore _liveSeatStore;
late final VoidCallback _seatListListener = _onSeatListChanged;
@override
void initState() {
super.initState();
_liveSeatStore = LiveSeatStore.create(_liveID);
_observeSeatList();
}
void _observeSeatList() {
_liveSeatStore.liveSeatState.seatList.addListener(_seatListListener);
}
void _onSeatListChanged() {
final seatInfoList = _liveSeatStore.liveSeatState.seatList.value;
debugPrint("AudiencePage Seat list updated: ${seatInfoList.length} seats");
setState(() {});
}
@override
void dispose() {
_liveSeatStore.liveSeatState.seatList.removeListener(_seatListListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
2. Join the Voice Chat Room
By calling the LiveListStore joinLive API to join a voice chat room, sample code:
class _YourAudiencePageState extends State<YourAudiencePage> {
@override
void initState() {
super.initState();
_joinLive();
}
Future<void> _joinLive() async {
final result = await _liveListStore.joinLive(_liveID);
if (result.isSuccess) {
debugPrint("joinLive success");
} else {
debugPrint("joinLive error: ${result.message}");
}
}
}
3. Build a Seat UI
4. Leave the Voice Chat Room
When the audience logs out of the voice chat room, they need to call the LiveListStore's leaveLive API to exit.
class _YourAudiencePageState extends State<YourAudiencePage> {
Future<void> _leaveLive() async {
final result = await _liveListStore.leaveLive();
if (result.isSuccess) {
debugPrint("leaveLive success");
} else {
debugPrint("leaveLive error: ${result.message}");
}
}
}
Run and Test
After completing the above steps, you can complete a most basic voice chat live streaming scenario. You can refer to various voice chat room scenarios to refine the voice chat scenario. Advanced Features
Display Speaking Wave Animation for Users on Seat
In a voice chat room scenario, a common need is to display a wave animation on the avatar of an on-mic user when they speak, notifying all room users "who is speaking". LiveSeatStore provides the speakingUsers data stream, specialized for implementing this feature.
Implementation
Note:
When an Anchor speaks, a wave animation effect is shown. See the seat_grid_widget.dart file in the TUILiveKit open-source project to learn about the complete implementation logic. On YourAnchorPage or YourAudiencePage, listen for speakingUsers transition and refresh the "speaking" status. Sample code:
class _YourAnchorPageState extends State<YourAnchorPage> {
late final VoidCallback _speakingUsersListener = _onSpeakingUsersChanged;
@override
void initState() {
super.initState();
_observeSpeakingUsersState();
}
void _observeSpeakingUsersState() {
_liveSeatStore.liveSeatState.speakingUsers.addListener(_speakingUsersListener);
}
void _onSpeakingUsersChanged() {
final speakingUserMap = _liveSeatStore.liveSeatState.speakingUsers.value;
debugPrint("Speaking users updated: ${speakingUserMap.length} users");
setState(() {});
}
@override
void dispose() {
_liveSeatStore.liveSeatState.speakingUsers.removeListener(_speakingUsersListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<Map<String, int>>(
valueListenable: _liveSeatStore.liveSeatState.speakingUsers,
builder: (context, speakingUsers, child) {
return YourSpeakingIndicatorWidget(speakingUsers: speakingUsers);
},
);
}
}
class YourSpeakingIndicatorWidget extends StatelessWidget {
final Map<String, int> speakingUsers;
const YourSpeakingIndicatorWidget({
super.key,
required this.speakingUsers,
});
@override
Widget build(BuildContext context) {
return Container();
}
}
Enrich Voice Chat Room Scenarios
After completing the basic voice chat room features, you can refer to the following feature guide to add various interactive gameplay features to the voice chat room.
|
Audience Take Seat | Audience members can apply to take a seat and interact with the host in real time. | | |
Add Barrage Chat | Room members can send and receive real-time text messages. | | |
Gift System | Audience can send virtual gifts to hosts to increase interaction and engagement. | | |
API documentation
|
LiveListStore | Full lifecycle management of live streaming room: create/join/leave/terminate room, query room list, modify live information (name, notice), listen to live streaming status (for example, being kicked out, ended). | |
LiveSeatStore | Core of seat management: manage seat list, Anchor status, and related operations (taking a seat, leaving a seat, removing user, locking seat, switching microphone on or off/camera), listen to seat events. | |
DeviceStore | Audio/Video device control: microphone (on/off, volume), camera (on/off, switchover, video quality), screen sharing, monitor device status in real time. | |
CoGuestStore | Audience co-broadcasting management: join microphone application/invite/grant/deny, member permission control (microphone/camera), state synchronization. | |
CoHostStore | Anchor cross-room connection: support for multiple layout templates (dynamic grid, etc.), initiate/accept/reject connection, and interactive management of co-broadcasting Anchors. | |
BattleStore | Anchor PK battle: initiate PK (duration configuration/competitor), manage PK status (start/end), synchronize score, listen to battle results. | |
GiftStore | Gift interaction: get gift list, send/receive gifts, listen to gift events (including sender, gift details). | |
BarrageStore | Danmaku function: send text/custom barrage item, maintain bullet screen list, monitor danmaku status in real time. | |
LikeStore | Like interaction: send likes, listen to like events, sync total likes. | |
LiveAudienceStore | Audience management: get real-time audience list (ID/name/avatar), check audience size, listen to Enter/Exit Event. | |
AudioEffectStore | Audio effects: voice-changing (child voice/male voice), reverberation (KTV), ear monitoring adjustment, switch special effects in real time. | |
BaseBeautyStore | Basic beauty filter: adjust skin smoothing/whitening/rosy (0-100 levels), reset beauty status, sync effect parameters. | |
FAQs
No Sound After Audience Joins Room
Check device permissions: Please ensure App has declared and obtained system authorization for microphone in Info.plist (iOS) or AndroidManifest.xml (Android).
Check the homeowner side: Whether the room owner has normally called DeviceStore.shared.openLocalMicrophone() to open the microphone.
Check the network: Please check whether the device network connection is normal.
Seat List Not Displayed or Updated
Check Store initialization: Please ensure you have already used the same liveID to correctly create the LiveSeatStore instance (LiveSeatStore.create(liveID)) before createLive or joinLive.
Check data listening: Please check whether you have used addListener correctly to listen to liveSeatStore.liveSeatState.seatList, and ensure to call removeListener in dispose to remove listener.
Check API call: Please ensure the createLive (room owner) or joinLive (listener) API has been successfully called (confirm when isSuccess in the callback result is true).