修复ui开启音频无法播放的bug
修复可能导致录象查看的bug
修复开启openRTPServer时的bug
| | |
| | | # wvp |
| | | WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的网络视频平台,负责实现核心信令与设备管理后台部分,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR、DVR接入。 |
| | | 流媒体服务基于ZLMediaKit-https://github.com/xiongziliang/ZLMediaKit |
| | | 流媒体服务基于ZLMediaKit-https://github.com/xiongziliang/ZLMediaKit |
| | | 前段页面基于MediaServerUI进行修改. |
| | | |
| | | # 应用场景: |
| | |
| | |  |
| | | |
| | | # 原版特性: |
| | | 1. 视频预览 |
| | | 2. 云台控制(方向、缩放控制) |
| | | 3. 视频设备信息同步 |
| | | 4. 离在线监控 |
| | | 5. 录像查询与回放(基于NVR\DVR,暂不支持快进、seek操作) |
| | | 6. 无人观看自动断流 |
| | | 1. 视频预览; |
| | | 2. 云台控制(方向、缩放控制); |
| | | 3. 视频设备信息同步; |
| | | 4. 离在线监控; |
| | | 5. 录像查询与回放(基于NVR\DVR,暂不支持快进、seek操作); |
| | | 6. 无人观看自动断流; |
| | | 7. 支持UDP和TCP两种国标信令传输模式; |
| | | |
| | | |
| | | # 新支持特性 |
| | | 1. 集成web界面, 不需要单独部署前端服务, 直接利用wvp内置文件服务部署. |
| | | 2. 支持平台接入, 针对大平台大量设备的情况进行优化. |
| | | 3. 支持检索,通道筛选. |
| | | 4. 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题. |
| | | 5. 支持启用udp多端口模式, 提高udp模式下媒体传输性能. |
| | | 6. 支持通道是否含有音频的设置 |
| | | 7. 支持通道子目录查询 |
| | | 8. 支持udp/tcp,两种模式传输视频流 |
| | | |
| | | # 新支持特性 |
| | | 1. 集成web界面, 不需要单独部署前端服务, 直接利用wvp内置文件服务部署, 随wvp一起部署; |
| | | 2. 支持平台接入, 针对大平台大量设备的情况进行优化; |
| | | 3. 支持检索,通道筛选; |
| | | 4. 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题; |
| | | 5. 支持启用udp多端口模式, 提高udp模式下媒体传输性能; |
| | | 6. 支持通道是否含有音频的设置; |
| | | 7. 支持通道子目录查询; |
| | | 8. 支持udp/tcp国标流传输模式; |
| | | 9. 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址 |
| | | 10. |
| | | # 待实现: |
| | | 上级级联 |
| | | 推流列表 |
| | |
| | | recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
|
| | | recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>\r\n");
|
| | | recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>\r\n");
|
| | | recordInfoXml.append("<Secrecy>0</Secrecy>\\r\n");
|
| | | recordInfoXml.append("<Secrecy>0</Secrecy>\r\n");
|
| | | // 大华NVR要求必须增加一个值为all的文本元素节点Type
|
| | | recordInfoXml.append("<Type>all</Type>\r\n");
|
| | | recordInfoXml.append("</Query>\r\n");
|
| | |
| | | streamInfo.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getLocalIP(), mediaInfo.getRtmpPort(), streamId));
|
| | | streamInfo.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId));
|
| | | streamInfo.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getLocalIP(), mediaInfo.getRtspPort(), streamId));
|
| | |
|
| | |
|
| | | storager.startPlay(streamInfo);
|
| | | }
|
| | |
|
| | |
| | | return sendPost("getMediaList",param); |
| | | } |
| | | |
| | | public JSONObject getMediaInfo(String app, String schema, String stream){ |
| | | Map<String, Object> param = new HashMap<>(); |
| | | param.put("app",app); |
| | | param.put("schema",schema); |
| | | param.put("stream",stream); |
| | | param.put("vhost","__defaultVhost__"); |
| | | return sendPost("getMediaInfo",param); |
| | | } |
| | | |
| | | public JSONObject getRtpInfo(String stream_id){ |
| | | Map<String, Object> param = new HashMap<>(); |
| | | param.put("stream_id",stream_id); |
| | |
| | | System.out.println(jsonObject.toJSONString()); |
| | | return newPort; |
| | | }else { |
| | | return getNewRTPPort(streamId); |
| | | return getNewRTPPort(ssrc); |
| | | } |
| | | } |
| | | |
| | |
| | |
|
| | | @Override
|
| | | public void updateCatch() {
|
| | |
|
| | | System.out.println("##################");
|
| | | }
|
| | |
|
| | | @Override
|
| | |
| | | |
| | | while (lockFlag) { |
| | | try { |
| | | |
| | | if (System.currentTimeMillis() - startTime > 15 * 1000) { |
| | | storager.stopPlay(streamInfo); |
| | | return new ResponseEntity<String>("timeout",HttpStatus.OK); |
| | | }else { |
| | | streamInfo = storager.queryPlayByDevice(deviceId, channelId); |
| | | JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); |
| | | if (rtpInfo == null || !rtpInfo.getBoolean("exist") || storager.queryPlayByDevice(deviceId, channelId).getFlv() == null){ |
| | | if (rtpInfo != null && rtpInfo.getBoolean("exist") && streamInfo.getFlv() != null){ |
| | | JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo("rtp", "rtmp", streamId); |
| | | if (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")) { |
| | | lockFlag = false; |
| | | JSONArray tracks = mediaInfo.getJSONArray("tracks"); |
| | | streamInfo.setTracks(tracks); |
| | | storager.startPlay(streamInfo); |
| | | }else { |
| | | |
| | | } |
| | | }else { |
| | | Thread.sleep(2000); |
| | | continue; |
| | | }else { |
| | | lockFlag = false; |
| | | streamInfo = storager.queryPlay(streamInfo); |
| | | }; |
| | | } |
| | | } catch (InterruptedException e) { |
| | |
| | | </el-table-column> |
| | | <el-table-column prop="ptztypeText" label="云台类型"> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="240" align="center" fixed="right"> |
| | | <el-table-column label="操作" width="280" align="center" fixed="right"> |
| | | <template slot-scope="scope"> |
| | | <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">播放</el-button> |
| | | <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="scope.row.play" @click="stopDevicePush(scope.row)">停止</el-button> |
| | | <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.parental == 1" @click="changeSubchannel(scope.row)">查看子目录</el-button> |
| | | <!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> --> |
| | | <el-button-group> |
| | | <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">播放</el-button> |
| | | <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="scope.row.play" @click="stopDevicePush(scope.row)">停止</el-button> |
| | | <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.parental == 1" @click="changeSubchannel(scope.row)">查看</el-button> |
| | | <!-- <el-button size="mini" icon="el-icon-video-camera" type="primary" >设备录象</el-button>--> |
| | | <!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> --> |
| | | </el-button-group> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <template> |
| | | <div id="devicePlayer"> |
| | | <el-dialog title="视频播放" top="0" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()"> |
| | | <LivePlayer v-if="showVideoDialog && hasaudio" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" hasaudio fluent autoplay live ></LivePlayer> |
| | | <LivePlayer v-if="showVideoDialog && !hasaudio" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" fluent autoplay live ></LivePlayer> |
| | | <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :hasaudio="hasaudio" fluent autoplay live ></LivePlayer> |
| | | <div id="shared" style="text-align: right; margin-top: 1rem;"> |
| | | <el-tabs v-model="tabActiveName"> |
| | | <el-tab-pane label="媒体流信息" name="media"> |
| | |
| | | methods: { |
| | | |
| | | play: function(streamInfo, deviceId, channelId, hasAudio) { |
| | | // this.hasaudio = hasAudio; |
| | | if (!hasAudio) { // hasaudio == false时设置播放器hasaudio false, 否则不设置 |
| | | this.hasaudio = hasAudio; |
| | | } |
| | | this.hasaudio = hasAudio; |
| | | // 根据媒体流信息二次判断 |
| | | // if( this.hasaudio && !!streamInfo.tracks && streamInfo.tracks.length > 0) { |
| | | // var realHasAudio = false; |
| | | // for (let i = 0; i < streamInfo.tracks; i++) { |
| | | // if (streamInfo.tracks[i].codec_type == 1) { // 判断为音频 |
| | | // realHasAudio = true; |
| | | // } |
| | | // } |
| | | // this.hasaudio = realHasAudio && this.hasaudio; |
| | | // } |
| | | if( this.hasaudio && !!streamInfo.tracks && streamInfo.tracks.length > 0) { |
| | | var realHasAudio = false; |
| | | for (let i = 0; i < streamInfo.tracks; i++) { |
| | | if (streamInfo.tracks[i].codec_type == 1) { // 判断为音频 |
| | | realHasAudio = true; |
| | | } |
| | | } |
| | | this.hasaudio = realHasAudio && this.hasaudio; |
| | | } |
| | | this.ssrc = streamInfo.ssrc; |
| | | this.deviceId = deviceId; |
| | | this.channelId = channelId; |