feat: 新增流媒体推流统计卡片
This commit is contained in:
@@ -1 +1,3 @@
|
||||
export * from './invite-stream-type';
|
||||
export * from './send-rtp-info';
|
||||
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';
|
||||
|
||||
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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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