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 | ... | ... |