@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,257 @@
1
+ ---
2
+ id: live/anchor-room-config
3
+ platform: ios
4
+ ---
5
+
6
+ # 直播间配置 — iOS 实现
7
+
8
+ ## 前置条件
9
+
10
+ **依赖安装(Podfile)**
11
+ ```ruby
12
+ pod 'AtomicXCore', '~> 4.0'
13
+ ```
14
+
15
+ **前置状态**:
16
+ - `LoginStore.shared` 登录成功(须完成登录)
17
+ - 摄像头/麦克风设备已打开(`DeviceStore.shared.openLocalCamera` 成功)
18
+
19
+ ## API 调用(真实签名)
20
+
21
+ ```swift
22
+ // 构建直播间配置
23
+ // ⚠️ LiveInfo 必须通过 init(seatTemplate:) 初始化,不是 LiveInfo()
24
+ var liveInfo = LiveInfo(seatTemplate: .videoDynamicGrid9Seats)
25
+ liveInfo.liveID = "your-live-id"
26
+ liveInfo.liveName = "直播间名称"
27
+ liveInfo.coverURL = "https://cdn.example.com/cover.jpg"
28
+ liveInfo.notice = "欢迎来到我的直播间"
29
+ liveInfo.isPublicVisible = true
30
+
31
+ // 创建直播间
32
+ // ⚠️ 第一参数无标签(unnamed),completion 返回 LiveInfo
33
+ LiveListStore.shared.createLive(_ liveInfo: LiveInfo,
34
+ completion: LiveInfoCompletionClosure?)
35
+ // LiveInfoCompletionClosure = (Result<LiveInfo, ErrorInfo>) -> Void
36
+
37
+ // 更新直播间元数据(仅房主/管理员可调用)
38
+ LiveListStore.shared.updateLiveMetaData(_ metaData: [String: String],
39
+ completion: CompletionClosure?)
40
+
41
+ // 更新直播间基础信息
42
+ LiveListStore.shared.updateLiveInfo(_ liveInfo: LiveInfo,
43
+ modifyFlag: LiveInfo.ModifyFlag,
44
+ completion: CompletionClosure?)
45
+ ```
46
+
47
+ **LiveInfo 初始化与关键字段**
48
+ ```swift
49
+ struct LiveInfo {
50
+ // ⚠️ 正确初始化方式:必须传 seatTemplate
51
+ init(seatTemplate: SeatLayoutTemplate)
52
+
53
+ var liveID: String
54
+ var liveName: String
55
+ var coverURL: String
56
+ var backgroundURL: String
57
+ var notice: String
58
+ var seatTemplate: SeatLayoutTemplate // 座位布局模板
59
+ var seatMode: TakeSeatMode // .apply 或 .free
60
+ var isPublicVisible: Bool
61
+ var isGiftEnabled: Bool
62
+ var isMessageDisable: Bool
63
+ var categoryList: [NSNumber]
64
+ var metaData: [String: String]
65
+ }
66
+
67
+ enum SeatLayoutTemplate {
68
+ case videoDynamicGrid9Seats // 9格视频动态布局(最常用)
69
+ case videoDynamicFloat7Seats // 7格视频浮动布局
70
+ case videoFixedGrid9Seats
71
+ case videoFixedFloat7Seats
72
+ case videoLandscape4Seats
73
+ case audioSalon(seatCount: Int)
74
+ case karaoke(seatCount: Int)
75
+ }
76
+
77
+ // createLive 的回调类型
78
+ typealias LiveInfoCompletionClosure = (Result<LiveInfo, ErrorInfo>) -> Void
79
+ ```
80
+
81
+ | 参数 | 类型 | 说明 |
82
+ |------|------|------|
83
+ | `liveInfo.seatTemplate` | `SeatLayoutTemplate` | 必须通过 `init(seatTemplate:)` 传入,不可省略 |
84
+ | `liveID` | `String` | 直播间唯一 ID;仅含 ASCII,长度 ≤ 48 字节 |
85
+ | `liveName` | `String` | 显示名称;UTF-8,长度 ≤ 30 字节(约 10 个汉字) |
86
+ | `metaData` | `[String: String]` | 键值对扩展信息;最多 10 key,单值 ≤ 2 KB,总 ≤ 16 KB |
87
+
88
+ ## 代码示例
89
+
90
+ ```swift
91
+ import AtomicXCore
92
+
93
+ // MARK: - 创建直播间
94
+
95
+ func createLive(liveID: String, liveName: String, coverURL: String) {
96
+ // ✅ 正确:使用 init(seatTemplate:) 初始化
97
+ var liveInfo = LiveInfo(seatTemplate: .videoDynamicGrid9Seats)
98
+ liveInfo.liveID = liveID
99
+ liveInfo.liveName = liveName.isEmpty ? "我的直播间" : liveName
100
+ liveInfo.coverURL = coverURL
101
+ liveInfo.isPublicVisible = true
102
+
103
+ // ⚠️ createLive 第一参数无标签,completion 返回 LiveInfo(不是 Void)
104
+ LiveListStore.shared.createLive(liveInfo) { result in
105
+ switch result {
106
+ case .success(let createdLiveInfo):
107
+ // 回调返回完整 LiveInfo(含服务端写入的字段,如 createTime)
108
+ print("[RoomConfig] 直播间创建成功, liveID: \(createdLiveInfo.liveID)")
109
+ print("[RoomConfig] 创建时间: \(createdLiveInfo.createTime)")
110
+ // 跳转直播中页面
111
+
112
+ case .failure(let errorInfo):
113
+ handleCreateLiveError(errorInfo)
114
+ }
115
+ }
116
+ }
117
+
118
+ // MARK: - 参数校验
119
+
120
+ func validateLiveName(_ name: String) -> Bool {
121
+ // liveName 限制:UTF-8 字节数 ≤ 30
122
+ let byteCount = name.utf8.count
123
+ guard byteCount <= 30 else {
124
+ print("[RoomConfig] 名称超过 30 字节(约 10 个汉字),当前 \(byteCount) 字节")
125
+ return false
126
+ }
127
+ return true
128
+ }
129
+
130
+ // MARK: - 更新直播间元数据(开播后动态更新)
131
+
132
+ func updateMetaData(_ updates: [String: String]) {
133
+ LiveListStore.shared.updateLiveMetaData(updates) { result in
134
+ switch result {
135
+ case .success:
136
+ print("[RoomConfig] MetaData 更新成功")
137
+ case .failure(let errorInfo):
138
+ if errorInfo.code == -2300 {
139
+ print("[RoomConfig] 权限不足,仅房主/管理员可更新 MetaData")
140
+ } else {
141
+ print("[RoomConfig] MetaData 更新失败, code: \(errorInfo.code)")
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ // MARK: - MetaData 校验(最多 10 key,单值 ≤ 2KB,总 ≤ 16KB)
148
+
149
+ func buildValidatedMetaData(_ raw: [String: String]) -> [String: String] {
150
+ var validated: [String: String] = [:]
151
+ let maxKeyCount = 10
152
+ let maxValueBytes = 2 * 1024 // 2 KB
153
+ let maxTotalBytes = 16 * 1024 // 16 KB
154
+ var totalBytes = 0
155
+
156
+ for (key, value) in raw.prefix(maxKeyCount) {
157
+ let valueBytes = value.utf8.count
158
+ guard valueBytes <= maxValueBytes else {
159
+ print("[RoomConfig] key '\(key)' 值超过 2KB,已跳过")
160
+ continue
161
+ }
162
+ guard totalBytes + valueBytes <= maxTotalBytes else {
163
+ print("[RoomConfig] MetaData 总大小超过 16KB,已停止添加")
164
+ break
165
+ }
166
+ validated[key] = value
167
+ totalBytes += valueBytes
168
+ }
169
+ return validated
170
+ }
171
+
172
+ // MARK: - 错误处理
173
+
174
+ func handleCreateLiveError(_ errorInfo: ErrorInfo) {
175
+ switch errorInfo.code {
176
+ case -2105: print("直播间 ID 格式非法(须为 ASCII,≤ 48 字节)")
177
+ case -2107: print("直播间名称非法(UTF-8,≤ 30 字节)")
178
+ case -2108: print("您已在其他直播间,请先退出后再试")
179
+ default: print("创建失败(code: \(errorInfo.code)): \(errorInfo.message)")
180
+ }
181
+ }
182
+ ```
183
+
184
+ ## 调用时序
185
+
186
+ ```
187
+ 设备已就绪(openLocalCamera + openLocalMicrophone 成功)
188
+
189
+
190
+ 构建 LiveInfo
191
+ // ⚠️ 必须:LiveInfo(seatTemplate: .videoDynamicGrid9Seats)
192
+ // 设置 liveID / liveName / coverURL 等字段
193
+
194
+
195
+ 客户端校验
196
+ ├── liveName UTF-8 字节数 ≤ 30?
197
+ ├── MetaData 单值 ≤ 2KB?
198
+ └── MetaData 总大小 ≤ 16KB?
199
+
200
+
201
+ // ⚠️ 第一参数无标签
202
+ LiveListStore.shared.createLive(liveInfo) { result in ... }
203
+
204
+ ├─ .failure(errorInfo)
205
+ │ ├─ code -2105 → liveID 格式错误
206
+ │ ├─ code -2107 → liveName 超长/非法
207
+ │ └─ code -2108 → 已在其他房间
208
+
209
+ └─ .success(createdLiveInfo) ← 包含服务端写入字段
210
+
211
+
212
+ 进入直播中状态
213
+ (监听 liveListEventPublisher 生命周期事件)
214
+ ```
215
+
216
+ ## 平台特有注意事项
217
+
218
+ ### 1. LiveInfo 必须用 `init(seatTemplate:)` 初始化
219
+ `LiveInfo` **没有无参初始化方法**(`LiveInfo()` 不正确)。必须传入 `SeatLayoutTemplate`:
220
+ ```swift
221
+ // ✅ 正确
222
+ var liveInfo = LiveInfo(seatTemplate: .videoDynamicGrid9Seats)
223
+
224
+ // ❌ 错误:LiveInfo() 无此初始化方法
225
+ var liveInfo = LiveInfo()
226
+ ```
227
+
228
+ ### 2. createLive 第一参数无标签
229
+ `createLive` 签名为 `createLive(_ liveInfo: LiveInfo, completion:)`,第一参数**无标签**:
230
+ ```swift
231
+ // ✅ 正确(无标签)
232
+ LiveListStore.shared.createLive(liveInfo) { result in ... }
233
+
234
+ // ❌ 错误(带标签)
235
+ LiveListStore.shared.createLive(liveInfo: liveInfo) { ... }
236
+ ```
237
+
238
+ ### 3. createLive completion 返回 LiveInfo,不是 Void
239
+ `LiveInfoCompletionClosure = (Result<LiveInfo, ErrorInfo>) -> Void`,成功时回调携带服务端确认后的 `LiveInfo`(含 `createTime` 等服务端字段),应使用该对象而非本地构建的对象:
240
+ ```swift
241
+ case .success(let createdLiveInfo): // ✅ 使用服务端返回的 LiveInfo
242
+ navigateToLiveRoom(liveInfo: createdLiveInfo)
243
+ ```
244
+
245
+ ### 4. liveName 字节数计算
246
+ Swift 中汉字使用 UTF-8 编码,每个汉字占 3 字节:
247
+ ```swift
248
+ let byteCount = liveName.utf8.count // ✅ 字节数(正确)
249
+ let charCount = liveName.count // ❌ 字符数不等于字节数
250
+ ```
251
+ 30 字节 ≈ 10 个汉字 = 30 个英文字母。
252
+
253
+ ### 5. liveID 生成建议
254
+ 建议在服务端生成 liveID 并下发:
255
+ - 仅含字母、数字、下划线、连字符
256
+ - 长度控制在 8~32 字节
257
+ - 示例:`live_10001_1711593600`
@@ -0,0 +1,353 @@
1
+ ---
2
+ id: live/audience-list
3
+ platform: ios
4
+ ---
5
+
6
+ # 观众列表 — iOS 实现
7
+
8
+ ## 前置条件
9
+
10
+ **依赖安装(Podfile)**
11
+ ```ruby
12
+ pod 'AtomicXCore', '~> 4.0'
13
+ ```
14
+
15
+ **最低系统要求**:iOS 13.0+,Xcode 14.0+
16
+
17
+ **前置登录与进房**:必须在 `LoginStore.shared.login` 成功且 `liveCoreView.joinLive` 成功后才可调用 `fetchAudienceList`。
18
+
19
+ ## API 调用
20
+
21
+ ```swift
22
+ // 创建观众列表模块(每个直播间独立实例)
23
+ let audienceStore = LiveAudienceStore.create(liveID: String)
24
+
25
+ // 拉取当前观众列表快照
26
+ audienceStore.fetchAudienceList(completion: CompletionClosure?)
27
+ // CompletionClosure = (Result<Void, ErrorInfo>) -> Void
28
+ // ErrorInfo: .code: Int, .message: String
29
+
30
+ // 订阅观众实时事件(Combine)
31
+ audienceStore.liveAudienceEventPublisher // PassthroughSubject<LiveAudienceEvent, Never>
32
+
33
+ // 订阅状态变化(包含 audienceList 和 audienceCount)
34
+ audienceStore.state // StatePublisher<LiveAudienceState>
35
+ ```
36
+
37
+ **LiveUserInfo 字段**
38
+
39
+ | 字段 | 类型 | 说明 |
40
+ |------|------|------|
41
+ | `userID` | `String` | 用户唯一 ID |
42
+ | `userName` | `String` | 显示名称 |
43
+ | `avatarURL` | `String` | 头像 URL |
44
+
45
+ **LiveAudienceState 字段**
46
+
47
+ | 字段 | 类型 | 说明 |
48
+ |------|------|------|
49
+ | `audienceList` | `[LiveUserInfo]` | 当前观众数组快照 |
50
+ | `audienceCount` | `UInt` | 观众总数(近似值,受频控影响) |
51
+ | `messageBannedUserList` | `[LiveUserInfo]` | 已被禁言的用户列表 |
52
+
53
+ **LiveAudienceEvent 枚举**
54
+
55
+ | 事件 | 说明 |
56
+ |------|------|
57
+ | `.onAudienceJoined(audience: LiveUserInfo)` | 有观众进入直播间 |
58
+ | `.onAudienceLeft(audience: LiveUserInfo)` | 有观众离开直播间 |
59
+ | `.onAudienceMessageDisabled(audience: LiveUserInfo, isDisable: Bool)` | 某观众禁言状态变更 |
60
+ | `.onOwnerJoined(owner: LiveUserInfo)` | 房主进入直播间(4.1.0+) |
61
+ | `.onOwnerLeft(owner: LiveUserInfo)` | 房主离开直播间(4.1.0+) |
62
+ | `.onAdminJoined(admin: LiveUserInfo)` | 管理员进入直播间(4.1.0+) |
63
+ | `.onAdminLeft(admin: LiveUserInfo)` | 管理员离开直播间(4.1.0+) |
64
+
65
+ > **MUST**: switch LiveAudienceEvent 时必须添加 `default` 分支,因为枚举可能在后续版本继续扩展。
66
+
67
+ ## 代码示例
68
+
69
+ ### 1. 创建、拉取列表与订阅事件
70
+
71
+ ```swift
72
+ import AtomicXCore
73
+ import Combine
74
+ import UIKit
75
+
76
+ final class AudienceListViewModel {
77
+
78
+ // MARK: - Properties
79
+
80
+ private(set) var audienceList: [LiveUserInfo] = []
81
+ private(set) var audienceCount: UInt = 0
82
+
83
+ private var audienceStore: LiveAudienceStore?
84
+ private var cancellables = Set<AnyCancellable>()
85
+
86
+ // UI 刷新回调(通知 ViewController)
87
+ var onListUpdated: (() -> Void)?
88
+ var onCountUpdated: ((UInt) -> Void)?
89
+
90
+ // MARK: - 初始化(进房成功后调用)
91
+
92
+ func setup(liveID: String) {
93
+ // Step 1: 创建 LiveAudienceStore 实例
94
+ audienceStore = LiveAudienceStore.create(liveID: liveID)
95
+
96
+ // Step 2: 订阅实时事件
97
+ subscribeEvents()
98
+
99
+ // Step 3: 拉取初始列表快照
100
+ fetchInitialList()
101
+ }
102
+
103
+ // MARK: - 销毁(退出直播间时调用)
104
+
105
+ func teardown() {
106
+ cancellables.removeAll()
107
+ audienceStore = nil
108
+ audienceList = []
109
+ audienceCount = 0
110
+ }
111
+
112
+ // MARK: - 订阅实时进离场事件
113
+
114
+ private func subscribeEvents() {
115
+ audienceStore?.liveAudienceEventPublisher
116
+ .receive(on: DispatchQueue.main)
117
+ .sink { [weak self] event in
118
+ guard let self = self else { return }
119
+ switch event {
120
+ case .onAudienceJoined(let audience):
121
+ self.handleAudienceJoined(audience)
122
+ case .onAudienceLeft(let audience):
123
+ self.handleAudienceLeft(audience)
124
+ case .onAudienceMessageDisabled(let audience, let isDisable):
125
+ // 禁言状态变更,可在此刷新 UI 标记
126
+ print("[AudienceList] 用户 \(audience.userID) 禁言状态变更: \(isDisable)")
127
+ default:
128
+ break // 兼容 4.1.0+ 新增 case(onOwnerJoined/Left, onAdminJoined/Left)
129
+ }
130
+ }
131
+ .store(in: &cancellables)
132
+ }
133
+
134
+ // MARK: - 拉取初始列表
135
+
136
+ private func fetchInitialList() {
137
+ audienceStore?.fetchAudienceList { [weak self] result in
138
+ DispatchQueue.main.async {
139
+ guard let self = self else { return }
140
+ switch result {
141
+ case .success:
142
+ // 从 state.value 中取最新快照(state 是 StatePublisher,必须通过 .value 访问)
143
+ self.audienceList = self.audienceStore?.state.value.audienceList ?? []
144
+ self.audienceCount = self.audienceStore?.state.value.audienceCount ?? 0
145
+ self.onListUpdated?()
146
+ self.onCountUpdated?(self.audienceCount)
147
+ case .failure(let error):
148
+ print("[AudienceList] fetchAudienceList failed code=\(error.code) msg=\(error.message)")
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ // MARK: - 事件处理
155
+
156
+ private func handleAudienceJoined(_ audience: LiveUserInfo) {
157
+ // 避免重复插入
158
+ guard !audienceList.contains(where: { $0.userID == audience.userID }) else { return }
159
+ audienceList.insert(audience, at: 0) // 新观众插入列表头部
160
+ audienceCount = audienceStore?.state.value.audienceCount ?? audienceCount + 1
161
+ onListUpdated?()
162
+ onCountUpdated?(audienceCount)
163
+ }
164
+
165
+ private func handleAudienceLeft(_ audience: LiveUserInfo) {
166
+ audienceList.removeAll { $0.userID == audience.userID }
167
+ audienceCount = audienceStore?.state.value.audienceCount ?? (audienceCount > 0 ? audienceCount - 1 : 0)
168
+ onListUpdated?()
169
+ onCountUpdated?(audienceCount)
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### 2. 单列 UICollectionView 展示
175
+
176
+ ```swift
177
+ import UIKit
178
+
179
+ final class AudienceListViewController: UIViewController {
180
+
181
+ private let viewModel = AudienceListViewModel()
182
+
183
+ // MARK: - UI
184
+
185
+ private lazy var countLabel: UILabel = {
186
+ let label = UILabel()
187
+ label.font = .systemFont(ofSize: 14, weight: .medium)
188
+ label.textColor = .white
189
+ return label
190
+ }()
191
+
192
+ private lazy var collectionView: UICollectionView = {
193
+ let layout = UICollectionViewFlowLayout()
194
+ layout.scrollDirection = .vertical
195
+ layout.itemSize = CGSize(width: UIScreen.main.bounds.width, height: 60)
196
+ layout.minimumLineSpacing = 0
197
+ let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
198
+ cv.register(AudienceCell.self, forCellWithReuseIdentifier: "AudienceCell")
199
+ cv.dataSource = self
200
+ cv.backgroundColor = .clear
201
+ return cv
202
+ }()
203
+
204
+ // MARK: - Setup
205
+
206
+ init(liveID: String) {
207
+ super.init(nibName: nil, bundle: nil)
208
+ viewModel.setup(liveID: liveID)
209
+ }
210
+
211
+ required init?(coder: NSCoder) { fatalError() }
212
+
213
+ override func viewDidLoad() {
214
+ super.viewDidLoad()
215
+ setupUI()
216
+ bindViewModel()
217
+ }
218
+
219
+ deinit {
220
+ viewModel.teardown()
221
+ }
222
+
223
+ private func setupUI() {
224
+ view.addSubview(countLabel)
225
+ view.addSubview(collectionView)
226
+ // 布局代码省略
227
+ }
228
+
229
+ private func bindViewModel() {
230
+ viewModel.onListUpdated = { [weak self] in
231
+ self?.collectionView.reloadData()
232
+ }
233
+ viewModel.onCountUpdated = { [weak self] count in
234
+ // ⚠️ 人数仅展示,加"约"字说明非精确值
235
+ self?.countLabel.text = "约 \(count) 人在看"
236
+ }
237
+ }
238
+ }
239
+
240
+ extension AudienceListViewController: UICollectionViewDataSource {
241
+ func collectionView(_ cv: UICollectionView,
242
+ numberOfItemsInSection section: Int) -> Int {
243
+ viewModel.audienceList.count
244
+ }
245
+
246
+ func collectionView(_ cv: UICollectionView,
247
+ cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
248
+ let cell = cv.dequeueReusableCell(withReuseIdentifier: "AudienceCell",
249
+ for: indexPath) as! AudienceCell
250
+ cell.configure(with: viewModel.audienceList[indexPath.item])
251
+ return cell
252
+ }
253
+ }
254
+
255
+ // MARK: - 双列布局(可选)
256
+
257
+ extension AudienceListViewController {
258
+ /// 切换为双列显示(适合大观众列表)
259
+ func switchToDoubleColumnLayout() {
260
+ guard let layout = collectionView.collectionViewLayout
261
+ as? UICollectionViewFlowLayout else { return }
262
+ let padding: CGFloat = 8
263
+ let itemWidth = (UIScreen.main.bounds.width - padding * 3) / 2
264
+ layout.itemSize = CGSize(width: itemWidth, height: 56)
265
+ layout.minimumInteritemSpacing = padding
266
+ layout.minimumLineSpacing = padding
267
+ layout.sectionInset = UIEdgeInsets(top: padding, left: padding,
268
+ bottom: padding, right: padding)
269
+ collectionView.reloadData()
270
+ }
271
+ }
272
+
273
+ // MARK: - Cell
274
+
275
+ final class AudienceCell: UICollectionViewCell {
276
+
277
+ private let avatarImageView = UIImageView()
278
+ private let nameLabel = UILabel()
279
+
280
+ override init(frame: CGRect) {
281
+ super.init(frame: frame)
282
+ setupUI()
283
+ }
284
+
285
+ required init?(coder: NSCoder) { fatalError() }
286
+
287
+ private func setupUI() {
288
+ avatarImageView.layer.cornerRadius = 20
289
+ avatarImageView.clipsToBounds = true
290
+ contentView.addSubview(avatarImageView)
291
+ contentView.addSubview(nameLabel)
292
+ // 布局代码省略
293
+ }
294
+
295
+ func configure(with user: LiveUserInfo) {
296
+ nameLabel.text = user.userName
297
+ // 加载头像(使用业务图片加载框架)
298
+ if let url = URL(string: user.avatarURL) {
299
+ // e.g. avatarImageView.sd_setImage(with: url)
300
+ _ = url // 占位
301
+ }
302
+ }
303
+ }
304
+ ```
305
+
306
+ ## 调用时序
307
+
308
+ ```
309
+ LoginStore.login 成功
310
+
311
+
312
+ liveCoreView.joinLive 成功
313
+
314
+
315
+ LiveAudienceStore.create(liveID:) ← 每个直播间独立实例
316
+
317
+
318
+ audienceStore.liveAudienceEventPublisher 订阅(PassthroughSubject)
319
+
320
+
321
+ audienceStore.fetchAudienceList ← 获取初始快照
322
+
323
+ ├─ .failure(ErrorInfo) → 检查登录态 / 进房状态(error.code / error.message)
324
+ └─ .success → 读取 state.audienceList → reloadData
325
+
326
+
327
+ 实时事件推送
328
+ ├─ .onAudienceJoined(audience:) → 插入列表头 → reloadData
329
+ ├─ .onAudienceLeft(audience:) → 移除列表项 → reloadData
330
+ └─ .onAudienceMessageDisabled(audience:isDisable:) → 更新禁言标记
331
+
332
+
333
+ 退出直播间
334
+ └─ audienceStore = nil(释放订阅与资源)
335
+ ```
336
+
337
+ ## 平台特有注意事项
338
+
339
+ ### 1. AnyCancellable 必须存储在实例变量中
340
+
341
+ 若将 `liveAudienceEventPublisher.sink` 返回的 `AnyCancellable` 存在局部变量而非 `Set<AnyCancellable>` 实例变量中,订阅会在函数返回后立即释放,导致后续所有事件静默丢弃。务必使用 `.store(in: &cancellables)` 持久化订阅。
342
+
343
+ ### 2. 主线程刷新 UI
344
+
345
+ `liveAudienceEventPublisher` 的事件可能在后台线程分发。始终使用 `.receive(on: DispatchQueue.main)` 切换到主线程再更新 `audienceList` 和 `collectionView`,否则会触发 `UIKit` 主线程访问警告或崩溃。
346
+
347
+ ### 3. audienceCount 类型为 UInt
348
+
349
+ `LiveAudienceState.audienceCount` 是 `UInt` 类型(非 `Int`)。UI 展示时建议显示"约 X 人在看",而非精确数字。由于频控(40条/秒),在万人以上直播间中 `audienceCount` 可能与实际值相差数百,不要用它做业务逻辑判断(如活动奖励门槛)。
350
+
351
+ ### 4. 离场延迟:90 秒心跳窗口
352
+
353
+ 观众因网络断开(非主动退出)时,系统需等待约 90 秒心跳超时后才会触发 `onAudienceLeft`。UI 上观众头像可能在对方已断网后仍显示数分钟,属正常现象,无需特殊处理。