LiveCoreView
Component Overview
Core Controls for Video Live Streaming (LiveCoreView) provides rich APIs such as preview before broadcasting, starting video live streaming, stopping video live streaming, allowing the audience to watch live streams, stopping watching live streams, connecting anchors and audience in the live streaming room, and cross-room connection with other anchors. You can use our core controls to rapidly deploy the main process of video live streaming within half an hour. Then you can add other live streaming components on top of it or add your own business UI views.
Environment Preparation
Android 5.0 (SDK API Level 21) and above versions.
Gradle 7.0 and above versions.
Mobile devices running Android 5.0 or higher.
Step 1: activate the service
Step Two: Integration and Configuration
1. In the app directory, find the
build.gradle.kts (or build.gradle)
file and add the following code in it to add a dependency on the LiveCoreView component:api("io.trtc.uikit:live-stream-core:latest.release")
api 'io.trtc.uikit:live-stream-core:latest.release'
2. Since we use Java's reflection features internally in the SDK, some classes in the SDK need to be added to the non-obfuscation list. Therefore, you need to add the following code in the
proguard-rules.pro
file:-keep class com.tencent.** { *; }-keep class com.trtc.uikit.livekit.livestreamcore.** { *; }-keep class com.google.gson.** { *;}
3. In the app directory, find the
AndroidManifest.xml
file. In the application node, add tools:replace="android:allowBackup" and android:allowBackup="false". Override the settings within the component and use your own settings.// app/src/main/AndroidManifest.xml<application...// Add the following configuration to override the configuration in the dependent sdk.android:allowBackup="false"tools:replace="android:allowBackup">=
Use CocoaPods to import components. If you encounter problems, please first refer to Environment Preparation. The specific steps for importing components are as follows:
1. Add the
pod 'LiveStreamCore'
dependency in your Podfile
.target 'xxxx' do......pod 'LiveStreamCore'end
If you don't have a
Podfile
file, first use the terminal to cd
to the xxxx.xcodeproj
directory, then create it through the following command:pod init
2. In the terminal, first
cd
to the Podfile
directory, then execute the following commands to install components.pod install
If the latest version of SeatGridView cannot be installed, first delete Podfile.lock and Pods. Then execute the following commands to update the local CocoaPods repository list.
pod repo update
Then execute the following commands to update the Pod version of the component library.
pod update
3. Compile and run first. If you encounter problems, please see Common Issues. If the problem is still unresolved, you can try running our Example project. For any issues you encounter during integration and use, feel free to provide feedback.
Step 3: Logging In
Add the following code in your project. It enables logging in to the TUI component by calling relevant APIs in TUICore. This step is crucial. Only after a successful login can you properly use the various features provided by LiveCoreView.
TUIRoomEngine.login(applicationContext,1400000001, // Replace with the SDKAppID obtained in step 1"denny" // Replace with your UserID"xxxxxxxxxxx", // You can calculate a UserSig in the console and fill it in this positionobject : TUIRoomDefine.ActionCallback() {override fun onSuccess() {Log.i(TAG, "login success")}override fun onError(errorCode: Int, errorMessage: String) {Log.e(TAG, "login failed, errorCode: $errorCode msg:$errorMessage")}})
TUIRoomEngine.login(context,1400000001, // Replace with the SDKAppID obtained in step 1"denny" // Replace with your UserID"xxxxxxxxxxx", // You can calculate a UserSig in the console and fill it in this positionnew TUIRoomDefine.ActionCallback() {@Overridepublic void onSuccess() {Log.i(TAG, "login success");}@Overridepublic void onError(TUICommonDefine.Error error, String message) {Log.e(TAG, "login failed, errorCode: " + errorCode + " msg:" + errorMessage);}});
Add the following code to your project. It enables you to log in to the TUI component by calling the login-related APIs in RTCRoomEngine. This step is crucial. Only after successful log-in can you normally use the features provided by SeatGridView.
//// AppDelegate.swift//import RTCRoomEnginefunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {TUIRoomEngine.login(sdkAppId: 1400000001, // Replace with the SDKAppID obtained in step 1userId: "denny", // Replace with your UserIDuserSig: "xxxxxxxxxxx") { // You can calculate a UserSig in the console and fill it in this positionprint("login success")} onError: { code, message inprint("login failed, code: \(code), error: \(message ?? "nil")")}return true}
Parameter Description
Here is a detailed introduction to several key parameters needed in the login function:
Parameters | Type | Overview |
SDKAppID | int | In the final step of step 1, you have already obtained it. It is not necessary to elaborate here. |
UserID | String | Current user's ID, string type, only allow to include English letters (a-z and A-Z), digits (0-9), hyphen and underscore. |
userSig | String | Use the SecretKey obtained in step 3 of Step 1 to encrypt information such as SDKAppID and UserID to get a UserSig. It is an authentication token used by Tencent Cloud to verify whether the current user can use the TRTC service. You can generate a temporary available UserSig through the auxiliary tool in the console. For more information, see how to calculate and use UserSig. |
Notes:
Development environment: If you are in the local development and debugging stage, you can use the local
GenerateTestUserSig.genTestSig
function to generate userSig. In this method, the SDKSecretKey can be easily decompiled and reverse - engineered. Once your key is leaked, an attacker can steal your Tencent Cloud traffic.Production environment: If your project is to be launched, use the server-side generation of UserSig.
Step 4: Implement Live Streaming Functionality Using Core Controls
The anchor starts live streaming and the audience views the live broadcast effects preview
Anchor starts live streaming | Audience watches live. |
![]() |
![]() |
Anchor Previews and Goes Live
Create core controls
You can load our core control in the Activity where you implement streaming, either through java code or the xml method. The following is an example of the code method (the xml method is similar).
val livecoreView = LiveCoreView(this)
LiveCoreView liveCoreView = new LiveCoreView(this);
import LiveStreamCorelet liveCoreView = LiveCoreView()
Enable live preview: Local camera preview, and the live streaming room has not truly been launched.
livecoreView.startCamera(true, null)
liveCoreView.startCamera(true, null);
import LiveStreamCoreliveCoreView.startCamera(useFrontCamera: true) {} onError: { code, message in}
Anchor opens live room: Open a live room and stream data collected by the local camera and microphone to the live room.
val roomInfo = TUIRoomDefine.RoomInfo()roomInfo.roomId = "123456"livecoreView.startLiveStream(roomInfo, null)livecoreView.startMicrophone(null)
TUIRoomDefine.RoomInfo roomInfo = new TUIRoomDefine.RoomInfo();roomInfo.roomId = "roomId_123456";livecoreView.startLiveStream(roomInfo, null);livecoreView.startMicrophone(null);
import LiveStreamCoreimport RTCRoomEnginelet roomInfo = TUIRoomInfo()roomInfo.roomId = "123456"roomInfo.seatMode = .applyToTakeliveCoreView.startLiveStream(roomInfo: roomInfo) { roomInfo in} onError: { code, message in}liveCoreView.startMicrophone {} onError: { code, message in}
Audience Viewing
Create core controls
You can load our core control in the Activity where you implement streaming, either through java code or the xml method. The following is an example of the code method (the xml method is similar).
val livecoreView = LiveCoreView(this)
LiveCoreView liveCoreView = new LiveCoreView(this);
import LiveStreamCorelet liveCoreView = LiveCoreView()
Audience Joins Live Room: The audience enters the live room and pulls the video stream and audio stream of the live stream host.
livecoreView.joinLiveStream("roomId_123456", null)
livecoreView.joinLiveStream("roomId_123456", null);
import LiveStreamCoreliveCoreView.joinLiveStream(roomId: "roomId_123456") { roomInfo in} onError: { code, message in}
Audience Co-Broadcasting
Effect Preview of Audience Co-Broadcasting
Single-Person Co-Broadcasting | Multi-Person Microphone Connection |
![]() | ![]() |
You can call the following API functions to implement the audience mic connection feature. Take Audience B's application for co-broadcasting with Anchor A as an example, the implementation is as follows.
Notes:
The following are proactive invocation methods provided by LiveCoreView.
All callback methods refer to the callback methods in the ConnectionObserver object set by LiveCoreView.
Audience Sending Microphone Connection Request
Audience B sends a microphone connection request to Anchor A.
val userId = "anchorUserId"; // Change to the room owner's UserId. When an empty string is input, it defaults to the room owner's UserId.val timeout = 60;liveCoreView.requestIntraRoomConnection(userId, 10, null)
String userId = "anchorUserId"; // Change to the room owner's UserId. When an empty string is input, it defaults to the room owner's UserId.int timeout = 60;liveCoreView.requestIntraRoomConnection(userId, timeout, true, null);
let timeout = 60let userId = "anchorUserId" // Change to the room owner's UserId. When an empty string is input, it defaults to the room owner's UserId.liveCoreView.requestIntraRoomConnection(userId: userId, timeOut: timeOut, openCamera: true) {} onError: { code, message in}
NSInteger timeout = 60;NSString userId = "anchorUserId"; // Change to the room owner's UserId. When an empty string is input, it defaults to the room owner's UserId.[liveCoreView requestIntraRoomConnection:""timeOut:timeoutonSuccess:^(void) {} onError:^(NSInteger code, NSString * _Nonnull message) {}];
Anchor Receiving Mic Connection Request
Anchor A will receive Audience B's mic connection request in the onUserConnectionRequest callback method.
override fun onUserConnectionRequest(inviterUser: UserInfo) {Log.i(TAG, "Received connection request from audience: ${inviterUser.userId}")}
@Overridepublic void onUserConnectionRequest(LiveStreamDefine.LiveUser inviterUser) {Log.i(TAG, "Received connection request from audience: " + inviterUser.userId)}
func onUserConnectionRequest(inviterUser: TUIUserInfo) {print("Received connection request from audience: \(inviterUser.userId)")}
- (void)onUserConnectionRequest:(TUIUserInfo *)inviterUser {NSLog(@"Received connection request from audience: %@", hostUser.userId);}
Anchor Responding to Mic Connection Request
After Anchor A receives the mic connection request from the audience, they can call respondIntraRoomConnection to respond whether Audience B is agreed to the mic connection.
// Host agrees to connect micliveCoreView.respondIntraRoomConnection(audienceBUserId, true, null)
// Host agrees to connect micliveCoreView.respondIntraRoomConnection(userId, true, null);// Anchor rejects mic connectionliveCoreView.respondIntraRoomConnection(userId, false, null);
// Host agrees to connect micliveCoreView.respondIntraRoomConnection(userId: audienceBUserId, isAccepted: true) {} onError: { code, message in}
// Host agrees to connect mic[liveCoreView respondIntraRoomConnection:audienceBUserIdisAccepted:YESonSuccess:^(void) {} onError:^(NSInteger code, NSString * _Nonnull message) {}];
Audience Received Broadcaster Response Callback
After Anchor A agrees to Audience B's mic connection request, Audience B will receive a callback confirming the agreement through the onUserConnectionAccepted callback.
override fun onUserConnectionAccepted(inviterUser: UserInfo) {Log.i(TAG, "Audience agreed to connection: ${inviterUser.userId}")}
@Overridepublic void onUserConnectionAccepted(LiveStreamDefine.LiveUser liveUser) {Log.i(TAG, "Audience agreed to connection: " + liveUser.userId)}@Overridepublic void onUserConnectionRejected(LiveStreamDefine.LiveUser liveUser) {Log.i(TAG, "Audience rejected connection: " + liveUser.userId)}
func onUserConnectionAccepted(userId: String) {print("Audience agreed to connection: \(userId)")}
- (void)onUserConnectionAccepted:(NSString *)userId {NSLog(@"Audience agreed to connection: %@", userId);}
Callback for Changes in the List of Mic-Connected Users
After Anchor A agrees to Audience B's mic connection request, LiveCoreView will simultaneously send notifications about changes in the list of co-broadcasting users to both Anchor A and Audience B.
override fun onConnectedUsersUpdated(inviterUser: UserInfo) {Log.i(TAG, "Changes in the list of mic-connected users")}
@Overridepublic void onConnectedUsersUpdated(List<UserInfo> userList, List<UserInfo> joinList, List<UserInfo> leaveList) {Log.i(TAG, "Changes in the list of mic-connected users");}
func onConnectedUsersUpdated(userList: [TUIUserInfo], joinList: [TUIUserInfo], leaveList: [TUIUserInfo]) {print("Changes in the list of mic-connected users")}
- (void)onConnectedUsersUpdated:(NSArray<TUIUserInfo *> *)userListjoinList:(NSArray<TUIUserInfo *> *)joinListleaveList:(NSArray<TUIUserInfo *> *)leaveList {NSLog(@"Changes in the list of mic-connected users"); // If necessary, you can handle userList, joinList and leaveList here}
During a mic connection, to disconnect, you can call the following APIs.
Audience Mic Connection Successful. Anchor Hangs Up the Audience'S Mic Connection
Upon success of the microphone connection between Audience B and Anchor A, Anchor A disconnects the microphone connection with Audience B.
val userId = "audienceBUserId"liveCoreView.disconnectUser(userId, null)
String userId = "audienceUserId";liveCoreView.disconnectUser(userId, null);
let userId = "audienceBUserId"liveCoreView.disconnectUser(userId: userId) {} onError: { code, message in}
NSString *userId = @"audienceBUserId";[liveCoreView disconnectUser:userIdonSuccess:^{} onError:^(NSInteger code, NSString * _Nonnull message) {}];
Audience Receives Callback of Anchor Disconnecting Mic Connection
After Anchor A disconnects the mic connection request with Audience B, Audience B will receive the onUserConnectionTerminated callback.
override fun onUserConnectionTerminated(inviterUser: UserInfo) {Log.i(TAG, "Anchor closed the connection")}
@Overridepublic void onUserConnectionTerminated() {Log.i(TAG, "Anchor closes the connection");}
func onUserConnectionTerminated() {print("Anchor closed the connection")}
- (void)onUserConnectionTerminated {NSLog(@"Anchor closed the connection");}
Audience Mic Connection Successful. Audience Ends Mic Connection
Upon successful mic connection between Audience B and Anchor A, Audience B can proactively disconnect by calling terminateIntraRoomConnection.
liveCoreView.terminateIntraRoomConnection()
liveCoreView.terminateIntraRoomConnection();
liveCoreView.terminateIntraRoomConnection()
[liveCoreView terminateIntraRoomConnection]
Anchor Receives Callback of Audience Disconnecting Mic Connection
When Audience B proactively disconnects the mic, the anchor will receive the onUserConnectionExited callback.
override fun onUserConnectionExited(inviterUser: LiveStreamDefine.LiveUser) {Log.i(TAG, "Audience exited the connection")}
@Overridepublic void onUserConnectionExited(UserInfo liveUser) {Log.i(TAG, "Audience exited the connection: ${liveUser.userId}");}
func onUserConnectionExited(userInfo: TUIUserInfo) {print("Audience exited the connection")}
- (void)onUserConnectionExited:(TUIUserInfo *)userInfo {NSLog(@"Audience exited the connection");}
Anchor Connection
Effect preview of anchor connection
Dual-Anchor Connection | Multi-Anchor Connection |
![]() | ![]() |
You can use the following API to implement the anchor connection feature. Here is an example of implementing the connection between Anchor A and Anchor B.
Notes:
The following are proactive invocation methods provided by LiveCoreView.
All callback methods refer to the callback methods in the ConnectionObserver object set by LiveCoreView.
Anchor a Initiating Connection
Anchor A initiates a connecting line by calling
requestCrossRoomConnection
, and inputs the room id of anchor B, who needs to be connected, in the parameter roomId.val roomId = "anchorBRoomId"mLiveViewList.requestCrossRoomConnection(roomId, 10, null)
String roomId = "anchorBRoomId";mLiveViewList.requestCrossRoomConnection(roomId, 10, null);
let roomId = "anchorRoomId"liveCoreView.requestCrossRoomConnection(roomId: roomId, timeOut: 60) {} onError: { code, message in}
NSString *roomId = @"anchorRoomId";[liveCoreView requestCrossRoomConnection:roomIdtimeOut:60onSuccess:^(void) {} onError:^(NSInteger code, NSString * _Nonnull message) {}];
Anchor A can receive the request acceptance callback through
onCrossRoomConnectionAccepted
.Anchor B Received a Connection Request
Anchor B receives the connection request callback through
onCrossRoomConnectionRequest
.override fun onCrossRoomConnectionRequest(inviterUser: UserInfo) {Log.i(TAG, "Received room connection request from anchor A: ${inviterUser.userId}")}
@Overridepublic void onCrossRoomConnectionRequest(LiveStreamDefine.RoomInfo roomInfo) {Log.i(TAG, "Received room connection request from anchor A: " + roomInfo.roomId);}
func onCrossRoomConnectionRequest(hostUser: TUIConnectionUser) {print("Received room connection request from anchor A: \(hostUser.userId)")}
- (void)onCrossRoomConnectionRequest:(TUIConnectionUser *)hostUser {NSLog(@"Received room connection request from anchor A: %@", hostUser.userId);}
Anchor B responds to the connection request by calling
respondToCrossRoomConnection
.liveCoreView.respondToCrossRoomConnection(roomId, true, null)
// Grant connection requestliveCoreView.respondToCrossRoomConnection(roomId, true, null);
liveCoreView.respondToCrossRoomConnection(roomId: roomId, isAccepted: true) {} onError: { code, message in}
[liveCoreView respondToCrossRoomConnection:roomIdisAccepted:trueonSuccess:^(void) {} onError:^(NSInteger code, NSString * _Nonnull message) {}];
Anchor A, Anchor B, and the audience in the room receive the
onConnectedRoomsUpdated
callback and are notified that the list of accepted connections has changed. override fun onConnectedRoomsUpdated(inviterUser: UserInfo) {Log.i(TAG, "Anchor connection room list updated")}
@Overridepublic void onConnectedRoomsUpdated(List<LiveStreamDefine.RoomInfo> roomList) {Log.i(TAG, "Anchor connection room list updated");}
func onConnectedRoomsUpdated(hostUserList: [TUIConnectionUser]) {print("Anchor connection room list updated")}
- (void)onConnectedRoomsUpdated:(NSArray<TUIConnectionUser *> *)hostUserList {NSLog(@"Anchor connection room list updated");}
Exiting Connection Process
Anchor B calls
terminateIntraRoomConnection
to exit the connection.public void disconnect(TUIRoomDefine.ActionCallback callback) {mTUILiveConnectionManager.disconnect(callback);}
liveCoreView.terminateIntraRoomConnection()
liveCoreView.terminateCrossRoomConnection()
[liveCoreView terminateCrossRoomConnection];
Setting Connection Layout
Custom layout connecting line effect preview
Anchor Connection - Nine-Grid Layout | Anchor Connecting - Floating Window Layout | Anchor Connecting - Custom Layout |
![]() | ![]() |
![]() |
You can quickly set the layout between you and the connected host or between you and the mic-connecting audience in the following ways.
// Set grid layoutmLiveStreamListView.setLayoutMode(LiveCoreViewDefine.LayoutMode.GRID_LAYOUT, "")// Set floating window layoutmLiveStreamListView.setLayoutMode(LiveCoreViewDefine.LayoutMode.FLOAT_LAYOUT, "")// Set custom layoutvar layoutJson = ""mLiveStreamListView.setLayoutMode(LiveCoreViewDefine.LayoutMode.FREE_LAYOUT, layoutJson)
// Set grid layoutmLiveStreamListView.setLayoutMode(LiveCoreViewDefine.LayoutMode.GRID_LAYOUT, "");// Set floating window layoutmLiveStreamListView.setLayoutMode(LiveCoreViewDefine.LayoutMode.FLOAT_LAYOUT, "");// Set custom layoutString layoutJson = "";mLiveStreamListView.setLayoutMode(LiveCoreViewDefine.LayoutMode.FREE_LAYOUT, layoutJson);
import LiveStreamCore// Set grid layoutliveCoreView.setLayoutMode(layoutMode: .gridLayout)// Set floating window layoutliveCoreView.setLayoutMode(layoutMode: .floatLayout)// Set custom layoutlet layoutJson = ""liveCoreView.setLayoutMode(layoutMode: .freeLayout, layoutJson: layoutJson)
Notes:
Custom Layout Json
The structure description of the custom layout's json is as follows:
{"1": { // Number of video views"backgroundColor": "#000000", // Background color of the canvas, in RGB hexadecimal format"viewInfoList": [{ // Layout information and background color of each video view"x": 0, // Horizontal offset ratio to screen width, in the range of [0, 1]"y": 0, // Vertical offset ratio to screen width, in the range of [0, 1]"width": 1, // Width ratio of video view to screen width, in the range of [0, 1]"height": -1, // Height ratio of video view to screen width, value ranges from [0, 1] or -1; -1 indicates the view height is the same as the screen height
Anchor PK
LiveCoreView provides APIs related to anchor PK. Since the UI for each PK play mode is different, LiveCoreView only provides APIs, and you need to implement the UI effect yourself.
Anchor PK Effect Preview
Dual-Anchor PK | Multi-Anchor PK |
![]() | ![]() |
Notes:
The PK feature depends on the connection feature, so you need to be in an anchor connection scenario to initiate a PK request.
The following proactive invocation APIs are provided by
LiveCoreView
.All callback methods refer to the callback methods in the BattleObserver object set by LiveCoreView.
You can use the following API to implement the anchor PK feature. Here, taking the PK between Anchor A and Anchor B as an example, the implementation is as follows.
Anchor a Initiating PK
Anchor A initiates a PK by calling
requestBattle
.val BATTLE_DURATION = 30val battleConfig = BattleConfig()battleConfig.duration = BATTLE_DURATIONbattleConfig.needResponse = truebattleConfig.extensionInfo = ""val list: MutableList<String> = ArrayList()list.add("anchorBUserId")val BATTLE_REQUEST_TIMEOUT = 10liveCoreView.requestBattle(battleConfig, list, BATTLE_REQUEST_TIMEOUT,object : LiveCoreViewDefine.BattleRequestCallback {override fun onSuccess(battleId: String, requestedUserIdList: List<String>) {}override fun onError(error: TUICommonDefine.Error, message: String) {}})
final int BATTLE_DURATION = 30;TUILiveBattleManager.BattleConfig battleConfig = new TUILiveBattleManager.BattleConfig();battleConfig.duration = BATTLE_DURATION;battleConfig.needResponse = true;battleConfig.extensionInfo = "";List<String> list = new ArrayList<>();list.add("anchorBUserId");final int BATTLE_REQUEST_TIMEOUT = 10;liveCoreView.requestBattle(battleConfig, list, BATTLE_REQUEST_TIMEOUT,new LiveCoreViewDefine.BattleRequestCallback() {@Overridepublic void onSuccess(String battleId, List<String> requestedUserIdList) {}@Overridepublic void onError(TUICommonDefine.Error error, String message) {}});
let roomId = "anchorRoomId"let config = TUIBattleConfig()config.duration = battleDurationconfig.needResponse = trueconfig.extensionInfo = ""let userIdList: [String] = []let timeout: TimeInterval = 10liveCoreView.requestBattle(config: config, userIdList: userIdList, timeout: timeout) {[weak self] (battleId, battleUserList) in} onError: { _, _ in}
Anchor A can receive the PK acceptance callback through
onBattleRequestAccept
.Anchor B Receives Battle Request
Anchor B receives the PK request callback through
onBattleRequestReceived
.override fun onBattleRequestReceived(battleId: String, inviterUser: BattleUser, invitee: BattleUser) {Log.i(TAG, "Received anchor A's PK request:: $battleId")}
@Overridepublic void onBattleRequestReceived(String battleId, BattleUser inviter, BattleUser invitee) {Log.i(TAG, "Received anchor A's PK request: " + battleId)}
extension ViewController: BattleObserver { //Replace with your actual class namefunc onBattleRequestReceived(battleId: String, inviter: TUIBattleUser, invitee: TUIBattleUser) {}}
Anchor B responds to the PK request by calling
respondToBattle
.// Accept PK requestval battleId = "battleId"liveCoreView.respondToBattle(battleId, true, null)
// Accept PK requestString battleId = "battleId";liveCoreView.respondToBattle(battleId, true, null);
liveCoreView.respondToBattle(battleId: manager.battleState.battleId, isAccepted: false, onSuccess: { [weak self] in}, onError: { _, _ in})
Anchor A, Anchor B, and the audience in the room receive
onBattleStarted
, receiving the PK start notification. override fun onBattleStarted(battleInfo: BattleInfo) {Log.i(TAG, "PK start callback")}
@Overridepublic void onBattleStarted(BattleInfo battleInfo) {Log.i(TAG, "Anchor connection room list updated")}
extension ViewController: BattleObserver { //Replace with your actual class namefunc onBattleStarted(battleInfo: TUIBattleInfo)) {}}
Exiting the PK Process
Natural end: When the PK time ends, you will receive the
onBattleEnded
callback.Actively stop: Audience B calls
terminateBattle
and will exit the PK. Audience A will receive onUserExitBattle
.mLiveCoreView.terminateBattle(battleId, null)
mLiveCoreView.terminateBattle(battleId, null);
liveCoreView.terminateBattle(battleId: manager.battleState.battleId) {} onError: { _, _ in}