|
|
@@ -2,6 +2,7 @@ package org.jetlinks.community.media.controller;
|
|
|
|
|
|
import cn.hutool.json.JSONObject;
|
|
|
import com.google.common.collect.Maps;
|
|
|
+import gov.nist.javax.sip.stack.SIPDialog;
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
|
import io.swagger.v3.oas.annotations.Parameter;
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
@@ -16,18 +17,23 @@ import org.jetlinks.community.media.bean.StreamInfo;
|
|
|
import org.jetlinks.community.media.entity.MediaDevice;
|
|
|
import org.jetlinks.community.media.gb28181.result.PlayResult;
|
|
|
import org.jetlinks.community.media.gb28181.result.WVPResult;
|
|
|
+import org.jetlinks.community.media.message.MediaMessage;
|
|
|
import org.jetlinks.community.media.message.MediaMessageReply;
|
|
|
import org.jetlinks.community.media.service.LocalMediaDeviceChannelService;
|
|
|
import org.jetlinks.community.media.service.LocalMediaDeviceService;
|
|
|
import org.jetlinks.community.media.service.LocalMediaServerItemService;
|
|
|
import org.jetlinks.community.media.service.LocalPlayService;
|
|
|
+import org.jetlinks.community.media.session.VideoStreamSessionManager;
|
|
|
import org.jetlinks.community.media.storage.impl.RedisCacheStorageImpl;
|
|
|
import org.jetlinks.community.media.transmit.callback.DeferredResultHolder;
|
|
|
import org.jetlinks.community.media.transmit.callback.RequestMessage;
|
|
|
import org.jetlinks.community.media.transmit.cmd.SipCommander;
|
|
|
+import org.jetlinks.community.utils.SubscribeKeyGenerate;
|
|
|
import org.jetlinks.core.device.DeviceOperationBroker;
|
|
|
import org.jetlinks.core.device.DeviceRegistry;
|
|
|
import org.jetlinks.core.device.StandaloneDeviceMessageBroker;
|
|
|
+import org.jetlinks.core.exception.DeviceOperationException;
|
|
|
+import org.jetlinks.core.message.DeviceMessageReply;
|
|
|
import org.jetlinks.core.server.MessageHandler;
|
|
|
import org.springframework.http.ResponseEntity;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
@@ -36,6 +42,7 @@ import reactor.core.publisher.Flux;
|
|
|
import reactor.core.publisher.Mono;
|
|
|
import reactor.util.function.Tuple2;
|
|
|
|
|
|
+import java.time.Duration;
|
|
|
import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
@@ -56,12 +63,13 @@ import java.util.stream.Collectors;
|
|
|
public class MediaDeviceController implements ReactiveServiceCrudController<MediaDevice, String>,DeferredController{
|
|
|
private final LocalMediaDeviceService mediaDeviceService;
|
|
|
private final RedisCacheStorageImpl redisCacheStorage;
|
|
|
- private final DeferredResultHolder resultHolder;
|
|
|
private final SipCommander cmder;
|
|
|
private final LocalPlayService playService;
|
|
|
private final LocalMediaDeviceChannelService deviceChannelService;
|
|
|
private final LocalMediaServerItemService mediaServerItemService;
|
|
|
private final DeviceRegistry registry;
|
|
|
+ private final StandaloneDeviceMessageBroker messageBroker;
|
|
|
+ private final VideoStreamSessionManager streamSessionManager;
|
|
|
@Override
|
|
|
public ReactiveCrudService<MediaDevice, String> getService() {
|
|
|
return mediaDeviceService;
|
|
|
@@ -85,131 +93,111 @@ public class MediaDeviceController implements ReactiveServiceCrudController<Medi
|
|
|
//获取设备相连的媒体流服务器信息
|
|
|
.flatMap(playService::getNewMediaServerItem)
|
|
|
.switchIfEmpty(Mono.error(new BusinessException("未找到可用的zlm媒体服务器")))
|
|
|
- .flatMapMany(mediaServerItem ->playService.play(mediaServerItem, deviceId, channelId, null, null)
|
|
|
- .map(MediaMessageReply::getResult));
|
|
|
+ .flatMapMany(mediaServerItem -> {
|
|
|
+ String key = SubscribeKeyGenerate.getSubscribeKey(DeferredResultHolder.CALLBACK_CMD_PLAY,deviceId,channelId);
|
|
|
+ return messageBroker.handleReply(deviceId,key,Duration.ofSeconds(10))
|
|
|
+ .onErrorResume(DeviceOperationException.class,error->
|
|
|
+ //超时响应处理
|
|
|
+ Mono.defer(()->{
|
|
|
+ log.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId));
|
|
|
+ MediaMessageReply messageReply = new MediaMessageReply();
|
|
|
+ messageReply.setDeviceId(deviceId);
|
|
|
+ messageReply.setMessageId(key);
|
|
|
+ messageReply.setSuccess(false);
|
|
|
+ SIPDialog dialog = streamSessionManager.getDialog(deviceId, channelId);
|
|
|
+ if (dialog != null) {
|
|
|
+ messageReply.setErrMsg("收流超时,请稍候重试");
|
|
|
+ }else {
|
|
|
+ messageReply.setErrMsg("点播超时,请稍候重试");
|
|
|
+ }
|
|
|
+ // 点播超时回复BYE
|
|
|
+ return cmder.streamByeCmd(deviceId, channelId)
|
|
|
+ .doOnNext(ignore->
|
|
|
+ // 释放rtpserver
|
|
|
+ mediaServerItemService.closeRTPServer(deviceId, channelId))
|
|
|
+ .flatMap(ignore->
|
|
|
+ // 回复之前所有的点播请求
|
|
|
+ messageBroker.reply(messageReply));
|
|
|
+ }).then(Mono.error(error))
|
|
|
+ )
|
|
|
+ .mergeWith(playService.play(mediaServerItem, deviceId, channelId, null, null).thenMany(Flux.empty()))
|
|
|
+ .flatMap(this::convertReply);
|
|
|
+ })
|
|
|
+ ;
|
|
|
}
|
|
|
|
|
|
|
|
|
@QueryAction
|
|
|
@Operation(summary = "停止点播")
|
|
|
@GetMapping("/{deviceId}/{channelId}/_stop")
|
|
|
- public Mono<Object> playStop(@PathVariable String deviceId, @PathVariable String channelId) {
|
|
|
+ public Flux<Object> playStop(@PathVariable String deviceId, @PathVariable String channelId) {
|
|
|
if(log.isDebugEnabled()){
|
|
|
log.debug(String.format("设备预览/回放停止API调用,streamId:%s_%s", deviceId, channelId ));
|
|
|
}
|
|
|
- String uuid = cn.hutool.core.lang.UUID.randomUUID().toString();
|
|
|
- DeferredResult<ResponseEntity<String>> result = new DeferredResult<>();
|
|
|
// 录像查询以channelId作为deviceId查询
|
|
|
String key = DeferredResultHolder.CALLBACK_CMD_STOP + deviceId + channelId;
|
|
|
- resultHolder.put(key, uuid, result);
|
|
|
- PlayResult<String> playResult = new PlayResult<String>();
|
|
|
- playResult.setResult(result);
|
|
|
- // 超时处理
|
|
|
- playResult.onTimeout(Mono.fromRunnable(()->{
|
|
|
- log.warn(String.format("设备预览/回放停止响应超时,deviceId/channelId:%s_%s ", deviceId, channelId));
|
|
|
- RequestMessage msg = new RequestMessage();
|
|
|
- msg.setId(uuid);
|
|
|
- msg.setKey(key);
|
|
|
- msg.setData("设备预览/回放停止响应超时");
|
|
|
- resultHolder.invokeAllResult(msg);
|
|
|
- }));
|
|
|
-
|
|
|
- return Mono.justOrEmpty(redisCacheStorage.getDevice(deviceId))
|
|
|
- .flatMap(device->
|
|
|
- Mono.zip(cmder.streamByeCmd(deviceId, channelId, (event) -> {
|
|
|
- StreamInfo streamInfo = redisCacheStorage.queryPlayByDevice(deviceId, channelId);
|
|
|
- if (streamInfo == null) {
|
|
|
- RequestMessage msg = new RequestMessage();
|
|
|
- msg.setId(uuid);
|
|
|
- msg.setKey(key);
|
|
|
- msg.setData("点播未找到");
|
|
|
- resultHolder.invokeAllResult(msg);
|
|
|
- deviceChannelService.stopPlay(deviceId, channelId).subscribe();
|
|
|
- }else {
|
|
|
- redisCacheStorage.stopPlay(streamInfo);
|
|
|
- deviceChannelService.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()).subscribe();
|
|
|
- RequestMessage msg = new RequestMessage();
|
|
|
- msg.setId(uuid);
|
|
|
- msg.setKey(key);
|
|
|
- msg.setData("success");
|
|
|
- resultHolder.invokeAllResult(msg);
|
|
|
- }
|
|
|
- mediaServerItemService.closeRTPServer(device, channelId);
|
|
|
- })
|
|
|
- .mergeWith(Mono.fromRunnable(()->{
|
|
|
- if (deviceId != null || channelId != null) {
|
|
|
- JSONObject json = new JSONObject()
|
|
|
- .putOpt("deviceId", deviceId)
|
|
|
- .putOpt("channelId", channelId);
|
|
|
- RequestMessage msg = new RequestMessage();
|
|
|
- msg.setId(uuid);
|
|
|
- msg.setKey(key);
|
|
|
- msg.setData(json.toString());
|
|
|
- resultHolder.invokeAllResult(msg);
|
|
|
- } else {
|
|
|
- log.warn("设备预览/回放停止API调用失败!");
|
|
|
- RequestMessage msg = new RequestMessage();
|
|
|
- msg.setId(uuid);
|
|
|
- msg.setKey(key);
|
|
|
- msg.setData("streamId null");
|
|
|
- resultHolder.invokeAllResult(msg);
|
|
|
- }
|
|
|
- }))
|
|
|
- .then(),Mono.just(deferredResultHandler(playResult)))
|
|
|
- )
|
|
|
- .flatMap(Tuple2::getT2);
|
|
|
+ MediaMessage mediaMessage = new MediaMessage();
|
|
|
+ mediaMessage.setDeviceId(deviceId);
|
|
|
+ mediaMessage.setMessageId(key);
|
|
|
+ return messageBroker.handleReply(deviceId,key, Duration.ofSeconds(10))
|
|
|
+ .onErrorResume(DeviceOperationException.class,error->
|
|
|
+ Mono.error(new BusinessException("设备预览/回放停止响应超时")))
|
|
|
+ .mergeWith(
|
|
|
+ Mono.justOrEmpty(redisCacheStorage.getDevice(deviceId))
|
|
|
+ .flatMap(device->
|
|
|
+ cmder.streamByeCmd(deviceId, channelId, (event) -> {
|
|
|
+ MediaMessageReply<String> mediaMessageReply = new MediaMessageReply<>();
|
|
|
+ mediaMessageReply.setMessageId(key);
|
|
|
+ mediaMessage.setDeviceId(deviceId);
|
|
|
+ StreamInfo streamInfo = redisCacheStorage.queryPlayByDevice(deviceId, channelId);
|
|
|
+ if(streamInfo!=null){
|
|
|
+ redisCacheStorage.stopPlay(streamInfo);
|
|
|
+ }
|
|
|
+ messageBroker.reply(mediaMessageReply)
|
|
|
+ .then(deviceChannelService.stopPlay(deviceId, channelId))
|
|
|
+ .doOnNext(ignore -> mediaServerItemService.closeRTPServer(device, channelId))
|
|
|
+ .subscribe();
|
|
|
+ }).then(Mono.empty())))
|
|
|
+ .flatMap(this::convertReply);
|
|
|
}
|
|
|
|
|
|
@PostMapping("/{deviceId}/channels/_sync")
|
|
|
@CreateAction
|
|
|
@Operation(summary = "更新通道")
|
|
|
- public Mono<Object> getDeviceDetailInfo(@PathVariable("deviceId") @Parameter(description = "设备ID") String id) {
|
|
|
+ public Flux<Object> getDeviceDetailInfo(@PathVariable("deviceId") @Parameter(description = "设备ID") String id) {
|
|
|
if (log.isDebugEnabled()) {
|
|
|
log.debug("设备通道信息同步API调用,deviceId:" + id);
|
|
|
}
|
|
|
MediaDevice device = redisCacheStorage.getDevice(id);
|
|
|
if(device==null){
|
|
|
- return Mono.error(new BusinessException("设备已离线,无法更新通道最新信息"));
|
|
|
+ return Flux.error(new BusinessException("设备已离线,无法更新通道最新信息"));
|
|
|
}
|
|
|
- PlayResult<MediaDevice> playResult = new PlayResult<>();
|
|
|
String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + id;
|
|
|
- String uuid = UUID.randomUUID().toString();
|
|
|
- //默认超时时间为30分钟
|
|
|
- DeferredResult<ResponseEntity<MediaDevice>> result = new DeferredResult<>();
|
|
|
- playResult.setResult(result);
|
|
|
- playResult.onTimeout(Mono.fromRunnable(()->{
|
|
|
- log.warn("设备[{}]通道信息同步超时", id);
|
|
|
- // 释放rtpserver
|
|
|
- RequestMessage msg = new RequestMessage();
|
|
|
- msg.setKey(key);
|
|
|
- msg.setId(uuid);
|
|
|
- WVPResult<Object> wvpResult = new WVPResult<>();
|
|
|
- wvpResult.setCode(-1);
|
|
|
- wvpResult.setData(device);
|
|
|
- wvpResult.setMsg("设备响应超时,请检查设备是否在线或网络是否通畅");
|
|
|
- msg.setData(wvpResult);
|
|
|
- //设备下线
|
|
|
- mediaDeviceService.deviceOffline(device);
|
|
|
- resultHolder.invokeAllResult(msg);
|
|
|
- }));
|
|
|
- // 等待其他相同请求返回时一起返回
|
|
|
- if (resultHolder.exist(key, null)) {
|
|
|
- return deferredResultHandler(playResult);
|
|
|
- }
|
|
|
- resultHolder.put(key, uuid, result);
|
|
|
- return cmder.catalogQuery(device, event -> {
|
|
|
- RequestMessage msg = new RequestMessage();
|
|
|
- msg.setKey(key);
|
|
|
- msg.setId(uuid);
|
|
|
- WVPResult<Object> wvpResult = new WVPResult<>();
|
|
|
- wvpResult.setCode(-1);
|
|
|
- wvpResult.setData(device);
|
|
|
- wvpResult.setMsg(String.format("同步通道失败,错误码: %s, %s", event.statusCode, event.msg));
|
|
|
- msg.setData(wvpResult);
|
|
|
- resultHolder.invokeAllResult(msg);
|
|
|
- })
|
|
|
- .thenReturn(1L)
|
|
|
- .flatMap(ignore->deferredResultHandler(playResult));
|
|
|
+ return messageBroker.handleReply(id,key,Duration.ofSeconds(10))
|
|
|
+ .onErrorResume(DeviceOperationException.class,error-> {
|
|
|
+ //设备下线
|
|
|
+ mediaDeviceService.deviceOffline(device);
|
|
|
+ return Mono.error(new BusinessException("设备响应超时"));
|
|
|
+ })
|
|
|
+ .mergeWith(cmder.catalogQuery(device, event -> {
|
|
|
+ MediaMessageReply<String> reply = MediaMessageReply.of(String.format("同步通道失败,错误码: %s, %s", event.statusCode, event.msg), null);
|
|
|
+ reply.setSuccess(false);
|
|
|
+ reply.setMessageId(key);
|
|
|
+ messageBroker.reply(reply).subscribe();
|
|
|
+ })
|
|
|
+ .then(Mono.empty()))
|
|
|
+ .flatMap(this::convertReply);
|
|
|
}
|
|
|
|
|
|
+ private Flux<Object> convertReply(DeviceMessageReply reply){
|
|
|
+ if(reply instanceof MediaMessageReply){
|
|
|
+ MediaMessageReply<Object> messageReply= (MediaMessageReply<Object>) reply;
|
|
|
+ if(messageReply.isSuccess()){
|
|
|
+ return Flux.just(messageReply.getResult());
|
|
|
+ }else {
|
|
|
+ return Flux.error(new BusinessException(Optional.ofNullable(messageReply.getErrMsg()).orElse("响应错误,请重试")));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return Flux.error(new BusinessException("服务器响应错误"));
|
|
|
+ }
|
|
|
}
|