Commit 8cfa3384bd19fbceeff2bfc15d5e460393429387
Merge branch 'feat/data-board-video-component-support-rtsp-protocol' into 'main_dev'
feat: 数据看板视频组件支持rtsp协议播放 See merge request yunteng/thingskit-front!671
Showing
6 changed files
with
112 additions
and
54 deletions
@@ -25,6 +25,7 @@ | @@ -25,6 +25,7 @@ | ||
25 | 25 | ||
26 | const getOptions = computed(() => { | 26 | const getOptions = computed(() => { |
27 | const { options, withToken } = props; | 27 | const { options, withToken } = props; |
28 | + | ||
28 | const defaultOptions: VideoJsPlayerOptions & Recordable = { | 29 | const defaultOptions: VideoJsPlayerOptions & Recordable = { |
29 | language: 'zh', | 30 | language: 'zh', |
30 | muted: true, | 31 | muted: true, |
@@ -50,8 +51,7 @@ | @@ -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 | const getWidthHeight = computed(() => { | 57 | const getWidthHeight = computed(() => { |
@@ -62,6 +62,7 @@ | @@ -62,6 +62,7 @@ | ||
62 | }); | 62 | }); |
63 | 63 | ||
64 | const init = () => { | 64 | const init = () => { |
65 | + if (unref(videoPlayInstance)) unref(videoPlayInstance)?.dispose(); | ||
65 | videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => { | 66 | videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => { |
66 | emit('ready', unref(videoPlayInstance)); | 67 | emit('ready', unref(videoPlayInstance)); |
67 | }); | 68 | }); |
@@ -76,6 +77,11 @@ | @@ -76,6 +77,11 @@ | ||
76 | videoPlayInstance.value = null; | 77 | videoPlayInstance.value = null; |
77 | emit('onUnmounted'); | 78 | emit('onUnmounted'); |
78 | }); | 79 | }); |
80 | + | ||
81 | + defineExpose({ | ||
82 | + reloadPlayer: init, | ||
83 | + getInstance: () => unref(videoPlayInstance), | ||
84 | + }); | ||
79 | </script> | 85 | </script> |
80 | 86 | ||
81 | <template> | 87 | <template> |
@@ -10,6 +10,7 @@ import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages | @@ -10,6 +10,7 @@ import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages | ||
10 | 10 | ||
11 | export const option: PublicPresetOptions = { | 11 | export const option: PublicPresetOptions = { |
12 | componetDesign: false, | 12 | componetDesign: false, |
13 | + trendQuery: false, | ||
13 | }; | 14 | }; |
14 | 15 | ||
15 | export default class Config extends PublicConfigClass implements CreateComponentType { | 16 | export default class Config extends PublicConfigClass implements CreateComponentType { |
@@ -26,7 +26,7 @@ export enum VideoOriginalEnum { | @@ -26,7 +26,7 @@ export enum VideoOriginalEnum { | ||
26 | 26 | ||
27 | export enum VideoOriginalNameEnum { | 27 | export enum VideoOriginalNameEnum { |
28 | CUSTOM = '自定义', | 28 | CUSTOM = '自定义', |
29 | - VIDEO_MANAGE = '流媒体获取', | 29 | + VIDEO_MANAGE = '平台获取', |
30 | } | 30 | } |
31 | 31 | ||
32 | export const formSchemas: FormSchema[] = [ | 32 | export const formSchemas: FormSchema[] = [ |
@@ -36,15 +36,29 @@ export const formSchemas: FormSchema[] = [ | @@ -36,15 +36,29 @@ export const formSchemas: FormSchema[] = [ | ||
36 | label: '视频来源', | 36 | label: '视频来源', |
37 | colProps: { span: 8 }, | 37 | colProps: { span: 8 }, |
38 | defaultValue: VideoOriginalEnum.CUSTOM, | 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,9 +83,19 @@ export const formSchemas: FormSchema[] = [ | ||
69 | label: '组织', | 83 | label: '组织', |
70 | colProps: { span: 8 }, | 84 | colProps: { span: 8 }, |
71 | ifShow: ({ model }) => model[FormFieldEnum.VIDEO_TYPE] === VideoOriginalEnum.VIDEO_MANAGE, | 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,7 +115,6 @@ export const formSchemas: FormSchema[] = [ | ||
91 | valueField: 'id', | 115 | valueField: 'id', |
92 | resultField: 'data', | 116 | resultField: 'data', |
93 | onChange(value: string, options: CameraModel) { | 117 | onChange(value: string, options: CameraModel) { |
94 | - console.log(options); | ||
95 | setFieldsValue({ | 118 | setFieldsValue({ |
96 | [FormFieldEnum.ACCESS_MODE]: value ? (options || {}).accessMode : null, | 119 | [FormFieldEnum.ACCESS_MODE]: value ? (options || {}).accessMode : null, |
97 | [FormFieldEnum.VIDEO_PLAY_URL]: value ? (options || {}).videoUrl : null, | 120 | [FormFieldEnum.VIDEO_PLAY_URL]: value ? (options || {}).videoUrl : null, |
@@ -2,14 +2,17 @@ | @@ -2,14 +2,17 @@ | ||
2 | import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type'; | 2 | import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type'; |
3 | import { option } from './config'; | 3 | import { option } from './config'; |
4 | import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video'; | 4 | import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video'; |
5 | - import { computed } from 'vue'; | 5 | + import { computed, onMounted, onUnmounted, toRaw } from 'vue'; |
6 | import { VideoJsPlayerOptions } from 'video.js'; | 6 | import { VideoJsPlayerOptions } from 'video.js'; |
7 | import { AccessMode } from '/@/views/camera/manage/config.data'; | 7 | import { AccessMode } from '/@/views/camera/manage/config.data'; |
8 | - import { onMounted } from 'vue'; | ||
9 | import { unref } from 'vue'; | 8 | import { unref } from 'vue'; |
10 | - import { getStreamingPlayUrl } from '/@/api/camera/cameraManager'; | 9 | + import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '/@/api/camera/cameraManager'; |
11 | import { ref } from 'vue'; | 10 | import { ref } from 'vue'; |
12 | import { Spin } from 'ant-design-vue'; | 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 | const props = defineProps<{ | 17 | const props = defineProps<{ |
15 | config: ComponentPropsConfigType<typeof option>; | 18 | config: ComponentPropsConfigType<typeof option>; |
@@ -17,15 +20,9 @@ | @@ -17,15 +20,9 @@ | ||
17 | 20 | ||
18 | const loading = ref(true); | 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 | const playSource = ref<Record<'src' | 'type', string>>( | 27 | const playSource = ref<Record<'src' | 'type', string>>( |
31 | {} as unknown as Record<'src' | 'type', string> | 28 | {} as unknown as Record<'src' | 'type', string> |
@@ -34,55 +31,79 @@ | @@ -34,55 +31,79 @@ | ||
34 | const exampleVideoPlay = | 31 | const exampleVideoPlay = |
35 | 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm'; | 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 | const getOptions = computed<VideoJsPlayerOptions>(() => { | 34 | const getOptions = computed<VideoJsPlayerOptions>(() => { |
51 | return { | 35 | return { |
52 | width: '100%' as unknown as number, | 36 | width: '100%' as unknown as number, |
53 | height: '100%' as unknown as number, | 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 | const handleGetVideoPlayUrl = async () => { | 43 | const handleGetVideoPlayUrl = async () => { |
61 | try { | 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 | } finally { | 76 | } finally { |
73 | loading.value = false; | 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 | onMounted(() => { | 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 | </script> | 101 | </script> |
81 | 102 | ||
82 | <template> | 103 | <template> |
83 | <main class="w-full h-full flex flex-col justify-center items-center p-2"> | 104 | <main class="w-full h-full flex flex-col justify-center items-center p-2"> |
84 | <Spin :spinning="loading" wrapper-class-name="video-spin"> | 105 | <Spin :spinning="loading" wrapper-class-name="video-spin"> |
85 | - <BasicVideoPlay :options="getOptions" /> | 106 | + <BasicVideoPlay ref="basicVideoPlayEl" :options="getOptions" :with-token="withToken" /> |
86 | </Spin> | 107 | </Spin> |
87 | </main> | 108 | </main> |
88 | </template> | 109 | </template> |
@@ -89,6 +89,7 @@ export interface PublicPresetOptions { | @@ -89,6 +89,7 @@ export interface PublicPresetOptions { | ||
89 | maxScale?: number; | 89 | maxScale?: number; |
90 | minScale?: number; | 90 | minScale?: number; |
91 | componetDesign?: boolean; | 91 | componetDesign?: boolean; |
92 | + trendQuery?: boolean; | ||
92 | multipleDataSourceComponent?: boolean; | 93 | multipleDataSourceComponent?: boolean; |
93 | [ComponentConfigFieldEnum.FONT_COLOR]?: string; | 94 | [ComponentConfigFieldEnum.FONT_COLOR]?: string; |
94 | [ComponentConfigFieldEnum.UNIT]?: string; | 95 | [ComponentConfigFieldEnum.UNIT]?: string; |
@@ -132,7 +133,6 @@ export interface ComponentPropsConfigType<P = PublicPresetOptions, O = DataSourc | @@ -132,7 +133,6 @@ export interface ComponentPropsConfigType<P = PublicPresetOptions, O = DataSourc | ||
132 | 133 | ||
133 | export interface PackagesType { | 134 | export interface PackagesType { |
134 | [PackagesCategoryEnum.TEXT]: ConfigType[]; | 135 | [PackagesCategoryEnum.TEXT]: ConfigType[]; |
135 | - [PackagesCategoryEnum.PICTURE]: ConfigType[]; | ||
136 | [PackagesCategoryEnum.INSTRUMENT]: ConfigType[]; | 136 | [PackagesCategoryEnum.INSTRUMENT]: ConfigType[]; |
137 | [PackagesCategoryEnum.CONTROL]: ConfigType[]; | 137 | [PackagesCategoryEnum.CONTROL]: ConfigType[]; |
138 | [PackagesCategoryEnum.MAP]: ConfigType[]; | 138 | [PackagesCategoryEnum.MAP]: ConfigType[]; |
@@ -17,6 +17,7 @@ | @@ -17,6 +17,7 @@ | ||
17 | import { Layout } from 'vue3-grid-layout'; | 17 | import { Layout } from 'vue3-grid-layout'; |
18 | import { DataComponentRecord } from '/@/api/dataBoard/model'; | 18 | import { DataComponentRecord } from '/@/api/dataBoard/model'; |
19 | import { computed } from 'vue'; | 19 | import { computed } from 'vue'; |
20 | + import { useGetComponentConfig } from '../../../packages/hook/useGetComponetConfig'; | ||
20 | 21 | ||
21 | const props = defineProps<{ | 22 | const props = defineProps<{ |
22 | sourceInfo: WidgetDataType; | 23 | sourceInfo: WidgetDataType; |
@@ -79,6 +80,12 @@ | @@ -79,6 +80,12 @@ | ||
79 | return { id, w, h, x, y } as ComponentLayoutType; | 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 | async function handleCopy() { | 89 | async function handleCopy() { |
83 | const id = props.sourceInfo.id; | 90 | const id = props.sourceInfo.id; |
84 | const copyRecord = props.rawDataSource.componentData.find((item) => item.id === id); | 91 | const copyRecord = props.rawDataSource.componentData.find((item) => item.id === id); |
@@ -123,7 +130,7 @@ | @@ -123,7 +130,7 @@ | ||
123 | </Tooltip> | 130 | </Tooltip> |
124 | 131 | ||
125 | <div v-if="!getIsSharePage" class="flex items-center w-16 justify-evenly"> | 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 | <AreaChartOutlined class="text-lg" @click="handleOpenTrendModal" /> | 134 | <AreaChartOutlined class="text-lg" @click="handleOpenTrendModal" /> |
128 | </Tooltip> | 135 | </Tooltip> |
129 | <AuthDropDown | 136 | <AuthDropDown |