feat: add support for video thumbnail preview in the attachment library (#6265)

#### What type of PR is this?

/kind feature

#### What this PR does / why we need it:

附件页面视频文件展示封面

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/5945

#### Special notes for your reviewer:

![image](https://github.com/halo-dev/halo/assets/111493458/772aa224-fb56-445d-8da5-be5a67be2fa4)

#### Does this PR introduce a user-facing change?


```release-note
附件库支持预览视频封面。
```
pull/6310/head
LonelySnowman 2024-07-10 10:13:24 +08:00 committed by GitHub
parent 45d0a475b5
commit 66d4986531
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 80 additions and 14 deletions

View File

@ -35,9 +35,12 @@ import AttachmentGroupList from "./components/AttachmentGroupList.vue";
import AttachmentListItem from "./components/AttachmentListItem.vue"; import AttachmentListItem from "./components/AttachmentListItem.vue";
import AttachmentPoliciesModal from "./components/AttachmentPoliciesModal.vue"; import AttachmentPoliciesModal from "./components/AttachmentPoliciesModal.vue";
import AttachmentUploadModal from "./components/AttachmentUploadModal.vue"; import AttachmentUploadModal from "./components/AttachmentUploadModal.vue";
import AttachmentLoading from "./components/AttachmentLoading.vue";
import AttachmentError from "./components/AttachmentError.vue";
import { useAttachmentControl } from "./composables/use-attachment"; import { useAttachmentControl } from "./composables/use-attachment";
import { useFetchAttachmentGroup } from "./composables/use-attachment-group"; import { useFetchAttachmentGroup } from "./composables/use-attachment-group";
import { useFetchAttachmentPolicy } from "./composables/use-attachment-policy"; import { useFetchAttachmentPolicy } from "./composables/use-attachment-policy";
import LazyVideo from "@/components/video/LazyVideo.vue";
const { t } = useI18n(); const { t } = useI18n();
@ -526,24 +529,26 @@ onMounted(() => {
classes="pointer-events-none object-cover group-hover:opacity-75 transform-gpu" classes="pointer-events-none object-cover group-hover:opacity-75 transform-gpu"
> >
<template #loading> <template #loading>
<div <AttachmentLoading />
class="flex h-full items-center justify-center object-cover"
>
<span class="text-xs text-gray-400">
{{ $t("core.common.status.loading") }}...
</span>
</div>
</template> </template>
<template #error> <template #error>
<div <AttachmentError />
class="flex h-full items-center justify-center object-cover"
>
<span class="text-xs text-red-400">
{{ $t("core.common.status.loading_error") }}
</span>
</div>
</template> </template>
</LazyImage> </LazyImage>
<LazyVideo
v-else-if="
attachment?.spec.mediaType?.startsWith('video/')
"
:src="attachment.status?.permalink"
classes="object-cover group-hover:opacity-75"
>
<template #loading>
<AttachmentLoading />
</template>
<template #error>
<AttachmentError />
</template>
</LazyVideo>
<AttachmentFileTypeIcon <AttachmentFileTypeIcon
v-else v-else
:file-name="attachment.spec.displayName" :file-name="attachment.spec.displayName"

View File

@ -0,0 +1,7 @@
<template>
<div class="flex h-full items-center justify-center object-cover">
<span class="text-xs text-red-400">
{{ $t("core.common.status.loading_error") }}
</span>
</div>
</template>

View File

@ -0,0 +1,7 @@
<template>
<div class="flex h-full items-center justify-center object-cover">
<span class="text-xs text-gray-400">
{{ $t("core.common.status.loading") }}...
</span>
</div>
</template>

View File

@ -0,0 +1,47 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
const props = withDefaults(
defineProps<{
src: string;
classes?: string | string[];
}>(),
{
src: "",
classes: "",
}
);
const isLoading = ref(false);
const error = ref(false);
const loadVideo = async () => {
const video = document.createElement("video");
video.src = props.src;
return new Promise((resolve, reject) => {
video.onloadedmetadata = () => resolve(video);
video.onerror = (err) => reject(err);
});
};
onMounted(async () => {
isLoading.value = true;
try {
await loadVideo();
} catch (e) {
error.value = true;
} finally {
isLoading.value = false;
}
isLoading.value = false;
});
</script>
<template>
<template v-if="isLoading">
<slot name="loading"> loading... </slot>
</template>
<template v-else-if="error">
<slot name="error"> error </slot>
</template>
<video v-else :src="src" preload="metadata" :class="classes" />
</template>