Compare commits

...

16 Commits

Author SHA1 Message Date
阿斌
f2dd1f9314
Pre Merge pull request !41 from 阿斌/N/A 2025-09-23 10:05:41 +00:00
lin
0ed37d58ec 完善录像回放播放器切换 2025-09-23 18:04:30 +08:00
lin
e7d26b34b3 Merge branch 'dev/test' into dev/abl功能完善
# Conflicts:
#	web/src/views/cloudRecord/detail.vue
2025-09-23 16:18:46 +08:00
lin
f7eea28951 临时提交 2025-09-23 14:32:14 +08:00
lin
a706069e97 完善h265web seek 2025-09-22 23:30:09 +08:00
lin
3a594f7bee 优化abl云端录像播放效果 2025-09-22 14:31:04 +08:00
lin
15ff87a323 修复云端录像播放页面 2025-09-21 22:51:04 +08:00
lin
9edbcd9768 完善新的云端录像播放界面 2025-09-20 15:48:17 +08:00
lin
a2ac65dc70 zlm适配新的云端录像播放界面 2025-09-19 23:52:24 +08:00
lin
ff0d54968d zlm适配新的云端录像播放界面 2025-09-19 22:55:44 +08:00
lin
af3a283b3d 适配abl新的端口命名 2025-09-19 20:48:41 +08:00
lin
fd306d8ede 云端录像回放使用单文件播放 2025-09-19 18:02:53 +08:00
lin
a064f27bf8 支持ABL风格地址 2025-09-18 18:53:12 +08:00
lin
fb9ff67701 支持ABL录像回放 2025-09-17 18:19:33 +08:00
lin
403e7648f9 优化ABL结果封装完成 2025-09-16 17:57:59 +08:00
阿斌
34d1dbb399
修复两次密码不一致时,任然可以修改密码,且成功提交,密码用*代替
还有以下一处需要修改web\src\layout\components\dialog\changePassword.vue

Signed-off-by: 阿斌 <38912748@qq.com>
2025-08-23 15:03:02 +00:00
49 changed files with 1846 additions and 1354 deletions

View File

@ -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("?", "&");
}

View File

@ -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);

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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());
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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) : "") +
'}';
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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; // 默认关闭
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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"));

View File

@ -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

View File

@ -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 {
// 非国标流 推流/拉流代理
// 拉流代理

View File

@ -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>" +

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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(

View File

@ -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

View File

@ -171,7 +171,32 @@ declare namespace Jessibuca {
* https://github.com/langhuihui/jessibuca/issues/152 解决方案
* WebGL图像预处理默认每次取4字节的数据540x960分辨率下的UV分量宽度是540/2=2704绿
*/
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

View File

@ -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
}
})
}

View File

@ -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.

View 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>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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;

View File

@ -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() {
}
}
}

View File

@ -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"

View File

@ -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

View File

@ -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;">

View File

@ -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,

View File

@ -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,

View File

@ -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 ;

View File

@ -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;