@tencent-rtc/trtc-agent-skills 0.1.0

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 (205) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/README.zh.md +173 -0
  4. package/bin/cli.js +434 -0
  5. package/knowledge-base/index.yaml +454 -0
  6. package/knowledge-base/platform-slice-template.md +233 -0
  7. package/knowledge-base/scenario-spec.md +350 -0
  8. package/knowledge-base/scenarios/conference/base/general-conference.md +365 -0
  9. package/knowledge-base/scenarios/conference/base/webinar-conference.md +130 -0
  10. package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +145 -0
  11. package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +113 -0
  12. package/knowledge-base/scenarios/live/entertainment-live-room.md +118 -0
  13. package/knowledge-base/slice-spec.md +546 -0
  14. package/knowledge-base/slices/conference/web/ai-tools.md +225 -0
  15. package/knowledge-base/slices/conference/web/beauty-effects.md +188 -0
  16. package/knowledge-base/slices/conference/web/device-control.md +338 -0
  17. package/knowledge-base/slices/conference/web/login-auth.md +261 -0
  18. package/knowledge-base/slices/conference/web/network-quality.md +190 -0
  19. package/knowledge-base/slices/conference/web/official-roomkit-api.md +298 -0
  20. package/knowledge-base/slices/conference/web/official-roomkit-login-ui.md +246 -0
  21. package/knowledge-base/slices/conference/web/participant-list.md +238 -0
  22. package/knowledge-base/slices/conference/web/participant-management.md +718 -0
  23. package/knowledge-base/slices/conference/web/prejoin-check.md +293 -0
  24. package/knowledge-base/slices/conference/web/room-call.md +213 -0
  25. package/knowledge-base/slices/conference/web/room-chat.md +426 -0
  26. package/knowledge-base/slices/conference/web/room-lifecycle.md +534 -0
  27. package/knowledge-base/slices/conference/web/room-schedule.md +281 -0
  28. package/knowledge-base/slices/conference/web/screen-share.md +211 -0
  29. package/knowledge-base/slices/conference/web/video-layout.md +675 -0
  30. package/knowledge-base/slices/conference/web/virtual-background.md +197 -0
  31. package/knowledge-base/slices/conference/web/webinar-interaction.md +206 -0
  32. package/knowledge-base/slices/live/anchor-lifecycle.md +122 -0
  33. package/knowledge-base/slices/live/anchor-preview.md +90 -0
  34. package/knowledge-base/slices/live/anchor-room-config.md +104 -0
  35. package/knowledge-base/slices/live/audience-list.md +86 -0
  36. package/knowledge-base/slices/live/audience-manage.md +92 -0
  37. package/knowledge-base/slices/live/audience-watch.md +85 -0
  38. package/knowledge-base/slices/live/audio.md +116 -0
  39. package/knowledge-base/slices/live/barrage.md +88 -0
  40. package/knowledge-base/slices/live/beauty.md +99 -0
  41. package/knowledge-base/slices/live/coguest-apply.md +105 -0
  42. package/knowledge-base/slices/live/device-control.md +91 -0
  43. package/knowledge-base/slices/live/error-codes.md +167 -0
  44. package/knowledge-base/slices/live/gift.md +84 -0
  45. package/knowledge-base/slices/live/ios/.gitkeep +0 -0
  46. package/knowledge-base/slices/live/ios/anchor-lifecycle.md +313 -0
  47. package/knowledge-base/slices/live/ios/anchor-preview.md +228 -0
  48. package/knowledge-base/slices/live/ios/anchor-room-config.md +257 -0
  49. package/knowledge-base/slices/live/ios/audience-list.md +353 -0
  50. package/knowledge-base/slices/live/ios/audience-manage.md +381 -0
  51. package/knowledge-base/slices/live/ios/audience-watch.md +286 -0
  52. package/knowledge-base/slices/live/ios/audio.md +373 -0
  53. package/knowledge-base/slices/live/ios/barrage.md +285 -0
  54. package/knowledge-base/slices/live/ios/beauty.md +323 -0
  55. package/knowledge-base/slices/live/ios/coguest-apply.md +506 -0
  56. package/knowledge-base/slices/live/ios/device-control.md +286 -0
  57. package/knowledge-base/slices/live/ios/error-codes.md +270 -0
  58. package/knowledge-base/slices/live/ios/gift.md +315 -0
  59. package/knowledge-base/slices/live/ios/live-list.md +269 -0
  60. package/knowledge-base/slices/live/ios/login-auth.md +247 -0
  61. package/knowledge-base/slices/live/live-list.md +82 -0
  62. package/knowledge-base/slices/live/login-auth.md +78 -0
  63. package/package.json +34 -0
  64. package/skills/trtc/SKILL.md +326 -0
  65. package/skills/trtc/room-builder/SKILL.md +138 -0
  66. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +108 -0
  67. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/backend-contract.zh-CN.md +162 -0
  68. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/integration.zh-CN.md +154 -0
  69. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/theme.zh-CN.md +78 -0
  70. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/index.html +12 -0
  71. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/package.json +28 -0
  72. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/postcss.config.js +5 -0
  73. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/App.vue +25 -0
  74. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/ConsultationManagePanel.vue +838 -0
  75. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LanguageSwitch.vue +102 -0
  76. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LoadingSpinner.vue +6 -0
  77. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalAlert.vue +34 -0
  78. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalBusinessPanel.vue +148 -0
  79. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalButton.vue +49 -0
  80. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalConfirmDialog.vue +68 -0
  81. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalDataPanel.vue +196 -0
  82. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalRecordPanel.vue +270 -0
  83. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/PrescriptionPanel.vue +363 -0
  84. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/basic-info-config.ts +29 -0
  85. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
  86. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
  87. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/runtime-config.ts +12 -0
  88. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/env.d.ts +32 -0
  89. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +123 -0
  90. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +230 -0
  91. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +135 -0
  92. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +113 -0
  93. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +132 -0
  94. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +50 -0
  95. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/types.ts +77 -0
  96. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationChat.ts +97 -0
  97. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationDevices.ts +48 -0
  98. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationParticipants.ts +121 -0
  99. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationPermissions.ts +25 -0
  100. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/utils.ts +70 -0
  101. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/en-US/index.ts +553 -0
  102. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/index.ts +25 -0
  103. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/medicalTranslate.ts +85 -0
  104. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/state.ts +49 -0
  105. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/zh-CN/index.ts +463 -0
  106. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/main.ts +12 -0
  107. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/appointments.ts +96 -0
  108. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/users.ts +79 -0
  109. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/router/index.ts +63 -0
  110. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/index.ts +25 -0
  111. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/appointmentService.ts +77 -0
  112. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/authService.ts +38 -0
  113. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/launchContext.ts +31 -0
  114. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/userService.ts +35 -0
  115. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/appointmentService.ts +43 -0
  116. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/authService.ts +33 -0
  117. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/userService.ts +43 -0
  118. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/types.ts +135 -0
  119. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/shared/icons.ts +53 -0
  120. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/index.css +106 -0
  121. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/tailwind.css +3 -0
  122. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/theme.css +209 -0
  123. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/auth.ts +50 -0
  124. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/format.ts +24 -0
  125. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/navigation.ts +12 -0
  126. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/session.ts +28 -0
  127. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorConsultationView.vue +777 -0
  128. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorDashboardView.vue +678 -0
  129. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/LoginView.vue +441 -0
  130. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationFinishedView.vue +185 -0
  131. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationView.vue +1003 -0
  132. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientSelectDoctorView.vue +317 -0
  133. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientWaitingView.vue +454 -0
  134. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.json +21 -0
  135. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.node.json +8 -0
  136. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/vite.config.ts +17 -0
  137. package/skills/trtc/room-builder/templates/scenarios/medical-consultation//346/216/245/345/205/245/350/257/264/346/230/216.md +6 -0
  138. package/skills/trtc/room-builder/tools/render_ai_instructions.py +226 -0
  139. package/skills/trtc-apply/SKILL.md +97 -0
  140. package/skills/trtc-apply/guardrails/apply_lib/__init__.py +0 -0
  141. package/skills/trtc-apply/guardrails/apply_lib/__pycache__/__init__.cpython-313.pyc +0 -0
  142. package/skills/trtc-apply/guardrails/apply_lib/__pycache__/rule_parser.cpython-313.pyc +0 -0
  143. package/skills/trtc-apply/guardrails/apply_lib/rule_parser.py +268 -0
  144. package/skills/trtc-docs/SKILL.md +207 -0
  145. package/skills/trtc-onboarding/SKILL.md +839 -0
  146. package/skills/trtc-onboarding/reference/path-a1-demo.md +103 -0
  147. package/skills/trtc-onboarding/reference/path-a2-integrate.md +693 -0
  148. package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +115 -0
  149. package/skills/trtc-onboarding/reference/path-c-expand.md +43 -0
  150. package/skills/trtc-onboarding/reference/reporting-protocol.md +174 -0
  151. package/skills/trtc-onboarding/reference/supported-matrix.md +100 -0
  152. package/skills/trtc-onboarding/reference/usersig-handling.md +140 -0
  153. package/skills/trtc-search/SKILL.md +221 -0
  154. package/skills/trtc-topic/SKILL.md +638 -0
  155. package/skills/trtc-topic/guardrails/__pycache__/gate_slice_read.cpython-313.pyc +0 -0
  156. package/skills/trtc-topic/guardrails/__pycache__/gate_slice_write.cpython-313.pyc +0 -0
  157. package/skills/trtc-topic/guardrails/__pycache__/stop_require_apply_evidence.cpython-313.pyc +0 -0
  158. package/skills/trtc-topic/guardrails/gate_slice_read.py +133 -0
  159. package/skills/trtc-topic/guardrails/gate_slice_write.py +169 -0
  160. package/skills/trtc-topic/guardrails/stop_require_apply_evidence.py +97 -0
  161. package/skills/trtc-topic/references/execution-units.yaml +58 -0
  162. package/skills/trtc-topic/runtime/README.md +50 -0
  163. package/skills/trtc-topic/runtime/RUNTIME.md +128 -0
  164. package/skills/trtc-topic/runtime/lib/__init__.py +0 -0
  165. package/skills/trtc-topic/runtime/lib/platforms.py +194 -0
  166. package/skills/trtc-topic/runtime/package-lock.json +1211 -0
  167. package/skills/trtc-topic/runtime/package.json +13 -0
  168. package/skills/trtc-topic/runtime/telemetry-bridge.mjs +339 -0
  169. package/skills/trtc-topic/runtime/telemetry_collector.py +293 -0
  170. package/skills/trtc-topic/scripts/STATE-MACHINE-GUIDE.md +186 -0
  171. package/skills/trtc-topic/scripts/__pycache__/apply.cpython-313.pyc +0 -0
  172. package/skills/trtc-topic/scripts/apply.py +581 -0
  173. package/skills/trtc-topic/scripts/finalize_session.py +113 -0
  174. package/skills/trtc-topic/scripts/init_slice_queue.py +96 -0
  175. package/skills/trtc-topic/scripts/lib/__pycache__/state_machine.cpython-313.pyc +0 -0
  176. package/skills/trtc-topic/scripts/lib/state_machine.py +328 -0
  177. package/skills/trtc-topic/scripts/next_slice.py +137 -0
  178. package/skills/trtc-topic/tests/README.md +70 -0
  179. package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc +0 -0
  180. package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  181. package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.2.pyc +0 -0
  182. package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  183. package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.2.pyc +0 -0
  184. package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.3.pyc +0 -0
  185. package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.2.pyc +0 -0
  186. package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.3.pyc +0 -0
  187. package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.2.pyc +0 -0
  188. package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.3.pyc +0 -0
  189. package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.2.pyc +0 -0
  190. package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.3.pyc +0 -0
  191. package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.2.pyc +0 -0
  192. package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.3.pyc +0 -0
  193. package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.2.pyc +0 -0
  194. package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.3.pyc +0 -0
  195. package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.2.pyc +0 -0
  196. package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.3.pyc +0 -0
  197. package/skills/trtc-topic/tests/conftest.py +72 -0
  198. package/skills/trtc-topic/tests/test_apply_cli.py +480 -0
  199. package/skills/trtc-topic/tests/test_end_to_end.py +305 -0
  200. package/skills/trtc-topic/tests/test_finalize_session.py +51 -0
  201. package/skills/trtc-topic/tests/test_gates.py +316 -0
  202. package/skills/trtc-topic/tests/test_session_resolver.py +260 -0
  203. package/skills/trtc-topic/tests/test_state_machine.py +414 -0
  204. package/skills/trtc-topic/tests/test_stop_require_apply.py +99 -0
  205. package/skills/trtc-topic/tests/test_topic_skill_invariants.py +130 -0
@@ -0,0 +1,506 @@
1
+ ---
2
+ id: live/coguest-apply
3
+ platform: ios
4
+ api_docs:
5
+ - title: CoGuestStore
6
+ url: https://tencent-rtc.github.io/TUIKit_iOS/documentation/atomicxcore/cogueststore/
7
+ ---
8
+
9
+ # 观众申请连麦 — iOS 实现
10
+
11
+ ## 前置条件 [必填]
12
+
13
+ **通用依赖**:见 [login-auth 平台 slice](../login-auth.md)(SDK 安装、Info.plist 权限声明)
14
+
15
+ **额外依赖**:无
16
+
17
+ **前置状态**:
18
+ - `LoginStore.shared.isLogin == true`(登录成功)(→ live/login-auth)
19
+ - 已进入直播间,持有有效的 `liveID`(→ live/audience-watch)
20
+ - `CoGuestStore` 已通过 `create(liveID:)` 初始化
21
+
22
+ ## 代码示例 [必填]
23
+
24
+ ### 观众端:申请 → 等待 → 开设备 → 连麦 → 断开
25
+
26
+ ```swift
27
+ import AtomicXCore
28
+ import Combine
29
+
30
+ final class AudienceCoGuestViewModel: ObservableObject {
31
+
32
+ // MARK: 状态
33
+
34
+ enum CoGuestStatus {
35
+ case idle // 未连麦
36
+ case applying // 申请中
37
+ case connected // 连麦中
38
+ }
39
+
40
+ @Published var status: CoGuestStatus = .idle
41
+ @Published var errorMessage: String?
42
+
43
+ private let coGuestStore: CoGuestStore
44
+ private var cancellables = Set<AnyCancellable>()
45
+ private let applyTimeout: TimeInterval = 30
46
+
47
+ init(liveID: String) {
48
+ self.coGuestStore = CoGuestStore.create(liveID: liveID)
49
+ observeGuestEvents()
50
+ }
51
+
52
+ // MARK: - 观众端事件订阅
53
+
54
+ private func observeGuestEvents() {
55
+ coGuestStore.guestEventPublisher
56
+ .receive(on: DispatchQueue.main)
57
+ .sink { [weak self] event in
58
+ guard let self else { return }
59
+ switch event {
60
+ case .onGuestApplicationResponded(let isAccept, let hostUser):
61
+ if isAccept {
62
+ // ✅ 申请通过,立即开启设备
63
+ print("[CoGuest] 主播 \(hostUser.userName) 已同意申请")
64
+ self.openDevicesAfterAccepted()
65
+ } else {
66
+ // 申请被拒绝
67
+ self.status = .idle
68
+ self.errorMessage = "连麦申请被主播拒绝"
69
+ }
70
+
71
+ case .onGuestApplicationNoResponse(let reason):
72
+ // 超时未响应
73
+ self.status = .idle
74
+ self.errorMessage = "申请超时,请重试"
75
+ print("[CoGuest] 申请超时,原因: \(reason)")
76
+
77
+ case .onKickedOffSeat(let seatIndex, let hostUser):
78
+ // 被主播踢下麦位
79
+ self.closeDevicesAfterDisconnect()
80
+ self.status = .idle
81
+ self.errorMessage = "已被主播移出麦位(座位 \(seatIndex))"
82
+ print("[CoGuest] 被 \(hostUser.userName) 踢下麦位 \(seatIndex)")
83
+
84
+ case .onHostInvitationReceived(let hostUser):
85
+ // 收到主播邀请,可展示弹窗供用户选择
86
+ print("[CoGuest] 收到主播 \(hostUser.userName) 的邀请")
87
+
88
+ case .onHostInvitationCancelled(let hostUser):
89
+ // 主播取消邀请
90
+ print("[CoGuest] 主播 \(hostUser.userName) 取消了邀请")
91
+ }
92
+ }
93
+ .store(in: &cancellables)
94
+ }
95
+
96
+ // MARK: - 申请连麦
97
+ // seatIndex: Int 默认 -1 表示自动分配麦位
98
+
99
+ func applyForSeat(seatIndex: Int = -1) {
100
+ guard status == .idle else { return }
101
+ status = .applying
102
+
103
+ coGuestStore.applyForSeat(
104
+ seatIndex: seatIndex,
105
+ timeout: applyTimeout,
106
+ extraInfo: nil
107
+ ) { [weak self] result in
108
+ guard let self else { return }
109
+ DispatchQueue.main.async {
110
+ switch result {
111
+ case .success:
112
+ // 申请发送成功,等待主播响应(通过 guestEventPublisher 回调)
113
+ print("[CoGuest] 申请已发送,等待主播响应...")
114
+ case .failure(let error):
115
+ self.status = .idle
116
+ if error.code == -2340 {
117
+ self.errorMessage = "当前连麦人数已达上限,请稍后再试"
118
+ } else {
119
+ self.errorMessage = "申请失败:\(error.message)"
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ // MARK: - 取消申请
127
+
128
+ func cancelApplication() {
129
+ guard status == .applying else { return }
130
+ coGuestStore.cancelApplication { [weak self] _ in
131
+ DispatchQueue.main.async {
132
+ self?.status = .idle
133
+ }
134
+ }
135
+ }
136
+
137
+ // MARK: - 接受主播邀请(inviterID 为邀请方主播的 userID)
138
+
139
+ func acceptInvitation(inviterID: String) {
140
+ coGuestStore.acceptInvitation(inviterID: inviterID) { [weak self] result in
141
+ DispatchQueue.main.async {
142
+ switch result {
143
+ case .success:
144
+ self?.openDevicesAfterAccepted()
145
+ case .failure(let error):
146
+ self?.errorMessage = "接受邀请失败:\(error.message)"
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ // MARK: - 拒绝主播邀请
153
+
154
+ func rejectInvitation(inviterID: String) {
155
+ coGuestStore.rejectInvitation(inviterID: inviterID) { result in
156
+ if case .failure(let error) = result {
157
+ print("[CoGuest] 拒绝邀请失败 code=\(error.code)")
158
+ }
159
+ }
160
+ }
161
+
162
+ // MARK: - 申请通过后开设备
163
+
164
+ private func openDevicesAfterAccepted() {
165
+ // 前置:设备控制能力(→ live/device-control)
166
+ // 先开麦克风
167
+ DeviceStore.shared.openLocalMicrophone { [weak self] micResult in
168
+ guard let self else { return }
169
+ switch micResult {
170
+ case .failure(let error):
171
+ print("[CoGuest] 麦克风打开失败 code=\(error.code)")
172
+ self.errorMessage = "麦克风打开失败,请检查权限"
173
+ // 麦克风失败,断开连麦
174
+ self.coGuestStore.disConnect(completion: nil)
175
+ DispatchQueue.main.async { self.status = .idle }
176
+ case .success:
177
+ // 再开摄像头
178
+ DeviceStore.shared.openLocalCamera(isFront: true) { cameraResult in
179
+ DispatchQueue.main.async {
180
+ if case .failure(let error) = cameraResult {
181
+ print("[CoGuest] 摄像头打开失败 code=\(error.code),以纯音频模式连麦")
182
+ }
183
+ self.status = .connected
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ // MARK: - 主动断开连麦
191
+
192
+ func disconnect() {
193
+ guard status == .connected else { return }
194
+ coGuestStore.disConnect { [weak self] _ in
195
+ self?.closeDevicesAfterDisconnect()
196
+ DispatchQueue.main.async { self?.status = .idle }
197
+ }
198
+ }
199
+
200
+ // MARK: - 断开后关闭设备
201
+
202
+ private func closeDevicesAfterDisconnect() {
203
+ // 前置:设备控制能力(→ live/device-control)
204
+ DeviceStore.shared.closeLocalCamera()
205
+ DeviceStore.shared.closeLocalMicrophone()
206
+ print("[CoGuest] 连麦已断开,设备已关闭")
207
+ }
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ### 主播端:监听申请 → 同意 / 拒绝 → 邀请 → 管理连麦
214
+
215
+ ```swift
216
+ import AtomicXCore
217
+ import Combine
218
+
219
+ final class HostCoGuestViewModel: ObservableObject {
220
+
221
+ // MARK: 状态
222
+
223
+ @Published var pendingApplicants: [LiveUserInfo] = [] // 待审批申请列表
224
+ @Published var connectedGuests: [SeatUserInfo] = [] // 当前连麦列表
225
+
226
+ private let coGuestStore: CoGuestStore
227
+ private var cancellables = Set<AnyCancellable>()
228
+
229
+ init(liveID: String) {
230
+ self.coGuestStore = CoGuestStore.create(liveID: liveID)
231
+ observeHostEvents()
232
+ observeState()
233
+ }
234
+
235
+ // MARK: - 主播端事件订阅
236
+
237
+ private func observeHostEvents() {
238
+ coGuestStore.hostEventPublisher
239
+ .receive(on: DispatchQueue.main)
240
+ .sink { [weak self] event in
241
+ guard let self else { return }
242
+ switch event {
243
+ case .onGuestApplicationReceived(let guestUser):
244
+ // 新收到观众申请,添加到待审批列表
245
+ if !self.pendingApplicants.contains(where: { $0.userID == guestUser.userID }) {
246
+ self.pendingApplicants.append(guestUser)
247
+ }
248
+
249
+ case .onGuestApplicationCancelled(let guestUser):
250
+ // 观众撤回了申请
251
+ self.pendingApplicants.removeAll { $0.userID == guestUser.userID }
252
+
253
+ case .onGuestApplicationProcessedByOtherHost(let guestUser, let hostUser):
254
+ // 申请被其他主播处理(多主播场景)
255
+ self.pendingApplicants.removeAll { $0.userID == guestUser.userID }
256
+ print("[Host] \(guestUser.userName) 的申请已被 \(hostUser.userName) 处理")
257
+
258
+ case .onHostInvitationResponded(let isAccept, let guestUser):
259
+ if isAccept {
260
+ print("[Host] \(guestUser.userName) 接受了邀请")
261
+ } else {
262
+ print("[Host] \(guestUser.userName) 拒绝了邀请")
263
+ }
264
+
265
+ case .onHostInvitationNoResponse(let guestUser, let reason):
266
+ print("[Host] \(guestUser.userName) 未响应邀请,原因: \(reason)")
267
+ }
268
+ }
269
+ .store(in: &cancellables)
270
+ }
271
+
272
+ // MARK: - 状态订阅(实时同步连麦列表)
273
+
274
+ private func observeState() {
275
+ coGuestStore.state
276
+ .map(\.connected)
277
+ .receive(on: DispatchQueue.main)
278
+ .assign(to: &$connectedGuests)
279
+ }
280
+
281
+ // MARK: - 同意申请
282
+
283
+ func acceptApplication(userID: String) {
284
+ coGuestStore.acceptApplication(userID: userID) { [weak self] result in
285
+ DispatchQueue.main.async {
286
+ switch result {
287
+ case .success:
288
+ self?.pendingApplicants.removeAll { $0.userID == userID }
289
+ print("[Host] 已同意 \(userID) 的连麦申请")
290
+ case .failure(let error):
291
+ print("[Host] 同意申请失败 code=\(error.code) msg=\(error.message)")
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ // MARK: - 拒绝申请
298
+
299
+ func rejectApplication(userID: String) {
300
+ coGuestStore.rejectApplication(userID: userID) { [weak self] result in
301
+ DispatchQueue.main.async {
302
+ switch result {
303
+ case .success:
304
+ self?.pendingApplicants.removeAll { $0.userID == userID }
305
+ print("[Host] 已拒绝 \(userID) 的连麦申请")
306
+ case .failure(let error):
307
+ print("[Host] 拒绝申请失败 code=\(error.code) msg=\(error.message)")
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ // MARK: - 主播邀请观众上麦
314
+
315
+ func inviteToSeat(userID: String, seatIndex: Int = -1) {
316
+ coGuestStore.inviteToSeat(
317
+ userID: userID,
318
+ seatIndex: seatIndex,
319
+ timeout: 30,
320
+ extraInfo: nil
321
+ ) { result in
322
+ if case .failure(let error) = result {
323
+ print("[Host] 邀请失败 code=\(error.code) msg=\(error.message)")
324
+ }
325
+ }
326
+ }
327
+
328
+ // MARK: - 主播踢出已连麦观众
329
+
330
+ func disconnectGuest() {
331
+ coGuestStore.disConnect { result in
332
+ DispatchQueue.main.async {
333
+ if case .failure(let error) = result {
334
+ print("[Host] 断开失败 code=\(error.code) msg=\(error.message)")
335
+ }
336
+ }
337
+ }
338
+ }
339
+ }
340
+ ```
341
+
342
+ ## 调用时序 [条件必填:多角色异步交互 或 回调嵌套 ≥3 层]
343
+
344
+ ```
345
+ 【观众端】
346
+ 用户点击"申请连麦"
347
+
348
+
349
+ coGuestStore.applyForSeat(seatIndex: -1, timeout: 30, extraInfo: nil)
350
+
351
+ ├─ .failure(code: -2340) → 麦位满,提示用户
352
+ ├─ .failure(ErrorInfo) → 展示 error.message
353
+
354
+ └─ .success(申请发出,等待主播响应)
355
+
356
+ ▼(guestEventPublisher 回调)
357
+ .onGuestApplicationResponded(isAccept:hostUser:)
358
+
359
+ ├─ isAccept == false → 提示被拒,status = .idle
360
+
361
+ └─ isAccept == true
362
+
363
+
364
+ openLocalMicrophone()
365
+
366
+ ├─ .failure → disConnect,提示权限问题
367
+ └─ .success
368
+
369
+
370
+ openLocalCamera(isFront: true)
371
+ └─ status = .connected(连麦中)
372
+
373
+ .onKickedOffSeat(seatIndex:hostUser:) → closeDevices() → status = .idle
374
+ .onGuestApplicationNoResponse(reason:) → status = .idle,提示超时
375
+
376
+ 【主播端(并行)】
377
+ 订阅 hostEventPublisher
378
+
379
+
380
+ .onGuestApplicationReceived(guestUser:) → 加入待审批列表
381
+
382
+ ├─ 主播点击"同意" → acceptApplication(userID:)
383
+ │ └─ 从 pendingApplicants 移除
384
+ └─ 主播点击"拒绝" → rejectApplication(userID:)
385
+ └─ 从 pendingApplicants 移除
386
+
387
+ .onGuestApplicationCancelled(guestUser:) → 从待审批列表移除
388
+ ```
389
+
390
+ ## 平台特有注意事项 [必填:至少 1 条]
391
+
392
+ ### 1. seatIndex 参数
393
+ `applyForSeat` 包含 `seatIndex: Int` 参数(默认值 `-1`),`-1` 表示由系统自动分配麦位。若业务有固定麦位布局(如卡拉 OK 多人),可传具体的麦位索引(从 0 开始)。
394
+ ```swift
395
+ // ✅ 自动分配麦位
396
+ coGuestStore.applyForSeat(seatIndex: -1, timeout: 30, extraInfo: nil)
397
+
398
+ // ✅ 指定麦位(如卡拉 OK 场景,固定 6 个座位)
399
+ coGuestStore.applyForSeat(seatIndex: 2, timeout: 30, extraInfo: nil)
400
+ ```
401
+
402
+ ### 2. acceptInvitation / rejectInvitation 参数为 inviterID
403
+ 观众接受/拒绝主播邀请时,参数名为 `inviterID`(邀请方),不是 `userID`。不要与 `acceptApplication(userID:)` 混淆,两者语义不同。
404
+ ```swift
405
+ // ✅ 正确:传主播的 userID 作为 inviterID
406
+ coGuestStore.acceptInvitation(inviterID: hostUserID, completion: nil)
407
+
408
+ // ❌ 错误:传了观众自己的 userID,接受邀请永远失败
409
+ coGuestStore.acceptInvitation(inviterID: selfUserID, completion: nil)
410
+ ```
411
+
412
+ ### 3. Combine cancellable 生命周期管理
413
+ `hostEventPublisher` 和 `guestEventPublisher` 是 Combine Publisher。订阅时返回的 `AnyCancellable` 必须存储到 ViewModel/ViewController 的属性中(如 `Set<AnyCancellable>`),否则订阅会立即被释放,导致主播收不到任何申请事件。
414
+
415
+ ### 4. 连麦中 App 进入后台
416
+ iOS 系统进入后台时会挂起摄像头采集,但麦克风仍可持续(需在 `Info.plist` 开启 `audio` 后台模式)。连麦场景建议在 App 进入后台时关闭摄像头(`closeLocalCamera()`),避免观众看到定格画面。
417
+ ```swift
418
+ // ✅ 监听进入后台,主动关闭摄像头
419
+ NotificationCenter.default.addObserver(
420
+ forName: UIApplication.didEnterBackgroundNotification,
421
+ object: nil, queue: .main
422
+ ) { _ in
423
+ DeviceStore.shared.closeLocalCamera()
424
+ }
425
+
426
+ // ❌ 不处理后台切换,观众看到定格黑屏画面
427
+ ```
428
+
429
+ ### 5. `-2340` 麦位超限
430
+ 错误码 `-2340` 由服务端返回,表示当前直播间连麦人数已达上限。此时应禁用"申请连麦"按钮,并订阅 `CoGuestState.connected` 列表变化:当连麦人数减少时,自动重新启用按钮。
431
+ ```swift
432
+ // ✅ 区分 -2340 给出有意义的提示
433
+ case .failure(let error):
434
+ if error.code == -2340 {
435
+ self.errorMessage = "当前连麦人数已达上限,请稍后再试"
436
+ self.isApplyButtonDisabled = true
437
+ }
438
+
439
+ // ❌ 所有错误统一提示,用户不知道为什么失败
440
+ case .failure(let error):
441
+ self.errorMessage = "操作失败"
442
+ ```
443
+
444
+ ## 代码生成约束 [必填]
445
+
446
+ 本 section 供 AI 在生成/验证代码时使用,定义了此 slice 在 iOS 平台上的硬性约束。
447
+ 所有规则必须基于实际 SDK 行为核实后填入,不允许凭经验推测。
448
+
449
+ ### 编译必要条件 [必填]
450
+
451
+ - **通用条件**:见 [login-auth 平台 slice](login-auth.md)(pod 安装、Info.plist 权限)
452
+ - **额外导入**: `TODO: 列出本 slice 相对 login-auth 额外需要的 import(例如 Combine)`
453
+ - **最低平台版本**: `TODO: 若高于 login-auth 基线(iOS 13.0+),填入具体版本并说明原因`
454
+
455
+ ### 生成规则 [必填]
456
+
457
+ #### MUST(生成时必须包含)
458
+
459
+ <!-- TODO: 基于实际 SDK 行为填入本 slice 独有的 MUST 规则。
460
+ 每条格式:**规则** — 违反后果。**Verify**: 可检查手段
461
+ 规则必须来自 SDK 文档 / 实测 / 研发明确约定,不要凭经验推测。
462
+ -->
463
+
464
+ 1. `TODO: 规则 1` — `TODO: 违反后果`
465
+ **Verify**: `TODO: grep 正则 / 编译报错 / 运行时日志 / 人工观察`
466
+ 2. `TODO: 规则 2 ...`
467
+
468
+ #### MUST NOT(生成时绝不能出现)
469
+
470
+ <!-- TODO: 基于实际 SDK 行为填入本 slice 独有的 MUST NOT 规则。
471
+ 重点是「看起来能跑但逻辑错误」的写法 — 需要 SDK 语义支撑才能列出。
472
+ -->
473
+
474
+ 1. `TODO: 规则 1` — `TODO: 违反后果`
475
+ **Verify**: `TODO: 可检查手段`
476
+ 2. `TODO: 规则 2 ...`
477
+
478
+ ### 集成检查点 [必填]
479
+
480
+ <!-- TODO: 结合本 slice 在已有 iOS 项目中的集成经验填入,例如:
481
+ - 是否与项目已有的 SDK 初始化冲突
482
+ - 是否依赖其他 slice 的前置状态(引用具体 slice ID)
483
+ - 对已有代码的侵入性(新增文件 vs 修改已有文件)
484
+ -->
485
+
486
+ - `TODO: 集成检查点 1`
487
+ - `TODO: 集成检查点 2`
488
+
489
+ ## 验证矩阵 [必填]
490
+
491
+ AI 生成代码后或人工 review 时,自上而下跑一遍即可完成验收。
492
+
493
+ <!-- TODO: 基于本 slice 实际填好的 MUST / MUST NOT 规则,逐条映射到矩阵的层级 1/2。
494
+ 另外补充:
495
+ - 至少 1 条层级 3(运行时日志锚点),基于代码示例中实际的 print 语句填入
496
+ - 至少 1 条层级 4(业务行为),基于产品级 ALWAYS / NEVER 的运行时体现填入
497
+ -->
498
+
499
+ | 层级 | 检查项 | 验证手段 | 预期结果 |
500
+ |------|--------|----------|---------|
501
+ | 1. 编译级 | `TODO: 模块导入齐全` | `xcodebuild build -workspace {Project}.xcworkspace -scheme {Scheme} -destination 'platform=iOS Simulator,name=iPhone 16' -quiet` | exit code 0 |
502
+ | 1. 编译级 | `TODO: 最低平台版本达标` | 查 `IPHONEOS_DEPLOYMENT_TARGET` | `TODO: ≥ 版本` |
503
+ | 2. 静态规则级 | `TODO: 对应 MUST 规则 1` | `TODO: grep 正则` | `TODO: 匹配条件` |
504
+ | 2. 静态规则级 | `TODO: 对应 MUST NOT 规则 1` | `TODO: grep 正则` | `TODO: 不应匹配` |
505
+ | 3. 运行时级 | `TODO: 关键路径日志` | `TODO: 触发操作 → 查 Xcode 控制台` | `TODO: 日志内容` |
506
+ | 4. 业务行为级 | `TODO: 业务语义观察` | `TODO: 操作步骤` | `TODO: UI / 硬件状态` |