diff --git a/src/main/java/com/genersoft/iot/vmp/media/bean/MediaInfo.java b/src/main/java/com/genersoft/iot/vmp/media/bean/MediaInfo.java index ace469866..6c288e5e4 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/bean/MediaInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/media/bean/MediaInfo.java @@ -33,6 +33,10 @@ public class MediaInfo { private Integer width; @Schema(description = "视频高度") private Integer height; + @Schema(description = "FPS") + private Integer fps; + @Schema(description = "丢包率") + private Integer loss; @Schema(description = "音频编码类型") private String audioCodec; @Schema(description = "音频通道数") @@ -58,6 +62,7 @@ public class MediaInfo { @Schema(description = "服务ID") private String serverId; + public static MediaInfo getInstance(JSONObject jsonObject, MediaServer mediaServer, String serverId) { MediaInfo mediaInfo = new MediaInfo(); mediaInfo.setMediaServer(mediaServer); @@ -112,6 +117,13 @@ public class MediaInfo { Integer sampleRate = trackJson.getInteger("sample_rate"); Integer height = trackJson.getInteger("height"); Integer width = trackJson.getInteger("height"); + Integer fps = trackJson.getInteger("fps"); + Integer loss = trackJson.getInteger("loss"); + Integer frames = trackJson.getInteger("frames"); + Long keyFrames = trackJson.getLongValue("key_frames"); + Integer gop_interval_ms = trackJson.getInteger("gop_interval_ms"); + Long gop_size = trackJson.getLongValue("gop_size"); + Long duration = trackJson.getLongValue("duration"); if (channels != null) { mediaInfo.setAudioChannels(channels); @@ -125,6 +137,12 @@ public class MediaInfo { if (width != null) { mediaInfo.setWidth(width); } + if (fps != null) { + mediaInfo.setFps(fps); + } + if (loss != null) { + mediaInfo.setLoss(loss); + } if (duration > 0L) { mediaInfo.setDuration(duration); } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java index 6b1c1d135..87aa801ea 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -118,15 +118,6 @@ public class ZLMHttpHookListener { } } - /** - * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 - */ -// @ResponseBody -// @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") -// public HookResult onStreamChanged(@RequestBody JSONObject param) { -// System.out.println(11); -// return HookResult.SUCCESS(); -// } /** * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 */ diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java index 075197565..7292c9bd2 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java @@ -11,6 +11,7 @@ import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.security.JwtUtils; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; +import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerChangeEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; @@ -162,6 +163,20 @@ public class ServerController { mediaServerService.delete(mediaServer); } + @Operation(summary = "获取流信息", security = @SecurityRequirement(name = JwtUtils.HEADER)) + @Parameter(name = "app", description = "应用名", required = true) + @Parameter(name = "stream", description = "流ID", required = true) + @Parameter(name = "mediaServerId", description = "流媒体ID", required = true) + @GetMapping(value = "/media_server/media_info") + @ResponseBody + public MediaInfo getMediaInfo(String app, String stream, String mediaServerId) { + MediaServer mediaServer = mediaServerService.getOne(mediaServerId); + if (mediaServer == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "流媒体不存在"); + } + return mediaServerService.getMediaInfo(mediaServer, app, stream); + } + @Operation(summary = "重启服务", security = @SecurityRequirement(name = JwtUtils.HEADER)) @GetMapping(value = "/restart") diff --git a/web_src/src/components/common/mediaInfo.vue b/web_src/src/components/common/mediaInfo.vue new file mode 100644 index 000000000..b410ce398 --- /dev/null +++ b/web_src/src/components/common/mediaInfo.vue @@ -0,0 +1,98 @@ +<template> + <div id="mediaInfo" > + <el-button style="position: absolute; right: 1rem;" icon="el-icon-refresh-right" circle size="mini" @click="getMediaInfo"></el-button> + <el-descriptions size="mini" :column="3" title="概况"> + <el-descriptions-item label="观看人数">{{ info.readerCount }}</el-descriptions-item> + <el-descriptions-item label="网络">{{ formatByteSpeed() }}</el-descriptions-item> + <el-descriptions-item label="持续时间">{{info.aliveSecond}}秒</el-descriptions-item> + </el-descriptions> + <div style="display: grid; grid-template-columns: 1fr 1fr"> + <el-descriptions size="mini" v-if="info.videoCodec" :column="2" title="视频信息"> + <el-descriptions-item label="编码">{{ info.videoCodec }}</el-descriptions-item> + <el-descriptions-item label="分辨率" + >{{ info.width }}x{{ info.height }} + </el-descriptions-item> + <el-descriptions-item label="FPS">{{ info.fps }}</el-descriptions-item> + <el-descriptions-item label="丢包率">{{ info.loss }}</el-descriptions-item> + </el-descriptions> + <el-descriptions size="mini" v-if="info.audioCodec" :column="2" title="音频信息"> + <el-descriptions-item label="编码"> + {{ info.audioCodec }} + </el-descriptions-item> + <el-descriptions-item label="采样率">{{ info.audioSampleRate }}</el-descriptions-item> + </el-descriptions> + </div> + + </div> +</template> + +<script> + +export default { + name: "mediaInfo", + props: [ 'app', 'stream', 'mediaServerId'], + components: {}, + created() { + this.getMediaInfo() + }, + data() { + return { + info: {} + }; + }, + methods: { + getMediaInfo: function () { + this.$axios({ + method: 'get', + url: `/api/server/media_server/media_info`, + params: { + app: this.app, + stream: this.stream, + mediaServerId: this.mediaServerId, + } + }).then((res)=> { + console.log(res.data.data); + if (res.data.code === 0) { + this.info = res.data.data + } + + }).catch((error)=> { + + console.log(error); + }); + }, + formatByteSpeed: function (){ + let bytesSpeed = this.info.bytesSpeed + let num = 1024.0 //byte + if (bytesSpeed < num) return bytesSpeed + ' B/S' + if (bytesSpeed < Math.pow(num, 2)) return (bytesSpeed / num).toFixed(2) + ' KB/S' //kb + if (bytesSpeed < Math.pow(num, 3)) + return (bytesSpeed / Math.pow(num, 2)).toFixed(2) + ' MB/S' //M + if (bytesSpeed < Math.pow(num, 4)) + return (bytesSpeed / Math.pow(num, 3)).toFixed(2) + ' G/S' //G + return (bytesSpeed / Math.pow(num, 4)).toFixed(2) + ' T/S' //T + }, + formatAliveSecond: function (){ + let aliveSecond = this.info.aliveSecond + const h = parseInt(aliveSecond.value / 3600) + const minute = parseInt((aliveSecond.value / 60) % 60) + const second = Math.ceil(aliveSecond.value % 60) + + const hours = h < 10 ? '0' + h : h + const formatSecond = second > 59 ? 59 : second + return `${hours > 0 ? `${hours}小时` : ''}${minute < 10 ? '0' + minute : minute}分${ + formatSecond < 10 ? '0' + formatSecond : formatSecond + }秒` + } + }, +}; +</script> +<style> +.channel-form { + display: grid; + background-color: #FFFFFF; + padding: 1rem 2rem 0 2rem; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem; +} +</style> diff --git a/web_src/src/components/dialog/devicePlayer.vue b/web_src/src/components/dialog/devicePlayer.vue index f02c2c9fd..97162b5f4 100755 --- a/web_src/src/components/dialog/devicePlayer.vue +++ b/web_src/src/components/dialog/devicePlayer.vue @@ -232,31 +232,7 @@ </div> </el-tab-pane> <el-tab-pane label="编码信息" name="codec" v-loading="tracksLoading"> - <p> - 无法播放或者没有声音?   试一试  - <el-button size="mini" type="primary" v-if="!coverPlaying" @click="coverPlay">转码播放</el-button> - <el-button size="mini" type="danger" v-if="coverPlaying" @click="convertStopClick">停止转码</el-button> - </p> - <div class="trank"> - <p v-if="tracksNotLoaded" style="text-align: center;padding-top: 3rem;">暂无数据</p> - <div v-for="(item, index) in tracks" style="width: 50%; float: left" loading> - <span>流 {{ index }}</span> - <div class="trankInfo" v-if="item.codec_type == 0"> - <p>格式: {{ item.codec_id_name }}</p> - <p>类型: 视频</p> - <p>分辨率: {{ item.width }} x {{ item.height }}</p> - <p>帧率: {{ item.fps }}</p> - </div> - <div class="trankInfo" v-if="item.codec_type == 1"> - <p>格式: {{ item.codec_id_name }}</p> - <p>类型: 音频</p> - <p>采样位数: {{ item.sample_bit }}</p> - <p>采样率: {{ item.sample_rate }}</p> - </div> - </div> - - </div> - + <mediaInfo :app="app" :stream="streamId" :mediaServerId="mediaServerId"></mediaInfo> </el-tab-pane> <el-tab-pane label="语音对讲" name="broadcast"> <div style="padding: 0 10px"> @@ -293,12 +269,13 @@ import PtzCruising from "../common/ptzCruising.vue"; import ptzScan from "../common/ptzScan.vue"; import ptzWiper from "../common/ptzWiper.vue"; import ptzSwitch from "../common/ptzSwitch.vue"; +import mediaInfo from "../common/mediaInfo.vue"; export default { name: 'devicePlayer', props: {}, components: { - PtzPreset,PtzCruising,ptzScan,ptzWiper,ptzSwitch, + PtzPreset,PtzCruising,ptzScan,ptzWiper,ptzSwitch,mediaInfo, LivePlayer, jessibucaPlayer, rtcPlayer, }, computed: { @@ -334,7 +311,6 @@ export default { ptzPresetId: '', app: '', mediaServerId: '', - convertKey: '', deviceId: '', channelId: '', tabActiveName: 'media', @@ -353,7 +329,6 @@ export default { scanSpeed: 100, scanGroup: 0, tracks: [], - coverPlaying: false, tracksLoading: false, showPtz: true, showRrecord: true, @@ -453,63 +428,6 @@ export default { } return this.videoUrl; - }, - coverPlay: function () { - var that = this; - this.coverPlaying = true; - this.$refs[this.activePlayer].pause() - that.$axios({ - method: 'post', - url: '/api/play/convert/' + that.streamId - }).then(function (res) { - if (res.data.code === 0) { - that.convertKey = res.data.key; - setTimeout(() => { - that.isLoging = false; - that.playFromStreamInfo(false, res.data.data); - }, 2000) - } else { - that.isLoging = false; - that.coverPlaying = false; - that.$message({ - showClose: true, - message: '转码失败', - type: 'error' - }); - } - }).catch(function (e) { - console.log(e) - that.coverPlaying = false; - that.$message({ - showClose: true, - message: '播放错误', - type: 'error' - }); - }); - }, - convertStopClick: function () { - this.convertStop(() => { - this.$refs[this.activePlayer].play(this.videoUrl) - }); - }, - convertStop: function (callback) { - var that = this; - that.$refs.videoPlayer.pause() - this.$axios({ - method: 'post', - url: '/api/play/convertStop/' + this.convertKey - }).then(function (res) { - if (res.data.code == 0) { - console.log(res.data.msg) - } else { - console.error(res.data.msg) - } - if (callback) callback(); - }).catch(function (e) { - }); - that.coverPlaying = false; - that.convertKey = ""; - // if (callback )callback(); }, playFromStreamInfo: function (realHasAudio, streamInfo) { @@ -531,10 +449,6 @@ export default { this.videoUrl = ''; this.coverPlaying = false; this.showVideoDialog = false; - if (this.convertKey != '') { - this.convertStop(); - } - this.convertKey = '' this.stopBroadcast() },