panlinlin
4 years ago
37 changed files with 1418 additions and 218 deletions
@ -0,0 +1,112 @@ |
|||||
|
package com.genersoft.iot.vmp.media.zlm.dto; |
||||
|
|
||||
|
public class StreamProxyDto { |
||||
|
private String type; |
||||
|
private String app; |
||||
|
private String stream; |
||||
|
private String url; |
||||
|
private String src_url; |
||||
|
private String dst_url; |
||||
|
private int timeout_ms; |
||||
|
private String ffmpeg_cmd_key; |
||||
|
private String rtp_type; |
||||
|
private boolean enable; |
||||
|
private boolean enable_hls; |
||||
|
private boolean enable_mp4; |
||||
|
|
||||
|
public String getType() { |
||||
|
return type; |
||||
|
} |
||||
|
|
||||
|
public void setType(String type) { |
||||
|
this.type = type; |
||||
|
} |
||||
|
|
||||
|
public String getApp() { |
||||
|
return app; |
||||
|
} |
||||
|
|
||||
|
public void setApp(String app) { |
||||
|
this.app = app; |
||||
|
} |
||||
|
|
||||
|
public String getStream() { |
||||
|
return stream; |
||||
|
} |
||||
|
|
||||
|
public void setStream(String stream) { |
||||
|
this.stream = stream; |
||||
|
} |
||||
|
|
||||
|
public String getUrl() { |
||||
|
return url; |
||||
|
} |
||||
|
|
||||
|
public void setUrl(String url) { |
||||
|
this.url = url; |
||||
|
} |
||||
|
|
||||
|
public String getSrc_url() { |
||||
|
return src_url; |
||||
|
} |
||||
|
|
||||
|
public void setSrc_url(String src_url) { |
||||
|
this.src_url = src_url; |
||||
|
} |
||||
|
|
||||
|
public String getDst_url() { |
||||
|
return dst_url; |
||||
|
} |
||||
|
|
||||
|
public void setDst_url(String dst_url) { |
||||
|
this.dst_url = dst_url; |
||||
|
} |
||||
|
|
||||
|
public int getTimeout_ms() { |
||||
|
return timeout_ms; |
||||
|
} |
||||
|
|
||||
|
public void setTimeout_ms(int timeout_ms) { |
||||
|
this.timeout_ms = timeout_ms; |
||||
|
} |
||||
|
|
||||
|
public String getFfmpeg_cmd_key() { |
||||
|
return ffmpeg_cmd_key; |
||||
|
} |
||||
|
|
||||
|
public void setFfmpeg_cmd_key(String ffmpeg_cmd_key) { |
||||
|
this.ffmpeg_cmd_key = ffmpeg_cmd_key; |
||||
|
} |
||||
|
|
||||
|
public String getRtp_type() { |
||||
|
return rtp_type; |
||||
|
} |
||||
|
|
||||
|
public void setRtp_type(String rtp_type) { |
||||
|
this.rtp_type = rtp_type; |
||||
|
} |
||||
|
|
||||
|
public boolean isEnable() { |
||||
|
return enable; |
||||
|
} |
||||
|
|
||||
|
public void setEnable(boolean enable) { |
||||
|
this.enable = enable; |
||||
|
} |
||||
|
|
||||
|
public boolean isEnable_hls() { |
||||
|
return enable_hls; |
||||
|
} |
||||
|
|
||||
|
public void setEnable_hls(boolean enable_hls) { |
||||
|
this.enable_hls = enable_hls; |
||||
|
} |
||||
|
|
||||
|
public boolean isEnable_mp4() { |
||||
|
return enable_mp4; |
||||
|
} |
||||
|
|
||||
|
public void setEnable_mp4(boolean enable_mp4) { |
||||
|
this.enable_mp4 = enable_mp4; |
||||
|
} |
||||
|
} |
@ -0,0 +1,46 @@ |
|||||
|
package com.genersoft.iot.vmp.storager.dao; |
||||
|
|
||||
|
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyDto; |
||||
|
import org.apache.ibatis.annotations.*; |
||||
|
import org.springframework.stereotype.Repository; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Mapper |
||||
|
@Repository |
||||
|
public interface StreamProxyMapper { |
||||
|
|
||||
|
@Insert("INSERT INTO stream_proxy (type, app, stream, url, src_url, dst_url, " + |
||||
|
"timeout_ms, ffmpeg_cmd_key, rtp_type, enable_hls, enable_mp4, enable) VALUES" + |
||||
|
"('${type}','${app}', '${stream}', '${url}', '${src_url}', '${dst_url}', " + |
||||
|
"'${timeout_ms}', '${ffmpeg_cmd_key}', '${rtp_type}', ${enable_hls}, ${enable_mp4}, ${enable} )") |
||||
|
int add(StreamProxyDto streamProxyDto); |
||||
|
|
||||
|
@Update("UPDATE stream_proxy " + |
||||
|
"SET type=#{type}, " + |
||||
|
"app=#{app}," + |
||||
|
"stream=#{stream}," + |
||||
|
"url=#{url}, " + |
||||
|
"src_url=#{src_url}," + |
||||
|
"dst_url=#{dst_url}, " + |
||||
|
"timeout_ms=#{timeout_ms}, " + |
||||
|
"ffmpeg_cmd_key=#{ffmpeg_cmd_key}, " + |
||||
|
"rtp_type=#{rtp_type}, " + |
||||
|
"enable_hls=#{enable_hls}, " + |
||||
|
"enable=#{enable}, " + |
||||
|
"enable_mp4=#{enable_mp4} " + |
||||
|
"WHERE app=#{app} AND stream=#{stream}") |
||||
|
int update(StreamProxyDto streamProxyDto); |
||||
|
|
||||
|
@Delete("DELETE FROM stream_proxy WHERE app=#{app} AND stream=#{stream}") |
||||
|
int del(String app, String stream); |
||||
|
|
||||
|
@Select("SELECT * FROM stream_proxy") |
||||
|
List<StreamProxyDto> selectAll(); |
||||
|
|
||||
|
@Select("SELECT * FROM stream_proxy WHERE enable=${enable}") |
||||
|
List<StreamProxyDto> selectForEnable(boolean enable); |
||||
|
|
||||
|
@Select("SELECT * FROM stream_proxy WHERE app=#{app} AND stream=#{stream}") |
||||
|
StreamProxyDto selectOne(String app, String stream); |
||||
|
} |
@ -0,0 +1,52 @@ |
|||||
|
package com.genersoft.iot.vmp.vmanager.media; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.genersoft.iot.vmp.common.StreamInfo; |
||||
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
||||
|
import com.genersoft.iot.vmp.vmanager.service.IMediaService; |
||||
|
import com.genersoft.iot.vmp.vmanager.service.IStreamProxyService; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Controller; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
|
||||
|
@Controller |
||||
|
@CrossOrigin |
||||
|
@RequestMapping(value = "/api/media") |
||||
|
public class MediaController { |
||||
|
|
||||
|
private final static Logger logger = LoggerFactory.getLogger(MediaController.class); |
||||
|
|
||||
|
@Autowired |
||||
|
private IRedisCatchStorage redisCatchStorage; |
||||
|
|
||||
|
@Autowired |
||||
|
private IStreamProxyService streamProxyService; |
||||
|
|
||||
|
@Autowired |
||||
|
private IMediaService mediaService; |
||||
|
|
||||
|
|
||||
|
@RequestMapping(value = "/list") |
||||
|
@ResponseBody |
||||
|
public JSONObject list( @RequestParam(required = false)Integer page, |
||||
|
@RequestParam(required = false)Integer count, |
||||
|
@RequestParam(required = false)String q, |
||||
|
@RequestParam(required = false)Boolean online ){ |
||||
|
|
||||
|
JSONObject jsonObject = redisCatchStorage.getMediaList(page - 1, page - 1 + count); |
||||
|
return jsonObject; |
||||
|
} |
||||
|
|
||||
|
@RequestMapping(value = "/getStreamInfoByAppAndStream") |
||||
|
@ResponseBody |
||||
|
public StreamInfo getStreamInfoByAppAndStream(String app, String stream){ |
||||
|
return mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
package com.genersoft.iot.vmp.vmanager.service; |
||||
|
|
||||
|
import com.genersoft.iot.vmp.common.StreamInfo; |
||||
|
|
||||
|
/** |
||||
|
* 媒体信息业务 |
||||
|
*/ |
||||
|
public interface IMediaService { |
||||
|
|
||||
|
/** |
||||
|
* 根据应用名和流ID获取播放地址, 通过zlm接口检查是否存在 |
||||
|
* @param app |
||||
|
* @param stream |
||||
|
* @return |
||||
|
*/ |
||||
|
StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream); |
||||
|
|
||||
|
/** |
||||
|
* 根据应用名和流ID获取播放地址, 只是地址拼接 |
||||
|
* @param app |
||||
|
* @param stream |
||||
|
* @return |
||||
|
*/ |
||||
|
StreamInfo getStreamInfoByAppAndStream(String app, String stream); |
||||
|
} |
@ -0,0 +1,60 @@ |
|||||
|
package com.genersoft.iot.vmp.vmanager.service; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyDto; |
||||
|
import com.genersoft.iot.vmp.vmanager.streamProxy.StreamProxyController; |
||||
|
import com.github.pagehelper.PageInfo; |
||||
|
|
||||
|
public interface IStreamProxyService { |
||||
|
|
||||
|
/** |
||||
|
* 保存视频代理 |
||||
|
* @param param |
||||
|
*/ |
||||
|
void save(StreamProxyDto param); |
||||
|
|
||||
|
/** |
||||
|
* 添加视频代理到zlm |
||||
|
* @param param |
||||
|
* @return |
||||
|
*/ |
||||
|
JSONObject addStreamProxyToZlm(StreamProxyDto param); |
||||
|
|
||||
|
/** |
||||
|
* 从zlm移除视频代理 |
||||
|
* @param param |
||||
|
* @return |
||||
|
*/ |
||||
|
JSONObject removeStreamProxyFromZlm(StreamProxyDto param); |
||||
|
|
||||
|
/** |
||||
|
* 分页查询 |
||||
|
* @param page |
||||
|
* @param count |
||||
|
* @return |
||||
|
*/ |
||||
|
PageInfo<StreamProxyDto> getAll(Integer page, Integer count); |
||||
|
|
||||
|
/** |
||||
|
* 删除视频代理 |
||||
|
* @param app |
||||
|
* @param stream |
||||
|
*/ |
||||
|
void del(String app, String stream); |
||||
|
|
||||
|
/** |
||||
|
* 启用视频代理 |
||||
|
* @param app |
||||
|
* @param stream |
||||
|
* @return |
||||
|
*/ |
||||
|
boolean start(String app, String stream); |
||||
|
|
||||
|
/** |
||||
|
* 停用用视频代理 |
||||
|
* @param app |
||||
|
* @param stream |
||||
|
* @return |
||||
|
*/ |
||||
|
boolean stop(String app, String stream); |
||||
|
} |
@ -0,0 +1,52 @@ |
|||||
|
package com.genersoft.iot.vmp.vmanager.service.impl; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.genersoft.iot.vmp.common.StreamInfo; |
||||
|
import com.genersoft.iot.vmp.conf.MediaServerConfig; |
||||
|
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
||||
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
||||
|
import com.genersoft.iot.vmp.storager.IVideoManagerStorager; |
||||
|
import com.genersoft.iot.vmp.vmanager.service.IMediaService; |
||||
|
import com.genersoft.iot.vmp.vmanager.service.IStreamProxyService; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
@Service |
||||
|
public class MediaServiceImpl implements IMediaService { |
||||
|
|
||||
|
@Autowired |
||||
|
private IRedisCatchStorage redisCatchStorage; |
||||
|
|
||||
|
@Autowired |
||||
|
private IVideoManagerStorager storager; |
||||
|
|
||||
|
@Autowired |
||||
|
private ZLMRESTfulUtils zlmresTfulUtils; |
||||
|
|
||||
|
@Override |
||||
|
public StreamInfo getStreamInfoByAppAndStream(String app, String stream) { |
||||
|
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); |
||||
|
StreamInfo streamInfoResult = new StreamInfo(); |
||||
|
streamInfoResult.setRtmp(String.format("rtmp://%s:%s/%s/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), app, stream)); |
||||
|
streamInfoResult.setRtsp(String.format("rtsp://%s:%s/%s/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), app, stream)); |
||||
|
streamInfoResult.setFlv(String.format("http://%s:%s/%s/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), app, stream)); |
||||
|
streamInfoResult.setWs_flv(String.format("ws://%s:%s/%s/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), app, stream)); |
||||
|
streamInfoResult.setHls(String.format("http://%s:%s/%s/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), app, stream)); |
||||
|
streamInfoResult.setWs_hls(String.format("ws://%s:%s/%s/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), app, stream)); |
||||
|
streamInfoResult.setFmp4(String.format("http://%s:%s/%s/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), app, stream)); |
||||
|
streamInfoResult.setWs_fmp4(String.format("ws://%s:%s/%s/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), app, stream)); |
||||
|
streamInfoResult.setTs(String.format("http://%s:%s/%s/%s.live.ts", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), app, stream)); |
||||
|
streamInfoResult.setWs_ts(String.format("ws://%s:%s/%s/%s.live.ts", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), app, stream)); |
||||
|
return streamInfoResult; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream) { |
||||
|
StreamInfo streamInfo = null; |
||||
|
JSONObject mediaList = zlmresTfulUtils.getMediaList(app, stream); |
||||
|
if (mediaList != null) { |
||||
|
streamInfo = getStreamInfoByAppAndStream(app, stream); |
||||
|
} |
||||
|
return streamInfo; |
||||
|
} |
||||
|
} |
@ -0,0 +1,123 @@ |
|||||
|
package com.genersoft.iot.vmp.vmanager.service.impl; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.genersoft.iot.vmp.conf.MediaServerConfig; |
||||
|
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
||||
|
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
||||
|
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyDto; |
||||
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
||||
|
import com.genersoft.iot.vmp.storager.IVideoManagerStorager; |
||||
|
import com.genersoft.iot.vmp.storager.dao.StreamProxyMapper; |
||||
|
import com.genersoft.iot.vmp.vmanager.service.IStreamProxyService; |
||||
|
import com.genersoft.iot.vmp.vmanager.streamProxy.StreamProxyController; |
||||
|
import com.github.pagehelper.PageHelper; |
||||
|
import com.github.pagehelper.PageInfo; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 视频代理业务 |
||||
|
*/ |
||||
|
@Service |
||||
|
public class StreamProxyServiceImpl implements IStreamProxyService { |
||||
|
|
||||
|
@Autowired |
||||
|
private IVideoManagerStorager videoManagerStorager; |
||||
|
|
||||
|
@Autowired |
||||
|
private IRedisCatchStorage redisCatchStorage; |
||||
|
|
||||
|
@Autowired |
||||
|
private ZLMRESTfulUtils zlmresTfulUtils; |
||||
|
|
||||
|
@Autowired |
||||
|
private StreamProxyMapper streamProxyMapper; |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public void save(StreamProxyDto param) { |
||||
|
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); |
||||
|
String dstUrl = String.format("rtmp://%s:%s/%s/%s", "127.0.0.1", mediaInfo.getRtmpPort(), param.getApp(), |
||||
|
param.getStream() ); |
||||
|
param.setDst_url(dstUrl); |
||||
|
// 更新
|
||||
|
if (videoManagerStorager.queryStreamProxy(param.getApp(), param.getStream()) != null) { |
||||
|
int result = videoManagerStorager.updateStreamProxy(param); |
||||
|
if (result > 0 && param.isEnable()) { |
||||
|
addStreamProxyToZlm(param); |
||||
|
} |
||||
|
}else { // 新增
|
||||
|
int result = videoManagerStorager.addStreamProxy(param); |
||||
|
if (result > 0 && param.isEnable()) { |
||||
|
addStreamProxyToZlm(param); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public JSONObject addStreamProxyToZlm(StreamProxyDto param) { |
||||
|
JSONObject result = null; |
||||
|
if ("default".equals(param.getType())){ |
||||
|
result = zlmresTfulUtils.addStreamProxy(param.getApp(), param.getStream(), param.getUrl(), |
||||
|
param.isEnable_hls(), param.isEnable_mp4(), param.getRtp_type()); |
||||
|
}else if ("ffmpeg".equals(param.getType())) { |
||||
|
result = zlmresTfulUtils.addFFmpegSource(param.getSrc_url(), param.getDst_url(), |
||||
|
param.getTimeout_ms() + ""); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public JSONObject removeStreamProxyFromZlm(StreamProxyDto param) { |
||||
|
JSONObject result = zlmresTfulUtils.closeStreams(param.getApp(), param.getStream()); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public PageInfo<StreamProxyDto> getAll(Integer page, Integer count) { |
||||
|
return videoManagerStorager.queryStreamProxyList(page, count); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void del(String app, String stream) { |
||||
|
StreamProxyDto streamProxyDto = new StreamProxyDto(); |
||||
|
streamProxyDto.setApp(app); |
||||
|
streamProxyDto.setStream(stream); |
||||
|
JSONObject jsonObject = removeStreamProxyFromZlm(streamProxyDto); |
||||
|
if (jsonObject.getInteger("code") == 0) { |
||||
|
videoManagerStorager.deleteStreamProxy(app, stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean start(String app, String stream) { |
||||
|
boolean result = false; |
||||
|
StreamProxyDto streamProxyDto = videoManagerStorager.queryStreamProxy(app, stream); |
||||
|
if (!streamProxyDto.isEnable() && streamProxyDto != null) { |
||||
|
JSONObject jsonObject = addStreamProxyToZlm(streamProxyDto); |
||||
|
if (jsonObject.getInteger("code") == 0) { |
||||
|
result = true; |
||||
|
streamProxyDto.setEnable(true); |
||||
|
videoManagerStorager.updateStreamProxy(streamProxyDto); |
||||
|
} |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean stop(String app, String stream) { |
||||
|
boolean result = false; |
||||
|
StreamProxyDto streamProxyDto = videoManagerStorager.queryStreamProxy(app, stream); |
||||
|
if (streamProxyDto.isEnable() && streamProxyDto != null) { |
||||
|
JSONObject jsonObject = removeStreamProxyFromZlm(streamProxyDto); |
||||
|
if (jsonObject.getInteger("code") == 0) { |
||||
|
result = true; |
||||
|
streamProxyDto.setEnable(false); |
||||
|
videoManagerStorager.updateStreamProxy(streamProxyDto); |
||||
|
} |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
} |
@ -0,0 +1,73 @@ |
|||||
|
package com.genersoft.iot.vmp.vmanager.streamProxy; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
||||
|
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyDto; |
||||
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
||||
|
import com.genersoft.iot.vmp.vmanager.service.IStreamProxyService; |
||||
|
import com.github.pagehelper.PageInfo; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Controller; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
/** |
||||
|
* 拉流代理接口 |
||||
|
*/ |
||||
|
@Controller |
||||
|
@CrossOrigin |
||||
|
@RequestMapping(value = "/api/proxy") |
||||
|
public class StreamProxyController { |
||||
|
|
||||
|
private final static Logger logger = LoggerFactory.getLogger(StreamProxyController.class); |
||||
|
|
||||
|
@Autowired |
||||
|
private IRedisCatchStorage redisCatchStorage; |
||||
|
|
||||
|
@Autowired |
||||
|
private IStreamProxyService streamProxyService; |
||||
|
|
||||
|
|
||||
|
@RequestMapping(value = "/list") |
||||
|
@ResponseBody |
||||
|
public PageInfo<StreamProxyDto> list(@RequestParam(required = false)Integer page, |
||||
|
@RequestParam(required = false)Integer count, |
||||
|
@RequestParam(required = false)String q, |
||||
|
@RequestParam(required = false)Boolean online ){ |
||||
|
|
||||
|
return streamProxyService.getAll(page, count); |
||||
|
} |
||||
|
|
||||
|
@RequestMapping(value = "/save") |
||||
|
@ResponseBody |
||||
|
public Object save(@RequestBody StreamProxyDto param){ |
||||
|
logger.info("添加代理: " + JSONObject.toJSONString(param)); |
||||
|
streamProxyService.save(param); |
||||
|
return "success"; |
||||
|
} |
||||
|
|
||||
|
@RequestMapping(value = "/del") |
||||
|
@ResponseBody |
||||
|
public Object del(String app, String stream){ |
||||
|
logger.info("移除代理: " + app + "/" + stream); |
||||
|
streamProxyService.del(app, stream); |
||||
|
return "success"; |
||||
|
} |
||||
|
|
||||
|
@RequestMapping(value = "/start") |
||||
|
@ResponseBody |
||||
|
public Object start(String app, String stream){ |
||||
|
logger.info("启用代理: " + app + "/" + stream); |
||||
|
boolean result = streamProxyService.start(app, stream); |
||||
|
return "success"; |
||||
|
} |
||||
|
|
||||
|
@RequestMapping(value = "/stop") |
||||
|
@ResponseBody |
||||
|
public Object stop(String app, String stream){ |
||||
|
logger.info("停用代理: " + app + "/" + stream); |
||||
|
boolean result = streamProxyService.stop(app, stream); |
||||
|
return "success"; |
||||
|
} |
||||
|
} |
@ -1,45 +0,0 @@ |
|||||
package com.genersoft.iot.vmp.web; |
|
||||
|
|
||||
import com.alibaba.fastjson.JSONObject; |
|
||||
import com.genersoft.iot.vmp.common.RealVideo; |
|
||||
import com.genersoft.iot.vmp.conf.SipConfig; |
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
|
||||
import org.slf4j.Logger; |
|
||||
import org.slf4j.LoggerFactory; |
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||
import org.springframework.stereotype.Controller; |
|
||||
import org.springframework.web.bind.annotation.CrossOrigin; |
|
||||
import org.springframework.web.bind.annotation.RequestMapping; |
|
||||
import org.springframework.web.bind.annotation.RequestParam; |
|
||||
import org.springframework.web.bind.annotation.ResponseBody; |
|
||||
|
|
||||
import java.util.List; |
|
||||
|
|
||||
/** |
|
||||
* 兼容LiveGBS的API:系统接口 |
|
||||
*/ |
|
||||
@Controller |
|
||||
@CrossOrigin |
|
||||
@RequestMapping(value = "/api/v1/media") |
|
||||
public class ApiMediaController { |
|
||||
|
|
||||
private final static Logger logger = LoggerFactory.getLogger(ApiMediaController.class); |
|
||||
|
|
||||
@Autowired |
|
||||
private IRedisCatchStorage redisCatchStorage; |
|
||||
|
|
||||
|
|
||||
@RequestMapping(value = "/list") |
|
||||
@ResponseBody |
|
||||
public JSONObject list( @RequestParam(required = false)Integer start, |
|
||||
@RequestParam(required = false)Integer limit, |
|
||||
@RequestParam(required = false)String q, |
|
||||
@RequestParam(required = false)Boolean online ){ |
|
||||
|
|
||||
List<Object> mediaList = redisCatchStorage.getMediaList(start - 1, start - 1 + limit); |
|
||||
JSONObject jsonObject = new JSONObject(); |
|
||||
jsonObject.put("code", 0); |
|
||||
jsonObject.put("data", mediaList); |
|
||||
return jsonObject; |
|
||||
} |
|
||||
} |
|
Binary file not shown.
@ -0,0 +1,297 @@ |
|||||
|
<template> |
||||
|
<div id="streamProxyList"> |
||||
|
<el-container> |
||||
|
<el-header> |
||||
|
<uiHeader></uiHeader> |
||||
|
</el-header> |
||||
|
<el-main> |
||||
|
<div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;"> |
||||
|
<span style="font-size: 1rem; font-weight: bold;">拉流代理列表</span> |
||||
|
</div> |
||||
|
<div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;font-size: 14px;"> |
||||
|
<el-button icon="el-icon-plus" size="mini" style="margin-right: 1rem;" type="primary" @click="addStreamProxy">添加代理</el-button> |
||||
|
</div> |
||||
|
<devicePlayer ref="devicePlayer"></devicePlayer> |
||||
|
<el-table :data="streamProxyList" border style="width: 100%" :height="winHeight"> |
||||
|
<el-table-column prop="app" label="应用名" align="center" show-overflow-tooltip/> |
||||
|
<el-table-column prop="stream" label="流ID" align="center" show-overflow-tooltip/> |
||||
|
<el-table-column label="流地址" width="400" align="center" show-overflow-tooltip > |
||||
|
<template slot-scope="scope"> |
||||
|
<div slot="reference" class="name-wrapper"> |
||||
|
|
||||
|
<el-tag size="medium" v-if="scope.row.type == 'default'"> |
||||
|
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="scope.row.url" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i> |
||||
|
{{scope.row.url}} |
||||
|
</el-tag> |
||||
|
<el-tag size="medium" v-if="scope.row.type != 'default'"> |
||||
|
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="scope.row.src_url" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i> |
||||
|
{{scope.row.src_url}} |
||||
|
</el-tag> |
||||
|
</div> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
|
||||
|
<el-table-column label="转HLS" width="120" align="center"> |
||||
|
<template slot-scope="scope"> |
||||
|
<div slot="reference" class="name-wrapper"> |
||||
|
<el-tag size="medium" v-if="scope.row.enable_hls">已启用</el-tag> |
||||
|
<el-tag size="medium" type="info" v-if="!scope.row.enable_hls">未启用</el-tag> |
||||
|
</div> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
<el-table-column label="MP4录制" width="120" align="center"> |
||||
|
<template slot-scope="scope"> |
||||
|
<div slot="reference" class="name-wrapper"> |
||||
|
<el-tag size="medium" v-if="scope.row.enable_mp4">已启用</el-tag> |
||||
|
<el-tag size="medium" type="info" v-if="!scope.row.enable_mp4">未启用</el-tag> |
||||
|
</div> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
<el-table-column label="启用" width="120" align="center"> |
||||
|
<template slot-scope="scope"> |
||||
|
<div slot="reference" class="name-wrapper"> |
||||
|
<el-tag size="medium" v-if="scope.row.enable">已启用</el-tag> |
||||
|
<el-tag size="medium" type="info" v-if="!scope.row.enable">未启用</el-tag> |
||||
|
</div> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
|
||||
|
|
||||
|
<el-table-column label="操作" width="360" align="center" fixed="right"> |
||||
|
<template slot-scope="scope"> |
||||
|
<el-button-group> |
||||
|
<el-button size="mini" icon="el-icon-video-play" v-if="scope.row.enable" @click="play(scope.row)">播放</el-button> |
||||
|
<el-button size="mini" icon="el-icon-close" type="success" v-if="scope.row.enable" @click="stop(scope.row)">停用</el-button> |
||||
|
<el-button size="mini" icon="el-icon-check" type="primary" v-if="!scope.row.enable" @click="start(scope.row)">启用</el-button> |
||||
|
<el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteStreamProxy(scope.row)">删除</el-button> |
||||
|
</el-button-group> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
</el-table> |
||||
|
<el-pagination |
||||
|
style="float: right" |
||||
|
@size-change="handleSizeChange" |
||||
|
@current-change="currentChange" |
||||
|
:current-page="currentPage" |
||||
|
:page-size="count" |
||||
|
:page-sizes="[15, 25, 35, 50]" |
||||
|
layout="total, sizes, prev, pager, next" |
||||
|
:total="total"> |
||||
|
</el-pagination> |
||||
|
<streamProxyEdit ref="streamProxyEdit" ></streamProxyEdit> |
||||
|
</el-main> |
||||
|
</el-container> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import streamProxyEdit from './dialog/StreamProxyEdit.vue' |
||||
|
import devicePlayer from './dialog/devicePlayer.vue' |
||||
|
import uiHeader from './UiHeader.vue' |
||||
|
export default { |
||||
|
name: 'streamProxyList', |
||||
|
components: { |
||||
|
devicePlayer, |
||||
|
streamProxyEdit, |
||||
|
uiHeader |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
streamProxyList: [], |
||||
|
currentPusher: {}, //当前操作设备对象 |
||||
|
updateLooper: 0, //数据刷新轮训标志 |
||||
|
currentDeviceChannelsLenth:0, |
||||
|
winHeight: window.innerHeight - 200, |
||||
|
currentPage:1, |
||||
|
count:15, |
||||
|
total:0, |
||||
|
getListLoading: false |
||||
|
}; |
||||
|
}, |
||||
|
computed: { |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.initData(); |
||||
|
// this.updateLooper = setInterval(this.initData, 10000); |
||||
|
}, |
||||
|
destroyed() { |
||||
|
this.$destroy('videojs'); |
||||
|
clearTimeout(this.updateLooper); |
||||
|
}, |
||||
|
methods: { |
||||
|
initData: function() { |
||||
|
this.getStreamProxyList(); |
||||
|
}, |
||||
|
currentChange: function(val){ |
||||
|
this.currentPage = val; |
||||
|
this.getStreamProxyList(); |
||||
|
}, |
||||
|
handleSizeChange: function(val){ |
||||
|
this.count = val; |
||||
|
this.getStreamProxyList(); |
||||
|
}, |
||||
|
getStreamProxyList: function() { |
||||
|
let that = this; |
||||
|
this.getListLoading = true; |
||||
|
this.$axios.get(`/api/proxy/list`,{ |
||||
|
params: { |
||||
|
page: that.currentPage, |
||||
|
count: that.count |
||||
|
} |
||||
|
} ) |
||||
|
.then(function (res) { |
||||
|
console.log(res); |
||||
|
console.log(res.data.list); |
||||
|
that.total = res.data.total; |
||||
|
that.streamProxyList = res.data.list; |
||||
|
that.getListLoading = false; |
||||
|
}) |
||||
|
.catch(function (error) { |
||||
|
console.log(error); |
||||
|
that.getListLoading = false; |
||||
|
}); |
||||
|
}, |
||||
|
addStreamProxy: function(){ |
||||
|
this.$refs.streamProxyEdit.openDialog(null, this.initData) |
||||
|
}, |
||||
|
saveStreamProxy: function(){ |
||||
|
}, |
||||
|
play: function(row){ |
||||
|
let that = this; |
||||
|
this.getListLoading = true; |
||||
|
this.$axios.get(`/api/media/getStreamInfoByAppAndStream`,{ |
||||
|
params: { |
||||
|
app: row.app, |
||||
|
stream: row.stream |
||||
|
} |
||||
|
}) |
||||
|
.then(function (res) { |
||||
|
that.getListLoading = false; |
||||
|
that.$refs.devicePlayer.openDialog("streamPlay", null, null, { |
||||
|
streamInfo: res.data, |
||||
|
hasAudio: true |
||||
|
}); |
||||
|
}) |
||||
|
.catch(function (error) { |
||||
|
console.log(error); |
||||
|
that.getListLoading = false; |
||||
|
}); |
||||
|
|
||||
|
}, |
||||
|
deleteStreamProxy: function(row){ |
||||
|
console.log(1111) |
||||
|
let that = this; |
||||
|
this.getListLoading = true; |
||||
|
this.$axios.get(`/api/proxy/del`,{ |
||||
|
params: { |
||||
|
app: row.app, |
||||
|
stream: row.stream |
||||
|
} |
||||
|
}) |
||||
|
.then(function (res) { |
||||
|
that.getListLoading = false; |
||||
|
that.initData() |
||||
|
}) |
||||
|
.catch(function (error) { |
||||
|
console.log(error); |
||||
|
that.getListLoading = false; |
||||
|
}); |
||||
|
}, |
||||
|
start: function(row){ |
||||
|
let that = this; |
||||
|
this.getListLoading = true; |
||||
|
this.$axios.get(`/api/proxy/start`,{ |
||||
|
params: { |
||||
|
app: row.app, |
||||
|
stream: row.stream |
||||
|
} |
||||
|
}) |
||||
|
.then(function (res) { |
||||
|
that.getListLoading = false; |
||||
|
that.initData() |
||||
|
}) |
||||
|
.catch(function (error) { |
||||
|
console.log(error); |
||||
|
that.getListLoading = false; |
||||
|
}); |
||||
|
}, |
||||
|
stop: function(row){ |
||||
|
let that = this; |
||||
|
this.getListLoading = true; |
||||
|
this.$axios.get(`/api/proxy/stop`,{ |
||||
|
params: { |
||||
|
app: row.app, |
||||
|
stream: row.stream |
||||
|
} |
||||
|
}) |
||||
|
.then(function (res) { |
||||
|
that.getListLoading = false; |
||||
|
that.initData() |
||||
|
}) |
||||
|
.catch(function (error) { |
||||
|
console.log(error); |
||||
|
that.getListLoading = false; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
.videoList { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
align-content: flex-start; |
||||
|
} |
||||
|
|
||||
|
.video-item { |
||||
|
position: relative; |
||||
|
width: 15rem; |
||||
|
height: 10rem; |
||||
|
margin-right: 1rem; |
||||
|
background-color: #000000; |
||||
|
} |
||||
|
|
||||
|
.video-item-img { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
margin: auto; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.video-item-img:after { |
||||
|
content: ""; |
||||
|
display: inline-block; |
||||
|
position: absolute; |
||||
|
z-index: 2; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
margin: auto; |
||||
|
width: 3rem; |
||||
|
height: 3rem; |
||||
|
background-image: url("../assets/loading.png"); |
||||
|
background-size: cover; |
||||
|
background-color: #000000; |
||||
|
} |
||||
|
|
||||
|
.video-item-title { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
color: #000000; |
||||
|
background-color: #ffffff; |
||||
|
line-height: 1.5rem; |
||||
|
padding: 0.3rem; |
||||
|
width: 14.4rem; |
||||
|
} |
||||
|
.cpoy-btn { |
||||
|
cursor: pointer; |
||||
|
margin-right: 10px; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,186 @@ |
|||||
|
<template> |
||||
|
<div id="addStreamProxy" v-loading="isLoging"> |
||||
|
<el-dialog |
||||
|
title="添加代理" |
||||
|
width="40%" |
||||
|
top="2rem" |
||||
|
:close-on-click-modal="false" |
||||
|
:visible.sync="showDialog" |
||||
|
:destroy-on-close="true" |
||||
|
@close="close()" |
||||
|
> |
||||
|
<div id="shared" style="margin-top: 1rem;margin-right: 100px;"> |
||||
|
<el-form ref="streamProxy" :rules="rules" :model="proxyParam" label-width="140px"> |
||||
|
<el-form-item label="类型" prop="type"> |
||||
|
<el-select |
||||
|
v-model="proxyParam.type" |
||||
|
style="width: 100%" |
||||
|
placeholder="请选择代理类型" |
||||
|
> |
||||
|
<el-option label="默认" value="default"></el-option> |
||||
|
<el-option label="FFmpeg" value="ffmpeg"></el-option> |
||||
|
</el-select> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="应用名" prop="app"> |
||||
|
<el-input v-model="proxyParam.app" clearable></el-input> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="流ID" prop="stream"> |
||||
|
<el-input v-model="proxyParam.stream" clearable></el-input> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="拉流地址" prop="url" v-if="proxyParam.type=='default'"> |
||||
|
<el-input v-model="proxyParam.url" clearable></el-input> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="拉流地址" prop="src_url" v-if="proxyParam.type=='ffmpeg'"> |
||||
|
<el-input v-model="proxyParam.src_url" clearable></el-input> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="超时时间" prop="timeout_ms" v-if="proxyParam.type=='ffmpeg'"> |
||||
|
<el-input v-model="proxyParam.timeout_ms" clearable></el-input> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="FFmpeg命令模板" prop="ffmpeg_cmd_key" v-if="proxyParam.type=='ffmpeg'"> |
||||
|
<el-input v-model="proxyParam.ffmpeg_cmd_key" clearable></el-input> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="拉流方式" prop="rtp_type" v-if="proxyParam.type=='default'"> |
||||
|
<el-select |
||||
|
v-model="proxyParam.rtp_type" |
||||
|
style="width: 100%" |
||||
|
placeholder="请选择拉流方式" |
||||
|
> |
||||
|
<el-option label="TCP" value="0"></el-option> |
||||
|
<el-option label="UDP" value="1"></el-option> |
||||
|
<el-option label="组播" value="2"></el-option> |
||||
|
</el-select> |
||||
|
</el-form-item> |
||||
|
<el-form-item label="其他选项"> |
||||
|
<div style="float: left;"> |
||||
|
<el-checkbox label="启用" v-model="proxyParam.enable" ></el-checkbox> |
||||
|
<el-checkbox label="转HLS" v-model="proxyParam.enable_hls" ></el-checkbox> |
||||
|
<el-checkbox label="MP4录制" v-model="proxyParam.enable_mp4" ></el-checkbox> |
||||
|
</div> |
||||
|
|
||||
|
</el-form-item> |
||||
|
<el-form-item> |
||||
|
<div style="float: right;"> |
||||
|
<el-button type="primary" @click="onSubmit">{{onSubmit_text}}</el-button> |
||||
|
<el-button @click="close">取消</el-button> |
||||
|
</div> |
||||
|
|
||||
|
</el-form-item> |
||||
|
</el-form> |
||||
|
</div> |
||||
|
</el-dialog> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: "streamProxyEdit", |
||||
|
props: {}, |
||||
|
computed: {}, |
||||
|
created() {}, |
||||
|
data() { |
||||
|
// var deviceGBIdRules = async (rule, value, callback) => { |
||||
|
// console.log(value); |
||||
|
// if (value === "") { |
||||
|
// callback(new Error("请输入设备国标编号")); |
||||
|
// } else { |
||||
|
// var exit = await this.deviceGBIdExit(value); |
||||
|
// console.log(exit); |
||||
|
// console.log(exit == "true"); |
||||
|
// console.log(exit === "true"); |
||||
|
// if (exit) { |
||||
|
// callback(new Error("设备国标编号已存在")); |
||||
|
// } else { |
||||
|
// callback(); |
||||
|
// } |
||||
|
// } |
||||
|
// }; |
||||
|
return { |
||||
|
listChangeCallback: null, |
||||
|
showDialog: false, |
||||
|
isLoging: false, |
||||
|
onSubmit_text: "立即创建", |
||||
|
proxyParam: { |
||||
|
type: "default", |
||||
|
app: null, |
||||
|
stream: null, |
||||
|
url: "rtmp://58.200.131.2:1935/livetv/hunantv", |
||||
|
src_url: null, |
||||
|
timeout_ms: null, |
||||
|
ffmpeg_cmd_key: null, |
||||
|
rtp_type: null, |
||||
|
enable: true, |
||||
|
enable_hls: true, |
||||
|
enable_mp4: false, |
||||
|
}, |
||||
|
|
||||
|
rules: { |
||||
|
app: [{ required: true, message: "请输入应用名", trigger: "blur" }], |
||||
|
stream: [{ required: true, message: "请输入流ID", trigger: "blur" }], |
||||
|
url: [{ required: true, message: "请输入要代理的流", trigger: "blur" }], |
||||
|
src_url: [{ required: true, message: "请输入要代理的流", trigger: "blur" }], |
||||
|
timeout_ms: [{ required: true, message: "请输入FFmpeg推流成功超时时间", trigger: "blur" }], |
||||
|
ffmpeg_cmd_key: [{ required: false, message: "请输入FFmpeg命令参数模板(可选)", trigger: "blur" }], |
||||
|
}, |
||||
|
}; |
||||
|
}, |
||||
|
methods: { |
||||
|
openDialog: function (proxyParam, callback) { |
||||
|
this.showDialog = true; |
||||
|
this.listChangeCallback = callback; |
||||
|
if (proxyParam != null) { |
||||
|
this.proxyParam = proxyParam; |
||||
|
this.onSubmit_text = "保存"; |
||||
|
} else { |
||||
|
this.onSubmit_text = "立即创建"; |
||||
|
} |
||||
|
}, |
||||
|
onSubmit: function () { |
||||
|
console.log("onSubmit"); |
||||
|
var that = this; |
||||
|
that.$axios |
||||
|
.post(`/api/proxy/save`, that.proxyParam) |
||||
|
.then(function (res) { |
||||
|
console.log(res); |
||||
|
console.log(res.data == "success"); |
||||
|
if (res.data == "success") { |
||||
|
that.$message({ |
||||
|
showClose: true, |
||||
|
message: "保存成功", |
||||
|
type: "success", |
||||
|
}); |
||||
|
that.showDialog = false; |
||||
|
if (that.listChangeCallback != null) { |
||||
|
that.listChangeCallback(); |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
.catch(function (error) { |
||||
|
console.log(error); |
||||
|
}); |
||||
|
}, |
||||
|
close: function () { |
||||
|
console.log("关闭添加视频平台"); |
||||
|
this.showDialog = false; |
||||
|
this.$refs.streamProxy.resetFields(); |
||||
|
}, |
||||
|
deviceGBIdExit: async function (deviceGbId) { |
||||
|
var result = false; |
||||
|
var that = this; |
||||
|
await that.$axios |
||||
|
.post(`/api/platforms/exit/${deviceGbId}`) |
||||
|
.then(function (res) { |
||||
|
result = res.data; |
||||
|
}) |
||||
|
.catch(function (error) { |
||||
|
console.log(error); |
||||
|
}); |
||||
|
return result; |
||||
|
}, |
||||
|
checkExpires: function() { |
||||
|
if (this.platform.enable && this.platform.expires == "0") { |
||||
|
this.platform.expires = "300"; |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
}; |
||||
|
</script> |
Loading…
Reference in new issue