@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,313 @@
1
+ ---
2
+ id: live/anchor-lifecycle
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
+ - `DeviceStore.shared.openLocalMicrophone` 已成功(麦克风就绪)
19
+ - `LiveInfo` 已配置完毕(通过 `init(seatTemplate:)` 初始化,liveID 必填)
20
+
21
+ ## API 调用(真实签名)
22
+
23
+ ```swift
24
+ // LiveCoreView 初始化:参数名是 viewType(不是 liveScene)
25
+ // CoreViewType 枚举:.pushView(推流/主播端)、.playView(拉流/观众端)
26
+ LiveCoreView(viewType: CoreViewType, frame: CGRect = .zero)
27
+
28
+ // 开播:第一参数无标签;completion 返回 LiveInfo
29
+ LiveListStore.shared.createLive(_ liveInfo: LiveInfo,
30
+ completion: LiveInfoCompletionClosure?)
31
+ // LiveInfoCompletionClosure = (Result<LiveInfo, ErrorInfo>) -> Void
32
+
33
+ // 结束直播:无 liveID 参数
34
+ LiveListStore.shared.endLive(completion: StopLiveCompletionClosure?)
35
+ // StopLiveCompletionClosure = ... // TODO: 待验证 — 确认实际签名
36
+
37
+ // 订阅直播列表事件(Combine)
38
+ LiveListStore.shared.liveListEventPublisher // PassthroughSubject<LiveListEvent, Never>
39
+ ```
40
+
41
+ **LiveListEvent 完整签名**
42
+ ```swift
43
+ enum LiveListEvent {
44
+ // 直播被动结束(服务端强制终止/超时)
45
+ case onLiveEnded(liveID: String, reason: LiveEndedReason, message: String)
46
+
47
+ // 被踢出直播间
48
+ case onKickedOutOfLive(liveID: String, reason: LiveKickedOutReason, message: String)
49
+ }
50
+ ```
51
+
52
+ **关键类型说明**
53
+ ```swift
54
+ // LiveInfo 初始化(必须传 seatTemplate)
55
+ var liveInfo = LiveInfo(seatTemplate: .videoDynamicGrid9Seats)
56
+ liveInfo.liveID = "your-live-id"
57
+ liveInfo.liveName = "直播间名称"
58
+
59
+ // 通用回调
60
+ typealias CompletionClosure = (Result<Void, ErrorInfo>) -> Void
61
+ typealias LiveInfoCompletionClosure = (Result<LiveInfo, ErrorInfo>) -> Void
62
+
63
+ struct ErrorInfo {
64
+ var code: Int
65
+ var message: String
66
+ }
67
+ ```
68
+
69
+ ## 代码示例
70
+
71
+ ```swift
72
+ import AtomicXCore
73
+ import Combine
74
+
75
+ var cancellables = Set<AnyCancellable>()
76
+ var isLiving = false
77
+
78
+ // MARK: - 开播
79
+
80
+ func startLive(liveID: String, liveName: String) {
81
+ // ⚠️ 必须用 init(seatTemplate:),不是 LiveInfo()
82
+ var liveInfo = LiveInfo(seatTemplate: .videoDynamicGrid9Seats)
83
+ liveInfo.liveID = liveID
84
+ liveInfo.liveName = liveName
85
+
86
+ // ⚠️ 第一参数无标签(unnamed first param)
87
+ LiveListStore.shared.createLive(liveInfo) { result in
88
+ switch result {
89
+ case .success(let createdLiveInfo):
90
+ // 成功时回调携带服务端确认的 LiveInfo(含 createTime 等字段)
91
+ isLiving = true
92
+ print("[Lifecycle] 开播成功, liveID: \(createdLiveInfo.liveID)")
93
+
94
+ case .failure(let errorInfo):
95
+ print("[Lifecycle] 开播失败, code: \(errorInfo.code), msg: \(errorInfo.message)")
96
+ handleCreateError(errorInfo)
97
+ }
98
+ }
99
+ }
100
+
101
+ // MARK: - 结束直播
102
+
103
+ func stopLive() {
104
+ guard isLiving else { return }
105
+ isLiving = false
106
+
107
+ // Step 1: 关闭设备(先关设备再结束房间)
108
+ DeviceStore.shared.closeLocalCamera()
109
+ DeviceStore.shared.closeLocalMicrophone()
110
+
111
+ // Step 2: 结束直播(无 liveID 参数,endLive 结束当前房间)
112
+ LiveListStore.shared.endLive { result in
113
+ switch result {
114
+ case .success:
115
+ print("[Lifecycle] 直播结束成功")
116
+ cleanupSubscriptions()
117
+
118
+ case .failure(let errorInfo):
119
+ // 即使失败也应清理本地资源
120
+ print("[Lifecycle] endLive 失败, code: \(errorInfo.code)")
121
+ cleanupSubscriptions()
122
+ }
123
+ }
124
+ }
125
+
126
+ // MARK: - 订阅被动结束事件(先订阅再开播,防止事件丢失)
127
+
128
+ func subscribeLiveEvents(currentLiveID: String) {
129
+ LiveListStore.shared.liveListEventPublisher
130
+ .receive(on: DispatchQueue.main)
131
+ .sink { event in
132
+ switch event {
133
+ // ⚠️ onLiveEnded 有三个关联值:liveID, reason, message
134
+ case .onLiveEnded(let liveID, let reason, let message)
135
+ where liveID == currentLiveID:
136
+ print("[Lifecycle] 直播被动结束, reason: \(reason), msg: \(message)")
137
+ handlePassiveEnd(reason: message)
138
+
139
+ // ⚠️ onKickedOutOfLive 有三个关联值:liveID, reason, message
140
+ case .onKickedOutOfLive(let liveID, let reason, let message)
141
+ where liveID == currentLiveID:
142
+ print("[Lifecycle] 被踢出直播间, reason: \(reason), msg: \(message)")
143
+ handleKickedOut(message: message)
144
+
145
+ default:
146
+ break
147
+ }
148
+ }
149
+ .store(in: &cancellables)
150
+ }
151
+
152
+ // MARK: - 被动结束处理
153
+
154
+ func handlePassiveEnd(reason: String) {
155
+ isLiving = false
156
+ DeviceStore.shared.closeLocalCamera()
157
+ DeviceStore.shared.closeLocalMicrophone()
158
+ cleanupSubscriptions()
159
+ print("[Lifecycle] 被动结束处理完成:\(reason)")
160
+ }
161
+
162
+ func handleKickedOut(message: String) {
163
+ isLiving = false
164
+ DeviceStore.shared.closeLocalCamera()
165
+ DeviceStore.shared.closeLocalMicrophone()
166
+ cleanupSubscriptions()
167
+ print("[Lifecycle] 被踢出处理完成:\(message)")
168
+ }
169
+
170
+ // MARK: - 错误处理
171
+
172
+ func handleCreateError(_ errorInfo: ErrorInfo) {
173
+ switch errorInfo.code {
174
+ case -2105: print("直播间 ID 格式非法")
175
+ case -2107: print("直播间名称非法(超长或含特殊字符)")
176
+ case -2108: print("您已在其他直播间,请先退出")
177
+ default: print("开播失败(code: \(errorInfo.code)): \(errorInfo.message)")
178
+ }
179
+ }
180
+
181
+ // MARK: - 清理
182
+
183
+ func cleanupSubscriptions() {
184
+ cancellables.removeAll()
185
+ }
186
+ ```
187
+
188
+ ## 调用时序
189
+
190
+ ```
191
+ 设备就绪(openLocalCamera + openLocalMicrophone 成功)
192
+
193
+
194
+ subscribeLiveEvents(currentLiveID:) ← ① 先订阅事件,防止 createLive 前丢失
195
+
196
+
197
+ 构建 LiveInfo(seatTemplate: .videoDynamicGrid9Seats)
198
+ 设置 liveID + liveName(+ 其他选填字段)
199
+
200
+
201
+ // 第一参数无标签
202
+ LiveListStore.shared.createLive(liveInfo) { result in ... }
203
+
204
+ ├─ .failure(errorInfo)
205
+ │ ├─ code -2105/-2107/-2108 → 展示错误,退出
206
+ │ └─ 其他 → 展示错误信息
207
+
208
+ └─ .success(createdLiveInfo)
209
+
210
+
211
+ isLiving = true
212
+ UI 更新为「🔴 直播中」
213
+
214
+ ┌───────────────────────────┐
215
+ │ 直播进行中 │
216
+ │ onLiveEnded →被动结束 │ liveID + reason + message
217
+ │ onKickedOutOfLive→踢出 │ liveID + reason + message
218
+ └───────────────────────────┘
219
+
220
+ [用户主动结束 / 被动结束]
221
+
222
+
223
+ 步骤1: DeviceStore.closeLocalCamera()
224
+ DeviceStore.closeLocalMicrophone()
225
+ 步骤2: LiveListStore.shared.endLive(completion:)
226
+
227
+ └─ .success / .failure
228
+
229
+
230
+ 步骤3: cancellables.removeAll() ← 取消事件订阅
231
+ 返回上层页面
232
+ ```
233
+
234
+ ## 平台特有注意事项
235
+
236
+ ### 1. createLive 第一参数无标签
237
+ ```swift
238
+ // ✅ 正确
239
+ LiveListStore.shared.createLive(liveInfo) { result in ... }
240
+
241
+ // ❌ 错误(带标签)
242
+ LiveListStore.shared.createLive(liveInfo: liveInfo) { ... }
243
+ ```
244
+
245
+ ### 2. endLive 无 liveID 参数
246
+ `endLive(completion:)` 结束的是 SDK 当前所在的直播间,不需要传 liveID:
247
+ ```swift
248
+ // ✅ 正确
249
+ LiveListStore.shared.endLive { result in ... }
250
+
251
+ // ❌ 错误(没有此参数)
252
+ LiveListStore.shared.endLive(liveID: liveID) { ... }
253
+ ```
254
+
255
+ ### 3. LiveListEvent 关联值有三个字段
256
+ `onLiveEnded` 和 `onKickedOutOfLive` 的关联值均为 `(liveID: String, reason: …, message: String)`,不是只有 `liveID`:
257
+ ```swift
258
+ // ✅ 正确
259
+ case .onLiveEnded(let liveID, let reason, let message):
260
+ // 使用 liveID 过滤当前直播间
261
+
262
+ // ❌ 错误(缺少 reason 和 message)
263
+ case .onLiveEnded(let liveID):
264
+ ```
265
+
266
+ ### 4. Combine 订阅生命周期管理
267
+ `liveListEventPublisher` 的 Combine 订阅存储在 `cancellables` 中。务必在退出直播间时调用 `cancellables.removeAll()`,否则:
268
+ - ViewController 被释放后订阅仍存活(内存泄漏)
269
+ - 后续事件可能触发已释放对象的回调(野指针)
270
+
271
+ ### 5. endLive 失败时的兜底处理
272
+ 网络异常可能导致 `endLive` 回调超时或失败。即使失败也应清理本地资源(关闭设备、取消订阅),并在下次启动时通过服务端状态检查直播间是否仍存在。
273
+
274
+ ### 6. 导航返回的拦截
275
+ 通过 `viewWillDisappear` + `isMovingFromParent` 检测用户通过导航返回键意外退出:
276
+ ```swift
277
+ override func viewWillDisappear(_ animated: Bool) {
278
+ super.viewWillDisappear(animated)
279
+ if isLiving && isMovingFromParent {
280
+ stopLive()
281
+ }
282
+ }
283
+ ```
284
+
285
+ ### 7. LiveCoreView 初始化参数名是 `viewType`(不是 `liveScene`)
286
+ `LiveCoreView` 的初始化方法参数名为 `viewType`,类型为 `CoreViewType` 枚举(`.pushView` 推流 / `.playView` 拉流):
287
+ ```swift
288
+ // ✅ 正确:参数名是 viewType
289
+ let liveCoreView = LiveCoreView(viewType: .pushView)
290
+
291
+ // ❌ 错误:没有 liveScene 参数
292
+ let liveCoreView = LiveCoreView(liveScene: .pushView)
293
+ ```
294
+
295
+ ### 8. StatePublisher 读取属性必须通过 `.value`
296
+ 所有 Store 的 `.state` 属性类型是 `StatePublisher<T>`,不是 `T` 本身。读取当前状态值必须通过 `.value`:
297
+ ```swift
298
+ // ✅ 正确:通过 .value 访问 state 属性
299
+ let count = audienceStore?.state.value.audienceCount ?? 0
300
+
301
+ // ❌ 错误:StatePublisher 上没有 audienceCount 成员
302
+ let count = audienceStore?.state.audienceCount ?? 0
303
+ ```
304
+ 同理适用于所有 StatePublisher 包装的状态结构体:`LiveListState`、`DeviceState`、`BarrageState` 等。
305
+
306
+ ### 9. MUST: 文件头必须 `import AtomicXCore`
307
+ 使用 LiveCoreView、LiveListStore、DeviceStore、LiveAudienceStore 等类型时,必须在文件顶部显式 import:
308
+ ```swift
309
+ import UIKit
310
+ import AtomicXCore // ← 必须,否则编译报 "cannot find type 'XXX' in scope"
311
+ import Combine // 如果使用 Publisher/AnyCancellable
312
+ ```
313
+ 不要将 import 写在注释中,必须是真正的 import 语句。
@@ -0,0 +1,228 @@
1
+ ---
2
+ id: live/anchor-preview
3
+ platform: ios
4
+ ---
5
+
6
+ # 主播预览 — iOS 实现
7
+
8
+ ## 前置条件
9
+
10
+ **依赖安装(Podfile)**
11
+ ```ruby
12
+ pod 'AtomicXCore', '~> 4.0'
13
+ ```
14
+
15
+ **Info.plist 权限声明**
16
+ ```xml
17
+ <key>NSCameraUsageDescription</key>
18
+ <string>需要访问摄像头以进行视频直播</string>
19
+ <key>NSMicrophoneUsageDescription</key>
20
+ <string>需要访问麦克风以进行语音直播</string>
21
+ ```
22
+
23
+ **前置状态**:
24
+ - `LoginStore.shared` 登录成功(须完成登录)
25
+ - 摄像头/麦克风系统权限已授予
26
+
27
+ ## API 调用(真实签名)
28
+
29
+ ```swift
30
+ // LiveCoreView 初始化:主播端用 .pushView(推流模式)
31
+ // ⚠️ 参数名是 viewType(不是 liveScene)
32
+ LiveCoreView(viewType: .pushView, frame: CGRect = .zero)
33
+
34
+ // 绑定直播间 ID(创建后必须立即调用,否则黑屏)
35
+ liveCoreView.setLiveID(_ liveID: String)
36
+
37
+ // 1. 打开前/后置摄像头(含 completion 回调)
38
+ DeviceStore.shared.openLocalCamera(isFront: Bool, completion: CompletionClosure?)
39
+ // CompletionClosure = (Result<Void, ErrorInfo>) -> Void
40
+
41
+ // 2. 打开麦克风
42
+ DeviceStore.shared.openLocalMicrophone(completion: CompletionClosure?)
43
+
44
+ // 3. 不中断采集情况下切换前后摄像头
45
+ DeviceStore.shared.switchCamera(isFront: Bool)
46
+
47
+ // 4. 退出预览时关闭设备
48
+ DeviceStore.shared.closeLocalCamera()
49
+ DeviceStore.shared.closeLocalMicrophone()
50
+ ```
51
+
52
+ | 参数 | 类型 | 说明 |
53
+ |------|------|------|
54
+ | `isFront` | `Bool` | `true` = 前置摄像头(默认),`false` = 后置 |
55
+ | `completion` | `CompletionClosure?` | `(Result<Void, ErrorInfo>) -> Void`;`nil` 表示不关心结果 |
56
+
57
+ ## 代码示例
58
+
59
+ ```swift
60
+ import UIKit
61
+ import AtomicXCore
62
+ import AVFoundation
63
+ import Combine
64
+
65
+ // MARK: - 主播预览核心逻辑(SDK API 使用为主)
66
+
67
+ // Step 1: 检查摄像头权限,通过后打开设备
68
+ func setupPreview() {
69
+ switch AVCaptureDevice.authorizationStatus(for: .video) {
70
+ case .authorized:
71
+ openCameraAndMic()
72
+ case .notDetermined:
73
+ AVCaptureDevice.requestAccess(for: .video) { granted in
74
+ DispatchQueue.main.async {
75
+ if granted { openCameraAndMic() }
76
+ }
77
+ }
78
+ case .denied, .restricted:
79
+ showPermissionAlert(for: .video)
80
+ @unknown default:
81
+ break
82
+ }
83
+ }
84
+
85
+ // Step 2: 先打开摄像头,成功后再打开麦克风
86
+ func openCameraAndMic() {
87
+ DeviceStore.shared.openLocalCamera(isFront: true) { result in
88
+ switch result {
89
+ case .success:
90
+ print("[Preview] 摄像头打开成功")
91
+ openMicrophone()
92
+
93
+ case .failure(let errorInfo):
94
+ // errorInfo: ErrorInfo(.code + .message)
95
+ print("[Preview] 摄像头打开失败, code: \(errorInfo.code), msg: \(errorInfo.message)")
96
+ handleDeviceError(errorInfo)
97
+ }
98
+ }
99
+ }
100
+
101
+ func openMicrophone() {
102
+ DeviceStore.shared.openLocalMicrophone { result in
103
+ switch result {
104
+ case .success:
105
+ print("[Preview] 麦克风打开成功,预览就绪")
106
+ case .failure(let errorInfo):
107
+ print("[Preview] 麦克风打开失败, code: \(errorInfo.code)")
108
+ handleDeviceError(errorInfo)
109
+ }
110
+ }
111
+ }
112
+
113
+ // Step 3: 退出预览时关闭设备
114
+ func teardownPreview() {
115
+ DeviceStore.shared.closeLocalCamera()
116
+ DeviceStore.shared.closeLocalMicrophone()
117
+ }
118
+
119
+ // Step 4: 不中断预览切换前后置摄像头
120
+ func flipCamera(toFront: Bool) {
121
+ DeviceStore.shared.switchCamera(isFront: toFront)
122
+ }
123
+
124
+ // MARK: - 错误处理(使用 ErrorInfo 的 .code 字段)
125
+
126
+ func handleDeviceError(_ errorInfo: ErrorInfo) {
127
+ let message: String
128
+ switch errorInfo.code {
129
+ case -1101: message = "摄像头权限被拒,请前往系统设置开启"
130
+ case -1102: message = "摄像头被其他应用占用,请关闭后重试"
131
+ case -1103: message = "当前设备不支持摄像头(请使用真机测试)"
132
+ case -1105: message = "麦克风权限被拒,请前往系统设置开启"
133
+ default: message = "设备打开失败(错误码 \(errorInfo.code)),请重试"
134
+ }
135
+ // 业务自行展示 Alert
136
+ print("[Preview] 设备错误:\(message)")
137
+ }
138
+
139
+ func showPermissionAlert(for mediaType: AVMediaType) {
140
+ let message = mediaType == .video
141
+ ? "请在「设置 > 隐私 > 摄像头」中允许本应用访问摄像头"
142
+ : "请在「设置 > 隐私 > 麦克风」中允许本应用访问麦克风"
143
+ // 业务自行展示 Alert + 跳转设置
144
+ if let url = URL(string: UIApplication.openSettingsURLString) {
145
+ UIApplication.shared.open(url)
146
+ }
147
+ }
148
+ ```
149
+
150
+ **App 生命周期处理(后台暂停/前台恢复)**:
151
+ ```swift
152
+ import Combine
153
+
154
+ var cancellables = Set<AnyCancellable>()
155
+
156
+ func observeAppLifecycle() {
157
+ NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
158
+ .sink { [weak self] _ in
159
+ // 进后台时释放设备,避免摄像头指示灯常亮
160
+ DeviceStore.shared.closeLocalCamera()
161
+ DeviceStore.shared.closeLocalMicrophone()
162
+ }
163
+ .store(in: &cancellables)
164
+
165
+ NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
166
+ .sink { [weak self] _ in
167
+ // 回到前台时重新打开(注意:需要重新检查权限)
168
+ openCameraAndMic()
169
+ }
170
+ .store(in: &cancellables)
171
+ }
172
+ ```
173
+
174
+ ## 调用时序
175
+
176
+ ```
177
+ 进入预览界面
178
+
179
+
180
+ checkCameraPermission()
181
+
182
+ ├─ denied → showPermissionAlert → 用户跳转系统设置
183
+
184
+ └─ authorized
185
+
186
+
187
+ DeviceStore.shared.openLocalCamera(isFront: true, completion:)
188
+
189
+ ├─ .failure(errorInfo) → handleDeviceError(显示错误提示)
190
+
191
+ └─ .success
192
+
193
+
194
+ DeviceStore.shared.openLocalMicrophone(completion:)
195
+
196
+ ├─ .failure(errorInfo) → handleDeviceError
197
+
198
+ └─ .success → 预览画面出现,等待用户点击「开始直播」
199
+
200
+ [用户点击开始直播]
201
+
202
+
203
+ 导航到房间配置页
204
+ 由配置页调用 createLive(参见 anchor-room-config)
205
+
206
+ 退出预览界面
207
+
208
+
209
+ DeviceStore.shared.closeLocalCamera()
210
+ DeviceStore.shared.closeLocalMicrophone() ← 退出预览时释放设备
211
+ ```
212
+
213
+ ## 平台特有注意事项
214
+
215
+ ### 1. openLocalCamera 有 completion 参数,不可忽略
216
+ `DeviceStore.shared.openLocalCamera(isFront: Bool, completion: CompletionClosure?)` 包含 completion 参数。必须在 `.success` 回调中再打开麦克风,避免设备尚未就绪时即执行后续逻辑。
217
+
218
+ ### 2. iOS 模拟器不支持摄像头
219
+ 所有摄像头相关功能必须在**真实设备**上测试。模拟器调用 `openLocalCamera` 会返回 code `-1103`。
220
+
221
+ ### 3. 进入后台时摄像头自动暂停
222
+ iOS 系统在 App 进入后台时会自动挂起摄像头采集。监听 `UIApplication.didEnterBackgroundNotification` 主动关闭设备,再在 `didBecomeActiveNotification` 时重新打开,避免 `-1102` 被占用错误。
223
+
224
+ ### 4. 前后摄像头切换(不中断预览)
225
+ 预览阶段可直接调用 `DeviceStore.shared.switchCamera(isFront:)` 切换前后摄像头,无需重新调用 `openLocalCamera`,画面切换无黑屏。
226
+
227
+ ### 5. ErrorInfo 不是 Swift Error
228
+ 设备回调的 `.failure` 分支中,错误对象是 `ErrorInfo` 结构体(`.code: Int` + `.message: String`),不是 `Swift.Error`,不可调用 `localizedDescription`。