mirror of
https://gitee.com/pan648540858/wvp-GB28181-pro.git
synced 2026-05-21 12:57:50 +08:00
Compare commits
16 Commits
e866959451
...
f2dd1f9314
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2dd1f9314 | ||
|
|
0ed37d58ec | ||
|
|
e7d26b34b3 | ||
|
|
f7eea28951 | ||
|
|
a706069e97 | ||
|
|
3a594f7bee | ||
|
|
15ff87a323 | ||
|
|
9edbcd9768 | ||
|
|
a2ac65dc70 | ||
|
|
ff0d54968d | ||
|
|
af3a283b3d | ||
|
|
fd306d8ede | ||
|
|
a064f27bf8 | ||
|
|
fb9ff67701 | ||
|
|
403e7648f9 | ||
|
|
34d1dbb399 |
@ -79,6 +79,8 @@ public class StreamInfo implements Serializable, Cloneable{
|
||||
private String startTime;
|
||||
@Schema(description = "结束时间")
|
||||
private String endTime;
|
||||
@Schema(description = "时长(回放时使用)")
|
||||
private Double duration;
|
||||
@Schema(description = "进度(录像下载使用)")
|
||||
private double progress;
|
||||
@Schema(description = "文件下载地址(录像下载使用)")
|
||||
@ -101,84 +103,108 @@ public class StreamInfo implements Serializable, Cloneable{
|
||||
@Schema(description = "使用的WVP ID")
|
||||
private String serverId;
|
||||
|
||||
public void setRtmp(String host, int port, int sslPort, String app, String stream, String callIdParam) {
|
||||
@Schema(description = "流绑定的流媒体操作key")
|
||||
private String key;
|
||||
|
||||
public void setRtmp(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) {
|
||||
String file = String.format("%s/%s%s", app, stream, callIdParam);
|
||||
if (port > 0) {
|
||||
if (port != null && port > 0) {
|
||||
this.rtmp = new StreamURL("rtmp", host, port, file);
|
||||
}
|
||||
if (sslPort > 0) {
|
||||
if (sslPort != null && sslPort > 0) {
|
||||
this.rtmps = new StreamURL("rtmps", host, sslPort, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRtsp(String host, int port, int sslPort, String app, String stream, String callIdParam) {
|
||||
public void setRtsp(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) {
|
||||
String file = String.format("%s/%s%s", app, stream, callIdParam);
|
||||
if (port > 0) {
|
||||
if (port != null && port > 0) {
|
||||
this.rtsp = new StreamURL("rtsp", host, port, file);
|
||||
}
|
||||
if (sslPort > 0) {
|
||||
if (sslPort != null && sslPort > 0) {
|
||||
this.rtsps = new StreamURL("rtsps", host, sslPort, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFlv(String host, int port, int sslPort, String file) {
|
||||
if (port > 0) {
|
||||
public void setFlv(String host, Integer port, Integer sslPort, String file) {
|
||||
if (port != null && port > 0) {
|
||||
this.flv = new StreamURL("http", host, port, file);
|
||||
}
|
||||
this.ws_flv = new StreamURL("ws", host, port, file);
|
||||
if (sslPort > 0) {
|
||||
if (sslPort != null && sslPort > 0) {
|
||||
this.https_flv = new StreamURL("https", host, sslPort, file);
|
||||
this.wss_flv = new StreamURL("wss", host, sslPort, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWsFlv(String host, int port, int sslPort, String file) {
|
||||
if (port > 0) {
|
||||
public void setWsFlv(String host, Integer port, Integer sslPort, String file) {
|
||||
if (port != null && port > 0) {
|
||||
this.ws_flv = new StreamURL("ws", host, port, file);
|
||||
}
|
||||
if (sslPort > 0) {
|
||||
if (sslPort != null && sslPort > 0) {
|
||||
this.wss_flv = new StreamURL("wss", host, sslPort, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFmp4(String host, int port, int sslPort, String app, String stream, String callIdParam) {
|
||||
String file = String.format("%s/%s.live.mp4%s", app, stream, callIdParam);
|
||||
if (port > 0) {
|
||||
public void setFmp4(String host, Integer port, Integer sslPort, String file) {
|
||||
if (port != null && port > 0) {
|
||||
this.fmp4 = new StreamURL("http", host, port, file);
|
||||
}
|
||||
if (sslPort != null && sslPort > 0) {
|
||||
this.https_fmp4 = new StreamURL("https", host, sslPort, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWsMp4(String host, Integer port, Integer sslPort, String file) {
|
||||
if (port != null && port > 0) {
|
||||
this.ws_fmp4 = new StreamURL("ws", host, port, file);
|
||||
}
|
||||
if (sslPort > 0) {
|
||||
this.https_fmp4 = new StreamURL("https", host, sslPort, file);
|
||||
if (sslPort != null && sslPort > 0) {
|
||||
this.wss_fmp4 = new StreamURL("wss", host, sslPort, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHls(String host, int port, int sslPort, String app, String stream, String callIdParam) {
|
||||
public void setHls(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) {
|
||||
String file = String.format("%s/%s/hls.m3u8%s", app, stream, callIdParam);
|
||||
if (port > 0) {
|
||||
if (port != null && port > 0) {
|
||||
this.hls = new StreamURL("http", host, port, file);
|
||||
}
|
||||
if (sslPort != null && sslPort > 0) {
|
||||
this.https_hls = new StreamURL("https", host, sslPort, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWsHls(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) {
|
||||
String file = String.format("%s/%s/hls.m3u8%s", app, stream, callIdParam);
|
||||
if (port != null && port > 0) {
|
||||
this.ws_hls = new StreamURL("ws", host, port, file);
|
||||
}
|
||||
if (sslPort > 0) {
|
||||
this.https_hls = new StreamURL("https", host, sslPort, file);
|
||||
if (sslPort != null && sslPort > 0) {
|
||||
this.wss_hls = new StreamURL("wss", host, sslPort, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTs(String host, int port, int sslPort, String app, String stream, String callIdParam) {
|
||||
public void setTs(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) {
|
||||
String file = String.format("%s/%s.live.ts%s", app, stream, callIdParam);
|
||||
|
||||
if (port > 0) {
|
||||
if (port != null && port > 0) {
|
||||
this.ts = new StreamURL("http", host, port, file);
|
||||
}
|
||||
if (sslPort != null && sslPort > 0) {
|
||||
this.https_ts = new StreamURL("https", host, sslPort, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWsTs(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam) {
|
||||
String file = String.format("%s/%s.live.ts%s", app, stream, callIdParam);
|
||||
|
||||
if (port != null && port > 0) {
|
||||
this.ws_ts = new StreamURL("ws", host, port, file);
|
||||
}
|
||||
if (sslPort > 0) {
|
||||
this.https_ts = new StreamURL("https", host, sslPort, file);
|
||||
if (sslPort != null && sslPort > 0) {
|
||||
this.wss_ts = new StreamURL("wss", host, sslPort, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam, boolean isPlay) {
|
||||
public void setRtc(String host, Integer port, Integer sslPort, String app, String stream, String callIdParam, boolean isPlay) {
|
||||
if (callIdParam != null) {
|
||||
callIdParam = Objects.equals(callIdParam, "") ? callIdParam : callIdParam.replace("?", "&");
|
||||
}
|
||||
|
||||
@ -47,6 +47,9 @@ public class MediaConfig{
|
||||
@Value("${media.flv-port:0}")
|
||||
private Integer flvPort = 0;
|
||||
|
||||
@Value("${media.mp4-port:0}")
|
||||
private Integer mp4Port = 0;
|
||||
|
||||
@Value("${media.ws-flv-port:0}")
|
||||
private Integer wsFlvPort = 0;
|
||||
|
||||
@ -159,26 +162,11 @@ public class MediaConfig{
|
||||
mediaServer.setSdpIp(getSdpIp());
|
||||
mediaServer.setStreamIp(getStreamIp());
|
||||
mediaServer.setHttpPort(httpPort);
|
||||
if (flvPort == 0) {
|
||||
mediaServer.setFlvPort(httpPort);
|
||||
}else {
|
||||
mediaServer.setFlvPort(flvPort);
|
||||
}
|
||||
if (wsFlvPort == 0) {
|
||||
mediaServer.setWsFlvPort(httpPort);
|
||||
}else {
|
||||
mediaServer.setWsFlvPort(wsFlvPort);
|
||||
}
|
||||
if (flvSSlPort == 0) {
|
||||
mediaServer.setFlvSSLPort(httpSSlPort);
|
||||
}else {
|
||||
mediaServer.setFlvSSLPort(flvSSlPort);
|
||||
}
|
||||
if (wsFlvSSlPort == 0) {
|
||||
mediaServer.setWsFlvSSLPort(httpSSlPort);
|
||||
}else {
|
||||
mediaServer.setWsFlvSSLPort(wsFlvSSlPort);
|
||||
}
|
||||
mediaServer.setFlvPort(flvPort);
|
||||
mediaServer.setMp4Port(mp4Port);
|
||||
mediaServer.setWsFlvPort(wsFlvPort);
|
||||
mediaServer.setFlvSSLPort(flvSSlPort);
|
||||
mediaServer.setWsFlvSSLPort(wsFlvSSlPort);
|
||||
|
||||
mediaServer.setHttpSSlPort(httpSSlPort);
|
||||
mediaServer.setRtmpPort(rtmpPort);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.genersoft.iot.vmp.gb28181.service.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.common.InviteInfo;
|
||||
import com.genersoft.iot.vmp.common.*;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
@ -34,7 +33,6 @@ import com.genersoft.iot.vmp.service.ISendRtpServerService;
|
||||
import com.genersoft.iot.vmp.service.bean.*;
|
||||
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.utils.CloudRecordUtils;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
@ -981,8 +979,8 @@ public class PlayServiceImpl implements IPlayService {
|
||||
}
|
||||
|
||||
|
||||
private void download(MediaServer mediaServerItem, Device device, DeviceChannel channel, String startTime, String endTime, int downloadSpeed, ErrorCallback<StreamInfo> callback) {
|
||||
if (mediaServerItem == null ) {
|
||||
private void download(MediaServer mediaServer, Device device, DeviceChannel channel, String startTime, String endTime, int downloadSpeed, ErrorCallback<StreamInfo> callback) {
|
||||
if (mediaServer == null ) {
|
||||
callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(),
|
||||
InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(),
|
||||
null);
|
||||
@ -992,7 +990,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
int tcpMode = device.getStreamMode().equals("TCP-ACTIVE")? 2: (device.getStreamMode().equals("TCP-PASSIVE")? 1:0);
|
||||
// 录像下载不使用固定流地址,固定流地址会导致如果开始时间与结束时间一致时文件错误的叠加在一起
|
||||
RTPServerParam rtpServerParam = new RTPServerParam();
|
||||
rtpServerParam.setMediaServerItem(mediaServerItem);
|
||||
rtpServerParam.setMediaServerItem(mediaServer);
|
||||
rtpServerParam.setSsrcCheck(device.isSsrcCheck());
|
||||
rtpServerParam.setPlayback(true);
|
||||
rtpServerParam.setPort(0);
|
||||
@ -1002,7 +1000,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
SSRCInfo ssrcInfo = receiveRtpServerService.openRTPServer(rtpServerParam, (code, msg, result) -> {
|
||||
if (code == InviteErrorCode.SUCCESS.getCode() && result != null && result.getHookData() != null) {
|
||||
// hook响应
|
||||
StreamInfo streamInfo = onPublishHandlerForDownload(mediaServerItem, result.getHookData().getMediaInfo(), device, channel, startTime, endTime);
|
||||
StreamInfo streamInfo = onPublishHandlerForDownload(mediaServer, result.getHookData().getMediaInfo(), device, channel, startTime, endTime);
|
||||
if (streamInfo == null) {
|
||||
log.warn("[录像下载] 获取流地址信息失败");
|
||||
callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
|
||||
@ -1048,24 +1046,24 @@ public class PlayServiceImpl implements IPlayService {
|
||||
device.isSsrcCheck());
|
||||
|
||||
// 初始化redis中的invite消息状态
|
||||
InviteInfo inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channel.getId(), ssrcInfo.getStream(), ssrcInfo, mediaServerItem.getId(),
|
||||
mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.DOWNLOAD,
|
||||
InviteInfo inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channel.getId(), ssrcInfo.getStream(), ssrcInfo, mediaServer.getId(),
|
||||
mediaServer.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.DOWNLOAD,
|
||||
InviteSessionStatus.ready, true);
|
||||
inviteInfo.setStartTime(startTime);
|
||||
inviteInfo.setEndTime(endTime);
|
||||
|
||||
inviteStreamService.updateInviteInfo(inviteInfo);
|
||||
try {
|
||||
cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channel, startTime, endTime, downloadSpeed,
|
||||
cmder.downloadStreamCmd(mediaServer, ssrcInfo, device, channel, startTime, endTime, downloadSpeed,
|
||||
eventResult -> {
|
||||
// 对方返回错误
|
||||
callback.run(InviteErrorCode.FAIL.getCode(), String.format("录像下载失败, 错误码: %s, %s", eventResult.statusCode, eventResult.msg), null);
|
||||
receiveRtpServerService.closeRTPServer(mediaServerItem, ssrcInfo);
|
||||
receiveRtpServerService.closeRTPServer(mediaServer, ssrcInfo);
|
||||
sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream());
|
||||
inviteStreamService.removeInviteInfo(inviteInfo);
|
||||
}, eventResult ->{
|
||||
// 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
|
||||
InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channel,
|
||||
InviteOKHandler(eventResult, ssrcInfo, mediaServer, device, channel,
|
||||
callback, inviteInfo, InviteSessionType.DOWNLOAD);
|
||||
|
||||
// 注册录像回调事件,录像下载结束后写入下载地址
|
||||
@ -1074,8 +1072,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
inviteInfo.getDeviceId(), inviteInfo.getChannelId(), ssrcInfo.getStream());
|
||||
log.info("[录像下载] 收到录像写入磁盘消息内容: " + hookData);
|
||||
RecordInfo recordInfo = hookData.getRecordInfo();
|
||||
String filePath = recordInfo.getFilePath();
|
||||
DownloadFileInfo downloadFileInfo = CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath);
|
||||
DownloadFileInfo downloadFileInfo = mediaServerService.getDownloadFilePath(mediaServer, recordInfo);
|
||||
InviteInfo inviteInfoForNew = inviteStreamService.getInviteInfo(inviteInfo.getType()
|
||||
, inviteInfo.getChannelId(), inviteInfo.getStream());
|
||||
if (inviteInfoForNew != null && inviteInfoForNew.getStreamInfo() != null) {
|
||||
@ -1084,7 +1081,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
inviteStreamService.updateInviteInfo(inviteInfoForNew, 60*15L);
|
||||
}
|
||||
};
|
||||
Hook hook = Hook.getInstance(HookType.on_record_mp4, "rtp", ssrcInfo.getStream(), mediaServerItem.getId());
|
||||
Hook hook = Hook.getInstance(HookType.on_record_mp4, "rtp", ssrcInfo.getStream(), mediaServer.getId());
|
||||
// 设置过期时间,下载失败时自动处理订阅数据
|
||||
hook.setExpireTime(System.currentTimeMillis() + 24 * 60 * 60 * 1000);
|
||||
subscribe.addSubscribe(hook, hookEventForRecord);
|
||||
@ -1092,7 +1089,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
} catch (InvalidArgumentException | SipException | ParseException e) {
|
||||
log.error("[命令发送失败] 录像下载: {}", e.getMessage());
|
||||
callback.run(InviteErrorCode.FAIL.getCode(),e.getMessage(), null);
|
||||
receiveRtpServerService.closeRTPServer(mediaServerItem, ssrcInfo);
|
||||
receiveRtpServerService.closeRTPServer(mediaServer, ssrcInfo);
|
||||
sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream());
|
||||
inviteStreamService.removeInviteInfo(inviteInfo);
|
||||
}
|
||||
@ -1112,11 +1109,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
log.warn("[获取下载进度] 未查询到录像下载的信息 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream);
|
||||
return null;
|
||||
}
|
||||
String filePath = allList.get(0).getFilePath();
|
||||
if (filePath == null) {
|
||||
log.warn("[获取下载进度] 未查询到录像下载的文件路径 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream);
|
||||
return null;
|
||||
}
|
||||
|
||||
String mediaServerId = allList.get(0).getMediaServerId();
|
||||
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
|
||||
if (mediaServer == null) {
|
||||
@ -1124,7 +1117,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
return null;
|
||||
}
|
||||
log.warn("[获取下载进度] 发现下载已经结束,直接从数据库获取到文件 {}/{}-{}", device.getDeviceId(), channel.getDeviceId(), stream);
|
||||
DownloadFileInfo downloadFileInfo = CloudRecordUtils.getDownloadFilePath(mediaServer, filePath);
|
||||
DownloadFileInfo downloadFileInfo = mediaServerService.getDownloadFilePath(mediaServer, RecordInfo.getInstance(allList.get(0)));
|
||||
StreamInfo streamInfo = new StreamInfo();
|
||||
streamInfo.setDownLoadFilePath(downloadFileInfo);
|
||||
streamInfo.setApp(app);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package com.genersoft.iot.vmp.media.abl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.CommonCallback;
|
||||
import com.genersoft.iot.vmp.common.InviteInfo;
|
||||
import com.genersoft.iot.vmp.common.InviteSessionType;
|
||||
@ -11,20 +10,23 @@ import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
|
||||
import com.genersoft.iot.vmp.media.abl.bean.ABLMedia;
|
||||
import com.genersoft.iot.vmp.media.abl.bean.ABLResult;
|
||||
import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig;
|
||||
import com.genersoft.iot.vmp.media.abl.bean.hook.OnStreamArriveABLHookParam;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.media.bean.RecordInfo;
|
||||
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
|
||||
import com.genersoft.iot.vmp.media.event.media.MediaRecordProcessEvent;
|
||||
import com.genersoft.iot.vmp.media.service.IMediaNodeServerService;
|
||||
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
|
||||
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
|
||||
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -32,8 +34,14 @@ import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service("abl")
|
||||
public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
|
||||
@ -70,18 +78,10 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
if (mediaServer == null) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("stream_id", streamId);
|
||||
param.put("force", 1);
|
||||
JSONObject jsonObject = ablresTfulUtils.closeStreams(mediaServer, "rtp", streamId);
|
||||
logger.info("关闭RTP Server " + jsonObject);
|
||||
if (jsonObject != null ) {
|
||||
if (jsonObject.getInteger("code") != 0) {
|
||||
logger.error("[closeRtpServer] 失败: " + jsonObject.getString("memo"));
|
||||
}
|
||||
}else {
|
||||
// 检查ZLM状态
|
||||
logger.error("[closeRtpServer] 失败: 请检查ZLM服务");
|
||||
ABLResult result = ablresTfulUtils.closeStreams(mediaServer, "rtp", streamId);
|
||||
logger.info("关闭RTP Server " + result);
|
||||
if (result.getCode() != 0) {
|
||||
logger.error("[closeRtpServer] 失败: {}", result.getMemo());
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,31 +96,18 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
if (mediaServer == null) {
|
||||
return;
|
||||
}
|
||||
JSONObject jsonObject = ablresTfulUtils.closeStreams(mediaServer, "1078", streamId);
|
||||
logger.info("关闭RTP Server " + jsonObject);
|
||||
if (jsonObject != null ) {
|
||||
if (jsonObject.getInteger("code") != 0) {
|
||||
logger.error("[closeRtpServer] 失败: " + jsonObject.getString("memo"));
|
||||
}
|
||||
}else {
|
||||
// 检查ZLM状态
|
||||
logger.error("[closeRtpServer] 失败: 请检查ZLM服务");
|
||||
ABLResult result = ablresTfulUtils.closeStreams(mediaServer, "1078", streamId);
|
||||
logger.info("关闭JT-RTP Server " + result);
|
||||
if (result.getCode() != 0) {
|
||||
logger.error("[JT-closeRtpServer] 失败: {}", result.getMemo());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeStreams(MediaServer mediaServer, String app, String streamId) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("stream_id", streamId);
|
||||
param.put("force", 1);
|
||||
JSONObject jsonObject = ablresTfulUtils.closeStreams(mediaServer, app, streamId);
|
||||
if (jsonObject != null ) {
|
||||
if (jsonObject.getInteger("code") != 0) {
|
||||
logger.error("[closeStreams] 失败: " + jsonObject.getString("memo"));
|
||||
}
|
||||
}else {
|
||||
// 检查ZLM状态
|
||||
logger.error("[closeStreams] 失败: 请检查ZLM服务");
|
||||
ABLResult result = ablresTfulUtils.closeStreams(mediaServer, app, streamId);
|
||||
if (result.getCode() != 0) {
|
||||
logger.error("[closeStreams] 失败: {}", result.getMemo());
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,8 +133,8 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
mediaServer.setIp(ip);
|
||||
mediaServer.setHttpPort(port);
|
||||
mediaServer.setSecret(secret);
|
||||
JSONObject responseJSON = ablresTfulUtils.getServerConfig(mediaServer);
|
||||
JSONArray data = responseJSON.getJSONArray("params");
|
||||
ABLResult result = ablresTfulUtils.getServerConfig(mediaServer);
|
||||
JSONArray data = result.getParams();
|
||||
if (data != null && !data.isEmpty()) {
|
||||
AblServerConfig config = AblServerConfig.getInstance(data);
|
||||
config.setServerIp(ip);
|
||||
@ -173,20 +160,18 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
|
||||
@Override
|
||||
public List<StreamInfo> getMediaList(MediaServer mediaServer, String app, String stream, String callId) {
|
||||
JSONObject jsonObject = ablresTfulUtils.getMediaList(mediaServer, app, stream);
|
||||
if (jsonObject == null || jsonObject.getInteger("code") != 0) {
|
||||
ABLResult result = ablresTfulUtils.getMediaList(mediaServer, app, stream);
|
||||
if (result.getCode() != 0) {
|
||||
return null;
|
||||
}
|
||||
JSONArray mediaList = jsonObject.getJSONArray("mediaList");
|
||||
if (mediaList == null || mediaList.isEmpty()) {
|
||||
if (result.getMediaList() == null || result.getMediaList().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<StreamInfo> streamInfoList = new ArrayList<>();
|
||||
for (int i = 0; i < mediaList.size(); i++) {
|
||||
JSONObject mediaJSON = mediaList.getJSONObject(i);
|
||||
OnStreamArriveABLHookParam onStreamArriveABLHookParam = mediaJSON.to(OnStreamArriveABLHookParam.class);
|
||||
MediaInfo mediaInfo = MediaInfo.getInstance(onStreamArriveABLHookParam, mediaServer);
|
||||
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, app, stream, mediaInfo, callId, true);
|
||||
for (int i = 0; i < result.getMediaList().size(); i++) {
|
||||
ABLMedia ablMedia = result.getMediaList().get(i);
|
||||
MediaInfo mediaInfo = MediaInfo.getInstance(ablMedia, mediaServer);
|
||||
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, app, stream, mediaInfo, null, callId, true);
|
||||
if (streamInfo != null) {
|
||||
streamInfoList.add(streamInfo);
|
||||
}
|
||||
@ -194,26 +179,79 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
return streamInfoList;
|
||||
}
|
||||
|
||||
public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String callId, boolean isPlay) {
|
||||
@Override
|
||||
public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo,
|
||||
String addr, String callId, boolean isPlay) {
|
||||
StreamInfo streamInfoResult = new StreamInfo();
|
||||
streamInfoResult.setStream(stream);
|
||||
streamInfoResult.setApp(app);
|
||||
String addr = mediaServer.getStreamIp();
|
||||
if (addr == null) {
|
||||
addr = mediaServer.getStreamIp();
|
||||
}
|
||||
|
||||
streamInfoResult.setIp(addr);
|
||||
if (mediaInfo != null) {
|
||||
streamInfoResult.setServerId(mediaInfo.getServerId());
|
||||
}else {
|
||||
streamInfoResult.setServerId(userSetting.getServerId());
|
||||
}
|
||||
|
||||
streamInfoResult.setMediaServer(mediaServer);
|
||||
String callIdParam = ObjectUtils.isEmpty(callId)?"":"?callId=" + callId;
|
||||
Map<String, String> param = new HashMap<>();
|
||||
if (!ObjectUtils.isEmpty(callId)) {
|
||||
param.put("callId", callId);
|
||||
}
|
||||
if (mediaInfo != null && !ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) {
|
||||
param.put("originTypeStr", mediaInfo.getOriginTypeStr());
|
||||
}
|
||||
StringBuilder callIdParamBuilder = new StringBuilder();
|
||||
if (!param.isEmpty()) {
|
||||
callIdParamBuilder.append("?");
|
||||
for (Map.Entry<String, String> entry : param.entrySet()) {
|
||||
callIdParamBuilder.append(entry.getKey()).append("=").append(entry.getValue());
|
||||
callIdParamBuilder.append("&");
|
||||
}
|
||||
callIdParamBuilder.deleteCharAt(callIdParamBuilder.length() - 1);
|
||||
}
|
||||
|
||||
String callIdParam = callIdParamBuilder.toString();
|
||||
|
||||
streamInfoResult.setRtmp(addr, mediaServer.getRtmpPort(),mediaServer.getRtmpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setRtsp(addr, mediaServer.getRtspPort(),mediaServer.getRtspSSLPort(), app, stream, callIdParam);
|
||||
|
||||
String flvFile = String.format("%s/%s.flv%s", app, stream, callIdParam);
|
||||
streamInfoResult.setFlv(addr, mediaServer.getFlvPort(),mediaServer.getHttpSSlPort(), flvFile);
|
||||
streamInfoResult.setWsFlv(addr, mediaServer.getWsFlvPort(),mediaServer.getHttpSSlPort(), flvFile);
|
||||
streamInfoResult.setFmp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setHls(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setTs(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setRtc(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay);
|
||||
if ((mediaServer.getFlvPort() & 1) == 1) {
|
||||
// 奇数端口 默认ssl端口
|
||||
streamInfoResult.setFlv(addr, null, mediaServer.getFlvPort(), flvFile);
|
||||
}else {
|
||||
streamInfoResult.setFlv(addr, mediaServer.getFlvPort(),null, flvFile);
|
||||
}
|
||||
if ((mediaServer.getWsFlvPort() & 1) == 1) {
|
||||
// 奇数端口 默认ssl端口
|
||||
streamInfoResult.setWsFlv(addr, null, mediaServer.getWsFlvPort(), flvFile);
|
||||
}else {
|
||||
streamInfoResult.setWsFlv(addr, mediaServer.getWsFlvPort(),null, flvFile);
|
||||
}
|
||||
String mp4File = String.format("%s/%s.mp4%s", app, stream, callIdParam);
|
||||
if ((mediaServer.getMp4Port() & 1) == 1) {
|
||||
// 奇数端口 默认ssl端口
|
||||
streamInfoResult.setFmp4(addr, null, mediaServer.getMp4Port(), mp4File);
|
||||
}else {
|
||||
streamInfoResult.setFmp4(addr, mediaServer.getMp4Port(), null, mp4File);
|
||||
}
|
||||
|
||||
streamInfoResult.setHls(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setTs(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setRtc(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay);
|
||||
|
||||
streamInfoResult.setMediaInfo(mediaInfo);
|
||||
streamInfoResult.setOriginType(mediaInfo.getOriginType());
|
||||
|
||||
if (!"broadcast".equalsIgnoreCase(app) && !ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix())) {
|
||||
String newStream = stream + "_" + mediaServer.getTranscodeSuffix();
|
||||
mediaServer.setTranscodeSuffix(null);
|
||||
StreamInfo transcodeStreamInfo = getStreamInfoByAppAndStream(mediaServer, app, newStream, null, addr, callId, isPlay);
|
||||
streamInfoResult.setTranscodeStream(transcodeStreamInfo);
|
||||
}
|
||||
return streamInfoResult;
|
||||
}
|
||||
|
||||
@ -230,37 +268,26 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
|
||||
@Override
|
||||
public MediaInfo getMediaInfo(MediaServer mediaServer, String app, String stream) {
|
||||
JSONObject jsonObject = ablresTfulUtils.getMediaList(mediaServer, app, stream);
|
||||
if (jsonObject == null || jsonObject.getInteger("code") != 0) {
|
||||
ABLResult ablResult = ablresTfulUtils.getMediaList(mediaServer, app, stream);
|
||||
if (ablResult.getCode() != 0) {
|
||||
return null;
|
||||
}
|
||||
JSONArray mediaList = jsonObject.getJSONArray("mediaList");
|
||||
if (mediaList == null || mediaList.isEmpty()) {
|
||||
if (ablResult.getMediaList() == null || ablResult.getMediaList().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
MediaInfo mediaInfo = null;
|
||||
for (int i = 0; i < mediaList.size(); i++) {
|
||||
JSONObject mediaJSON = mediaList.getJSONObject(i);
|
||||
OnStreamArriveABLHookParam onStreamArriveABLHookParam = mediaJSON.to(OnStreamArriveABLHookParam.class);
|
||||
if (onStreamArriveABLHookParam == null) {
|
||||
continue;
|
||||
}
|
||||
mediaInfo = MediaInfo.getInstance(onStreamArriveABLHookParam, mediaServer);
|
||||
|
||||
}
|
||||
return mediaInfo;
|
||||
return MediaInfo.getInstance(ablResult.getMediaList().get(0), mediaServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean pauseRtpCheck(MediaServer mediaServer, String streamKey) {
|
||||
logger.warn("[abl-pauseRtpCheck] 未实现");
|
||||
return null;
|
||||
ABLResult ablResult = ablresTfulUtils.pauseRtpServer(mediaServer, streamKey);
|
||||
return ablResult.getCode() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean resumeRtpCheck(MediaServer mediaServer, String streamKey) {
|
||||
logger.warn("[abl-resumeRtpCheck] 未实现");
|
||||
return null;
|
||||
ABLResult ablResult = ablresTfulUtils.resumeRtpServer(mediaServer, streamKey);
|
||||
return ablResult.getCode() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -270,14 +297,14 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
|
||||
@Override
|
||||
public Boolean delFFmpegSource(MediaServer mediaServer, String streamKey) {
|
||||
JSONObject jsonObject = ablresTfulUtils.delFFmpegProxy(mediaServer, streamKey);
|
||||
return jsonObject.getInteger("code") == 0;
|
||||
ABLResult ablResult = ablresTfulUtils.delFFmpegProxy(mediaServer, streamKey);
|
||||
return ablResult.getCode() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean delStreamProxy(MediaServer mediaServer, String streamKey) {
|
||||
JSONObject jsonObject = ablresTfulUtils.delStreamProxy(mediaServer, streamKey);
|
||||
return jsonObject.getInteger("code") == 0;
|
||||
ABLResult ablResult = ablresTfulUtils.delStreamProxy(mediaServer, streamKey);
|
||||
return ablResult.getCode() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -286,18 +313,18 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
}
|
||||
|
||||
// 接受进度通知
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaRecordProcessEvent event) {
|
||||
CloudRecordItem cloudRecordItem = cloudRecordServiceMapper.getListByFileName(event.getApp(), event.getStream(), event.getFileName());
|
||||
if (cloudRecordItem == null) {
|
||||
cloudRecordItem = CloudRecordItem.getInstance(event);
|
||||
cloudRecordItem.setStartTime(event.getStartTime());
|
||||
cloudRecordItem.setEndTime(event.getEndTime());
|
||||
cloudRecordServiceMapper.add(cloudRecordItem);
|
||||
}else {
|
||||
cloudRecordServiceMapper.updateTimeLen(cloudRecordItem.getId(), (long)event.getCurrentFileDuration() * 1000, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
// @EventListener
|
||||
// public void onApplicationEvent(MediaRecordProcessEvent event) {
|
||||
// CloudRecordItem cloudRecordItem = cloudRecordServiceMapper.getListByFileName(event.getApp(), event.getStream(), event.getFileName());
|
||||
// if (cloudRecordItem == null) {
|
||||
// cloudRecordItem = CloudRecordItem.getInstance(event);
|
||||
// cloudRecordItem.setStartTime(event.getStartTime());
|
||||
// cloudRecordItem.setEndTime(event.getEndTime());
|
||||
// cloudRecordServiceMapper.add(cloudRecordItem);
|
||||
// }else {
|
||||
// cloudRecordServiceMapper.updateTimeLen(cloudRecordItem.getId(), (long)event.getCurrentFileDuration() * 1000, System.currentTimeMillis());
|
||||
// }
|
||||
// }
|
||||
@EventListener
|
||||
public void onApplicationEvent(MediaRecordMp4Event event) {
|
||||
InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, null, event.getStream());
|
||||
@ -311,17 +338,15 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
}
|
||||
long startTime = cloudRecordItemList.get(cloudRecordItemList.size() - 1).getStartTime();
|
||||
long endTime = cloudRecordItemList.get(0).getEndTime();
|
||||
JSONObject jsonObject = ablresTfulUtils.queryRecordList(event.getMediaServer(), event.getApp(), event.getStream(), DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(startTime),
|
||||
ABLResult ablResult = ablresTfulUtils.queryRecordList(event.getMediaServer(), event.getApp(), event.getStream(), DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(startTime),
|
||||
DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(endTime));
|
||||
System.err.println(jsonObject);
|
||||
if (jsonObject == null || jsonObject.getInteger("code") != 0) {
|
||||
if (ablResult.getCode() != 0) {
|
||||
return;
|
||||
}
|
||||
JSONObject urlJson = jsonObject.getJSONObject("url");
|
||||
if (urlJson == null) {
|
||||
if (ablResult.getUrl() == null) {
|
||||
return;
|
||||
}
|
||||
String download = urlJson.getString("http-mp4") + "?download_speed=6";
|
||||
String download = ablResult.getUrl().getDownload();
|
||||
DownloadFileInfo downloadFileInfo = new DownloadFileInfo();
|
||||
downloadFileInfo.setHttpPath(download);
|
||||
downloadFileInfo.setHttpsPath(download);
|
||||
@ -346,11 +371,11 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
@Override
|
||||
public WVPResult<String> addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enableAudio, boolean enableMp4, String rtpType, Integer timeout) {
|
||||
|
||||
JSONObject jsonObject = ablresTfulUtils.addStreamProxy(mediaServer, app, stream, url, !enableAudio, enableMp4, rtpType, timeout);
|
||||
if (jsonObject.getInteger("code") != 0) {
|
||||
return WVPResult.fail(ErrorCode.ERROR100.getCode(), jsonObject.getString("memo"));
|
||||
ABLResult result = ablresTfulUtils.addStreamProxy(mediaServer, app, stream, url, !enableAudio, enableMp4, rtpType, timeout);
|
||||
if (result.getCode() != 0) {
|
||||
return WVPResult.fail(ErrorCode.ERROR100.getCode(), result.getMemo());
|
||||
}else {
|
||||
return WVPResult.success(jsonObject.getString("key"));
|
||||
return WVPResult.success(result.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,25 +405,23 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
closeStreams(mediaServer, streamProxy.getApp(), streamProxy.getStream());
|
||||
}
|
||||
|
||||
JSONObject jsonObject = null;
|
||||
ABLResult ablResult = null;
|
||||
if ("ffmpeg".equalsIgnoreCase(streamProxy.getType())){
|
||||
if (streamProxy.getTimeout() == 0) {
|
||||
streamProxy.setTimeout(15);
|
||||
}
|
||||
jsonObject = ablresTfulUtils.addFFmpegProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(),
|
||||
ablResult = ablresTfulUtils.addFFmpegProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(),
|
||||
!streamProxy.isEnableAudio(), streamProxy.isEnableMp4(), streamProxy.getRtspType(), streamProxy.getTimeout());
|
||||
}else {
|
||||
jsonObject = ablresTfulUtils.addStreamProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(),
|
||||
ablResult = ablresTfulUtils.addStreamProxy(mediaServer, streamProxy.getApp(), streamProxy.getStream(), streamProxy.getSrcUrl().trim(),
|
||||
streamProxy.isEnableAudio(), streamProxy.isEnableMp4(), streamProxy.getRtspType(), streamProxy.getTimeout());
|
||||
}
|
||||
if (jsonObject == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
|
||||
}else if (jsonObject.getInteger("code") != 0) {
|
||||
throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("memo"));
|
||||
if (ablResult.getCode() != 0) {
|
||||
throw new ControllerException(ablResult.getCode(), ablResult.getMemo());
|
||||
}else {
|
||||
String key = jsonObject.getString("key");
|
||||
String key = ablResult.getKey();
|
||||
if (key == null) {
|
||||
throw new ControllerException(jsonObject.getInteger("code"), "代理结果异常: " + jsonObject);
|
||||
throw new ControllerException(ablResult.getCode(), "代理结果异常: " + ablResult);
|
||||
}else {
|
||||
return key;
|
||||
}
|
||||
@ -407,49 +430,110 @@ public class ABLMediaNodeServerService implements IMediaNodeServerService {
|
||||
|
||||
@Override
|
||||
public void stopProxy(MediaServer mediaServer, String streamKey, String type) {
|
||||
JSONObject jsonObject = null;
|
||||
ABLResult ablResult = null;
|
||||
if ("ffmpeg".equalsIgnoreCase(type)){
|
||||
jsonObject = ablresTfulUtils.delFFmpegProxy(mediaServer, streamKey);
|
||||
ablResult = ablresTfulUtils.delFFmpegProxy(mediaServer, streamKey);
|
||||
}else {
|
||||
jsonObject = ablresTfulUtils.delStreamProxy(mediaServer, streamKey);
|
||||
ablResult = ablresTfulUtils.delStreamProxy(mediaServer, streamKey);
|
||||
}
|
||||
if (jsonObject == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
|
||||
}else if (jsonObject.getInteger("code") != 0) {
|
||||
throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("memo"));
|
||||
if (ablResult.getCode() != 0) {
|
||||
throw new ControllerException(ablResult.getCode(), ablResult.getMemo());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> listRtpServer(MediaServer mediaServer) {
|
||||
JSONObject jsonObject = ablresTfulUtils.getMediaList(mediaServer, "rtp", null);
|
||||
if (jsonObject == null || jsonObject.getInteger("code") != 0) {
|
||||
ABLResult ablResult = ablresTfulUtils.getMediaList(mediaServer, "rtp", null);
|
||||
if (ablResult.getCode() != 0) {
|
||||
return null;
|
||||
}
|
||||
JSONArray mediaList = jsonObject.getJSONArray("mediaList");
|
||||
if (mediaList == null || mediaList.isEmpty()) {
|
||||
if (ablResult.getMediaList() == null || ablResult.getMediaList().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<String> result = new ArrayList<>();
|
||||
for (int i = 0; i < mediaList.size(); i++) {
|
||||
JSONObject mediaJSON = mediaList.getJSONObject(i);
|
||||
result.add(mediaJSON.getString("stream"));
|
||||
for (int i = 0; i < ablResult.getMediaList().size(); i++) {
|
||||
ABLMedia ablMedia = ablResult.getMediaList().get(i);
|
||||
result.add(ablMedia.getStream());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
|
||||
logger.warn("[abl-loadMP4File] 未实现");
|
||||
public void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback<StreamInfo> callback) {
|
||||
String buildStream = String.format("%s__ReplayFMP4RecordFile__%s", stream, fileName);
|
||||
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, app, buildStream, null, null, null, true);
|
||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback<StreamInfo> callback) {
|
||||
// 解析为 LocalDate
|
||||
LocalDate localDate = LocalDate.parse(date, DateUtil.DateFormatter);
|
||||
LocalDateTime startOfDay = localDate.atStartOfDay();
|
||||
LocalDateTime endOfDay = localDate.atTime(23, 59,59, 999);
|
||||
String startTime = DateUtil.urlFormatter.format(startOfDay);
|
||||
String endTime = DateUtil.urlFormatter.format(endOfDay);
|
||||
|
||||
ABLResult ablResult = ablresTfulUtils.queryRecordList(mediaServer, app, stream, startTime, endTime);
|
||||
if (ablResult.getCode() != 0) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), ablResult.getMemo());
|
||||
}
|
||||
String resultApp = ablResult.getApp();
|
||||
String resultStream = ablResult.getStream();
|
||||
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, resultApp, resultStream, null, null,null, true);
|
||||
streamInfo.setKey(ablResult.getKey());
|
||||
if (callback != null) {
|
||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) {
|
||||
logger.warn("[abl-seekRecordStamp] 未实现");
|
||||
ABLResult ablResult = ablresTfulUtils.controlRecordPlay(mediaServer, app, stream, "seek", stamp/1000 + "");
|
||||
if (ablResult.getCode() != 0) {
|
||||
log.warn("[abl-seek] 失败:{}", ablResult.getMemo());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) {
|
||||
logger.warn("[abl-setRecordSpeed] 未实现");
|
||||
ABLResult ablResult = ablresTfulUtils.controlRecordPlay(mediaServer, app, stream, "scale", speed + "");
|
||||
if (ablResult.getCode() != 0) {
|
||||
log.warn("[abl-倍速] 失败:{}", ablResult.getMemo());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo) {
|
||||
// 将filePath作为独立参数传入,避免%符号解析问题
|
||||
String pathTemplate = "%s://%s:%s/%s/%s__ReplayFMP4RecordFile__%s?download_speed=16";
|
||||
|
||||
DownloadFileInfo info = new DownloadFileInfo();
|
||||
if ((mediaServer.getMp4Port() & 1) == 1) {
|
||||
info.setHttpsPath(
|
||||
String.format(
|
||||
pathTemplate,
|
||||
"https",
|
||||
mediaServer.getStreamIp(),
|
||||
mediaServer.getMp4Port(),
|
||||
recordInfo.getApp(),
|
||||
recordInfo.getStream(),
|
||||
recordInfo.getFileName()
|
||||
)
|
||||
);
|
||||
}else {
|
||||
info.setHttpPath(
|
||||
String.format(
|
||||
pathTemplate,
|
||||
"http",
|
||||
mediaServer.getStreamIp(),
|
||||
mediaServer.getMp4Port(),
|
||||
recordInfo.getApp(),
|
||||
recordInfo.getStream(),
|
||||
recordInfo.getFileName()
|
||||
)
|
||||
);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
package com.genersoft.iot.vmp.media.abl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.media.abl.bean.ABLResult;
|
||||
import com.genersoft.iot.vmp.media.abl.bean.AblServerConfig;
|
||||
import com.genersoft.iot.vmp.media.abl.bean.ConfigKeyId;
|
||||
import com.genersoft.iot.vmp.media.abl.event.HookAblServerKeepaliveEvent;
|
||||
import com.genersoft.iot.vmp.media.abl.event.HookAblServerStartEvent;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerChangeEvent;
|
||||
import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent;
|
||||
import com.genersoft.iot.vmp.media.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -131,17 +131,18 @@ public class ABLMediaServerStatusManger {
|
||||
continue;
|
||||
}
|
||||
logger.info("[ABL-尝试连接] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
|
||||
JSONObject responseJson = ablResTfulUtils.getServerConfig(mediaServerItem);
|
||||
ABLResult ablResult = ablResTfulUtils.getServerConfig(mediaServerItem);
|
||||
AblServerConfig ablServerConfig = null;
|
||||
if (responseJson == null) {
|
||||
if (ablResult.getCode() != 0) {
|
||||
logger.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
|
||||
continue;
|
||||
}
|
||||
JSONArray data = responseJson.getJSONArray("params");
|
||||
if (data == null || data.isEmpty()) {
|
||||
JSONArray params = ablResult.getParams();
|
||||
|
||||
if (params == null || params.isEmpty()) {
|
||||
logger.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
|
||||
}else {
|
||||
ablServerConfig = AblServerConfig.getInstance(data);
|
||||
ablServerConfig = AblServerConfig.getInstance(params);
|
||||
initPort(mediaServerItem, ablServerConfig);
|
||||
online(mediaServerItem, ablServerConfig);
|
||||
}
|
||||
@ -153,19 +154,19 @@ public class ABLMediaServerStatusManger {
|
||||
continue;
|
||||
}
|
||||
logger.info("[ABL-尝试连接] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
|
||||
JSONObject responseJson = ablResTfulUtils.getServerConfig(mediaServerItem);
|
||||
ABLResult ablResult = ablResTfulUtils.getServerConfig(mediaServerItem);
|
||||
AblServerConfig ablServerConfig = null;
|
||||
if (responseJson == null) {
|
||||
if (ablResult.getCode() != 0) {
|
||||
logger.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
|
||||
offlineAblTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis());
|
||||
continue;
|
||||
}
|
||||
JSONArray data = responseJson.getJSONArray("params");
|
||||
if (data == null || data.isEmpty()) {
|
||||
JSONArray params = ablResult.getParams();
|
||||
if (params == null || params.isEmpty()) {
|
||||
logger.info("[ABL-尝试连接]失败, ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
|
||||
offlineAblTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis());
|
||||
}else {
|
||||
ablServerConfig = AblServerConfig.getInstance(data);
|
||||
ablServerConfig = AblServerConfig.getInstance(params);
|
||||
initPort(mediaServerItem, ablServerConfig);
|
||||
online(mediaServerItem, ablServerConfig);
|
||||
}
|
||||
@ -173,85 +174,76 @@ public class ABLMediaServerStatusManger {
|
||||
}
|
||||
}
|
||||
|
||||
private void online(MediaServer mediaServerItem, AblServerConfig config) {
|
||||
offlineABLPrimaryMap.remove(mediaServerItem.getId());
|
||||
offlineAblsecondaryMap.remove(mediaServerItem.getId());
|
||||
offlineAblTimeMap.remove(mediaServerItem.getId());
|
||||
if (!mediaServerItem.isStatus()) {
|
||||
logger.info("[ABL-连接成功] ID:{}, 地址: {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
|
||||
mediaServerItem.setStatus(true);
|
||||
mediaServerItem.setHookAliveInterval(10F);
|
||||
mediaServerService.update(mediaServerItem);
|
||||
if(mediaServerItem.isAutoConfig()) {
|
||||
private void online(MediaServer mediaServer, AblServerConfig config) {
|
||||
offlineABLPrimaryMap.remove(mediaServer.getId());
|
||||
offlineAblsecondaryMap.remove(mediaServer.getId());
|
||||
offlineAblTimeMap.remove(mediaServer.getId());
|
||||
if (!mediaServer.isStatus()) {
|
||||
logger.info("[ABL-连接成功] ID:{}, 地址: {}:{}", mediaServer.getId(), mediaServer.getIp(), mediaServer.getHttpPort());
|
||||
mediaServer.setStatus(true);
|
||||
mediaServer.setHookAliveInterval(10F);
|
||||
mediaServerService.update(mediaServer);
|
||||
if(mediaServer.isAutoConfig()) {
|
||||
if (config == null) {
|
||||
JSONObject responseJSON = ablResTfulUtils.getServerConfig(mediaServerItem);
|
||||
JSONArray data = responseJSON.getJSONArray("params");
|
||||
ABLResult ablResult = ablResTfulUtils.getServerConfig(mediaServer);
|
||||
JSONArray data = ablResult.getParams();
|
||||
if (data != null && !data.isEmpty()) {
|
||||
config = AblServerConfig.getInstance(data);
|
||||
}
|
||||
}
|
||||
if (config != null) {
|
||||
initPort(mediaServerItem, config);
|
||||
setAblConfig(mediaServerItem, false, config);
|
||||
initPort(mediaServer, config);
|
||||
setAblConfig(mediaServer, false, config);
|
||||
}
|
||||
}
|
||||
mediaServerService.update(mediaServerItem);
|
||||
mediaServerService.update(mediaServer);
|
||||
}
|
||||
// 设置两次心跳未收到则认为zlm离线
|
||||
String key = "ABL-keepalive-" + mediaServerItem.getId();
|
||||
String key = "ABL-keepalive-" + mediaServer.getId();
|
||||
dynamicTask.startDelay(key, ()->{
|
||||
logger.warn("[ABL-心跳超时] ID:{}", mediaServerItem.getId());
|
||||
mediaServerItem.setStatus(false);
|
||||
offlineABLPrimaryMap.put(mediaServerItem.getId(), mediaServerItem);
|
||||
offlineAblTimeMap.put(mediaServerItem.getId(), System.currentTimeMillis());
|
||||
logger.warn("[ABL-心跳超时] ID:{}", mediaServer.getId());
|
||||
mediaServer.setStatus(false);
|
||||
offlineABLPrimaryMap.put(mediaServer.getId(), mediaServer);
|
||||
offlineAblTimeMap.put(mediaServer.getId(), System.currentTimeMillis());
|
||||
// TODO 发送离线通知
|
||||
mediaServerService.update(mediaServerItem);
|
||||
}, (int)(mediaServerItem.getHookAliveInterval() * 2 * 1000));
|
||||
mediaServerService.update(mediaServer);
|
||||
}, (int)(mediaServer.getHookAliveInterval() * 2 * 1000));
|
||||
}
|
||||
private void initPort(MediaServer mediaServerItem, AblServerConfig ablServerConfig) {
|
||||
private void initPort(MediaServer mediaServer, AblServerConfig ablServerConfig) {
|
||||
// 端口只会从配置中读取一次,一旦自己配置或者读取过了将不在配置
|
||||
// if (mediaServerItem.getHttpSSlPort() == 0) {
|
||||
// mediaServerItem.setHttpSSlPort(ablServerConfig.getHttpSSLport());
|
||||
// }
|
||||
if (mediaServerItem.getRtmpPort() != ablServerConfig.getRtmpPort()) {
|
||||
mediaServerItem.setRtmpPort(ablServerConfig.getRtmpPort());
|
||||
if (ablServerConfig.getRtmpPort() != null && mediaServer.getRtmpPort() != ablServerConfig.getRtmpPort()) {
|
||||
mediaServer.setRtmpPort(ablServerConfig.getRtmpPort());
|
||||
}
|
||||
// if (mediaServerItem.getRtmpSSlPort() == 0) {
|
||||
// mediaServerItem.setRtmpSSlPort(ablServerConfig.getRtmpSslPort());
|
||||
// }
|
||||
if (mediaServerItem.getRtspPort() != ablServerConfig.getRtspPort()) {
|
||||
mediaServerItem.setRtspPort(ablServerConfig.getRtspPort());
|
||||
if (ablServerConfig.getRtspPort() != null && mediaServer.getRtspPort() != ablServerConfig.getRtspPort()) {
|
||||
mediaServer.setRtspPort(ablServerConfig.getRtspPort());
|
||||
}
|
||||
if (mediaServerItem.getFlvPort() != ablServerConfig.getHttpFlvPort()) {
|
||||
mediaServerItem.setFlvPort(ablServerConfig.getHttpFlvPort());
|
||||
if (ablServerConfig.getHttpFlvPort() != null && mediaServer.getFlvPort() != ablServerConfig.getHttpFlvPort()) {
|
||||
mediaServer.setFlvPort(ablServerConfig.getHttpFlvPort());
|
||||
}
|
||||
if (mediaServerItem.getWsFlvPort() != ablServerConfig.getWsPort()) {
|
||||
mediaServerItem.setWsFlvPort(ablServerConfig.getWsPort());
|
||||
if (ablServerConfig.getHttpMp4Port() != null && mediaServer.getMp4Port() != ablServerConfig.getHttpMp4Port()) {
|
||||
mediaServer.setMp4Port(ablServerConfig.getHttpMp4Port());
|
||||
}
|
||||
if (mediaServerItem.getRtpProxyPort() != ablServerConfig.getPsTsRecvPort()) {
|
||||
mediaServerItem.setRtpProxyPort(ablServerConfig.getPsTsRecvPort());
|
||||
if (ablServerConfig.getWsFlvPort() != null && mediaServer.getWsFlvPort() != ablServerConfig.getWsFlvPort()) {
|
||||
mediaServer.setWsFlvPort(ablServerConfig.getWsFlvPort());
|
||||
}
|
||||
if (mediaServerItem.getRtpProxyPort() != ablServerConfig.getJtt1078RecvPort()) {
|
||||
mediaServerItem.setJttProxyPort(ablServerConfig.getJtt1078RecvPort());
|
||||
if (ablServerConfig.getPsTsRecvPort() != null && mediaServer.getRtpProxyPort() != ablServerConfig.getPsTsRecvPort()) {
|
||||
mediaServer.setRtpProxyPort(ablServerConfig.getPsTsRecvPort());
|
||||
}
|
||||
// if (mediaServerItem.getRtspSSLPort() == 0) {
|
||||
// mediaServerItem.setRtspSSLPort(ablServerConfig.getRtspSSlport());
|
||||
// }
|
||||
// if (mediaServerItem.getRtpProxyPort() == 0) {
|
||||
// mediaServerItem.setRtpProxyPort(ablServerConfig.getRtpProxyPort());
|
||||
// }
|
||||
mediaServerItem.setHookAliveInterval(10F);
|
||||
if (ablServerConfig.getJtt1078RecvPort() != null && mediaServer.getRtpProxyPort() != ablServerConfig.getJtt1078RecvPort()) {
|
||||
mediaServer.setJttProxyPort(ablServerConfig.getJtt1078RecvPort());
|
||||
}
|
||||
mediaServer.setHookAliveInterval(10F);
|
||||
}
|
||||
|
||||
public void setAblConfig(MediaServer mediaServerItem, boolean restart, AblServerConfig config) {
|
||||
try {
|
||||
if (config.getHookEnable() == 0) {
|
||||
logger.info("[媒体服务节点-ABL] 开启HOOK功能 :{}", mediaServerItem.getId());
|
||||
JSONObject responseJSON = ablResTfulUtils.setConfigParamValue(mediaServerItem, "hook_enable", "1");
|
||||
if (responseJSON.getInteger("code") == 0) {
|
||||
ABLResult ablResult = ablResTfulUtils.setConfigParamValue(mediaServerItem, "hook_enable", "1");
|
||||
if (ablResult.getCode() == 0) {
|
||||
logger.info("[媒体服务节点-ABL] 开启HOOK功能成功 :{}", mediaServerItem.getId());
|
||||
}else {
|
||||
logger.info("[媒体服务节点-ABL] 开启HOOK功能失败 :{}->{}", mediaServerItem.getId(), responseJSON.getString("memo"));
|
||||
logger.info("[媒体服务节点-ABL] 开启HOOK功能失败 :{}->{}", mediaServerItem.getId(), ablResult.getMemo());
|
||||
}
|
||||
}
|
||||
}catch (Exception e) {
|
||||
@ -286,11 +278,11 @@ public class ABLMediaServerStatusManger {
|
||||
field.setAccessible(true);
|
||||
// 利用反射获取值后对比是否与配置中相同,不同则进行设置
|
||||
if (!hookUrl.equals(field.get(config))) {
|
||||
JSONObject responseJSON = ablResTfulUtils.setConfigParamValue(mediaServerItem, hook, hookUrl);
|
||||
if (responseJSON.getInteger("code") == 0) {
|
||||
ABLResult ablResult = ablResTfulUtils.setConfigParamValue(mediaServerItem, hook, hookUrl);
|
||||
if (ablResult.getCode() == 0) {
|
||||
logger.info("[媒体服务节点-ABL] 设置HOOK {} 成功 :{}", hook, mediaServerItem.getId());
|
||||
}else {
|
||||
logger.info("[媒体服务节点-ABL] 设置HOOK {} 失败 :{}->{}", hook, mediaServerItem.getId(), responseJSON.getString("memo"));
|
||||
logger.info("[媒体服务节点-ABL] 设置HOOK {} 失败 :{}->{}", hook, mediaServerItem.getId(), ablResult.getMemo());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package com.genersoft.iot.vmp.media.abl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||
import com.genersoft.iot.vmp.media.abl.bean.ABLResult;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import okhttp3.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
@ -12,8 +14,10 @@ import org.springframework.stereotype.Component;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@ -27,7 +31,10 @@ public class ABLRESTfulUtils {
|
||||
private OkHttpClient client;
|
||||
|
||||
public interface RequestCallback{
|
||||
void run(JSONObject response);
|
||||
void run(String response);
|
||||
}
|
||||
public interface ResultCallback{
|
||||
void run(ABLResult response);
|
||||
}
|
||||
|
||||
private OkHttpClient getClient(){
|
||||
@ -53,26 +60,23 @@ public class ABLRESTfulUtils {
|
||||
|
||||
}
|
||||
|
||||
public JSONObject sendPost(MediaServer mediaServerItem, String api, Map<String, Object> param, RequestCallback callback) {
|
||||
public String sendPost(MediaServer mediaServerItem, String api, Map<String, Object> param, RequestCallback callback) {
|
||||
return sendPost(mediaServerItem, api, param, callback, null);
|
||||
}
|
||||
|
||||
|
||||
public JSONObject sendPost(MediaServer mediaServerItem, String api, Map<String, Object> param, RequestCallback callback, Integer readTimeOut) {
|
||||
public String sendPost(MediaServer mediaServerItem, String api, Map<String, Object> param, RequestCallback callback, Integer readTimeOut) {
|
||||
OkHttpClient client = getClient(readTimeOut);
|
||||
|
||||
if (mediaServerItem == null) {
|
||||
return null;
|
||||
}
|
||||
String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
|
||||
JSONObject responseJSON = new JSONObject();
|
||||
//-2自定义流媒体 调用错误码
|
||||
responseJSON.put("code",-2);
|
||||
responseJSON.put("msg","流媒体调用失败");
|
||||
String result = null;
|
||||
|
||||
FormBody.Builder builder = new FormBody.Builder();
|
||||
builder.add("secret",mediaServerItem.getSecret());
|
||||
if (param != null && param.keySet().size() > 0) {
|
||||
if (param != null && !param.isEmpty()) {
|
||||
for (String key : param.keySet()){
|
||||
if (param.get(key) != null) {
|
||||
builder.add(key, param.get(key).toString());
|
||||
@ -93,8 +97,7 @@ public class ABLRESTfulUtils {
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody != null) {
|
||||
String responseStr = responseBody.string();
|
||||
responseJSON = JSON.parseObject(responseStr);
|
||||
result = responseBody.string();
|
||||
}
|
||||
}else {
|
||||
response.close();
|
||||
@ -123,7 +126,7 @@ public class ABLRESTfulUtils {
|
||||
if (response.isSuccessful()) {
|
||||
try {
|
||||
String responseStr = Objects.requireNonNull(response.body()).string();
|
||||
callback.run(JSON.parseObject(responseStr));
|
||||
callback.run(responseStr);
|
||||
} catch (IOException e) {
|
||||
logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
|
||||
}
|
||||
@ -149,19 +152,16 @@ public class ABLRESTfulUtils {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
return responseJSON;
|
||||
return result;
|
||||
}
|
||||
|
||||
public JSONObject sendGet(MediaServer mediaServerItem, String api, Map<String, Object> param) {
|
||||
public String sendGet(MediaServer mediaServerItem, String api, Map<String, Object> param) {
|
||||
OkHttpClient client = getClient();
|
||||
|
||||
if (mediaServerItem == null) {
|
||||
return null;
|
||||
}
|
||||
JSONObject responseJSON = null;
|
||||
String result = null;
|
||||
StringBuilder stringBuffer = new StringBuilder();
|
||||
stringBuffer.append(String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api));
|
||||
if (param != null && !param.keySet().isEmpty()) {
|
||||
@ -188,8 +188,7 @@ public class ABLRESTfulUtils {
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody != null) {
|
||||
String responseStr = responseBody.string();
|
||||
responseJSON = JSON.parseObject(responseStr);
|
||||
result = responseBody.string();
|
||||
}
|
||||
}else {
|
||||
response.close();
|
||||
@ -201,10 +200,7 @@ public class ABLRESTfulUtils {
|
||||
}catch (IOException e) {
|
||||
logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
return responseJSON;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void sendGetForImg(MediaServer mediaServerItem, String api, Map<String, Object> params, String targetPath, String fileName) {
|
||||
@ -330,32 +326,55 @@ public class ABLRESTfulUtils {
|
||||
param.put("enable_mp4", 1);
|
||||
}
|
||||
|
||||
JSONObject jsonObject = sendPost(mediaServer, "openRtpServer", param, null);
|
||||
if (jsonObject.getInteger("code") == 0) {
|
||||
return jsonObject.getInteger("port");
|
||||
}else {
|
||||
String response = sendPost(mediaServer, "openRtpServer", param, null);
|
||||
if (response == null) {
|
||||
return 0;
|
||||
}else {
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult.getCode() == 0) {
|
||||
return ablResult.getPort();
|
||||
}else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject closeStreams(MediaServer mediaServerItem, String app, String stream) {
|
||||
public ABLResult closeStreams(MediaServer mediaServerItem, String app, String stream) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("vhost", "__defaultVhost__");
|
||||
param.put("app", app);
|
||||
param.put("stream", stream);
|
||||
param.put("force", 1);
|
||||
return sendPost(mediaServerItem, "close_streams",param, null);
|
||||
String response = sendPost(mediaServerItem, "close_streams", param, null);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject getServerConfig(MediaServer mediaServerItem){
|
||||
return sendPost(mediaServerItem, "getServerConfig",null, null);
|
||||
public ABLResult getServerConfig(MediaServer mediaServerItem){
|
||||
String response = sendPost(mediaServerItem, "getServerConfig", null, null);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject setConfigParamValue(MediaServer mediaServerItem, String key, Object value){
|
||||
public ABLResult setConfigParamValue(MediaServer mediaServerItem, String key, Object value){
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("key", key);
|
||||
param.put("value", value);
|
||||
return sendGet(mediaServerItem,"setConfigParamValue", param);
|
||||
String response = sendGet(mediaServerItem, "setConfigParamValue", param);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public void stopSendRtp(MediaServer mediaServer,String key) {
|
||||
@ -364,22 +383,35 @@ public class ABLRESTfulUtils {
|
||||
sendPost(mediaServer,"stopSendRtp", param, null);
|
||||
}
|
||||
|
||||
public JSONObject getMediaList(MediaServer mediaServer, String app, String stream) {
|
||||
public ABLResult getMediaList(MediaServer mediaServer, String app, String stream) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("app", app);
|
||||
if (stream != null) {
|
||||
param.put("stream", stream);
|
||||
}
|
||||
return sendPost(mediaServer,"getMediaList", param, null);
|
||||
|
||||
String response = sendGet(mediaServer, "getMediaList", param);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject queryRecordList(MediaServer mediaServer, String app, String stream, String startTime, String endTime) {
|
||||
public ABLResult queryRecordList(MediaServer mediaServer, String app, String stream, String startTime, String endTime) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("app", app);
|
||||
param.put("stream", stream);
|
||||
param.put("starttime", startTime);
|
||||
param.put("endtime", endTime);
|
||||
return sendPost(mediaServer,"queryRecordList", param, null);
|
||||
String response = sendGet(mediaServer, "queryRecordList", param);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public void getSnap(MediaServer mediaServer, String app, String stream, int timeoutSec, String path, String fileName) {
|
||||
@ -397,7 +429,13 @@ public class ABLRESTfulUtils {
|
||||
|
||||
}
|
||||
|
||||
public JSONObject addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) {
|
||||
public ABLResult addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) {
|
||||
try {
|
||||
url = URLEncoder.encode(url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
|
||||
}
|
||||
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("app", app);
|
||||
param.put("stream", stream);
|
||||
@ -405,10 +443,21 @@ public class ABLRESTfulUtils {
|
||||
param.put("disableAudio", disableAudio? "1" : "0");
|
||||
param.put("enable_mp4", enableMp4 ? "1" : "0");
|
||||
// TODO rtpType timeout 尚不支持
|
||||
return sendPost(mediaServer,"addStreamProxy", param, null);
|
||||
String response = sendGet(mediaServer, "addStreamProxy", param);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject addFFmpegProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) {
|
||||
public ABLResult addFFmpegProxy(MediaServer mediaServer, String app, String stream, String url, boolean disableAudio, boolean enableMp4, String rtpType, Integer timeout) {
|
||||
try {
|
||||
url = URLEncoder.encode(url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
|
||||
}
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("app", app);
|
||||
param.put("stream", stream);
|
||||
@ -416,19 +465,76 @@ public class ABLRESTfulUtils {
|
||||
param.put("disableAudio", disableAudio);
|
||||
param.put("enable_mp4", enableMp4);
|
||||
// TODO rtpType timeout 尚不支持
|
||||
return sendPost(mediaServer,"addFFmpegProxy", param, null);
|
||||
String response = sendGet(mediaServer, "addFFmpegProxy", param);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject delStreamProxy(MediaServer mediaServer, String streamKey) {
|
||||
public ABLResult delStreamProxy(MediaServer mediaServer, String streamKey) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("key", streamKey);
|
||||
return sendPost(mediaServer,"delStreamProxy", param, null);
|
||||
String response = sendGet(mediaServer, "delStreamProxy", param);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject delFFmpegProxy(MediaServer mediaServer, String streamKey) {
|
||||
public ABLResult delFFmpegProxy(MediaServer mediaServer, String streamKey) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("key", streamKey);
|
||||
return sendPost(mediaServer,"delFFmpegProxy", param, null);
|
||||
String response = sendGet(mediaServer, "delFFmpegProxy", param);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public ABLResult pauseRtpServer(MediaServer mediaServer, String streamKey) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("key", streamKey);
|
||||
String response = sendGet(mediaServer, "pauseRtpServer", param);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public ABLResult resumeRtpServer(MediaServer mediaServer, String streamKey) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("key", streamKey);
|
||||
String response = sendGet(mediaServer, "resumeRtpServer", param);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
public ABLResult controlRecordPlay(MediaServer mediaServer, String app, String stream, String command, String value) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("app", app);
|
||||
param.put("stream", stream);
|
||||
param.put("command", command);
|
||||
param.put("value", value);
|
||||
String response = sendGet(mediaServer, "controlRecordPlay", param);
|
||||
ABLResult ablResult = JSON.parseObject(response, ABLResult.class);
|
||||
if (ablResult == null) {
|
||||
return ABLResult.getFailForMediaServer();
|
||||
}else {
|
||||
return ablResult;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package com.genersoft.iot.vmp.media.abl.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ABLMedia {
|
||||
private String key;
|
||||
private String app;
|
||||
private String stream;
|
||||
private Integer sourceType;
|
||||
private Long duration;
|
||||
private String sim;
|
||||
private Boolean status;
|
||||
private Boolean enable_hls;
|
||||
private Boolean transcodingStatus;
|
||||
private String sourceURL;
|
||||
private Integer networkType;
|
||||
private Integer readerCount;
|
||||
private String videoCodec;
|
||||
private Integer width;
|
||||
private Integer height;
|
||||
private String audioCodec;
|
||||
private Integer audioChannels;
|
||||
private Integer audioSampleRate;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.genersoft.iot.vmp.media.abl.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ABLRecordFile {
|
||||
private String file;
|
||||
private Long duration;
|
||||
private ABLUrls url;
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package com.genersoft.iot.vmp.media.abl.bean;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ABLResult {
|
||||
private int code;
|
||||
private String memo;
|
||||
|
||||
|
||||
private String key;
|
||||
private Integer port;
|
||||
private JSONArray params;
|
||||
private List<ABLMedia> mediaList;
|
||||
|
||||
private String app;
|
||||
private String stream;
|
||||
private String starttime;
|
||||
private String endtime;
|
||||
private Long duration;
|
||||
private ABLUrls url;
|
||||
private List<ABLRecordFile> recordFileList;
|
||||
|
||||
public static ABLResult getFailForMediaServer() {
|
||||
ABLResult zlmResult = new ABLResult();
|
||||
zlmResult.setCode(-2);
|
||||
zlmResult.setMemo("流媒体调用失败");
|
||||
return zlmResult;
|
||||
}
|
||||
|
||||
public static ABLResult getMediaServer(int code, String msg) {
|
||||
ABLResult zlmResult = new ABLResult();
|
||||
zlmResult.setCode(code);
|
||||
zlmResult.setMemo(msg);
|
||||
return zlmResult;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ZLMResult{" +
|
||||
"code=" + code +
|
||||
", memo='" + memo + '\'' +
|
||||
(key != null ? (", key=" + key) : "") +
|
||||
(port != null ? (", port=" + port) : "") +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.genersoft.iot.vmp.media.abl.bean;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ABLUrls {
|
||||
private String rtsp;
|
||||
private String rtmp;
|
||||
|
||||
@JSONField(name = "http-flv")
|
||||
private String httpFlv;
|
||||
|
||||
@JSONField(name = "ws-flv")
|
||||
private String wsFlv;
|
||||
|
||||
@JSONField(name = "http-mp4")
|
||||
private String httpMp4;
|
||||
|
||||
private String download;
|
||||
}
|
||||
@ -44,11 +44,11 @@ public class AblServerConfig {
|
||||
@ConfigKeyId("hlsPort")
|
||||
private Integer hlsPort;
|
||||
|
||||
@ConfigKeyId("wsPort")
|
||||
private Integer wsPort;
|
||||
@ConfigKeyId("wsFlvPort")
|
||||
private Integer wsFlvPort;
|
||||
|
||||
@ConfigKeyId("mp4Port")
|
||||
private Integer mp4Port;
|
||||
@ConfigKeyId("httpMp4Port")
|
||||
private Integer httpMp4Port;
|
||||
|
||||
@ConfigKeyId("ps_tsRecvPort")
|
||||
private Integer psTsRecvPort;
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package com.genersoft.iot.vmp.media.abl.bean.hook;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class OnRecordMp4ABLHookParam extends ABLHookParam{
|
||||
private String fileName;
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
private String startTime;
|
||||
private String endTime;
|
||||
private long fileSize;
|
||||
}
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
package com.genersoft.iot.vmp.media.abl.bean.hook;
|
||||
|
||||
import com.genersoft.iot.vmp.media.abl.bean.AblUrls;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 流到来的事件
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class OnStreamArriveABLHookParam extends ABLHookParam{
|
||||
|
||||
|
||||
@ -105,141 +109,4 @@ public class OnStreamArriveABLHookParam extends ABLHookParam{
|
||||
|
||||
|
||||
private AblUrls url;
|
||||
|
||||
|
||||
public String getCallId() {
|
||||
return callId;
|
||||
}
|
||||
|
||||
public void setCallId(String callId) {
|
||||
this.callId = callId;
|
||||
}
|
||||
|
||||
public Boolean getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Boolean status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Boolean getEnableHls() {
|
||||
return enableHls;
|
||||
}
|
||||
|
||||
public void setEnableHls(Boolean enableHls) {
|
||||
this.enableHls = enableHls;
|
||||
}
|
||||
|
||||
public Boolean getTranscodingStatus() {
|
||||
return transcodingStatus;
|
||||
}
|
||||
|
||||
public void setTranscodingStatus(Boolean transcodingStatus) {
|
||||
this.transcodingStatus = transcodingStatus;
|
||||
}
|
||||
|
||||
public String getSourceURL() {
|
||||
return sourceURL;
|
||||
}
|
||||
|
||||
public void setSourceURL(String sourceURL) {
|
||||
this.sourceURL = sourceURL;
|
||||
}
|
||||
|
||||
public Integer getReaderCount() {
|
||||
return readerCount;
|
||||
}
|
||||
|
||||
public void setReaderCount(Integer readerCount) {
|
||||
this.readerCount = readerCount;
|
||||
}
|
||||
|
||||
public Integer getNoneReaderDuration() {
|
||||
return noneReaderDuration;
|
||||
}
|
||||
|
||||
public void setNoneReaderDuration(Integer noneReaderDuration) {
|
||||
this.noneReaderDuration = noneReaderDuration;
|
||||
}
|
||||
|
||||
public String getVideoCodec() {
|
||||
return videoCodec;
|
||||
}
|
||||
|
||||
public void setVideoCodec(String videoCodec) {
|
||||
this.videoCodec = videoCodec;
|
||||
}
|
||||
|
||||
public Integer getVideoFrameSpeed() {
|
||||
return videoFrameSpeed;
|
||||
}
|
||||
|
||||
public void setVideoFrameSpeed(Integer videoFrameSpeed) {
|
||||
this.videoFrameSpeed = videoFrameSpeed;
|
||||
}
|
||||
|
||||
public Integer getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(Integer width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public Integer getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(Integer height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public Integer getVideoBitrate() {
|
||||
return videoBitrate;
|
||||
}
|
||||
|
||||
public void setVideoBitrate(Integer videoBitrate) {
|
||||
this.videoBitrate = videoBitrate;
|
||||
}
|
||||
|
||||
public String getAudioCodec() {
|
||||
return audioCodec;
|
||||
}
|
||||
|
||||
public void setAudioCodec(String audioCodec) {
|
||||
this.audioCodec = audioCodec;
|
||||
}
|
||||
|
||||
public Integer getAudioChannels() {
|
||||
return audioChannels;
|
||||
}
|
||||
|
||||
public void setAudioChannels(Integer audioChannels) {
|
||||
this.audioChannels = audioChannels;
|
||||
}
|
||||
|
||||
public Integer getAudioSampleRate() {
|
||||
return audioSampleRate;
|
||||
}
|
||||
|
||||
public void setAudioSampleRate(Integer audioSampleRate) {
|
||||
this.audioSampleRate = audioSampleRate;
|
||||
}
|
||||
|
||||
public Integer getAudioBitrate() {
|
||||
return audioBitrate;
|
||||
}
|
||||
|
||||
public void setAudioBitrate(Integer audioBitrate) {
|
||||
this.audioBitrate = audioBitrate;
|
||||
}
|
||||
|
||||
public AblUrls getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(AblUrls url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.media.bean;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.media.abl.bean.ABLMedia;
|
||||
import com.genersoft.iot.vmp.media.abl.bean.hook.OnStreamArriveABLHookParam;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType;
|
||||
@ -270,7 +271,38 @@ public class MediaInfo {
|
||||
return mediaInfo;
|
||||
}
|
||||
|
||||
public static MediaInfo getInstanceForAblJson(JSONObject mediaJSON, MediaServer mediaServer) {
|
||||
return null;
|
||||
public static MediaInfo getInstance(ABLMedia ablMedia, MediaServer mediaServer) {
|
||||
MediaInfo mediaInfo = new MediaInfo();
|
||||
mediaInfo.setApp(ablMedia.getApp());
|
||||
mediaInfo.setStream(ablMedia.getStream());
|
||||
mediaInfo.setMediaServer(mediaServer);
|
||||
mediaInfo.setReaderCount(ablMedia.getReaderCount());
|
||||
mediaInfo.setOnline(true);
|
||||
mediaInfo.setVideoCodec(ablMedia.getVideoCodec());
|
||||
switch (ablMedia.getNetworkType()) {
|
||||
case 21:
|
||||
mediaInfo.setOriginType(OriginType.RTMP_PUSH.ordinal());
|
||||
break;
|
||||
case 23:
|
||||
mediaInfo.setOriginType(OriginType.RTSP_PUSH.ordinal());
|
||||
break;
|
||||
case 30:
|
||||
case 31:
|
||||
case 32:
|
||||
case 33:
|
||||
mediaInfo.setOriginType(OriginType.PULL.ordinal());
|
||||
break;
|
||||
default:
|
||||
mediaInfo.setOriginType(OriginType.UNKNOWN.ordinal());
|
||||
break;
|
||||
|
||||
}
|
||||
mediaInfo.setWidth(ablMedia.getWidth());
|
||||
mediaInfo.setHeight(ablMedia.getHeight());
|
||||
mediaInfo.setAudioCodec(ablMedia.getAudioCodec());
|
||||
mediaInfo.setAudioChannels(ablMedia.getAudioChannels());
|
||||
mediaInfo.setAudioSampleRate(ablMedia.getAudioSampleRate());
|
||||
|
||||
return mediaInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,6 +41,9 @@ public class MediaServer {
|
||||
@Schema(description = "https-flv端口")
|
||||
private int flvSSLPort;
|
||||
|
||||
@Schema(description = "mp4端口")
|
||||
private int mp4Port;
|
||||
|
||||
@Schema(description = "ws-flv端口")
|
||||
private int wsFlvPort;
|
||||
|
||||
@ -122,11 +125,7 @@ public class MediaServer {
|
||||
sdpIp = ObjectUtils.isEmpty(zlmServerConfig.getSdpIp())? zlmServerConfig.getIp(): zlmServerConfig.getSdpIp();
|
||||
streamIp = ObjectUtils.isEmpty(zlmServerConfig.getStreamIp())? zlmServerConfig.getIp(): zlmServerConfig.getStreamIp();
|
||||
httpPort = zlmServerConfig.getHttpPort();
|
||||
flvPort = zlmServerConfig.getHttpPort();
|
||||
wsFlvPort = zlmServerConfig.getHttpPort();
|
||||
httpSSlPort = zlmServerConfig.getHttpSSLport();
|
||||
flvSSLPort = zlmServerConfig.getHttpSSLport();
|
||||
wsFlvSSLPort = zlmServerConfig.getHttpSSLport();
|
||||
rtmpPort = zlmServerConfig.getRtmpPort();
|
||||
rtmpSSlPort = zlmServerConfig.getRtmpSslPort();
|
||||
rtpProxyPort = zlmServerConfig.getRtpProxyPort();
|
||||
@ -150,20 +149,14 @@ public class MediaServer {
|
||||
streamIp = config.getServerIp();
|
||||
httpPort = config.getHttpServerPort();
|
||||
flvPort = config.getHttpFlvPort();
|
||||
wsFlvPort = config.getWsPort();
|
||||
// httpSSlPort = config.getHttpSSLport();
|
||||
// flvSSLPort = config.getHttpSSLport();
|
||||
// wsFlvSSLPort = config.getHttpSSLport();
|
||||
mp4Port = config.getHttpMp4Port();
|
||||
wsFlvPort = config.getWsFlvPort();
|
||||
rtmpPort = config.getRtmpPort();
|
||||
// rtmpSSlPort = config.getRtmpSslPort();
|
||||
rtpProxyPort = config.getJtt1078RecvPort();
|
||||
rtspPort = config.getRtspPort();
|
||||
// rtspSSLPort = config.getRtspSSlport();
|
||||
autoConfig = true; // 默认值true;
|
||||
secret = config.getSecret();
|
||||
// hookAliveInterval = config.getHookAliveInterval();
|
||||
rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口
|
||||
// rtpPortRange = config.getPortRange().replace("_",","); // 默认使用30000,30500作为级联时发送流的端口号
|
||||
rtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号
|
||||
recordAssistPort = 0; // 默认关闭
|
||||
}
|
||||
|
||||
@ -2,34 +2,63 @@ package com.genersoft.iot.vmp.media.bean;
|
||||
|
||||
import com.genersoft.iot.vmp.media.abl.bean.hook.OnRecordMp4ABLHookParam;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
|
||||
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RecordInfo {
|
||||
private String app;
|
||||
private String stream;
|
||||
private String fileName;
|
||||
private String filePath;
|
||||
private long fileSize;
|
||||
private String folder;
|
||||
private String url;
|
||||
/**
|
||||
* 单位毫秒
|
||||
*/
|
||||
private long startTime;
|
||||
/**
|
||||
* 单位毫秒
|
||||
*/
|
||||
private double timeLen;
|
||||
private String params;
|
||||
|
||||
public static RecordInfo getInstance(OnRecordMp4HookParam hookParam) {
|
||||
RecordInfo recordInfo = new RecordInfo();
|
||||
recordInfo.setApp(hookParam.getApp());
|
||||
recordInfo.setStream(hookParam.getStream());
|
||||
recordInfo.setFileName(hookParam.getFile_name());
|
||||
recordInfo.setUrl(hookParam.getUrl());
|
||||
recordInfo.setFolder(hookParam.getFolder());
|
||||
recordInfo.setFilePath(hookParam.getFile_path());
|
||||
recordInfo.setFileSize(hookParam.getFile_size());
|
||||
recordInfo.setStartTime(hookParam.getStart_time());
|
||||
recordInfo.setTimeLen(hookParam.getTime_len());
|
||||
recordInfo.setStartTime(hookParam.getStart_time() * 1000);
|
||||
recordInfo.setTimeLen(hookParam.getTime_len() * 1000);
|
||||
return recordInfo;
|
||||
}
|
||||
|
||||
public static RecordInfo getInstance(OnRecordMp4ABLHookParam hookParam) {
|
||||
RecordInfo recordInfo = new RecordInfo();
|
||||
recordInfo.setApp(hookParam.getApp());
|
||||
recordInfo.setStream(hookParam.getStream());
|
||||
recordInfo.setFileName(hookParam.getFileName());
|
||||
recordInfo.setStartTime(DateUtil.urlToTimestampMs(hookParam.getStartTime()));
|
||||
recordInfo.setTimeLen(DateUtil.urlToTimestampMs(hookParam.getEndTime()) - recordInfo.getStartTime());
|
||||
recordInfo.setFileSize(hookParam.getFileSize());
|
||||
return recordInfo;
|
||||
}
|
||||
|
||||
public static RecordInfo getInstance(CloudRecordItem cloudRecordItem) {
|
||||
RecordInfo recordInfo = new RecordInfo();
|
||||
recordInfo.setApp(cloudRecordItem.getApp());
|
||||
recordInfo.setStream(cloudRecordItem.getStream());
|
||||
recordInfo.setFileName(cloudRecordItem.getFileName());
|
||||
recordInfo.setStartTime(cloudRecordItem.getStartTime());
|
||||
recordInfo.setTimeLen(cloudRecordItem.getTimeLen());
|
||||
recordInfo.setFileSize(cloudRecordItem.getFileSize());
|
||||
recordInfo.setFilePath(cloudRecordItem.getFilePath());
|
||||
return recordInfo;
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,9 @@ import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.media.bean.RecordInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||
|
||||
@ -74,9 +77,15 @@ public interface IMediaNodeServerService {
|
||||
|
||||
List<String> listRtpServer(MediaServer mediaServer);
|
||||
|
||||
void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath);
|
||||
void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String datePath, String dateDir, ErrorCallback<StreamInfo> callback);
|
||||
|
||||
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema);
|
||||
|
||||
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema);
|
||||
|
||||
DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo);
|
||||
|
||||
StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay);
|
||||
|
||||
void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback<StreamInfo> callback);
|
||||
}
|
||||
|
||||
@ -5,8 +5,8 @@ import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.service.bean.MediaServerLoad;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.RecordInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.*;
|
||||
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||
|
||||
@ -164,9 +164,13 @@ public interface IMediaServerService {
|
||||
|
||||
List<String> listRtpServer(MediaServer mediaServer);
|
||||
|
||||
StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath);
|
||||
void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String datePath, String dateDir, ErrorCallback<StreamInfo> callback);
|
||||
|
||||
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema);
|
||||
|
||||
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema);
|
||||
|
||||
DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo);
|
||||
|
||||
void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback<StreamInfo> callback);
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
|
||||
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.media.bean.RecordInfo;
|
||||
import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent;
|
||||
import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent;
|
||||
import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerDeleteEvent;
|
||||
@ -20,8 +21,7 @@ import com.genersoft.iot.vmp.media.service.IMediaNodeServerService;
|
||||
import com.genersoft.iot.vmp.media.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType;
|
||||
import com.genersoft.iot.vmp.service.bean.MediaServerLoad;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.*;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
|
||||
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
|
||||
@ -841,68 +841,12 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
|
||||
@Override
|
||||
public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay) {
|
||||
StreamInfo streamInfoResult = new StreamInfo();
|
||||
streamInfoResult.setStream(stream);
|
||||
streamInfoResult.setApp(app);
|
||||
if (addr == null) {
|
||||
addr = mediaServer.getStreamIp();
|
||||
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
|
||||
if (mediaNodeServerService == null) {
|
||||
log.info("[getStreamInfoByAppAndStream] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType());
|
||||
return null;
|
||||
}
|
||||
|
||||
streamInfoResult.setIp(addr);
|
||||
if (mediaInfo != null) {
|
||||
streamInfoResult.setServerId(mediaInfo.getServerId());
|
||||
}else {
|
||||
streamInfoResult.setServerId(userSetting.getServerId());
|
||||
}
|
||||
|
||||
streamInfoResult.setMediaServer(mediaServer);
|
||||
Map<String, String> param = new HashMap<>();
|
||||
if (!ObjectUtils.isEmpty(callId)) {
|
||||
param.put("callId", callId);
|
||||
}
|
||||
if (mediaInfo != null && !ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) {
|
||||
param.put("originTypeStr", mediaInfo.getOriginTypeStr());
|
||||
}
|
||||
StringBuilder callIdParamBuilder = new StringBuilder();
|
||||
if (!param.isEmpty()) {
|
||||
callIdParamBuilder.append("?");
|
||||
for (Map.Entry<String, String> entry : param.entrySet()) {
|
||||
callIdParamBuilder.append(entry.getKey()).append("=").append(entry.getValue());
|
||||
callIdParamBuilder.append("&");
|
||||
}
|
||||
callIdParamBuilder.deleteCharAt(callIdParamBuilder.length() - 1);
|
||||
}
|
||||
|
||||
String callIdParam = callIdParamBuilder.toString();
|
||||
|
||||
streamInfoResult.setRtmp(addr, mediaServer.getRtmpPort(),mediaServer.getRtmpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setRtsp(addr, mediaServer.getRtspPort(),mediaServer.getRtspSSLPort(), app, stream, callIdParam);
|
||||
|
||||
|
||||
if ("abl".equals(mediaServer.getType())) {
|
||||
String flvFile = String.format("%s/%s.flv%s", app, stream, callIdParam);
|
||||
streamInfoResult.setFlv(addr, mediaServer.getFlvPort(),mediaServer.getFlvSSLPort(), flvFile);
|
||||
streamInfoResult.setWsFlv(addr, mediaServer.getWsFlvPort(),mediaServer.getWsFlvSSLPort(), flvFile);
|
||||
}else {
|
||||
String flvFile = String.format("%s/%s.live.flv%s", app, stream, callIdParam);
|
||||
streamInfoResult.setFlv(addr, mediaServer.getFlvPort(),mediaServer.getFlvSSLPort(), flvFile);
|
||||
streamInfoResult.setWsFlv(addr, mediaServer.getWsFlvPort(),mediaServer.getWsFlvSSLPort(), flvFile);
|
||||
}
|
||||
|
||||
streamInfoResult.setFmp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setHls(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setTs(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setRtc(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay);
|
||||
|
||||
streamInfoResult.setMediaInfo(mediaInfo);
|
||||
|
||||
if (!"broadcast".equalsIgnoreCase(app) && !ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix())) {
|
||||
String newStream = stream + "_" + mediaServer.getTranscodeSuffix();
|
||||
mediaServer.setTranscodeSuffix(null);
|
||||
StreamInfo transcodeStreamInfo = getStreamInfoByAppAndStream(mediaServer, app, newStream, null, addr, callId, isPlay);
|
||||
streamInfoResult.setTranscodeStream(transcodeStreamInfo);
|
||||
}
|
||||
return streamInfoResult;
|
||||
return mediaNodeServerService.getStreamInfoByAppAndStream(mediaServer, app, stream, mediaInfo, addr, callId, isPlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1008,14 +952,24 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
|
||||
public void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback<StreamInfo> callback) {
|
||||
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
|
||||
if (mediaNodeServerService == null) {
|
||||
log.info("[loadMP4FileForDate] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType());
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
|
||||
}
|
||||
mediaNodeServerService.loadMP4FileForDate(mediaServer, app, stream, date, dateDir, callback);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback<StreamInfo> callback) {
|
||||
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
|
||||
if (mediaNodeServerService == null) {
|
||||
log.info("[loadMP4File] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType());
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
|
||||
}
|
||||
mediaNodeServerService.loadMP4File(mediaServer, app, stream, datePath);
|
||||
return getStreamInfoByAppAndStream(mediaServer, app, stream, null, null);
|
||||
mediaNodeServerService.loadMP4File(mediaServer, app, stream, filePath, fileName, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1037,4 +991,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
}
|
||||
mediaNodeServerService.setRecordSpeed(mediaServer, app, stream, speed, schema);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadFileInfo getDownloadFilePath(MediaServer mediaServer, RecordInfo recordInfo) {
|
||||
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
|
||||
if (mediaNodeServerService == null) {
|
||||
log.info("[setRecordSpeed] 失败, mediaServer的类型: {},未找到对应的实现类", mediaServer.getType());
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
|
||||
}
|
||||
return mediaNodeServerService.getDownloadFilePath(mediaServer, recordInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,12 +10,19 @@ import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.media.bean.RecordInfo;
|
||||
import com.genersoft.iot.vmp.media.event.hook.Hook;
|
||||
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.event.hook.HookType;
|
||||
import com.genersoft.iot.vmp.media.service.IMediaNodeServerService;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.*;
|
||||
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@ -36,6 +43,9 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
@Autowired
|
||||
private HookSubscribe subscribe;
|
||||
|
||||
@Override
|
||||
public int createRTPServer(MediaServer mediaServer, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) {
|
||||
return zlmServerFactory.createRTPServer(mediaServer, "rtp", streamId, ssrc, port, onlyAuto, disableAudio, reUsePort, tcpMode);
|
||||
@ -97,8 +107,6 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
|
||||
mediaServer.setServerId(userSetting.getServerId());
|
||||
mediaServer.setIp(ip);
|
||||
mediaServer.setHttpPort(port);
|
||||
mediaServer.setFlvPort(port);
|
||||
mediaServer.setWsFlvPort(port);
|
||||
mediaServer.setSecret(secret);
|
||||
ZLMResult<List<JSONObject>> mediaServerConfigResult = zlmresTfulUtils.getMediaServerConfig(mediaServer);
|
||||
if (mediaServerConfigResult == null) {
|
||||
@ -114,8 +122,6 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
|
||||
}
|
||||
mediaServer.setId(zlmServerConfig.getGeneralMediaServerId());
|
||||
mediaServer.setHttpSSlPort(zlmServerConfig.getHttpSSLport());
|
||||
mediaServer.setFlvSSLPort(zlmServerConfig.getHttpSSLport());
|
||||
mediaServer.setWsFlvSSLPort(zlmServerConfig.getHttpSSLport());
|
||||
mediaServer.setRtmpPort(zlmServerConfig.getRtmpPort());
|
||||
mediaServer.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
|
||||
mediaServer.setRtspPort(zlmServerConfig.getRtspPort());
|
||||
@ -190,7 +196,8 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
|
||||
for (int i = 0; i < zlmResult.getData().size(); i++) {
|
||||
JSONObject mediaJSON = zlmResult.getData().getJSONObject(0);
|
||||
MediaInfo mediaInfo = MediaInfo.getInstance(mediaJSON, mediaServer, userSetting.getServerId());
|
||||
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, mediaInfo.getApp(), mediaInfo.getStream(), mediaInfo, callId, true);
|
||||
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, mediaInfo.getApp(),
|
||||
mediaInfo.getStream(), mediaInfo, null, callId, true);
|
||||
if (streamInfo != null) {
|
||||
streamInfoList.add(streamInfo);
|
||||
}
|
||||
@ -200,52 +207,6 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
|
||||
return streamInfoList;
|
||||
}
|
||||
|
||||
public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String callId, boolean isPlay) {
|
||||
StreamInfo streamInfoResult = new StreamInfo();
|
||||
streamInfoResult.setServerId(userSetting.getServerId());
|
||||
streamInfoResult.setStream(stream);
|
||||
streamInfoResult.setApp(app);
|
||||
String addr = mediaServer.getStreamIp();
|
||||
streamInfoResult.setIp(addr);
|
||||
streamInfoResult.setMediaServer(mediaServer);
|
||||
|
||||
Map<String, String> param = new HashMap<>();
|
||||
if (!ObjectUtils.isEmpty(callId)) {
|
||||
param.put("callId", callId);
|
||||
}
|
||||
if (mediaInfo != null && !ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) {
|
||||
param.put("originTypeStr", mediaInfo.getOriginTypeStr());
|
||||
}
|
||||
StringBuilder callIdParamBuilder = new StringBuilder();
|
||||
if (!param.isEmpty()) {
|
||||
callIdParamBuilder.append("?");
|
||||
for (Map.Entry<String, String> entry : param.entrySet()) {
|
||||
callIdParamBuilder.append(entry.getKey()).append("=").append(entry.getValue());
|
||||
callIdParamBuilder.append("&");
|
||||
}
|
||||
callIdParamBuilder.deleteCharAt(callIdParamBuilder.length() - 1);
|
||||
}
|
||||
|
||||
String callIdParam = callIdParamBuilder.toString();
|
||||
|
||||
streamInfoResult.setRtmp(addr, mediaServer.getRtmpPort(),mediaServer.getRtmpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setRtsp(addr, mediaServer.getRtspPort(),mediaServer.getRtspSSLPort(), app, stream, callIdParam);
|
||||
String flvFile = String.format("%s/%s.live.flv%s", app, stream, callIdParam);
|
||||
streamInfoResult.setFlv(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), flvFile);
|
||||
streamInfoResult.setWsFlv(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), flvFile);
|
||||
streamInfoResult.setFmp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setHls(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setTs(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setRtc(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay);
|
||||
|
||||
streamInfoResult.setMediaInfo(mediaInfo);
|
||||
if (mediaInfo != null) {
|
||||
streamInfoResult.setOriginType(mediaInfo.getOriginType());
|
||||
streamInfoResult.setOriginTypeStr(mediaInfo.getOriginTypeStr());
|
||||
}
|
||||
return streamInfoResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean connectRtpServer(MediaServer mediaServer, String address, int port, String stream) {
|
||||
ZLMResult<?> zlmResult = zlmresTfulUtils.connectRtpServer(mediaServer, address, port, stream);
|
||||
@ -552,8 +513,51 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
|
||||
ZLMResult<?> zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, app, stream, datePath);
|
||||
public void loadMP4File(MediaServer mediaServer, String app, String stream, String filePath, String fileName, ErrorCallback<StreamInfo> callback) {
|
||||
String buildApp = "mp4_record";
|
||||
String buildStream = app + "_" + stream + "_" + fileName + "_" + RandomStringUtils.randomAlphabetic(6).toLowerCase();
|
||||
|
||||
Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServer.getServerId());
|
||||
subscribe.addSubscribe(hook, (hookData) -> {
|
||||
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null, null, true);
|
||||
if (callback != null) {
|
||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
|
||||
}
|
||||
});
|
||||
|
||||
ZLMResult<?> zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, buildApp, buildStream, filePath);
|
||||
|
||||
if (zlmResult == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
|
||||
}
|
||||
if (zlmResult.getCode() != 0) {
|
||||
throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMP4FileForDate(MediaServer mediaServer, String app, String stream, String date, String dateDir, ErrorCallback<StreamInfo> callback) {
|
||||
String buildApp = "mp4_record";
|
||||
String buildStream = app + "_" + stream + "_" + date;
|
||||
MediaInfo mediaInfo = getMediaInfo(mediaServer, buildApp, buildStream);
|
||||
if (mediaInfo != null) {
|
||||
if (callback != null) {
|
||||
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, mediaInfo, null, null, true);
|
||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServer.getServerId());
|
||||
subscribe.addSubscribe(hook, (hookData) -> {
|
||||
StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null, null, true);
|
||||
if (callback != null) {
|
||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
|
||||
}
|
||||
});
|
||||
|
||||
ZLMResult<?> zlmResult = zlmresTfulUtils.loadMP4File(mediaServer, buildApp, buildStream, dateDir);
|
||||
|
||||
if (zlmResult == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
|
||||
}
|
||||
@ -583,4 +587,96 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
|
||||
throw new ControllerException(zlmResult.getCode(), zlmResult.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadFileInfo getDownloadFilePath(MediaServer mediaServerItem, RecordInfo recordInfo) {
|
||||
|
||||
// 将filePath作为独立参数传入,避免%符号解析问题
|
||||
String pathTemplate = "%s://%s:%s/index/api/downloadFile?file_path=%s";
|
||||
|
||||
DownloadFileInfo info = new DownloadFileInfo();
|
||||
|
||||
// filePath作为第4个参数
|
||||
info.setHttpPath(String.format(pathTemplate,
|
||||
"http",
|
||||
mediaServerItem.getStreamIp(),
|
||||
mediaServerItem.getHttpPort(),
|
||||
recordInfo.getFilePath()));
|
||||
|
||||
// 同样作为第4个参数
|
||||
if (mediaServerItem.getHttpSSlPort() > 0) {
|
||||
info.setHttpsPath(String.format(pathTemplate,
|
||||
"https",
|
||||
mediaServerItem.getStreamIp(),
|
||||
mediaServerItem.getHttpSSlPort(),
|
||||
recordInfo.getFilePath()));
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamInfo getStreamInfoByAppAndStream(MediaServer mediaServer, String app, String stream, MediaInfo mediaInfo, String addr, String callId, boolean isPlay) {
|
||||
StreamInfo streamInfoResult = new StreamInfo();
|
||||
streamInfoResult.setStream(stream);
|
||||
streamInfoResult.setApp(app);
|
||||
if (addr == null) {
|
||||
addr = mediaServer.getStreamIp();
|
||||
}
|
||||
|
||||
streamInfoResult.setIp(addr);
|
||||
if (mediaInfo != null) {
|
||||
streamInfoResult.setServerId(mediaInfo.getServerId());
|
||||
}else {
|
||||
streamInfoResult.setServerId(userSetting.getServerId());
|
||||
}
|
||||
|
||||
streamInfoResult.setMediaServer(mediaServer);
|
||||
Map<String, String> param = new HashMap<>();
|
||||
if (!ObjectUtils.isEmpty(callId)) {
|
||||
param.put("callId", callId);
|
||||
}
|
||||
if (mediaInfo != null && !ObjectUtils.isEmpty(mediaInfo.getOriginTypeStr())) {
|
||||
param.put("originTypeStr", mediaInfo.getOriginTypeStr());
|
||||
}
|
||||
StringBuilder callIdParamBuilder = new StringBuilder();
|
||||
if (!param.isEmpty()) {
|
||||
callIdParamBuilder.append("?");
|
||||
for (Map.Entry<String, String> entry : param.entrySet()) {
|
||||
callIdParamBuilder.append(entry.getKey()).append("=").append(entry.getValue());
|
||||
callIdParamBuilder.append("&");
|
||||
}
|
||||
callIdParamBuilder.deleteCharAt(callIdParamBuilder.length() - 1);
|
||||
}
|
||||
|
||||
String callIdParam = callIdParamBuilder.toString();
|
||||
|
||||
streamInfoResult.setRtmp(addr, mediaServer.getRtmpPort(),mediaServer.getRtmpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setRtsp(addr, mediaServer.getRtspPort(),mediaServer.getRtspSSLPort(), app, stream, callIdParam);
|
||||
|
||||
String flvFile = String.format("%s/%s.live.flv%s", app, stream, callIdParam);
|
||||
streamInfoResult.setFlv(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), flvFile);
|
||||
streamInfoResult.setWsFlv(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), flvFile);
|
||||
|
||||
String mp4File = String.format("%s/%s.live.mp4%s", app, stream, callIdParam);
|
||||
streamInfoResult.setFmp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), mp4File);
|
||||
streamInfoResult.setWsMp4(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), mp4File);
|
||||
|
||||
streamInfoResult.setHls(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setWsHls(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
|
||||
streamInfoResult.setTs(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setWsTs(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam);
|
||||
|
||||
streamInfoResult.setRtc(addr, mediaServer.getHttpPort(), mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay);
|
||||
|
||||
streamInfoResult.setMediaInfo(mediaInfo);
|
||||
|
||||
if (!"broadcast".equalsIgnoreCase(app) && !ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix())) {
|
||||
String newStream = stream + "_" + mediaServer.getTranscodeSuffix();
|
||||
mediaServer.setTranscodeSuffix(null);
|
||||
StreamInfo transcodeStreamInfo = getStreamInfoByAppAndStream(mediaServer, app, newStream, null, addr, callId, isPlay);
|
||||
streamInfoResult.setTranscodeStream(transcodeStreamInfo);
|
||||
}
|
||||
return streamInfoResult;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,8 +4,10 @@ import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.*;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
@ -15,9 +17,11 @@ import org.springframework.stereotype.Component;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -327,6 +331,12 @@ public class ZLMRESTfulUtils {
|
||||
|
||||
public ZLMResult<StreamProxyResult> addFFmpegSource(MediaServer mediaServer, String src_url, String dst_url, Integer timeout_sec,
|
||||
boolean enable_audio, boolean enable_mp4, String ffmpeg_cmd_key){
|
||||
try {
|
||||
src_url = URLEncoder.encode(src_url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
|
||||
}
|
||||
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("src_url", src_url);
|
||||
param.put("dst_url", dst_url);
|
||||
@ -573,6 +583,11 @@ public class ZLMRESTfulUtils {
|
||||
}
|
||||
|
||||
public ZLMResult<StreamProxyResult> addStreamProxy(MediaServer mediaServer, String app, String stream, String url, boolean enable_audio, boolean enable_mp4, String rtp_type, Integer timeOut) {
|
||||
try {
|
||||
url = URLEncoder.encode(url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(),"url编码失败");
|
||||
}
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("vhost", "__defaultVhost__");
|
||||
param.put("app", app);
|
||||
|
||||
@ -59,11 +59,13 @@ public interface ICloudRecordService {
|
||||
/**
|
||||
* 加载录像文件,形成录像流
|
||||
*/
|
||||
void loadRecord(String app, String stream, String date, ErrorCallback<StreamInfo> callback);
|
||||
void loadMP4FileForDate(String app, String stream, String date, ErrorCallback<StreamInfo> callback);
|
||||
|
||||
void seekRecord(String mediaServerId,String app, String stream, Double seek, String schema);
|
||||
|
||||
void setRecordSpeed(String mediaServerId, String app, String stream, Integer speed, String schema);
|
||||
|
||||
void deleteFileByIds(Set<Integer> ids);
|
||||
|
||||
void loadMP4File(String app, String stream, int cloudRecordId, ErrorCallback<StreamInfo> callback);
|
||||
}
|
||||
|
||||
@ -91,14 +91,14 @@ public class CloudRecordItem {
|
||||
CloudRecordItem cloudRecordItem = new CloudRecordItem();
|
||||
cloudRecordItem.setApp(param.getApp());
|
||||
cloudRecordItem.setStream(param.getStream());
|
||||
cloudRecordItem.setStartTime(param.getRecordInfo().getStartTime()*1000);
|
||||
cloudRecordItem.setStartTime(param.getRecordInfo().getStartTime());
|
||||
cloudRecordItem.setFileName(param.getRecordInfo().getFileName());
|
||||
cloudRecordItem.setFolder(param.getRecordInfo().getFolder());
|
||||
cloudRecordItem.setFileSize(param.getRecordInfo().getFileSize());
|
||||
cloudRecordItem.setFilePath(param.getRecordInfo().getFilePath());
|
||||
cloudRecordItem.setMediaServerId(param.getMediaServer().getId());
|
||||
cloudRecordItem.setTimeLen(param.getRecordInfo().getTimeLen() * 1000);
|
||||
cloudRecordItem.setEndTime((param.getRecordInfo().getStartTime() + (long)param.getRecordInfo().getTimeLen()) * 1000);
|
||||
cloudRecordItem.setTimeLen(param.getRecordInfo().getTimeLen());
|
||||
cloudRecordItem.setEndTime((param.getRecordInfo().getStartTime() + (long)param.getRecordInfo().getTimeLen()));
|
||||
Map<String, String> paramsMap = MediaServerUtils.urlParamToMap(param.getRecordInfo().getParams());
|
||||
if (paramsMap.get("callId") != null) {
|
||||
cloudRecordItem.setCallId(paramsMap.get("callId"));
|
||||
|
||||
@ -5,11 +5,8 @@ import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.media.event.hook.Hook;
|
||||
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.event.hook.HookType;
|
||||
import com.genersoft.iot.vmp.media.bean.RecordInfo;
|
||||
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
|
||||
import com.genersoft.iot.vmp.media.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
|
||||
@ -21,7 +18,6 @@ import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
|
||||
import com.genersoft.iot.vmp.utils.CloudRecordUtils;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
@ -37,7 +33,10 @@ import org.springframework.util.Assert;
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@ -61,9 +60,6 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
|
||||
@Autowired
|
||||
private IRedisRpcPlayService redisRpcPlayService;
|
||||
|
||||
@Autowired
|
||||
private HookSubscribe subscribe;
|
||||
|
||||
@Override
|
||||
public PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime,
|
||||
String endTime, List<MediaServer> mediaServerItems, String callId, Boolean ascOrder) {
|
||||
@ -255,9 +251,10 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
|
||||
if (!userSetting.getServerId().equals(recordItem.getServerId())) {
|
||||
return redisRpcPlayService.getRecordPlayUrl(recordItem.getServerId(), recordId);
|
||||
}
|
||||
String filePath = recordItem.getFilePath();
|
||||
MediaServer mediaServerItem = mediaServerService.getOne(recordItem.getMediaServerId());
|
||||
return CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath);
|
||||
|
||||
MediaServer mediaServer = mediaServerService.getOne(recordItem.getMediaServerId());
|
||||
|
||||
return mediaServerService.getDownloadFilePath(mediaServer, RecordInfo.getInstance(recordItem));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -284,7 +281,36 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRecord(String app, String stream, String date, ErrorCallback<StreamInfo> callback) {
|
||||
public void loadMP4File(String app, String stream, int cloudRecordId, ErrorCallback<StreamInfo> callback) {
|
||||
|
||||
CloudRecordItem recordItem = cloudRecordServiceMapper.queryOne(cloudRecordId);
|
||||
if (recordItem == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "无录像");
|
||||
}
|
||||
String mediaServerId = recordItem.getMediaServerId();
|
||||
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
|
||||
if (mediaServer == null) {
|
||||
log.warn("[云端录像] 播放 未找到录制的流媒体,将自动选择低负载流媒体使用");
|
||||
mediaServer = mediaServerService.getMediaServerForMinimumLoad(null);
|
||||
}
|
||||
if (mediaServer == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "无可用流媒体");
|
||||
}
|
||||
String fileName = recordItem.getFileName().substring(0 , recordItem.getFileName().indexOf("."));
|
||||
String filePath = recordItem.getFilePath();
|
||||
// if (filePath != null) {
|
||||
// fileName = filePath.substring(0, filePath.lastIndexOf("/"));
|
||||
// }
|
||||
mediaServerService.loadMP4File(mediaServer, app, stream, filePath, fileName, ((code, msg, streamInfo) -> {
|
||||
if (code == ErrorCode.SUCCESS.getCode()) {
|
||||
streamInfo.setDuration(recordItem.getTimeLen());
|
||||
}
|
||||
callback.run(code, msg, streamInfo);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMP4FileForDate(String app, String stream, String date, ErrorCallback<StreamInfo> callback) {
|
||||
long startTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(date + " 00:00:00");
|
||||
long endTimestamp = startTimestamp + 24 * 60 * 60 * 1000;
|
||||
|
||||
@ -297,26 +323,13 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
|
||||
if (mediaServer == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId);
|
||||
}
|
||||
String buildApp = "mp4_record";
|
||||
String buildStream = app + "_" + stream + "_" + date;
|
||||
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, buildApp, buildStream);
|
||||
if (mediaInfo != null) {
|
||||
if (callback != null) {
|
||||
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, mediaInfo, null);
|
||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
|
||||
}
|
||||
return;
|
||||
}
|
||||
String dateDir = null;
|
||||
String filePath = recordItemList.get(0).getFilePath();
|
||||
if (filePath != null) {
|
||||
dateDir = filePath.substring(0, filePath.lastIndexOf("/"));
|
||||
}
|
||||
mediaServerService.loadMP4FileForDate(mediaServer, app, stream, date, dateDir, callback);
|
||||
|
||||
Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServerId);
|
||||
subscribe.addSubscribe(hook, (hookData) -> {
|
||||
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null);
|
||||
if (callback != null) {
|
||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
|
||||
}
|
||||
});
|
||||
String dateDir = recordItemList.get(0).getFilePath().substring(0, recordItemList.get(0).getFilePath().lastIndexOf("/"));
|
||||
mediaServerService.loadMP4File(mediaServer, buildApp, buildStream, dateDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -7,13 +7,10 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService;
|
||||
import com.genersoft.iot.vmp.gb28181.session.SipInviteSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.jt1078.bean.JTMediaStreamType;
|
||||
import com.genersoft.iot.vmp.jt1078.service.Ijt1078PlayService;
|
||||
import com.genersoft.iot.vmp.jt1078.service.Ijt1078Service;
|
||||
@ -21,8 +18,8 @@ import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.media.bean.ResultForOnPublish;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
|
||||
import com.genersoft.iot.vmp.service.IMediaService;
|
||||
import com.genersoft.iot.vmp.service.ISendRtpServerService;
|
||||
import com.genersoft.iot.vmp.service.IRecordPlanService;
|
||||
import com.genersoft.iot.vmp.service.ISendRtpServerService;
|
||||
import com.genersoft.iot.vmp.service.IUserService;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
|
||||
@ -37,7 +34,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@ -112,6 +108,12 @@ public class MediaServiceImpl implements IMediaService {
|
||||
result.setEnable_audio(true);
|
||||
return result;
|
||||
}
|
||||
if ("mp4_record".equals(app) ) {
|
||||
ResultForOnPublish result = new ResultForOnPublish();
|
||||
result.setEnable_mp4(false);
|
||||
result.setEnable_audio(true);
|
||||
return result;
|
||||
}
|
||||
StreamProxy streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, stream);
|
||||
if (streamProxyItem != null) {
|
||||
ResultForOnPublish result = new ResultForOnPublish();
|
||||
@ -170,7 +172,7 @@ public class MediaServiceImpl implements IMediaService {
|
||||
inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc);
|
||||
if (inviteInfo != null) {
|
||||
result.setStream_replace(inviteInfo.getStream());
|
||||
log.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", stream, inviteInfo.getStream());
|
||||
log.info("[HOOK]推流鉴权 stream: {} 替换为 {}", stream, inviteInfo.getStream());
|
||||
stream = inviteInfo.getStream();
|
||||
}
|
||||
}
|
||||
@ -273,6 +275,8 @@ public class MediaServiceImpl implements IMediaService {
|
||||
}
|
||||
}else if ("talk".equals(app) || "broadcast".equals(app)) {
|
||||
return false;
|
||||
} else if ("mp4_record".equals(app)) {
|
||||
return true;
|
||||
} else {
|
||||
// 非国标流 推流/拉流代理
|
||||
// 拉流代理
|
||||
|
||||
@ -26,6 +26,7 @@ public interface MediaServerMapper {
|
||||
"jtt_proxy_port,"+
|
||||
"rtsp_port,"+
|
||||
"flv_port," +
|
||||
"mp4_port," +
|
||||
"flv_ssl_port," +
|
||||
"ws_flv_port," +
|
||||
"ws_flv_ssl_port," +
|
||||
@ -60,6 +61,7 @@ public interface MediaServerMapper {
|
||||
"#{jttProxyPort}, " +
|
||||
"#{rtspPort}, " +
|
||||
"#{flvPort}, " +
|
||||
"#{mp4Port}, " +
|
||||
"#{flvSSLPort}, " +
|
||||
"#{wsFlvPort}, " +
|
||||
"#{wsFlvSSLPort}, " +
|
||||
@ -97,6 +99,7 @@ public interface MediaServerMapper {
|
||||
"<if test=\"rtspPort != null\">, rtsp_port=#{rtspPort}</if>" +
|
||||
"<if test=\"rtspSSLPort != null\">, rtsp_ssl_port=#{rtspSSLPort}</if>" +
|
||||
"<if test=\"flvPort != null\">, flv_port=#{flvPort}</if>" +
|
||||
"<if test=\"mp4Port != null\">, mp4_port=#{mp4Port}</if>" +
|
||||
"<if test=\"flvSSLPort != null\">, flv_ssl_port=#{flvSSLPort}</if>" +
|
||||
"<if test=\"wsFlvPort != null\">, ws_flv_port=#{wsFlvPort}</if>" +
|
||||
"<if test=\"wsFlvSSLPort != null\">, ws_flv_ssl_port=#{wsFlvSSLPort}</if>" +
|
||||
@ -130,6 +133,7 @@ public interface MediaServerMapper {
|
||||
"<if test=\"rtspPort != null\">, rtsp_port=#{rtspPort}</if>" +
|
||||
"<if test=\"rtspSSLPort != null\">, rtsp_ssl_port=#{rtspSSLPort}</if>" +
|
||||
"<if test=\"flvPort != null\">, flv_port=#{flvPort}</if>" +
|
||||
"<if test=\"mp4Port != null\">, mp4_port=#{mp4Port}</if>" +
|
||||
"<if test=\"flvSSLPort != null\">, flv_ssl_port=#{flvSSLPort}</if>" +
|
||||
"<if test=\"wsFlvPort != null\">, ws_flv_port=#{wsFlvPort}</if>" +
|
||||
"<if test=\"wsFlvSSLPort != null\">, ws_flv_ssl_port=#{wsFlvSSLPort}</if>" +
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
package com.genersoft.iot.vmp.utils;
|
||||
|
||||
import com.genersoft.iot.vmp.media.bean.MediaServer;
|
||||
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* 云录像工具类
|
||||
*
|
||||
* @author 648540858
|
||||
*/
|
||||
@UtilityClass
|
||||
public class CloudRecordUtils {
|
||||
|
||||
|
||||
/**
|
||||
* 修复原始工具类中的格式化问题
|
||||
*
|
||||
* @param mediaServerItem 媒体服务器配置
|
||||
* @param filePath 文件路径(可能包含%等特殊字符)
|
||||
* @return 修复后的下载信息
|
||||
*/
|
||||
public static DownloadFileInfo getDownloadFilePath(MediaServer mediaServerItem, String filePath) {
|
||||
// 将filePath作为独立参数传入,避免%符号解析问题
|
||||
String pathTemplate = "%s://%s:%s/index/api/downloadFile?file_path=%s";
|
||||
|
||||
DownloadFileInfo info = new DownloadFileInfo();
|
||||
|
||||
// filePath作为第4个参数
|
||||
info.setHttpPath(String.format(pathTemplate,
|
||||
"http",
|
||||
mediaServerItem.getStreamIp(),
|
||||
mediaServerItem.getHttpPort(),
|
||||
filePath));
|
||||
|
||||
// 同样作为第4个参数
|
||||
if (mediaServerItem.getHttpSSlPort() > 0) {
|
||||
info.setHttpsPath(String.format(pathTemplate,
|
||||
"https",
|
||||
mediaServerItem.getStreamIp(),
|
||||
mediaServerItem.getHttpSSlPort(),
|
||||
filePath));
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,9 @@ import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.media.bean.MediaInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "流信息")
|
||||
public class StreamContent {
|
||||
|
||||
@ -95,6 +97,9 @@ public class StreamContent {
|
||||
@Schema(description = "结束时间")
|
||||
private String endTime;
|
||||
|
||||
@Schema(description = "时长(回放时使用)")
|
||||
private Double duration;
|
||||
|
||||
@Schema(description = "文件下载地址(录像下载使用)")
|
||||
private DownloadFileInfo downLoadFilePath;
|
||||
|
||||
@ -103,6 +108,9 @@ public class StreamContent {
|
||||
|
||||
private double progress;
|
||||
|
||||
@Schema(description = "拉流代理返回的KEY")
|
||||
private String key;
|
||||
|
||||
public StreamContent(StreamInfo streamInfo) {
|
||||
if (streamInfo == null) {
|
||||
return;
|
||||
@ -180,6 +188,8 @@ public class StreamContent {
|
||||
this.startTime = streamInfo.getStartTime();
|
||||
this.endTime = streamInfo.getEndTime();
|
||||
this.progress = streamInfo.getProgress();
|
||||
this.duration = streamInfo.getDuration();
|
||||
this.key = streamInfo.getKey();
|
||||
|
||||
if (streamInfo.getDownLoadFilePath() != null) {
|
||||
this.downLoadFilePath = streamInfo.getDownLoadFilePath();
|
||||
@ -189,259 +199,4 @@ public class StreamContent {
|
||||
}
|
||||
}
|
||||
|
||||
public StreamContent getTranscodeStream() {
|
||||
return transcodeStream;
|
||||
}
|
||||
|
||||
public void setTranscodeStream(StreamContent transcodeStream) {
|
||||
this.transcodeStream = transcodeStream;
|
||||
}
|
||||
|
||||
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 getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public String getFlv() {
|
||||
return flv;
|
||||
}
|
||||
|
||||
public void setFlv(String flv) {
|
||||
this.flv = flv;
|
||||
}
|
||||
|
||||
public String getHttps_flv() {
|
||||
return https_flv;
|
||||
}
|
||||
|
||||
public void setHttps_flv(String https_flv) {
|
||||
this.https_flv = https_flv;
|
||||
}
|
||||
|
||||
public String getWs_flv() {
|
||||
return ws_flv;
|
||||
}
|
||||
|
||||
public void setWs_flv(String ws_flv) {
|
||||
this.ws_flv = ws_flv;
|
||||
}
|
||||
|
||||
public String getWss_flv() {
|
||||
return wss_flv;
|
||||
}
|
||||
|
||||
public void setWss_flv(String wss_flv) {
|
||||
this.wss_flv = wss_flv;
|
||||
}
|
||||
|
||||
public String getFmp4() {
|
||||
return fmp4;
|
||||
}
|
||||
|
||||
public void setFmp4(String fmp4) {
|
||||
this.fmp4 = fmp4;
|
||||
}
|
||||
|
||||
public String getHttps_fmp4() {
|
||||
return https_fmp4;
|
||||
}
|
||||
|
||||
public void setHttps_fmp4(String https_fmp4) {
|
||||
this.https_fmp4 = https_fmp4;
|
||||
}
|
||||
|
||||
public String getWs_fmp4() {
|
||||
return ws_fmp4;
|
||||
}
|
||||
|
||||
public void setWs_fmp4(String ws_fmp4) {
|
||||
this.ws_fmp4 = ws_fmp4;
|
||||
}
|
||||
|
||||
public String getWss_fmp4() {
|
||||
return wss_fmp4;
|
||||
}
|
||||
|
||||
public void setWss_fmp4(String wss_fmp4) {
|
||||
this.wss_fmp4 = wss_fmp4;
|
||||
}
|
||||
|
||||
public String getHls() {
|
||||
return hls;
|
||||
}
|
||||
|
||||
public void setHls(String hls) {
|
||||
this.hls = hls;
|
||||
}
|
||||
|
||||
public String getHttps_hls() {
|
||||
return https_hls;
|
||||
}
|
||||
|
||||
public void setHttps_hls(String https_hls) {
|
||||
this.https_hls = https_hls;
|
||||
}
|
||||
|
||||
public String getWs_hls() {
|
||||
return ws_hls;
|
||||
}
|
||||
|
||||
public void setWs_hls(String ws_hls) {
|
||||
this.ws_hls = ws_hls;
|
||||
}
|
||||
|
||||
public String getWss_hls() {
|
||||
return wss_hls;
|
||||
}
|
||||
|
||||
public void setWss_hls(String wss_hls) {
|
||||
this.wss_hls = wss_hls;
|
||||
}
|
||||
|
||||
public String getTs() {
|
||||
return ts;
|
||||
}
|
||||
|
||||
public void setTs(String ts) {
|
||||
this.ts = ts;
|
||||
}
|
||||
|
||||
public String getHttps_ts() {
|
||||
return https_ts;
|
||||
}
|
||||
|
||||
public void setHttps_ts(String https_ts) {
|
||||
this.https_ts = https_ts;
|
||||
}
|
||||
|
||||
public String getWs_ts() {
|
||||
return ws_ts;
|
||||
}
|
||||
|
||||
public void setWs_ts(String ws_ts) {
|
||||
this.ws_ts = ws_ts;
|
||||
}
|
||||
|
||||
public String getWss_ts() {
|
||||
return wss_ts;
|
||||
}
|
||||
|
||||
public void setWss_ts(String wss_ts) {
|
||||
this.wss_ts = wss_ts;
|
||||
}
|
||||
|
||||
public String getRtmp() {
|
||||
return rtmp;
|
||||
}
|
||||
|
||||
public void setRtmp(String rtmp) {
|
||||
this.rtmp = rtmp;
|
||||
}
|
||||
|
||||
public String getRtmps() {
|
||||
return rtmps;
|
||||
}
|
||||
|
||||
public void setRtmps(String rtmps) {
|
||||
this.rtmps = rtmps;
|
||||
}
|
||||
|
||||
public String getRtsp() {
|
||||
return rtsp;
|
||||
}
|
||||
|
||||
public void setRtsp(String rtsp) {
|
||||
this.rtsp = rtsp;
|
||||
}
|
||||
|
||||
public String getRtsps() {
|
||||
return rtsps;
|
||||
}
|
||||
|
||||
public void setRtsps(String rtsps) {
|
||||
this.rtsps = rtsps;
|
||||
}
|
||||
|
||||
public String getRtc() {
|
||||
return rtc;
|
||||
}
|
||||
|
||||
public void setRtc(String rtc) {
|
||||
this.rtc = rtc;
|
||||
}
|
||||
|
||||
public String getRtcs() {
|
||||
return rtcs;
|
||||
}
|
||||
|
||||
public void setRtcs(String rtcs) {
|
||||
this.rtcs = rtcs;
|
||||
}
|
||||
|
||||
public String getMediaServerId() {
|
||||
return mediaServerId;
|
||||
}
|
||||
|
||||
public void setMediaServerId(String mediaServerId) {
|
||||
this.mediaServerId = mediaServerId;
|
||||
}
|
||||
|
||||
public MediaInfo getMediaInfo() {
|
||||
return mediaInfo;
|
||||
}
|
||||
|
||||
public void setMediaInfo(MediaInfo mediaInfo) {
|
||||
this.mediaInfo = mediaInfo;
|
||||
}
|
||||
|
||||
public String getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setStartTime(String startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public String getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public void setEndTime(String endTime) {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public double getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
public void setProgress(double progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
public DownloadFileInfo getDownLoadFilePath() {
|
||||
return downLoadFilePath;
|
||||
}
|
||||
|
||||
public void setDownLoadFilePath(DownloadFileInfo downLoadFilePath) {
|
||||
this.downLoadFilePath = downLoadFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,17 +253,17 @@ public class CloudRecordController {
|
||||
@Operation(summary = "加载录像文件形成播放地址")
|
||||
@Parameter(name = "app", description = "应用名", required = true)
|
||||
@Parameter(name = "stream", description = "流ID", required = true)
|
||||
@Parameter(name = "date", description = "日期, 例如 2025-04-10", required = true)
|
||||
@Parameter(name = "cloudRecordId", description = "云端录像ID", required = true)
|
||||
public DeferredResult<WVPResult<StreamContent>> loadRecord(
|
||||
HttpServletRequest request,
|
||||
@RequestParam(required = true) String app,
|
||||
@RequestParam(required = true) String stream,
|
||||
@RequestParam(required = true) String date
|
||||
@RequestParam(required = true) int cloudRecordId
|
||||
) {
|
||||
DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>();
|
||||
|
||||
result.onTimeout(()->{
|
||||
log.info("[加载录像文件超时] app={}, stream={}, date={}", app, stream, date);
|
||||
log.info("[加载录像文件超时] app={}, stream={}, cloudRecordId={}", app, stream, cloudRecordId);
|
||||
WVPResult<StreamContent> wvpResult = new WVPResult<>();
|
||||
wvpResult.setCode(ErrorCode.ERROR100.getCode());
|
||||
wvpResult.setMsg("加载录像文件超时");
|
||||
@ -304,7 +304,7 @@ public class CloudRecordController {
|
||||
result.setResult(wvpResult);
|
||||
};
|
||||
|
||||
cloudRecordService.loadRecord(app, stream, date, callback);
|
||||
cloudRecordService.loadMP4File(app, stream, cloudRecordId, callback);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -312,6 +312,7 @@ public class CloudRecordController {
|
||||
@GetMapping("/seek")
|
||||
@Operation(summary = "定位录像播放到制定位置")
|
||||
@Parameter(name = "mediaServerId", description = "使用的节点Id", required = true)
|
||||
@Parameter(name = "app", description = "应用名", required = true)
|
||||
@Parameter(name = "stream", description = "流ID", required = true)
|
||||
@Parameter(name = "seek", description = "要定位的时间位置,从录像开始的时间算起", required = true)
|
||||
public void seekRecord(
|
||||
@ -331,6 +332,7 @@ public class CloudRecordController {
|
||||
@GetMapping("/speed")
|
||||
@Operation(summary = "设置录像播放速度")
|
||||
@Parameter(name = "mediaServerId", description = "使用的节点Id", required = true)
|
||||
@Parameter(name = "app", description = "应用名", required = true)
|
||||
@Parameter(name = "stream", description = "流ID", required = true)
|
||||
@Parameter(name = "speed", description = "要设置的录像倍速", required = true)
|
||||
public void setRecordSpeed(
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
"element-ui": "^2.15.14",
|
||||
"js-cookie": "2.2.0",
|
||||
"moment": "^2.29.1",
|
||||
"moment-duration-format": "^2.3.2",
|
||||
"normalize.css": "7.0.0",
|
||||
"nprogress": "0.2.0",
|
||||
"ol": "^6.14.1",
|
||||
|
||||
File diff suppressed because one or more lines are too long
96
web/public/static/js/jessibuca/jessibuca.d.ts
vendored
96
web/public/static/js/jessibuca/jessibuca.d.ts
vendored
@ -171,7 +171,32 @@ declare namespace Jessibuca {
|
||||
* https://github.com/langhuihui/jessibuca/issues/152 解决方案
|
||||
* 例如:WebGL图像预处理默认每次取4字节的数据,但是540x960分辨率下的U、V分量宽度是540/2=270不能被4整除,导致绿屏。
|
||||
*/
|
||||
openWebglAlignment?: boolean
|
||||
openWebglAlignment?: boolean,
|
||||
|
||||
/**
|
||||
* webcodecs硬解码是否通过video标签渲染
|
||||
*/
|
||||
wcsUseVideoRender?: boolean,
|
||||
|
||||
/**
|
||||
* 底部控制台是否自动隐藏
|
||||
*/
|
||||
controlAutoHide?: boolean,
|
||||
|
||||
/**
|
||||
* 录制的视频格式
|
||||
*/
|
||||
recordType?: 'webm' | 'mp4',
|
||||
|
||||
/**
|
||||
* 是否使用web全屏(旋转90度)(只会在移动端生效)。
|
||||
*/
|
||||
useWebFullScreen?: boolean,
|
||||
|
||||
/**
|
||||
* 是否自动使用系统全屏
|
||||
*/
|
||||
autoUseSystemFullScreen?: boolean,
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,8 +247,8 @@ declare class Jessibuca {
|
||||
jessibuca.setTimeout(10)
|
||||
|
||||
jessibuca.on('timeout',function(){
|
||||
//
|
||||
});
|
||||
//
|
||||
});
|
||||
*/
|
||||
setTimeout(): void;
|
||||
|
||||
@ -249,17 +274,17 @@ declare class Jessibuca {
|
||||
* 可以在pause 之后,再调用 `play()`方法就继续播放之前的流。
|
||||
@example
|
||||
jessibuca.pause().then(()=>{
|
||||
console.log('pause success')
|
||||
console.log('pause success')
|
||||
|
||||
jessibuca.play().then(()=>{
|
||||
jessibuca.play().then(()=>{
|
||||
|
||||
}).catch((e)=>{
|
||||
}).catch((e)=>{
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
}).catch((e)=>{
|
||||
console.log('pause error',e);
|
||||
})
|
||||
}).catch((e)=>{
|
||||
console.log('pause error',e);
|
||||
})
|
||||
*/
|
||||
pause(): Promise<void>;
|
||||
|
||||
@ -289,14 +314,20 @@ declare class Jessibuca {
|
||||
@example
|
||||
|
||||
jessibuca.play('url').then(()=>{
|
||||
console.log('play success')
|
||||
}).catch((e)=>{
|
||||
console.log('play error',e)
|
||||
})
|
||||
//
|
||||
jessibuca.play()
|
||||
console.log('play success')
|
||||
}).catch((e)=>{
|
||||
console.log('play error',e)
|
||||
})
|
||||
// 添加请求头
|
||||
jessibuca.play('url',{headers:{'Authorization':'test111'}}).then(()=>{
|
||||
console.log('play success')
|
||||
}).catch((e)=>{
|
||||
console.log('play error',e)
|
||||
})
|
||||
*/
|
||||
play(url?: string): Promise<void>;
|
||||
play(url?: string, options?: {
|
||||
headers: Object
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* 重新调整视图大小
|
||||
@ -427,6 +458,21 @@ declare class Jessibuca {
|
||||
*/
|
||||
isRecording(): boolean;
|
||||
|
||||
/**
|
||||
* 切换底部控制条 隐藏/显示
|
||||
* @param isShow
|
||||
*
|
||||
* @example
|
||||
* jessibuca.toggleControlBar(true) // 显示
|
||||
* jessibuca.toggleControlBar(false) // 隐藏
|
||||
* jessibuca.toggleControlBar() // 切换 隐藏/显示
|
||||
*/
|
||||
toggleControlBar(isShow:boolean): void;
|
||||
|
||||
/**
|
||||
* 获取底部控制条是否显示
|
||||
*/
|
||||
getControlBarShow(): boolean;
|
||||
|
||||
/**
|
||||
* 监听 jessibuca 初始化事件
|
||||
@ -477,14 +523,14 @@ declare class Jessibuca {
|
||||
* 错误信息
|
||||
* @example
|
||||
* jessibuca.on("error",function(error){
|
||||
if(error === Jessibuca.ERROR.fetchError){
|
||||
//
|
||||
}
|
||||
else if(error === Jessibuca.ERROR.webcodecsH265NotSupport){
|
||||
//
|
||||
}
|
||||
console.log('error:',error)
|
||||
})
|
||||
if(error === Jessibuca.ERROR.fetchError){
|
||||
//
|
||||
}
|
||||
else if(error === Jessibuca.ERROR.webcodecsH265NotSupport){
|
||||
//
|
||||
}
|
||||
console.log('error:',error)
|
||||
})
|
||||
*/
|
||||
on(event: 'error', callback: (err: Jessibuca.ERROR) => void): void;
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -28,14 +28,14 @@ export function queryListByData(params) {
|
||||
}
|
||||
|
||||
export function loadRecord(params) {
|
||||
const { app, stream, date } = params
|
||||
const { app, stream, cloudRecordId } = params
|
||||
return request({
|
||||
method: 'get',
|
||||
url: `/api/cloud/record/loadRecord`,
|
||||
params: {
|
||||
app: app,
|
||||
stream: stream,
|
||||
date: date
|
||||
cloudRecordId: cloudRecordId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 1291092 */
|
||||
src: url('iconfont.woff2?t=1743052226670') format('woff2');
|
||||
src: url('iconfont.woff2?t=1758456390170') format('woff2')
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -11,6 +11,34 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-a-bofangqi1:before {
|
||||
content: "\ec17";
|
||||
}
|
||||
|
||||
.icon-sanjiaoxing:before {
|
||||
content: "\e7f1";
|
||||
}
|
||||
|
||||
.icon-icon_gps:before {
|
||||
content: "\e7f0";
|
||||
}
|
||||
|
||||
.icon-yidingdaoweizhuangtai:before {
|
||||
content: "\e7ef";
|
||||
}
|
||||
|
||||
.icon-gps:before {
|
||||
content: "\e8b6";
|
||||
}
|
||||
|
||||
.icon-tongdao:before {
|
||||
content: "\e7ee";
|
||||
}
|
||||
|
||||
.icon-xiazailiebiao:before {
|
||||
content: "\e7ed";
|
||||
}
|
||||
|
||||
.icon-zoom-in:before {
|
||||
content: "\e7eb";
|
||||
}
|
||||
|
||||
Binary file not shown.
425
web/src/views/cloudRecord/cloudRecordPlayer.vue
Executable file
425
web/src/views/cloudRecord/cloudRecordPlayer.vue
Executable file
@ -0,0 +1,425 @@
|
||||
<template>
|
||||
<div id="cloudRecordPlayer" style="height: 100%">
|
||||
<div class="cloud-record-playBox" :style="playBoxStyle">
|
||||
<h265web v-if="playerType === 'H265web'" ref="recordVideoPlayer" :video-url="videoUrl" :height="'calc(100% - 250px)'" :show-button="false" @playTimeChange="showPlayTimeChange" @playStatusChange="playingChange"/>
|
||||
<jessibucaPlayer
|
||||
v-if="playerType === 'Jessibuca'"
|
||||
ref="recordVideoPlayer"
|
||||
:height="'calc(100% - 250px)'"
|
||||
:show-button="false"
|
||||
:video-url="videoUrl"
|
||||
@playTimeChange="showPlayTimeChange"
|
||||
@playStatusChange="playingChange"
|
||||
fluent
|
||||
autoplay
|
||||
live
|
||||
/>
|
||||
</div>
|
||||
<div class="cloud-record-player-option-box">
|
||||
<div class="cloud-record-show-time">
|
||||
{{showPlayTimeValue}}
|
||||
</div>
|
||||
<div class="cloud-record-time-process" ref="timeProcess" @click="timeProcessClick($event)"
|
||||
@mouseenter="timeProcessMouseEnter($event)" @mousemove="timeProcessMouseMove($event)"
|
||||
@mouseleave="timeProcessMouseLeave($event)">
|
||||
<div v-if="streamInfo">
|
||||
<div class="cloud-record-time-process-value" :style="playTimeValue"></div>
|
||||
<transition name="el-fade-in-linear">
|
||||
<div v-show="showTimeLeft" class="cloud-record-time-process-title" :style="playTimeTitleStyle" >{{showPlayTimeTitle}}</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cloud-record-show-time">
|
||||
{{showPlayTimeTotal}}
|
||||
</div>
|
||||
</div>
|
||||
<div style="height: 40px; background-color: #383838; display: grid; grid-template-columns: 1fr auto 1fr">
|
||||
<div style="text-align: left;">
|
||||
<div class="cloud-record-record-play-control" style="background-color: transparent; box-shadow: 0 0 10px transparent">
|
||||
<a v-if="showListCallback" target="_blank" class="cloud-record-record-play-control-item iconfont icon-list" title="列表" @click="sidebarControl()" />
|
||||
<a target="_blank" class="cloud-record-record-play-control-item iconfont icon-camera1196054easyiconnet" title="截图" @click="snap()" />
|
||||
<!-- <a target="_blank" class="cloud-record-record-play-control-item iconfont icon-shuaxin11" title="刷新" @click="refresh()" />-->
|
||||
<!-- <a target="_blank" class="cloud-record-record-play-control-item iconfont icon-xiazai011" title="下载" />-->
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="cloud-record-record-play-control">
|
||||
<a v-if="!lastDiable" target="_blank" class="cloud-record-record-play-control-item iconfont icon-diyigeshipin" title="上一个" @click="playLast()" />
|
||||
<a v-else style="color: #acacac; cursor: not-allowed" target="_blank" class="cloud-record-record-play-control-item iconfont icon-diyigeshipin" title="上一个" />
|
||||
<a target="_blank" class="cloud-record-record-play-control-item iconfont icon-kuaijin" title="快退五秒" @click="seekBackward()" />
|
||||
<a target="_blank" class="cloud-record-record-play-control-item iconfont icon-stop1" style="font-size: 14px" title="停止" @click="stopPLay()" />
|
||||
<a v-if="playing" target="_blank" class="cloud-record-record-play-control-item iconfont icon-zanting" title="暂停" @click="pausePlay()" />
|
||||
<a v-if="!playing" target="_blank" class="cloud-record-record-play-control-item iconfont icon-kaishi" title="播放" @click="play()" />
|
||||
<a target="_blank" class="cloud-record-record-play-control-item iconfont icon-houtui" title="快进五秒" @click="seekForward()" />
|
||||
<a v-if="!nextDiable" target="_blank" class="cloud-record-record-play-control-item iconfont icon-zuihouyigeshipin" title="下一个" @click="playNext()" />
|
||||
<a v-else style="color: #acacac; cursor: not-allowed" target="_blank" class="cloud-record-record-play-control-item iconfont icon-zuihouyigeshipin" title="下一个" @click="playNext()" />
|
||||
<el-dropdown @command="changePlaySpeed" :popper-append-to-body='false' >
|
||||
<a target="_blank" class="cloud-record-record-play-control-item record-play-control-speed" title="倍速播放">{{ playSpeed }}X</a>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="item in playSpeedRange"
|
||||
:key="item"
|
||||
:command="item"
|
||||
>{{ item }}X</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div class="cloud-record-record-play-control" style="background-color: transparent; box-shadow: 0 0 10px transparent">
|
||||
<div class="cloud-record-record-play-control-item record-play-control-player">
|
||||
|
||||
<el-dropdown @command="changePlayerType" :popper-append-to-body='false' >
|
||||
<a target="_blank" class="cloud-record-record-play-control-item record-play-control-speed" title="选择播放器">{{ playerType }}</a>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="H265web" >H265web</el-dropdown-item>
|
||||
<el-dropdown-item command="Jessibuca" >Jessibuca</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<a v-if="!isFullScreen" target="_blank" class="cloud-record-record-play-control-item iconfont icon-fangdazhanshi" title="全屏" @click="fullScreen()" />
|
||||
<a v-else target="_blank" class="cloud-record-record-play-control-item iconfont icon-suoxiao1" title="全屏" @click="fullScreen()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import h265web from '../common/h265web.vue'
|
||||
import moment from 'moment'
|
||||
import momentDurationFormatSetup from 'moment-duration-format'
|
||||
import screenfull from 'screenfull'
|
||||
import jessibucaPlayer from '@/views/common/jessibuca.vue'
|
||||
|
||||
momentDurationFormatSetup(moment)
|
||||
|
||||
export default {
|
||||
name: 'CloudRecordPlayer',
|
||||
components: {
|
||||
jessibucaPlayer,
|
||||
h265web
|
||||
},
|
||||
props: ['showListCallback', 'showNextCallback', 'showLastCallback', 'lastDiable', 'nextDiable'],
|
||||
data() {
|
||||
return {
|
||||
showSidebar: false,
|
||||
videoUrl: null,
|
||||
streamInfo: null,
|
||||
timeLen: null,
|
||||
startTime: null,
|
||||
showTimeLeft: null,
|
||||
isMousedown: false,
|
||||
loading: false,
|
||||
playerTime: null,
|
||||
playSpeed: 1,
|
||||
playLoading: false,
|
||||
isFullScreen: false,
|
||||
playing: false,
|
||||
initTime: null,
|
||||
playerType: 'Jessibuca',
|
||||
playSpeedRange: [1, 2, 4, 6, 8, 16, 20]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
playBoxStyle() {
|
||||
return this.isFullScreen ? { height: 'calc(100vh - 61px)' } : { height: '100%' }
|
||||
},
|
||||
showPlayTimeValue() {
|
||||
return this.streamInfo === null ? '--:--:--' : moment.duration(this.playerTime, 'milliseconds').format('hh:mm:ss', {
|
||||
trim: false
|
||||
})
|
||||
},
|
||||
playTimeValue() {
|
||||
return { width: this.playerTime/this.streamInfo.duration * 100 + '%' }
|
||||
},
|
||||
showPlayTimeTotal() {
|
||||
if (this.streamInfo === null) {
|
||||
return '--:--:--'
|
||||
}else {
|
||||
return moment.duration(this.streamInfo.duration, 'milliseconds').format('hh:mm:ss', {
|
||||
trim: false
|
||||
})
|
||||
}
|
||||
},
|
||||
playTimeTotal() {
|
||||
return { left: `calc(${this.playerTime/this.streamInfo.duration * 100}% - 6px)` }
|
||||
},
|
||||
playTimeTitleStyle() {
|
||||
return { left: (this.showTimeLeft - 16) + 'px' }
|
||||
},
|
||||
showPlayTimeTitle() {
|
||||
if (this.showTimeLeft) {
|
||||
let time = this.showTimeLeft / this.$refs.timeProcess.clientWidth * this.streamInfo.duration
|
||||
let realTime = this.timeLen/this.streamInfo.duration * time + this.startTime
|
||||
return `${moment(time).format('mm:ss')}(${moment(realTime).format('HH:mm:ss')})`
|
||||
}else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
document.addEventListener('mousemove', this.timeProcessMousemove)
|
||||
document.addEventListener('mouseup', this.timeProcessMouseup)
|
||||
},
|
||||
mounted() {},
|
||||
destroyed() {
|
||||
this.$destroy('recordVideoPlayer')
|
||||
},
|
||||
methods: {
|
||||
changePlayer(command) {
|
||||
this.playerType = command
|
||||
},
|
||||
timeProcessMouseup(event) {
|
||||
this.isMousedown = false
|
||||
},
|
||||
timeProcessMousemove(event) {
|
||||
|
||||
},
|
||||
timeProcessClick(event) {
|
||||
let x = event.offsetX
|
||||
let clientWidth = this.$refs.timeProcess.clientWidth
|
||||
this.seekRecord(x / clientWidth * this.streamInfo.duration)
|
||||
},
|
||||
timeProcessMousedown(event) {
|
||||
this.isMousedown = true
|
||||
},
|
||||
timeProcessMouseEnter(event) {
|
||||
this.showTimeLeft = event.offsetX
|
||||
},
|
||||
timeProcessMouseMove(event) {
|
||||
this.showTimeLeft = event.offsetX
|
||||
},
|
||||
timeProcessMouseLeave(event) {
|
||||
this.showTimeLeft = null
|
||||
},
|
||||
sidebarControl() {
|
||||
this.showSidebar = !this.showSidebar
|
||||
this.showListCallback(this.showSidebar)
|
||||
},
|
||||
snap() {
|
||||
this.$refs.recordVideoPlayer.screenshot()
|
||||
},
|
||||
refresh() {
|
||||
this.$refs.recordVideoPlayer.destroy()
|
||||
this.$refs.recordVideoPlayer.playBtnClick()
|
||||
},
|
||||
playLast() {
|
||||
this.showLastCallback()
|
||||
},
|
||||
playNext() {
|
||||
this.showNextCallback()
|
||||
},
|
||||
changePlaySpeed(speed) {
|
||||
// 倍速播放
|
||||
this.playSpeed = speed
|
||||
this.$store.dispatch('cloudRecord/speed', {
|
||||
mediaServerId: this.streamInfo.mediaServerId,
|
||||
app: this.streamInfo.app,
|
||||
stream: this.streamInfo.stream,
|
||||
key: this.streamInfo.key,
|
||||
speed: this.playSpeed,
|
||||
schema: 'ts'
|
||||
})
|
||||
this.$refs.recordVideoPlayer.setPlaybackRate(this.playSpeed)
|
||||
},
|
||||
changePlayerType(playerType) {
|
||||
if (this.playerType === playerType) {
|
||||
return
|
||||
}
|
||||
let streamInfo = this.streamInfo
|
||||
let videoUrl = this.videoUrl
|
||||
this.$refs.recordVideoPlayer.destroy()
|
||||
this.seekRecord(0, () => {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.playerType = playerType
|
||||
this.playerTime = 0
|
||||
this.streamInfo = streamInfo
|
||||
this.videoUrl = videoUrl
|
||||
}, 1000)
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
},
|
||||
seekBackward() {
|
||||
// 快退五秒
|
||||
this.seekRecord(this.playerTime - 5 * 1000)
|
||||
},
|
||||
seekForward() {
|
||||
// 快进五秒
|
||||
this.seekRecord(this.playerTime + 5 * 1000)
|
||||
},
|
||||
stopPLay() {
|
||||
// 停止
|
||||
if (this.$refs.recordVideoPlayer) {
|
||||
this.$refs.recordVideoPlayer.destroy()
|
||||
}
|
||||
this.streamInfo = null
|
||||
this.playerTime = null
|
||||
this.playSpeed = 1
|
||||
},
|
||||
pausePlay() {
|
||||
// 暂停
|
||||
this.$refs.recordVideoPlayer.pause()
|
||||
// TODO
|
||||
},
|
||||
play() {
|
||||
if (this.$refs.recordVideoPlayer.loaded) {
|
||||
this.$refs.recordVideoPlayer.unPause()
|
||||
} else {
|
||||
this.playRecord()
|
||||
}
|
||||
},
|
||||
fullScreen() {
|
||||
// 全屏
|
||||
if (this.isFullScreen) {
|
||||
screenfull.exit()
|
||||
this.isFullScreen = false
|
||||
return
|
||||
}
|
||||
const playerWidth = this.$refs.recordVideoPlayer.playerWidth
|
||||
const playerHeight = this.$refs.recordVideoPlayer.playerHeight
|
||||
screenfull.request(document.getElementById('cloudRecordPlayer'))
|
||||
screenfull.on('change', (event) => {
|
||||
this.$refs.recordVideoPlayer.resize(playerWidth, playerHeight)
|
||||
this.isFullScreen = screenfull.isFullscreen
|
||||
})
|
||||
this.isFullScreen = true
|
||||
},
|
||||
setStreamInfo(streamInfo, timeLen, startTime) {
|
||||
if (location.protocol === 'https:') {
|
||||
this.videoUrl = streamInfo['wss_flv']
|
||||
} else {
|
||||
this.videoUrl = streamInfo['ws_flv']
|
||||
}
|
||||
this.streamInfo = streamInfo
|
||||
this.timeLen = timeLen
|
||||
this.startTime = startTime
|
||||
},
|
||||
seekRecord(playSeekValue, callback) {
|
||||
this.$store.dispatch('cloudRecord/seek', {
|
||||
mediaServerId: this.streamInfo.mediaServerId,
|
||||
app: this.streamInfo.app,
|
||||
stream: this.streamInfo.stream,
|
||||
seek: playSeekValue,
|
||||
schema: 'fmp4'
|
||||
})
|
||||
.then((data) => {
|
||||
this.playerTime = playSeekValue
|
||||
if (callback) {
|
||||
callback(playSeekValue)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
},
|
||||
showPlayTimeChange(val) {
|
||||
console.log(val)
|
||||
if (Number(val)) {
|
||||
this.playerTime = Number(val)
|
||||
}
|
||||
},
|
||||
playingChange(val) {
|
||||
this.playing = val
|
||||
if (!val) {
|
||||
this.stopPLay()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.cloud-record-playBox {
|
||||
width: 100%;
|
||||
background-color: #000000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.cloud-record-record-play-control {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
padding: 0 10px;
|
||||
-webkit-box-shadow: 0 0 10px #262626;
|
||||
box-shadow: 0 0 10px #262626;
|
||||
background-color: #262626;
|
||||
margin: 4px 0;
|
||||
}
|
||||
.cloud-record-record-play-control-item {
|
||||
display: inline-block;
|
||||
padding: 0 10px;
|
||||
color: #fff;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.cloud-record-record-play-control-item:hover {
|
||||
color: #1f83e6;
|
||||
}
|
||||
.cloud-record-record-play-control-speed {
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
user-select: none;
|
||||
}
|
||||
.cloud-record-player-option-box {
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 70px auto 70px;
|
||||
background-color: rgb(0, 0, 0);
|
||||
}
|
||||
.cloud-record-time-process {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
margin: 6px 0 ;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #505050;
|
||||
background-color: rgb(56, 56, 56);
|
||||
cursor: pointer;
|
||||
}
|
||||
.cloud-record-show-time {
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 20px
|
||||
}
|
||||
.cloud-record-time-process-value {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background-color: rgb(162, 162, 162);
|
||||
}
|
||||
.cloud-record-time-process-value1::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: rgb(192 190 190);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
right: -6px;
|
||||
float: right;
|
||||
}
|
||||
.cloud-record-time-process-title {
|
||||
width: fit-content;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
top: -35px;
|
||||
color: rgb(217, 217, 217);
|
||||
font-size: 14px;
|
||||
text-shadow:
|
||||
-1px -1px 0 black, /* 左上角阴影 */
|
||||
1px -1px 0 black, /* 右上角阴影 */
|
||||
-1px 1px 0 black, /* 左下角阴影 */
|
||||
1px 1px 0 black; /* 右下角阴影 */
|
||||
}
|
||||
.record-play-control-player {
|
||||
width: fit-content;
|
||||
height: 32px;
|
||||
}
|
||||
</style>
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="recordDetail" class="app-container">
|
||||
<div id="recordDetail" style="padding: 10px 20px">
|
||||
<div :style="boxStyle">
|
||||
<div>
|
||||
<div v-if="this.$route.query.mediaServerId" class="page-header-btn" style="padding-right: 1rem">
|
||||
@ -45,64 +45,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="playerBox">
|
||||
<div class="playBox" style="height: calc(100% - 90px); width: 100%; background-color: #000000">
|
||||
<div v-if="playLoading" style="position: relative; left: calc(50% - 32px); top: 43%; z-index: 100;color: #fff;float: left; text-align: center;">
|
||||
<div class="el-icon-loading" />
|
||||
<div style="width: 100%; line-height: 2rem">正在加载</div>
|
||||
</div>
|
||||
<h265web ref="recordVideoPlayer" :video-url="videoUrl" :height="'calc(100vh - 250px)'" :show-button="false" @playTimeChange="showPlayTimeChange" @playStatusChange="playingChange"/>
|
||||
</div>
|
||||
<div class="player-option-box">
|
||||
<VideoTimeline
|
||||
ref="Timeline"
|
||||
:init-time="initTime"
|
||||
:time-segments="timeSegments"
|
||||
:init-zoom-index="4"
|
||||
@timeChange="playTimeChange"
|
||||
@mousedown="timelineMouseDown"
|
||||
@mouseup="mouseupTimeline"
|
||||
/>
|
||||
<div v-if="showTime" class="time-line-show">{{ showTimeValue }}</div>
|
||||
</div>
|
||||
<div style="height: 40px; background-color: #383838; display: grid; grid-template-columns: 1fr 600px 1fr">
|
||||
<div style="text-align: left;">
|
||||
<div class="record-play-control" style="background-color: transparent; box-shadow: 0 0 10px transparent">
|
||||
<a target="_blank" class="record-play-control-item iconfont icon-list" title="列表" @click="sidebarControl()" />
|
||||
<a target="_blank" class="record-play-control-item iconfont icon-camera1196054easyiconnet" title="截图" @click="snap()" />
|
||||
<!-- <a target="_blank" class="record-play-control-item iconfont icon-xiazai011" title="下载" @click="gbPause()" />-->
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="record-play-control">
|
||||
<a v-if="chooseFileIndex > 0" target="_blank" class="record-play-control-item iconfont icon-diyigeshipin" title="上一个" @click="playLast()" />
|
||||
<a v-else style="color: #acacac; cursor: not-allowed" target="_blank" class="record-play-control-item iconfont icon-diyigeshipin" title="上一个" />
|
||||
<a target="_blank" class="record-play-control-item iconfont icon-kuaijin" title="快退五秒" @click="seekBackward()" />
|
||||
<a target="_blank" class="record-play-control-item iconfont icon-stop1" style="font-size: 14px" title="停止" @click="stopPLay()" />
|
||||
<a v-if="playing" target="_blank" class="record-play-control-item iconfont icon-zanting" title="暂停" @click="pausePlay()" />
|
||||
<a v-if="!playing" target="_blank" class="record-play-control-item iconfont icon-kaishi" title="播放" @click="play()" />
|
||||
<a target="_blank" class="record-play-control-item iconfont icon-houtui" title="快进五秒" @click="seekForward()" />
|
||||
<a v-if="chooseFileIndex < detailFiles.length - 1" target="_blank" class="record-play-control-item iconfont icon-zuihouyigeshipin" title="下一个" @click="playNext()" />
|
||||
<a v-else style="color: #acacac; cursor: not-allowed" target="_blank" class="record-play-control-item iconfont icon-zuihouyigeshipin" title="下一个" @click="playNext()" />
|
||||
<el-dropdown @command="changePlaySpeed">
|
||||
<a target="_blank" class="record-play-control-item record-play-control-speed" title="倍速播放">{{ playSpeed }}X</a>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="item in playSpeedRange"
|
||||
:key="item"
|
||||
:command="item"
|
||||
>{{ item }}X</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div class="record-play-control" style="background-color: transparent; box-shadow: 0 0 10px transparent">
|
||||
<a v-if="!isFullScreen" target="_blank" class="record-play-control-item iconfont icon-fangdazhanshi" title="全屏" @click="fullScreen()" />
|
||||
<a v-else target="_blank" class="record-play-control-item iconfont icon-suoxiao1" title="全屏" @click="fullScreen()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="playerBox" :style="playBoxStyle">
|
||||
<cloudRecordPlayer ref="cloudRecordPlayer" :showListCallback="sidebarControl" :showNextCallback="playNext"
|
||||
:showLastCallback="playLast" :lastDiable="lastBtnDiable" :nextDiable="nextBtnDiable" ></cloudRecordPlayer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -110,15 +55,17 @@
|
||||
|
||||
<script>
|
||||
|
||||
import h265web from '../common/h265web.vue'
|
||||
import VideoTimeline from '../common/VideoTimeLine/index.vue'
|
||||
import moment from 'moment'
|
||||
import momentDurationFormatSetup from 'moment-duration-format'
|
||||
import screenfull from 'screenfull'
|
||||
import cloudRecordPlayer from './cloudRecordPlayer.vue'
|
||||
|
||||
momentDurationFormatSetup(moment)
|
||||
|
||||
export default {
|
||||
name: 'CloudRecordDetail',
|
||||
components: {
|
||||
h265web, VideoTimeline
|
||||
cloudRecordPlayer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -126,14 +73,15 @@ export default {
|
||||
app: this.$route.params.app,
|
||||
stream: this.$route.params.stream,
|
||||
mediaServerId: null,
|
||||
dateFilesObj: [],
|
||||
dateFilesObj: {},
|
||||
mediaServerList: [],
|
||||
detailFiles: [],
|
||||
videoUrl: null,
|
||||
streamInfo: null,
|
||||
showTimeLeft: null,
|
||||
isMousedown: false,
|
||||
loading: false,
|
||||
chooseDate: null,
|
||||
playTime: null,
|
||||
playerTime: null,
|
||||
playSpeed: 1,
|
||||
chooseFileIndex: null,
|
||||
@ -142,15 +90,14 @@ export default {
|
||||
count: 1000000, // TODO 分页导致滑轨视频有效值无法获取完全
|
||||
total: 0,
|
||||
playLoading: false,
|
||||
showTime: true,
|
||||
isFullScreen: false,
|
||||
playSeekValue: 0,
|
||||
playing: false,
|
||||
taskTimeRange: [],
|
||||
timeFormat: '00:00:00',
|
||||
initTime: null,
|
||||
timelineControl: false,
|
||||
showOtherSpeed: true,
|
||||
lastBtnDiable: this.chooseFileIndex - 1 <= 0,
|
||||
nextBtnDiable: false,
|
||||
timeSegments: [],
|
||||
pickerOptions: {
|
||||
cellClassName: (date) => {
|
||||
@ -167,6 +114,11 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
playBoxStyle() {
|
||||
return {
|
||||
height: this.isFullScreen ? 'calc(100vh - 61px)' : 'calc(100vh - 164px)'
|
||||
}
|
||||
},
|
||||
boxStyle() {
|
||||
if (this.showSidebar) {
|
||||
return {
|
||||
@ -178,11 +130,12 @@ export default {
|
||||
display: 'grid', gridTemplateColumns: '0 minmax(0, 1fr)'
|
||||
}
|
||||
}
|
||||
},
|
||||
showTimeValue() {
|
||||
return moment(this.playTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
},
|
||||
created() {
|
||||
document.addEventListener('mousemove', this.timeProcessMousemove)
|
||||
document.addEventListener('mouseup', this.timeProcessMouseup)
|
||||
},
|
||||
mounted() {
|
||||
// 查询当年有视频的日期
|
||||
this.getDateInYear(() => {
|
||||
@ -196,8 +149,14 @@ export default {
|
||||
this.$destroy('recordVideoPlayer')
|
||||
},
|
||||
methods: {
|
||||
sidebarControl() {
|
||||
this.showSidebar = !this.showSidebar
|
||||
timeProcessMouseup(event) {
|
||||
this.isMousedown = false
|
||||
},
|
||||
timeProcessMousemove(event) {
|
||||
|
||||
},
|
||||
sidebarControl(status) {
|
||||
this.showSidebar = status
|
||||
},
|
||||
snap() {
|
||||
this.$refs.recordVideoPlayer.screenshot()
|
||||
@ -212,40 +171,18 @@ export default {
|
||||
playNext() {
|
||||
// 播放上一个
|
||||
if (this.chooseFileIndex === this.detailFiles.length - 1) {
|
||||
this.nextBtnDiable = true
|
||||
return
|
||||
}
|
||||
if (this.chooseFileIndex < Object.values(this.dateFilesObj).length - 1) {
|
||||
this.nextBtnDiable = true
|
||||
}
|
||||
this.chooseFile(this.chooseFileIndex + 1)
|
||||
},
|
||||
changePlaySpeed(speed) {
|
||||
console.log(speed)
|
||||
// 倍速播放
|
||||
this.playSpeed = speed
|
||||
this.$store.dispatch('cloudRecord/speed', {
|
||||
mediaServerId: this.streamInfo.mediaServerId,
|
||||
app: this.streamInfo.app,
|
||||
stream: this.streamInfo.stream,
|
||||
speed: this.playSpeed,
|
||||
schema: 'ts'
|
||||
})
|
||||
this.$refs.recordVideoPlayer.setPlaybackRate(this.playSpeed)
|
||||
},
|
||||
seekBackward() {
|
||||
// 快退五秒
|
||||
this.playSeekValue -= 5 * 1000
|
||||
this.playRecord()
|
||||
},
|
||||
seekForward() {
|
||||
// 快进五秒
|
||||
this.playSeekValue += 5 * 1000
|
||||
this.playRecord()
|
||||
|
||||
},
|
||||
stopPLay() {
|
||||
// 停止
|
||||
this.$refs.recordVideoPlayer.destroy()
|
||||
},
|
||||
pausePlay() {
|
||||
// 暂停
|
||||
this.$refs.recordVideoPlayer.pause()
|
||||
this.$refs.cloudRecordPlayer.stopPLay()
|
||||
},
|
||||
play() {
|
||||
if (this.$refs.recordVideoPlayer.loaded) {
|
||||
@ -271,9 +208,12 @@ export default {
|
||||
this.isFullScreen = true
|
||||
},
|
||||
dateChange() {
|
||||
this.$refs.cloudRecordPlayer.stopPLay()
|
||||
this.streamInfo = null
|
||||
this.chooseFileIndex = null
|
||||
this.detailFiles = []
|
||||
this.currentPage = 1
|
||||
const chooseFullDate = new Date(this.chooseDate + ' ' + this.timeFormat)
|
||||
const chooseFullDate = new Date(this.chooseDate + ' 00:00:00')
|
||||
if (chooseFullDate.getFullYear() !== this.queryDate.getFullYear() ||
|
||||
chooseFullDate.getMonth() !== this.queryDate.getMonth()) {
|
||||
this.queryDate = chooseFullDate
|
||||
@ -330,32 +270,18 @@ export default {
|
||||
},
|
||||
chooseFile(index) {
|
||||
this.chooseFileIndex = index
|
||||
let timeLength = 0
|
||||
for (let i = 0; i < this.detailFiles.length; i++) {
|
||||
if (i < index) {
|
||||
timeLength += this.detailFiles[i].timeLen
|
||||
}
|
||||
}
|
||||
this.playSeekValue = timeLength
|
||||
this.playRecord()
|
||||
},
|
||||
playRecord() {
|
||||
if (!this.$refs.recordVideoPlayer.playing) {
|
||||
this.$refs.recordVideoPlayer.destroy()
|
||||
}
|
||||
this.$refs.cloudRecordPlayer.stopPLay()
|
||||
this.$store.dispatch('cloudRecord/loadRecord', {
|
||||
app: this.app,
|
||||
stream: this.stream,
|
||||
date: this.chooseDate
|
||||
cloudRecordId: this.detailFiles[this.chooseFileIndex].id
|
||||
})
|
||||
.then(data => {
|
||||
this.streamInfo = data
|
||||
if (location.protocol === 'https:') {
|
||||
this.videoUrl = data['https_fmp4'] + '&time=' + new Date().getTime()
|
||||
} else {
|
||||
this.videoUrl = data['fmp4'] + '&time=' + new Date().getTime()
|
||||
}
|
||||
this.seekRecord()
|
||||
this.playerTime = 0
|
||||
this.$refs.cloudRecordPlayer.setStreamInfo(data, this.detailFiles[this.chooseFileIndex].timeLen, this.detailFiles[this.chooseFileIndex].startTime)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
@ -363,18 +289,7 @@ export default {
|
||||
.finally(() => {
|
||||
this.playLoading = false
|
||||
})
|
||||
},
|
||||
seekRecord() {
|
||||
this.$store.dispatch('cloudRecord/seek', {
|
||||
mediaServerId: this.streamInfo.mediaServerId,
|
||||
app: this.streamInfo.app,
|
||||
stream: this.streamInfo.stream,
|
||||
seek: this.playSeekValue,
|
||||
schema: 'fmp4'
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
},
|
||||
downloadFile(file) {
|
||||
this.$store.dispatch('cloudRecord/getPlayPath', file.id)
|
||||
@ -392,84 +307,9 @@ export default {
|
||||
console.log(error)
|
||||
})
|
||||
},
|
||||
backToList() {
|
||||
this.$router.back()
|
||||
},
|
||||
getFileShowName(item) {
|
||||
return moment(item.startTime).format('HH:mm:ss') + '-' + moment(item.endTime).format('HH:mm:ss')
|
||||
},
|
||||
|
||||
showPlayTimeChange(val) {
|
||||
this.playTime += (val * 1000 - this.playerTime)
|
||||
this.playerTime = val * 1000
|
||||
},
|
||||
playingChange(val) {
|
||||
this.playing = val
|
||||
},
|
||||
playTimeChange(val) {
|
||||
if (val === this.playTime) {
|
||||
return
|
||||
}
|
||||
this.playTime = val
|
||||
},
|
||||
timelineMouseDown() {
|
||||
this.timelineControl = true
|
||||
},
|
||||
mouseupTimeline(event) {
|
||||
if (!this.timelineControl) {
|
||||
this.timelineControl = false
|
||||
return
|
||||
}
|
||||
this.timelineControl = false
|
||||
let timeLength = 0
|
||||
for (let i = 0; i < this.detailFiles.length; i++) {
|
||||
const item = this.detailFiles[i]
|
||||
if (this.playTime > item.endTime) {
|
||||
timeLength += item.timeLen
|
||||
} else if (this.playTime === item.endTime) {
|
||||
timeLength += item.timeLen
|
||||
this.chooseFileIndex = i
|
||||
break
|
||||
} else if (this.playTime > item.startTime && this.playTime < item.endTime) {
|
||||
timeLength += (this.playTime - item.startTime)
|
||||
this.chooseFileIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
this.playSeekValue = timeLength
|
||||
this.playRecord()
|
||||
},
|
||||
getTimeForFile(file) {
|
||||
const starTime = new Date(file.startTime * 1000)
|
||||
let endTime = new Date(file.endTime * 1000)
|
||||
if (this.checkIsOver24h(starTime, endTime)) {
|
||||
endTime = new Date(this.chooseDate + ' ' + '23:59:59')
|
||||
}
|
||||
return [starTime, endTime, endTime.getTime() - starTime.getTime()]
|
||||
},
|
||||
checkIsOver24h(starTime, endTime) {
|
||||
return starTime > endTime
|
||||
},
|
||||
playTimeFormat(val) {
|
||||
const h = parseInt(val / 3600)
|
||||
const m = parseInt((val - h * 3600) / 60)
|
||||
const s = parseInt(val - h * 3600 - m * 60)
|
||||
|
||||
let hStr = h
|
||||
let mStr = m
|
||||
let sStr = s
|
||||
if (h < 10) {
|
||||
hStr = '0' + hStr
|
||||
}
|
||||
if (m < 10) {
|
||||
mStr = '0' + mStr
|
||||
s
|
||||
}
|
||||
if (s < 10) {
|
||||
sStr = '0' + sStr
|
||||
}
|
||||
return hStr + ':' + mStr + ':' + sStr
|
||||
},
|
||||
getDateInYear(callback) {
|
||||
this.dateFilesObj = {}
|
||||
this.$store.dispatch('cloudRecord/queryListByData', {
|
||||
@ -492,20 +332,15 @@ export default {
|
||||
console.log(error)
|
||||
})
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/cloudRecord')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
<style scoped>
|
||||
.record-list-box-box {
|
||||
width: fit-content;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.record-list-box {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
@ -564,13 +399,55 @@ export default {
|
||||
user-select: none;
|
||||
}
|
||||
.player-option-box {
|
||||
height: 50px
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 70px auto 70px;
|
||||
background-color: rgb(0, 0, 0);
|
||||
}
|
||||
.time-line-show {
|
||||
.cloud-record-time-process {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
margin: 6px 0 ;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #505050;
|
||||
background-color: rgb(56, 56, 56);
|
||||
cursor: pointer;
|
||||
}
|
||||
.cloud-record-show-time {
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 20px
|
||||
}
|
||||
.cloud-record-time-process-value {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background-color: rgb(162, 162, 162);
|
||||
}
|
||||
.cloud-record-time-process-value::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: rgb(192 190 190);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
color: rgba(250, 249, 249, 0.89);
|
||||
left: calc(50% - 85px);
|
||||
top: -72px;
|
||||
text-shadow: 1px 0 #5f6b7c, -1px 0 #5f6b7c, 0 1px #5f6b7c, 0 -1px #5f6b7c, 1.1px 1.1px #5f6b7c, 1.1px -1.1px #5f6b7c, -1.1px 1.1px #5f6b7c, -1.1px -1.1px #5f6b7c;
|
||||
top: -3px;
|
||||
right: -6px;
|
||||
float: right;
|
||||
}
|
||||
.cloud-record-time-process-title {
|
||||
width: fit-content;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
top: -35px;
|
||||
color: rgb(217, 217, 217);
|
||||
font-size: 14px;
|
||||
text-shadow:
|
||||
-1px -1px 0 black, /* 左上角阴影 */
|
||||
1px -1px 0 black, /* 右上角阴影 */
|
||||
-1px 1px 0 black, /* 左下角阴影 */
|
||||
1px 1px 0 black; /* 右下角阴影 */
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -117,32 +117,23 @@
|
||||
@current-change="currentChange"
|
||||
/>
|
||||
</div>
|
||||
<el-dialog
|
||||
:title="playerTitle"
|
||||
:visible.sync="showPlayer"
|
||||
top="2rem"
|
||||
width="1200px"
|
||||
height="560px"
|
||||
>
|
||||
<h265web ref="recordVideoPlayer" :video-url="videoUrl" :height="false" :show-button="true" />
|
||||
</el-dialog>
|
||||
<playerDialog ref="playerDialog"></playerDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import h265web from '../common/h265web.vue'
|
||||
import playerDialog from './playerDialog.vue'
|
||||
import moment from 'moment'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'CloudRecord',
|
||||
components: { h265web },
|
||||
components: { playerDialog },
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
showPlayer: false,
|
||||
playerTitle: '',
|
||||
videoUrl: '',
|
||||
mediaServerList: [], // 滅体节点列表
|
||||
@ -216,20 +207,22 @@ export default {
|
||||
})
|
||||
},
|
||||
play(row) {
|
||||
console.log(row)
|
||||
this.chooseRecord = row
|
||||
this.$store.dispatch('cloudRecord/getPlayPath', row.id)
|
||||
.then((data) => {
|
||||
if (location.protocol === 'https:') {
|
||||
this.videoUrl = data.httpsPath
|
||||
} else {
|
||||
this.videoUrl = data.httpPath
|
||||
}
|
||||
this.showPlayer = true
|
||||
this.$refs.playerDialog.stopPlay()
|
||||
this.$store.dispatch('cloudRecord/loadRecord', {
|
||||
app: row.app,
|
||||
stream: row.stream,
|
||||
cloudRecordId: row.id
|
||||
})
|
||||
.then(data => {
|
||||
this.$refs.playerDialog.openDialog(data, row.timeLen, row.startTime)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
.finally(() => {
|
||||
this.playLoading = false
|
||||
})
|
||||
},
|
||||
downloadFile(row) {
|
||||
this.$store.dispatch('cloudRecord/getPlayPath', row.id)
|
||||
@ -237,9 +230,27 @@ export default {
|
||||
const link = document.createElement('a')
|
||||
link.target = '_blank'
|
||||
if (location.protocol === 'https:') {
|
||||
link.href = data.httpsPath + '&save_name=' + row.fileName
|
||||
if (data.httpsPath) {
|
||||
link.href = data.httpsPath + '&save_name=' + row.fileName
|
||||
}else if (data.httpPath){
|
||||
link.href = data.httpPath + '&save_name=' + row.fileName
|
||||
}else {
|
||||
this.$message.error({
|
||||
showClose: true,
|
||||
message: '获取下载地址失败'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
link.href = data.httpPath + '&save_name=' + row.fileName
|
||||
if (data.httpPath) {
|
||||
link.href = data.httpPath + '&save_name=' + row.fileName
|
||||
}else if (data.httpsPath){
|
||||
link.href = data.httpsPath + '&save_name=' + row.fileName
|
||||
}else {
|
||||
this.$message.error({
|
||||
showClose: true,
|
||||
message: '获取下载地址失败'
|
||||
})
|
||||
}
|
||||
}
|
||||
link.click()
|
||||
})
|
||||
@ -310,6 +321,6 @@ export default {
|
||||
|
||||
<style>
|
||||
.el-dialog__body {
|
||||
padding: 30px 0 !important;
|
||||
padding: 20px 0 0 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
61
web/src/views/cloudRecord/playerDialog.vue
Normal file
61
web/src/views/cloudRecord/playerDialog.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div id="playerDialog" >
|
||||
<el-dialog
|
||||
v-el-drag-dialog
|
||||
top="2rem"
|
||||
width="800px"
|
||||
height="450px"
|
||||
:append-to-body="false"
|
||||
:modal-append-to-body="false"
|
||||
:modal="false"
|
||||
:close-on-click-modal="false"
|
||||
:visible.sync="showDialog"
|
||||
:destroy-on-close="true"
|
||||
@close="close()"
|
||||
>
|
||||
<cloudRecordPlayer style="height: 450px" ref="cloudRecordPlayer" ></cloudRecordPlayer>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import elDragDialog from '@/directive/el-drag-dialog'
|
||||
import cloudRecordPlayer from './cloudRecordPlayer.vue'
|
||||
|
||||
export default {
|
||||
name: 'PlayerDialog',
|
||||
components: { cloudRecordPlayer },
|
||||
directives: { elDragDialog },
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
showDialog: false,
|
||||
streamInfo: null
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
created() {},
|
||||
methods: {
|
||||
openDialog: function(streamInfo, timeLen, startTime) {
|
||||
console.log(streamInfo)
|
||||
this.showDialog = true
|
||||
this.streamInfo = streamInfo
|
||||
this.$nextTick(() => {
|
||||
this.$refs.cloudRecordPlayer.setStreamInfo(streamInfo, timeLen, startTime)
|
||||
})
|
||||
},
|
||||
stopPlay: function() {
|
||||
if (this.$refs.cloudRecordPlayer) {
|
||||
this.$refs.cloudRecordPlayer.stopPLay()
|
||||
}
|
||||
},
|
||||
close: function() {
|
||||
if (this.$refs.cloudRecordPlayer) {
|
||||
this.$refs.cloudRecordPlayer.stopPLay()
|
||||
}
|
||||
this.showDialog = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,10 +1,12 @@
|
||||
<template>
|
||||
<div id="h265Player" ref="container" style="background-color: #000000; " @dblclick="fullscreenSwich">
|
||||
<div id="glplayer" ref="playerBox" style="width: 100%; height: 100%; margin: 0 auto;" />
|
||||
<div v-if="playerLoading" class="player-loading">
|
||||
<i class="el-icon-loading" />
|
||||
<span>视频加载中</span>
|
||||
<div id="glplayer" ref="playerBox" style="width: 100%; height: 100%; margin: 0 auto;" >
|
||||
<div v-if="playerLoading" class="play-loading">
|
||||
<i class="el-icon-loading" />
|
||||
视频加载中
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showButton" id="buttonsBox" class="buttons-box">
|
||||
<div class="buttons-box-left">
|
||||
<i v-if="!playing" class="iconfont icon-play h265web-btn" @click="unPause" />
|
||||
@ -142,7 +144,7 @@ export default {
|
||||
extInfo: {
|
||||
coreProbePart: 0.4,
|
||||
probeSize: 8192,
|
||||
ignoreAudio: this.hasAudio == null ? 0 : (this.hasAudio ? 0 : 1)
|
||||
ignoreAudio: this.hasAudio === null ? 0 : (this.hasAudio ? 0 : 1)
|
||||
}
|
||||
},
|
||||
options
|
||||
@ -167,7 +169,7 @@ export default {
|
||||
this.mediaInfo = h265web.mediaInfo()
|
||||
}
|
||||
h265web.onPlayTime = (videoPTS) => {
|
||||
this.$emit('playTimeChange', videoPTS)
|
||||
this.$emit('playTimeChange', videoPTS * 1000)
|
||||
}
|
||||
h265web.do()
|
||||
},
|
||||
@ -189,6 +191,9 @@ export default {
|
||||
playBtnClick: function(event) {
|
||||
this.play(this.videoUrl)
|
||||
},
|
||||
refresh: function() {
|
||||
this.play(this.videoUrl)
|
||||
},
|
||||
play: function(url) {
|
||||
if (h265webPlayer[this._uid]) {
|
||||
this.destroy()
|
||||
@ -262,6 +267,16 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.play-loading {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: rgb(255, 255, 255);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
.buttons-box {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
@dblclick="fullscreenSwich"
|
||||
>
|
||||
<div style="width:100%; padding-top: 56.25%; position: relative;" />
|
||||
<div id="buttonsBox" class="buttons-box">
|
||||
<div id="buttonsBox" class="buttons-box" v-if="typeof showButton == 'undefined' || showButton">
|
||||
<div class="buttons-box-left">
|
||||
<i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick" />
|
||||
<i v-if="playing" class="iconfont icon-pause jessibuca-btn" @click="pause" />
|
||||
@ -34,11 +34,11 @@
|
||||
const jessibucaPlayer = {}
|
||||
export default {
|
||||
name: 'Jessibuca',
|
||||
props: ['videoUrl', 'error', 'hasAudio', 'height'],
|
||||
props: ['videoUrl', 'error', 'hasAudio', 'height', 'showButton'],
|
||||
data() {
|
||||
return {
|
||||
playing: false,
|
||||
isNotMute: false,
|
||||
isNotMute: true,
|
||||
quieting: false,
|
||||
fullscreen: false,
|
||||
loaded: false, // mute
|
||||
@ -48,6 +48,7 @@ export default {
|
||||
btnDom: null,
|
||||
videoInfo: null,
|
||||
volume: 1,
|
||||
playerTime: 0,
|
||||
rotate: 0,
|
||||
vod: true, // 点播
|
||||
forceNoOffscreen: false
|
||||
@ -66,81 +67,57 @@ export default {
|
||||
created() {
|
||||
const paramUrl = decodeURIComponent(this.$route.params.url)
|
||||
this.$nextTick(() => {
|
||||
this.updatePlayerDomSize()
|
||||
window.onresize = this.updatePlayerDomSize
|
||||
if (typeof (this.videoUrl) === 'undefined') {
|
||||
this.videoUrl = paramUrl
|
||||
}
|
||||
this.btnDom = document.getElementById('buttonsBox')
|
||||
})
|
||||
},
|
||||
// mounted() {
|
||||
// const ro = new ResizeObserver(entries => {
|
||||
// entries.forEach(entry => {
|
||||
// this.updatePlayerDomSize()
|
||||
// });
|
||||
// });
|
||||
// ro.observe(this.$refs.container);
|
||||
// },
|
||||
mounted() {
|
||||
this.updatePlayerDomSize()
|
||||
},
|
||||
mounted() {},
|
||||
destroyed() {
|
||||
if (jessibucaPlayer[this._uid]) {
|
||||
jessibucaPlayer[this._uid].videoPTS = 0
|
||||
jessibucaPlayer[this._uid].destroy()
|
||||
}
|
||||
this.playing = false
|
||||
this.loaded = false
|
||||
this.performance = ''
|
||||
this.playerTime = 0
|
||||
},
|
||||
methods: {
|
||||
updatePlayerDomSize() {
|
||||
const dom = this.$refs.container
|
||||
if (!this.parentNodeResizeObserver) {
|
||||
this.parentNodeResizeObserver = new ResizeObserver(entries => {
|
||||
this.updatePlayerDomSize()
|
||||
})
|
||||
this.parentNodeResizeObserver.observe(dom.parentNode)
|
||||
}
|
||||
const boxWidth = dom.parentNode.clientWidth
|
||||
const boxHeight = dom.parentNode.clientHeight
|
||||
let width = boxWidth
|
||||
let height = (9 / 16) * width
|
||||
if (boxHeight > 0 && boxWidth > boxHeight / 9 * 16) {
|
||||
height = boxHeight
|
||||
width = boxHeight / 9 * 16
|
||||
}
|
||||
|
||||
const clientHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight)
|
||||
if (height > clientHeight) {
|
||||
height = clientHeight
|
||||
width = (16 / 9) * height
|
||||
}
|
||||
this.playerWidth = width
|
||||
this.playerHeight = height
|
||||
if (this.playing) {
|
||||
jessibucaPlayer[this._uid].resize(this.playerWidth, this.playerHeight)
|
||||
}
|
||||
},
|
||||
create() {
|
||||
if (jessibucaPlayer[this._uid]) {
|
||||
jessibucaPlayer[this._uid].destroy()
|
||||
}
|
||||
this.$refs.container.dataset['jessibuca'] = undefined
|
||||
if (this.$refs.container.getAttribute('data-jessibuca')) {
|
||||
this.$refs.container.removeAttribute('data-jessibuca')
|
||||
}
|
||||
const options = {
|
||||
container: this.$refs.container,
|
||||
autoWasm: true,
|
||||
background: '',
|
||||
videoBuffer: 0,
|
||||
isResize: false,
|
||||
useMSE: true,
|
||||
useWCS: true,
|
||||
text: '',
|
||||
// background: '',
|
||||
controlAutoHide: false,
|
||||
debug: false,
|
||||
decoder: 'static/js/jessibuca/decoder.js',
|
||||
forceNoOffscreen: false,
|
||||
hotKey: true,
|
||||
decoder: '/static/js/jessibuca/decoder.js',
|
||||
sNotMute: true,
|
||||
timeout: 10,
|
||||
recordType: 'mp4',
|
||||
isFlv: false,
|
||||
forceNoOffscreen: true,
|
||||
hasAudio: typeof (this.hasAudio) === 'undefined' ? true : this.hasAudio,
|
||||
heartTimeout: 5,
|
||||
heartTimeoutReplay: true,
|
||||
heartTimeoutReplayTimes: 3,
|
||||
hiddenAutoPause: false,
|
||||
hotKey: true,
|
||||
isFlv: false,
|
||||
isFullResize: false,
|
||||
|
||||
isNotMute: this.isNotMute,
|
||||
isResize: true,
|
||||
keepScreenOn: true,
|
||||
loadingText: '请稍等, 视频加载中......',
|
||||
loadingTimeout: 10,
|
||||
@ -152,38 +129,37 @@ export default {
|
||||
screenshot: false,
|
||||
play: false,
|
||||
audio: false,
|
||||
record: false
|
||||
recorder: false
|
||||
},
|
||||
recordType: 'mp4',
|
||||
rotate: 0,
|
||||
// rotate: 0,
|
||||
showBandwidth: false,
|
||||
supportDblclickFullscreen: false,
|
||||
timeout: 10,
|
||||
useMSE: true,
|
||||
useWCS: false,
|
||||
useWebFullScreen: true,
|
||||
videoBuffer: 0.1,
|
||||
|
||||
useWebFullSreen: true,
|
||||
|
||||
wasmDecodeErrorReplay: true,
|
||||
wcsUseVideoRender: true
|
||||
wcsUseVideoRendcer: true
|
||||
}
|
||||
console.log('Jessibuca -> options: ', options)
|
||||
jessibucaPlayer[this._uid] = new window.Jessibuca({ ...options })
|
||||
jessibucaPlayer[this._uid] = new window.Jessibuca(options)
|
||||
|
||||
const jessibuca = jessibucaPlayer[this._uid]
|
||||
const _this = this
|
||||
jessibuca.on('pause', function() {
|
||||
jessibuca.on('pause', () => {
|
||||
_this.playing = false
|
||||
this.$emit('playStatusChange', false)
|
||||
})
|
||||
jessibuca.on('play', function() {
|
||||
jessibuca.on('play', () => {
|
||||
_this.playing = true
|
||||
this.$emit('playStatusChange', true)
|
||||
})
|
||||
jessibuca.on('fullscreen', function(msg) {
|
||||
jessibuca.on('fullscreen', (msg) => {
|
||||
_this.fullscreen = msg
|
||||
})
|
||||
jessibuca.on('mute', function(msg) {
|
||||
jessibuca.on('mute', (msg) => {
|
||||
_this.isNotMute = !msg
|
||||
})
|
||||
jessibuca.on('performance', function(performance) {
|
||||
jessibuca.on('performance', (performance) => {
|
||||
let show = '卡顿'
|
||||
if (performance === 2) {
|
||||
show = '非常流畅'
|
||||
@ -192,30 +168,38 @@ export default {
|
||||
}
|
||||
_this.performance = show
|
||||
})
|
||||
jessibuca.on('kBps', function(kBps) {
|
||||
jessibuca.on('kBps', (kBps) => {
|
||||
_this.kBps = Math.round(kBps)
|
||||
})
|
||||
jessibuca.on('videoInfo', function(msg) {
|
||||
jessibuca.on('videoInfo', (msg) => {
|
||||
console.log('Jessibuca -> videoInfo: ', msg)
|
||||
})
|
||||
jessibuca.on('audioInfo', function(msg) {
|
||||
jessibuca.on('audioInfo', (msg) => {
|
||||
console.log('Jessibuca -> audioInfo: ', msg)
|
||||
})
|
||||
jessibuca.on('error', function(msg) {
|
||||
jessibuca.on('error', (msg) => {
|
||||
console.log('Jessibuca -> error: ', msg)
|
||||
})
|
||||
jessibuca.on('timeout', function(msg) {
|
||||
jessibuca.on('timeout', (msg) => {
|
||||
console.log('Jessibuca -> timeout: ', msg)
|
||||
})
|
||||
jessibuca.on('loadingTimeout', function(msg) {
|
||||
jessibuca.on('loadingTimeout', (msg) => {
|
||||
console.log('Jessibuca -> timeout: ', msg)
|
||||
})
|
||||
jessibuca.on('delayTimeout', function(msg) {
|
||||
jessibuca.on('delayTimeout', (msg) => {
|
||||
console.log('Jessibuca -> timeout: ', msg)
|
||||
})
|
||||
jessibuca.on('playToRenderTimes', function(msg) {
|
||||
jessibuca.on('playToRenderTimes', (msg) => {
|
||||
console.log('Jessibuca -> playToRenderTimes: ', msg)
|
||||
})
|
||||
jessibuca.on('timeUpdate', (videoPTS) => {
|
||||
console.log(videoPTS)
|
||||
if (jessibuca.videoPTS) {
|
||||
this.playerTime += (videoPTS - jessibuca.videoPTS)
|
||||
this.$emit('playTimeChange', this.playerTime)
|
||||
}
|
||||
jessibuca.videoPTS = videoPTS
|
||||
})
|
||||
},
|
||||
playBtnClick: function(event) {
|
||||
this.play(this.videoUrl)
|
||||
@ -266,9 +250,9 @@ export default {
|
||||
if (jessibucaPlayer[this._uid]) {
|
||||
jessibucaPlayer[this._uid].destroy()
|
||||
}
|
||||
if (document.getElementById('buttonsBox') == null) {
|
||||
this.$refs.container.appendChild(this.btnDom)
|
||||
}
|
||||
// if (document.getElementById('buttonsBox') === null && (typeof this.showButton === 'undefined' || this.showButton)) {
|
||||
// this.$refs.container.appendChild(this.btnDom)
|
||||
// }
|
||||
jessibucaPlayer[this._uid] = null
|
||||
this.playing = false
|
||||
this.err = ''
|
||||
@ -284,6 +268,9 @@ export default {
|
||||
document.msFullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement || false
|
||||
},
|
||||
setPlaybackRate: function() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="playerBox">
|
||||
<div id="playerBox" style="width: 100%">
|
||||
<div class="playBox" style="height: calc(100% - 90px); width: 100%; background-color: #000000">
|
||||
<div
|
||||
v-if="playLoading"
|
||||
|
||||
@ -13,10 +13,10 @@
|
||||
<div id="shared" style="margin-right: 20px;">
|
||||
<el-form ref="passwordForm" :rules="rules" status-icon label-width="80px">
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="newPassword" autocomplete="off" />
|
||||
<el-input v-model="newPassword" autocomplete="off" type="password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="confirmPassword" autocomplete="off" />
|
||||
<el-input v-model="confirmPassword" autocomplete="off" type="password" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
@ -88,6 +88,14 @@ export default {
|
||||
}
|
||||
},
|
||||
onSubmit: function() {
|
||||
if (this.newPassword !== this.confirmPassword) {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '两次输入密码不一致!',
|
||||
type: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
this.$store.dispatch('user/changePasswordForAdmin', {
|
||||
password: this.newPassword,
|
||||
userId: this.form.id
|
||||
|
||||
@ -7,11 +7,12 @@
|
||||
title="视频播放"
|
||||
top="0"
|
||||
append-to-body
|
||||
width="40vw"
|
||||
:close-on-click-modal="false"
|
||||
:visible.sync="showVideoDialog"
|
||||
@close="close()"
|
||||
>
|
||||
<div style="width: 100%; height: 100%">
|
||||
<div style="width: 100%; ">
|
||||
<el-tabs
|
||||
v-if="Object.keys(this.player).length > 1"
|
||||
v-model="activePlayer"
|
||||
@ -20,18 +21,21 @@
|
||||
@tab-click="changePlayer"
|
||||
>
|
||||
<el-tab-pane label="Jessibuca" name="jessibuca">
|
||||
<jessibucaPlayer
|
||||
v-if="activePlayer === 'jessibuca'"
|
||||
ref="jessibuca"
|
||||
:visible.sync="showVideoDialog"
|
||||
:video-url="videoUrl"
|
||||
:error="videoError"
|
||||
:message="videoError"
|
||||
:has-audio="hasAudio"
|
||||
fluent
|
||||
autoplay
|
||||
live
|
||||
/>
|
||||
<div style="height: 22.5vw">
|
||||
<jessibucaPlayer
|
||||
v-if="activePlayer === 'jessibuca'"
|
||||
ref="jessibuca"
|
||||
:visible.sync="showVideoDialog"
|
||||
:video-url="videoUrl"
|
||||
:error="videoError"
|
||||
:message="videoError"
|
||||
:has-audio="hasAudio"
|
||||
fluent
|
||||
autoplay
|
||||
live
|
||||
/>
|
||||
</div>
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="WebRTC" name="webRTC">
|
||||
<rtc-player
|
||||
@ -64,44 +68,6 @@
|
||||
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<jessibucaPlayer
|
||||
v-if="Object.keys(this.player).length == 1 && this.player.jessibuca"
|
||||
ref="jessibuca"
|
||||
:visible.sync="showVideoDialog"
|
||||
:video-url="videoUrl"
|
||||
:error="videoError"
|
||||
:message="videoError"
|
||||
:has-audio="hasAudio"
|
||||
fluent
|
||||
autoplay
|
||||
live
|
||||
/>
|
||||
<rtc-player
|
||||
v-if="Object.keys(this.player).length == 1 && this.player.webRTC"
|
||||
ref="jessibuca"
|
||||
:visible.sync="showVideoDialog"
|
||||
:video-url="videoUrl"
|
||||
:error="videoError"
|
||||
:message="videoError"
|
||||
height="100px"
|
||||
:has-audio="hasAudio"
|
||||
fluent
|
||||
autoplay
|
||||
live
|
||||
/>
|
||||
<h265web
|
||||
v-if="Object.keys(this.player).length == 1 && this.player.h265web"
|
||||
ref="jessibuca"
|
||||
:visible.sync="showVideoDialog"
|
||||
:video-url="videoUrl"
|
||||
:error="videoError"
|
||||
:message="videoError"
|
||||
height="100px"
|
||||
:has-audio="hasAudio"
|
||||
fluent
|
||||
autoplay
|
||||
live
|
||||
/>
|
||||
</div>
|
||||
<div id="shared" style="text-align: right; margin-top: 1rem;">
|
||||
|
||||
|
||||
@ -181,6 +181,8 @@ create table IF NOT EXISTS wvp_media_server
|
||||
rtsp_ssl_port integer,
|
||||
flv_port integer,
|
||||
flv_ssl_port integer,
|
||||
mp4_port integer,
|
||||
mp4_ssl_port integer,
|
||||
ws_flv_port integer,
|
||||
ws_flv_ssl_port integer,
|
||||
jtt_proxy_port integer,
|
||||
|
||||
@ -182,6 +182,8 @@ create table IF NOT EXISTS wvp_media_server
|
||||
rtsp_ssl_port integer,
|
||||
flv_port integer,
|
||||
flv_ssl_port integer,
|
||||
mp4_port integer,
|
||||
mp4_ssl_port integer,
|
||||
ws_flv_port integer,
|
||||
ws_flv_ssl_port integer,
|
||||
jtt_proxy_port integer,
|
||||
|
||||
@ -51,4 +51,28 @@ call wvp_20250708();
|
||||
DROP PROCEDURE wvp_20250708;
|
||||
DELIMITER ;
|
||||
|
||||
/*
|
||||
* 20250917
|
||||
*/
|
||||
DELIMITER // -- 重定义分隔符避免分号冲突
|
||||
CREATE PROCEDURE `wvp_20250917`()
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT column_name FROM information_schema.columns
|
||||
WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'mp4_port')
|
||||
THEN
|
||||
ALTER TABLE wvp_media_server ADD mp4_port integer;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT column_name FROM information_schema.columns
|
||||
WHERE TABLE_SCHEMA = (SELECT DATABASE()) and table_name = 'wvp_media_server' and column_name = 'mp4_ssl_port')
|
||||
THEN
|
||||
ALTER TABLE wvp_media_server ADD mp4_ssl_port integer;
|
||||
END IF;
|
||||
END; //
|
||||
call wvp_20250917();
|
||||
DROP PROCEDURE wvp_20250917;
|
||||
DELIMITER ;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -35,3 +35,5 @@ create table IF NOT EXISTS wvp_jt_channel (
|
||||
);
|
||||
|
||||
ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS jtt_proxy_port integer;
|
||||
ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS mp4_port integer;
|
||||
ALTER table wvp_media_server ADD COLUMN IF NOT EXISTS mp4_ssl_port integer;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user