# ZQVRViewer TRTC（Android）集成说明

---

## 1. 开发环境要求

| 要求项 | 说明 |
|--------|------|
| **JDK** | **1.8+** |
| **Android** | **minSdk 19**（`trtc-x5` 变体若随腾讯 TBS 要求为 **minSdk 20**） |
| **语言** | **Kotlin** 或 **Java**；SDK 为 Kotlin 编写 |
| **构建** | `compileSdk` 建议 **≥ 30** |

---

## 2. 需要引入的依赖与 Gradle 配置

须**同时**集成：

1. **带看SDK**：随交付包提供 **本地 AAR**，二选一引入——`vrviewer-sdk-trtc-webview-release.aar`（系统 WebView）或 `vrviewer-sdk-trtc-x5-release.aar`（腾讯 X5）。**不提供** Maven 等远程仓库坐标，请勿使用 `com.zhongqu.vrviewer:...` 形式的依赖声明。
2. **腾讯云 TRTC**（`LiteAVSDK_TRTC`）**未内嵌**在带看 SDK 中，须由 App **自行**从腾讯官方渠道引入。
3. 若选用 X5 变体，**TBS / X5 内核**亦须由 App **自行**按腾讯文档引入并完成初始化。

### 2.1 产物变体（二选一）

| 交付 AAR 文件名 | WebView 类型 | 说明 |
|-----------------|----------------|------|
| `vrviewer-sdk-trtc-webview-release.aar` | `android.webkit.WebView` | TRTC + **系统 WebView** |
| `vrviewer-sdk-trtc-x5-release.aar` | `com.tencent.smtt.sdk.WebView` | TRTC + **腾讯 X5 / TBS** |

- 带看 AAR **不内嵌** TRTC、TBS，也**不会**作为传递依赖引入二者；宿主须在 `dependencies` 中对 TRTC（及 X5 场景下的 TBS）**显式**声明。
- 使用 **`trtc-x5` 对应 AAR** 时，还须按腾讯文档引入 **TBS / tbssdk** 等，并完成内核初始化。

### 2.2 系统 WebView 集成示例（`trtc-webview`）

将 `vrviewer-sdk-trtc-webview-release.aar` 拷贝到应用模块目录（示例为 `app/libs/`，可按项目调整），在**该模块**的 `build.gradle` 中：

```gradle
repositories {
    google()
    mavenCentral()
    // 若 TRTC 需走腾讯云文档指定的仓库，按腾讯官方说明追加
}
dependencies {
    // 本地 AAR；路径相对于本文件所在模块（通常为 app/）
    implementation files("libs/vrviewer-sdk-trtc-webview-release.aar")
    implementation "com.tencent.liteav:LiteAVSDK_TRTC:12.9.0.19478"  // 与厂商验证版本对齐
    // SDK 为 Kotlin：Java 宿主须显式依赖 kotlin-stdlib，否则可能 ClassNotFoundException
    implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.24"
}
```

也可使用 `flatDir { dirs 'libs' }` + `implementation(name: 'vrviewer-sdk-trtc-webview-release', ext: 'aar')` 等等价写法；**勿**为带看 SDK 配置众趣远程 Maven 依赖。

### 2.3 X5 内核集成示例（`trtc-x5`）

将 `vrviewer-sdk-trtc-x5-release.aar` 放到与上节相同的 `libs/`（或自定目录）后：

```gradle
dependencies {
    implementation files("libs/vrviewer-sdk-trtc-x5-release.aar")
    implementation "com.tencent.liteav:LiteAVSDK_TRTC:12.9.0.19478"
    implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.24"
    // TBS：坐标与版本以腾讯 TBS 集成文档为准，例如：
    // implementation "com.tencent.tbs:tbssdk:54002-beta"
}
```

### 2.4 混淆（R8 / ProGuard）

带看SDK自带 **`consumer-rules.pro`**，宿主开启混淆时会自动合并，保留对外稳定 API（如 `ZQVRViewerManager`、`ZQViewerStateListener`、`@JavascriptInterface` 等）。若仍有反射调用，可按需补充 `-keep`。

### 2.5 X5 / TBS 特别说明（选用 `trtc-x5` 时）

- 随包 **`config.tbs`** 与腾讯登记、**包名、签名、应用名**强相关；**勿**随意加 `applicationIdSuffix` 或改 `android:label`，否则易出现 **-1011 app name not match**、**-1012 app sig not match** 等。
- 多包并存或改包名时，需按腾讯流程重新申请/生成配置。

---

## 3. 权限与网络安全

### 3.1 `AndroidManifest.xml` 常用权限

带看语音至少需 **麦克风**；网络访问 TRTC/H5 需 **网络**权限。示例（可按业务裁剪）：

```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
```

自 **Android 6.0（API 23）** 起，`RECORD_AUDIO` 须在运行时 **`ActivityCompat.requestPermissions`**（或等价 API）向用户申请，**通过后再**进房/采集。

### 3.2 明文 HTTP（非 HTTPS）

若带看 H5 使用 **HTTP**，在 `Application` 或对应 `android:usesCleartextTraffic="true"`，或按官方方式配置 **Network Security Config**；否则 `loadUrl` 可能失败。

---

## 4. 集成代码示例

**时序建议**（与 iOS 一致）：先 **`registerViewerStateListener`**，再 **`openViewer`**，再 **`loadUrl` / `loadData`**；离开页或不需要带看时 **`destroyViewer()`**。再次进入建议先 **`destroyViewer`** 再绑定新 WebView，避免重复注入同名 JS 桥 **`VRViewer`**。

### 4.1 WebView 前置条件

- 开启 **JavaScript**（及业务需要的 **DOM Storage**、**javaScriptCanOpenWindowsAutomatically** 等）。
- SDK 会通过 **`addJavascriptInterface(..., "VRViewer")`** 注册桥；**不要**在同一 WebView 上重复注册同名接口。
- **`openViewer` 须在首次 `loadUrl` 之前**调用。

### 4.2 Kotlin（系统 `WebView` + `trtc-webview` AAR）

`ZQViewerStateListener` 为**普通接口**，Kotlin/Java 需**实现全部方法**（与 Swift/ObjC 中「协议方法均为 optional」不同，无调用方默认空实现）。

```kotlin
import android.os.Bundle
import android.webkit.WebView
import androidx.appcompat.app.AppCompatActivity
import com.zhongqu.vrviewer.ZQVRViewerManager
import com.zhongqu.vrviewer.ZQViewerStateListener

class MyTourActivity : AppCompatActivity(), ZQViewerStateListener {

    private lateinit var webView: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        webView = WebView(this)
        setContentView(webView)
        webView.settings.javaScriptEnabled = true
        webView.settings.javaScriptCanOpenWindowsAutomatically = true
        webView.settings.domStorageEnabled = true

        ZQVRViewerManager.registerViewerStateListener(this)
        ZQVRViewerManager.openViewer(webView)
        webView.loadUrl(tourUrl) // 由宿主拼好的带看页 URL
    }

    override fun onDestroy() {
        ZQVRViewerManager.destroyViewer()
        super.onDestroy()
    }

    // region ZQViewerStateListener
    override fun onEnterRoom(result: Int) {
        // 成功时多为非负（如进房耗时 ms）；参数不合法等失败为负错误码（与 iOS / TRTC 语义对齐）
    }
    override fun onExitRoom(reason: Int) {}
    override fun onError(errCode: Int, errMsg: String?, extraInfo: Bundle?) {}
    override fun onRemoteUserEnterRoom(userId: String) {}
    override fun onRemoteUserLeaveRoom(userId: String, reason: Int) {}
    override fun onFirstAudioFrame(userId: String) {}
    /** userId 为 "" 表示本机上行网络质量（TRTC 语义）；非空为远端用户 id */
    override fun onNetworkQuality(userId: String, quality: Int) {}
    override fun onExitVR() {
        // H5 通知退出 VR；是否随后 destroyViewer 由宿主决定，常见写法：
        ZQVRViewerManager.destroyViewer()
    }
    // endregion
}
```

### 4.3 Java 8+（系统 `WebView`）

入口类名为 **`ZQVRViewerManager`**（Kotlin `@file:JvmName` + `@file:JvmMultifileClass`），Java 侧以 **`ZQVRViewerManager.openViewer(...)`** 等**静态方法**调用，无单例 `INSTANCE`。

```java
import android.os.Bundle;
import android.webkit.WebView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.zhongqu.vrviewer.ZQVRViewerManager;
import com.zhongqu.vrviewer.ZQViewerStateListener;

public class MyTourActivity extends AppCompatActivity implements ZQViewerStateListener {

    private WebView webView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        webView = new WebView(this);
        setContentView(webView);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
        webView.getSettings().setDomStorageEnabled(true);

        ZQVRViewerManager.registerViewerStateListener(this);
        ZQVRViewerManager.openViewer(webView);
        webView.loadUrl(tourUrl);
    }

    @Override
    protected void onDestroy() {
        ZQVRViewerManager.destroyViewer();
        super.onDestroy();
    }

    @Override
    public void onEnterRoom(int result) { }

    @Override
    public void onExitRoom(int reason) { }

    @Override
    public void onError(int errCode, @Nullable String errMsg, @Nullable Bundle extraInfo) { }

    @Override
    public void onRemoteUserEnterRoom(@NonNull String userId) { }

    @Override
    public void onRemoteUserLeaveRoom(@NonNull String userId, int reason) { }

    @Override
    public void onFirstAudioFrame(@NonNull String userId) { }

    @Override
    public void onNetworkQuality(@NonNull String userId, int quality) { }

    @Override
    public void onExitVR() {
        ZQVRViewerManager.destroyViewer();
    }
}
```

### 4.4 X5：`trtc-x5` AAR

将 `WebView` 类型改为 **`com.tencent.smtt.sdk.WebView`**，`openViewer` 重载与之匹配；**调用顺序不变**：`registerViewerStateListener` → `openViewer` → `loadUrl`。

---

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

`ZQVRViewerManager` 只管 **WebView 桥接与 TRTC**；**带看页要加载的 URL 由宿主自己拼好再 `loadUrl`**。路径与查询串由**业务 H5 与后台**约定。

**链接参数**

| 参数名 | 类型 | 是否必须 | 描述 |
|--------|------|----------|------|
| `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 字符串等） | 否 | 自定义参数；呼叫模式下由发起方传给接收方 |

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

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

---

## 6. 主要 API 说明

对外入口为 **`ZQVRViewerManager`**（Kotlin 顶层方法在 Java 中表现为 **`ZQVRViewerManager` 的静态方法**）。状态回调通过 **`registerViewerStateListener`** 注册（内部为 **WeakReference**，注意生命周期与匿名内部类泄漏问题，建议使用 **Activity/Fragment 显式 register** 并在 `onDestroy` 前 `destroyViewer`）。

### 6.1 `ZQVRViewerManager`

| API | 说明 |
|-----|------|
| `registerViewerStateListener(listener)` | 注册 **`ZQViewerStateListener`**；未注册时 RTC 与 H5 仍可能工作，但**不会**回调到 App。 |
| `openViewer(webView)` | 绑定 WebView、初始化 RTC、注入名为 **`VRViewer`** 的 JS 接口。**重复调用**会先按 `destroyViewer` 等效清理再绑定。重载类型随 AAR：`android.webkit.WebView` 或 `com.tencent.smtt.sdk.WebView`。 |
| `destroyViewer()` | 停止本地音频、退房、移除桥接、释放 RTC 与 WebView 关联。与 `openViewer` 配对。 |
| `setAudioCaptureVolume(volume)` | 设置**采集**（上行）音量（0–100）；未 `openViewer` 时仍可能记录日志但**不生效**于会话。 |
| `setAudioPlayoutVolume(volume)` | 设置**播放**（下行）音量（0–100）；同上。 |

### 6.2 `ZQViewerStateListener`

回调由 SDK 在合适线程派发（与 RTC/Web 回调对齐）；**`onError` 的 `errMsg`、`extraInfo` 可为 null**。

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

---
