# ZQVRViewer_TRTC 集成说明

---

## 1. 开发环境要求

| 要求项 | 版本 |
|----|------|
| **Swift** | **Swift 5.0+** |
| **iOS版本** | **iOS 12.0+** |

---

## 2. 需要引入的 Framework 与 Xcode 配置

须**同时**集成以下两个包（`TXLiteAVSDK_TRTC` 不内嵌在 `ZQVRViewer_TRTC` 内，须由你的 App 自己链入**与厂商验证过的同版本**）：

- `ZQVRViewer_TRTC.framework` — 带看封装（WKWebView 桥、RTC 抽象等）
- `TXLiteAVSDK_TRTC.framework` — 腾讯云 TRTC 原生库

**配置步骤：**

1. 将两个 `.framework` 拷贝到工程内目录，并加入 **Framework Search Paths**。  
2. **Target** → **Build Phases → Link Binary With Libraries**：添加 **上述两个** framework。  
3. **Target** → **Embed Frameworks**（或 **General → Frameworks, Libraries, and Embedded Content**）：二者均选 **Embed & Sign**。  
4. **Other Linker Flags** 中加入：  
   - `-ObjC`  
   - `-lz`、`-lresolv`  
   - `-lc++`（OC项目需要，Swift项目不需要）

---

## 3. 隐私与能力

**麦克风权限申请：**

- 在 `Info.plist` 中设置 **`NSMicrophoneUsageDescription`**，并填写**面向用户的说明文案**（不可为空，否则影响审核与首次采集权限弹窗）。 

---

## 4. 集成代码示例

**时序建议**：先 `openViewer` 再 `load` H5；离开页或不需要带看时 `destroyViewer()`。重复进入建议先 `destroy` 再绑新 WebView，避免重复注册同名 `WKScriptMessageHandler`。

### Swift

声明承载带看页的 `UIViewController` 并实现 `ZQVRViewerStateDelegate`（**协议方法均为 `optional`，只实现业务关心的即可**；回调均在**主线程**）：

```swift
import UIKit
import WebKit
import ZQVRViewer_TRTC

final class MyTourViewController: UIViewController, ZQVRViewerStateDelegate {

    // MARK: - ZQVRViewerStateDelegate
    func onEnterRoom(_ result: Int) {
        // 成功时多为非负；若 H5 enterRoom 参数不合法，SDK 不真正进房，result 为 -900001
    }
    func onError(_ errCode: Int, errMsg: String, extInfo: [String: Any]?) { }
    func onExitVR() {
        ZQVRViewerManager.shared.destroyViewer() // 示例：H5 通知退出 VR 后在此释放
    }
    // 其他：onExitRoom、onRemoteUserEnterRoom:、onFirstAudioFrame:、onNetworkQuality:quality: 等按需补全
}
```

`WKWebView` 的创建、绑定与加载（在适当时机调用，例如子视图布局完成后）：

```swift
// 1. 对 WKWebView 的要求（须满足后再 openViewer + load）：
//    - 用 WKWebViewConfiguration 创建；开启 JavaScript，并视需要打开 `javaScriptCanOpenWindowsAutomatically`。
//    - SDK 会在 `configuration.userContentController` 上注册名为 `VRViewer` 的 handler；勿重复注册同名。
//    - 再次进入带看时：宜先对上一会话 `destroyViewer`；`openViewer` 内部也会先清理已绑定状态。

let config = WKWebViewConfiguration()
config.preferences.javaScriptEnabled = true
config.preferences.javaScriptCanOpenWindowsAutomatically = true
let webView = WKWebView(frame: .zero, configuration: config)

// 2. 绑定与加载（须先 openViewer 再 load；`self` 须为已实现 `ZQVRViewerStateDelegate` 的页面实例）
ZQVRViewerManager.shared.viewerStateDelegate = self
ZQVRViewerManager.shared.openViewer(webView)
webView.load(URLRequest(url: tourURL))

// 3. 结束带看
ZQVRViewerManager.shared.destroyViewer()
```

### Objective-C

`WKWebView` 的约束与 Swift 示例相同；`UIViewController` 的 **`<ZQVRViewerStateDelegate>`** 与若干回调示例：

```objc
#import <WebKit/WebKit.h>
#import <ZQVRViewer_TRTC/ZQVRViewer_TRTC-Swift.h>

@interface MyTourViewController () <ZQVRViewerStateDelegate>
@end

@implementation MyTourViewController

- (void)onEnterRoom:(NSInteger)result {
    // 成功时多为非负；参数不合法时 result 为 -900001
}
- (void)onError:(NSInteger)errCode errMsg:(NSString *)errMsg extInfo:(NSDictionary<NSString *,id> *)extInfo { }
- (void)onExitVR {
    [[ZQVRViewerManager shared] destroyViewer];
}
// 其他可选：onExitRoom:、onRemoteUserEnterRoom:、onFirstAudioFrame:、onNetworkQuality:quality: 等

@end
```

`WKWebView` 的创建与绑定（须先 `openViewer` 再 `load`；`self` 为实现了 `<ZQVRViewerStateDelegate>` 的页面实例）：

```objc
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.preferences.javaScriptEnabled = YES;
config.preferences.javaScriptCanOpenWindowsAutomatically = YES;
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];

[ZQVRViewerManager shared].viewerStateDelegate = self;
[[ZQVRViewerManager shared] openViewer:webView];
[webView loadRequest:request];

// 结束
[[ZQVRViewerManager shared] destroyViewer];
```

---

## 5. 带看 H5 的 Web 链接与查询参数

`ZQVRViewerManager` 只管 **WebView 桥接与 TRTC**；**带看页要加载的 URL 由宿主自己拼好再 `load`**。页面上具体需要哪些**路径/查询串**，由**业务 H5 与后台**约定。

**做法概要**

1. 确定带看 H5 的**链接地址** 
2. 在 **query string** 上追加/覆盖业务方要求的参数。  
3. 对 **非 HTTPS** 的地址，需在 `Info.plist` 里配好 **App Transport Security**，否则 `load` 会失败。  
4. 拼好 `URL` / `NSURL` 后，在 **`openViewer` 之后**执行 `loadRequest`。

**链接参数**

| 参数名 | 类型 | 是否必须 | 描述 |
|--------|------|----------|------|
| `packageId` | String | **是** | 房源ID |
| `userId` | String | **是** | 用户 ID |
| `callerUserId` | String | **是** | 发起者 ID |
| `imPlatform` | String（`app` \| `web` \| `mp`） | **是** | 平台，SDK传入 `app` |
| `meetingId` | Number 或 String | 接收方**必须**，发起方不带 | 带看会议 ID |
| `roomId` | Number 或 String | 接收方**必须**，发起方不带 | 带看房间 ID |
| `useVrTakeSee` | Number（0、1） | 否 | 是否开启带看功能 |
| `showTakeSeeButton` | Number（0、1）| 否 | 是否显示带看发起按钮 |
| `startTour` | Number（0、1） | 否 | 是否立即进入带看 |
| `pattern` | String（`callout`） | 否 | callout表示呼叫模式，会触发服务端呼叫 |
| `noAudioCall` | Number（0、1） | 否 | 无语音模式 |
| `ext` | String（`JSON.stringify()`、String） | 否 | 用户自定义参数；呼叫模式下，由发起方传给接收方的信息 |

**调试参数（生产务必移除）**

- **`takeSeeTestMode=1`**：测试模式，发起带看时 Web 展示测试用接听端 URL 二维码；**仅供临时验证**，测试/生产环境都应移除。  
- **`vconsole=1`**：便于查看 WebView 控制台日志；**仅调试**，生产移除。

---

## 6. `ZQVRViewer_TRTC` 主要 API 说明

对外入口为 **`ZQVRViewerManager`**（`@objcMembers`），在 ObjC 中类名为 `ZQVRViewerManager`。

### 6.1 `ZQVRViewerManager`

| API | 说明 |
|-----|------|
| `shared` | 单例。SDK 内维护 WebView、RTC、消息桥，一般全局一个实例。 |
| `viewerStateDelegate: (weak) id<ZQVRViewerStateDelegate>` | 业务层状态回调；不设置时仍会驱动 RTC 与 H5 回调，但**不会**通知到 App。 |
| `openViewer(_ webView: WKWebView)` | 绑定 WebView、初始化 RTC、注册 H5 脚本通道（名称为 `VRViewer`，见下）。**重复调用**时会先按 `destroyViewer` 等效清理再绑定。 |
| `destroyViewer()` | 停止本地音频、退房、移除桥接、释放 RTC 与 WebView 引用。与 `openViewer` 配对。 |
| `setAudioCaptureVolume(_ volume: Int)` | 设置**采集**（上行）音量；**未** `openViewer` 时无效。 |
| `setAudioPlayoutVolume(_ volume: Int)` | 设置**播放**（下行）音量；**未** `openViewer` 时无效。 |


### 6.2 `ZQVRViewerStateDelegate`

`ZQVRViewerStateDelegate` 的回调均在 **主线程** 执行。协议方法均为 **可选**，仅实现需要的即可。

| 方法 | 说明 |
|------|------|
| `onEnterRoom(_ result: Int)` | 进房结果通知：成功时为非负的进房时长（毫秒）；失败时为负的错误码 |
| `onExitRoom(_ reason: Int)` | 已退出 RTC 房间 |
| `onError(_ errCode: Int, errMsg: String, extInfo: [String: Any]?)` | SDK/RTC 错误 |
| `onRemoteUserEnterRoom(_ userId: String)` | 远端用户进入同一房间。userId为远端用户id |
| `onRemoteUserLeaveRoom(_ userId: String, reason: Int)` | 远端用户离开房间。userId为远端用户id。reason为里房原因：0表示用户主动退出房间，1表示用户超时退出，2表示被踢出房间 |
| `onFirstAudioFrame(_ userId: String)` | 收到某用户的**首帧音频**。userId为远端用户id |
| `onNetworkQuality(_ userId: String, quality: Int)` |网络质量监控。 userId 如果为空，代表是自己本地的网络质量，否则为远端用户id。 quality是网络质量：0表示未定义 1表示非常好 2表示比较好 3表示一般 4表示较差 5表示很差 6表示不满足RTC的最低要求 |
| `onExitVR()` | H5 侧通知结束VR场景；是否随后 `destroyViewer` 由宿主在回调里决定。 |

---

