Commit 8cfa3384bd19fbceeff2bfc15d5e460393429387

Authored by xp.Huang
2 parents c8506463 bc4d5806

Merge branch 'feat/data-board-video-component-support-rtsp-protocol' into 'main_dev'

feat: 数据看板视频组件支持rtsp协议播放

See merge request yunteng/thingskit-front!671
@@ -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