Commit 445cfec6d1aac75bfc1127a720030c8777b3f628

Authored by xp.Huang
2 parents 16a6e0fc dff69cdb

Merge branch 'feat/video-component-support-gbt' into 'main_dev'

feat: 视频组件支持gbt

See merge request yunteng/thingskit-scada!220
@@ -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 {
  1 +export enum VideoAccessModeEnum {
  2 + ManuallyEnter = 0,
  3 + Streaming = 1,
  4 + GBT28181 = 2,
  5 +}