feat: 新增流媒体推流统计卡片

This commit is contained in:
yangsy
2026-01-04 11:27:46 +08:00
parent 80e879e61f
commit 670054ca01
7 changed files with 169 additions and 3 deletions

View File

@@ -1 +1,3 @@
export * from './invite-stream-type';
export * from './send-rtp-info';
export * from './snap-result';

View File

@@ -0,0 +1 @@
export type InviteStreamType = 'PLAY' | 'PLAYBACK' | 'DOWNLOAD' | 'PUSH' | 'PROXY' | 'CLOUD_RECORD_PUSH' | 'CLOUD_RECORD_PROXY';

View 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的ptuint8_t,不传时默认为96
*/
pt: number;
/**
* 发送时rtp的负载类型。为true时负载为ps为false时为es
*/
usePs: boolean;
/**
* 当usePs 为false时有效。为1时发送音频为0时发送视频不传时默认为0
*/
onlyAudio: boolean;
/**
* 是否开启rtcp保活
*/
rtcp: boolean;
playType: InviteStreamType;
}>;

View File

@@ -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';
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 }) => {
const { stationCode, signal } = options ?? {};
const client = stationCode ? ndmClient : userClient;

View File

@@ -2,6 +2,7 @@ import ServerAlive from './server-alive.vue';
import ServerCard from './server-card.vue';
import ServerCurrentDiag from './server-current-diag.vue';
import ServerHistoryDiag from './server-history-diag.vue';
import ServerStreamPush from './server-stream-push.vue';
import ServerUpdate from './server-update.vue';
export { ServerAlive, ServerCard, ServerCurrentDiag, ServerHistoryDiag, ServerUpdate };
export { ServerAlive, ServerCard, ServerCurrentDiag, ServerHistoryDiag, ServerUpdate, ServerStreamPush };

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
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 { NFlex } from 'naive-ui';
import { computed, toRefs } from 'vue';
@@ -30,6 +30,7 @@ const runningTime = computed(() => lastDiagInfo.value?.commInfo?.系统运行时
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
<DeviceHardwareCard running-time-label="服务器运行时间" :cpu-usage="cpuUsage" :mem-usage="memUsage" :disk-usage="diskUsage" :running-time="runningTime" />
<ServerAlive :ndm-device="ndmDevice" :station="station" />
<ServerStreamPush :ndm-device="ndmDevice" :station="station" />
</NFlex>
</template>

View File

@@ -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>