@tencent-rtc/mcp 1.3.8 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/package.json +1 -1
  2. package/resource/doc/chatuikit/android/integration.md +499 -203
  3. package/resource/doc/chatuikit/android/workflow.md +713 -0
  4. package/resource/doc/chatuikit/flutter/integration.md +1 -1
  5. package/resource/doc/livekit/voice/android/voice-audience-list.md +334 -0
  6. package/resource/doc/livekit/voice/android/voice-audio-effect.md +380 -0
  7. package/resource/doc/livekit/voice/android/voice-barrage.md +565 -0
  8. package/resource/doc/livekit/voice/android/voice-co-guest-interaction.md +745 -0
  9. package/resource/doc/livekit/voice/android/voice-gift.md +822 -0
  10. package/resource/doc/livekit/voice/android/voice-host-interaction.md +703 -0
  11. package/resource/doc/livekit/voice/android/voice-integration.md +940 -0
  12. package/resource/doc/livekit/voice/android/voice-room-list.md +575 -0
  13. package/resource/doc/livekit/voice/flutter/voice-co-guest-interaction.md +675 -0
  14. package/resource/doc/livekit/voice/flutter/voice-integration.md +936 -0
  15. package/resource/doc/livekit/voice/ios/voice-audience-list.md +339 -0
  16. package/resource/doc/livekit/voice/ios/voice-audio-effect.md +370 -0
  17. package/resource/doc/livekit/voice/ios/voice-barrage.md +517 -0
  18. package/resource/doc/livekit/voice/ios/voice-co-guest-interaction.md +708 -0
  19. package/resource/doc/livekit/voice/ios/voice-gift.md +783 -0
  20. package/resource/doc/livekit/voice/ios/voice-host-interaction.md +770 -0
  21. package/resource/doc/livekit/voice/ios/voice-integration.md +898 -0
  22. package/resource/doc/livekit/voice/ios/voice-room-list.md +485 -0
  23. package/resource/doc/roomkit/vue/integration.md +179 -288
  24. package/resource/doc/roomkit/vue/workflow.md +499 -253
  25. package/resource/doc/tuicallkit/android/integration.md +539 -83
  26. package/resource/doc/tuicallkit/android/workflow.md +441 -0
  27. package/tools/common/present-scenario-choice.js +16 -0
  28. package/tools/common/present-scenario-choice.js.map +1 -0
  29. package/tools/definitions/get_native_call_uikit_integration_def.js +25 -9
  30. package/tools/definitions/get_native_call_uikit_integration_def.js.map +1 -1
  31. package/tools/definitions/get_native_chat_uikit_integration_def.js +36 -14
  32. package/tools/definitions/get_native_chat_uikit_integration_def.js.map +1 -1
  33. package/tools/definitions/get_native_live_uikit_integration_def.js +55 -29
  34. package/tools/definitions/get_native_live_uikit_integration_def.js.map +1 -1
  35. package/tools/definitions/get_native_live_uikit_interaction_def.js +43 -54
  36. package/tools/definitions/get_native_live_uikit_interaction_def.js.map +1 -1
  37. package/tools/definitions/get_web_room_feature_detail_def.js +52 -55
  38. package/tools/definitions/get_web_room_feature_detail_def.js.map +1 -1
  39. package/tools/definitions/get_web_room_uikit_integration_def.js +41 -25
  40. package/tools/definitions/get_web_room_uikit_integration_def.js.map +1 -1
  41. package/tools/definitions/present_scenario_choice_def.js +53 -0
  42. package/tools/definitions/present_scenario_choice_def.js.map +1 -0
  43. package/tools/definitions/record_prompt_def.js +26 -21
  44. package/tools/definitions/record_prompt_def.js.map +1 -1
  45. package/tools/definitions/record_result_def.js +20 -4
  46. package/tools/definitions/record_result_def.js.map +1 -1
  47. package/tools/index.js +2 -0
  48. package/tools/index.js.map +1 -1
  49. package/tools/live/get-native-live-uikit-integration.js +110 -34
  50. package/tools/live/get-native-live-uikit-integration.js.map +1 -1
  51. package/tools/live/get_native_live_uikit_interaction.js +128 -35
  52. package/tools/live/get_native_live_uikit_interaction.js.map +1 -1
  53. package/utils/report-cls-client.js +1 -1
  54. package/utils/report-es-client.js +1 -1
@@ -0,0 +1,770 @@
1
+ `AtomicXCore` provides two core modules: `CoHostStore` for **cross-room connections** and `BattleStore` for **PK battles**. This guide explains how to integrate these modules to enable connection and PK features in a voice chat room application.
2
+
3
+
4
+
5
+ ![](https://write-document-release-1258344699.cos.ap-guangzhou.tencentcos.cn/100027182498/281d42a3c9fd11f09e745254007c27c5.png)
6
+
7
+ ## Core Concepts
8
+ <table>
9
+ <tr>
10
+ <td rowspan="1" colSpan="1" >**Key Concept**</td>
11
+
12
+ <td rowspan="1" colSpan="1" >**Type**</td>
13
+
14
+ <td rowspan="1" colSpan="1" >**Core Responsibilities & Description**</td>
15
+ </tr>
16
+
17
+ <tr>
18
+ <td rowspan="1" colSpan="1" >**CoHostStore**</td>
19
+
20
+ <td rowspan="1" colSpan="1" >`class`</td>
21
+
22
+ <td rowspan="1" colSpan="1" >Manages the lifecycle of co-hosting between streamers, providing APIs for co-hosting (requestHostConnection, acceptHostConnection).</td>
23
+ </tr>
24
+
25
+ <tr>
26
+ <td rowspan="1" colSpan="1" >**BattleStore**</td>
27
+
28
+ <td rowspan="1" colSpan="1" >`class`</td>
29
+
30
+ <td rowspan="1" colSpan="1" >Handles signaling and state management for cross-room PK battles, including PK status.</td>
31
+ </tr>
32
+
33
+ <tr>
34
+ <td rowspan="1" colSpan="1" >**LiveListStore**</td>
35
+
36
+ <td rowspan="1" colSpan="1" >`class`</td>
37
+
38
+ <td rowspan="1" colSpan="1" >Fetches the list of live rooms (`fetchLiveList`), enabling discovery of other rooms available for co-hosting.</td>
39
+ </tr>
40
+
41
+ <tr>
42
+ <td rowspan="1" colSpan="1" >**LiveSeatStore**</td>
43
+
44
+ <td rowspan="1" colSpan="1" >`class`</td>
45
+
46
+ <td rowspan="1" colSpan="1" >Manages the status of all seats in a room (including cross-room seats during co-hosting), tracks users joining/leaving seats and microphone status.</td>
47
+ </tr>
48
+
49
+ <tr>
50
+ <td rowspan="1" colSpan="1" >**CoHostLayoutTemplate**</td>
51
+
52
+ <td rowspan="1" colSpan="1" >`enum`</td>
53
+
54
+ <td rowspan="1" colSpan="1" >Defines seat layout templates for co-hosting, such as `.hostStaticVoice6v6`.</td>
55
+ </tr>
56
+
57
+ <tr>
58
+ <td rowspan="1" colSpan="1" >**BattleConfig**</td>
59
+
60
+ <td rowspan="1" colSpan="1" >`struct`</td>
61
+
62
+ <td rowspan="1" colSpan="1" >Configures PK details, including PK duration (`duration`) and whether a response (`needResponse`) is required.</td>
63
+ </tr>
64
+ </table>
65
+
66
+
67
+ ## Prerequisites
68
+
69
+ Integrate the [Quick Start](https://write.woa.com/document/194571595685527552) and ensure you have implemented both **streamer start live** and **audience watch** features.
70
+
71
+ ## Streamer Initiates a Co-hosting Session
72
+
73
+ ### Step 1: Fetch Active Streamers
74
+
75
+ To initiate co-hosting or PK, fetch the current list of live rooms and select a target streamer.
76
+
77
+ **Implementation:** Call `LiveListStore.shared.fetchLiveList`.
78
+
79
+ > **Note:**
80
+ >
81
+ > - If the desired live room is missing from the list, verify that the isPublicVisible field in TUILiveInfo was set to true when starting the stream.
82
+ > - This API supports pagination. Use the cursor from the `Result` is `.success` callback for subsequent requests. If the returned cursor is empty, all rooms have been fetched.
83
+ > - For optimal performance, set count to 20.
84
+
85
+ ``` swift
86
+ import AtomicXCore
87
+
88
+ func fetchLiveListForCoHost() {
89
+ var cursor = "" // Initial fetch, cursor is empty
90
+ let count = 20 // Fetch 20 items per request
91
+ let liveListStore = LiveListStore.shared
92
+ liveListStore.fetchLiveList(cursor: cursor, count: count, completion: { [weak self] result in
93
+ guard let self = self else { return }
94
+ switch result {
95
+ case .success:
96
+ print("Live room list fetched successfully")
97
+ // The live room list is updated in LiveListStore.shared.state.value.liveList.
98
+ // Update the co-host invitation panel with the recommended users.
99
+ // inviteAdapter.submitList(LiveListStore.shared.state.value.liveList)
100
+ case .failure(let error):
101
+ print("Failed to fetch live room list: \(error.code), \(error.message)")
102
+ }
103
+ })
104
+ }
105
+ ```
106
+
107
+ ### Step 2: Send a Co-hosting Invitation
108
+
109
+ After selecting a target streamer, use `CoHostStore.requestHostConnection` to send a co-hosting invitation.
110
+
111
+ > **Note:**
112
+ >
113
+ > - AtomicXCore supports up to 9 rooms for cross-room co-hosting, with 50 users per room.
114
+ > - The API supports batch invitations. To check if an invitation to a single room was successful, you need to check if the` Result` is `.success` in the completion callback.
115
+ > - The API accepts extension fields. After the invitation is sent, this field is delivered to the target streamer via the `onCoHostRequestReceived` callback. Use it to pass custom business data, such as "start PK immediately".
116
+
117
+ ``` swift
118
+ import AtomicXCore
119
+
120
+ let targetHostLiveID = "target_host_room_id"
121
+ let layoutTemplate = .hostStaticVoice6v6
122
+ let timeout: TimeInterval = 10 // Invitation timeout (seconds)
123
+ let extraInfo: String = "" // Custom business info, e.g., for direct PK
124
+
125
+ coHostStore.requestHostConnection(
126
+ targetHost: targetHostLiveID,
127
+ layoutTemplate: layoutTemplate,
128
+ timeout: timeout,
129
+ extraInfo: extraInfo,
130
+ completion: { [weak self] result in
131
+ guard let self = self else { return }
132
+ switch result {
133
+ case .success():
134
+ print("Invitation sent successfully")
135
+ case .failure(let error):
136
+ print("Failed to send invitation: \(error.code), \(error.message)")
137
+ }
138
+ }
139
+ )
140
+ ```
141
+
142
+ ### Step 3: Target Streamer Receives Invitation
143
+
144
+ Once the invitation is sent, the target streamer receives the `onCoHostRequestReceived` event from `CoHostEventPublisher`. Listen for this event to display a UI prompt, such as "Streamer invites you to join co-hosting".
145
+ ``` swift
146
+ var coHostStore: CoHostStore {
147
+ return CoHostStore.create(liveID: liveID)
148
+ }
149
+
150
+ coHostStore.coHostEventPublisher
151
+ .receive(on: RunLoop.main)
152
+ .sink { [weak self] event in
153
+ guard let self = self else { return }
154
+ switch event {
155
+ case .onCoHostRequestReceived(let inviter, let extensionInfo):
156
+ // Handle co-hosting invitation
157
+ default:
158
+ break
159
+ }
160
+ }.store(in: &cancellableSet)
161
+ ```
162
+
163
+ ### Step 4: Target Streamer Accepts or Rejects Invitation
164
+
165
+ On the UI, the target streamer can accept or reject the invitation using `acceptHostConnection` or `rejectHostConnection`.
166
+ ``` swift
167
+ import AtomicXCore
168
+ import Combine
169
+
170
+ private var cancellableSet: Set<AnyCancellable> = []
171
+
172
+ var coHostStore: CoHostStore {
173
+ return CoHostStore.create(liveID: liveID)
174
+ }
175
+
176
+ coHostStore.coHostEventPublisher
177
+ .receive(on: RunLoop.main)
178
+ .sink { [weak self] event in
179
+ guard let self = self else { return }
180
+ switch event {
181
+ case .onCoHostRequestReceived(let inviter, let extensionInfo):
182
+ // Accept co-hosting request
183
+ coHostStore.acceptHostConnection(fromHostLiveID: inviter.liveID) { [weak self] result in
184
+ guard let self = self else { return }
185
+ switch result {
186
+ case .success():
187
+ // Accepted
188
+ case .failure(let error):
189
+ // Handle error
190
+ }
191
+ }
192
+ // To reject:
193
+ // coHostStore.rejectHostConnection(fromHostLiveID: inviter.liveID) { ... }
194
+ default:
195
+ break
196
+ }
197
+ }.store(in: &cancellableSet)
198
+ ```
199
+
200
+ ### Step 5: Render Co-hosting UI Layout
201
+
202
+ Once co-hosting is established, listen to `CoHostStore.coHostState.connected` and `LiveSeatStore.liveSeatState.seatList` to update the seat layout dynamically.
203
+ ``` swift
204
+ import AtomicXCore
205
+ import Combine
206
+
207
+ private var cancellableSet: Set<AnyCancellable> = []
208
+
209
+ var liveSeatStore: LiveSeatStore {
210
+ return LiveSeatStore.create(liveID: liveID)
211
+ }
212
+
213
+ // Listen for co-hosting state
214
+ coHostStore.state.subscribe(StatePublisherSelector(keyPath: \CoHostState.connected))
215
+ .receive(on: RunLoop.main)
216
+ .dropFirst()
217
+ .sink { [weak self] connected in
218
+ guard let self = self else { return }
219
+ // If connected > 0, host is in co-hosting state
220
+ }
221
+ .store(in: &cancellableSet)
222
+
223
+ // Listen for seat info to refresh avatars, mute status, etc.
224
+ liveSeatStore.state.subscribe(StatePublisherSelector(keyPath: \LiveSeatState.seatList))
225
+ .removeDuplicates()
226
+ .receive(on: RunLoop.main)
227
+ .sink { [weak self] seatList in
228
+ guard let self = self else { return }
229
+ // Use seatInfo.userInfo.liveID to distinguish local/remote users
230
+ // Use seatInfo.userInfo.microphoneStatus for mute icon
231
+ }
232
+ .store(in: &cancellableSet)
233
+ ```
234
+
235
+ ### Step 6: Invite Additional Streamers During Co-hosting
236
+
237
+ You can invite more streamers to join co-hosting at any time by calling `requestHostConnection` again.
238
+ ``` swift
239
+ import AtomicXCore
240
+
241
+ let targetHostLiveID = "target_host_room_id"
242
+ let layoutTemplate = .hostStaticVoice6v6
243
+ let timeout: TimeInterval = 10
244
+ let extraInfo: String = ""
245
+
246
+ coHostStore.requestHostConnection(
247
+ targetHost: targetHostLiveID,
248
+ layoutTemplate: layoutTemplate,
249
+ timeout: timeout,
250
+ extraInfo: extraInfo,
251
+ completion: { [weak self] result in
252
+ guard let self = self else { return }
253
+ switch result {
254
+ case .success():
255
+ print("Invitation sent successfully")
256
+ case .failure(let error):
257
+ print("Failed to send invitation: \(error.code), \(error.message)")
258
+ }
259
+ }
260
+ )
261
+ ```
262
+
263
+ ### Step 7: Exit Co-hosting
264
+
265
+ To exit co-hosting, call `exitHostConnection`. Other streamers in the session will receive updated seat information and should change their layouts.
266
+ ``` swift
267
+ func onExitButtonClicked() {
268
+ coHostStore.exitHostConnection() { [weak self] result in
269
+ guard let self = self else { return }
270
+ switch result {
271
+ case .success():
272
+ // Exited co-hosting
273
+ case .failure(let error):
274
+ // Handle error
275
+ }
276
+ }
277
+ }
278
+ ```
279
+
280
+
281
+
282
+ PK Streamer Initiates a PK BattleStreamers can start PK battles using two main flows, depending on the room state before PK begins:
283
+ <table>
284
+ <tr>
285
+ <td rowspan="1" colSpan="1" >**PK Mode**</td>
286
+
287
+ <td rowspan="1" colSpan="1" >**Room State (Before PK)**</td>
288
+
289
+ <td rowspan="1" colSpan="1" >**Room State (After PK)**</td>
290
+ </tr>
291
+
292
+ <tr>
293
+ <td rowspan="1" colSpan="1" >**Scenario 1: PK after co-hosting**</td>
294
+
295
+ <td rowspan="1" colSpan="1" >Both sides **already co-hosting**</td>
296
+
297
+ <td rowspan="1" colSpan="1" >Return to co-hosting state</td>
298
+ </tr>
299
+
300
+ <tr>
301
+ <td rowspan="1" colSpan="1" >**Scenario 2: Co-hosting with PK mode**</td>
302
+
303
+ <td rowspan="1" colSpan="1" >Both sides **not co-hosting**</td>
304
+
305
+ <td rowspan="1" colSpan="1" >Return to co-hosting state</td>
306
+ </tr>
307
+ </table>
308
+
309
+
310
+ ### Scenario 1: PK After Co-hosting
311
+
312
+ **AtomicXCore** enables timed PK battles between co-hosted streamers. The typical flow:
313
+ 1. Send co-hosting invitation; target streamer accepts.
314
+
315
+ 2. Co-hosted streamers click `PK` to start a timed battle. Winner is determined by the gifts received.
316
+
317
+ 3. Target streamer receives a PK prompt and can accept or decline.
318
+
319
+ 4. Upon acceptance, PK mode starts, and a progress bar displays metrics (gifts, likes, etc.).
320
+
321
+ 5. When PK time ends, results are shown and a penalty phase may follow.
322
+
323
+ 6. After PK, the session returns to co-hosting.
324
+
325
+
326
+ ![](https://write-document-release-1258344699.cos.ap-guangzhou.tencentcos.cn/100027182498/27f2a816c9fd11f0a7775254005ef0f7.png)
327
+
328
+
329
+ ![](https://write-document-release-1258344699.cos.ap-guangzhou.tencentcos.cn/100027182498/27feada9c9fd11f0a93d52540044a08e.png)
330
+
331
+
332
+ #### Step 1: Initiate PK Invitation
333
+
334
+ While co-hosting, call `BattleStore.requestBattle` to send a PK invitation. Configure PK duration, whether acceptance is required, and any extension info.
335
+
336
+ > **Note:**
337
+ >
338
+ > - If duration is not set, PK defaults to 5 minutes.
339
+ > - To skip target acceptance, set needResponse to false in BattleConfig. PK starts immediately.
340
+ > - If needResponse is false, set timeout to 0.
341
+ > - If a timeout is set and not accepted in time, both inviter and invitee receive onBattleRequestTimeout. Co-hosted streamers can initiate another PK.
342
+
343
+ ``` swift
344
+ var battleStore: BattleStore {
345
+ return BattleStore.create(liveID: liveID)
346
+ }
347
+ let targetUserID = ""
348
+
349
+ let config = BattleConfig(
350
+ duration: 30, // PK duration in seconds
351
+ needResponse: true, // Target must accept
352
+ extensionInfo: ""
353
+ )
354
+ battleStore.requestBattle(config: config, userIDList: [targetUserID], timeout: 10) { [weak self] result in
355
+ guard let self = self else { return }
356
+ switch result {
357
+ case .success(let (battleInfo, _)):
358
+ // PK started
359
+ case .failure(_):
360
+ // Handle error
361
+ }
362
+ }
363
+ ```
364
+
365
+ #### Step 2: Target Streamer Receives PK Invitation
366
+
367
+ The target streamer receives the `onBattleRequestReceived` event from `BattleEventPublisher`. Use this event to prompt the target in the UI.
368
+ ``` swift
369
+ battleStore.battleEventPublisher
370
+ .receive(on: RunLoop.main)
371
+ .sink { [weak self] event in
372
+ guard let self = self else { return }
373
+ switch event {
374
+ case .onBattleRequestReceived(let battleID, let inviter, let invitee):
375
+ // Handle PK invitation
376
+ default:
377
+ break
378
+ }
379
+ }
380
+ .store(in: &cancellableSet)
381
+ ```
382
+
383
+ #### Step 3: Enter PK State
384
+
385
+ When you receive the `onBattleStarted` event, render the PK progress bar in the UI.
386
+ - If `needResponse` is `false`, PK starts automatically after sending the invitation.
387
+
388
+ - If `needResponse` is `true`, PK starts only after all targets call `acceptBattle`.
389
+
390
+ - When a target accepts, all PK participants receive `onUserJoinBattle`.
391
+
392
+ - When PK begins, all participants receive `onBattleStarted`.
393
+
394
+
395
+ ![](https://write-document-release-1258344699.cos.ap-guangzhou.tencentcos.cn/100027182498/27ff4d76c9fd11f0b011525400bf7822.png)
396
+
397
+
398
+ #### Step 4: End PK State
399
+
400
+ PK can end in two ways:
401
+ 1. **PK Time Expires:**When the configured duration ends, all participants receive `onBattleEnded`.
402
+
403
+ 2. **Manual Exit:**Any participant can call `exitBattle` to leave PK. Remaining users receive `onUserExitBattle`. If all participants exit before time ends, PK ends early.
404
+
405
+ ``` swift
406
+ var battleStore: BattleStore {
407
+ return BattleStore.create(liveID: liveID)
408
+ }
409
+ guard let battleID = battleStore.state.value.currentBattleInfo?.battleID else { return }
410
+ battleStore.exitBattle(battleID: battleID, completion: { [weak self] result in
411
+ guard let self = self else { return }
412
+ switch result {
413
+ case .success():
414
+ // Exited PK
415
+ case .failure(let error):
416
+ // Handle error
417
+ }
418
+ })
419
+ ```
420
+
421
+ After PK ends (`onBattleEnded`), display results and identify the winner/loser (see Winner Determination section).
422
+
423
+
424
+ > **Note:**
425
+ >
426
+ > - After PK ends, the session returns to cross-room co-hosting.
427
+ > - To end co-hosting after PK, call `exitHostConnection`.
428
+
429
+
430
+ ### Scenario 2: Co-hosting with PK Mode
431
+
432
+ **AtomicXCore** supports direct PK initiation during a live session, without prior co-hosting. After PK ends, the session returns to the live room state.
433
+ 1. Streamer clicks `PK`, opens a panel with active streamers (see Step 1 above).
434
+
435
+ 2. Selects a streamer and sends a PK invitation.
436
+
437
+ 3. Target streamer receives the invitation and can accept or reject.
438
+
439
+ 4. Upon acceptance, PK starts automatically. Points are accumulated via gifts or likes.
440
+
441
+ 5. After PK ends, the winner is determined by points.
442
+
443
+
444
+ ![](https://write-document-release-1258344699.cos.ap-guangzhou.tencentcos.cn/100027182498/280714c7c9fd11f0aedb525400454e06.png)
445
+
446
+
447
+ ![](https://write-document-release-1258344699.cos.ap-guangzhou.tencentcos.cn/100027182498/280558e0c9fd11f08942525400e889b2.png)
448
+
449
+
450
+ > **Note:**
451
+ >
452
+
453
+ > This flow requires both CoHostStore and BattleStore, with `BattleConfig.needResponse` set to `false`.
454
+ >
455
+
456
+
457
+ #### Step 1: Send Co-hosting Request
458
+
459
+ Similar to previous steps, but set the`extraInfo` field in `requestHostConnection` to indicate PK should follow co-hosting. This enables custom UI prompts for the target streamer.
460
+
461
+ For example:
462
+ - `{"withPK:true"}` prompts "Streamer invites you to PK".
463
+
464
+ - `{"withPK:false"}` prompts "Streamer invites you to join cross-room co-hosting".
465
+
466
+ ``` swift
467
+ import AtomicXCore
468
+
469
+ let targetHostLiveID = "target_host_room_id"
470
+ let layoutTemplate = .hostStaticVoice6v6
471
+ let timeout: TimeInterval = 10
472
+ let extraInfo: String = "" // e.g., {"withPK:true"}
473
+
474
+ coHostStore.requestHostConnection(
475
+ targetHost: targetHostLiveID,
476
+ layoutTemplate: layoutTemplate,
477
+ timeout: timeout,
478
+ extraInfo: extraInfo,
479
+ completion: { [weak self] result in
480
+ guard let self = self else { return }
481
+ switch result {
482
+ case .success():
483
+ print("Invitation sent successfully")
484
+ case .failure(let error):
485
+ print("Failed to send invitation: \(error.code), \(error.message)")
486
+ }
487
+ }
488
+ )
489
+ ```
490
+
491
+ #### Step 2: Target Streamer Receives Request and Confirms
492
+
493
+ The target streamer receives `onCoHostRequestReceived` and can accept or reject the invitation.
494
+ ``` swift
495
+ var coHostStore: CoHostStore {
496
+ return CoHostStore.create(liveID: liveID)
497
+ }
498
+
499
+ coHostStore.coHostEventPublisher
500
+ .receive(on: RunLoop.main)
501
+ .sink { [weak self] event in
502
+ guard let self = self else { return }
503
+ switch event {
504
+ case .onCoHostRequestReceived(let inviter, let extensionInfo):
505
+ // Accept co-hosting request
506
+ coHostStore.acceptHostConnection(fromHostLiveID: inviter.liveID) { [weak self] result in
507
+ guard let self = self else { return }
508
+ switch result {
509
+ case .success():
510
+ // Accepted
511
+ case .failure(let error):
512
+ // Handle error
513
+ }
514
+ }
515
+ // To reject:
516
+ // coHostStore.rejectHostConnection(fromHostLiveID: inviter.liveID) { ... }
517
+ default:
518
+ break
519
+ }
520
+ }.store(in: &cancellableSet)
521
+ ```
522
+
523
+ #### Step 3: Initiate PK Automatically
524
+
525
+ When the target streamer accepts, immediately call `BattleStore.requestBattle` with `needResponse: false` to start PK.
526
+ ``` swift
527
+ import AtomicXCore
528
+
529
+ var coHostStore: CoHostStore {
530
+ return CoHostStore.create(liveID: liveID)
531
+ }
532
+
533
+ coHostStore.coHostEventPublisher
534
+ .receive(on: RunLoop.main)
535
+ .sink { [weak self] event in
536
+ guard let self = self else { return }
537
+ switch event {
538
+ case .onCoHostRequestAccepted(let invitee):
539
+ let config = BattleConfig(duration: 30, needResponse: false, extensionInfo: "")
540
+ battleStore.requestBattle(config: config, userIDList: [invitee.userID], timeout: 0) { [weak self] result in
541
+ guard let self = self else { return }
542
+ switch result {
543
+ case .success:
544
+ // PK started
545
+ case .failure(let error):
546
+ // Handle error
547
+ }
548
+ }
549
+ default:
550
+ break
551
+ }
552
+ }.store(in: &cancellableSet)
553
+ ```
554
+
555
+ #### Step 4: Enter PK State
556
+
557
+ This step is same as [Enter PK State](https://write.woa.com/#41199d6f-8352-47bb-ada3-c2202d58391e).
558
+
559
+ ## PK Score Update and Winner Determination
560
+
561
+ ### Updating PK Score via REST API
562
+
563
+ In typical** live host PK scenarios,** the value of gifts received by the host is linked to the PK score (for example, when a viewer sends a "Rocket" gift, the host's PK score increases by 500 points). You can implement real-time PK score updates using our `REST API`.
564
+
565
+ > **Note:**
566
+ >
567
+
568
+ > The PK score system in the LiveKit backend uses pure numeric calculation and accumulation. You must calculate the PK score according to your own business logic before calling the update API. See the following PK score calculation examples:
569
+ >
570
+ <table>
571
+ <tr>
572
+ <td rowspan="1" colSpan="1" >Gift Type</td>
573
+
574
+ <td rowspan="1" colSpan="1" >Score Calculation Rule</td>
575
+
576
+ <td rowspan="1" colSpan="1" >Example</td>
577
+ </tr>
578
+
579
+ <tr>
580
+ <td rowspan="1" colSpan="1" >Basic Gift</td>
581
+
582
+ <td rowspan="1" colSpan="1" >Gift value × 5</td>
583
+
584
+ <td rowspan="1" colSpan="1" >10 RMB gift → 50 points</td>
585
+ </tr>
586
+
587
+ <tr>
588
+ <td rowspan="1" colSpan="1" >Intermediate Gift</td>
589
+
590
+ <td rowspan="1" colSpan="1" >Gift value × 8</td>
591
+
592
+ <td rowspan="1" colSpan="1" >50 RMB gift → 400 points</td>
593
+ </tr>
594
+
595
+ <tr>
596
+ <td rowspan="1" colSpan="1" >Advanced Gift</td>
597
+
598
+ <td rowspan="1" colSpan="1" >Gift value × 12</td>
599
+
600
+ <td rowspan="1" colSpan="1" >100 RMB gift → 1200 points</td>
601
+ </tr>
602
+
603
+ <tr>
604
+ <td rowspan="1" colSpan="1" >Special Effect Gift</td>
605
+
606
+ <td rowspan="1" colSpan="1" >Fixed high score</td>
607
+
608
+ <td rowspan="1" colSpan="1" >520 RMB gift → 1314 points</td>
609
+ </tr>
610
+ </table>
611
+
612
+
613
+
614
+ #### REST API Call Flow
615
+
616
+ ![](https://write-document-release-1258344699.cos.ap-guangzhou.tencentcos.cn/100027182498/34d084f8ca0011f08942525400e889b2.png)
617
+
618
+
619
+ #### Key Process Description
620
+ 1. **Obtain PK Status:**
621
+
622
+ - **Callback Configuration**: Configure [PK Status Callback](https://write.woa.com/document/170203265552953344) to have the `LiveKit` backend actively notify your system when PK starts or ends.
623
+
624
+ - **Active Query**: Your backend can call the [PK Status Query](https://write.woa.com/document/170220847572692992) API at any time to check the current PK status.
625
+
626
+ 2. **PK Score Calculation**: Your backend calculates the PK score increment based on your business rules.
627
+
628
+ 3. **PK Score Update**: Your backend calls the [Update PK Score](https://write.woa.com/document/170220034100383744) API to update the PK score in the `LiveKit` backend.
629
+
630
+ 4. **LiveKit Backend Syncs to Client**: The backend automatically synchronizes the updated PK score to all clients.
631
+
632
+
633
+ #### Involved REST API Endpoints
634
+ <table>
635
+ <tr>
636
+ <td rowspan="1" colSpan="1" >**API**</td>
637
+
638
+ <td rowspan="1" colSpan="1" >**Function Description**</td>
639
+
640
+ <td rowspan="1" colSpan="1" >**Request Example**</td>
641
+ </tr>
642
+
643
+ <tr>
644
+ <td rowspan="1" colSpan="1" >Active API - Query PK Status</td>
645
+
646
+ <td rowspan="1" colSpan="1" >Check whether the current room is in PK</td>
647
+
648
+ <td rowspan="1" colSpan="1" >[Example](https://write.woa.com/document/170220847572692992)</td>
649
+ </tr>
650
+
651
+ <tr>
652
+ <td rowspan="1" colSpan="1" >Active API - Update PK Score</td>
653
+
654
+ <td rowspan="1" colSpan="1" >Update the calculated PK score</td>
655
+
656
+ <td rowspan="1" colSpan="1" >[Example](https://write.woa.com/document/170220034100383744)</td>
657
+ </tr>
658
+
659
+ <tr>
660
+ <td rowspan="1" colSpan="1" >Callback Configuration - PK Start Callback</td>
661
+
662
+ <td rowspan="1" colSpan="1" >Receive real-time notification when PK starts</td>
663
+
664
+ <td rowspan="1" colSpan="1" >[Example](https://write.woa.com/document/170203265552953344)</td>
665
+ </tr>
666
+
667
+ <tr>
668
+ <td rowspan="1" colSpan="1" >Callback Configuration - PK End Callback</td>
669
+
670
+ <td rowspan="1" colSpan="1" >Receive real-time notification when PK ends</td>
671
+
672
+ <td rowspan="1" colSpan="1" >[Example](https://write.woa.com/document/170221608179601408)</td>
673
+ </tr>
674
+ </table>
675
+
676
+
677
+ ### Winner Determination
678
+
679
+ In PK scenarios, streamer scores are tied to gifts received. The PK module in RoomEngine supports score updates and broadcasting. Link gift events to PK scoring for real-time progress bar updates.
680
+
681
+ ![](https://write-document-release-1258344699.cos.ap-guangzhou.tencentcos.cn/100027182498/cb8a5a37cc3d11f091ab5254007c27c5.png)
682
+
683
+
684
+ When your billing system detects a successful gift transaction, call the [Modify PK Score REST API](https://write.woa.com/document/170220034100383744) to update the streamer's PK score. All users in the PK rooms will see the updated `battleScore` in `BattleState`. Use this to update the PK progress bar.
685
+ ``` swift
686
+ battleStore.state.subscribe(StatePublisherSelector(keyPath: \BattleState.battleScore))
687
+ .receive(on: RunLoop.main)
688
+ .sink { [weak self] battleScore in
689
+ guard let self = self else { return }
690
+ // Update PK progress bar here
691
+ }
692
+ .store(in: &cancellableSet)
693
+ ```
694
+
695
+ ## API Documentation
696
+
697
+ For complete API details, refer to the [AtomicXCore API documentation](https://tencent-rtc.github.io/TUIKit_iOS/documentation/atomicxcore):
698
+ <table>
699
+ <tr>
700
+ <td rowspan="1" colSpan="1" >**Store/Component**</td>
701
+
702
+ <td rowspan="1" colSpan="1" >**Function Description**</td>
703
+
704
+ <td rowspan="1" colSpan="1" >**API Documentation**</td>
705
+ </tr>
706
+
707
+ <tr>
708
+ <td rowspan="1" colSpan="1" >**CoHostStore**</td>
709
+
710
+ <td rowspan="1" colSpan="1" >Core component for live stream display and interaction: handles video rendering, widget management, streamer live, audience mic-link, streamer co-hosting, and more.</td>
711
+
712
+ <td rowspan="1" colSpan="1" >[API Documentation](https://tencent-rtc.github.io/TUIKit_iOS/documentation/atomicxcore/cohoststore)</td>
713
+ </tr>
714
+
715
+ <tr>
716
+ <td rowspan="1" colSpan="1" >**BattleStore**</td>
717
+
718
+ <td rowspan="1" colSpan="1" >Manages the full lifecycle of live rooms: create, join, leave, destroy, query room list, modify live info, listen for live status changes.</td>
719
+
720
+ <td rowspan="1" colSpan="1" >[API Documentation](https://tencent-rtc.github.io/TUIKit_iOS/documentation/atomicxcore/battlestore)</td>
721
+ </tr>
722
+
723
+ <tr>
724
+ <td rowspan="1" colSpan="1" >**LiveListStore**</td>
725
+
726
+ <td rowspan="1" colSpan="1" >Manages live room lifecycle: query room list, modify live info, etc.</td>
727
+
728
+ <td rowspan="1" colSpan="1" >[API Documentation](https://tencent-rtc.github.io/TUIKit_iOS/documentation/atomicxcore/liveliststore)</td>
729
+ </tr>
730
+
731
+ <tr>
732
+ <td rowspan="1" colSpan="1" >**LiveSeatStore**</td>
733
+
734
+ <td rowspan="1" colSpan="1" >Manages seat status: tracks all seats in the room (including cross-room seats during co-hosting), check user join/leave, microphone, seat status, etc.</td>
735
+
736
+ <td rowspan="1" colSpan="1" >[API Documentation](https://tencent-rtc.github.io/TUIKit_iOS/documentation/atomicxcore/liveseatstore)</td>
737
+ </tr>
738
+ </table>
739
+
740
+
741
+ ## FAQs
742
+
743
+ #### What are the maximum limits for co-hosted rooms and users during co-hosting or PK?
744
+
745
+ You can co-host up to 9 rooms, with up to 50 users per room.
746
+
747
+ #### How do I implement a penalty phase after PK loss?
748
+
749
+ After receiving the `onBattleEnded` callback from `battleEventPublisher`, use `battleScore` in `BattleState` to determine the winner and loser. Co-hosting continues after PK ends. Implement a custom 30-second penalty phase, then disconnect co-hosting after the countdown.
750
+
751
+ #### Why didn't the co-hosting invitation reach the other party?
752
+ - Ensure `targetHostLiveId` is correct and the target room is actively streaming.
753
+
754
+ - Check network connectivity; invitation signaling times out after 30 seconds.
755
+
756
+
757
+ #### What happens if a streamer disconnects or the app crashes during co-hosting or PK?
758
+
759
+ **CoHostStore** and **BattleStore** include heartbeat and timeout detection. If a participant exits by accident, the other party receives events such as **onCoHostUserLeft** or **onUserExitBattle**. Use these events to update the UI (e.g., "The other party has disconnected") and end the interaction.
760
+
761
+ #### Why can PK scores only be updated via REST API?
762
+
763
+ `REST API` ensures PK score **security, real-time performance, and scalability**:
764
+ - **Tamper-proof and fair**: Requires authentication and data verification; each update is traceable (e.g., gift event), prevents manual changes or cheating, and ensures fair competition.
765
+
766
+ - **Real-time multi-end sync**: Standardized formats (e.g., JSON) enable rapid integration with gift, PK, and display systems, keeping scores consistent and real-time across streamer, audience, and backend.
767
+
768
+ - **Flexible rule adaptation**: Backend configuration changes (e.g., gift-to-score mapping, bonus points) can be made without frontend updates, reducing iteration costs.
769
+
770
+