Commit 445cfec6d1aac75bfc1127a720030c8777b3f628
Merge branch 'feat/video-component-support-gbt' into 'main_dev'
feat: 视频组件支持gbt See merge request yunteng/thingskit-scada!220
Showing
7 changed files
with
176 additions
and
39 deletions
@@ -68,6 +68,8 @@ export interface VideoOptionType { | @@ -68,6 +68,8 @@ export interface VideoOptionType { | ||
68 | orgId: string | 68 | orgId: string |
69 | videoComponentFlag: boolean | 69 | videoComponentFlag: boolean |
70 | videoUrl: string | 70 | videoUrl: string |
71 | + channelId?: string | ||
72 | + deviceId?: string | ||
71 | } | 73 | } |
72 | 74 | ||
73 | export interface AlarmListOptionType { | 75 | export interface AlarmListOptionType { |
1 | +import type { VideoChannelPlayAddressType, VideoItemRecordType } from './model' | ||
1 | import { defHttp } from '@/utils/http' | 2 | import { defHttp } from '@/utils/http' |
2 | 3 | ||
3 | enum Api { | 4 | enum Api { |
4 | GET_VIDEO_LIST = '/video', | 5 | GET_VIDEO_LIST = '/video', |
5 | STREAMING_PLAY_GET_URL = '/video/url', | 6 | STREAMING_PLAY_GET_URL = '/video/url', |
7 | + GET_VIDEO_ALL_LIST = '/video/list', | ||
6 | RTSP_CLOSEFLV = '/rtsp.closeFlv', | 8 | RTSP_CLOSEFLV = '/rtsp.closeFlv', |
9 | + GET_VIDEO_CONTROL_START = '/video/control/start', | ||
10 | + GET_VIDEO_CONTROL_STOP = '/video/control/stop', | ||
7 | } | 11 | } |
8 | 12 | ||
9 | // 获取视频组件--->频流 | 13 | // 获取视频组件--->频流 |
10 | export const getVideoList = (organizationId?: string) => { | 14 | export const getVideoList = (organizationId?: string) => { |
11 | - return defHttp.get({ | 15 | + return defHttp.get<{ items: VideoItemRecordType[] }>({ |
12 | url: Api.GET_VIDEO_LIST, | 16 | url: Api.GET_VIDEO_LIST, |
13 | params: { | 17 | params: { |
14 | organizationId, | 18 | organizationId, |
@@ -35,3 +39,21 @@ export const closeFlvPlay = (url: string, browserId: string) => { | @@ -35,3 +39,21 @@ export const closeFlvPlay = (url: string, browserId: string) => { | ||
35 | }) | 39 | }) |
36 | } | 40 | } |
37 | 41 | ||
42 | +export const getVideoControlStart = ({ | ||
43 | + deviceId, | ||
44 | + channelId, | ||
45 | +}: Record<'deviceId' | 'channelId', string>) => { | ||
46 | + return defHttp.get<VideoChannelPlayAddressType>( | ||
47 | + { | ||
48 | + url: `${Api.GET_VIDEO_CONTROL_START}/${deviceId}/${channelId}`, | ||
49 | + timeout: 30 * 1000, | ||
50 | + }, | ||
51 | + {}, | ||
52 | + ) | ||
53 | +} | ||
54 | +export const getCameraList = (params: Record<'organizationId', string>) => { | ||
55 | + return defHttp.get<{ data: VideoItemRecordType[] }>({ | ||
56 | + url: Api.GET_VIDEO_ALL_LIST, | ||
57 | + params, | ||
58 | + }) | ||
59 | +} |
1 | +export interface VideoItemRecordType { | ||
2 | + id: string | ||
3 | + creator: string | ||
4 | + createTime: string | ||
5 | + name: string | ||
6 | + enabled: boolean | ||
7 | + tenantId: string | ||
8 | + sn: string | ||
9 | + organizationId: string | ||
10 | + organizationName: string | ||
11 | + status: boolean | ||
12 | + accessMode: number | ||
13 | + playProtocol: number | ||
14 | + params: Params | ||
15 | + videoUrl: string | ||
16 | +} | ||
17 | + | ||
18 | +export interface Params { | ||
19 | + channelNo: string | ||
20 | + deviceId: string | ||
21 | +} | ||
22 | + | ||
23 | +export interface VideoChannelPlayAddressType { | ||
24 | + code: number | ||
25 | + message: string | ||
26 | + data: Data | ||
27 | +} | ||
28 | + | ||
29 | +export interface Data { | ||
30 | + app: string | ||
31 | + stream: string | ||
32 | + ip: any | ||
33 | + flv: string | ||
34 | + https_flv: string | ||
35 | + ws_flv: string | ||
36 | + wss_flv: string | ||
37 | + fmp4: string | ||
38 | + https_fmp4: string | ||
39 | + ws_fmp4: string | ||
40 | + wss_fmp4: string | ||
41 | + hls: string | ||
42 | + https_hls: string | ||
43 | + ws_hls: string | ||
44 | + wss_hls: string | ||
45 | + ts: string | ||
46 | + https_ts: string | ||
47 | + ws_ts: string | ||
48 | + wss_ts: any | ||
49 | + rtmp: string | ||
50 | + rtmps: string | ||
51 | + rtsp: string | ||
52 | + rtsps: string | ||
53 | + rtc: string | ||
54 | + rtcs: string | ||
55 | + mediaServerId: string | ||
56 | + tracks: Track[] | ||
57 | + startTime: any | ||
58 | + endTime: any | ||
59 | + progress: number | ||
60 | +} | ||
61 | + | ||
62 | +export interface Track { | ||
63 | + channels: number | ||
64 | + codecId: number | ||
65 | + codecIdName: any | ||
66 | + codecType: number | ||
67 | + ready: boolean | ||
68 | + sampleBit: number | ||
69 | + sampleRate: number | ||
70 | + fps: number | ||
71 | + height: number | ||
72 | + width: number | ||
73 | +} |
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | import { Button, Divider } from 'ant-design-vue' | 2 | import { Button, Divider } from 'ant-design-vue' |
3 | import { nextTick, onMounted, ref, unref } from 'vue' | 3 | import { nextTick, onMounted, ref, unref } from 'vue' |
4 | import { getOrganization } from '@/api/device' | 4 | import { getOrganization } from '@/api/device' |
5 | -import { getVideoList } from '@/api/video' | 5 | +import { getCameraList } from '@/api/video' |
6 | import { BasicForm, useForm } from '@/components/Form' | 6 | import { BasicForm, useForm } from '@/components/Form' |
7 | import { ComponentEnum, FormLayoutEnum } from '@/components/Form/src/enum' | 7 | import { ComponentEnum, FormLayoutEnum } from '@/components/Form/src/enum' |
8 | import { ContentDataFieldsEnum, ContentDataFieldsNameEnum } from '@/enums/datasource' | 8 | import { ContentDataFieldsEnum, ContentDataFieldsNameEnum } from '@/enums/datasource' |
@@ -13,6 +13,8 @@ import { createPublicFormContext } from '@/core/Library/components/PublicForm/us | @@ -13,6 +13,8 @@ import { createPublicFormContext } from '@/core/Library/components/PublicForm/us | ||
13 | import type { NodeDataDataSourceJsonType, VideoOptionType } from '@/api/node/model' | 13 | import type { NodeDataDataSourceJsonType, VideoOptionType } from '@/api/node/model' |
14 | import { useMessage } from '@/hooks/web/useMessage' | 14 | import { useMessage } from '@/hooks/web/useMessage' |
15 | import { useSavePageContent } from '@/core/Library/hook/useSavePageContent' | 15 | import { useSavePageContent } from '@/core/Library/hook/useSavePageContent' |
16 | +import { VideoAccessModeEnum } from '@/enums/videoEnum' | ||
17 | +import type { VideoItemRecordType } from '@/api/video/model' | ||
16 | 18 | ||
17 | const props = defineProps<ConfigComponentProps>() | 19 | const props = defineProps<ConfigComponentProps>() |
18 | const { organizationId } = useParseParams() | 20 | const { organizationId } = useParseParams() |
@@ -20,6 +22,23 @@ const nodeDataActinType = useNodeData({ cell: props.cell!, immediate: false }) | @@ -20,6 +22,23 @@ const nodeDataActinType = useNodeData({ cell: props.cell!, immediate: false }) | ||
20 | 22 | ||
21 | const { getNodeData, getNodeAllData, saveNodeAllData } = nodeDataActinType | 23 | const { getNodeData, getNodeAllData, saveNodeAllData } = nodeDataActinType |
22 | 24 | ||
25 | +enum VideoFormFieldsEnum { | ||
26 | + VIDEO_ID = 'id', | ||
27 | + VIDEO_URL = 'videoUrl', | ||
28 | + ACCESS_MODE = 'accessMode', | ||
29 | + VIDEO_FLAG = 'videoComponentFlag', | ||
30 | + CHANNEL_ID = 'channelId', | ||
31 | + DEVICE_ID = 'deviceId', | ||
32 | +} | ||
33 | + | ||
34 | +enum VideoFormFieldsNameEnum { | ||
35 | + VIDEO_ID = '视频流', | ||
36 | + ACCESS_MODE = 'ACCESS_MODE', | ||
37 | + VIDEO_URL = '视频地址', | ||
38 | + CHANNEL_ID = '通道号', | ||
39 | + DEVICE_ID = '设备id', | ||
40 | +} | ||
41 | + | ||
23 | const loading = ref(false) | 42 | const loading = ref(false) |
24 | 43 | ||
25 | const [register, { getFieldsValue, validate, setFieldsValue }] = useForm({ | 44 | const [register, { getFieldsValue, validate, setFieldsValue }] = useForm({ |
@@ -36,45 +55,63 @@ const [register, { getFieldsValue, validate, setFieldsValue }] = useForm({ | @@ -36,45 +55,63 @@ const [register, { getFieldsValue, validate, setFieldsValue }] = useForm({ | ||
36 | labelField: 'name', | 55 | labelField: 'name', |
37 | valueField: 'id', | 56 | valueField: 'id', |
38 | onChange() { | 57 | onChange() { |
39 | - formModel[ContentDataFieldsEnum.VIDEO_ID] = null | 58 | + formModel[VideoFormFieldsEnum.VIDEO_ID] = null |
59 | + formModel[VideoFormFieldsEnum.CHANNEL_ID] = null | ||
60 | + formModel[VideoFormFieldsEnum.DEVICE_ID] = null | ||
40 | }, | 61 | }, |
41 | } | 62 | } |
42 | }, | 63 | }, |
43 | }, | 64 | }, |
44 | { | 65 | { |
45 | - field: ContentDataFieldsEnum.VIDEO_ID, | ||
46 | - label: ContentDataFieldsNameEnum.VIDEO_ID, | 66 | + field: VideoFormFieldsEnum.VIDEO_ID, |
67 | + label: VideoFormFieldsNameEnum.VIDEO_ID, | ||
47 | component: ComponentEnum.API_SELECT, | 68 | component: ComponentEnum.API_SELECT, |
48 | componentProps: ({ formModel }) => { | 69 | componentProps: ({ formModel }) => { |
49 | return { | 70 | return { |
50 | - api: getVideoList, | ||
51 | - params: formModel[ContentDataFieldsEnum.ORG_ID], | ||
52 | - | 71 | + api: getCameraList, |
72 | + params: { | ||
73 | + organizationId: formModel[ContentDataFieldsEnum.ORG_ID], | ||
74 | + }, | ||
53 | fieldNames: { label: 'name', value: 'id' }, | 75 | fieldNames: { label: 'name', value: 'id' }, |
54 | - resultField: 'items', | ||
55 | - onSelect(value: string, option: any) { | ||
56 | - formModel[ContentDataFieldsEnum.ACCESS_MODE] = value && option ? option.accessMode : null | ||
57 | - formModel[ContentDataFieldsEnum.VIDEO_URL] = value && option ? option.videoUrl : null | ||
58 | - formModel[ContentDataFieldsEnum.VIDEO_FLAG] = true | 76 | + resultField: 'data', |
77 | + onSelect(_: string, option: VideoItemRecordType) { | ||
78 | + const accessMode = option?.accessMode | ||
79 | + formModel[VideoFormFieldsEnum.ACCESS_MODE] = accessMode | ||
80 | + formModel[VideoFormFieldsEnum.VIDEO_URL] = option?.videoUrl | ||
81 | + formModel[VideoFormFieldsEnum.CHANNEL_ID] = accessMode === VideoAccessModeEnum.GBT28181 ? option?.params?.channelNo : null | ||
82 | + formModel[VideoFormFieldsEnum.DEVICE_ID] = accessMode === VideoAccessModeEnum.GBT28181 ? option?.params?.deviceId : null | ||
83 | + formModel[VideoFormFieldsEnum.VIDEO_FLAG] = true | ||
59 | }, | 84 | }, |
60 | } | 85 | } |
61 | }, | 86 | }, |
62 | }, | 87 | }, |
63 | { | 88 | { |
64 | - field: ContentDataFieldsEnum.ACCESS_MODE, | ||
65 | - label: ContentDataFieldsNameEnum.ACCESS_MODE, | 89 | + field: VideoFormFieldsEnum.ACCESS_MODE, |
90 | + label: VideoFormFieldsNameEnum.ACCESS_MODE, | ||
91 | + component: ComponentEnum.INPUT, | ||
92 | + ifShow: false, | ||
93 | + }, | ||
94 | + { | ||
95 | + field: VideoFormFieldsEnum.DEVICE_ID, | ||
96 | + label: VideoFormFieldsNameEnum.DEVICE_ID, | ||
97 | + component: ComponentEnum.INPUT, | ||
98 | + ifShow: false, | ||
99 | + }, | ||
100 | + { | ||
101 | + field: VideoFormFieldsEnum.CHANNEL_ID, | ||
102 | + label: VideoFormFieldsNameEnum.CHANNEL_ID, | ||
66 | component: ComponentEnum.INPUT, | 103 | component: ComponentEnum.INPUT, |
67 | ifShow: false, | 104 | ifShow: false, |
68 | }, | 105 | }, |
69 | { | 106 | { |
70 | - field: ContentDataFieldsEnum.VIDEO_URL, | ||
71 | - label: ContentDataFieldsNameEnum.VIDEO_URL, | 107 | + field: VideoFormFieldsEnum.VIDEO_URL, |
108 | + label: VideoFormFieldsNameEnum.VIDEO_URL, | ||
72 | component: ComponentEnum.INPUT, | 109 | component: ComponentEnum.INPUT, |
73 | ifShow: false, | 110 | ifShow: false, |
74 | }, | 111 | }, |
75 | { | 112 | { |
76 | - field: ContentDataFieldsEnum.VIDEO_FLAG, | ||
77 | - label: ContentDataFieldsEnum.VIDEO_FLAG, | 113 | + field: VideoFormFieldsEnum.VIDEO_FLAG, |
114 | + label: VideoFormFieldsEnum.VIDEO_FLAG, | ||
78 | component: ComponentEnum.INPUT, | 115 | component: ComponentEnum.INPUT, |
79 | ifShow: false, | 116 | ifShow: false, |
80 | }, | 117 | }, |
@@ -4,13 +4,14 @@ import 'video.js/dist/video-js.css' | @@ -4,13 +4,14 @@ import 'video.js/dist/video-js.css' | ||
4 | 4 | ||
5 | import type { VideoJsPlayerOptions } from 'video.js' | 5 | import type { VideoJsPlayerOptions } from 'video.js' |
6 | import { BasicVideoPlay } from './component/index.ts' | 6 | import { BasicVideoPlay } from './component/index.ts' |
7 | -import { getVideoTypeByUrl, isRtspProtocol, useFingerprint } from './component/config' | 7 | +import { VideoPlayerType, getVideoTypeByUrl, isRtspProtocol, useFingerprint } from './component/config' |
8 | 8 | ||
9 | import { getJwtToken, getShareJwtToken } from '@/utils/auth' | 9 | import { getJwtToken, getShareJwtToken } from '@/utils/auth' |
10 | import { isLightboxMode, isShareMode } from '@/utils/env' | 10 | import { isLightboxMode, isShareMode } from '@/utils/env' |
11 | import type { CreateComponentType } from '@/core/Library/types' | 11 | import type { CreateComponentType } from '@/core/Library/types' |
12 | -import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '@/api/video' | 12 | +import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl, getVideoControlStart } from '@/api/video' |
13 | import { useContentDataStore } from '@/store/modules/contentData' | 13 | import { useContentDataStore } from '@/store/modules/contentData' |
14 | +import { VideoAccessModeEnum } from '@/enums/videoEnum' | ||
14 | 15 | ||
15 | const props = defineProps<{ | 16 | const props = defineProps<{ |
16 | config: CreateComponentType | 17 | config: CreateComponentType |
@@ -48,16 +49,24 @@ const playSource = ref<Record<'src' | 'type', string>>( | @@ -48,16 +49,24 @@ const playSource = ref<Record<'src' | 'type', string>>( | ||
48 | const handleGetVideoPlay = async () => { | 49 | const handleGetVideoPlay = async () => { |
49 | const { dataSourceJson } = unref(videoConfig)[0] || {} | 50 | const { dataSourceJson } = unref(videoConfig)[0] || {} |
50 | const { videoOption } = dataSourceJson || {} | 51 | const { videoOption } = dataSourceJson || {} |
51 | - const { id, accessMode, videoUrl } = videoOption || {} | 52 | + const { id, accessMode, videoUrl, deviceId, channelId } = videoOption || {} |
52 | let type = getVideoTypeByUrl(videoUrl!) | 53 | let type = getVideoTypeByUrl(videoUrl!) |
53 | let playUrl = videoUrl | 54 | let playUrl = videoUrl |
54 | 55 | ||
55 | // 判断是否是流媒体播放 | 56 | // 判断是否是流媒体播放 |
56 | - if (accessMode === 1 && id) { | 57 | + if (accessMode === VideoAccessModeEnum.Streaming && id) { |
57 | const { data: { url } = { url: '' } } = await getStreamingPlayUrl(id!) | 58 | const { data: { url } = { url: '' } } = await getStreamingPlayUrl(id!) |
58 | playUrl = url | 59 | playUrl = url |
59 | playUrl && (type = getVideoTypeByUrl(playUrl!)) | 60 | playUrl && (type = getVideoTypeByUrl(playUrl!)) |
60 | } | 61 | } |
62 | + else if (accessMode === VideoAccessModeEnum.GBT28181 && deviceId && channelId) { | ||
63 | + const { | ||
64 | + data: { flv }, | ||
65 | + } = await getVideoControlStart({ channelId, deviceId }) | ||
66 | + | ||
67 | + playUrl = flv | ||
68 | + type = VideoPlayerType.flv | ||
69 | + } | ||
61 | 70 | ||
62 | // 判断是否是rtsp播放 | 71 | // 判断是否是rtsp播放 |
63 | if (isRtspProtocol(videoUrl!)) { | 72 | if (isRtspProtocol(videoUrl!)) { |
@@ -128,9 +137,7 @@ onUnmounted(async () => { | @@ -128,9 +137,7 @@ onUnmounted(async () => { | ||
128 | <div class="w-full h-full flex justify-center items-center"> | 137 | <div class="w-full h-full flex justify-center items-center"> |
129 | <!-- <Spin :spinning="loading" wrapper-class-name="video-spin" class="w-full h-full"> --> | 138 | <!-- <Spin :spinning="loading" wrapper-class-name="video-spin" class="w-full h-full"> --> |
130 | <BasicVideoPlay | 139 | <BasicVideoPlay |
131 | - ref="basicVideoPlayEl" | ||
132 | - :options="getOptions" | ||
133 | - :with-token="withToken" | 140 | + ref="basicVideoPlayEl" :options="getOptions" :with-token="withToken" |
134 | :immediate-init-on-mounted="false" | 141 | :immediate-init-on-mounted="false" |
135 | /> | 142 | /> |
136 | <!-- </Spin> --> | 143 | <!-- </Spin> --> |
@@ -139,10 +146,10 @@ onUnmounted(async () => { | @@ -139,10 +146,10 @@ onUnmounted(async () => { | ||
139 | 146 | ||
140 | <style lang="less" scoped> | 147 | <style lang="less" scoped> |
141 | .video-spin { | 148 | .video-spin { |
142 | - @apply w-full h-full; | 149 | + @apply w-full h-full; |
143 | 150 | ||
144 | - :deep(.ant-spin-container) { | ||
145 | - @apply w-full h-full; | ||
146 | - } | 151 | + :deep(.ant-spin-container) { |
152 | + @apply w-full h-full; | ||
147 | } | 153 | } |
154 | +} | ||
148 | </style> | 155 | </style> |
@@ -195,11 +195,6 @@ export enum ContentDataFieldsEnum { | @@ -195,11 +195,6 @@ export enum ContentDataFieldsEnum { | ||
195 | ORG_ID = 'orgId', | 195 | ORG_ID = 'orgId', |
196 | ATTR = 'attr', | 196 | ATTR = 'attr', |
197 | DEVICE_NAME = 'deviceName', | 197 | DEVICE_NAME = 'deviceName', |
198 | - VIDEO_FILTER = 'videoFilter', | ||
199 | - VIDEO_ID = 'id', | ||
200 | - VIDEO_URL = 'videoUrl', | ||
201 | - ACCESS_MODE = 'accessMode', | ||
202 | - VIDEO_FLAG = 'videoComponentFlag', | ||
203 | 198 | ||
204 | CODE_TYPE = 'codeType', | 199 | CODE_TYPE = 'codeType', |
205 | } | 200 | } |
@@ -211,10 +206,6 @@ export enum ContentDataFieldsNameEnum { | @@ -211,10 +206,6 @@ export enum ContentDataFieldsNameEnum { | ||
211 | DEVICE_ID = '设备', | 206 | DEVICE_ID = '设备', |
212 | ATTR = '属性', | 207 | ATTR = '属性', |
213 | deviceName = '设备名称', | 208 | deviceName = '设备名称', |
214 | - VIDEO_ID = '视频流', | ||
215 | - ACCESS_MODE = 'ACCESS_MODE', | ||
216 | - VIDEO_URL = '视频地址', | ||
217 | - | ||
218 | } | 209 | } |
219 | 210 | ||
220 | export enum VariableImageSourceEnum { | 211 | export enum VariableImageSourceEnum { |