148 lines
5.8 KiB
Vue
148 lines
5.8 KiB
Vue
<template>
|
|
<div class="pv-page" :style="{
|
|
height: bsInfo.height - 120 + 'px',
|
|
}">
|
|
<VideoPlayer v-model:controls="controlsVisible" :videos="videos" />
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { ref, onMounted, onUnmounted, watch } from "vue";
|
|
import { useRoute } from "vue-router";
|
|
|
|
import VideoPlayer from "@/components/basic/VideoPlayer.vue";
|
|
import type { VideoItem } from "@/components/basic/VideoPlayer.vue";
|
|
|
|
import { bsInfo } from "@/utils/browser";
|
|
import publishVideoService from "@/service/master/publishVideoService";
|
|
import { PublishVideoData } from "@/data/auto/publishVideoData";
|
|
import { PublishVideoDetailDisplayData } from "@/data/auto/publishVideoDetailDisplayData";
|
|
import { DmsFileSearchCriteria } from "@/data/auto/dmsFileSearchCriteria";
|
|
import dmsFileService from "@/service/master/dmsFileService";
|
|
import { DmsFileUploadViewDisplayData } from "@/data/auto/dmsFileUploadViewDisplayData";
|
|
import { SearchFilter } from "@/data/auto/searchFilter";
|
|
import { FileTypePolicy } from "@/policy/custom/fileTypePolicy";
|
|
import { DmsFileResultData } from "@/data/auto/dmsFileResultData";
|
|
|
|
const route = useRoute();
|
|
|
|
const publishName = ref<string>(route.params.id as string);
|
|
const mode = ref<string>(route.params.mode as string);
|
|
|
|
// 视频列表:与 VinylAudioPlayer 的 tracks 一致,组件负责 UI,页面只负责数据
|
|
const videos = ref<VideoItem[]>([]);
|
|
|
|
/**
|
|
* 底部控制区显隐(点播放器控制栏里的眼睛图标切换)。
|
|
* 影视厅是整页用法,收起控制区时连 layout 顶部的两条栏(标题栏 + 页面头)一起藏掉,
|
|
* 视频才能占满整屏。class 挂在 body 上,离开本页/恢复显示时立即移除,不会影响其它页面。
|
|
*/
|
|
const controlsVisible = ref(true);
|
|
|
|
/** 退出全屏后让顶栏短暂不响应指针的定时器 */
|
|
let settleTimer = 0;
|
|
|
|
watch(controlsVisible, (visible) => {
|
|
document.body.classList.toggle("pv-fullscreen", !visible);
|
|
|
|
// 退出全屏时鼠标往往正好停在右上角(恢复按钮的位置,也是顶栏菜单的位置),
|
|
// 顶栏一恢复显示就会被 hover 展开下拉。让它先"冷静" 400ms,避开这次误触。
|
|
if (visible) {
|
|
window.clearTimeout(settleTimer);
|
|
document.body.classList.add("pv-settling");
|
|
settleTimer = window.setTimeout(() => {
|
|
document.body.classList.remove("pv-settling");
|
|
}, 400);
|
|
}
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
document.body.classList.remove("pv-fullscreen");
|
|
document.body.classList.remove("pv-settling");
|
|
window.clearTimeout(settleTimer);
|
|
});
|
|
|
|
onMounted(async () => {
|
|
await loadVideos();
|
|
});
|
|
|
|
/**
|
|
* 发布专辑详情(FindPublishVideoDefault / FindByKey)里,Url 存的是
|
|
* OrderKey(形如 xxxx-3-3.mp4 的文件名),不是可播放地址;
|
|
* 真正的原码地址在 FormattedUrl 上(后端 3 参 PackUrl 现在固定返回原码)。
|
|
* 这里把 Url 补成地址,否则播放器切「原码」时会拿文件名当地址请求、直接 404。
|
|
* 网盘列表模式(mode=list)的 Url 本身就是完整地址,不受影响。
|
|
*/
|
|
const normalizeDetails = (details: PublishVideoDetailDisplayData[]) =>
|
|
(details ?? []).map((d) => {
|
|
if (d.Url && !/^(https?:)?\/\//i.test(d.Url)) {
|
|
d.Url = d.FormattedUrl || "";
|
|
}
|
|
return d;
|
|
});
|
|
|
|
const loadVideos = async () => {
|
|
try {
|
|
// 模式一:从网盘中心加载某个路径下的所有视频
|
|
if (mode.value === "list") {
|
|
const c = new DmsFileSearchCriteria();
|
|
c.limit = 100;
|
|
c.page = 1;
|
|
|
|
let filter: SearchFilter;
|
|
filter = new SearchFilter();
|
|
filter.Column = DmsFileUploadViewDisplayData.DB_FILE_PATH;
|
|
filter.Type = "SingleSelection";
|
|
filter.Operator = "single";
|
|
filter.Value = publishName.value;
|
|
c.Filter.push(filter);
|
|
|
|
filter = new SearchFilter();
|
|
filter.Column = DmsFileUploadViewDisplayData.DB_FILE_TYPE;
|
|
filter.Type = "SingleSelection";
|
|
filter.Operator = "single";
|
|
filter.Value = FileTypePolicy.VIDEO;
|
|
c.Filter.push(filter);
|
|
|
|
const dr: DmsFileResultData = await dmsFileService.FindBySearchCriteria(c);
|
|
|
|
if (dr && dr.Datas && dr.Datas.length > 0) {
|
|
for (const item of dr.Datas) {
|
|
const detail = new PublishVideoDetailDisplayData();
|
|
detail.Title = item.OrgFileName;
|
|
detail.Url = item.Url; // 原码
|
|
detail.FormattedM3u8 = item.FormattedM3u8; // 转码(10秒)
|
|
detail.FormattedUrl = item.FormattedM3u8 || item.Url;
|
|
// m3u8 通常配套同名 jpg/png 封面,这里把后缀替换成 .png 兜底
|
|
detail.FormattedPic = (item.FormattedPic || "").replace(/\.m3u8$/, ".png");
|
|
videos.value.push(detail);
|
|
}
|
|
}
|
|
}
|
|
// 默认发布视频
|
|
else if (!publishName.value || publishName.value.length === 0) {
|
|
const data: PublishVideoData = await publishVideoService.FindPublishVideoDefault();
|
|
videos.value = normalizeDetails(data?.Details ?? []);
|
|
}
|
|
// 按 key 加载
|
|
else {
|
|
const data: PublishVideoData = await publishVideoService.FindByKey(publishName.value);
|
|
videos.value = normalizeDetails(data?.Details ?? []);
|
|
}
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.pv-page {
|
|
width: 100%;
|
|
/* 外层 .layout-content-view 只有 padding-left: 8px,这里补上右侧
|
|
同样的间距,播放器才会左右居中而不是偏右贴边 */
|
|
padding-right: 8px;
|
|
box-sizing: border-box;
|
|
}
|
|
</style>
|
|
|