Showing
6 changed files
with
112 additions
and
54 deletions
| ... | ... | @@ -25,6 +25,7 @@ |
| 25 | 25 | |
| 26 | 26 | const getOptions = computed(() => { |
| 27 | 27 | const { options, withToken } = props; |
| 28 | + | |
| 28 | 29 | const defaultOptions: VideoJsPlayerOptions & Recordable = { |
| 29 | 30 | language: 'zh', |
| 30 | 31 | muted: true, |
| ... | ... | @@ -50,8 +51,7 @@ |
| 50 | 51 | }, |
| 51 | 52 | }, |
| 52 | 53 | }; |
| 53 | - | |
| 54 | - return { ...defaultOptions, ...options }; | |
| 54 | + return videoJs.mergeOptions(defaultOptions, options); | |
| 55 | 55 | }); |
| 56 | 56 | |
| 57 | 57 | const getWidthHeight = computed(() => { |
| ... | ... | @@ -62,6 +62,7 @@ |
| 62 | 62 | }); |
| 63 | 63 | |
| 64 | 64 | const init = () => { |
| 65 | + if (unref(videoPlayInstance)) unref(videoPlayInstance)?.dispose(); | |
| 65 | 66 | videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => { |
| 66 | 67 | emit('ready', unref(videoPlayInstance)); |
| 67 | 68 | }); |
| ... | ... | @@ -76,6 +77,11 @@ |
| 76 | 77 | videoPlayInstance.value = null; |
| 77 | 78 | emit('onUnmounted'); |
| 78 | 79 | }); |
| 80 | + | |
| 81 | + defineExpose({ | |
| 82 | + reloadPlayer: init, | |
| 83 | + getInstance: () => unref(videoPlayInstance), | |
| 84 | + }); | |
| 79 | 85 | </script> |
| 80 | 86 | |
| 81 | 87 | <template> | ... | ... |
| ... | ... | @@ -10,6 +10,7 @@ import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages |
| 10 | 10 | |
| 11 | 11 | export const option: PublicPresetOptions = { |
| 12 | 12 | componetDesign: false, |
| 13 | + trendQuery: false, | |
| 13 | 14 | }; |
| 14 | 15 | |
| 15 | 16 | export default class Config extends PublicConfigClass implements CreateComponentType { | ... | ... |
| ... | ... | @@ -26,7 +26,7 @@ export enum VideoOriginalEnum { |
| 26 | 26 | |
| 27 | 27 | export enum VideoOriginalNameEnum { |
| 28 | 28 | CUSTOM = '自定义', |
| 29 | - VIDEO_MANAGE = '流媒体获取', | |
| 29 | + VIDEO_MANAGE = '平台获取', | |
| 30 | 30 | } |
| 31 | 31 | |
| 32 | 32 | export const formSchemas: FormSchema[] = [ |
| ... | ... | @@ -36,15 +36,29 @@ export const formSchemas: FormSchema[] = [ |
| 36 | 36 | label: '视频来源', |
| 37 | 37 | colProps: { span: 8 }, |
| 38 | 38 | defaultValue: VideoOriginalEnum.CUSTOM, |
| 39 | - componentProps: { | |
| 40 | - placeholder: '请选择视频来源', | |
| 41 | - options: [ | |
| 42 | - { label: VideoOriginalNameEnum[VideoOriginalEnum.CUSTOM], value: VideoOriginalEnum.CUSTOM }, | |
| 43 | - { | |
| 44 | - label: VideoOriginalNameEnum[VideoOriginalEnum.VIDEO_MANAGE], | |
| 45 | - value: VideoOriginalEnum.VIDEO_MANAGE, | |
| 39 | + componentProps: ({ formActionType }) => { | |
| 40 | + const { setFieldsValue } = formActionType; | |
| 41 | + return { | |
| 42 | + placeholder: '请选择视频来源', | |
| 43 | + options: [ | |
| 44 | + { | |
| 45 | + label: VideoOriginalNameEnum[VideoOriginalEnum.CUSTOM], | |
| 46 | + value: VideoOriginalEnum.CUSTOM, | |
| 47 | + }, | |
| 48 | + { | |
| 49 | + label: VideoOriginalNameEnum[VideoOriginalEnum.VIDEO_MANAGE], | |
| 50 | + value: VideoOriginalEnum.VIDEO_MANAGE, | |
| 51 | + }, | |
| 52 | + ], | |
| 53 | + onChange() { | |
| 54 | + setFieldsValue({ | |
| 55 | + [FormFieldEnum.ACCESS_MODE]: null, | |
| 56 | + [FormFieldEnum.ORGANIZATION_ID]: null, | |
| 57 | + [FormFieldEnum.VIDEO_PLAY_URL]: null, | |
| 58 | + [FormFieldEnum.VIDEO_ID]: null, | |
| 59 | + }); | |
| 46 | 60 | }, |
| 47 | - ], | |
| 61 | + }; | |
| 48 | 62 | }, |
| 49 | 63 | }, |
| 50 | 64 | { |
| ... | ... | @@ -69,9 +83,19 @@ export const formSchemas: FormSchema[] = [ |
| 69 | 83 | label: '组织', |
| 70 | 84 | colProps: { span: 8 }, |
| 71 | 85 | ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.VIDEO_MANAGE, |
| 72 | - componentProps: { | |
| 73 | - showCreate: false, | |
| 74 | - allowClean: true, | |
| 86 | + componentProps: ({ formActionType }) => { | |
| 87 | + const { setFieldsValue } = formActionType; | |
| 88 | + return { | |
| 89 | + showCreate: false, | |
| 90 | + allowClean: true, | |
| 91 | + onChange() { | |
| 92 | + setFieldsValue({ | |
| 93 | + [FormFieldEnum.ACCESS_MODE]: null, | |
| 94 | + [FormFieldEnum.VIDEO_PLAY_URL]: null, | |
| 95 | + [FormFieldEnum.VIDEO_ID]: null, | |
| 96 | + }); | |
| 97 | + }, | |
| 98 | + }; | |
| 75 | 99 | }, |
| 76 | 100 | }, |
| 77 | 101 | { |
| ... | ... | @@ -91,7 +115,6 @@ export const formSchemas: FormSchema[] = [ |
| 91 | 115 | valueField: 'id', |
| 92 | 116 | resultField: 'data', |
| 93 | 117 | onChange(value: string, options: CameraModel) { |
| 94 | - console.log(options); | |
| 95 | 118 | setFieldsValue({ |
| 96 | 119 | [FormFieldEnum.ACCESS_MODE]: value ? (options || {}).accessMode : null, |
| 97 | 120 | [FormFieldEnum.VIDEO_PLAY_URL]: value ? (options || {}).videoUrl : null, | ... | ... |
| ... | ... | @@ -2,14 +2,17 @@ |
| 2 | 2 | import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type'; |
| 3 | 3 | import { option } from './config'; |
| 4 | 4 | import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video'; |
| 5 | - import { computed } from 'vue'; | |
| 5 | + import { computed, onMounted, onUnmounted, toRaw } from 'vue'; | |
| 6 | 6 | import { VideoJsPlayerOptions } from 'video.js'; |
| 7 | 7 | import { AccessMode } from '/@/views/camera/manage/config.data'; |
| 8 | - import { onMounted } from 'vue'; | |
| 9 | 8 | import { unref } from 'vue'; |
| 10 | - import { getStreamingPlayUrl } from '/@/api/camera/cameraManager'; | |
| 9 | + import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '/@/api/camera/cameraManager'; | |
| 11 | 10 | import { ref } from 'vue'; |
| 12 | 11 | import { Spin } from 'ant-design-vue'; |
| 12 | + import { useFingerprint } from '/@/utils/useFingerprint'; | |
| 13 | + import { isRtspProtocol } from '/@/components/Video/src/utils'; | |
| 14 | + import { isShareMode } from '/@/views/sys/share/hook'; | |
| 15 | + import { getJwtToken, getShareJwtToken } from '/@/utils/auth'; | |
| 13 | 16 | |
| 14 | 17 | const props = defineProps<{ |
| 15 | 18 | config: ComponentPropsConfigType<typeof option>; |
| ... | ... | @@ -17,15 +20,9 @@ |
| 17 | 20 | |
| 18 | 21 | const loading = ref(true); |
| 19 | 22 | |
| 20 | - const getIsSelectPreviewMode = computed(() => { | |
| 21 | - return props.config.option.mode === ComponentMode.SELECT_PREVIEW; | |
| 22 | - }); | |
| 23 | + const basicVideoPlayEl = ref<Nullable<InstanceType<typeof BasicVideoPlay>>>(null); | |
| 23 | 24 | |
| 24 | - const getIsStreamingMode = computed(() => { | |
| 25 | - const { option } = props.config; | |
| 26 | - const { videoConfig } = option; | |
| 27 | - return videoConfig?.accessMode === AccessMode.Streaming; | |
| 28 | - }); | |
| 25 | + const withToken = ref(false); | |
| 29 | 26 | |
| 30 | 27 | const playSource = ref<Record<'src' | 'type', string>>( |
| 31 | 28 | {} as unknown as Record<'src' | 'type', string> |
| ... | ... | @@ -34,55 +31,79 @@ |
| 34 | 31 | const exampleVideoPlay = |
| 35 | 32 | 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm'; |
| 36 | 33 | |
| 37 | - const getVideoPlaySources = computed(() => { | |
| 38 | - const { option } = props.config; | |
| 39 | - const { videoConfig } = option; | |
| 40 | - const { url } = videoConfig || {}; | |
| 41 | - return Object.assign( | |
| 42 | - { | |
| 43 | - src: unref(getIsSelectPreviewMode) ? exampleVideoPlay : url, | |
| 44 | - type: getVideoTypeByUrl(url || ''), | |
| 45 | - }, | |
| 46 | - unref(playSource) | |
| 47 | - ); | |
| 48 | - }); | |
| 49 | - | |
| 50 | 34 | const getOptions = computed<VideoJsPlayerOptions>(() => { |
| 51 | 35 | return { |
| 52 | 36 | width: '100%' as unknown as number, |
| 53 | 37 | height: '100%' as unknown as number, |
| 54 | - sources: unref(getVideoPlaySources).src | |
| 55 | - ? ([unref(getVideoPlaySources)] as VideoJsPlayerOptions['sources']) | |
| 56 | - : [], | |
| 57 | 38 | }; |
| 58 | 39 | }); |
| 59 | 40 | |
| 41 | + const { getResult } = useFingerprint(); | |
| 42 | + | |
| 60 | 43 | const handleGetVideoPlayUrl = async () => { |
| 61 | 44 | try { |
| 62 | - if (unref(getIsStreamingMode) && !unref(getVideoPlaySources).src) { | |
| 63 | - const { option } = props.config; | |
| 64 | - const { videoConfig } = option; | |
| 65 | - if (!videoConfig?.id) return; | |
| 66 | - const { data: { url } = { url: '' } } = await getStreamingPlayUrl(videoConfig?.id); | |
| 67 | - playSource.value = { | |
| 68 | - src: url, | |
| 69 | - type: getVideoTypeByUrl(url), | |
| 45 | + const instance = unref(basicVideoPlayEl)?.getInstance(); | |
| 46 | + const { config } = props; | |
| 47 | + const { option } = config; | |
| 48 | + const { videoConfig, uuid } = option || {}; | |
| 49 | + if (!uuid) return; | |
| 50 | + const { url, id, accessMode } = videoConfig || {}; | |
| 51 | + const type = getVideoTypeByUrl(url!); | |
| 52 | + let playUrl = url; | |
| 53 | + if (accessMode === AccessMode.Streaming && id) { | |
| 54 | + const { data: { url } = { url: '' } } = await getStreamingPlayUrl(id!); | |
| 55 | + playUrl = url; | |
| 56 | + } | |
| 57 | + | |
| 58 | + if (isRtspProtocol(url!)) { | |
| 59 | + const result = await getResult(); | |
| 60 | + const { visitorId } = result; | |
| 61 | + playUrl = getFlvPlayUrl(playUrl!, visitorId); | |
| 62 | + withToken.value = true; | |
| 63 | + | |
| 64 | + (instance!.options_ as any).flvjs.config.headers = { | |
| 65 | + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | |
| 70 | 66 | }; |
| 71 | 67 | } |
| 68 | + | |
| 69 | + playSource.value = { | |
| 70 | + src: playUrl!, | |
| 71 | + type, | |
| 72 | + }; | |
| 73 | + | |
| 74 | + instance?.src(toRaw(unref(playSource))); | |
| 75 | + instance?.play(); | |
| 72 | 76 | } finally { |
| 73 | 77 | loading.value = false; |
| 74 | 78 | } |
| 75 | 79 | }; |
| 76 | 80 | |
| 81 | + const handleSelectPreview = () => { | |
| 82 | + loading.value = false; | |
| 83 | + const instance = unref(basicVideoPlayEl)?.getInstance(); | |
| 84 | + instance?.src({ type: getVideoTypeByUrl(exampleVideoPlay), src: exampleVideoPlay }); | |
| 85 | + instance?.play(); | |
| 86 | + }; | |
| 87 | + | |
| 77 | 88 | onMounted(() => { |
| 78 | - handleGetVideoPlayUrl(); | |
| 89 | + const { option } = props.config; | |
| 90 | + const { mode } = option; | |
| 91 | + mode === ComponentMode.SELECT_PREVIEW ? handleSelectPreview() : handleGetVideoPlayUrl(); | |
| 92 | + }); | |
| 93 | + | |
| 94 | + onUnmounted(async () => { | |
| 95 | + const { visitorId } = await getResult(); | |
| 96 | + const { option } = props.config; | |
| 97 | + const { videoConfig } = option || {}; | |
| 98 | + const { url } = videoConfig || {}; | |
| 99 | + isRtspProtocol(url!) && closeFlvPlay(url!, visitorId); | |
| 79 | 100 | }); |
| 80 | 101 | </script> |
| 81 | 102 | |
| 82 | 103 | <template> |
| 83 | 104 | <main class="w-full h-full flex flex-col justify-center items-center p-2"> |
| 84 | 105 | <Spin :spinning="loading" wrapper-class-name="video-spin"> |
| 85 | - <BasicVideoPlay :options="getOptions" /> | |
| 106 | + <BasicVideoPlay ref="basicVideoPlayEl" :options="getOptions" :with-token="withToken" /> | |
| 86 | 107 | </Spin> |
| 87 | 108 | </main> |
| 88 | 109 | </template> | ... | ... |
| ... | ... | @@ -89,6 +89,7 @@ export interface PublicPresetOptions { |
| 89 | 89 | maxScale?: number; |
| 90 | 90 | minScale?: number; |
| 91 | 91 | componetDesign?: boolean; |
| 92 | + trendQuery?: boolean; | |
| 92 | 93 | multipleDataSourceComponent?: boolean; |
| 93 | 94 | [ComponentConfigFieldEnum.FONT_COLOR]?: string; |
| 94 | 95 | [ComponentConfigFieldEnum.UNIT]?: string; |
| ... | ... | @@ -132,7 +133,6 @@ export interface ComponentPropsConfigType<P = PublicPresetOptions, O = DataSourc |
| 132 | 133 | |
| 133 | 134 | export interface PackagesType { |
| 134 | 135 | [PackagesCategoryEnum.TEXT]: ConfigType[]; |
| 135 | - [PackagesCategoryEnum.PICTURE]: ConfigType[]; | |
| 136 | 136 | [PackagesCategoryEnum.INSTRUMENT]: ConfigType[]; |
| 137 | 137 | [PackagesCategoryEnum.CONTROL]: ConfigType[]; |
| 138 | 138 | [PackagesCategoryEnum.MAP]: ConfigType[]; | ... | ... |
| ... | ... | @@ -17,6 +17,7 @@ |
| 17 | 17 | import { Layout } from 'vue3-grid-layout'; |
| 18 | 18 | import { DataComponentRecord } from '/@/api/dataBoard/model'; |
| 19 | 19 | import { computed } from 'vue'; |
| 20 | + import { useGetComponentConfig } from '../../../packages/hook/useGetComponetConfig'; | |
| 20 | 21 | |
| 21 | 22 | const props = defineProps<{ |
| 22 | 23 | sourceInfo: WidgetDataType; |
| ... | ... | @@ -79,6 +80,12 @@ |
| 79 | 80 | return { id, w, h, x, y } as ComponentLayoutType; |
| 80 | 81 | }); |
| 81 | 82 | |
| 83 | + const hasTrendQueryIcon = computed(() => { | |
| 84 | + const frontId = props.sourceInfo.frontId; | |
| 85 | + const config = useGetComponentConfig(frontId); | |
| 86 | + return !!config.persetOption?.trendQuery; | |
| 87 | + }); | |
| 88 | + | |
| 82 | 89 | async function handleCopy() { |
| 83 | 90 | const id = props.sourceInfo.id; |
| 84 | 91 | const copyRecord = props.rawDataSource.componentData.find((item) => item.id === id); |
| ... | ... | @@ -123,7 +130,7 @@ |
| 123 | 130 | </Tooltip> |
| 124 | 131 | |
| 125 | 132 | <div v-if="!getIsSharePage" class="flex items-center w-16 justify-evenly"> |
| 126 | - <Tooltip v-if="!isCustomerUser" title="趋势"> | |
| 133 | + <Tooltip v-if="!isCustomerUser && hasTrendQueryIcon" title="趋势"> | |
| 127 | 134 | <AreaChartOutlined class="text-lg" @click="handleOpenTrendModal" /> |
| 128 | 135 | </Tooltip> |
| 129 | 136 | <AuthDropDown | ... | ... |