# VAP 播放接入说明

本文档整理当前项目中 VAP mp4 播放相关的组件、工具函数和活动页用法，方便后续复用、排查和扩展。

## 相关文件

| 文件 | 作用 |
| --- | --- |
| `src/components/VapMp4Player.vue` | 通用 VAP 播放组件，支持全屏弹层和页面内嵌播放 |
| `src/utils/vapMp4Config.js` | 从 mp4 文件中解析 `vapc` 配置并缓存 |
| `src/views/Activities/ActiveGame/vapBg.vue` | ActiveGame 顶部动图背景封装 |
| `src/views/Activities/ActiveGame/index.vue` | ActiveGame 中背景 VAP、奖励全屏 VAP 和预加载示例 |
| `src/config/imagePaths.js` | OSS 图片/视频 URL 生成，包含 `getMp4Url` |
| `src/utils/protectedAssets.js` | 受保护资源地址的编码和还原 |

依赖包在 `package.json` 中：

```json
"video-animation-player": "^1.0.5"
```

## 播放组件

`VapMp4Player.vue` 是项目里的通用播放器，内部使用 `video-animation-player`。

基础用法：

```vue
<VapMp4Player
  v-model="show"
  :src="mp4Url"
  mode="overlay"
  fit="contain"
  :loop="false"
  :mute="false"
  :auto-close="true"
  :show-close="true"
  :show-status="false"
/>
```

主要参数：

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `modelValue` | Boolean | `false` | 控制播放组件显示，支持 `v-model` |
| `src` | String | `''` | mp4 地址 |
| `mode` | String | `'overlay'` | `overlay` 为全屏弹层，`inline` 为页面内嵌 |
| `fit` | String | `'contain'` | `contain`、`cover`、`fill` |
| `loop` | Boolean | `false` | 是否循环播放 |
| `mute` | Boolean | `false` | 是否静音 |
| `accurate` | Boolean | `true` | 传给 VAP 播放器的精准模式 |
| `autoClose` | Boolean | `true` | 非循环播放结束后是否自动关闭 |
| `showClose` | Boolean | `true` | 全屏弹层是否展示关闭按钮 |
| `showStatus` | Boolean | `true` | 是否展示加载/错误状态文案 |
| `vapData` | Object | `{}` | 额外透传给 `new Vap().play()` 的参数 |

事件：

| 事件 | 说明 |
| --- | --- |
| `update:modelValue` | 更新显示状态 |
| `loaded` | VAP 进入 playing 后触发 |
| `ended` | 播放结束 |
| `error` | 加载或播放失败 |
| `close` | 调用关闭逻辑后触发 |

注意：

- `overlay` 模式会通过 `Teleport` 挂到 `body`，默认 `z-index: 3000`。
- `inline` 模式是 `position: absolute; inset: 0`，父容器需要有明确尺寸和定位。
- 组件内部会先调用 `resolveProtectedAssetUrl(src)`，因此可以直接传 `getMp4Url` 生成的受保护地址。

## VAP 配置解析

`src/utils/vapMp4Config.js` 负责从 mp4 中读取名为 `vapc` 的 box，并解析其中的 JSON 配置。

导出方法：

```js
getVapConfigFromMp4(mp4Url)
preloadVapConfig(mp4Url)
clearVapConfigCache(mp4Url)
```

说明：

- `getVapConfigFromMp4(mp4Url)` 会 `fetch` 整个 mp4，读取 ArrayBuffer 后查找 `vapc`。
- 解析结果以 `mp4Url` 为 key 缓存在 `configCache` 中，重复播放同一个地址不会重复解析。
- `preloadVapConfig(mp4Url)` 适合页面预加载阶段使用，失败会吞掉错误并返回 `null`。
- `clearVapConfigCache(mp4Url)` 可清除某个地址或全部缓存。

常见错误：

| 错误 | 可能原因 |
| --- | --- |
| `vapc box not found` | mp4 不是 VAP 格式，或没有内嵌 VAP 配置 |
| `mp4 request failed: xxx` | 地址不可访问、跨域、鉴权、OSS 路径错误 |
| `vapc box does not contain json` | `vapc` 存在但内容不是合法 JSON |

## mp4 地址约定

`src/config/imagePaths.js` 中提供了 mp4 URL 方法：

```js
getRawMp4Url(imagePath, filename)
getMp4Url(imagePath, filename)
```

示例：

```js
import { getMp4Url } from '@/config/imagePaths.js'

const mp4Url = (filename) => getMp4Url('Activities/ActiveGame/', filename)
const bgVapUrl = mp4Url('bg')
```

生成的线上地址形如：

```text
https://cdn.azizichat.com/h5/Azizi/Activities/ActiveGame/bg.mp4
```

开发环境会走：

```text
/oss/h5/Azizi/Activities/ActiveGame/bg.mp4
```

如果资源路径属于受保护范围，`getMp4Url` 会返回保护后的地址。播放器或预加载工具在真正 `fetch` 前需要用 `resolveProtectedAssetUrl` 还原。`VapMp4Player.vue` 内部已经处理播放时的还原；页面手动预加载时也要处理：

```js
import { preloadVapConfig } from '@/utils/vapMp4Config.js'
import { resolveProtectedAssetUrl } from '@/utils/protectedAssets.js'

await preloadVapConfig(resolveProtectedAssetUrl(bgVapUrl))
```

## ActiveGame 当前用法

### 顶部背景 VAP

`src/views/Activities/ActiveGame/vapBg.vue` 用于顶部背景：

```vue
<VapBg class="vap-background-layer" :src="bgVapUrl" />
```

内部实际使用：

```vue
<VapMp4Player
  v-model="backgroundVisible"
  :src="src"
  mode="inline"
  fit="cover"
  :loop="true"
  :mute="false"
  :auto-close="false"
  :show-status="false"
/>
```

页面样式中给背景 VAP 固定区域：

```scss
.vap-background-layer {
  width: 100vw;
  height: 150vw;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  pointer-events: none;
}
```

### 奖励全屏 VAP

点击奖励展示的左侧奖励图时，展示全屏居中的 VAP：

```vue
<VapMp4Player
  v-model="rewardVapShow"
  :src="rewardVapUrl"
  mode="overlay"
  fit="contain"
  :loop="false"
  :mute="false"
  :auto-close="true"
  :show-close="true"
  :show-status="false"
  @error="handleRewardVapError"
/>
```

当前临时地址：

```js
const rewardVapUrl =
  'https://cdn.azizichat.com/svgasource/manager-d824e59b-1a88-4d4f-aed3-01768ba20542.mp4'
```

## 预加载建议

如果页面打开后要尽快展示 VAP，建议同时做两件事：

1. 在页面预加载阶段调用 `preloadVapConfig`。
2. 不要等 loading 结束后才挂载 VAP 组件，可以让页面在 loading 遮罩下先挂载，VAP 提前初始化。

ActiveGame 当前预加载示例：

```js
await Promise.all([
  preloadImages(criticalImages),
  preloadVapConfig(resolveProtectedAssetUrl(bgVapUrl)),
  preloadVapConfig(rewardVapUrl),
])
```

说明：

- `bgVapUrl` 来自 `getMp4Url`，需要先 `resolveProtectedAssetUrl`。
- `rewardVapUrl` 是普通完整 OSS 地址，可以直接传入。
- `preloadVapConfig` 主要预热 VAP 配置解析和浏览器请求缓存，但最终播放仍由 `VapMp4Player` 创建 canvas 和播放器实例。

## 接入新页面步骤

1. 准备带 `vapc` 配置的 VAP mp4 文件。
2. 如果放在活动 OSS 目录，使用 `getMp4Url('Activities/xxx/', 'filename')` 生成地址。
3. 页面中引入 `VapMp4Player.vue`。
4. 全屏播放用 `mode="overlay"`，页面背景或局部播放用 `mode="inline"`。
5. 页面预加载阶段调用 `preloadVapConfig(resolveProtectedAssetUrl(mp4Url))`。
6. 对 `@error` 做兜底提示或日志，避免静默失败。

全屏弹层模板：

```vue
<VapMp4Player
  v-model="vapShow"
  :src="vapUrl"
  mode="overlay"
  fit="contain"
  :loop="false"
  :auto-close="true"
/>
```

页面背景模板：

```vue
<div class="vap-bg">
  <VapMp4Player
    v-model="vapVisible"
    :src="vapUrl"
    mode="inline"
    fit="cover"
    :loop="true"
    :auto-close="false"
    :show-close="false"
    :show-status="false"
  />
</div>
```

```scss
.vap-bg {
  position: absolute;
  width: 100vw;
  height: 150vw;
  overflow: hidden;
  pointer-events: none;
}
```

## 排查清单

- 页面空白或不播放：确认组件 `modelValue` 为 `true`，`src` 非空。
- 只有 loading 不结束：确认页面预加载流程有 `finally` 关闭 loading。
- 提示找不到 VAP 配置：确认 mp4 内有 `vapc` box。
- 本地能播线上不能播：检查 OSS 路径、跨域、资源保护地址是否还原。
- inline 不显示：确认父容器有 `position: relative/absolute` 和明确宽高。
- overlay 被遮挡：检查是否有更高的 z-index；默认播放器弹层是 `3000`。
- 背景遮挡内容：检查页面层级，ActiveGame 当前是页面背景 `z-index: 0`、VAP 背景 `z-index: 1`、页面内容 `z-index: 2`。

