feat(新组件): VAP视频播放组件
This commit is contained in:
parent
56d766e134
commit
8cc2fb87aa
286
docs/vap播放说明.md
Normal file
286
docs/vap播放说明.md
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
# 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://tkm-likei.oss-ap-southeast-1.aliyuncs.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://tkm-likei.oss-ap-southeast-1.aliyuncs.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`。
|
||||||
|
|
||||||
@ -1,22 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport v-if="teleportToBody" to="body">
|
||||||
<div v-if="modelValue" class="vap-overlay" @touchmove.prevent>
|
<div v-if="modelValue" class="vap-player vap-overlay" @touchmove.prevent>
|
||||||
<button class="vap-close" type="button" aria-label="Close" @click="close">×</button>
|
<button
|
||||||
|
v-if="showClose"
|
||||||
|
class="vap-close"
|
||||||
|
type="button"
|
||||||
|
aria-label="Close"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
|
||||||
<div ref="containerRef" class="vap-stage"></div>
|
<div ref="containerRef" class="vap-stage" :class="`vap-fit-${fit}`"></div>
|
||||||
|
|
||||||
<div v-if="loading" class="vap-status">Loading...</div>
|
<div v-if="showStatus && loading" class="vap-status">Loading...</div>
|
||||||
<div v-else-if="errorMessage" class="vap-status vap-status-error">
|
<div v-else-if="showStatus && errorMessage" class="vap-status vap-status-error">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
|
||||||
|
<div v-else-if="modelValue" class="vap-player vap-inline">
|
||||||
|
<div ref="containerRef" class="vap-stage" :class="`vap-fit-${fit}`"></div>
|
||||||
|
|
||||||
|
<div v-if="showStatus && loading" class="vap-status">Loading...</div>
|
||||||
|
<div v-else-if="showStatus && errorMessage" class="vap-status vap-status-error">
|
||||||
|
{{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
import Vap from 'video-animation-player'
|
import Vap from 'video-animation-player'
|
||||||
import { getVapConfigFromMp4 } from '@/utils/vapMp4Config'
|
import { getVapConfigFromMp4 } from '@/utils/vapMp4Config'
|
||||||
|
import { resolveProtectedAssetUrl } from '@/utils/protectedAssets.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@ -43,6 +61,24 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: 'overlay',
|
||||||
|
validator: (value) => ['overlay', 'inline'].includes(value),
|
||||||
|
},
|
||||||
|
showClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
showStatus: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
fit: {
|
||||||
|
type: String,
|
||||||
|
default: 'contain',
|
||||||
|
validator: (value) => ['contain', 'cover', 'fill'].includes(value),
|
||||||
|
},
|
||||||
vapData: {
|
vapData: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
@ -57,6 +93,9 @@ const errorMessage = ref('')
|
|||||||
let player = null
|
let player = null
|
||||||
let playToken = 0
|
let playToken = 0
|
||||||
|
|
||||||
|
const teleportToBody = computed(() => props.mode === 'overlay')
|
||||||
|
const resolvedSrc = computed(() => resolveProtectedAssetUrl(props.src))
|
||||||
|
|
||||||
function destroyPlayer() {
|
function destroyPlayer() {
|
||||||
if (player) {
|
if (player) {
|
||||||
player.destroy()
|
player.destroy()
|
||||||
@ -99,7 +138,7 @@ async function startPlay() {
|
|||||||
destroyPlayer()
|
destroyPlayer()
|
||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
if (!props.modelValue || !props.src) {
|
if (!props.modelValue || !resolvedSrc.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +151,7 @@ async function startPlay() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await getVapConfigFromMp4(props.src)
|
const config = await getVapConfigFromMp4(resolvedSrc.value)
|
||||||
|
|
||||||
if (!containerRef.value || currentToken !== playToken) {
|
if (!containerRef.value || currentToken !== playToken) {
|
||||||
return
|
return
|
||||||
@ -120,7 +159,7 @@ async function startPlay() {
|
|||||||
|
|
||||||
player = new Vap().play({
|
player = new Vap().play({
|
||||||
container: containerRef.value,
|
container: containerRef.value,
|
||||||
src: props.src,
|
src: resolvedSrc.value,
|
||||||
config,
|
config,
|
||||||
loop: props.loop,
|
loop: props.loop,
|
||||||
mute: props.mute,
|
mute: props.mute,
|
||||||
@ -175,35 +214,67 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.vap-overlay {
|
.vap-player {
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 3000;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vap-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 3000;
|
||||||
background: rgba(0, 0, 0, 0.72);
|
background: rgba(0, 0, 0, 0.72);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vap-inline {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.vap-stage {
|
.vap-stage {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vap-stage :deep(canvas) {
|
.vap-stage :deep(canvas) {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 100vw;
|
max-width: 100%;
|
||||||
max-height: 100vh;
|
max-height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vap-fit-contain :deep(canvas) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vap-fit-cover :deep(canvas),
|
||||||
|
.vap-fit-fill :deep(canvas) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-width: none;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vap-fit-cover :deep(canvas) {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vap-fit-fill :deep(canvas) {
|
||||||
|
object-fit: fill;
|
||||||
|
}
|
||||||
|
|
||||||
.vap-close {
|
.vap-close {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: calc(env(safe-area-inset-top) + 12px);
|
top: calc(env(safe-area-inset-top) + 12px);
|
||||||
|
|||||||
@ -13,6 +13,8 @@ export const getRawPngUrl = (imagePath, filename) => buildAssetUrl(imagePath, fi
|
|||||||
|
|
||||||
export const getRawWebpUrl = (imagePath, filename) => buildAssetUrl(imagePath, filename, 'webp')
|
export const getRawWebpUrl = (imagePath, filename) => buildAssetUrl(imagePath, filename, 'webp')
|
||||||
|
|
||||||
|
export const getRawMp4Url = (imagePath, filename) => buildAssetUrl(imagePath, filename, 'mp4')
|
||||||
|
|
||||||
export const getPngUrl = (imagePath, filename) => {
|
export const getPngUrl = (imagePath, filename) => {
|
||||||
return protectAssetUrl(getRawPngUrl(imagePath, filename), imagePath)
|
return protectAssetUrl(getRawPngUrl(imagePath, filename), imagePath)
|
||||||
}
|
}
|
||||||
@ -20,3 +22,7 @@ export const getPngUrl = (imagePath, filename) => {
|
|||||||
export const getWebpUrl = (imagePath, filename) => {
|
export const getWebpUrl = (imagePath, filename) => {
|
||||||
return protectAssetUrl(getRawWebpUrl(imagePath, filename), imagePath)
|
return protectAssetUrl(getRawWebpUrl(imagePath, filename), imagePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getMp4Url = (imagePath, filename) => {
|
||||||
|
return protectAssetUrl(getRawMp4Url(imagePath, filename), imagePath)
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user