@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.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/README.zh.md +173 -0
- package/bin/cli.js +434 -0
- package/knowledge-base/index.yaml +454 -0
- package/knowledge-base/platform-slice-template.md +233 -0
- package/knowledge-base/scenario-spec.md +350 -0
- package/knowledge-base/scenarios/conference/base/general-conference.md +365 -0
- package/knowledge-base/scenarios/conference/base/webinar-conference.md +130 -0
- package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +145 -0
- package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +113 -0
- package/knowledge-base/scenarios/live/entertainment-live-room.md +118 -0
- package/knowledge-base/slice-spec.md +546 -0
- package/knowledge-base/slices/conference/web/ai-tools.md +225 -0
- package/knowledge-base/slices/conference/web/beauty-effects.md +188 -0
- package/knowledge-base/slices/conference/web/device-control.md +338 -0
- package/knowledge-base/slices/conference/web/login-auth.md +261 -0
- package/knowledge-base/slices/conference/web/network-quality.md +190 -0
- package/knowledge-base/slices/conference/web/official-roomkit-api.md +298 -0
- package/knowledge-base/slices/conference/web/official-roomkit-login-ui.md +246 -0
- package/knowledge-base/slices/conference/web/participant-list.md +238 -0
- package/knowledge-base/slices/conference/web/participant-management.md +718 -0
- package/knowledge-base/slices/conference/web/prejoin-check.md +293 -0
- package/knowledge-base/slices/conference/web/room-call.md +213 -0
- package/knowledge-base/slices/conference/web/room-chat.md +426 -0
- package/knowledge-base/slices/conference/web/room-lifecycle.md +534 -0
- package/knowledge-base/slices/conference/web/room-schedule.md +281 -0
- package/knowledge-base/slices/conference/web/screen-share.md +211 -0
- package/knowledge-base/slices/conference/web/video-layout.md +675 -0
- package/knowledge-base/slices/conference/web/virtual-background.md +197 -0
- package/knowledge-base/slices/conference/web/webinar-interaction.md +206 -0
- package/knowledge-base/slices/live/anchor-lifecycle.md +122 -0
- package/knowledge-base/slices/live/anchor-preview.md +90 -0
- package/knowledge-base/slices/live/anchor-room-config.md +104 -0
- package/knowledge-base/slices/live/audience-list.md +86 -0
- package/knowledge-base/slices/live/audience-manage.md +92 -0
- package/knowledge-base/slices/live/audience-watch.md +85 -0
- package/knowledge-base/slices/live/audio.md +116 -0
- package/knowledge-base/slices/live/barrage.md +88 -0
- package/knowledge-base/slices/live/beauty.md +99 -0
- package/knowledge-base/slices/live/coguest-apply.md +105 -0
- package/knowledge-base/slices/live/device-control.md +91 -0
- package/knowledge-base/slices/live/error-codes.md +167 -0
- package/knowledge-base/slices/live/gift.md +84 -0
- package/knowledge-base/slices/live/ios/.gitkeep +0 -0
- package/knowledge-base/slices/live/ios/anchor-lifecycle.md +313 -0
- package/knowledge-base/slices/live/ios/anchor-preview.md +228 -0
- package/knowledge-base/slices/live/ios/anchor-room-config.md +257 -0
- package/knowledge-base/slices/live/ios/audience-list.md +353 -0
- package/knowledge-base/slices/live/ios/audience-manage.md +381 -0
- package/knowledge-base/slices/live/ios/audience-watch.md +286 -0
- package/knowledge-base/slices/live/ios/audio.md +373 -0
- package/knowledge-base/slices/live/ios/barrage.md +285 -0
- package/knowledge-base/slices/live/ios/beauty.md +323 -0
- package/knowledge-base/slices/live/ios/coguest-apply.md +506 -0
- package/knowledge-base/slices/live/ios/device-control.md +286 -0
- package/knowledge-base/slices/live/ios/error-codes.md +270 -0
- package/knowledge-base/slices/live/ios/gift.md +315 -0
- package/knowledge-base/slices/live/ios/live-list.md +269 -0
- package/knowledge-base/slices/live/ios/login-auth.md +247 -0
- package/knowledge-base/slices/live/live-list.md +82 -0
- package/knowledge-base/slices/live/login-auth.md +78 -0
- package/package.json +34 -0
- package/skills/trtc/SKILL.md +326 -0
- package/skills/trtc/room-builder/SKILL.md +138 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +108 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/backend-contract.zh-CN.md +162 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/integration.zh-CN.md +154 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/docs/theme.zh-CN.md +78 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/index.html +12 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/package.json +28 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/postcss.config.js +5 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/App.vue +25 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/ConsultationManagePanel.vue +838 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LanguageSwitch.vue +102 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/LoadingSpinner.vue +6 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalAlert.vue +34 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalBusinessPanel.vue +148 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalButton.vue +49 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalConfirmDialog.vue +68 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalDataPanel.vue +196 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/MedicalRecordPanel.vue +270 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/components/PrescriptionPanel.vue +363 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/basic-info-config.ts +29 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/config/runtime-config.ts +12 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/env.d.ts +32 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +123 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +230 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +135 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +113 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +132 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +50 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/types.ts +77 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationChat.ts +97 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationDevices.ts +48 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationParticipants.ts +121 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/useConsultationPermissions.ts +25 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/features/consultation/utils.ts +70 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/en-US/index.ts +553 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/index.ts +25 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/medicalTranslate.ts +85 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/state.ts +49 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/i18n/zh-CN/index.ts +463 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/main.ts +12 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/appointments.ts +96 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/mock/users.ts +79 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/router/index.ts +63 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/index.ts +25 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/appointmentService.ts +77 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/authService.ts +38 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/launchContext.ts +31 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/integration/userService.ts +35 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/appointmentService.ts +43 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/authService.ts +33 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/mock/userService.ts +43 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/services/adapters/types.ts +135 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/shared/icons.ts +53 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/index.css +106 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/tailwind.css +3 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/styles/theme.css +209 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/auth.ts +50 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/format.ts +24 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/navigation.ts +12 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/utils/session.ts +28 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorConsultationView.vue +777 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/DoctorDashboardView.vue +678 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/LoginView.vue +441 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationFinishedView.vue +185 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientConsultationView.vue +1003 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientSelectDoctorView.vue +317 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/src/views/PatientWaitingView.vue +454 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.json +21 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/tsconfig.node.json +8 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/vite.config.ts +17 -0
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation//346/216/245/345/205/245/350/257/264/346/230/216.md +6 -0
- package/skills/trtc/room-builder/tools/render_ai_instructions.py +226 -0
- package/skills/trtc-apply/SKILL.md +97 -0
- package/skills/trtc-apply/guardrails/apply_lib/__init__.py +0 -0
- package/skills/trtc-apply/guardrails/apply_lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/trtc-apply/guardrails/apply_lib/__pycache__/rule_parser.cpython-313.pyc +0 -0
- package/skills/trtc-apply/guardrails/apply_lib/rule_parser.py +268 -0
- package/skills/trtc-docs/SKILL.md +207 -0
- package/skills/trtc-onboarding/SKILL.md +839 -0
- package/skills/trtc-onboarding/reference/path-a1-demo.md +103 -0
- package/skills/trtc-onboarding/reference/path-a2-integrate.md +693 -0
- package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +115 -0
- package/skills/trtc-onboarding/reference/path-c-expand.md +43 -0
- package/skills/trtc-onboarding/reference/reporting-protocol.md +174 -0
- package/skills/trtc-onboarding/reference/supported-matrix.md +100 -0
- package/skills/trtc-onboarding/reference/usersig-handling.md +140 -0
- package/skills/trtc-search/SKILL.md +221 -0
- package/skills/trtc-topic/SKILL.md +638 -0
- package/skills/trtc-topic/guardrails/__pycache__/gate_slice_read.cpython-313.pyc +0 -0
- package/skills/trtc-topic/guardrails/__pycache__/gate_slice_write.cpython-313.pyc +0 -0
- package/skills/trtc-topic/guardrails/__pycache__/stop_require_apply_evidence.cpython-313.pyc +0 -0
- package/skills/trtc-topic/guardrails/gate_slice_read.py +133 -0
- package/skills/trtc-topic/guardrails/gate_slice_write.py +169 -0
- package/skills/trtc-topic/guardrails/stop_require_apply_evidence.py +97 -0
- package/skills/trtc-topic/references/execution-units.yaml +58 -0
- package/skills/trtc-topic/runtime/README.md +50 -0
- package/skills/trtc-topic/runtime/RUNTIME.md +128 -0
- package/skills/trtc-topic/runtime/lib/__init__.py +0 -0
- package/skills/trtc-topic/runtime/lib/platforms.py +194 -0
- package/skills/trtc-topic/runtime/package-lock.json +1211 -0
- package/skills/trtc-topic/runtime/package.json +13 -0
- package/skills/trtc-topic/runtime/telemetry-bridge.mjs +339 -0
- package/skills/trtc-topic/runtime/telemetry_collector.py +293 -0
- package/skills/trtc-topic/scripts/STATE-MACHINE-GUIDE.md +186 -0
- package/skills/trtc-topic/scripts/__pycache__/apply.cpython-313.pyc +0 -0
- package/skills/trtc-topic/scripts/apply.py +581 -0
- package/skills/trtc-topic/scripts/finalize_session.py +113 -0
- package/skills/trtc-topic/scripts/init_slice_queue.py +96 -0
- package/skills/trtc-topic/scripts/lib/__pycache__/state_machine.cpython-313.pyc +0 -0
- package/skills/trtc-topic/scripts/lib/state_machine.py +328 -0
- package/skills/trtc-topic/scripts/next_slice.py +137 -0
- package/skills/trtc-topic/tests/README.md +70 -0
- package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_apply_cli.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_end_to_end.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_finalize_session.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_gates.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_session_resolver.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_state_machine.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_stop_require_apply.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-topic/tests/__pycache__/test_topic_skill_invariants.cpython-313-pytest-9.0.3.pyc +0 -0
- package/skills/trtc-topic/tests/conftest.py +72 -0
- package/skills/trtc-topic/tests/test_apply_cli.py +480 -0
- package/skills/trtc-topic/tests/test_end_to_end.py +305 -0
- package/skills/trtc-topic/tests/test_finalize_session.py +51 -0
- package/skills/trtc-topic/tests/test_gates.py +316 -0
- package/skills/trtc-topic/tests/test_session_resolver.py +260 -0
- package/skills/trtc-topic/tests/test_state_machine.py +414 -0
- package/skills/trtc-topic/tests/test_stop_require_apply.py +99 -0
- package/skills/trtc-topic/tests/test_topic_skill_invariants.py +130 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: live/device-control
|
|
3
|
+
platform: ios
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 设备管理 — iOS 实现
|
|
7
|
+
|
|
8
|
+
## 前置条件
|
|
9
|
+
|
|
10
|
+
**依赖安装(Podfile)**
|
|
11
|
+
```ruby
|
|
12
|
+
pod 'AtomicXCore', '~> 4.0'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Info.plist 权限声明**(两项均须配置,否则系统拒绝授权或 App 崩溃)
|
|
16
|
+
```xml
|
|
17
|
+
<key>NSCameraUsageDescription</key>
|
|
18
|
+
<string>需要访问摄像头以进行视频直播</string>
|
|
19
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
20
|
+
<string>需要访问麦克风以进行语音直播</string>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**前置状态**:
|
|
24
|
+
- `LoginStore.shared` 登录成功(登录成功后才可操作设备)
|
|
25
|
+
- 系统权限已授予(AVAuthorizationStatus == .authorized)
|
|
26
|
+
|
|
27
|
+
## API 调用(真实签名)
|
|
28
|
+
|
|
29
|
+
```swift
|
|
30
|
+
// ── 摄像头 ──────────────────────────────────────────────────────────
|
|
31
|
+
DeviceStore.shared.openLocalCamera(isFront: Bool, completion: CompletionClosure?)
|
|
32
|
+
DeviceStore.shared.closeLocalCamera()
|
|
33
|
+
DeviceStore.shared.switchCamera(isFront: Bool) // 不中断采集,切换前/后置
|
|
34
|
+
|
|
35
|
+
// ── 麦克风 ──────────────────────────────────────────────────────────
|
|
36
|
+
DeviceStore.shared.openLocalMicrophone(completion: CompletionClosure?)
|
|
37
|
+
DeviceStore.shared.closeLocalMicrophone()
|
|
38
|
+
|
|
39
|
+
// ── 音量控制 ─────────────────────────────────────────────────────────
|
|
40
|
+
DeviceStore.shared.setCaptureVolume(volume: Int) // 采集音量,range [0, 100]
|
|
41
|
+
DeviceStore.shared.setOutputVolume(_ volume: Int) // 播放音量,range [0, 100]
|
|
42
|
+
|
|
43
|
+
// ── 音频路由 ─────────────────────────────────────────────────────────
|
|
44
|
+
DeviceStore.shared.setAudioRoute(_ route: AudioRoute)
|
|
45
|
+
|
|
46
|
+
// ── 镜像与画质 ───────────────────────────────────────────────────────
|
|
47
|
+
DeviceStore.shared.switchMirror(mirrorType: MirrorType)
|
|
48
|
+
DeviceStore.shared.updateVideoQuality(_ quality: VideoQuality)
|
|
49
|
+
|
|
50
|
+
// ── 状态快照 ─────────────────────────────────────────────────────────
|
|
51
|
+
DeviceStore.shared.state // StatePublisher<DeviceState>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**DeviceState 关键字段**
|
|
55
|
+
```swift
|
|
56
|
+
struct DeviceState {
|
|
57
|
+
var microphoneStatus: DeviceStatus
|
|
58
|
+
var captureVolume: Int // range [0, 100] ⚠️ 不是 0-150
|
|
59
|
+
var outputVolume: Int // range [0, 100]
|
|
60
|
+
var cameraStatus: DeviceStatus
|
|
61
|
+
var isFrontCamera: Bool
|
|
62
|
+
var localMirrorType: MirrorType
|
|
63
|
+
var localVideoQuality: VideoQuality
|
|
64
|
+
var currentAudioRoute: AudioRoute
|
|
65
|
+
var networkInfo: NetworkInfo
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**通用回调类型**
|
|
70
|
+
```swift
|
|
71
|
+
typealias CompletionClosure = (Result<Void, ErrorInfo>) -> Void
|
|
72
|
+
|
|
73
|
+
struct ErrorInfo {
|
|
74
|
+
var code: Int
|
|
75
|
+
var message: String
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
| 参数 | 类型 | 说明 |
|
|
80
|
+
|------|------|------|
|
|
81
|
+
| `isFront` | `Bool` | `true` = 前置摄像头,`false` = 后置摄像头 |
|
|
82
|
+
| `completion` | `CompletionClosure?` | 异步回调 `(Result<Void, ErrorInfo>) -> Void`;`nil` 表示不关心结果 |
|
|
83
|
+
| `volume` | `Int` | 音量范围 **[0, 100]**(注意不是 0-150) |
|
|
84
|
+
|
|
85
|
+
## 代码示例
|
|
86
|
+
|
|
87
|
+
```swift
|
|
88
|
+
import AtomicXCore
|
|
89
|
+
import AVFoundation
|
|
90
|
+
|
|
91
|
+
// MARK: - 打开摄像头(含权限检查)
|
|
92
|
+
|
|
93
|
+
func openCamera(isFront: Bool = true) {
|
|
94
|
+
// 先检查系统权限
|
|
95
|
+
switch AVCaptureDevice.authorizationStatus(for: .video) {
|
|
96
|
+
case .authorized:
|
|
97
|
+
DeviceStore.shared.openLocalCamera(isFront: isFront) { result in
|
|
98
|
+
switch result {
|
|
99
|
+
case .success:
|
|
100
|
+
print("[Device] 摄像头打开成功, 前置: \(isFront)")
|
|
101
|
+
case .failure(let errorInfo):
|
|
102
|
+
print("[Device] 摄像头打开失败, code: \(errorInfo.code), msg: \(errorInfo.message)")
|
|
103
|
+
handleDeviceError(errorInfo)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
case .notDetermined:
|
|
107
|
+
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
108
|
+
DispatchQueue.main.async {
|
|
109
|
+
if granted { openCamera(isFront: isFront) }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
case .denied, .restricted:
|
|
113
|
+
guideToSettings(message: "请在「设置 > 隐私 > 摄像头」中开启权限")
|
|
114
|
+
@unknown default:
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// MARK: - 打开麦克风
|
|
120
|
+
|
|
121
|
+
func openMicrophone() {
|
|
122
|
+
DeviceStore.shared.openLocalMicrophone { result in
|
|
123
|
+
switch result {
|
|
124
|
+
case .success:
|
|
125
|
+
print("[Device] 麦克风打开成功")
|
|
126
|
+
case .failure(let errorInfo):
|
|
127
|
+
print("[Device] 麦克风打开失败, code: \(errorInfo.code)")
|
|
128
|
+
handleDeviceError(errorInfo)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// MARK: - 关闭设备
|
|
134
|
+
|
|
135
|
+
func closeAllDevices() {
|
|
136
|
+
DeviceStore.shared.closeLocalCamera()
|
|
137
|
+
DeviceStore.shared.closeLocalMicrophone()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// MARK: - 切换前后摄像头(不中断推流)
|
|
141
|
+
|
|
142
|
+
func flipCamera(toFront: Bool) {
|
|
143
|
+
DeviceStore.shared.switchCamera(isFront: toFront)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// MARK: - 音量调节(range [0, 100])
|
|
147
|
+
|
|
148
|
+
func setCaptureVolume(_ volume: Int) {
|
|
149
|
+
// ⚠️ 范围是 [0, 100],不是 [0, 150]
|
|
150
|
+
let clamped = min(max(volume, 0), 100)
|
|
151
|
+
DeviceStore.shared.setCaptureVolume(volume: clamped)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
func setOutputVolume(_ volume: Int) {
|
|
155
|
+
let clamped = min(max(volume, 0), 100)
|
|
156
|
+
DeviceStore.shared.setOutputVolume(clamped)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// MARK: - 音频路由切换(扬声器 / 听筒)
|
|
160
|
+
|
|
161
|
+
func switchToSpeaker() {
|
|
162
|
+
DeviceStore.shared.setAudioRoute(.speaker)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
func switchToEarpiece() {
|
|
166
|
+
DeviceStore.shared.setAudioRoute(.earpiece)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// MARK: - 错误处理
|
|
170
|
+
|
|
171
|
+
func handleDeviceError(_ errorInfo: ErrorInfo) {
|
|
172
|
+
switch errorInfo.code {
|
|
173
|
+
case -1101:
|
|
174
|
+
guideToSettings(message: "摄像头权限被拒,请前往系统设置开启")
|
|
175
|
+
case -1102:
|
|
176
|
+
print("摄像头被其他应用占用,请关闭后重试")
|
|
177
|
+
case -1103:
|
|
178
|
+
print("当前设备不支持摄像头(请使用真机测试)")
|
|
179
|
+
case -1105:
|
|
180
|
+
guideToSettings(message: "麦克风权限被拒,请前往系统设置开启")
|
|
181
|
+
case -1106:
|
|
182
|
+
print("麦克风被其他应用占用,请结束通话后重试")
|
|
183
|
+
default:
|
|
184
|
+
print("设备错误 code: \(errorInfo.code), msg: \(errorInfo.message)")
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func guideToSettings(message: String) {
|
|
189
|
+
DispatchQueue.main.async {
|
|
190
|
+
// 引导用户跳转到系统设置(业务自行实现 Alert UI)
|
|
191
|
+
if let url = URL(string: UIApplication.openSettingsURLString) {
|
|
192
|
+
UIApplication.shared.open(url)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**主播开播:同时打开摄像头和麦克风**:
|
|
199
|
+
```swift
|
|
200
|
+
func openAllDevicesForAnchor() {
|
|
201
|
+
openCamera(isFront: true) // 先打开摄像头
|
|
202
|
+
openMicrophone() // 再打开麦克风
|
|
203
|
+
// 注意:如需串行等待,请在 openCamera 的 completion .success 分支内调用 openMicrophone
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 主播下播:关闭所有设备
|
|
207
|
+
func closeAllDevicesOnStop() {
|
|
208
|
+
DeviceStore.shared.closeLocalCamera()
|
|
209
|
+
DeviceStore.shared.closeLocalMicrophone()
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## 调用时序
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
权限检查
|
|
217
|
+
│
|
|
218
|
+
├─ 未确定(notDetermined)
|
|
219
|
+
│ └─ 系统弹窗 → 用户授权/拒绝
|
|
220
|
+
│
|
|
221
|
+
├─ 已拒绝(denied)
|
|
222
|
+
│ └─ 展示引导弹窗 → 跳转系统设置
|
|
223
|
+
│
|
|
224
|
+
└─ 已授权(authorized)
|
|
225
|
+
│
|
|
226
|
+
▼
|
|
227
|
+
DeviceStore.shared.openLocalCamera(isFront: true, completion:)
|
|
228
|
+
│
|
|
229
|
+
├─ .failure(errorInfo)
|
|
230
|
+
│ ├─ code -1100 → 重试或上报
|
|
231
|
+
│ ├─ code -1101 → 系统授权异常(引导去设置)
|
|
232
|
+
│ ├─ code -1102 → 提示关闭其他应用
|
|
233
|
+
│ └─ code -1103 → 模拟器,提示换真机
|
|
234
|
+
│
|
|
235
|
+
└─ .success
|
|
236
|
+
│
|
|
237
|
+
▼
|
|
238
|
+
DeviceStore.shared.openLocalMicrophone(completion:)
|
|
239
|
+
│
|
|
240
|
+
├─ .failure(errorInfo) → 同上处理
|
|
241
|
+
│
|
|
242
|
+
└─ .success
|
|
243
|
+
│
|
|
244
|
+
▼
|
|
245
|
+
设备就绪,进行推流/预览
|
|
246
|
+
│
|
|
247
|
+
[使用中]
|
|
248
|
+
switchCamera(isFront:) ← 切换前后置(无黑屏)
|
|
249
|
+
setCaptureVolume(volume:) ← 调节采集音量 [0, 100]
|
|
250
|
+
setAudioRoute(.speaker) ← 切换音频路由
|
|
251
|
+
│
|
|
252
|
+
▼
|
|
253
|
+
DeviceStore.shared.closeLocalCamera()
|
|
254
|
+
DeviceStore.shared.closeLocalMicrophone()
|
|
255
|
+
(下播 / 退房 / App 进入后台)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## 平台特有注意事项
|
|
259
|
+
|
|
260
|
+
### 1. captureVolume 范围是 [0, 100],不是 [0, 150]
|
|
261
|
+
`setCaptureVolume(volume:)` 和 `setOutputVolume(_:)` 的有效范围均为 **[0, 100]**。传入超过 100 的值会被 SDK 忽略或截断。不要将范围设为 0-150。
|
|
262
|
+
|
|
263
|
+
### 2. iOS 权限弹窗时机
|
|
264
|
+
系统权限弹窗**只会弹出一次**(首次请求时)。若用户拒绝后,后续调用 `requestAccess` 不再弹窗,必须引导用户手动前往系统设置开启。建议在开播前明确告知用户权限用途,提高授权通过率。
|
|
265
|
+
|
|
266
|
+
### 3. 后台摄像头自动关闭
|
|
267
|
+
iOS 系统在 App 进入后台时会**自动挂起摄像头采集**。主播场景中若需要后台推流,须在 `Info.plist` 中开启后台音视频模式:
|
|
268
|
+
```xml
|
|
269
|
+
<key>UIBackgroundModes</key>
|
|
270
|
+
<array>
|
|
271
|
+
<string>audio</string>
|
|
272
|
+
</array>
|
|
273
|
+
```
|
|
274
|
+
即使开启后台模式,摄像头视频帧在后台仍会停止推送,建议切后台时向观众提示"主播暂时离开"。
|
|
275
|
+
|
|
276
|
+
### 4. 摄像头被其他进程占用(-1102)
|
|
277
|
+
iOS 系统级应用(如 FaceTime、系统相机)在前台时会独占摄像头。当 App 从后台切回前台并重新打开摄像头时,若系统摄像头仍被其他应用持有,会触发 `-1102`。解决方案:监听 `UIApplication.didBecomeActiveNotification`,延迟 0.5~1 秒后重试打开摄像头。
|
|
278
|
+
|
|
279
|
+
### 5. ErrorInfo 不是 Swift Error
|
|
280
|
+
回调中的 `errorInfo` 是 `ErrorInfo` 结构体,直接用 `.code` 和 `.message` 访问,不可当作 `Error` 使用:
|
|
281
|
+
```swift
|
|
282
|
+
case .failure(let errorInfo):
|
|
283
|
+
let code = errorInfo.code // ✅
|
|
284
|
+
let msg = errorInfo.message // ✅
|
|
285
|
+
// errorInfo as? Error // ❌ 不适用
|
|
286
|
+
```
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: live/error-codes
|
|
3
|
+
platform: ios
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 错误码参考 — iOS 实现
|
|
7
|
+
|
|
8
|
+
## 前置条件
|
|
9
|
+
|
|
10
|
+
AtomicXCore SDK 所有异步接口均通过 Swift `Result<T, ErrorInfo>` 回调返回错误。
|
|
11
|
+
|
|
12
|
+
**错误类型是 `ErrorInfo` 结构体,不是 Swift `Error` 协议**:
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
struct ErrorInfo {
|
|
16
|
+
var code: Int // 错误码(负数:客户端;正数:服务端)
|
|
17
|
+
var message: String // 错误描述
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 所有异步回调的统一签名
|
|
21
|
+
typealias CompletionClosure = (Result<Void, ErrorInfo>) -> Void
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## API 调用(错误码提取方式)
|
|
25
|
+
|
|
26
|
+
```swift
|
|
27
|
+
// ✅ 正确:直接从 ErrorInfo 结构体取字段
|
|
28
|
+
LoginStore.shared.login(sdkAppID: 1400000001,
|
|
29
|
+
userID: "user_001",
|
|
30
|
+
userSig: userSig) { result in
|
|
31
|
+
switch result {
|
|
32
|
+
case .success:
|
|
33
|
+
break
|
|
34
|
+
case .failure(let errorInfo): // errorInfo: ErrorInfo(不是 Error)
|
|
35
|
+
let code = errorInfo.code
|
|
36
|
+
let msg = errorInfo.message
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ✅ 返回 LiveInfo 的回调(LiveInfoCompletionClosure)
|
|
41
|
+
// typealias LiveInfoCompletionClosure = (Result<LiveInfo, ErrorInfo>) -> Void
|
|
42
|
+
LiveListStore.shared.joinLive(liveID: "room_001") { result in
|
|
43
|
+
switch result {
|
|
44
|
+
case .success(let liveInfo): // liveInfo: LiveInfo
|
|
45
|
+
print("直播间名称: \(liveInfo.liveName)")
|
|
46
|
+
case .failure(let errorInfo):
|
|
47
|
+
print("进房失败, code: \(errorInfo.code)")
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 代码示例
|
|
53
|
+
|
|
54
|
+
```swift
|
|
55
|
+
import AtomicXCore
|
|
56
|
+
|
|
57
|
+
// MARK: - 统一错误处理器
|
|
58
|
+
|
|
59
|
+
final class ErrorHandler {
|
|
60
|
+
|
|
61
|
+
// MARK: - 从 ErrorInfo 提取信息
|
|
62
|
+
|
|
63
|
+
/// 直接从 ErrorInfo 取 code 和 message
|
|
64
|
+
static func log(_ errorInfo: ErrorInfo, context: String = #function) {
|
|
65
|
+
print("[ErrorHandler] [\(context)] code=\(errorInfo.code), msg=\(errorInfo.message)")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// MARK: - 分类处理(按 errorInfo.code 分支)
|
|
69
|
+
|
|
70
|
+
static func handle(_ errorInfo: ErrorInfo,
|
|
71
|
+
context: String = #function,
|
|
72
|
+
retryHandler: (() -> Void)? = nil) {
|
|
73
|
+
log(errorInfo, context: context)
|
|
74
|
+
|
|
75
|
+
switch errorInfo.code {
|
|
76
|
+
// ── 通用错误 ──────────────────────────────────────────
|
|
77
|
+
case -1000:
|
|
78
|
+
showAlert(title: "配置错误", message: "SDKAppID 不合法,请检查控制台配置")
|
|
79
|
+
case -1001:
|
|
80
|
+
showAlert(title: "参数错误", message: "UserSig 已过期或参数不合法,请重新获取")
|
|
81
|
+
case -1002:
|
|
82
|
+
showAlert(title: "未登录", message: "请先完成登录后再进行操作")
|
|
83
|
+
case -1003:
|
|
84
|
+
guideToSystemPermissionSettings()
|
|
85
|
+
|
|
86
|
+
// ── 限频(可重试)────────────────────────────────────
|
|
87
|
+
case -2:
|
|
88
|
+
retryWithBackoff(handler: retryHandler)
|
|
89
|
+
|
|
90
|
+
// ── 设备错误 ──────────────────────────────────────────
|
|
91
|
+
case -1101:
|
|
92
|
+
guideToSystemPermissionSettings(permissionType: .camera)
|
|
93
|
+
case -1102:
|
|
94
|
+
showAlert(title: "摄像头占用", message: "请关闭其他正在使用摄像头的应用后重试")
|
|
95
|
+
case -1103:
|
|
96
|
+
showAlert(title: "无摄像头", message: "当前设备不支持摄像头,请使用真机测试")
|
|
97
|
+
case -1105:
|
|
98
|
+
guideToSystemPermissionSettings(permissionType: .microphone)
|
|
99
|
+
case -1106:
|
|
100
|
+
showAlert(title: "麦克风占用", message: "请结束通话或关闭其他语音应用后重试")
|
|
101
|
+
case -1100, -1104:
|
|
102
|
+
showAlert(title: "设备错误",
|
|
103
|
+
message: "设备打开失败(code: \(errorInfo.code)),请重启应用后重试")
|
|
104
|
+
|
|
105
|
+
// ── 房间错误 ──────────────────────────────────────────
|
|
106
|
+
case -2101:
|
|
107
|
+
showAlert(title: "操作错误", message: "请先进入房间再执行此操作")
|
|
108
|
+
case -2105:
|
|
109
|
+
showAlert(title: "参数错误", message: "直播间 ID 格式非法(须为 ASCII,≤ 48 字节)")
|
|
110
|
+
case -2107:
|
|
111
|
+
showAlert(title: "参数错误", message: "直播间名称非法(UTF-8,≤ 30 字节)")
|
|
112
|
+
case -2108:
|
|
113
|
+
showAlert(title: "已在房间内", message: "您已在其他房间中,请先退出后再加入新房间")
|
|
114
|
+
|
|
115
|
+
// ── 权限/信令错误 ─────────────────────────────────────
|
|
116
|
+
case -2380:
|
|
117
|
+
showAlert(title: "全员禁言", message: "当前房间已开启全员禁言,请等待房主解除")
|
|
118
|
+
case -2381:
|
|
119
|
+
showAlert(title: "被禁言", message: "您已被禁言,请联系房主申请解除")
|
|
120
|
+
|
|
121
|
+
// ── 服务端错误(可重试)──────────────────────────────
|
|
122
|
+
case 100001:
|
|
123
|
+
retryWithBackoff(handler: retryHandler)
|
|
124
|
+
|
|
125
|
+
// ── 未知错误 ──────────────────────────────────────────
|
|
126
|
+
default:
|
|
127
|
+
showAlert(title: "未知错误", message: "错误码:\(errorInfo.code)\n\(errorInfo.message)")
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// MARK: - UI 工具方法
|
|
132
|
+
|
|
133
|
+
private static func showAlert(title: String, message: String) {
|
|
134
|
+
DispatchQueue.main.async {
|
|
135
|
+
guard let topVC = UIApplication.shared.topViewController else { return }
|
|
136
|
+
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
|
137
|
+
alert.addAction(UIAlertAction(title: "确定", style: .default))
|
|
138
|
+
topVC.present(alert, animated: true)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
enum PermissionType { case camera, microphone }
|
|
143
|
+
|
|
144
|
+
static func guideToSystemPermissionSettings(permissionType: PermissionType? = nil) {
|
|
145
|
+
let message: String
|
|
146
|
+
switch permissionType {
|
|
147
|
+
case .camera:
|
|
148
|
+
message = "请前往「设置 > 隐私与安全 > 摄像头」开启权限"
|
|
149
|
+
case .microphone:
|
|
150
|
+
message = "请前往「设置 > 隐私与安全 > 麦克风」开启权限"
|
|
151
|
+
case nil:
|
|
152
|
+
message = "请前往系统设置开启所需权限"
|
|
153
|
+
}
|
|
154
|
+
DispatchQueue.main.async {
|
|
155
|
+
guard let topVC = UIApplication.shared.topViewController else { return }
|
|
156
|
+
let alert = UIAlertController(title: "权限不足", message: message, preferredStyle: .alert)
|
|
157
|
+
alert.addAction(UIAlertAction(title: "去设置", style: .default) { _ in
|
|
158
|
+
if let url = URL(string: UIApplication.openSettingsURLString) {
|
|
159
|
+
UIApplication.shared.open(url)
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
|
163
|
+
topVC.present(alert, animated: true)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private static var retryCount = 0
|
|
168
|
+
private static let maxRetries = 3
|
|
169
|
+
|
|
170
|
+
private static func retryWithBackoff(handler: (() -> Void)?) {
|
|
171
|
+
guard let handler = handler, retryCount < maxRetries else {
|
|
172
|
+
retryCount = 0
|
|
173
|
+
showAlert(title: "请求失败", message: "多次重试后仍然失败,请稍后再试")
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
let delay = pow(2.0, Double(retryCount))
|
|
177
|
+
retryCount += 1
|
|
178
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
|
179
|
+
handler()
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// MARK: - UIApplication 顶层 ViewController 扩展
|
|
185
|
+
|
|
186
|
+
extension UIApplication {
|
|
187
|
+
var topViewController: UIViewController? {
|
|
188
|
+
var topVC = connectedScenes
|
|
189
|
+
.compactMap { $0 as? UIWindowScene }
|
|
190
|
+
.flatMap { $0.windows }
|
|
191
|
+
.first { $0.isKeyWindow }?
|
|
192
|
+
.rootViewController
|
|
193
|
+
while let presented = topVC?.presentedViewController {
|
|
194
|
+
topVC = presented
|
|
195
|
+
}
|
|
196
|
+
return topVC
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**调用示例**:
|
|
202
|
+
```swift
|
|
203
|
+
// 登录错误处理
|
|
204
|
+
LoginStore.shared.login(sdkAppID: 1400000001,
|
|
205
|
+
userID: "user_001",
|
|
206
|
+
userSig: userSig) { result in
|
|
207
|
+
switch result {
|
|
208
|
+
case .success:
|
|
209
|
+
print("登录成功")
|
|
210
|
+
case .failure(let errorInfo):
|
|
211
|
+
ErrorHandler.handle(errorInfo, context: "login")
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 设备错误处理(含重试)
|
|
216
|
+
DeviceStore.shared.openLocalCamera(isFront: true) { result in
|
|
217
|
+
switch result {
|
|
218
|
+
case .success:
|
|
219
|
+
print("摄像头打开成功")
|
|
220
|
+
case .failure(let errorInfo):
|
|
221
|
+
ErrorHandler.handle(errorInfo, context: "openLocalCamera") {
|
|
222
|
+
// 重试逻辑
|
|
223
|
+
DeviceStore.shared.openLocalCamera(isFront: true, completion: nil)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## iOS 特有:权限错误与系统权限对应
|
|
230
|
+
|
|
231
|
+
iOS 系统权限与 SDK 错误码的对应关系:
|
|
232
|
+
|
|
233
|
+
| SDK 错误码 | AVFoundation 权限状态 | 系统设置路径 | 处理方式 |
|
|
234
|
+
|------------|----------------------|-------------|---------|
|
|
235
|
+
| `-1101` | `AVAuthorizationStatus.denied`(摄像头) | 设置 > 隐私与安全 > 摄像头 | 弹窗引导跳转 `UIApplication.openSettingsURLString` |
|
|
236
|
+
| `-1105` | `AVAuthorizationStatus.denied`(麦克风) | 设置 > 隐私与安全 > 麦克风 | 弹窗引导跳转 `UIApplication.openSettingsURLString` |
|
|
237
|
+
| `-1003` | 系统级权限被拒 | 设置 > 隐私与安全 | 通用权限引导 |
|
|
238
|
+
|
|
239
|
+
**权限状态主动检测**(在调用 SDK 前预检,避免先触发错误码再处理):
|
|
240
|
+
|
|
241
|
+
```swift
|
|
242
|
+
import AVFoundation
|
|
243
|
+
|
|
244
|
+
func checkAndRequestCameraPermission(completion: @escaping (Bool) -> Void) {
|
|
245
|
+
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
246
|
+
switch status {
|
|
247
|
+
case .authorized:
|
|
248
|
+
completion(true)
|
|
249
|
+
case .notDetermined:
|
|
250
|
+
// 首次询问 — 系统弹窗只弹一次
|
|
251
|
+
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
252
|
+
DispatchQueue.main.async { completion(granted) }
|
|
253
|
+
}
|
|
254
|
+
case .denied, .restricted:
|
|
255
|
+
// 已拒绝 — 引导前往系统设置(requestAccess 不再弹窗)
|
|
256
|
+
DispatchQueue.main.async {
|
|
257
|
+
ErrorHandler.guideToSystemPermissionSettings(permissionType: .camera)
|
|
258
|
+
completion(false)
|
|
259
|
+
}
|
|
260
|
+
@unknown default:
|
|
261
|
+
completion(false)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**注意事项**:
|
|
267
|
+
- `Result<Void, ErrorInfo>` 的失败分支中,`errorInfo` 是 `ErrorInfo` 结构体,**不是** `Swift.Error`,无法调用 `localizedDescription`。
|
|
268
|
+
- `AVCaptureDevice.requestAccess` 只触发**一次**系统弹窗。用户拒绝后,再次调用不会弹窗,需手动引导前往设置。
|
|
269
|
+
- iOS 模拟器不支持摄像头,`AVAuthorizationStatus` 始终返回 `.authorized` 但打开后会收到 `-1103`,请在真机上测试设备功能。
|
|
270
|
+
- 权限状态缓存在 App 沙盒中,卸载重装后会重置。测试时需注意。
|