|
|
@@ -4,6 +4,7 @@ import cn.hutool.core.lang.UUID;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import cn.hutool.json.JSONArray;
|
|
|
import cn.hutool.json.JSONObject;
|
|
|
+import gov.nist.javax.sip.stack.SIPDialog;
|
|
|
import lombok.AllArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.hswebframework.web.exception.BusinessException;
|
|
|
@@ -27,12 +28,18 @@ import org.springframework.http.HttpStatus;
|
|
|
import org.springframework.http.ResponseEntity;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.util.ResourceUtils;
|
|
|
-import reactor.core.publisher.Mono;
|
|
|
+import org.springframework.web.context.request.async.DeferredResult;
|
|
|
+import reactor.core.Disposable;
|
|
|
+import reactor.core.publisher.*;
|
|
|
+
|
|
|
import java.io.FileNotFoundException;
|
|
|
import java.time.Duration;
|
|
|
import java.util.Objects;
|
|
|
import java.util.Optional;
|
|
|
+import java.util.concurrent.*;
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
+import java.util.function.Consumer;
|
|
|
+import java.util.function.Function;
|
|
|
|
|
|
@SuppressWarnings(value = {"rawtypes", "unchecked"})
|
|
|
@Service
|
|
|
@@ -40,9 +47,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
@AllArgsConstructor
|
|
|
public class LocalPlayService {
|
|
|
|
|
|
- //// @Autowired
|
|
|
-//// private IVideoManagerStorager storager;
|
|
|
-//
|
|
|
+ // @Autowired
|
|
|
+// private IVideoManagerStorager storager;
|
|
|
private final SipCommander cmder;
|
|
|
|
|
|
private final RedisCacheStorageImpl redisCatchStorage;
|
|
|
@@ -56,130 +62,121 @@ public class LocalPlayService {
|
|
|
private final VideoStreamSessionManager streamSession;
|
|
|
|
|
|
private final LocalMediaDeviceChannelService deviceChannelService;
|
|
|
- // @Autowired
|
|
|
-//// private RedisUtil redis;
|
|
|
-////
|
|
|
- private DeferredResultHolder resultHolder;
|
|
|
|
|
|
private final ZLMRESTfulUtils zlmresTfulUtils;
|
|
|
|
|
|
-//
|
|
|
-// @Autowired
|
|
|
-// private UserSetup userSetup;
|
|
|
-
|
|
|
- public Mono<ResponseEntity> play(MediaServerItem mediaServerItem, String deviceId, String channelId,
|
|
|
- ZLMHttpHookSubscribe.Event hookEvent, ZLMHttpHookSubscribe.Event errorEvent) {
|
|
|
+ public Mono<WVPResult> play(MediaServerItem mediaServerItem, String deviceId, String channelId,
|
|
|
+ ZLMHttpHookSubscribe.Event hookEvent, ZLMHttpHookSubscribe.Event errorEvent) throws InterruptedException {
|
|
|
//todo
|
|
|
mediaServerItem.setRtpEnable(false);
|
|
|
mediaServerItem.setRtpProxyPort(10000);
|
|
|
PlayResult playResult = new PlayResult();
|
|
|
-
|
|
|
+ LinkedBlockingDeque<WVPResult> result = new LinkedBlockingDeque<>();
|
|
|
String msgId=UUID.randomUUID().toString();
|
|
|
|
|
|
RequestMessage msg = RequestMessage.of(deviceId,channelId,msgId,null);
|
|
|
|
|
|
- final AtomicBoolean start=new AtomicBoolean(false);
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
|
|
|
- return eventBus.subscribe(
|
|
|
- Subscription.of("media_play",DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg), Subscription.Feature.local))
|
|
|
- .mergeWith( Mono.justOrEmpty(redisCatchStorage.queryPlayByDevice(deviceId,channelId))
|
|
|
- .flatMap(streamInfo -> {
|
|
|
- if(StrUtil.isEmpty(streamInfo.getStreamId())){
|
|
|
- return Mono.error(new BusinessException("点播失败, redis缓存streamId等于null"));
|
|
|
- }
|
|
|
- String mediaServerId = streamInfo.getMediaServerId();
|
|
|
- MediaServerItem mediaInfo = mediaServerItemService.getOne(mediaServerId);
|
|
|
+ Disposable subscribe = eventBus.subscribe(
|
|
|
+ Subscription.of("media_play", DeferredResultHolder.getCmdCallBackTopic(DeferredResultHolder.CALLBACK_CMD_PLAY, msg), Subscription.Feature.local))
|
|
|
+ .mergeWith(Mono.justOrEmpty(redisCatchStorage.queryPlayByDevice(deviceId, channelId))
|
|
|
+ .flatMap(streamInfo -> {
|
|
|
+ if (StrUtil.isEmpty(streamInfo.getStreamId())) {
|
|
|
+ return Mono.error(new BusinessException("点播失败, redis缓存streamId等于null"));
|
|
|
+ }
|
|
|
+ String mediaServerId = streamInfo.getMediaServerId();
|
|
|
+ MediaServerItem mediaInfo = mediaServerItemService.getOneByServerId(mediaServerId);
|
|
|
|
|
|
- JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamInfo.getStreamId());
|
|
|
- if (rtpInfo != null && rtpInfo.getBool("exist")) {
|
|
|
- if (hookEvent != null) {
|
|
|
- //todo
|
|
|
+ JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamInfo.getStreamId());
|
|
|
+ if (rtpInfo != null && rtpInfo.getBool("exist")) {
|
|
|
+ if (hookEvent != null) {
|
|
|
+ //todo
|
|
|
// hookEvent.response(mediaServerItem, com.alibaba.fastjson.JSONObject.parseObject(JSON.toJSONString(streamInfo)));
|
|
|
- }
|
|
|
- clusterEventBus.publish(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
|
|
|
- ResponseEntity.ok(streamInfo));
|
|
|
- } else {
|
|
|
- // TODO 点播前是否重置状态
|
|
|
- redisCatchStorage.stopPlay(streamInfo);
|
|
|
- return deviceChannelService.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId())
|
|
|
- .flatMap(ignore->{
|
|
|
- SSRCInfo ssrcInfo;
|
|
|
- String streamId2 = null;
|
|
|
- if (mediaServerItem.isRtpEnable()) {
|
|
|
- streamId2 = String.format("%s_%s",deviceId, channelId);
|
|
|
- }
|
|
|
- ssrcInfo = mediaServerItemService.openRTPServer(mediaServerItem, streamId2);
|
|
|
-
|
|
|
- return cmder.playStreamCmd(mediaServerItem, ssrcInfo, redisCatchStorage.getDevice(deviceId), channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
|
|
|
- log.info("收到订阅消息: " + response.toString());
|
|
|
- onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, msgId)
|
|
|
- .subscribe();
|
|
|
- }, (event) -> {
|
|
|
- mediaServerItemService.closeRTPServer(playResult.getDevice(), channelId);
|
|
|
- WVPResult wvpResult = WVPResult.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg),null);
|
|
|
- clusterEventBus.publish(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
|
|
|
- ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(wvpResult));
|
|
|
- })
|
|
|
- .then();
|
|
|
+ }
|
|
|
+ clusterEventBus.publish(DeferredResultHolder.getCmdCallBackTopic(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
|
|
|
+ streamInfo);
|
|
|
+ } else {
|
|
|
+ // TODO 点播前是否重置状态
|
|
|
+ redisCatchStorage.stopPlay(streamInfo);
|
|
|
+ return deviceChannelService.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId())
|
|
|
+ .flatMap(ignore -> {
|
|
|
+ SSRCInfo ssrcInfo;
|
|
|
+ String streamId2 = null;
|
|
|
+ if (mediaServerItem.isRtpEnable()) {
|
|
|
+ streamId2 = String.format("%s_%s", deviceId, channelId);
|
|
|
+ }
|
|
|
+ ssrcInfo = mediaServerItemService.openRTPServer(mediaServerItem, streamId2);
|
|
|
+
|
|
|
+ return cmder.playStreamCmd(mediaServerItem, ssrcInfo, redisCatchStorage.getDevice(deviceId), channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
|
|
|
+ log.info("收到订阅消息: " + response.toString());
|
|
|
+ onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, msgId)
|
|
|
+ .subscribe();
|
|
|
+ }, (event) -> {
|
|
|
+ mediaServerItemService.closeRTPServer(playResult.getDevice(), channelId);
|
|
|
+ WVPResult wvpResult = WVPResult.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
|
|
|
+ clusterEventBus.publish(DeferredResultHolder.getCmdCallBackTopic(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
|
|
|
+ wvpResult);
|
|
|
});
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return Mono.empty();
|
|
|
+ })
|
|
|
+ .switchIfEmpty(Mono.just(mediaServerItem)
|
|
|
+ .flatMap(serverItem -> {
|
|
|
+ SSRCInfo ssrcInfo;
|
|
|
+ String streamId = null;
|
|
|
+ if (serverItem.isRtpEnable()) {
|
|
|
+ streamId = String.format("%s_%s", deviceId, channelId);
|
|
|
}
|
|
|
- return Mono.empty();
|
|
|
- })
|
|
|
- .switchIfEmpty(Mono.just(mediaServerItem)
|
|
|
- .flatMap(serverItem->{
|
|
|
- SSRCInfo ssrcInfo;
|
|
|
- String streamId = null;
|
|
|
- if (serverItem.isRtpEnable()) {
|
|
|
- streamId = String.format("%s_%s", deviceId, channelId);
|
|
|
- }
|
|
|
|
|
|
- ssrcInfo = mediaServerItemService.openRTPServer(serverItem, streamId);
|
|
|
+ ssrcInfo = mediaServerItemService.openRTPServer(serverItem, streamId);
|
|
|
|
|
|
- // 发送点播消息
|
|
|
- MediaDevice device = redisCatchStorage.getDevice(deviceId);
|
|
|
- return cmder.playStreamCmd(serverItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
|
|
|
- log.info("收到订阅消息: " + response.toString());
|
|
|
- onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, msgId).subscribe();
|
|
|
- if (hookEvent != null) {
|
|
|
+ // 发送点播消息
|
|
|
+ MediaDevice device = redisCatchStorage.getDevice(deviceId);
|
|
|
+ return cmder.playStreamCmd(serverItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
|
|
|
+ log.info("收到订阅消息: " + response.toString());
|
|
|
+ onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, msgId).subscribe();
|
|
|
+ if (hookEvent != null) {
|
|
|
// hookEvent.response(mediaServerItem, response);
|
|
|
- }
|
|
|
- }, (event) -> {
|
|
|
- WVPResult wvpResult = WVPResult.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
|
|
|
- // 点播返回sip错误
|
|
|
- mediaServerItemService.closeRTPServer(playResult.getDevice(), channelId);
|
|
|
- clusterEventBus.publish(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
|
|
|
- ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(wvpResult));
|
|
|
- if (errorEvent != null) {
|
|
|
- //todo
|
|
|
+ }
|
|
|
+ }, (event) -> {
|
|
|
+ WVPResult wvpResult = WVPResult.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
|
|
|
+ // 点播返回sip错误
|
|
|
+ mediaServerItemService.closeRTPServer(playResult.getDevice(), channelId);
|
|
|
+ clusterEventBus.publish(DeferredResultHolder.getCmdCallBackTopic(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
|
|
|
+ wvpResult);
|
|
|
+ if (errorEvent != null) {
|
|
|
+ //todo
|
|
|
// errorEvent.response(event);
|
|
|
- }
|
|
|
- })
|
|
|
- .then();
|
|
|
- })).then(Mono.empty()))
|
|
|
+ }
|
|
|
+ });
|
|
|
+ })).then(Mono.empty()))
|
|
|
+ .map(topicPayload -> topicPayload.bodyToJson(false).toJavaObject(WVPResult.class))
|
|
|
+ //返回结果
|
|
|
+ .doOnNext(wvpResult -> {
|
|
|
+ try {
|
|
|
+ result.put(wvpResult);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .subscribe();
|
|
|
|
|
|
- //超时报错 todo
|
|
|
- .timeout(Duration.ofSeconds(10), Mono.error(new BusinessException("点播/收流超时,请稍候重试")))
|
|
|
- //报错时,发送bye指令
|
|
|
- .flatMap(payload -> Mono.just(payload.bodyToJson(true)))
|
|
|
+ return Mono.justOrEmpty(result.pollFirst(60,TimeUnit.SECONDS))
|
|
|
+ .flatMap(wvpResult->{
|
|
|
+ //取消订阅
|
|
|
+ subscribe.dispose();
|
|
|
|
|
|
- .cast(ResponseEntity.class)
|
|
|
- //判断回复是否有效
|
|
|
- .flatMap(response->{
|
|
|
- if(response==null){
|
|
|
+ if(wvpResult==null){
|
|
|
return Mono.error(new BusinessException("服务器内部出现问题"));
|
|
|
}
|
|
|
- if(response.getStatusCode()!=HttpStatus.OK){
|
|
|
- WVPResult wvpResult = (WVPResult)response.getBody();
|
|
|
- return Mono.error(new BusinessException(Optional.ofNullable(wvpResult.getMsg()).orElse("服务器内部出现问题")));
|
|
|
+ if(wvpResult.getCode()!=HttpStatus.OK.value()){
|
|
|
+ return Mono.error(new BusinessException(Optional.ofNullable(wvpResult.getMsg()).orElse("点播失败")));
|
|
|
}
|
|
|
- return Mono.just(response);
|
|
|
+ return Mono.just(wvpResult);
|
|
|
})
|
|
|
-
|
|
|
// 点播结束时调用截图接口
|
|
|
- .doOnNext(response -> {
|
|
|
+ .doOnNext(wvpResult -> {
|
|
|
try {
|
|
|
String classPath = ResourceUtils.getURL("classpath:").getPath();
|
|
|
// System.out.println(classPath);
|
|
|
@@ -197,10 +194,9 @@ public class LocalPlayService {
|
|
|
path = path.substring(1);
|
|
|
}
|
|
|
String fileName = deviceId + "_" + channelId + ".jpg";
|
|
|
- WVPResult wvpResult = (WVPResult)response.getBody();
|
|
|
- if (Objects.requireNonNull(wvpResult).getCode() == 0) {
|
|
|
+ if (Objects.requireNonNull(wvpResult).getCode() == HttpStatus.OK.value()) {
|
|
|
StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
|
|
|
- MediaServerItem mediaInfo = mediaServerItemService.getOne(streamInfoForSuccess.getMediaServerId());
|
|
|
+ MediaServerItem mediaInfo = mediaServerItemService.getOneByServerId(streamInfoForSuccess.getMediaServerId());
|
|
|
String streamUrl = streamInfoForSuccess.getFmp4();
|
|
|
// 请求截图
|
|
|
log.info("[请求截图]: " + fileName);
|
|
|
@@ -211,32 +207,31 @@ public class LocalPlayService {
|
|
|
throw new BusinessException("设备上传视频截图文件找不到");
|
|
|
}
|
|
|
})
|
|
|
+ .switchIfEmpty(Mono.error(new BusinessException("点播超时")))
|
|
|
//保证接下来的操作流仅被触发一次
|
|
|
- .filter(ignore->!start.get())
|
|
|
- .doOnNext(ignore->start.set(true))
|
|
|
- .doOnError(BusinessException.class, e -> cmder.streamByeCmd(deviceId, channelId).subscribe())
|
|
|
- .single();
|
|
|
+ .doOnError(BusinessException.class, e -> cmder.streamByeCmd(deviceId, channelId).subscribe());
|
|
|
}
|
|
|
|
|
|
|
|
|
- public Mono<Void> onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String msgId) {
|
|
|
+ private Mono<Void> onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String msgId) {
|
|
|
RequestMessage msg = RequestMessage.of(deviceId, channelId, msgId, null);
|
|
|
return onPublishHandler(mediaServerItem, resonse, deviceId, channelId, msgId)
|
|
|
+ .switchIfEmpty(Mono.fromRunnable(()->{
|
|
|
+ log.warn("设备预览API调用失败!");
|
|
|
+ clusterEventBus.publish(DeferredResultHolder.getCmdCallBackTopic(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
|
|
|
+ WVPResult.of(HttpStatus.INTERNAL_SERVER_ERROR.value(),"设备预览API调用失败",null));
|
|
|
+ }))
|
|
|
.flatMap(streamInfo ->
|
|
|
deviceChannelService.startPlay(deviceId, channelId, streamInfo.getStreamId())
|
|
|
- .concatWith(Mono.fromRunnable(()->{
|
|
|
+ .mergeWith(Mono.fromRunnable(()->{
|
|
|
redisCatchStorage.startPlay(streamInfo);
|
|
|
WVPResult<StreamInfo> wvpResult = WVPResult.of(HttpStatus.OK.value(), "success", streamInfo);
|
|
|
- clusterEventBus.publish(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
|
|
|
- ResponseEntity.ok(wvpResult));
|
|
|
+ clusterEventBus.publish(DeferredResultHolder.getCmdCallBackTopic(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
|
|
|
+ wvpResult);
|
|
|
}))
|
|
|
.then()
|
|
|
- )
|
|
|
- .switchIfEmpty(Mono.fromRunnable(()->{
|
|
|
- log.warn("设备预览API调用失败!");
|
|
|
- clusterEventBus.publish(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
|
|
|
- ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(WVPResult.of(HttpStatus.INTERNAL_SERVER_ERROR.value(),"设备预览API调用失败",null)));
|
|
|
- }));
|
|
|
+ );
|
|
|
+
|
|
|
}
|
|
|
|
|
|
|