Browse Source

feat: videojs player

pull/8990/head
DarkPhoenix2704 4 months ago
parent
commit
bd84d75c4c
No known key found for this signature in database
GPG Key ID: 3F76B10622A07849
  1. 38
      packages/nc-gui/components/cell/attachment/Carousel.vue
  2. 1
      packages/nc-gui/components/cell/attachment/Image.vue
  3. 116
      packages/nc-gui/components/cell/attachment/Video.vue
  4. 26
      packages/nc-gui/utils/objectUtils.ts

38
packages/nc-gui/components/cell/attachment/Carousel.vue

@ -13,7 +13,7 @@ const { getPossibleAttachmentSrc } = useAttachment()
useEventListener(container, 'click', (e) => {
const target = e.target as HTMLElement
if (!target.closest('.keep-open') && !target.closest('img')) {
if (!target.closest('.keep-open') && (!target.closest('img') || !target.closest('video'))) {
selectedFile.value = false
}
})
@ -82,12 +82,16 @@ watchOnce(emblaMainApi, async (emblaMainApi) => {
</h3>
</div>
<NcCarousel class="relative p-22 keep-open" @init-api="(val) => (emblaMainApi = val)">
<NcCarousel
class="!absolute inset-16 keep-open flex justify-center items-center"
@init-api="(val) => (emblaMainApi = val)"
>
<NcCarouselContent>
<NcCarouselItem v-for="(item, index) in visibleItems" :key="index">
<div class="w-full flex items-center">
<LazyCellAttachmentImage
v-if="isImage(item.title, item.mimeType)"
class="nc-attachment-img-wrapper !h-[60%]"
class="nc-attachment-img-wrapper"
object-fit="contain"
:alt="item.title"
:srcs="getPossibleAttachmentSrc(item)"
@ -95,10 +99,15 @@ watchOnce(emblaMainApi, async (emblaMainApi) => {
<LazyCellAttachmentVideo
v-else-if="isVideo(item.title, item.mimeType)"
class="!h-[60%]"
:alt="item.title"
:srcs="getPossibleAttachmentSrc(item)"
class="!h-full"
:src="getPossibleAttachmentSrc(item)[0]"
:sources="getPossibleAttachmentSrc(item).map((src) => ({ src, type: item.mimeType }))"
/>
<div v-else class="bg-white h-full flex flex-col justify-center rounded-md gap-1 items-center w-full">
<component :is="iconMap.file" class="text-gray-600 w-20 h-20" />
<div class="text-gray-800 text-sm">{{ item.title }}</div>
</div>
</div>
</NcCarouselItem>
</NcCarouselContent>
</NcCarousel>
@ -129,6 +138,9 @@ watchOnce(emblaMainApi, async (emblaMainApi) => {
>
<GeneralIcon class="text-white" icon="play" />
</div>
<div v-else class="h-full flex items-center h-6 justify-center rounded-md px-2 py-1 border-1 border-gray-200">
<GeneralIcon class="text-white" icon="file" />
</div>
</div>
</NcCarouselItem>
</NcCarouselContent>
@ -140,7 +152,17 @@ watchOnce(emblaMainApi, async (emblaMainApi) => {
</template>
<style lang="scss">
.nc-attachment-img-wrapper {
width: fit-content !important;
.nc-attachment-carousel {
width: max-content;
}
.carousel-container {
@apply !w-full;
}
.vjs-fluid {
&:not(.vjs-audio-only-mode) {
padding-top: 49.25% !important;
}
}
</style>

1
packages/nc-gui/components/cell/attachment/Image.vue

@ -20,6 +20,7 @@ const onError = () => index.value++
}"
class="m-auto h-full max-h-full w-auto object-cover nc-attachment-image"
:src="props.srcs[index]"
loading="lazy"
:alt="props?.alt || ''"
placeholder
quality="75"

116
packages/nc-gui/components/cell/attachment/Video.vue

@ -1,23 +1,117 @@
<script setup lang="ts">
import videojs from 'video.js'
import { stripUndefinedOrNull } from '~/utils/objectUtils'
import 'video.js/dist/video-js.css'
interface Props {
srcs: string[]
alt?: string
mimeType?: string
objectFit?: string
autoplay?: boolean
controls?: boolean
height?: string | number
loop?: boolean
muted?: boolean
poster?: string
preload?: 'auto' | 'metadata' | 'none'
width?: string | number
aspectRatio?: string
audioOnlyMode?: boolean
audioPosterMode?: boolean
autoSetup?: boolean
disablePictureInPicture?: boolean
enableDocumentPictireInPicture?: boolean
enableSmoothSeeking?: boolean
fluid?: boolean
src?: string
fullScreen?: {
options?: {
navigationUI?: 'hide' | 'show'
id?: string
}
}
inactivityTimeout?: number
language?: string
liveui?: boolean
notSupportedMessage?: string
playbackRates?: number[]
playsinline?: boolean
plugins?: Record<string, any>
preferFullWindow?: boolean
responsive?: boolean
restoreEl?: Element | boolean
skipButtons?: {
forward?: number
back?: number
}
sources?: {
src: string
type: string
}[]
suppressNotSupportedError?: boolean
techOrder?: string[]
userActions?: {
click?: (event: Event) => void | boolean
doubleClick?: (event: Event) => void | boolean
hotkeys?: (event: Event) => void | boolean | Record<string, (event: Event) => void>
}
class?: string
}
const props = withDefaults(defineProps<Props>(), {
preload: 'metadata',
aspectRatio: '16:9',
controls: true,
audioOnlyMode: false,
audioPosterMode: false,
autoSetup: true,
disablePictureInPicture: false,
enableDocumentPictireInPicture: false,
enableSmoothSeeking: true,
fluid: true,
fullScreen: {
options: {
navigationUI: 'hide',
id: undefined,
},
},
language: 'en',
liveui: false,
playsinline: true,
preferFullWindow: false,
responsive: false,
restoreEl: false,
})
const emit = defineEmits<Emits>()
interface Emits {
(event: 'init', player: any): void
}
const props = defineProps<Props>()
const videoPlayer = ref<Element>()
const player = ref()
const index = ref(0)
onMounted(() => {
if (!videoPlayer.value) return
player.value = videojs(videoPlayer.value, stripUndefinedOrNull(props))
emit('init', player.value)
})
const onError = () => index.value++
onBeforeUnmount(() => {
if (player.value) {
player.value.dispose()
}
})
</script>
<template>
<video controls>
<source :src="props.srcs[index]" :type="props.mimeType" />
Your browser does not support the video tag.
</video>
<div
class="w-full"
:class="{
[props.class]: props.class,
}"
>
<video ref="videoPlayer" class="video-js h-full w-full"></video>
</div>
</template>
<style scoped lang="scss"></style>

26
packages/nc-gui/utils/objectUtils.ts

@ -0,0 +1,26 @@
type NonUndefined<T> = T extends undefined ? never : T
type NonNull<T> = T extends null ? never : T
type NonNullableObject<T> = {
[K in keyof T]: NonUndefined<NonNull<T[K]>>
}
type Prettify<T> = {
[K in keyof T]: T[K]
} & {}
export const stripUndefinedOrNull = <T>(obj: T): Prettify<NonNullableObject<T>> => {
const strip = (input: unknown): unknown => {
return Array.isArray(input)
? input.map(strip)
: input !== null && typeof input === 'object'
? Object.entries(input)
.filter(([, value]) => value !== undefined && value !== null)
.reduce((acc, [key, value]) => {
acc[key as keyof typeof acc] = strip(value)
return acc
}, {} as Record<string, unknown>)
: input
}
return strip(obj) as Prettify<NonNullableObject<T>>
}
Loading…
Cancel
Save