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
| 1 | +import type { VideoChannelPlayAddressType, VideoItemRecordType } from './model' | |
| 1 | 2 | import { defHttp } from '@/utils/http' | 
| 2 | 3 | |
| 3 | 4 | enum Api { | 
| 4 | 5 | GET_VIDEO_LIST = '/video', | 
| 5 | 6 | STREAMING_PLAY_GET_URL = '/video/url', | 
| 7 | + GET_VIDEO_ALL_LIST = '/video/list', | |
| 6 | 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 | 14 | export const getVideoList = (organizationId?: string) => { | 
| 11 | - return defHttp.get({ | |
| 15 | + return defHttp.get<{ items: VideoItemRecordType[] }>({ | |
| 12 | 16 | url: Api.GET_VIDEO_LIST, | 
| 13 | 17 | params: { | 
| 14 | 18 | organizationId, | 
| ... | ... | @@ -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 | 2 | import { Button, Divider } from 'ant-design-vue' | 
| 3 | 3 | import { nextTick, onMounted, ref, unref } from 'vue' | 
| 4 | 4 | import { getOrganization } from '@/api/device' | 
| 5 | -import { getVideoList } from '@/api/video' | |
| 5 | +import { getCameraList } from '@/api/video' | |
| 6 | 6 | import { BasicForm, useForm } from '@/components/Form' | 
| 7 | 7 | import { ComponentEnum, FormLayoutEnum } from '@/components/Form/src/enum' | 
| 8 | 8 | import { ContentDataFieldsEnum, ContentDataFieldsNameEnum } from '@/enums/datasource' | 
| ... | ... | @@ -13,6 +13,8 @@ import { createPublicFormContext } from '@/core/Library/components/PublicForm/us | 
| 13 | 13 | import type { NodeDataDataSourceJsonType, VideoOptionType } from '@/api/node/model' | 
| 14 | 14 | import { useMessage } from '@/hooks/web/useMessage' | 
| 15 | 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 | 19 | const props = defineProps<ConfigComponentProps>() | 
| 18 | 20 | const { organizationId } = useParseParams() | 
| ... | ... | @@ -20,6 +22,23 @@ const nodeDataActinType = useNodeData({ cell: props.cell!, immediate: false }) | 
| 20 | 22 | |
| 21 | 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 | 42 | const loading = ref(false) | 
| 24 | 43 | |
| 25 | 44 | const [register, { getFieldsValue, validate, setFieldsValue }] = useForm({ | 
| ... | ... | @@ -36,45 +55,63 @@ const [register, { getFieldsValue, validate, setFieldsValue }] = useForm({ | 
| 36 | 55 | labelField: 'name', | 
| 37 | 56 | valueField: 'id', | 
| 38 | 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 | 68 | component: ComponentEnum.API_SELECT, | 
| 48 | 69 | componentProps: ({ formModel }) => { | 
| 49 | 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 | 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 | 103 | component: ComponentEnum.INPUT, | 
| 67 | 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 | 109 | component: ComponentEnum.INPUT, | 
| 73 | 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 | 115 | component: ComponentEnum.INPUT, | 
| 79 | 116 | ifShow: false, | 
| 80 | 117 | }, | ... | ... | 
| ... | ... | @@ -4,13 +4,14 @@ import 'video.js/dist/video-js.css' | 
| 4 | 4 | |
| 5 | 5 | import type { VideoJsPlayerOptions } from 'video.js' | 
| 6 | 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 | 9 | import { getJwtToken, getShareJwtToken } from '@/utils/auth' | 
| 10 | 10 | import { isLightboxMode, isShareMode } from '@/utils/env' | 
| 11 | 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 | 13 | import { useContentDataStore } from '@/store/modules/contentData' | 
| 14 | +import { VideoAccessModeEnum } from '@/enums/videoEnum' | |
| 14 | 15 | |
| 15 | 16 | const props = defineProps<{ | 
| 16 | 17 | config: CreateComponentType | 
| ... | ... | @@ -48,16 +49,24 @@ const playSource = ref<Record<'src' | 'type', string>>( | 
| 48 | 49 | const handleGetVideoPlay = async () => { | 
| 49 | 50 | const { dataSourceJson } = unref(videoConfig)[0] || {} | 
| 50 | 51 | const { videoOption } = dataSourceJson || {} | 
| 51 | - const { id, accessMode, videoUrl } = videoOption || {} | |
| 52 | + const { id, accessMode, videoUrl, deviceId, channelId } = videoOption || {} | |
| 52 | 53 | let type = getVideoTypeByUrl(videoUrl!) | 
| 53 | 54 | let playUrl = videoUrl | 
| 54 | 55 | |
| 55 | 56 | // 判断是否是流媒体播放 | 
| 56 | - if (accessMode === 1 && id) { | |
| 57 | + if (accessMode === VideoAccessModeEnum.Streaming && id) { | |
| 57 | 58 | const { data: { url } = { url: '' } } = await getStreamingPlayUrl(id!) | 
| 58 | 59 | playUrl = url | 
| 59 | 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 | 71 | // 判断是否是rtsp播放 | 
| 63 | 72 | if (isRtspProtocol(videoUrl!)) { | 
| ... | ... | @@ -128,9 +137,7 @@ onUnmounted(async () => { | 
| 128 | 137 | <div class="w-full h-full flex justify-center items-center"> | 
| 129 | 138 | <!-- <Spin :spinning="loading" wrapper-class-name="video-spin" class="w-full h-full"> --> | 
| 130 | 139 | <BasicVideoPlay | 
| 131 | - ref="basicVideoPlayEl" | |
| 132 | - :options="getOptions" | |
| 133 | - :with-token="withToken" | |
| 140 | + ref="basicVideoPlayEl" :options="getOptions" :with-token="withToken" | |
| 134 | 141 | :immediate-init-on-mounted="false" | 
| 135 | 142 | /> | 
| 136 | 143 | <!-- </Spin> --> | 
| ... | ... | @@ -139,10 +146,10 @@ onUnmounted(async () => { | 
| 139 | 146 | |
| 140 | 147 | <style lang="less" scoped> | 
| 141 | 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 | 155 | </style> | ... | ... | 
| ... | ... | @@ -195,11 +195,6 @@ export enum ContentDataFieldsEnum { | 
| 195 | 195 | ORG_ID = 'orgId', | 
| 196 | 196 | ATTR = 'attr', | 
| 197 | 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 | 199 | CODE_TYPE = 'codeType', | 
| 205 | 200 | } | 
| ... | ... | @@ -211,10 +206,6 @@ export enum ContentDataFieldsNameEnum { | 
| 211 | 206 | DEVICE_ID = '设备', | 
| 212 | 207 | ATTR = '属性', | 
| 213 | 208 | deviceName = '设备名称', | 
| 214 | - VIDEO_ID = '视频流', | |
| 215 | - ACCESS_MODE = 'ACCESS_MODE', | |
| 216 | - VIDEO_URL = '视频地址', | |
| 217 | - | |
| 218 | 209 | } | 
| 219 | 210 | |
| 220 | 211 | export enum VariableImageSourceEnum { | ... | ... |