Commit bc4d5806bed5d092eca5799f5ba5e5b62d6ca0b3

Authored by ww
1 parent f25c4521

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

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