feat: 新增流媒体推流统计卡片
This commit is contained in:
@@ -1 +1,3 @@
|
|||||||
|
export * from './invite-stream-type';
|
||||||
|
export * from './send-rtp-info';
|
||||||
export * from './snap-result';
|
export * from './snap-result';
|
||||||
|
|||||||
1
src/apis/model/biz/vimp/invite-stream-type.ts
Normal file
1
src/apis/model/biz/vimp/invite-stream-type.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type InviteStreamType = 'PLAY' | 'PLAYBACK' | 'DOWNLOAD' | 'PUSH' | 'PROXY' | 'CLOUD_RECORD_PUSH' | 'CLOUD_RECORD_PROXY';
|
||||||
51
src/apis/model/biz/vimp/send-rtp-info.ts
Normal file
51
src/apis/model/biz/vimp/send-rtp-info.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import type { InviteStreamType } from '@/apis';
|
||||||
|
import type { Nullable } from '@/types';
|
||||||
|
|
||||||
|
export type SendRtpInfo = Nullable<{
|
||||||
|
ip: string;
|
||||||
|
port: number;
|
||||||
|
ssrc: string;
|
||||||
|
platformId: string;
|
||||||
|
deviceId: string;
|
||||||
|
channelId: string;
|
||||||
|
app: string;
|
||||||
|
streamId: string;
|
||||||
|
/**
|
||||||
|
* 推流状态
|
||||||
|
* 0 等待设备推流上来
|
||||||
|
* 1 等待上级平台回复ack
|
||||||
|
* 2 推流中
|
||||||
|
*/
|
||||||
|
status: number;
|
||||||
|
/**
|
||||||
|
* 是否为tcp
|
||||||
|
*/
|
||||||
|
tcp: boolean;
|
||||||
|
/**
|
||||||
|
* 是否为tcp主动模式
|
||||||
|
*/
|
||||||
|
tcpActive: boolean;
|
||||||
|
localPort: number;
|
||||||
|
mediaServerId: string;
|
||||||
|
serverId: string;
|
||||||
|
callId: string;
|
||||||
|
fromTag: string;
|
||||||
|
toTag: string;
|
||||||
|
/**
|
||||||
|
* 发送时,rtp的pt(uint8_t),不传时默认为96
|
||||||
|
*/
|
||||||
|
pt: number;
|
||||||
|
/**
|
||||||
|
* 发送时,rtp的负载类型。为true时,负载为ps;为false时,为es;
|
||||||
|
*/
|
||||||
|
usePs: boolean;
|
||||||
|
/**
|
||||||
|
* 当usePs 为false时,有效。为1时,发送音频;为0时,发送视频;不传时默认为0
|
||||||
|
*/
|
||||||
|
onlyAudio: boolean;
|
||||||
|
/**
|
||||||
|
* 是否开启rtcp保活
|
||||||
|
*/
|
||||||
|
rtcp: boolean;
|
||||||
|
playType: InviteStreamType;
|
||||||
|
}>;
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
import { ndmClient, userClient, type MediaServerStatus, type Station } from '@/apis';
|
import { ndmClient, userClient, type MediaServerStatus, type SendRtpInfo, type Station } from '@/apis';
|
||||||
import { unwrapResponse } from '@/utils';
|
import { unwrapResponse } from '@/utils';
|
||||||
|
|
||||||
|
export const getAllPushApi = async (options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||||
|
const { stationCode, signal } = options ?? {};
|
||||||
|
const client = stationCode ? ndmClient : userClient;
|
||||||
|
const prefix = stationCode ? `/${stationCode}` : '';
|
||||||
|
const endpoint = `${prefix}/api/ndm/ndmServiceAvailable/mediaServer/getAllPush`;
|
||||||
|
const resp = await client.get<SendRtpInfo[]>(endpoint, { signal });
|
||||||
|
const data = unwrapResponse(resp);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
export const isMediaServerAliveApi = async (options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
export const isMediaServerAliveApi = async (options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||||
const { stationCode, signal } = options ?? {};
|
const { stationCode, signal } = options ?? {};
|
||||||
const client = stationCode ? ndmClient : userClient;
|
const client = stationCode ? ndmClient : userClient;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import ServerAlive from './server-alive.vue';
|
|||||||
import ServerCard from './server-card.vue';
|
import ServerCard from './server-card.vue';
|
||||||
import ServerCurrentDiag from './server-current-diag.vue';
|
import ServerCurrentDiag from './server-current-diag.vue';
|
||||||
import ServerHistoryDiag from './server-history-diag.vue';
|
import ServerHistoryDiag from './server-history-diag.vue';
|
||||||
|
import ServerStreamPush from './server-stream-push.vue';
|
||||||
import ServerUpdate from './server-update.vue';
|
import ServerUpdate from './server-update.vue';
|
||||||
|
|
||||||
export { ServerAlive, ServerCard, ServerCurrentDiag, ServerHistoryDiag, ServerUpdate };
|
export { ServerAlive, ServerCard, ServerCurrentDiag, ServerHistoryDiag, ServerUpdate, ServerStreamPush };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type NdmServerDiagInfo, type NdmServerResultVO, type Station } from '@/apis';
|
import { type NdmServerDiagInfo, type NdmServerResultVO, type Station } from '@/apis';
|
||||||
import { DeviceHardwareCard, DeviceHeaderCard, ServerAlive } from '@/components';
|
import { DeviceHardwareCard, DeviceHeaderCard, ServerAlive, ServerStreamPush } from '@/components';
|
||||||
import destr from 'destr';
|
import destr from 'destr';
|
||||||
import { NFlex } from 'naive-ui';
|
import { NFlex } from 'naive-ui';
|
||||||
import { computed, toRefs } from 'vue';
|
import { computed, toRefs } from 'vue';
|
||||||
@@ -30,6 +30,7 @@ const runningTime = computed(() => lastDiagInfo.value?.commInfo?.系统运行时
|
|||||||
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
||||||
<DeviceHardwareCard running-time-label="服务器运行时间" :cpu-usage="cpuUsage" :mem-usage="memUsage" :disk-usage="diskUsage" :running-time="runningTime" />
|
<DeviceHardwareCard running-time-label="服务器运行时间" :cpu-usage="cpuUsage" :mem-usage="memUsage" :disk-usage="diskUsage" :running-time="runningTime" />
|
||||||
<ServerAlive :ndm-device="ndmDevice" :station="station" />
|
<ServerAlive :ndm-device="ndmDevice" :station="station" />
|
||||||
|
<ServerStreamPush :ndm-device="ndmDevice" :station="station" />
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { getAllPushApi, type NdmServerResultVO, type Station } from '@/apis';
|
||||||
|
import { DEVICE_TYPE_LITERALS, tryGetDeviceType } from '@/enums';
|
||||||
|
import { useSettingStore } from '@/stores';
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/vue-query';
|
||||||
|
import { NCard, NCollapse, NCollapseItem, NFlex, NTag, NText } from 'naive-ui';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { computed, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
ndmDevice: NdmServerResultVO;
|
||||||
|
station: Station;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
const { offlineDev } = storeToRefs(settingStore);
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { ndmDevice, station } = toRefs(props);
|
||||||
|
|
||||||
|
const deviceType = computed(() => tryGetDeviceType(ndmDevice.value.deviceType));
|
||||||
|
|
||||||
|
const showCard = computed(() => deviceType.value === DEVICE_TYPE_LITERALS.ndmMediaServer);
|
||||||
|
|
||||||
|
const SERVER_STREAM_PUSH_KEY = 'server-stream-push-query';
|
||||||
|
|
||||||
|
const { data: streamPushes } = useQuery({
|
||||||
|
queryKey: computed(() => [SERVER_STREAM_PUSH_KEY, ndmDevice.value.id, ndmDevice.value.lastDiagTime]),
|
||||||
|
enabled: computed(() => !offlineDev.value && showCard.value),
|
||||||
|
refetchInterval: 30 * 1000,
|
||||||
|
gcTime: 0,
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const streamPushes = await getAllPushApi({ stationCode: station.value.code, signal });
|
||||||
|
return streamPushes;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
watch(offlineDev, (offline) => {
|
||||||
|
if (offline) {
|
||||||
|
queryClient.cancelQueries({ queryKey: [SERVER_STREAM_PUSH_KEY] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface StreamPushStat {
|
||||||
|
ip: string;
|
||||||
|
port: number | null;
|
||||||
|
ssrc: string | null;
|
||||||
|
channelIds: string[];
|
||||||
|
}
|
||||||
|
const streamPushStat = computed(() => {
|
||||||
|
const stat: StreamPushStat[] = [];
|
||||||
|
streamPushes.value?.forEach((push) => {
|
||||||
|
if (!push.ip || !push.channelId) return;
|
||||||
|
const existIndex = stat.findIndex((item) => item.ip === push.ip);
|
||||||
|
if (existIndex === -1) {
|
||||||
|
stat.push({ ip: push.ip, port: push.port, ssrc: push.ssrc, channelIds: [push.channelId] });
|
||||||
|
} else {
|
||||||
|
const statItem = stat[existIndex];
|
||||||
|
if (!statItem) return;
|
||||||
|
statItem.channelIds.push(push.channelId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return stat;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NCard v-if="showCard" hoverable size="small">
|
||||||
|
<template #header>
|
||||||
|
<span>推流统计</span>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<template v-if="offlineDev">
|
||||||
|
<span>-</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<NFlex vertical>
|
||||||
|
<NText depth="3">共 {{ streamPushStat.length }} 个推流目标</NText>
|
||||||
|
<NCollapse v-if="streamPushStat.length > 0">
|
||||||
|
<NCollapseItem v-for="{ ip, port, ssrc, channelIds } in streamPushStat" :key="`${ip}:${port}:${ssrc}`" :name="`${ip}:${port}:${ssrc}`">
|
||||||
|
<template #header>
|
||||||
|
<span>{{ ip }}</span>
|
||||||
|
</template>
|
||||||
|
<template #header-extra>
|
||||||
|
<span>{{ channelIds.length }} 路</span>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<NFlex>
|
||||||
|
<NTag v-for="channelId in channelIds" :key="channelId" size="small" type="info" :bordered="false">{{ channelId }}</NTag>
|
||||||
|
</NFlex>
|
||||||
|
</template>
|
||||||
|
</NCollapseItem>
|
||||||
|
</NCollapse>
|
||||||
|
</NFlex>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</NCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
Reference in New Issue
Block a user