Compare commits

...

4 Commits

Author SHA1 Message Date
阿斌
5f38961007
Pre Merge pull request !36 from 阿斌/N/A 2025-08-01 10:59:36 +00:00
lin
09f1ef20a7 通用通道支持录像回放以及录像控制 2025-08-01 18:59:15 +08:00
lin
c75122008c 通用通道支持停止实时流 2025-08-01 15:58:30 +08:00
阿斌
da98101aac
update src/main/resources/civilCode.csv.
行政规划错误。江苏南通海门市,修改为海门区,浙江杭州删除下城区、江干区,新增钱塘区,临平区

Signed-off-by: 阿斌 <38912748@qq.com>
2024-12-15 08:58:42 +00:00
23 changed files with 1208 additions and 279 deletions

View File

@ -150,6 +150,9 @@ public class CommonGBChannel {
@Schema(description = "更新时间")
private String updateTime;
@Schema(description = "流唯一编号,存在表示正在直播")
private String streamId;
public String encode(String serverDeviceId) {
return encode(null, serverDeviceId);
}

View File

@ -0,0 +1,14 @@
package com.genersoft.iot.vmp.gb28181.bean;
import lombok.Data;
@Data
public class CommonRecordInfo {
// 开始时间
private String startTime;
// 结束时间
private String endTime;
}

View File

@ -179,9 +179,6 @@ public class DeviceChannel extends CommonGBChannel {
@Schema(description = "子设备数")
private int subCount;
@Schema(description = "流唯一编号,存在表示正在直播")
private String streamId;
@Schema(description = "是否含有音频")
private boolean hasAudio;

View File

@ -3,16 +3,20 @@ package com.genersoft.iot.vmp.gb28181.bean;
import com.genersoft.iot.vmp.utils.DateUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
/**
* @description:设备录像bean
* @description:设备录像bean
* @author: swwheihei
* @date: 2020年5月8日 下午2:06:54
* @date: 2020年5月8日 下午2:06:54
*/
@Setter
@Getter
@Schema(description = "设备录像详情")
public class RecordItem implements Comparable<RecordItem>{
@ -46,87 +50,7 @@ public class RecordItem implements Comparable<RecordItem>{
@Schema(description = "录像触发者ID(可选)")
private String recorderId;
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
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 int getSecrecy() {
return secrecy;
}
public void setSecrecy(int secrecy) {
this.secrecy = secrecy;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getRecorderId() {
return recorderId;
}
public void setRecorderId(String recorderId) {
this.recorderId = recorderId;
}
public String getFileSize() {
return fileSize;
}
public void setFileSize(String fileSize) {
this.fileSize = fileSize;
}
@Override
@Override
public int compareTo(@NotNull RecordItem recordItem) {
TemporalAccessor startTimeNow = DateUtil.formatter.parse(startTime);
TemporalAccessor startTimeParam = DateUtil.formatter.parse(recordItem.getStartTime());

View File

@ -2,11 +2,9 @@ package com.genersoft.iot.vmp.gb28181.controller;
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.conf.security.JwtUtils;
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
import com.genersoft.iot.vmp.gb28181.bean.DeviceType;
import com.genersoft.iot.vmp.gb28181.bean.IndustryCodeType;
import com.genersoft.iot.vmp.gb28181.bean.NetworkIdentificationType;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelToGroupByGbDeviceParam;
import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelToGroupParam;
import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelToRegionByGbDeviceParam;
@ -17,6 +15,8 @@ import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo;
@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Tag(name = "全局通道管理")
@ -276,7 +277,7 @@ public class ChannelController {
@Operation(summary = "播放通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
@GetMapping("/play")
public DeferredResult<WVPResult<StreamContent>> deleteChannelToGroupByGbDevice(HttpServletRequest request, Integer channelId){
public DeferredResult<WVPResult<StreamContent>> play(HttpServletRequest request, Integer channelId){
Assert.notNull(channelId,"参数异常");
CommonGBChannel channel = channelService.getOne(channelId);
Assert.notNull(channel, "通道不存在");
@ -316,4 +317,152 @@ public class ChannelController {
channelPlayService.play(channel, null, userSetting.getRecordSip(), callback);
return result;
}
@Operation(summary = "停止播放通道", security = @SecurityRequirement(name = JwtUtils.HEADER))
@GetMapping("/play/stop")
public void stopPlay(Integer channelId){
Assert.notNull(channelId,"参数异常");
CommonGBChannel channel = channelService.getOne(channelId);
Assert.notNull(channel, "通道不存在");
channelPlayService.stopPlay(channel, channel.getStreamId());
}
@Operation(summary = "录像查询", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "channelId", description = "通道ID", required = true)
@Parameter(name = "startTime", description = "开始时间", required = true)
@Parameter(name = "endTime", description = "结束时间", required = true)
@GetMapping("/playback/query")
public DeferredResult<WVPResult<List<CommonRecordInfo>>> queryRecord(Integer channelId, String startTime, String endTime){
DeferredResult<WVPResult<List<CommonRecordInfo>>> result = new DeferredResult<>(Long.valueOf(userSetting.getRecordInfoTimeout()), TimeUnit.MILLISECONDS);
if (!DateUtil.verification(startTime, DateUtil.formatter)){
throw new ControllerException(ErrorCode.ERROR100.getCode(), "startTime格式为" + DateUtil.PATTERN);
}
if (!DateUtil.verification(endTime, DateUtil.formatter)){
throw new ControllerException(ErrorCode.ERROR100.getCode(), "endTime格式为" + DateUtil.PATTERN);
}
CommonGBChannel channel = channelService.getOne(channelId);
Assert.notNull(channel, "通道不存在");
channelPlayService.queryRecord(channel, startTime, endTime, (code, msg, data) -> {
WVPResult<List<CommonRecordInfo>> wvpResult = new WVPResult<>();
wvpResult.setCode(code);
wvpResult.setMsg(msg);
wvpResult.setData(data);
result.setResult(wvpResult);
});
result.onTimeout(()->{
WVPResult<List<CommonRecordInfo>> wvpResult = new WVPResult<>();
wvpResult.setCode(ErrorCode.ERROR100.getCode());
wvpResult.setMsg("timeout");
result.setResult(wvpResult);
});
return result;
}
@Operation(summary = "录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "channelId", description = "通道ID", required = true)
@Parameter(name = "startTime", description = "开始时间", required = true)
@Parameter(name = "endTime", description = "结束时间", required = true)
@GetMapping("/playback")
public DeferredResult<WVPResult<StreamContent>> playback(HttpServletRequest request, Integer channelId, String startTime, String endTime){
Assert.notNull(channelId,"参数异常");
CommonGBChannel channel = channelService.getOne(channelId);
Assert.notNull(channel, "通道不存在");
DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
ErrorCallback<StreamInfo> callback = (code, msg, streamInfo) -> {
if (code == InviteErrorCode.SUCCESS.getCode()) {
WVPResult<StreamContent> wvpResult = WVPResult.success();
if (streamInfo != null) {
if (userSetting.getUseSourceIpAsStreamIp()) {
streamInfo=streamInfo.clone();//深拷贝
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
}
streamInfo.changeStreamIp(host);
}
if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix())
&& !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) {
streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix());
}
wvpResult.setData(new StreamContent(streamInfo));
}else {
wvpResult.setCode(code);
wvpResult.setMsg(msg);
}
result.setResult(wvpResult);
}else {
result.setResult(WVPResult.fail(code, msg));
}
};
channelPlayService.playback(channel, DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime),
DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime), callback);
return result;
}
@Operation(summary = "停止录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "channelId", description = "通道ID", required = true)
@Parameter(name = "stream", description = "流ID", required = true)
@GetMapping("/playback/stop")
public void stopPlayback(Integer channelId, String stream){
Assert.notNull(channelId,"参数异常");
CommonGBChannel channel = channelService.getOne(channelId);
Assert.notNull(channel, "通道不存在");
channelPlayService.stopPlayback(channel, stream);
}
@Operation(summary = "暂停录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "channelId", description = "通道ID", required = true)
@Parameter(name = "stream", description = "流ID", required = true)
@GetMapping("/playback/pause")
public void pausePlayback(Integer channelId, String stream){
Assert.notNull(channelId,"参数异常");
CommonGBChannel channel = channelService.getOne(channelId);
Assert.notNull(channel, "通道不存在");
channelPlayService.playbackPause(channel, stream);
}
@Operation(summary = "恢复录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "channelId", description = "通道ID", required = true)
@Parameter(name = "stream", description = "流ID", required = true)
@GetMapping("/playback/resume")
public void resumePlayback(Integer channelId, String stream){
Assert.notNull(channelId,"参数异常");
CommonGBChannel channel = channelService.getOne(channelId);
Assert.notNull(channel, "通道不存在");
channelPlayService.playbackResume(channel, stream);
}
@Operation(summary = "拖动录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "channelId", description = "通道ID", required = true)
@Parameter(name = "stream", description = "流ID", required = true)
@Parameter(name = "seekTime", description = "将要播放的时间", required = true)
@GetMapping("/playback/seek")
public void seekPlayback(Integer channelId, String stream, Long seekTime){
Assert.notNull(channelId,"参数异常");
Assert.notNull(seekTime,"参数异常");
CommonGBChannel channel = channelService.getOne(channelId);
Assert.notNull(channel, "通道不存在");
channelPlayService.playbackSeek(channel, stream, seekTime);
}
@Operation(summary = "拖动录像回放", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "channelId", description = "通道ID", required = true)
@Parameter(name = "stream", description = "流ID", required = true)
@Parameter(name = "speed", description = "倍速", required = true)
@GetMapping("/playback/speed")
public void seekPlayback(Integer channelId, String stream, Double speed){
Assert.notNull(channelId,"参数异常");
Assert.notNull(speed,"参数异常");
CommonGBChannel channel = channelService.getOne(channelId);
Assert.notNull(channel, "通道不存在");
channelPlayService.playbackSpeed(channel, stream, speed);
}
}

View File

@ -1,6 +1,5 @@
package com.genersoft.iot.vmp.gb28181.controller;
import com.genersoft.iot.vmp.common.InviteInfo;
import com.genersoft.iot.vmp.common.InviteSessionType;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting;
@ -26,6 +25,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -191,23 +191,14 @@ public class PlaybackController {
}
}
@Operation(summary = "回放拖动播放", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "streamId", description = "回放流ID", required = true)
@Parameter(name = "seekTime", description = "拖动偏移量单位s", required = true)
@GetMapping("/seek/{streamId}/{seekTime}")
public void playSeek(@PathVariable String streamId, @PathVariable long seekTime) {
public void playbackSeek(@PathVariable String streamId, @PathVariable long seekTime) {
log.info("playSeek: "+streamId+", "+seekTime);
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId);
if (null == inviteInfo || inviteInfo.getStreamInfo() == null) {
log.warn("streamId不存在!");
throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在");
}
Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId());
DeviceChannel channel = channelService.getOneById(inviteInfo.getChannelId());
try {
cmder.playSeekCmd(device, channel, inviteInfo.getStreamInfo(), seekTime);
playService.playbackSeek(streamId, seekTime);
} catch (InvalidArgumentException | ParseException | SipException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
}
@ -218,17 +209,10 @@ public class PlaybackController {
@Parameter(name = "speed", description = "倍速0.25 0.5 1、2、4、8", required = true)
@GetMapping("/speed/{streamId}/{speed}")
public void playSpeed(@PathVariable String streamId, @PathVariable Double speed) {
Assert.notNull(speed, "倍速不存在");
log.info("playSpeed: "+streamId+", "+speed);
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId);
if (null == inviteInfo || inviteInfo.getStreamInfo() == null) {
log.warn("streamId不存在!");
throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在");
}
Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId());
DeviceChannel channel = channelService.getOneById(inviteInfo.getChannelId());
try {
cmder.playSpeedCmd(device, channel, inviteInfo.getStreamInfo(), speed);
playService.playbackSpeed(streamId, speed);
} catch (InvalidArgumentException | ParseException | SipException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
}

View File

@ -580,4 +580,7 @@ public interface CommonGBChannelMapper {
@SelectProvider(type = ChannelProvider.class, method = "queryOnlineListsByGbDeviceId")
List<CommonGBChannel> queryOnlineListsByGbDeviceId(@Param("deviceId") int deviceId);
@Update("UPDATE wvp_device_channel SET stream_id = #{stream} where id = #{gbId}")
void updateStream(int gbId, String stream);
}

View File

@ -16,6 +16,7 @@ public class ChannelProvider {
" data_device_id,\n" +
" create_time,\n" +
" update_time,\n" +
" stream_id,\n" +
" record_plan_id,\n" +
" coalesce(gb_device_id, device_id) as gb_device_id,\n" +
" coalesce(gb_name, name) as gb_name,\n" +
@ -60,6 +61,7 @@ public class ChannelProvider {
" wdc.data_device_id,\n" +
" wdc.create_time,\n" +
" wdc.update_time,\n" +
" wdc.stream_id,\n" +
" wdc.record_plan_id,\n" +
" coalesce(wdc.gb_device_id, wdc.device_id) as gb_device_id,\n" +
" coalesce(wdc.gb_name, wdc.name) as gb_name,\n" +

View File

@ -3,10 +3,13 @@ package com.genersoft.iot.vmp.gb28181.service;
import com.genersoft.iot.vmp.common.InviteSessionType;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
import com.genersoft.iot.vmp.gb28181.bean.CommonRecordInfo;
import com.genersoft.iot.vmp.gb28181.bean.InviteMessageInfo;
import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import java.util.List;
public interface IGbChannelPlayService {
void startInvite(CommonGBChannel channel, InviteMessageInfo inviteInfo, Platform platform, ErrorCallback<StreamInfo> callback);
@ -29,4 +32,10 @@ public interface IGbChannelPlayService {
void playbackPause(CommonGBChannel channel, String streamId);
void playbackResume(CommonGBChannel channel, String streamId);
void playbackSeek(CommonGBChannel channel, String stream, long seekTime);
void playbackSpeed(CommonGBChannel channel, String stream, Double speed);
void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback<List<CommonRecordInfo>> callback);
}

View File

@ -1,7 +1,6 @@
package com.genersoft.iot.vmp.gb28181.service;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.streamPush.bean.StreamPush;
import com.github.pagehelper.PageInfo;
@ -87,8 +86,6 @@ public interface IGbChannelService {
PageInfo<CommonGBChannel> queryList(int page, int count, String query, Boolean online, Boolean hasRecordPlan, Integer channelType);
void queryRecordInfo(CommonGBChannel channel, String startTime, String endTime, ErrorCallback<RecordInfo> callback);
PageInfo<CommonGBChannel> queryListByCivilCodeForUnusual(int page, int count, String query, Boolean online, Integer channelType);
void clearChannelCivilCode(Boolean all, List<Integer> channelIds);

View File

@ -52,6 +52,10 @@ public interface IPlayService {
void playbackResume(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException;
void playbackSeek(String streamId, long seekTime) throws InvalidArgumentException, ParseException, SipException;
void playbackSpeed(String streamId, double speed) throws InvalidArgumentException, ParseException, SipException;
void startPushStream(SendRtpInfo sendRtpItem, DeviceChannel channel, SIPResponse sipResponse, Platform platform, CallIdHeader callIdHeader);
void startSendRtpStreamFailHand(SendRtpInfo sendRtpItem, Platform platform, CallIdHeader callIdHeader);

View File

@ -2,8 +2,11 @@ package com.genersoft.iot.vmp.gb28181.service;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
import com.genersoft.iot.vmp.gb28181.bean.CommonRecordInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import java.util.List;
/**
* 资源能力接入-录像回放
*/
@ -14,4 +17,12 @@ public interface ISourcePlaybackService {
void stopPlayback(CommonGBChannel channel, String stream);
void playbackPause(CommonGBChannel channel, String stream);
void playbackResume(CommonGBChannel channel, String stream);
void playbackSeek(CommonGBChannel channel, String stream, long seekTime);
void playbackSpeed(CommonGBChannel channel, String stream, Double speed);
void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback<List<CommonRecordInfo>> callback);
}

View File

@ -4,17 +4,17 @@ import com.genersoft.iot.vmp.common.InviteSessionType;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.common.enums.ChannelDataType;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
import com.genersoft.iot.vmp.gb28181.bean.InviteMessageInfo;
import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.bean.PlayException;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper;
import com.genersoft.iot.vmp.gb28181.service.*;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.sip.message.Response;
import java.util.List;
import java.util.Map;
@Slf4j
@ -24,6 +24,9 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService {
@Autowired
private UserSetting userSetting;
@Autowired
private CommonGBChannelMapper channelMapper;
@Autowired
private Map<String, ISourcePlayService> sourcePlayServiceMap;
@ -86,7 +89,15 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService {
log.error("[点播通用通道] 类型编号: {} 不支持实时流预览", dataType);
throw new PlayException(Response.BUSY_HERE, "channel not support");
}
sourceChannelPlayService.play(channel, platform, record, callback);
sourceChannelPlayService.play(channel, platform, record, (code, msg, data) -> {
if (code == InviteErrorCode.SUCCESS.getCode()) {
// 将流ID记录到数据库
if (channel.getDataType() != ChannelDataType.GB28181) {
channelMapper.updateStream(channel.getGbId(), data.getStream());
}
}
callback.run(code, msg, data);
});
}
@Override
public void playback(CommonGBChannel channel, Long startTime, Long stopTime, ErrorCallback<StreamInfo> callback) {
@ -176,6 +187,45 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService {
log.error("[点播通用通道] 类型编号: {} 不支持回放暂停恢复", dataType);
throw new PlayException(Response.BUSY_HERE, "channel not support");
}
playbackService.playbackPause(channel, stream);
playbackService.playbackResume(channel, stream);
}
@Override
public void playbackSeek(CommonGBChannel channel, String stream, long seekTime) {
log.info("[通用通道] 回放拖动播放, 类型: {} 编号:{} stream{}", channel.getDataType(), channel.getGbDeviceId(), stream);
Integer dataType = channel.getDataType();
ISourcePlaybackService playbackService = sourcePlaybackServiceMap.get(ChannelDataType.PLAYBACK_SERVICE + dataType);
if (playbackService == null) {
// 通道数据异常
log.error("[点播通用通道] 类型编号: {} 不支持回放暂停恢复", dataType);
throw new PlayException(Response.BUSY_HERE, "channel not support");
}
playbackService.playbackSeek(channel, stream, seekTime);
}
@Override
public void playbackSpeed(CommonGBChannel channel, String stream, Double speed) {
log.info("[通用通道] 回放倍速播放, 类型: {} 编号:{} stream{}", channel.getDataType(), channel.getGbDeviceId(), stream);
Integer dataType = channel.getDataType();
ISourcePlaybackService playbackService = sourcePlaybackServiceMap.get(ChannelDataType.PLAYBACK_SERVICE + dataType);
if (playbackService == null) {
// 通道数据异常
log.error("[点播通用通道] 类型编号: {} 不支持回放暂停恢复", dataType);
throw new PlayException(Response.BUSY_HERE, "channel not support");
}
playbackService.playbackSpeed(channel, stream, speed);
}
@Override
public void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback<List<CommonRecordInfo>> callback) {
log.info("[通用通道] 录像查询, 类型: {} 编号:{}", channel.getDataType(), channel.getGbDeviceId());
Integer dataType = channel.getDataType();
ISourcePlaybackService playbackService = sourcePlaybackServiceMap.get(ChannelDataType.PLAYBACK_SERVICE + dataType);
if (playbackService == null) {
// 通道数据异常
log.error("[点播通用通道] 类型编号: {} 不支持回放暂停恢复", dataType);
throw new PlayException(Response.BUSY_HERE, "channel not support");
}
playbackService.queryRecord(channel, startTime, endTime, callback);
}
}

View File

@ -717,25 +717,6 @@ public class GbChannelServiceImpl implements IGbChannelService {
return new PageInfo<>(all);
}
@Override
public void queryRecordInfo(CommonGBChannel channel, String startTime, String endTime, ErrorCallback<RecordInfo> callback) {
if (channel.getDataType() == ChannelDataType.GB28181) {
deviceChannelService.queryRecordInfo(channel, startTime, endTime, callback);
} else if (channel.getDataType() == ChannelDataType.STREAM_PROXY) {
// 拉流代理
log.warn("[下载通用通道录像] 不支持下载拉流代理的录像: {}({})", channel.getGbName(), channel.getGbDeviceId());
throw new PlayException(Response.FORBIDDEN, "forbidden");
} else if (channel.getDataType() == ChannelDataType.STREAM_PUSH) {
// 推流
log.warn("[下载通用通道录像] 不支持下载推流的录像: {}({})", channel.getGbName(), channel.getGbDeviceId());
throw new PlayException(Response.FORBIDDEN, "forbidden");
} else {
// 通道数据异常
log.error("[回放通用通道] 通道数据异常,无法识别通道来源: {}({})", channel.getGbName(), channel.getGbDeviceId());
throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error");
}
}
@Override
public PageInfo<CommonGBChannel> queryListByCivilCodeForUnusual(int page, int count, String query, Boolean online, Integer channelType) {
PageHelper.startPage(page, count);

View File

@ -1464,6 +1464,32 @@ public class PlayServiceImpl implements IPlayService {
cmder.playResumeCmd(device, channel, inviteInfo.getStreamInfo());
}
@Override
public void playbackSeek(String streamId, long seekTime) throws InvalidArgumentException, ParseException, SipException {
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId);
if (null == inviteInfo || inviteInfo.getStreamInfo() == null) {
log.warn("streamId不存在!");
throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在");
}
Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId());
DeviceChannel channel = deviceChannelService.getOneById(inviteInfo.getChannelId());
cmder.playSeekCmd(device, channel, inviteInfo.getStreamInfo(), seekTime);
}
@Override
public void playbackSpeed(String streamId, double speed) throws InvalidArgumentException, ParseException, SipException {
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId);
if (null == inviteInfo || inviteInfo.getStreamInfo() == null) {
log.warn("streamId不存在!");
throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在");
}
Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId());
DeviceChannel channel = deviceChannelService.getOneById(inviteInfo.getChannelId());
cmder.playSpeedCmd(device, channel, inviteInfo.getStreamInfo(), speed);
}
@Override
public void startPushStream(SendRtpInfo sendRtpInfo, DeviceChannel channel, SIPResponse sipResponse, Platform platform, CallIdHeader callIdHeader) {
// 开始发流

View File

@ -4,27 +4,36 @@ import com.genersoft.iot.vmp.common.InviteSessionType;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.common.enums.ChannelDataType;
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
import com.genersoft.iot.vmp.gb28181.bean.CommonRecordInfo;
import com.genersoft.iot.vmp.gb28181.bean.PlayException;
import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService;
import com.genersoft.iot.vmp.gb28181.service.IPlayService;
import com.genersoft.iot.vmp.gb28181.service.ISourcePlaybackService;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.sip.message.Response;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service(ChannelDataType.PLAYBACK_SERVICE + ChannelDataType.GB28181)
public class SourcePlaybackServiceForGbImpl implements ISourcePlaybackService {
@Autowired
private IPlayService deviceChannelPlayService;
private IPlayService playService;
@Autowired
private IDeviceChannelService channelService;
@Override
public void playback(CommonGBChannel channel, Long startTime, Long stopTime, ErrorCallback<StreamInfo> callback) {
try {
deviceChannelPlayService.playBack(channel, startTime, stopTime, callback);
playService.playBack(channel, startTime, stopTime, callback);
} catch (PlayException e) {
callback.run(e.getCode(), e.getMsg(), null);
} catch (Exception e) {
@ -36,7 +45,7 @@ public class SourcePlaybackServiceForGbImpl implements ISourcePlaybackService {
public void stopPlayback(CommonGBChannel channel, String stream) {
// 国标通道
try {
deviceChannelPlayService.stop(InviteSessionType.PLAYBACK, channel, stream);
playService.stop(InviteSessionType.PLAYBACK, channel, stream);
} catch (Exception e) {
log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e);
}
@ -44,6 +53,60 @@ public class SourcePlaybackServiceForGbImpl implements ISourcePlaybackService {
@Override
public void playbackPause(CommonGBChannel channel, String stream) {
// 国标通道
try {
playService.playbackPause(stream);
} catch (Exception e) {
log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e);
}
}
@Override
public void playbackResume(CommonGBChannel channel, String stream) {
// 国标通道
try {
playService.playbackPause(stream);
} catch (Exception e) {
log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e);
}
}
@Override
public void playbackSeek(CommonGBChannel channel, String stream, long seekTime) {
// 国标通道
try {
playService.playbackPause(stream);
} catch (Exception e) {
log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e);
}
}
@Override
public void playbackSpeed(CommonGBChannel channel, String stream, Double speed) {
// 国标通道
try {
playService.playbackSpeed(stream, speed);
} catch (Exception e) {
log.error("[停止点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e);
}
}
@Override
public void queryRecord(CommonGBChannel channel, String startTime, String endTime, ErrorCallback<List<CommonRecordInfo>> callback) {
channelService.queryRecordInfo(channel, startTime, endTime, (code, msg, data) -> {
if (code == ErrorCode.SUCCESS.getCode()) {
List<RecordItem> recordList = data.getRecordList();
List<CommonRecordInfo> recordInfoList = new ArrayList<>();
for (RecordItem recordItem : recordList) {
CommonRecordInfo recordInfo = new CommonRecordInfo();
recordInfo.setStartTime(recordItem.getStartTime());
recordInfo.setEndTime(recordItem.getEndTime());
recordInfoList.add(recordInfo);
}
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), recordInfoList);
}else {
callback.run(code, msg, null);
}
});
}
}

View File

@ -117,7 +117,7 @@ public class RedisRpcChannelPlayController extends RpcController {
}
try {
channelService.queryRecordInfo(channel, startTime, endTime, (code, msg, data) ->{
channelPlayService.queryRecord(channel, startTime, endTime, (code, msg, data) ->{
if (code == InviteErrorCode.SUCCESS.getCode()) {
response.setStatusCode(code);
response.setBody(data);

View File

@ -861,7 +861,7 @@
320623,如东县,3206
320681,启东市,3206
320682,如皋市,3206
320684,海门,3206
320684,海门,3206
320685,海安市,3206
3207,连云港市,32
320703,连云区,3207
@ -918,8 +918,6 @@
33,浙江省,
3301,杭州市,33
330102,上城区,3301
330103,下城区,3301
330104,江干区,3301
330105,拱墅区,3301
330106,西湖区,3301
330108,滨江区,3301
@ -927,6 +925,8 @@
330110,余杭区,3301
330111,富阳区,3301
330112,临安区,3301
330113,临平区,3301
330114,钱塘区,3301
330122,桐庐县,3301
330127,淳安县,3301
330182,建德市,3301

1 编号 名称 上级
861 320623 如东县 3206
862 320681 启东市 3206
863 320682 如皋市 3206
864 320684 海门市 海门区 3206
865 320685 海安市 3206
866 3207 连云港市 32
867 320703 连云区 3207
918 33 浙江省
919 3301 杭州市 33
920 330102 上城区 3301
330103 下城区 3301
330104 江干区 3301
921 330105 拱墅区 3301
922 330106 西湖区 3301
923 330108 滨江区 3301
925 330110 余杭区 3301
926 330111 富阳区 3301
927 330112 临安区 3301
928 330113 临平区 3301
929 330114 钱塘区 3301
930 330122 桐庐县 3301
931 330127 淳安县 3301
932 330182 建德市 3301

View File

@ -260,6 +260,15 @@ export function playChannel(channelId) {
}
})
}
export function stopPlayChannel(channelId) {
return request({
method: 'get',
url: '/api/common/channel/play/stop',
params: {
channelId: channelId
}
})
}
// 前端控制
@ -503,3 +512,77 @@ export function focus({ channelId, command, speed }) {
}
})
}
export function queryRecord({ channelId, startTime, endTime }) {
return request({
method: 'get',
url: '/api/common/channel/playback/query',
params: {
channelId: channelId,
startTime: startTime,
endTime: endTime
}
})
}
export function playback({ channelId, startTime, endTime }) {
return request({
method: 'get',
url: '/api/common/channel/playback',
params: {
channelId: channelId,
startTime: startTime,
endTime: endTime
}
})
}
export function stopPlayback({ channelId, stream }) {
return request({
method: 'get',
url: '/api/common/channel/playback/stop',
params: {
channelId: channelId,
stream: stream
}
})
}
export function pausePlayback({ channelId, stream}) {
return request({
method: 'get',
url: '/api/common/channel/playback/pause',
params: {
channelId: channelId,
stream: stream
}
})
}
export function resumePlayback({ channelId, stream}) {
return request({
method: 'get',
url: '/api/common/channel/playback/resume',
params: {
channelId: channelId,
stream: stream
}
})
}
export function seekPlayback({ channelId, stream, seekTime}) {
return request({
method: 'get',
url: '/api/common/channel/playback/seek',
params: {
channelId: channelId,
stream: stream,
seekTime: seekTime
}
})
}
export function speedPlayback({ channelId, stream, speed}) {
return request({
method: 'get',
url: '/api/common/channel/playback/speed',
params: {
channelId: channelId,
stream: stream,
speed: speed
}
})
}

View File

@ -70,12 +70,20 @@ export const constantRoutes = [
path: '/channel',
component: Layout,
redirect: '/channel',
onlyIndex: 0,
children: [{
path: '',
path: '/channel',
name: 'Channel',
component: () => import('@/views/channel/index'),
meta: {title: '通道列表', icon: 'channelManger'}
}]
meta: { title: '通道列表', icon: 'channelManger'}
},
{
path: '/channel/record/:channelId',
name: 'CommonRecord',
component: () => import('@/views/channel/record'),
meta: { title: '设备录像' }
}
]
},
{
path: '/device',

View File

@ -15,12 +15,22 @@ import {
clearUnusualCivilCodeList,
getIndustryList,
getTypeList,
getNetworkIdentificationList, playChannel, addToRegion, deleteFromRegion, addToGroup, deleteFromGroup, getList,
getNetworkIdentificationList,
playChannel,
addToRegion,
deleteFromRegion,
addToGroup,
deleteFromGroup,
getList,
addPointForCruise,
addPreset, auxiliary,
addPreset,
auxiliary,
callPreset,
deletePointForCruise,
deletePreset, focus, iris, ptz,
deletePreset,
focus,
iris,
ptz,
queryPreset,
setCruiseSpeed,
setCruiseTime,
@ -30,7 +40,16 @@ import {
startCruise,
startScan,
stopCruise,
stopScan, wiper, getAllForMap
stopScan,
wiper,
getAllForMap,
stopPlayChannel,
queryRecord,
playback,
stopPlayback,
pausePlayback,
resumePlayback,
seekPlayback, speedPlayback
} from '@/api/commonChannel'
const actions = {
@ -254,6 +273,16 @@ const actions = {
})
})
},
stopPlayChannel({ commit }, channelId) {
return new Promise((resolve, reject) => {
stopPlayChannel(channelId).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
getList({ commit }, param) {
return new Promise((resolve, reject) => {
getList(param).then(response => {
@ -473,6 +502,76 @@ const actions = {
reject(error)
})
})
},
queryRecord({ commit }, params) {
return new Promise((resolve, reject) => {
queryRecord(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
playback({ commit }, params) {
return new Promise((resolve, reject) => {
playback(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
stopPlayback({ commit }, params) {
return new Promise((resolve, reject) => {
stopPlayback(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
pausePlayback({ commit }, params) {
return new Promise((resolve, reject) => {
pausePlayback(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
resumePlayback({ commit }, params) {
return new Promise((resolve, reject) => {
resumePlayback(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
seekPlayback({ commit }, params) {
return new Promise((resolve, reject) => {
resumePlayback(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
speedPlayback({ commit }, params) {
return new Promise((resolve, reject) => {
speedPlayback(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
}
}

View File

@ -92,7 +92,6 @@
<el-button
v-if="!!scope.row.streamId"
size="medium"
:disabled="device == null || device.online === 0"
icon="el-icon-switch-button"
type="text"
style="color: #f56c6c"
@ -109,6 +108,19 @@
>
编辑
</el-button>
<el-divider direction="vertical" />
<el-dropdown @command="(command)=>{moreClick(command, scope.row)}">
<el-button size="medium" type="text">
更多<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu>
<el-dropdown-item command="records" :disabled="scope.row.gbStatus !== 'ON'">
设备录像</el-dropdown-item>
<el-dropdown-item command="cloudRecords" :disabled="scope.row.gbStatus !== 'ON'">
云端录像</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
@ -184,14 +196,14 @@ export default {
updateLooper: 0, //
searchStr: '',
channelType: '',
online: '',
online: 'true',
subStream: '',
winHeight: window.innerHeight - 200,
currentPage: this.defaultPage | 1,
count: this.defaultCount | 15,
total: 0,
beforeUrl: '/device',
editId: null,
editId: null
}
},
mounted() {
@ -256,10 +268,8 @@ export default {
})
},
queryRecords: function(itemData) {
const deviceId = this.deviceId
const channelId = itemData.deviceId
this.$router.push(`/device/record/${deviceId}/${channelId}`)
const channelId = itemData.gbId
this.$router.push(`/channel/record/${channelId}`)
},
queryCloudRecords: function(itemData) {
const deviceId = this.deviceId
@ -267,42 +277,8 @@ export default {
this.$router.push(`/cloudRecord/detail/rtp/${deviceId}_${channelId}`)
},
startRecord: function(itemData) {
this.$store.dispatch('device/deviceRecord', {
deviceId: this.deviceId,
channelId: itemData.deviceId,
recordCmdStr: 'Record'
}).then(data => {
this.$message.success({
showClose: true,
message: '开始录像成功'
})
}).catch((error) => {
this.$message.error({
showClose: true,
message: error.message
})
})
},
stopRecord: function(itemData) {
this.$store.dispatch('device/deviceRecord', {
deviceId: this.deviceId,
channelId: itemData.deviceId,
recordCmdStr: 'StopRecord'
}).then(data => {
this.$message.success({
showClose: true,
message: '停止录像成功'
})
}).catch((error) => {
this.$message.error({
showClose: true,
message: error.message
})
})
},
stopDevicePush: function(itemData) {
this.$store.dispatch('play/stop', [itemData.deviceId]).then(data => {
this.$store.dispatch('commonChanel/stopPlayChannel', itemData.gbId).then(data => {
this.initData()
}).catch((error) => {
if (error.response.status === 402) { //
@ -333,93 +309,11 @@ export default {
}, 1000)
}
},
showDevice: function() {
// this.$router.push(this.beforeUrl).then(() => {
// this.initParam()
// this.initData()
// })
this.$emit('show-device')
},
changeSubchannel(itemData) {
this.beforeUrl = this.$router.currentRoute.path
var url = `/${this.$router.currentRoute.name}/${this.$router.currentRoute.params.deviceId}/${itemData.deviceId}`
this.$router.push(url).then(() => {
this.searchStr = ''
this.channelType = ''
this.online = ''
this.initParam()
this.initData()
})
},
showSubChannels: function() {
this.$store.dispatch('device/querySubChannels', [
{
page: this.currentPage,
count: this.count,
query: this.searchStr,
online: this.online,
channelType: this.channelType
},
this.deviceId,
this.parentChannelId
])
.then(data => {
this.total = data.total
this.channelList = data.list
this.channelList.forEach(e => {
e.ptzType = e.ptzType + ''
})
//
this.$nextTick(() => {
this.$refs.channelListTable.doLayout()
})
})
},
search: function() {
this.currentPage = 1
this.total = 0
this.initData()
},
updateChannel: function(row) {
this.$store.dispatch('device/changeChannelAudio', {
channelId: row.gbId,
audio: row.hasAudio
})
},
subStreamChange: function() {
this.$confirm('确定重置所有通道的码流类型?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('device/updateChannelStreamIdentification', {
deviceDbId: this.device.id,
streamIdentification: this.subStream
})
.then(data => {
this.initData()
})
.finally(() => {
this.subStream = ''
})
}).catch(() => {
this.subStream = ''
})
},
channelSubStreamChange: function(row) {
this.$store.dispatch('device/updateChannelStreamIdentification', {
deviceDbId: row.deviceDbId,
id: row.id,
streamIdentification: row.streamIdentification
})
.then(data => {
this.initData()
})
.finally(() => {
this.subStream = ''
})
},
refresh: function() {
this.initData()
},
@ -432,8 +326,14 @@ export default {
closeEdit: function() {
this.editId = null
this.getChannelList()
},
moreClick: function(command, itemData) {
if (command === 'records') {
this.queryRecords(itemData)
} else if (command === 'cloudRecords') {
this.queryCloudRecords(itemData)
}
}
}
}
</script>

622
web/src/views/channel/record.vue Executable file
View File

@ -0,0 +1,622 @@
<template>
<div id="DeviceRecord" class="app-container">
<div :style="boxStyle">
<div>
<div v-if="this.$route.query.mediaServerId" class="page-header-btn" style="padding-right: 1rem">
<b>节点</b> {{ mediaServerId }}
</div>
<div v-if="this.$route.params.mediaServerId">
<span>流媒体{{ this.$route.params.mediaServerId }}</span>
</div>
<div class="record-list-box-box">
<div v-if="showSidebar">
<el-date-picker
v-model="chooseDate"
size="mini"
:picker-options="pickerOptions"
type="date"
value-format="yyyy-MM-dd"
placeholder="日期"
style="width: 190px"
@change="dateChange()"
/>
<!-- <el-button :disabled="!mediaServerId" size="mini" type="primary" icon="fa fa-cloud-download" style="margin: auto; margin-left: 12px " title="裁剪合并" @click="drawerOpen"></el-button>-->
</div>
<div class="record-list-box" style="height: calc(100vh - 170px); overflow: auto">
<ul v-if="detailFiles.length >0" class="infinite-list record-list">
<li v-for="(item,index) in detailFiles" :key="index" class="infinite-list-item record-list-item">
<el-tag
v-if="chooseFileIndex !== index"
style="background-color: #ecf5ff; color: #017690; "
@click="chooseFile(index)"
>
<i class="el-icon-video-camera" />
{{ getFileShowName(item) }}
</el-tag>
<el-tag v-if="chooseFileIndex === index" type="danger">
<i class="el-icon-video-camera" />
{{ getFileShowName(item) }}
</el-tag>
<!-- <a-->
<!-- class="el-icon-download"-->
<!-- style="color: #409EFF;font-weight: 600;margin-left: 10px;"-->
<!-- target="_blank"-->
<!-- @click="downloadFile(item)"-->
<!-- />-->
</li>
</ul>
<div v-if="detailFiles.length === 0" class="record-list-no-val">暂无数据</div>
</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"
:has-audio="true"
@playStatusChange="playingChange"
@playTimeChange="showPlayTimeChange"
/>
</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 400px 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-xiazai1"-->
<!-- title="下载录像"-->
<!-- @click="chooseTimeForRecord()"-->
<!-- />-->
<!-- <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>
</div>
<record-download ref="recordDownload" />
<chooseTimeRange ref="chooseTimeRange" />
</div>
</template>
<script>
import h265web from '../common/h265web.vue'
import VideoTimeline from '../common/VideoTimeLine/index.vue'
import recordDownload from '../dialog/recordDownload.vue'
import ChooseTimeRange from '../dialog/chooseTimeRange.vue'
import moment from 'moment'
import screenfull from 'screenfull'
export default {
name: 'CommonRecord',
components: {
h265web, VideoTimeline, recordDownload, ChooseTimeRange
},
data() {
return {
showSidebar: false,
channelId: this.$route.params.channelId,
mediaServerId: null,
dateFilesObj: [],
mediaServerList: [],
detailFiles: [],
videoUrl: null,
streamInfo: null,
loading: false,
chooseDate: null,
playTime: null,
playerTime: 0,
playSpeed: 1,
chooseFileIndex: null,
queryDate: new Date(),
currentPage: 1,
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,
timeSegments: [],
pickerOptions: {
cellClassName: (date) => {
//
const time = moment(date).format('YYYY-MM-DD')
if (this.dateFilesObj[time]) {
return 'data-picker-true'
} else {
return 'data-picker-false'
}
}
},
playSpeedRange: [0.25, 0.5, 1, 2, 4]
}
},
computed: {
boxStyle() {
if (this.showSidebar) {
return {
display: 'grid',
gridTemplateColumns: '210px minmax(0, 1fr)'
}
} else {
return {
display: 'grid', gridTemplateColumns: '0 minmax(0, 1fr)'
}
}
},
showTimeValue() {
return moment(this.playTime).format('YYYY-MM-DD HH:mm:ss')
},
startTime() {
return this.chooseDate + ' 00:00:00'
},
endTime() {
return this.chooseDate + ' 23:59:59'
}
},
mounted() {
//
this.chooseDate = moment().format('YYYY-MM-DD')
this.dateChange()
window.addEventListener('beforeunload', this.stopPlayRecord)
},
destroyed() {
this.$destroy('recordVideoPlayer')
window.removeEventListener('beforeunload', this.stopPlayRecord)
},
methods: {
sidebarControl() {
this.showSidebar = !this.showSidebar
},
snap() {
this.$refs.recordVideoPlayer.screenshot()
},
chooseTimeForRecord() {
let startTime = this.startTime
let endTime = this.endTime
if (this.detailFiles.length > 0) {
startTime = this.detailFiles[0].startTime
endTime = this.detailFiles[this.detailFiles.length - 1].endTime
}
this.$refs.chooseTimeRange.openDialog([new Date(startTime), new Date(endTime)], (time) => {
console.log(time)
const startTime = moment(time[0]).format('YYYY-MM-DD HH:mm:ss')
const endTime = moment(time[1]).format('YYYY-MM-DD HH:mm:ss')
this.downloadFile({
startTime: startTime,
endTime: endTime
})
})
},
playLast() {
//
if (this.chooseFileIndex === 0) {
return
}
this.chooseFile(this.chooseFileIndex - 1)
},
playNext() {
//
if (this.chooseFileIndex === this.detailFiles.length - 1) {
return
}
this.chooseFile(this.chooseFileIndex + 1)
},
changePlaySpeed(speed) {
console.log(this.streamInfo)
console.log(speed)
//
this.playSpeed = speed
this.$store.dispatch('commonChanel/speedPlayback',
{
channelId: this.channelId,
stream: this.streamInfo.stream,
speed: speed
})
.then(data => {
this.$refs.recordVideoPlayer.setPlaybackRate(this.playSpeed)
})
.catch((err) => {
console.log(err)
})
},
seekBackward() {
// 退
this.playSeekValue -= 5 * 1000
this.play()
},
seekForward() {
//
this.playSeekValue += 5 * 1000
this.play()
},
stopPLay() {
//
this.$refs.recordVideoPlayer.destroy()
},
pausePlay() {
//
this.$refs.recordVideoPlayer.pause()
},
play() {
if (this.$refs.recordVideoPlayer.loaded) {
this.$refs.recordVideoPlayer.unPause()
} else {
this.playRecord(this.showTimeValue, this.endTime)
}
},
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('playerBox'))
screenfull.on('change', (event) => {
this.$refs.recordVideoPlayer.resize(playerWidth, playerHeight)
this.isFullScreen = screenfull.isFullscreen
})
this.isFullScreen = true
},
dateChange() {
this.detailFiles = []
this.$store.dispatch('commonChanel/queryRecord',
{
channelId: this.channelId,
startTime: this.startTime,
endTime: this.endTime
})
.then(data => {
//
if (data.length === 0) {
return
}
this.detailFiles = data
this.initTime = new Date(this.detailFiles[0].startTime).getTime()
for (let i = 0; i < this.detailFiles.length; i++) {
this.timeSegments.push({
beginTime: new Date(this.detailFiles[i].startTime).getTime(),
endTime: new Date(this.detailFiles[i].endTime).getTime(),
color: '#017690',
startRatio: 0.7,
endRatio: 0.85,
index: i
})
}
})
.finally(() => {
this.recordsLoading = false
})
},
stopPlayRecord(callback) {
console.log('停止录像回放')
if (this.streamInfo !== null) {
this.$refs['recordVideoPlayer'].pause()
this.videoUrl = ''
this.$store.dispatch('commonChanel/stopPlayback',
{
channelId: this.channelId,
stream: this.streamInfo.stream
})
.then((data) => {
this.streamInfo = null
if (callback) callback()
})
} else {
if (callback) callback()
}
},
chooseFile(index) {
this.chooseFileIndex = index
const chooseFile = this.detailFiles[this.chooseFileIndex]
this.playTime = new Date(chooseFile.startTime).getTime()
this.playRecord(chooseFile.startTime, this.endTime)
},
playRecord(startTime, endTime) {
if (this.streamInfo !== null) {
this.stopPlayRecord(() => {
this.playRecord(startTime, endTime)
})
} else {
this.playerTime = 0
this.$store.dispatch('commonChanel/playback',
{
channelId: this.channelId,
startTime: startTime,
endTime: endTime
})
.then(data => {
this.streamInfo = data
this.videoUrl = this.getUrlByStreamInfo()
this.hasAudio = this.streamInfo.tracks && this.streamInfo.tracks.length > 1
})
}
},
getUrlByStreamInfo() {
if (location.protocol === 'https:') {
this.videoUrl = this.streamInfo['wss_flv']
} else {
this.videoUrl = this.streamInfo['ws_flv']
}
return this.videoUrl
},
downloadFile(row) {
if (!row) {
const startTimeStr = moment(new Date(this.chooseDate + ' 00:00:00').getTime() + this.playTime[0] * 1000).format('YYYY-MM-DD HH:mm:ss')
const endTimeStr = moment(new Date(this.chooseDate + ' 00:00:00').getTime() + this.playTime[1] * 1000).format('YYYY-MM-DD HH:mm:ss')
row = {
startTime: startTimeStr,
endTime: endTimeStr
}
}
if (this.streamInfo !== null) {
this.stopPlayRecord(() => {
this.downloadFile(row)
})
} else {
const loading = this.$loading({
lock: true,
text: '正在请求录像',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
this.$store.dispatch('gbRecord/startDownLoad', [
this.deviceId, this.channelId, row.startTime, row.endTime, this.playSpeedRange[this.playSpeedRange.length - 1]
])
.then(streamInfo => {
this.$refs.recordDownload.openDialog(this.deviceId, this.channelId, streamInfo.app, streamInfo.stream, streamInfo.mediaServerId)
})
.finally(() => {
loading.close()
})
}
},
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
this.playRecord(this.showTimeValue, this.endTime)
}
}
}
</script>
<style>
.record-list-box-box {
width: fit-content;
float: left;
}
.record-list-box {
width: 100%;
overflow: auto;
list-style: none;
padding: 0;
margin: 0;
background-color: #FFF;
margin-top: 10px;
}
.record-list {
list-style: none;
padding: 0;
margin: 0;
background-color: #FFF;
}
.record-list-no-val {
width: fit-content;
position: relative;
color: #9f9f9f;
top: 50%;
left: calc(50% - 2rem);
}
.record-list-item {
padding: 0;
margin: 0;
margin: 0.5rem 0;
cursor: pointer;
}
.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;
}
.record-play-control-item {
display: inline-block;
padding: 0 10px;
color: #fff;
margin-right: 2px;
}
.record-play-control-item:hover {
color: #1f83e6;
}
.record-play-control-speed {
font-weight: bold;
color: #fff;
user-select: none;
}
.player-option-box {
height: 50px
}
.time-line-show {
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;
}
</style>