Browse Source

1. 增加webrtc 功能示例;2. 增加自定义飞行区功能示例

v1.9.0
jessie.huo@dji.com 11 months ago
parent
commit
ae62a2c5ef
  1. 2
      .npmrc
  2. 2
      README.md
  3. 1072
      package-lock.json
  4. 81
      src/api/flight-area/index.ts
  5. 50
      src/components/GMap.vue
  6. 3
      src/components/common/sidebar.vue
  7. 59
      src/components/flight-area/FlightAreaActionIcon.vue
  8. 197
      src/components/flight-area/FlightAreaDevicePanel.vue
  9. 33
      src/components/flight-area/FlightAreaIcon.vue
  10. 89
      src/components/flight-area/FlightAreaItem.vue
  11. 43
      src/components/flight-area/FlightAreaPanel.vue
  12. 66
      src/components/flight-area/FlightAreaSyncPanel.vue
  13. 18
      src/components/flight-area/use-flight-area-drone-location-event.ts
  14. 17
      src/components/flight-area/use-flight-area-sync-progress-event.ts
  15. 30
      src/components/flight-area/use-flight-area-update.ts
  16. 155
      src/components/flight-area/use-flight-area.ts
  17. 36
      src/components/livestream-others.vue
  18. 15
      src/components/workspace/DividerLine.vue
  19. 21
      src/components/workspace/Title.vue
  20. 2
      src/constants/map.ts
  21. 3
      src/event-bus/index.ts
  22. 182
      src/hooks/use-g-map-cover.ts
  23. 2
      src/hooks/use-g-map.ts
  24. 16
      src/hooks/use-map-tool.ts
  25. 58
      src/hooks/use-mouse-tool.ts
  26. 98
      src/pages/page-web/projects/flight-area.vue
  27. 12
      src/pages/page-web/projects/workspace.vue
  28. 7
      src/router/index.ts
  29. 7
      src/store/index.ts
  30. 8
      src/types/device.ts
  31. 6
      src/types/enums.ts
  32. 80
      src/types/flight-area.ts
  33. 3
      src/types/map-enum.ts
  34. 27
      src/utils/genjson.ts
  35. 7
      src/utils/map-layer-utils.ts
  36. 2
      src/utils/time.ts
  37. 2
      src/vendors/jswebrtc.min.js
  38. 687
      src/vendors/srs.sdk.js
  39. 6
      src/websocket/index.ts
  40. 452
      yarn.lock

2
.npmrc

@ -1 +1 @@
registry=https://registry.npm.taobao.org/ registry=https://registry.npmmirror.com/

2
README.md

@ -14,7 +14,7 @@ For more documentation, please visit the [DJI Developer Documentation](https://d
## Latest Release ## Latest Release
Cloud API 1.8.0 was released on 11 Dec 2023. For more information, please visit the [Release Note](https://developer.dji.com/doc/cloud-api-tutorial/cn/). Cloud API 1.9.0 was released on 22 Feb 2024. For more information, please visit the [Release Note](https://developer.dji.com/doc/cloud-api-tutorial/cn/).
## License ## License

1072
package-lock.json generated

File diff suppressed because it is too large Load Diff

81
src/api/flight-area/index.ts

@ -0,0 +1,81 @@
import request from '../http/request'
import { IWorkspaceResponse } from '../http/type'
import { EFlightAreaType, ESyncStatus, FlightAreaContent } from './../../types/flight-area'
import { ELocalStorageKey } from '/@/types/enums'
import { GeojsonCoordinate } from '/@/utils/genjson'
export interface GetFlightArea {
area_id: string,
name: string,
type: EFlightAreaType,
content: FlightAreaContent,
status: boolean,
username: string,
create_time: number,
update_time: number,
}
export interface PostFlightAreaBody {
id: string,
name: string,
type: EFlightAreaType,
content: {
properties: {
color: string,
clampToGround: boolean,
},
geometry: {
type: string,
coordinates: GeojsonCoordinate | GeojsonCoordinate[][],
radius?: number,
}
}
}
export interface FlightAreaStatus {
sync_code: number,
sync_status: ESyncStatus,
sync_msg: string,
}
export interface GetDeviceStatus {
device_sn: string,
nickname?: string,
device_name?: string,
online?: boolean,
flight_area_status: FlightAreaStatus,
}
const MAP_API_PREFIX = '/map/api/v1'
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
export async function getFlightAreaList (): Promise<IWorkspaceResponse<GetFlightArea[]>> {
const resp = await request.get(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-areas`)
return resp.data
}
export async function changeFlightAreaStatus (area_id: string, status: boolean): Promise<IWorkspaceResponse<any>> {
const resp = await request.put(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/${area_id}`, { status })
return resp.data
}
export async function saveFlightArea (body: PostFlightAreaBody): Promise<IWorkspaceResponse<any>> {
const resp = await request.post(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area`, body)
return resp.data
}
export async function deleteFlightArea (area_id: string): Promise<IWorkspaceResponse<any>> {
const resp = await request.delete(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/${area_id}`)
return resp.data
}
export async function syncFlightArea (device_sn: string[]): Promise<IWorkspaceResponse<any>> {
const resp = await request.post(`${MAP_API_PREFIX}/workspaces/${workspaceId}/flight-area/sync`, { device_sn })
return resp.data
}
export async function getDeviceStatus (): Promise<IWorkspaceResponse<GetDeviceStatus[]>> {
const resp = await request.get(`${MAP_API_PREFIX}/workspaces/${workspaceId}/device-status`)
return resp.data
}

50
src/components/GMap.vue

@ -13,9 +13,10 @@
<div :class="state.currentType === 'polyline' ? 'g-action-item selection' : 'g-action-item'" @click="draw('polyline', true)"> <div :class="state.currentType === 'polyline' ? 'g-action-item selection' : 'g-action-item'" @click="draw('polyline', true)">
<a><LineOutlined :rotate="135" class="fz20"/></a> <a><LineOutlined :rotate="135" class="fz20"/></a>
</div> </div>
<div :class="state.currentType === 'polygon' ? 'g-action-item selection' : 'g-action-item'" @click="draw('polygon', true)"> <div :class="state.currentType === 'polygon' && !state.isFlightArea ? 'g-action-item selection' : 'g-action-item'" @click="draw('polygon', true)">
<a><BorderOutlined class="fz18" /></a> <a><BorderOutlined class="fz18" /></a>
</div> </div>
<FlightAreaActionIcon class="g-action-item mt10" :class="{'selection': mouseMode && state.isFlightArea}" @select-action="selectFlightAreaAction" @click="selectFlightAreaAction"/>
<div v-if="mouseMode" class="g-action-item" @click="draw('off', false)"> <div v-if="mouseMode" class="g-action-item" @click="draw('off', false)">
<a style="color: red;"><CloseOutlined /></a> <a style="color: red;"><CloseOutlined /></a>
</div> </div>
@ -464,6 +465,10 @@ import DroneControlPanel from './g-map/DroneControlPanel.vue'
import { useConnectMqtt } from './g-map/use-connect-mqtt' import { useConnectMqtt } from './g-map/use-connect-mqtt'
import LivestreamOthers from './livestream-others.vue' import LivestreamOthers from './livestream-others.vue'
import LivestreamAgora from './livestream-agora.vue' import LivestreamAgora from './livestream-agora.vue'
import FlightAreaActionIcon from './flight-area/FlightAreaActionIcon.vue'
import { EFlightAreaType } from '../types/flight-area'
import { useFlightArea } from './flight-area/use-flight-area'
import { useFlightAreaDroneLocationEvent } from './flight-area/use-flight-area-drone-location-event'
export default defineComponent({ export default defineComponent({
components: { components: {
@ -489,7 +494,8 @@ export default defineComponent({
CarryOutOutlined, CarryOutOutlined,
RocketOutlined, RocketOutlined,
LivestreamOthers, LivestreamOthers,
LivestreamAgora LivestreamAgora,
FlightAreaActionIcon,
}, },
name: 'GMap', name: 'GMap',
props: {}, props: {},
@ -503,7 +509,8 @@ export default defineComponent({
const store = useMyStore() const store = useMyStore()
const state = reactive({ const state = reactive({
currentType: '', currentType: '',
coverIndex: 0 coverIndex: 0,
isFlightArea: false,
}) })
const str: string = '--' const str: string = '--'
const deviceInfo = reactive({ const deviceInfo = reactive({
@ -589,19 +596,22 @@ export default defineComponent({
watch(() => store.state.deviceState, data => { watch(() => store.state.deviceState, data => {
if (data.currentType === EDeviceTypeName.Gateway && data.gatewayInfo[data.currentSn]) { if (data.currentType === EDeviceTypeName.Gateway && data.gatewayInfo[data.currentSn]) {
deviceTsaUpdateHook.moveTo(data.currentSn, data.gatewayInfo[data.currentSn].longitude, data.gatewayInfo[data.currentSn].latitude) const coordinate = wgs84togcj02(data.gatewayInfo[data.currentSn].longitude, data.gatewayInfo[data.currentSn].latitude)
deviceTsaUpdateHook.moveTo(data.currentSn, coordinate[0], coordinate[1])
if (osdVisible.value.visible && osdVisible.value.gateway_sn !== '') { if (osdVisible.value.visible && osdVisible.value.gateway_sn !== '') {
deviceInfo.gateway = data.gatewayInfo[osdVisible.value.gateway_sn] deviceInfo.gateway = data.gatewayInfo[osdVisible.value.gateway_sn]
} }
} }
if (data.currentType === EDeviceTypeName.Aircraft && data.deviceInfo[data.currentSn]) { if (data.currentType === EDeviceTypeName.Aircraft && data.deviceInfo[data.currentSn]) {
deviceTsaUpdateHook.moveTo(data.currentSn, data.deviceInfo[data.currentSn].longitude, data.deviceInfo[data.currentSn].latitude) const coordinate = wgs84togcj02(data.deviceInfo[data.currentSn].longitude, data.deviceInfo[data.currentSn].latitude)
deviceTsaUpdateHook.moveTo(data.currentSn, coordinate[0], coordinate[1])
if (osdVisible.value.visible && osdVisible.value.sn !== '') { if (osdVisible.value.visible && osdVisible.value.sn !== '') {
deviceInfo.device = data.deviceInfo[osdVisible.value.sn] deviceInfo.device = data.deviceInfo[osdVisible.value.sn]
} }
} }
if (data.currentType === EDeviceTypeName.Dock && data.dockInfo[data.currentSn]) { if (data.currentType === EDeviceTypeName.Dock && data.dockInfo[data.currentSn]) {
deviceTsaUpdateHook.initMarker(EDeviceTypeName.Dock, EDeviceTypeName[EDeviceTypeName.Dock], data.currentSn, data.dockInfo[data.currentSn].basic_osd?.longitude, data.dockInfo[data.currentSn].basic_osd?.latitude) const coordinate = wgs84togcj02(data.dockInfo[data.currentSn].basic_osd?.longitude, data.dockInfo[data.currentSn].basic_osd?.latitude)
deviceTsaUpdateHook.initMarker(EDeviceTypeName.Dock, EDeviceTypeName[EDeviceTypeName.Dock], data.currentSn, coordinate[0], coordinate[1])
if (osdVisible.value.visible && osdVisible.value.is_dock && osdVisible.value.gateway_sn !== '') { if (osdVisible.value.visible && osdVisible.value.is_dock && osdVisible.value.gateway_sn !== '') {
deviceInfo.dock = data.dockInfo[osdVisible.value.gateway_sn] deviceInfo.dock = data.dockInfo[osdVisible.value.gateway_sn]
deviceInfo.device = data.deviceInfo[deviceInfo.dock.basic_osd.sub_device?.device_sn ?? osdVisible.value.sn] deviceInfo.device = data.deviceInfo[deviceInfo.dock.basic_osd.sub_device?.device_sn ?? osdVisible.value.sn]
@ -677,10 +687,11 @@ export default defineComponent({
} }
) )
function draw (type: MapDoodleType, bool: boolean) { function draw (type: MapDoodleType, bool: boolean, flightAreaType?: EFlightAreaType) {
state.currentType = type state.currentType = type
useMouseToolHook.mouseTool(type, getDrawCallback)
mouseMode.value = bool mouseMode.value = bool
state.isFlightArea = !!flightAreaType
useMouseToolHook.mouseTool(type, getDrawCallback, flightAreaType)
} }
// dock // dock
@ -698,7 +709,18 @@ export default defineComponent({
useGMapManageHook.globalPropertiesConfig(app) useGMapManageHook.globalPropertiesConfig(app)
}) })
function getDrawCallback ({ obj }) { const { getDrawFlightAreaCallback, onFlightAreaDroneLocationWs } = useFlightArea()
useFlightAreaDroneLocationEvent(onFlightAreaDroneLocationWs)
function selectFlightAreaAction ({ type, isCircle }: { type: EFlightAreaType, isCircle: boolean }) {
draw(isCircle ? MapDoodleEnum.CIRCLE : MapDoodleEnum.POLYGON, true, type)
}
function getDrawCallback ({ obj }: { obj : any }) {
if (state.isFlightArea) {
getDrawFlightAreaCallback(obj)
return
}
switch (state.currentType) { switch (state.currentType) {
case MapDoodleEnum.PIN: case MapDoodleEnum.PIN:
postPinPositionResource(obj) postPinPositionResource(obj)
@ -721,8 +743,7 @@ export default defineComponent({
(req.resource.content.geometry.coordinates as GeojsonCoordinate).push((coordinates as GeojsonCoordinate)[2]) (req.resource.content.geometry.coordinates as GeojsonCoordinate).push((coordinates as GeojsonCoordinate)[2])
const result = await postElementsReq(shareId.value, req) const result = await postElementsReq(shareId.value, req)
obj.setExtData({ id: req.id, name: req.name }) obj.setExtData({ id: req.id, name: req.name })
store.state.coverList.push(obj) store.state.coverMap[req.id] = [obj]
// console.log(store.state.coverList)
} }
async function postPolylineResource (obj) { async function postPolylineResource (obj) {
const req = getPolylineResource(obj) const req = getPolylineResource(obj)
@ -730,8 +751,7 @@ export default defineComponent({
updateCoordinates('gcj02-wgs84', req) updateCoordinates('gcj02-wgs84', req)
const result = await postElementsReq(shareId.value, req) const result = await postElementsReq(shareId.value, req)
obj.setExtData({ id: req.id, name: req.name }) obj.setExtData({ id: req.id, name: req.name })
store.state.coverList.push(obj) store.state.coverMap[req.id] = [obj]
// console.log(store.state.coverList)
} }
async function postPolygonResource (obj) { async function postPolygonResource (obj) {
const req = getPoygonResource(obj) const req = getPoygonResource(obj)
@ -739,8 +759,7 @@ export default defineComponent({
updateCoordinates('gcj02-wgs84', req) updateCoordinates('gcj02-wgs84', req)
const result = await postElementsReq(shareId.value, req) const result = await postElementsReq(shareId.value, req)
obj.setExtData({ id: req.id, name: req.name }) obj.setExtData({ id: req.id, name: req.name })
store.state.coverList.push(obj) store.state.coverMap[req.id] = [obj]
// console.log(store.state.coverList)
} }
function getPinPositionResource (obj) { function getPinPositionResource (obj) {
@ -882,6 +901,7 @@ export default defineComponent({
closeLivestreamOthers, closeLivestreamOthers,
closeLivestreamAgora, closeLivestreamAgora,
qualityStyle, qualityStyle,
selectFlightAreaAction,
} }
} }
}) })

3
src/components/common/sidebar.vue

@ -58,7 +58,8 @@ export default defineComponent({
{ key: 2, label: 'Annotations', path: '/' + ERouterName.LAYER, icon: 'EnvironmentOutlined' }, { key: 2, label: 'Annotations', path: '/' + ERouterName.LAYER, icon: 'EnvironmentOutlined' },
{ key: 3, label: 'Media Files', path: '/' + ERouterName.MEDIA, icon: 'PictureOutlined' }, { key: 3, label: 'Media Files', path: '/' + ERouterName.MEDIA, icon: 'PictureOutlined' },
{ key: 4, label: 'Flight Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' }, { key: 4, label: 'Flight Route Library', path: '/' + ERouterName.WAYLINE, icon: 'NodeIndexOutlined' },
{ key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' } { key: 5, label: 'Task Plan Library', path: '/' + ERouterName.TASK, icon: 'CalendarOutlined' },
{ key: 6, label: 'Flight Area', path: '/' + ERouterName.FLIGHT_AREA, icon: 'GroupOutlined' },
] ]
function selectedRoute (item: IOptions) { function selectedRoute (item: IOptions) {

59
src/components/flight-area/FlightAreaActionIcon.vue

@ -0,0 +1,59 @@
<template>
<div @click="selectCurrent">
<a-dropdown class="height-100 width-100 icon-panel">
<FlightAreaIcon :type="actionMap[selectedKey].type" :is-circle="actionMap[selectedKey].isCircle" :hide-title="true"/>
<template #overlay>
<a-menu @click="selectAction" mode="vertical-right" :selectedKeys="[selectedKey]">
<a-menu-item v-for="(v, k) in actionMap" :key="k">
<FlightAreaIcon :type="v.type" :is-circle="v.isCircle"/>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</template>
<script lang="ts" setup>
import { ref, defineEmits } from 'vue'
import { EFlightAreaType } from '../../types/flight-area'
import FlightAreaIcon from './FlightAreaIcon.vue'
const emit = defineEmits(['select-action', 'click'])
const actionMap: Record<string, { type: EFlightAreaType, isCircle: boolean}> = {
1: {
type: EFlightAreaType.DFENCE,
isCircle: true,
},
2: {
type: EFlightAreaType.DFENCE,
isCircle: false,
},
3: {
type: EFlightAreaType.NFZ,
isCircle: true,
},
4: {
type: EFlightAreaType.NFZ,
isCircle: false,
},
}
const selectedKey = ref<string>('1')
const selectAction = (item: any) => {
selectedKey.value = item.key
emit('select-action', actionMap[item.key])
}
const selectCurrent = () => {
emit('click', actionMap[selectedKey.value])
}
</script>
<style lang="scss">
.icon-panel {
align-items: center;
justify-content: center;
cursor: pointer;
}
</style>

197
src/components/flight-area/FlightAreaDevicePanel.vue

@ -0,0 +1,197 @@
<template>
<div class="flight-area-device-panel">
<Title title="Choose Synchronous Devices">
<div style="position: absolute; right: 10px;">
<a style="color: white;" @click="closePanel"><CloseOutlined /></a>
</div>
</Title>
<div class="scrollbar">
<div id="data" v-if="data.length !== 0">
<div v-for="dock in data" :key="dock.device_sn">
<div class="pt5 panel flex-row" @click="selectDock(dock)" :style="{opacity: selectedDocksMap[dock.device_sn] ? 1 : 0.5 }">
<div style="width: 88%">
<div class="title">
<RobotFilled class="fz20"/>
<a-tooltip :title="dock.nickname">
<div class="pr10 ml5" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ dock.nickname }}</div>
</a-tooltip>
</div>
<div class="ml10 mr10 pr5 pl5 flex-align-center flex-row flex-justify-between" style="background: #595959;">
<div>
Custom Flight Area
</div>
<div>
<div v-if="!dock.status">
<a-tooltip title="Dock offline">
<ApiOutlined />
</a-tooltip>
</div>
<div v-else-if="deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZED">
<a-tooltip title="Data synced">
<CheckCircleTwoTone twoToneColor="#28d445"/>
</a-tooltip>
</div>
<div v-else-if="deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZING
|| deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.WAIT_SYNC">
<a-tooltip title="To be synced">
<SyncOutlined spin />
</a-tooltip>
</div>
<div v-else>
<a-tooltip :title="deviceStatusMap[dock.device_sn]?.flight_area_status?.sync_msg || 'No synchronization'">
<ExclamationCircleTwoTone twoToneColor="#e70102" />
</a-tooltip>
</div>
</div>
</div>
</div>
<div class="box" v-if="selectedDocksMap[dock.device_sn]">
<CheckOutlined />
</div>
</div>
</div>
<DividerLine style="position: absolute; bottom: 68px;" />
<div class="flex-row flex-justify-between footer">
<a-button class="mr10" @click="closePanel">Cancel
</a-button>
<a-button type="primary" :disabled="confirmDisabled" @click="syncDeviceFlightArea">Sync
</a-button>
</div>
</div>
<div v-else>
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { CloseOutlined, RobotFilled, CheckOutlined, ApiOutlined, CheckCircleTwoTone, SyncOutlined, ExclamationCircleTwoTone } from '@ant-design/icons-vue'
import Title from '/@/components/workspace/Title.vue'
import { defineEmits, onMounted, ref, defineProps, computed } from 'vue'
import { getBindingDevices } from '/@/api/manage'
import { EDeviceTypeName, ELocalStorageKey } from '/@/types'
import { IPage } from '/@/api/http/type'
import { Device } from '/@/types/device'
import DividerLine from '../workspace/DividerLine.vue'
import { message } from 'ant-design-vue'
import { GetDeviceStatus, syncFlightArea } from '/@/api/flight-area'
import { ESyncStatus } from '/@/types/flight-area'
const props = defineProps<{
data: GetDeviceStatus[]
}>()
const emit = defineEmits(['closePanel'])
const closePanel = () => {
emit('closePanel', false)
}
const confirmDisabled = ref(false)
const deviceStatusMap = computed(() => props.data.reduce((obj: Record<string, GetDeviceStatus>, val: GetDeviceStatus) => {
obj[val.device_sn] = val
return obj
}, {} as Record<string, GetDeviceStatus>))
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId) || ''
const body: IPage = {
page: 1,
total: 0,
page_size: 10,
}
const data = ref<Device[]>([])
const selectedDocksMap = ref<Record<string, boolean>>({})
const getDocks = async () => {
await getBindingDevices(workspaceId, body, EDeviceTypeName.Dock).then(res => {
if (res.code !== 0) {
return
}
data.value.push(...res.data.list)
body.page = res.data.pagination.page
body.page_size = res.data.pagination.page_size
body.total = res.data.pagination.total
})
}
const selectDock = (dock: Device) => {
if (!dock.status) {
message.info(`Dock(${dock.nickname}) is offline.`)
return
}
if (deviceStatusMap.value[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.SYNCHRONIZING ||
deviceStatusMap.value[dock.device_sn]?.flight_area_status?.sync_status === ESyncStatus.WAIT_SYNC) {
message.info('The dock is synchronizing.')
return
}
selectedDocksMap.value[dock.device_sn] = !selectedDocksMap.value[dock.device_sn]
}
onMounted(() => {
getDocks()
const key = setInterval(() => {
if (body.total === 0 || Math.ceil(body.total / body.page_size) <= body.page) {
clearInterval(key)
return
}
body.page++
getDocks()
}, 1000)
})
const syncDeviceFlightArea = () => {
const keys = Object.keys(selectedDocksMap.value)
if (keys.length === 0) {
message.warn('Please select the docks that need to be synchronized.')
return
}
confirmDisabled.value = true
Object.keys(selectedDocksMap.value).forEach(k => {
const device = deviceStatusMap.value[k]
if (device) {
device.flight_area_status = { sync_code: 0, sync_status: ESyncStatus.WAIT_SYNC, sync_msg: '' }
}
})
syncFlightArea(keys).then(res => {
if (res.code === 0) {
message.success('The devices are synchronizing...')
selectedDocksMap.value = {}
}
}).finally(() => setTimeout(() => {
confirmDisabled.value = false
}, 3000))
}
</script>
<style lang="scss" scoped>
.flight-area-device-panel {
position: absolute;
left: 285px;
width: 280px;
height: 100vh;
float: right;
top: 0;
z-index: 1000;
color: white;
background: #282828;
.footer {
position: absolute;
width: 100%;
bottom: 10px;
padding: 10px;
button {
width: 45%;
border: 0;
}
}
.scrollbar {
overflow-y: auto;
height: calc(100vh - 150px);
}
.box {
font-size: 22px;
line-height: 60px;
}
}
</style>

33
src/components/flight-area/FlightAreaIcon.vue

@ -0,0 +1,33 @@
<template>
<div class="flex-row flex-align-center">
<div class="shape" :class="type" :style="isCircle ? 'border-radius: 50%;' : ''"></div>
<div class="ml5" v-if="!hideTitle">{{ FlightAreaTypeTitleMap[type][isCircle ? EGeometryType.CIRCLE : EGeometryType.POLYGON] }}</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
import { EFlightAreaType, EGeometryType, FlightAreaTypeTitleMap } from '../../types/flight-area'
const props = defineProps<{
type: EFlightAreaType,
isCircle: boolean,
hideTitle?: boolean
}>()
</script>
<style lang="scss">
.nfz {
border-color: red;
}
.dfence {
border-color: $tag-green;
}
.shape {
width: 16px;
height: 16px;
border-width: 3px;
border-style: solid;
}
</style>

89
src/components/flight-area/FlightAreaItem.vue

@ -0,0 +1,89 @@
<template>
<div class="panel" style="padding-top: 5px;" :class="{disable: !flightArea.status}">
<div class="title">
<a-tooltip :title="flightArea.name">
<div class="pr10" style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ flightArea.name }}</div>
</a-tooltip>
</div>
<div class="mt5 ml10" style="color: hsla(0,0%,100%,0.35);">
<span class="mr10">Update at {{ formatDateTime(flightArea.update_time).toLocaleString() }}</span>
</div>
<div class="flex-row flex-justify-between flex-align-center ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
<FlightAreaIcon :type="flightArea.type" :isCircle="EGeometryType.CIRCLE === flightArea.content.geometry.type"/>
<div class="mr10 operate">
<a-popconfirm v-if="flightArea.status" title="Is it determined to disable the current area?" okText="Disable" @confirm="changeAreaStatus(false)">
<stop-outlined />
</a-popconfirm>
<a-popconfirm v-else @confirm="changeAreaStatus(true)" title="Is it determined to enable the current area?" okText="Enable" >
<check-circle-outlined />
</a-popconfirm>
<EnvironmentFilled class="ml10" @click="clickLocation"/>
<a-popconfirm title="Is it determined to delete the current area?" okText="Delete" okType="danger" @confirm="deleteArea">
<delete-outlined class="ml10" />
</a-popconfirm>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, reactive, defineEmits, computed } from 'vue'
import { GetFlightArea, changeFlightAreaStatus } from '../../api/flight-area'
import FlightAreaIcon from './FlightAreaIcon.vue'
import { formatDateTime } from '../../utils/time'
import { EGeometryType } from '../../types/flight-area'
import { StopOutlined, CheckCircleOutlined, DeleteOutlined, EnvironmentFilled } from '@ant-design/icons-vue'
const props = defineProps<{
data: GetFlightArea
}>()
const emit = defineEmits(['delete', 'update', 'location'])
const flightArea = computed(() => props.data)
const changeAreaStatus = (status: boolean) => {
changeFlightAreaStatus(props.data.area_id, status).then(res => {
if (res.code === 0) {
flightArea.value.status = status
emit('update', flightArea)
}
})
}
const deleteArea = () => {
emit('delete', flightArea.value.area_id)
}
const clickLocation = () => {
emit('location', flightArea.value.area_id)
}
</script>
<style lang="scss" scoped>
.panel {
background: #3c3c3c;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
height: 90px;
width: 95%;
font-size: 13px;
border-radius: 2px;
cursor: pointer;
.title {
display: flex;
flex-direction: row;
align-items: center;
height: 30px;
font-weight: bold;
margin: 0px 10px 0 10px;
}
.operate > *{
font-size: 16px;
}
}
.disable {
opacity: 50%;
}
</style>

43
src/components/flight-area/FlightAreaPanel.vue

@ -0,0 +1,43 @@
<template>
<div class="flight-area-panel">
<div v-if="data.length === 0">
<a-empty :image-style="{ height: '60px', marginTop: '60px' }" />
</div>
<div v-else v-for="area in flightAreaList" :key="area.area_id">
<FlightAreaItem :data="area" @delete="deleteArea" @update="updateArea" @location="clickLocation(area)"/>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, ref, computed } from 'vue'
import FlightAreaItem from './FlightAreaItem.vue'
import { GetFlightArea } from '/@/api/flight-area'
const emit = defineEmits(['deleteArea', 'updateArea', 'locationArea'])
const props = defineProps<{
data: GetFlightArea[]
}>()
const flightAreaList = computed(() => props.data)
const deleteArea = (areaId: string) => {
emit('deleteArea', areaId)
}
const updateArea = (area: GetFlightArea) => {
emit('updateArea', area)
}
const clickLocation = (area: GetFlightArea) => {
emit('locationArea', area)
}
</script>
<style lang="scss" scoped>
.flight-area-panel {
overflow-y: auto;
height: calc(100vh - 150px);
}
</style>

66
src/components/flight-area/FlightAreaSyncPanel.vue

@ -0,0 +1,66 @@
<template>
<div class="flight-area-sync-panel p10 flex-row flex-align-center" >
<RobotFilled class="fz30" twoToneColor="red" fill="#00ff00"/>
<div class="ml20 mr10 flex-column" @click="switchPanel">
<div class="fz18">Sync Across Devices</div>
<div v-if="syncDevicesCount > 0"><a-spin /> Syncing to {{ syncDevicesCount }} devices</div>
</div>
<RightOutlined class="fz18" @click="switchPanel"/>
<FlightAreaDevicePanel v-if="visible" @close-panel="closePanel" :data="syncDevices"/>
</div>
</template>
<script lang="ts" setup>
import { RobotFilled, RightOutlined } from '@ant-design/icons-vue'
import FlightAreaDevicePanel from '/@/components/flight-area/FlightAreaDevicePanel.vue'
import { computed, onMounted, ref, watch } from 'vue'
import { GetDeviceStatus, getDeviceStatus } from '/@/api/flight-area'
import { ESyncStatus, FlightAreaSyncProgress } from '/@/types/flight-area'
import { useFlightAreaSyncProgressEvent } from './use-flight-area-sync-progress-event'
const visible = ref(false)
const syncDevices = ref<GetDeviceStatus[]>([])
const syncDevicesCount = computed(() => syncDevices.value.filter(device =>
device.flight_area_status.sync_status === ESyncStatus.SYNCHRONIZING || device.flight_area_status.sync_status === ESyncStatus.WAIT_SYNC).length)
const getAllDeviceStatus = () => {
getDeviceStatus().then(res => {
if (res.code === 0) {
syncDevices.value = res.data
}
})
}
onMounted(() => {
getAllDeviceStatus()
})
const switchPanel = () => {
visible.value = !visible.value
}
const closePanel = (val: boolean) => {
visible.value = val
}
const handleSyncProgress = (data: FlightAreaSyncProgress) => {
let has = false
const status = { sync_code: data.result, sync_status: data.status, sync_msg: data.message }
syncDevices.value.forEach(device => {
if (data.sn === device.device_sn) {
device.flight_area_status = status
has = true
}
})
if (!has) {
syncDevices.value.push({ device_sn: data.sn, flight_area_status: status })
}
}
useFlightAreaSyncProgressEvent(handleSyncProgress)
</script>
<style lang="scss" scoped>
.flight-area-sync-panel {
height: 70px;
cursor: pointer;
}
</style>

18
src/components/flight-area/use-flight-area-drone-location-event.ts

@ -0,0 +1,18 @@
import { FlightAreasDroneLocation } from '/@/types/flight-area'
import { CommonHostWs } from '/@/websocket'
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
export function useFlightAreaDroneLocationEvent (onFlightAreaDroneLocationWs: (data: CommonHostWs<FlightAreasDroneLocation>) => void): void {
function handleDroneLocationEvent (data: any) {
onFlightAreaDroneLocationWs(data.data)
}
onMounted(() => {
EventBus.on('flightAreasDroneLocationWs', handleDroneLocationEvent)
})
onBeforeUnmount(() => {
EventBus.off('flightAreasDroneLocationWs', handleDroneLocationEvent)
})
}

17
src/components/flight-area/use-flight-area-sync-progress-event.ts

@ -0,0 +1,17 @@
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
import { FlightAreaSyncProgress } from '/@/types/flight-area'
export function useFlightAreaSyncProgressEvent (onFlightAreaSyncProgressWs: (data: FlightAreaSyncProgress) => void): void {
function handleSyncProgressEvent (data: FlightAreaSyncProgress) {
onFlightAreaSyncProgressWs(data)
}
onMounted(() => {
EventBus.on('flightAreasSyncProgressWs', handleSyncProgressEvent)
})
onBeforeUnmount(() => {
EventBus.off('flightAreasSyncProgressWs', handleSyncProgressEvent)
})
}

30
src/components/flight-area/use-flight-area-update.ts

@ -0,0 +1,30 @@
import { EFlightAreaUpdate, FlightAreaUpdate, FlightAreasDroneLocation } from '/@/types/flight-area'
import { CommonHostWs } from '/@/websocket'
import EventBus from '/@/event-bus/'
import { onMounted, onBeforeUnmount } from 'vue'
function doNothing (data: FlightAreaUpdate) {
}
export function useFlightAreaUpdateEvent (addFunc = doNothing, deleteFunc = doNothing, updateFunc = doNothing): void {
function handleDroneLocationEvent (data: FlightAreaUpdate) {
switch (data.operation) {
case EFlightAreaUpdate.ADD:
addFunc(data)
break
case EFlightAreaUpdate.UPDATE:
updateFunc(data)
break
case EFlightAreaUpdate.DELETE:
deleteFunc(data)
break
}
}
onMounted(() => {
EventBus.on('flightAreasUpdateWs', handleDroneLocationEvent)
})
onBeforeUnmount(() => {
EventBus.off('flightAreasUpdateWs', handleDroneLocationEvent)
})
}

155
src/components/flight-area/use-flight-area.ts

@ -0,0 +1,155 @@
import { message, notification } from 'ant-design-vue'
import { MapDoodleEnum } from '/@/types/map-enum'
import { getRoot } from '/@/root'
import { PostFlightAreaBody, saveFlightArea } from '/@/api/flight-area'
import { generateCircleContent, generatePolyContent } from '/@/utils/map-layer-utils'
import { GeojsonCoordinate } from '/@/utils/genjson'
import { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform.js'
import { uuidv4 } from '/@/utils/uuid'
import { CommonHostWs } from '/@/websocket'
import { FlightAreasDroneLocation } from '/@/types/flight-area'
import rootStore from '/@/store'
import { h } from 'vue'
import { useGMapCover } from '/@/hooks/use-g-map-cover'
import moment from 'moment'
import { DATE_FORMAT } from '/@/utils/constants'
export function useFlightArea () {
const root = getRoot()
const store = rootStore
const coverMap = store.state.coverMap
let useGMapCoverHook = useGMapCover()
const MIN_RADIUS = 10
function checkCircle (obj: any): boolean {
if (obj.getRadius() < MIN_RADIUS) {
message.error(`The radius must be greater than ${MIN_RADIUS}m.`)
root.$map.remove(obj)
return false
}
return true
}
function checkPolygon (obj: any): boolean {
const path: any[][] = obj.getPath()
if (path.length < 3) {
message.error('The path of the polygon cannot be crossed.')
root.$map.remove(obj)
return false
}
// root.$aMap.GeometryUtil.doesLineLineIntersect()
return true
}
function setExtData (obj: any) {
let ext = obj.getExtData()
const id = uuidv4()
const name = `${ext.type}-${moment().format(DATE_FORMAT)}`
ext = Object.assign({}, ext, { id, name })
obj.setExtData(ext)
return ext
}
function createFlightArea (obj: any) {
const ext = obj.getExtData()
const data = {
id: ext.id,
type: ext.type,
name: ext.name,
}
let coordinates: GeojsonCoordinate | GeojsonCoordinate[][]
let content
switch (ext.mapType) {
case 'circle':
content = generateCircleContent(obj.getCenter(), obj.getRadius())
coordinates = getWgs84(content.geometry.coordinates as GeojsonCoordinate)
break
case 'polygon':
content = generatePolyContent(obj.getPath()).content
coordinates = [getWgs84(content.geometry.coordinates[0] as GeojsonCoordinate[])]
break
default:
message.error(`Invalid type: ${obj.mapType}`)
root.$map.remove(obj)
return
}
content.geometry.coordinates = coordinates
saveFlightArea(Object.assign({}, data, { content }) as PostFlightAreaBody).then(res => {
if (res.code !== 0) {
useGMapCoverHook.removeCoverFromMap(ext.id)
}
}).finally(() => root.$map.remove(obj))
}
function getDrawFlightAreaCallback (obj: any) {
useGMapCoverHook = useGMapCover()
const ext = setExtData(obj)
switch (ext.mapType) {
case MapDoodleEnum.CIRCLE:
if (!checkCircle(obj)) {
return
}
break
case MapDoodleEnum.POLYGON:
if (!checkPolygon(obj)) {
return
}
break
default:
break
}
createFlightArea(obj)
}
const getWgs84 = <T extends GeojsonCoordinate | GeojsonCoordinate[]>(coordinate: T): T => {
if (coordinate[0] instanceof Array) {
return (coordinate as GeojsonCoordinate[]).map(c => gcj02towgs84(c[0], c[1])) as T
}
return gcj02towgs84(coordinate[0], coordinate[1])
}
const getGcj02 = <T extends GeojsonCoordinate | GeojsonCoordinate[]>(coordinate: T): T => {
if (coordinate[0] instanceof Array) {
return (coordinate as GeojsonCoordinate[]).map(c => wgs84togcj02(c[0], c[1])) as T
}
return wgs84togcj02(coordinate[0], coordinate[1])
}
const onFlightAreaDroneLocationWs = (data: CommonHostWs<FlightAreasDroneLocation>) => {
const nearArea = data.host.drone_locations.filter(val => !val.is_in_area)
const inArea = data.host.drone_locations.filter(val => val.is_in_area)
notification.warning({
key: `flight-area-${data.sn}`,
message: `Drone(${data.sn}) flight area information`,
description: h('div',
[
h('div', [
h('span', { class: 'fz18' }, 'In the flight area: '),
h('ul', [
...inArea.map(val => h('li', `There are ${val.area_distance} meters from the edge of the area(${coverMap[val.area_id][1]?.getText() || val.area_id}).`))
])
]),
h('div', [
h('span', { class: 'fz18' }, 'Near the flight area: '),
h('ul', [
...nearArea.map(val => h('li', `There are ${val.area_distance} meters from the edge of the area(${coverMap[val.area_id][1]?.getText() || val.area_id}).`))
])
])
]),
duration: null,
style: {
width: '420px',
marginTop: '-8px',
marginLeft: '-28px',
}
})
}
return {
getDrawFlightAreaCallback,
getGcj02,
getWgs84,
onFlightAreaDroneLocationWs,
}
}

36
src/components/livestream-others.vue

@ -5,6 +5,7 @@
id="video-webrtc" id="video-webrtc"
ref="videowebrtc" ref="videowebrtc"
controls controls
autoplay
class="mt20" class="mt20"
></video> ></video>
<p class="fz24">Live streaming source selection</p> <p class="fz24">Live streaming source selection</p>
@ -120,6 +121,8 @@ import { CURRENT_CONFIG as config } from '/@/api/http/config'
import { changeLivestreamLens, getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage' import { changeLivestreamLens, getLiveCapacity, setLivestreamQuality, startLivestream, stopLivestream } from '/@/api/manage'
import { getRoot } from '/@/root' import { getRoot } from '/@/root'
import jswebrtc from '/@/vendors/jswebrtc.min.js' import jswebrtc from '/@/vendors/jswebrtc.min.js'
import srs from '/@/vendors/srs.sdk.js'
const root = getRoot() const root = getRoot()
interface SelectOption { interface SelectOption {
@ -140,6 +143,10 @@ const liveTypeList: SelectOption[] = [
{ {
value: 3, value: 3,
label: 'GB28181' label: 'GB28181'
},
{
value: 4,
label: 'WEBRTC'
} }
] ]
const clarityList: SelectOption[] = [ const clarityList: SelectOption[] = [
@ -182,6 +189,7 @@ const lensList = ref<string[]>([])
const lensSelected = ref<String>() const lensSelected = ref<String>()
const isDockLive = ref(false) const isDockLive = ref(false)
const nonSwitchable = 'normal' const nonSwitchable = 'normal'
let webrtc: any = null
const onRefresh = async () => { const onRefresh = async () => {
droneList.value = [] droneList.value = []
@ -258,6 +266,9 @@ const onStart = async () => {
liveURL = `serverIP=${config.gbServerIp}&serverPort=${config.gbServerPort}&serverID=${config.gbServerId}&agentID=${config.gbAgentId}&agentPassword=${config.gbPassword}&localPort=${config.gbAgentPort}&channel=${config.gbAgentChannel}` liveURL = `serverIP=${config.gbServerIp}&serverPort=${config.gbServerPort}&serverID=${config.gbServerId}&agentID=${config.gbAgentId}&agentPassword=${config.gbPassword}&localPort=${config.gbAgentPort}&channel=${config.gbAgentChannel}`
break break
} }
case 4: {
break
}
default: default:
console.warn('warning: live type is not correct!!!') console.warn('warning: live type is not correct!!!')
break break
@ -291,13 +302,7 @@ const onStart = async () => {
}) })
} else if (livetypeSelected.value === 2) { } else if (livetypeSelected.value === 2) {
console.log(res) console.log(res)
rtspData.value = rtspData.value = 'url:' + res.data.url
'url:' +
res.data.url +
'&username:' +
res.data.username +
'&password:' +
res.data.password
} else if (livetypeSelected.value === 1) { } else if (livetypeSelected.value === 1) {
const url = res.data.url const url = res.data.url
const videoElement = videowebrtc.value const videoElement = videowebrtc.value
@ -310,6 +315,10 @@ const onStart = async () => {
console.log('start play livestream') console.log('start play livestream')
} }
}) })
} else if (livetypeSelected.value === 4) {
const videoElement = videowebrtc.value as unknown as HTMLMediaElement
videoElement.muted = true
playWebrtc(videoElement, res.data.url)
} }
liveState.value = true liveState.value = true
}) })
@ -412,6 +421,19 @@ const onSwitch = () => {
} }
}) })
} }
const playWebrtc = (videoElement: HTMLMediaElement, url: string) => {
if (webrtc) {
webrtc.close()
}
webrtc = new srs.SrsRtcWhipWhepAsync()
videoElement.srcObject = webrtc.stream
webrtc.play(url).then(function (session: any) {
console.info(session)
}).catch(function (reason: any) {
webrtc.close()
console.error(reason)
})
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

15
src/components/workspace/DividerLine.vue

@ -0,0 +1,15 @@
<template>
<Divider class="divider" />
</template>
<script lang="ts" setup>
import { Divider } from 'ant-design-vue'
</script>
<style lang="scss" scoped>
.divider {
margin: 10px 0;
height: 1px;
background-color: #4f4f4f;
}
</style>

21
src/components/workspace/Title.vue

@ -0,0 +1,21 @@
<template>
<div style="height: 40px; line-height: 50px; font-weight: 450;">
<a-row>
<a-col :span="1"></a-col>
<a-col :span="(23 - (extSpan || 0))">{{ title }}</a-col>
<a-col :span="extSpan"><slot /></a-col>
</a-row>
</div>
<DividerLine />
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
import DividerLine from '/@/components/workspace/DividerLine.vue'
const props = defineProps < {
extSpan?: number,
title: string,
} >()
</script>

2
src/constants/map.ts

@ -20,4 +20,4 @@ export enum MapElementEnum {
LINE = 1, LINE = 1,
POLY = 2 POLY = 2
} }
export type MapDoodleType = 'pin' | 'polyline' | 'polygon' | 'off' export type MapDoodleType = 'pin' | 'polyline' | 'polygon' | 'off' | 'circle'

3
src/event-bus/index.ts

@ -6,6 +6,9 @@ type Events = {
flightTaskWs: any // 机场任务消息 flightTaskWs: any // 机场任务消息
droneControlWs: any // 飞行指令信息 droneControlWs: any // 飞行指令信息
droneControlMqttInfo: any // drc 链路通知 droneControlMqttInfo: any // drc 链路通知
flightAreasDroneLocationWs: any
flightAreasSyncProgressWs: any
flightAreasUpdateWs: any
}; };
const emitter: Emitter<Events> = mitt<Events>() const emitter: Emitter<Events> = mitt<Events>()

182
src/hooks/use-g-map-cover.ts

@ -1,3 +1,4 @@
import { EFlightAreaType } from '../types/flight-area'
import pin19be6b from '/@/assets/icons/pin-19be6b.svg' import pin19be6b from '/@/assets/icons/pin-19be6b.svg'
import pin212121 from '/@/assets/icons/pin-212121.svg' import pin212121 from '/@/assets/icons/pin-212121.svg'
import pin2d8cf0 from '/@/assets/icons/pin-2d8cf0.svg' import pin2d8cf0 from '/@/assets/icons/pin-2d8cf0.svg'
@ -14,12 +15,16 @@ export function useGMapCover () {
const normalColor = '#2D8CF0' const normalColor = '#2D8CF0'
const store = rootStore const store = rootStore
const coverList = store.state.coverList const coverMap = store.state.coverMap
const flightAreaColorMap = {
[EFlightAreaType.DFENCE]: '#19be6b',
[EFlightAreaType.NFZ]: '#ff0000',
}
const disableColor = '#b3b3b3'
function AddCoverToMap (cover :any) { function AddCoverToMap (cover :any) {
root.$map.add(cover) root.$map.add(cover)
coverList.push(cover) coverMap[cover.getExtData().id] = [cover]
// console.log('coverList:', store.state.coverList)
} }
function getPinIcon (color?:string) { function getPinIcon (color?:string) {
@ -29,10 +34,10 @@ export function useGMapCover () {
} = { } = {
'2d8cf0': pin2d8cf0, '2d8cf0': pin2d8cf0,
'19be6b': pin19be6b, '19be6b': pin19be6b,
'212121': pin212121, 212121: pin212121,
'b620e0': pinb620e0, b620e0: pinb620e0,
'e23c39': pine23c39, e23c39: pine23c39,
'ffbb00': pineffbb00, ffbb00: pineffbb00,
} }
const iconName = (color?.replaceAll('#', '') || '').toLocaleLowerCase() const iconName = (color?.replaceAll('#', '') || '').toLocaleLowerCase()
return new AMap.Icon({ return new AMap.Icon({
@ -44,7 +49,6 @@ export function useGMapCover () {
} }
function init2DPin (name: string, coordinates:GeojsonCoordinate, color?:string, data?:{}) { function init2DPin (name: string, coordinates:GeojsonCoordinate, color?:string, data?:{}) {
console.log(name, coordinates[0], coordinates[1], color, data)
const pin = new AMap.Marker({ const pin = new AMap.Marker({
position: new AMap.LngLat(coordinates[0], coordinates[1]), position: new AMap.LngLat(coordinates[0], coordinates[1]),
title: name, title: name,
@ -59,7 +63,8 @@ export function useGMapCover () {
function AddOverlayGroup (overlayGroup) { function AddOverlayGroup (overlayGroup) {
root.$map.add(overlayGroup) root.$map.add(overlayGroup)
coverList.push(overlayGroup) const id = overlayGroup.getExtData().id
coverMap[id] = [...(coverMap[id] || []), overlayGroup]
} }
function initPolyline (name: string, coordinates:GeojsonCoordinate[], color?:string, data?:{}) { function initPolyline (name: string, coordinates:GeojsonCoordinate[], color?:string, data?:{}) {
const path = [] as GeojsonCoordinate[] const path = [] as GeojsonCoordinate[]
@ -98,36 +103,18 @@ export function useGMapCover () {
} }
function removeCoverFromMap (id:string) { function removeCoverFromMap (id:string) {
for (let i = 0; i < coverList.length; i++) { coverMap[id].forEach(cover => root.$map.remove(cover))
const ele = coverList[i] coverMap[id] = []
// console.log(ele)
const extdata = ele?.getExtData()
if (extdata?.id === id) {
console.log(extdata)
root.$map.remove(ele)
coverList.slice(i, 1)
break
}
}
} }
function getElementFromMap (id:string) { function getElementFromMap (id:string): any[] {
// console.log('start', new Date().getTime()) return coverMap[id]
const ele = coverList.find(ele => ele?.getExtData().id === id)
// console.log('end', new Date().getTime())
return ele
// coverList.forEach((ele:any) => {
// const extdata = ele?.getExtData()
// // console.log(extdata)
// if (extdata?.id === id) {
// return ele
// }
// })
} }
function updatePinElement (id:string, name: string, coordinates:GeojsonCoordinate, color?:string) { function updatePinElement (id:string, name: string, coordinates:GeojsonCoordinate, color?:string) {
const element = getElementFromMap(id) as any const elements = getElementFromMap(id)
if (element) { if (elements && elements.length > 0) {
const element = elements[0]
const icon = getPinIcon(color) const icon = getPinIcon(color)
element.setPosition(new AMap.LngLat(coordinates[0], coordinates[1])) element.setPosition(new AMap.LngLat(coordinates[0], coordinates[1]))
element.setIcon(icon) element.setIcon(icon)
@ -142,8 +129,9 @@ export function useGMapCover () {
} }
function updatePolylineElement (id:string, name: string, coordinates:GeojsonCoordinate[], color?:string) { function updatePolylineElement (id:string, name: string, coordinates:GeojsonCoordinate[], color?:string) {
const element = getElementFromMap(id) as any const elements = getElementFromMap(id)
if (element) { if (elements && elements.length > 0) {
const element = elements[0]
const options = element.getOptions() const options = element.getOptions()
options.strokeColor = color || normalColor options.strokeColor = color || normalColor
element.setOptions(options) element.setOptions(options)
@ -156,8 +144,9 @@ export function useGMapCover () {
} }
function updatePolygonElement (id:string, name: string, coordinates:GeojsonCoordinate[][], color?:string) { function updatePolygonElement (id:string, name: string, coordinates:GeojsonCoordinate[][], color?:string) {
const element = getElementFromMap(id) as any const elements = getElementFromMap(id)
if (element) { if (elements && elements.length > 0) {
const element = elements[0]
const options = element.getOptions() const options = element.getOptions()
options.fillColor = color || normalColor options.fillColor = color || normalColor
options.strokeColor = color || normalColor options.strokeColor = color || normalColor
@ -170,6 +159,116 @@ export function useGMapCover () {
} }
} }
function initTextInfo (content: string, coordinates: GeojsonCoordinate, id: string) {
const info = new AMap.Text({
text: content,
position: new AMap.LngLat(coordinates[0], coordinates[1]),
extData: { id: id, type: 'text' },
anchor: 'top-center',
style: {
background: 'none',
borderStyle: 'none',
fontSize: '16px',
},
})
AddOverlayGroup(info)
}
function initFlightAreaCircle (name: string, radius: number, position: GeojsonCoordinate, data: { id: string, type: EFlightAreaType, enable: boolean }) {
const circle = new AMap.Circle({
strokeColor: data.enable ? flightAreaColorMap[data.type] : disableColor,
strokeOpacity: 1,
strokeWeight: 6,
extData: data,
strokeStyle: 'dashed',
strokeDasharray: EFlightAreaType.NFZ === data.type ? [10, 2] : [10, 1, 2],
fillColor: flightAreaColorMap[data.type],
fillOpacity: EFlightAreaType.NFZ === data.type && data.enable ? 0.3 : 0,
radius: radius,
center: new AMap.LngLat(position[0], position[1]),
})
AddOverlayGroup(circle)
initTextInfo(name, position, data.id)
}
function updateFlightAreaCircle (id: string, name: string, radius: number, position: GeojsonCoordinate, enable: boolean, type: EFlightAreaType) {
const elements = getElementFromMap(id)
if (elements && elements.length > 0) {
let textIndex = elements.findIndex(ele => ele.getExtData()?.type === 'text')
if (textIndex === -1) {
textIndex = 1
initTextInfo(name, position, id)
} else {
const text = elements[textIndex]
text.setText(name)
text.setPosition(position)
}
const element = elements[textIndex ^ 1]
const options = element.getOptions()
options.fillOpacity = EFlightAreaType.NFZ === type && enable ? 0.3 : 0
options.strokeColor = enable ? flightAreaColorMap[type] : disableColor
options.radius = radius
options.center = new AMap.LngLat(position[0], position[1])
element.setOptions(options)
} else {
initFlightAreaCircle(name, radius, position, { id, type, enable })
}
}
function calcPolygonPosition (coordinate: GeojsonCoordinate[]): GeojsonCoordinate {
const index = coordinate.length - 1
return [(coordinate[0][0] + coordinate[index][0]) / 2.0, (coordinate[0][1] + coordinate[index][1]) / 2]
}
function initFlightAreaPolygon (name: string, coordinates: GeojsonCoordinate[], data: { id: string, type: EFlightAreaType, enable: boolean }) {
const path = [] as GeojsonCoordinate[]
coordinates.forEach(coordinate => {
path.push(new AMap.LngLat(coordinate[0], coordinate[1]))
})
const polygon = new AMap.Polygon({
path: path,
strokeColor: data.enable ? flightAreaColorMap[data.type] : disableColor,
strokeOpacity: 1,
strokeWeight: 4,
draggable: true,
extData: data,
strokeStyle: 'dashed',
strokeDasharray: EFlightAreaType.NFZ === data.type ? [10, 2] : [10, 1, 2],
fillColor: flightAreaColorMap[data.type],
fillOpacity: EFlightAreaType.NFZ === data.type && data.enable ? 0.3 : 0,
})
AddOverlayGroup(polygon)
initTextInfo(name, calcPolygonPosition(coordinates), data.id)
}
function updateFlightAreaPolygon (id: string, name: string, coordinates: GeojsonCoordinate[], enable: boolean, type: EFlightAreaType) {
const elements = getElementFromMap(id)
if (elements && elements.length > 0) {
let textIndex = elements.findIndex(ele => ele.getExtData()?.type === 'text')
if (textIndex === -1) {
textIndex = 1
initTextInfo(name, calcPolygonPosition(coordinates), id)
} else {
const text = elements[textIndex]
text.setText(name)
text.setPosition(calcPolygonPosition(coordinates))
}
const element = elements[textIndex ^ 1]
const options = element.getOptions()
const path = [] as GeojsonCoordinate[]
coordinates.forEach(coordinate => {
path.push(new AMap.LngLat(coordinate[0], coordinate[1]))
})
options.path = path
options.fillOpacity = EFlightAreaType.NFZ === type && enable ? 0.3 : 0
options.strokeColor = enable ? flightAreaColorMap[type] : disableColor
element.setOptions(options)
} else {
initFlightAreaPolygon(name, coordinates, { id, type, enable })
}
}
return { return {
init2DPin, init2DPin,
initPolyline, initPolyline,
@ -178,6 +277,11 @@ export function useGMapCover () {
getElementFromMap, getElementFromMap,
updatePinElement, updatePinElement,
updatePolylineElement, updatePolylineElement,
updatePolygonElement updatePolygonElement,
initFlightAreaCircle,
initFlightAreaPolygon,
updateFlightAreaPolygon,
updateFlightAreaCircle,
calcPolygonPosition,
} }
} }

2
src/hooks/use-g-map.ts

@ -16,7 +16,7 @@ export function useGMapManage () {
state.aMap = AMap state.aMap = AMap
state.map = new AMap.Map(container, { state.map = new AMap.Map(container, {
center: [113.943225499, 22.577673716], center: [113.943225499, 22.577673716],
zoom: 15 zoom: 20
}) })
state.mouseTool = new AMap.MouseTool(state.map) state.mouseTool = new AMap.MouseTool(state.map)

16
src/hooks/use-map-tool.ts

@ -0,0 +1,16 @@
import { GeojsonCoordinate } from '../utils/genjson'
import { getRoot } from '/@/root'
export function useMapTool () {
const root = getRoot()
const map = root.$map
const AMap = root.$aMap
function panTo (coordinate: GeojsonCoordinate) {
map.panTo(coordinate, 100)
map.setZoom(18, false, 100)
}
return {
panTo,
}
}

58
src/hooks/use-mouse-tool.ts

@ -3,6 +3,8 @@ import pin2d8cf0 from '/@/assets/icons/pin-2d8cf0.svg'
import { MapDoodleType } from '/@/constants/map' import { MapDoodleType } from '/@/constants/map'
import { getRoot } from '/@/root' import { getRoot } from '/@/root'
import { MapDoodleEnum } from '/@/types/map-enum' import { MapDoodleEnum } from '/@/types/map-enum'
import { EFlightAreaType } from '../types/flight-area'
import { message } from 'ant-design-vue'
export function useMouseTool () { export function useMouseTool () {
const root = getRoot() const root = getRoot()
@ -13,7 +15,10 @@ export function useMouseTool () {
PolygonNum: 0, PolygonNum: 0,
currentType: '', currentType: '',
}) })
const flightAreaColorMap = {
[EFlightAreaType.DFENCE]: '#19be6b',
[EFlightAreaType.NFZ]: '#ff0000',
}
function drawPin (type:MapDoodleType, getDrawCallback:Function) { function drawPin (type:MapDoodleType, getDrawCallback:Function) {
root?.$mouseTool.marker({ root?.$mouseTool.marker({
title: type + state.pinNum, title: type + state.pinNum,
@ -29,7 +34,6 @@ export function useMouseTool () {
strokeOpacity: 1, strokeOpacity: 1,
strokeWeight: 2, strokeWeight: 2,
strokeStyle: 'solid', strokeStyle: 'solid',
draggable: true,
title: type + state.polylineNum++ title: type + state.polylineNum++
}) })
root?.$mouseTool.on('draw', getDrawCallback) root?.$mouseTool.on('draw', getDrawCallback)
@ -42,7 +46,6 @@ export function useMouseTool () {
strokeWeight: 2, strokeWeight: 2,
fillColor: '#1791fc', fillColor: '#1791fc',
fillOpacity: 0.4, fillOpacity: 0.4,
draggable: true,
title: type + state.PolygonNum++ title: type + state.PolygonNum++
}) })
root?.$mouseTool.on('draw', getDrawCallback) root?.$mouseTool.on('draw', getDrawCallback)
@ -53,8 +56,55 @@ export function useMouseTool () {
root?.$mouseTool.off('draw') root?.$mouseTool.off('draw')
} }
function mouseTool (type: MapDoodleType, getDrawCallback: Function) { function drawFlightAreaPolygon (type: EFlightAreaType, getDrawFlightAreaCallback: Function) {
root?.$mouseTool.polygon({
strokeColor: flightAreaColorMap[type],
strokeOpacity: 1,
strokeWeight: 4,
extData: {
type: type,
mapType: 'polygon',
},
strokeStyle: 'dashed',
strokeDasharray: EFlightAreaType.NFZ === type ? [10, 2] : [10, 1, 2],
fillColor: flightAreaColorMap[type],
fillOpacity: EFlightAreaType.NFZ === type ? 0.3 : 0,
})
root?.$mouseTool.on('draw', getDrawFlightAreaCallback)
}
function drawFlightAreaCircle (type: EFlightAreaType, getDrawFlightAreaCallback: Function) {
root?.$mouseTool.circle({
strokeColor: flightAreaColorMap[type],
strokeOpacity: 1,
strokeWeight: 6,
extData: {
type: type,
mapType: 'circle',
},
strokeStyle: 'dashed',
strokeDasharray: EFlightAreaType.NFZ === type ? [10, 2] : [10, 1, 2],
fillColor: flightAreaColorMap[type],
fillOpacity: EFlightAreaType.NFZ === type ? 0.3 : 0,
})
root?.$mouseTool.on('draw', getDrawFlightAreaCallback)
}
function mouseTool (type: MapDoodleType, getDrawCallback: Function, flightAreaType?: EFlightAreaType) {
state.currentType = type state.currentType = type
if (flightAreaType) {
switch (type) {
case MapDoodleEnum.POLYGON:
drawFlightAreaPolygon(flightAreaType, getDrawCallback)
return
case MapDoodleEnum.CIRCLE:
drawFlightAreaCircle(flightAreaType, getDrawCallback)
return
default:
message.error(`Invalid type: ${flightAreaType}`)
return
}
}
switch (type) { switch (type) {
case MapDoodleEnum.PIN: case MapDoodleEnum.PIN:
drawPin(type, getDrawCallback) drawPin(type, getDrawCallback)

98
src/pages/page-web/projects/flight-area.vue

@ -0,0 +1,98 @@
<template>
<div class="project-flight-area-wrapper height-100">
<a-spin :spinning="loading" :delay="300" tip="loading" size="large" class="height-100">
<Title title="Custom Flight Area" />
<FlightAreaPanel :data="flightAreaList" @location-area="clickArea" @delete-area="deleteAreaById"/>
<DividerLine />
<FlightAreaSyncPanel />
</a-spin>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import Title from '/@/components/workspace/Title.vue'
import DividerLine from '/@/components/workspace/DividerLine.vue'
import FlightAreaPanel from '/@/components/flight-area/FlightAreaPanel.vue'
import FlightAreaSyncPanel from '/@/components/flight-area/FlightAreaSyncPanel.vue'
import { GetFlightArea, deleteFlightArea, getFlightAreaList } from '/@/api/flight-area'
import { useGMapCover } from '/@/hooks/use-g-map-cover'
import { useMapTool } from '/@/hooks/use-map-tool'
import { EFlightAreaType, EGeometryType, FlightAreaUpdate } from '/@/types/flight-area'
import { useFlightArea } from '/@/components/flight-area/use-flight-area'
import { useFlightAreaUpdateEvent } from '/@/components/flight-area/use-flight-area-update'
const loading = ref(false)
const flightAreaList = ref<GetFlightArea[]>([])
let useGMapCoverHook = useGMapCover()
let useMapToolHook = useMapTool()
onMounted(() => {
getDataList()
})
const { getGcj02 } = useFlightArea()
const initMapFlightArea = () => {
useMapToolHook = useMapTool()
useGMapCoverHook = useGMapCover()
flightAreaList.value.forEach(area => {
updateMapFlightArea(area)
})
}
const updateMapFlightArea = (area: GetFlightArea) => {
switch (area.content.geometry.type) {
case EGeometryType.CIRCLE:
useGMapCoverHook.updateFlightAreaCircle(area.area_id, area.name, area.content.geometry.radius, getGcj02(area.content.geometry.coordinates), area.status, area.type)
break
case 'Polygon':
useGMapCoverHook.updateFlightAreaPolygon(area.area_id, area.name, getGcj02(area.content.geometry.coordinates[0]), area.status, area.type)
break
}
}
const getDataList = () => {
loading.value = true
getFlightAreaList().then(res => {
flightAreaList.value = res.data
setTimeout(initMapFlightArea, 2000)
}).finally(() => {
loading.value = false
})
}
const deleteAreaById = (areaId: string) => {
deleteFlightArea(areaId)
}
const deleteArea = (area: FlightAreaUpdate) => {
flightAreaList.value = flightAreaList.value.filter(data => data.area_id !== area.area_id)
useGMapCoverHook.removeCoverFromMap(area.area_id)
}
const updateArea = (area: FlightAreaUpdate) => {
flightAreaList.value = flightAreaList.value.map(data => data.area_id === area.area_id ? area : data)
updateMapFlightArea(area as GetFlightArea)
}
const addArea = (area: FlightAreaUpdate) => {
flightAreaList.value.push(area as GetFlightArea)
updateMapFlightArea(area as GetFlightArea)
}
useFlightAreaUpdateEvent(addArea, deleteArea, updateArea)
const clickArea = (area: GetFlightArea) => {
console.info(area)
let coordinate
switch (area.content.geometry.type) {
case EGeometryType.CIRCLE:
coordinate = getGcj02(area.content.geometry.coordinates)
break
case 'Polygon':
coordinate = useGMapCoverHook.calcPolygonPosition(getGcj02(area.content.geometry.coordinates[0]))
break
}
useMapToolHook.panTo(coordinate)
}
</script>

12
src/pages/page-web/projects/workspace.vue

@ -109,6 +109,18 @@ const messageHandler = async (payload: any) => {
EventBus.emit('droneControlWs', payload) EventBus.emit('droneControlWs', payload)
break break
} }
case EBizCode.FlightAreasSyncProgress: {
EventBus.emit('flightAreasSyncProgressWs', payload.data)
break
}
case EBizCode.FlightAreasDroneLocation: {
EventBus.emit('flightAreasDroneLocationWs', payload)
break
}
case EBizCode.FlightAreasUpdate: {
EventBus.emit('flightAreasUpdateWs', payload.data)
break
}
default: default:
break break
} }

7
src/router/index.ts

@ -103,7 +103,12 @@ const routes: Array<RouteRecordRaw> = [
} }
] ]
} },
{
path: '/' + ERouterName.FLIGHT_AREA,
name: ERouterName.FLIGHT_AREA,
component: () => import('/@/pages/page-web/projects/flight-area.vue')
},
] ]
}, },
// pilot // pilot

7
src/store/index.ts

@ -6,6 +6,7 @@ import { getLayers } from '/@/api/layer'
import { LayerType } from '/@/types/mapLayer' import { LayerType } from '/@/types/mapLayer'
import { WaylineFile } from '/@/types/wayline' import { WaylineFile } from '/@/types/wayline'
import { DevicesCmdExecuteInfo } from '/@/types/device-cmd' import { DevicesCmdExecuteInfo } from '/@/types/device-cmd'
import { FlightAreaStatus } from '../api/flight-area'
const initStateFunc = () => ({ const initStateFunc = () => ({
Layers: [ Layers: [
@ -34,9 +35,9 @@ const initStateFunc = () => ({
drawVisible: false, drawVisible: false,
livestreamOthersVisible: false, livestreamOthersVisible: false,
livestreamAgoraVisible: false, livestreamAgoraVisible: false,
coverList: [ coverMap: {} as {
[key: string]: any[]
] as any, },
wsEvent: { wsEvent: {
mapElementCreat: {}, mapElementCreat: {},
mapElementUpdate: {}, mapElementUpdate: {},

8
src/types/device.ts

@ -22,7 +22,7 @@ export enum DOMAIN {
export enum DRONE_TYPE { export enum DRONE_TYPE {
M30 = 67, M30 = 67,
M300 = 60, M300 = 60,
Mavic3EnterpriseAdvanced = 77, Mavic3EnterpriseAdvanced= 77,
M350 = 89, M350 = 89,
M3D = 91, M3D = 91,
} }
@ -184,7 +184,7 @@ export interface OnlineDevice {
// 固件升级类型 // 固件升级类型
export enum DeviceFirmwareTypeEnum { export enum DeviceFirmwareTypeEnum {
ToUpgraded = 3, // 普通升级 ToUpgraded = 3, // 普通升级
ConsistencyUpgrade = 2, // 一致性升级 ConsistencyUpgrade =2, // 一致性升级
} }
// 固件升级状态 // 固件升级状态
@ -245,7 +245,7 @@ export interface OSDVisible {
is_dock: boolean, is_dock: boolean,
gateway_sn: string, gateway_sn: string,
gateway_callsign: string, gateway_callsign: string,
payloads: null | PayloadInfo[], payloads: null | PayloadInfo [],
} }
export interface GatewayOsd { export interface GatewayOsd {
@ -402,7 +402,7 @@ export interface DockLinkOsd {
down_quality: string, down_quality: string,
frequency_band: number, frequency_band: number,
}, },
wireless_link?: { // 图传链路<会包括4G和sdr信息 wireless_link?:{ // 图传链路<会包括4G和sdr信息
dongle_number: number, // dongle 数量 dongle_number: number, // dongle 数量
['4g_link_state']: FourGLinkStateEnum, // 4g_link_state ['4g_link_state']: FourGLinkStateEnum, // 4g_link_state
sdr_link_state: SdrLinkStateEnum, // sdr链路连接状态 sdr_link_state: SdrLinkStateEnum, // sdr链路连接状态

6
src/types/enums.ts

@ -15,6 +15,7 @@ export enum ERouterName {
CREATE_PLAN = 'create-plan', CREATE_PLAN = 'create-plan',
SELECT_PLAN = 'select-plan', SELECT_PLAN = 'select-plan',
FIRMWARES = 'firmwares', FIRMWARES = 'firmwares',
FLIGHT_AREA = 'flight-area',
PILOT = 'pilot-login', PILOT = 'pilot-login',
PILOT_HOME = 'pilot-home', PILOT_HOME = 'pilot-home',
@ -123,6 +124,11 @@ export enum EBizCode {
TakeoffToPointProgress = 'takeoff_to_point_progress', // 一键起飞 TakeoffToPointProgress = 'takeoff_to_point_progress', // 一键起飞
JoystickInvalidNotify = 'joystick_invalid_notify', // 设备端退出drc模式 JoystickInvalidNotify = 'joystick_invalid_notify', // 设备端退出drc模式
DrcStatusNotify = 'drc_status_notify', // 飞行控制模式状态 DrcStatusNotify = 'drc_status_notify', // 飞行控制模式状态
// custom flight area
FlightAreasSyncProgress = 'flight_areas_sync_progress',
FlightAreasDroneLocation = 'flight_areas_drone_location',
FlightAreasUpdate = 'flight_areas_update',
} }
export enum EDeviceTypeName { export enum EDeviceTypeName {

80
src/types/flight-area.ts

@ -0,0 +1,80 @@
import { GeojsonCoordinate, GeojsonPolygon } from '../utils/genjson'
export enum EFlightAreaType {
NFZ = 'nfz',
DFENCE = 'dfence',
}
export enum EGeometryType {
CIRCLE = 'Circle',
POLYGON = 'Polygon',
}
export enum EFlightAreaUpdate {
ADD = 'add',
UPDATE = 'update',
DELETE = 'delete',
}
export enum ESyncStatus {
WAIT_SYNC = 'wait_sync',
SWITCH_FAIL = 'switch_fail',
SYNCHRONIZING = 'synchronizing',
SYNCHRONIZED = 'synchronized',
FAIL = 'fail',
}
export interface GeojsonCircle {
type: 'Feature'
properties: {
color: string
clampToGround?: boolean
}
geometry: {
type: EGeometryType.CIRCLE
coordinates: GeojsonCoordinate
radius: number
}
}
export interface DroneLocation {
area_distance: number,
area_id: string,
is_in_area: boolean,
}
export interface FlightAreasDroneLocation {
drone_locations: DroneLocation[]
}
export type FlightAreaContent = GeojsonCircle | GeojsonPolygon
export interface FlightAreaUpdate {
operation: EFlightAreaUpdate,
area_id: string,
name: string,
type: EFlightAreaType,
content: FlightAreaContent,
status: boolean,
username: string,
create_time: number,
update_time: number,
}
export interface FlightAreaSyncProgress {
sn: string,
result: number,
status: ESyncStatus,
message: string,
}
export const FlightAreaTypeTitleMap = {
[EFlightAreaType.NFZ]: {
[EGeometryType.CIRCLE]: 'Circular GEO Zone',
[EGeometryType.POLYGON]: 'Polygonal GEO Zone',
},
[EFlightAreaType.DFENCE]: {
[EGeometryType.CIRCLE]: 'Circular Task Area',
[EGeometryType.POLYGON]: 'Polygonal Task Area',
},
}

3
src/types/map-enum.ts

@ -2,5 +2,6 @@ export enum MapDoodleEnum {
PIN = 'pin', PIN = 'pin',
POLYLINE = 'polyline', POLYLINE = 'polyline',
POLYGON = 'polygon', POLYGON = 'polygon',
Close = 'off' Close = 'off',
CIRCLE = 'circle',
} }

27
src/utils/genjson.ts

@ -39,7 +39,20 @@ export interface GeojsonPoint {
} }
} }
export type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint export interface GeojsonCircle {
type: 'Feature'
properties: {
color: string
clampToGround?: boolean
}
geometry: {
type: 'Circle'
coordinates: GeojsonCoordinate
radius: number
}
}
export type GeojsonFeature = GeojsonLine | GeojsonPolygon | GeojsonPoint | GeojsonCircle
export function geographic2Coordinate (position: MapGeographicPosition): GeojsonCoordinate { export function geographic2Coordinate (position: MapGeographicPosition): GeojsonCoordinate {
const coordinates: GeojsonCoordinate = [position.longitude, position.latitude] const coordinates: GeojsonCoordinate = [position.longitude, position.latitude]
@ -79,3 +92,15 @@ export function generatePoint (position: MapGeographicPosition, properties: Geoj
}, },
} }
} }
export function generateCircle (position: MapGeographicPosition, properties: GeojsonCircle['properties'], radius: number): GeojsonFeature {
return {
type: 'Feature',
properties,
geometry: {
type: 'Circle',
coordinates: geographic2Coordinate(position),
radius: radius,
},
}
}

7
src/utils/map-layer-utils.ts

@ -1,5 +1,5 @@
import { pinAMapPosition, MapGeographicPosition, Layer, LayerType, LayerElevationLoadStatus } from '/@/types/map' import { pinAMapPosition, MapGeographicPosition, Layer, LayerType, LayerElevationLoadStatus } from '/@/types/map'
import { generatePoint, generateLine, generatePolygon } from '/@/utils/genjson' import { generatePoint, generateLine, generatePolygon, generateCircle } from '/@/utils/genjson'
import { MapDoodleColor, MapElementEnum } from '/@/constants/map' import { MapDoodleColor, MapElementEnum } from '/@/constants/map'
function getPinPosition (pinAMapPosition: pinAMapPosition):MapGeographicPosition { function getPinPosition (pinAMapPosition: pinAMapPosition):MapGeographicPosition {
return { height: 0, latitude: pinAMapPosition.lat, longitude: pinAMapPosition.lng } return { height: 0, latitude: pinAMapPosition.lat, longitude: pinAMapPosition.lng }
@ -42,3 +42,8 @@ export function generatePolyContent (mapPosition: pinAMapPosition[]) {
}) })
} }
} }
export function generateCircleContent (pinAMapPosition: pinAMapPosition, radius: number) {
const position = getPinPosition(pinAMapPosition)
return generateCircle(position, { color: MapDoodleColor.PolygonColor }, radius)
}

2
src/utils/time.ts

@ -6,7 +6,7 @@ import moment, { Moment } from 'moment'
// 时间字符串 或者 Unix 时间戳(毫秒数) // 时间字符串 或者 Unix 时间戳(毫秒数)
export function formatDateTime (time: string | number, format = DATE_FORMAT) { export function formatDateTime (time: string | number, format = DATE_FORMAT) {
return time ? moment(time, format) : DEFAULT_PLACEHOLDER return time ? moment(time).format(format) : DEFAULT_PLACEHOLDER
} }
// Unix 时间戳 (秒) // Unix 时间戳 (秒)

2
src/vendors/jswebrtc.min.js vendored

File diff suppressed because one or more lines are too long

687
src/vendors/srs.sdk.js vendored

@ -0,0 +1,687 @@
//
// Copyright (c) 2013-2021 Winlin
//
// SPDX-License-Identifier: MIT
//
'use strict';
function SrsError(name, message) {
this.name = name;
this.message = message;
this.stack = (new Error()).stack;
}
SrsError.prototype = Object.create(Error.prototype);
SrsError.prototype.constructor = SrsError;
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-awat-prmise based SRS RTC Publisher.
function SrsRtcPublisherAsync() {
var self = {};
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
self.constraints = {
audio: true,
video: {
width: { ideal: 320, max: 576 }
}
};
// @see https://github.com/rtcdn/rtcdn-draft
// @url The WebRTC url to play with, for example:
// webrtc://r.ossrs.net/live/livestream
// or specifies the API port:
// webrtc://r.ossrs.net:11985/live/livestream
// or autostart the publish:
// webrtc://r.ossrs.net/live/livestream?autostart=true
// or change the app from live to myapp:
// webrtc://r.ossrs.net:11985/myapp/livestream
// or change the stream from livestream to mystream:
// webrtc://r.ossrs.net:11985/live/mystream
// or set the api server to myapi.domain.com:
// webrtc://myapi.domain.com/live/livestream
// or set the candidate(eip) of answer:
// webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
// or force to access https API:
// webrtc://r.ossrs.net/live/livestream?schema=https
// or use plaintext, without SRTP:
// webrtc://r.ossrs.net/live/livestream?encrypt=false
// or any other information, will pass-by in the query:
// webrtc://r.ossrs.net/live/livestream?vhost=xxx
// webrtc://r.ossrs.net/live/livestream?token=xxx
self.publish = async function (url) {
var conf = self.__internal.prepareUrl(url);
self.pc.addTransceiver("audio", { direction: "sendonly" });
self.pc.addTransceiver("video", { direction: "sendonly" });
//self.pc.addTransceiver("video", {direction: "sendonly"});
//self.pc.addTransceiver("audio", {direction: "sendonly"});
if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
}
var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
stream.getTracks().forEach(function (track) {
self.pc.addTrack(track);
// Notify about local track when stream is ok.
self.ontrack && self.ontrack({ track: track });
});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
var session = await new Promise(function (resolve, reject) {
// @see https://github.com/rtcdn/rtcdn-draft
var data = {
api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
clientip: null, sdp: offer.sdp
};
console.log("Generated offer: ", data);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = JSON.parse(xhr.responseText);
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', conf.apiUrl, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify(data));
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: session.sdp })
);
session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
return session;
};
// Close the publisher.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got local stream.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
self.ontrack = function (event) {
// Add track to stream of SDK.
self.stream.addTrack(event.track);
};
// Internal APIs.
self.__internal = {
defaultPath: '/rtc/v1/publish/',
prepareUrl: function (webrtcUrl) {
var urlObject = self.__internal.parse(webrtcUrl);
// If user specifies the schema, use it as API schema.
var schema = urlObject.user_query.schema;
schema = schema ? schema + ':' : window.location.protocol;
var port = urlObject.port || 1985;
if (schema === 'https:') {
port = urlObject.port || 443;
}
// @see https://github.com/rtcdn/rtcdn-draft
var api = urlObject.user_query.play || self.__internal.defaultPath;
if (api.lastIndexOf('/') !== api.length - 1) {
api += '/';
}
var apiUrl = schema + '//' + urlObject.server + ':' + port + api;
for (var key in urlObject.user_query) {
if (key !== 'api' && key !== 'play') {
apiUrl += '&' + key + '=' + urlObject.user_query[key];
}
}
// Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
apiUrl = apiUrl.replace(api + '&', api + '?');
var streamUrl = urlObject.url;
return {
apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
tid: Number(parseInt(new Date().getTime() * Math.random() * 100)).toString(16).slice(0, 7)
};
},
parse: function (url) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document.createElement("a");
a.href = url.replace("rtmp://", "http://")
.replace("webrtc://", "http://")
.replace("rtc://", "http://");
var vhost = a.hostname;
var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
// parse the vhost in the params of app, that srs supports.
app = app.replace("...vhost...", "?vhost=");
if (app.indexOf("?") >= 0) {
var params = app.slice(app.indexOf("?"));
app = app.slice(0, app.indexOf("?"));
if (params.indexOf("vhost=") > 0) {
vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
if (vhost.indexOf("&") > 0) {
vhost = vhost.slice(0, vhost.indexOf("&"));
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if (a.hostname === vhost) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
if (re.test(a.hostname)) {
vhost = "__defaultVhost__";
}
}
// parse the schema
var schema = "rtmp";
if (url.indexOf("://") > 0) {
schema = url.slice(0, url.indexOf("://"));
}
var port = a.port;
if (!port) {
// Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
}
// Guess by schema.
if (schema === 'http') {
port = 80;
} else if (schema === 'https') {
port = 443;
} else if (schema === 'rtmp') {
port = 1935;
}
}
var ret = {
url: url,
schema: schema,
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
self.__internal.fill_query(a.search, ret);
// For webrtc API, we use 443 if page is https, or schema specified it.
if (!ret.port) {
if (schema === 'webrtc' || schema === 'rtc') {
if (ret.user_query.schema === 'https') {
ret.port = 443;
} else if (window.location.href.indexOf('https://') === 0) {
ret.port = 443;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret.port = 1985;
}
}
}
return ret;
},
fill_query: function (query_string, obj) {
// pure user query object.
obj.user_query = {};
if (query_string.length === 0) {
return;
}
// split again for angularjs.
if (query_string.indexOf("?") >= 0) {
query_string = query_string.split("?")[1];
}
var queries = query_string.split("&");
for (var i = 0; i < queries.length; i++) {
var elem = queries[i];
var query = elem.split("=");
obj[query[0]] = query[1];
obj.user_query[query[0]] = query[1];
}
// alias domain for vhost.
if (obj.domain) {
obj.vhost = obj.domain;
}
}
};
self.pc = new RTCPeerConnection(null);
// To keep api consistent between player and publisher.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
// @see https://webrtc.org/getting-started/media-devices
self.stream = new MediaStream();
return self;
}
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-await-promise based SRS RTC Player.
function SrsRtcPlayerAsync() {
var self = {};
// @see https://github.com/rtcdn/rtcdn-draft
// @url The WebRTC url to play with, for example:
// webrtc://r.ossrs.net/live/livestream
// or specifies the API port:
// webrtc://r.ossrs.net:11985/live/livestream
// webrtc://r.ossrs.net:80/live/livestream
// or autostart the play:
// webrtc://r.ossrs.net/live/livestream?autostart=true
// or change the app from live to myapp:
// webrtc://r.ossrs.net:11985/myapp/livestream
// or change the stream from livestream to mystream:
// webrtc://r.ossrs.net:11985/live/mystream
// or set the api server to myapi.domain.com:
// webrtc://myapi.domain.com/live/livestream
// or set the candidate(eip) of answer:
// webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
// or force to access https API:
// webrtc://r.ossrs.net/live/livestream?schema=https
// or use plaintext, without SRTP:
// webrtc://r.ossrs.net/live/livestream?encrypt=false
// or any other information, will pass-by in the query:
// webrtc://r.ossrs.net/live/livestream?vhost=xxx
// webrtc://r.ossrs.net/live/livestream?token=xxx
self.play = async function (url) {
var conf = self.__internal.prepareUrl(url);
self.pc.addTransceiver("audio", { direction: "recvonly" });
self.pc.addTransceiver("video", { direction: "recvonly" });
//self.pc.addTransceiver("video", {direction: "recvonly"});
//self.pc.addTransceiver("audio", {direction: "recvonly"});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
var session = await new Promise(function (resolve, reject) {
// @see https://github.com/rtcdn/rtcdn-draft
var data = {
api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
clientip: null, sdp: offer.sdp
};
console.log("Generated offer: ", data);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = JSON.parse(xhr.responseText);
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', conf.apiUrl, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify(data));
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: session.sdp })
);
session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
return session;
};
// Close the player.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got remote track.
// Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream
self.ontrack = function (event) {
// https://webrtc.org/getting-started/remote-streams
self.stream.addTrack(event.track);
};
// Internal APIs.
self.__internal = {
defaultPath: '/rtc/v1/play/',
prepareUrl: function (webrtcUrl) {
var urlObject = self.__internal.parse(webrtcUrl);
// If user specifies the schema, use it as API schema.
var schema = urlObject.user_query.schema;
schema = schema ? schema + ':' : window.location.protocol;
var port = urlObject.port || 1985;
if (schema === 'https:') {
port = urlObject.port || 443;
}
// @see https://github.com/rtcdn/rtcdn-draft
var api = urlObject.user_query.play || self.__internal.defaultPath;
if (api.lastIndexOf('/') !== api.length - 1) {
api += '/';
}
var apiUrl = schema + '//' + urlObject.server + ':' + port + api;
for (var key in urlObject.user_query) {
if (key !== 'api' && key !== 'play') {
apiUrl += '&' + key + '=' + urlObject.user_query[key];
}
}
// Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
apiUrl = apiUrl.replace(api + '&', api + '?');
var streamUrl = urlObject.url;
return {
apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
tid: Number(parseInt(new Date().getTime() * Math.random() * 100)).toString(16).slice(0, 7)
};
},
parse: function (url) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document.createElement("a");
a.href = url.replace("rtmp://", "http://")
.replace("webrtc://", "http://")
.replace("rtc://", "http://");
var vhost = a.hostname;
var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
// parse the vhost in the params of app, that srs supports.
app = app.replace("...vhost...", "?vhost=");
if (app.indexOf("?") >= 0) {
var params = app.slice(app.indexOf("?"));
app = app.slice(0, app.indexOf("?"));
if (params.indexOf("vhost=") > 0) {
vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
if (vhost.indexOf("&") > 0) {
vhost = vhost.slice(0, vhost.indexOf("&"));
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if (a.hostname === vhost) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
if (re.test(a.hostname)) {
vhost = "__defaultVhost__";
}
}
// parse the schema
var schema = "rtmp";
if (url.indexOf("://") > 0) {
schema = url.slice(0, url.indexOf("://"));
}
var port = a.port;
if (!port) {
// Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
}
// Guess by schema.
if (schema === 'http') {
port = 80;
} else if (schema === 'https') {
port = 443;
} else if (schema === 'rtmp') {
port = 1935;
}
}
var ret = {
url: url,
schema: schema,
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
self.__internal.fill_query(a.search, ret);
// For webrtc API, we use 443 if page is https, or schema specified it.
if (!ret.port) {
if (schema === 'webrtc' || schema === 'rtc') {
if (ret.user_query.schema === 'https') {
ret.port = 443;
} else if (window.location.href.indexOf('https://') === 0) {
ret.port = 443;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret.port = 1985;
}
}
}
return ret;
},
fill_query: function (query_string, obj) {
// pure user query object.
obj.user_query = {};
if (query_string.length === 0) {
return;
}
// split again for angularjs.
if (query_string.indexOf("?") >= 0) {
query_string = query_string.split("?")[1];
}
var queries = query_string.split("&");
for (var i = 0; i < queries.length; i++) {
var elem = queries[i];
var query = elem.split("=");
obj[query[0]] = query[1];
obj.user_query[query[0]] = query[1];
}
// alias domain for vhost.
if (obj.domain) {
obj.vhost = obj.domain;
}
}
};
self.pc = new RTCPeerConnection(null);
// Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams
self.stream = new MediaStream();
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
self.pc.ontrack = function (event) {
if (self.ontrack) {
self.ontrack(event);
}
};
return self;
}
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-awat-prmise based SRS RTC Publisher by WHIP.
function SrsRtcWhipWhepAsync() {
var self = {};
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
self.constraints = {
audio: true,
video: {
width: { ideal: 320, max: 576 }
}
};
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
// @url The WebRTC url to publish with, for example:
// http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream
self.publish = async function (url) {
if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`);
self.pc.addTransceiver("audio", { direction: "sendonly" });
self.pc.addTransceiver("video", { direction: "sendonly" });
if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
}
var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
stream.getTracks().forEach(function (track) {
self.pc.addTrack(track);
// Notify about local track when stream is ok.
self.ontrack && self.ontrack({ track: track });
});
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
const answer = await new Promise(function (resolve, reject) {
console.log("Generated offer: ", offer);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = xhr.responseText;
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/sdp');
xhr.send(offer.sdp);
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: answer })
);
return self.__internal.parseId(url, offer.sdp, answer);
};
// See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
// @url The WebRTC url to play with, for example:
// http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream
self.play = async function (url) {
if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`);
self.pc.addTransceiver("video", { direction: "recvonly" });
var offer = await self.pc.createOffer();
await self.pc.setLocalDescription(offer);
const answer = await new Promise(function (resolve, reject) {
console.log("Generated offer: ", offer);
const xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState !== xhr.DONE) return;
if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
const data = xhr.responseText;
console.log("Got answer: ", data);
return data.code ? reject(xhr) : resolve(data);
}
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/sdp');
xhr.send(offer.sdp);
});
await self.pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: answer })
);
return self.__internal.parseId(url, offer.sdp, answer);
};
// Close the publisher.
self.close = function () {
self.pc && self.pc.close();
self.pc = null;
};
// The callback when got local stream.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
self.ontrack = function (event) {
// Add track to stream of SDK.
self.stream.addTrack(event.track);
};
self.pc = new RTCPeerConnection(null);
// To keep api consistent between player and publisher.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
// @see https://webrtc.org/getting-started/media-devices
self.stream = new MediaStream();
// Internal APIs.
self.__internal = {
parseId: (url, offer, answer) => {
let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':';
sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
sessionid = sessionid.substr(0, sessionid.indexOf('\n'));
const a = document.createElement("a");
a.href = url;
return {
sessionid: sessionid, // Should be ice-ufrag of answer:offer.
simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/',
};
},
};
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
self.pc.ontrack = function (event) {
if (self.ontrack) {
self.ontrack(event);
}
};
return self;
}
// Format the codec of RTCRtpSender, kind(audio/video) is optional filter.
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs
function SrsRtcFormatSenders(senders, kind) {
var codecs = [];
senders.forEach(function (sender) {
var params = sender.getParameters();
params && params.codecs && params.codecs.forEach(function (c) {
if (kind && sender.track.kind !== kind) {
return;
}
if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) {
return;
}
var s = '';
s += c.mimeType.replace('audio/', '').replace('video/', '');
s += ', ' + c.clockRate + 'HZ';
if (sender.track.kind === "audio") {
s += ', channels: ' + c.channels;
}
s += ', pt: ' + c.payloadType;
codecs.push(s);
});
});
return codecs.join(", ");
}
export default {
SrsError,
SrsRtcPublisherAsync,
SrsRtcPlayerAsync,
SrsRtcWhipWhepAsync,
SrsRtcFormatSenders,
}

6
src/websocket/index.ts

@ -1,5 +1,6 @@
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import ReconnectingWebSocket from 'reconnecting-websocket' import ReconnectingWebSocket from 'reconnecting-websocket'
import { EBizCode } from '../types'
interface WebSocketOptions { interface WebSocketOptions {
data: any data: any
@ -11,6 +12,11 @@ export interface MessageHandler {
(data : {[key: string]: any}): void (data : {[key: string]: any}): void
} }
export interface CommonHostWs<T> {
sn: string
host: T
}
/** /**
* ConnectWebSocket * ConnectWebSocket
* TODO: 优化messageHandler: EventEmitter * TODO: 优化messageHandler: EventEmitter

452
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save