ow
This commit is contained in:
1 parent
c10f8347e2
commit
70087c57cd
2 files changed
+104
No files matched your search
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<dragModal v-model:open="visible" :width="width" :title="title" :footer="null" :mask-closable="false"
|
||||||
|
:fullscreen="fullscreen" wrap-class-name="vpm-wrap">
|
||||||
|
<div class="vpm-body">
|
||||||
|
<VideoPlayer :videos="videos" :start-index="startIndex" :auto-play="true" />
|
||||||
|
</div>
|
||||||
|
</dragModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useVModel } from "@vueuse/core";
|
||||||
|
|
||||||
|
import dragModal from "@/components/basic/DragModal.vue";
|
||||||
|
import VideoPlayer from "@/components/basic/VideoPlayer.vue";
|
||||||
|
import type { VideoItem } from "@/components/basic/VideoPlayer.vue";
|
||||||
|
|
||||||
|
/** 弹出式视频播放器:与 PreviewVideo 页面同一套 VideoPlayer,只是外面套了可拖动弹窗 */
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
open?: boolean;
|
||||||
|
videos?: VideoItem[];
|
||||||
|
title?: string;
|
||||||
|
startIndex?: number;
|
||||||
|
width?: string | number;
|
||||||
|
fullscreen?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
open: false,
|
||||||
|
videos: () => [],
|
||||||
|
title: "视频播放",
|
||||||
|
startIndex: 0,
|
||||||
|
width: "880px",
|
||||||
|
fullscreen: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:open", "close"]);
|
||||||
|
|
||||||
|
const visible = useVModel(props, "open", emit);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* VideoPlayer 内部是 flex 撑满父级;高度由下面的 vpm-wrap 样式链提供确定值 */
|
||||||
|
.vpm-body {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 240px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 播放弹窗专用(通过 wrapClassName 挂到 .ant-modal-wrap 上,不影响其它弹窗)。
|
||||||
|
.ant-modal 默认高度是 auto,height:100% 解析不出来,之前只能用视口魔数估算,
|
||||||
|
结果给 footer 预留的空白去不掉;这里直接给 .ant-modal 确定高度,
|
||||||
|
让 DragModal 已有的 .ant-modal-content{height:100%} 和 body 的 flex:auto 生效,
|
||||||
|
播放器即可精确撑满标题栏以下的全部空间,拖拽改大小/全屏时也会跟着变。 */
|
||||||
|
.ant-modal-wrap.vpm-wrap .ant-modal {
|
||||||
|
height: calc(100vh - 124px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DragModal 全屏模式自带 height:100% !important,会自动覆盖上面的计算值,无需处理 */
|
||||||
|
.ant-modal-wrap.vpm-wrap .ant-modal .ant-modal-body {
|
||||||
|
height: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 图片加载失败重试。
|
||||||
|
*
|
||||||
|
* 背景:视频封面(jpg)是服务端 ffmpeg 异步生成的,列表查询返回时文件常常还没落盘,
|
||||||
|
* 首次请求会拿到 404;而 <img> 一旦失败不会自动重试,只有 src 变了才会再请求一次,
|
||||||
|
* 表现就是"必须手动刷新页面才有图"。
|
||||||
|
*
|
||||||
|
* 这里失败后按退避重试,重试时把查询参数换成一次性时间戳绕开缓存。
|
||||||
|
* 重试次数用 dataset 记在 <img> 上,src 变了(重新查询拿到新地址)就重新计数。
|
||||||
|
*/
|
||||||
|
const RETRY_DELAYS = [800, 2000, 4000];
|
||||||
|
|
||||||
|
export const retryImage = (e: Event, max: number = RETRY_DELAYS.length) => {
|
||||||
|
const img = e.target as HTMLImageElement | null;
|
||||||
|
if (!img) return;
|
||||||
|
|
||||||
|
const src = img.getAttribute("src") || img.src;
|
||||||
|
if (!src) return;
|
||||||
|
|
||||||
|
// 地址变了说明是新一轮数据,允许重新重试
|
||||||
|
if (img.dataset.retrySrc !== src) {
|
||||||
|
img.dataset.retrySrc = src;
|
||||||
|
img.dataset.retry = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
const times = Number(img.dataset.retry ?? 0);
|
||||||
|
if (times >= max) {
|
||||||
|
// 彻底拿不到就隐藏图片,露出底下的占位/播放图标,避免裂图
|
||||||
|
img.style.visibility = "hidden";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.dataset.retry = String(times + 1);
|
||||||
|
const base = src.split("?")[0];
|
||||||
|
window.setTimeout(() => {
|
||||||
|
img.src = `${base}?_t=${Date.now()}`;
|
||||||
|
img.style.visibility = "";
|
||||||
|
}, RETRY_DELAYS[Math.min(times, RETRY_DELAYS.length - 1)]);
|
||||||
|
};
|
||||||
Reference in new issue
Block a user