使用设备Id+通道Id作为session的识别标识,解决点播异常时无法释放session的问题

pull/93/head
panlinlin 2021-04-16 17:52:30 +08:00
parent 760f1f4d94
commit 2b3b7dbc79
15 changed files with 87 additions and 78 deletions

View File

@ -16,6 +16,7 @@ public class VideoStreamSessionManager {
private ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, String> ssrcMap = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, String> ssrcMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, String> streamIdMap = new ConcurrentHashMap<>();
public String createPlaySsrc(){ public String createPlaySsrc(){
return SsrcUtil.getPlaySsrc(); return SsrcUtil.getPlaySsrc();
@ -25,18 +26,23 @@ public class VideoStreamSessionManager {
return SsrcUtil.getPlayBackSsrc(); return SsrcUtil.getPlayBackSsrc();
} }
public void put(String streamId,String ssrc,ClientTransaction transaction){ public void put(String deviceId, String channelId ,String ssrc, String streamId, ClientTransaction transaction){
sessionMap.put(streamId, transaction); sessionMap.put(deviceId + "_" + channelId, transaction);
ssrcMap.put(streamId, ssrc); ssrcMap.put(deviceId + "_" + channelId, ssrc);
streamIdMap.put(deviceId + "_" + channelId, streamId);
} }
public ClientTransaction get(String streamId){ public ClientTransaction getTransaction(String deviceId, String channelId){
return sessionMap.get(streamId); return sessionMap.get(deviceId + "_" + channelId);
}
public String getStreamId(String deviceId, String channelId){
return streamIdMap.get(deviceId + "_" + channelId);
} }
public void remove(String streamId) { public void remove(String deviceId, String channelId) {
sessionMap.remove(streamId); sessionMap.remove(deviceId + "_" + channelId);
SsrcUtil.releaseSsrc(ssrcMap.get(streamId)); SsrcUtil.releaseSsrc(ssrcMap.get(deviceId + "_" + channelId));
ssrcMap.remove(streamId); ssrcMap.remove(deviceId + "_" + channelId);
} }
} }

View File

@ -87,7 +87,6 @@ public interface ISIPCommander {
/** /**
* *
*
* @param device * @param device
* @param channelId * @param channelId
*/ */
@ -108,8 +107,8 @@ public interface ISIPCommander {
* *
* @param ssrc ssrc * @param ssrc ssrc
*/ */
void streamByeCmd(String ssrc, SipSubscribe.Event okEvent); void streamByeCmd(String deviceId, String channelId, SipSubscribe.Event okEvent);
void streamByeCmd(String ssrc); void streamByeCmd(String deviceId, String channelId);
/** /**
* 广 * 广

View File

@ -332,17 +332,17 @@ public class SIPCommander implements ISIPCommander {
/** /**
* *
* @param device * @param device
* @param channelId * @param channelId
* @param event hook * @param event hook
* @param errorEvent sip * @param errorEvent sip
*/ */
@Override @Override
public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) { public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
String streamId = null;
try { try {
if (device == null) return; if (device == null) return;
String ssrc = streamSession.createPlaySsrc(); String ssrc = streamSession.createPlaySsrc();
String streamId = null;
if (rtpEnable) { if (rtpEnable) {
streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId); streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
}else { }else {
@ -444,9 +444,12 @@ public class SIPCommander implements ISIPCommander {
Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "FromInvt" + tm, null, ssrc, callIdHeader); Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "FromInvt" + tm, null, ssrc, callIdHeader);
ClientTransaction transaction = transmitRequest(device, request, errorEvent); ClientTransaction transaction = transmitRequest(device, request, (e -> {
streamSession.put(streamId,ssrc, transaction); streamSession.remove(device.getDeviceId(), channelId);
errorEvent.response(e);
}));
streamSession.put(device.getDeviceId(), channelId ,ssrc,streamId, transaction);
} catch ( SipException | ParseException | InvalidArgumentException e) { } catch ( SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -552,7 +555,7 @@ public class SIPCommander implements ISIPCommander {
Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader); Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader);
ClientTransaction transaction = transmitRequest(device, request, errorEvent); ClientTransaction transaction = transmitRequest(device, request, errorEvent);
streamSession.put(streamId, ssrc, transaction); streamSession.put(device.getDeviceId(), channelId, ssrc, streamId, transaction);
} catch ( SipException | ParseException | InvalidArgumentException e) { } catch ( SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace(); e.printStackTrace();
@ -566,17 +569,17 @@ public class SIPCommander implements ISIPCommander {
* *
*/ */
@Override @Override
public void streamByeCmd(String ssrc) { public void streamByeCmd(String deviceId, String channelId) {
streamByeCmd(ssrc, null); streamByeCmd(deviceId, channelId, null);
} }
@Override @Override
public void streamByeCmd(String streamId, SipSubscribe.Event okEvent) { public void streamByeCmd(String deviceId, String channelId, SipSubscribe.Event okEvent) {
try { try {
ClientTransaction transaction = streamSession.get(streamId); ClientTransaction transaction = streamSession.getTransaction(deviceId, channelId);
// 服务重启后 // 服务重启后
if (transaction == null) { if (transaction == null) {
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
if (streamInfo != null) { if (streamInfo != null) {
} }
@ -613,14 +616,9 @@ public class SIPCommander implements ISIPCommander {
} }
dialog.sendRequest(clientTransaction); dialog.sendRequest(clientTransaction);
zlmrtpServerFactory.closeRTPServer(streamSession.getStreamId(deviceId, channelId));
streamSession.remove(streamId); streamSession.remove(deviceId, channelId);
zlmrtpServerFactory.closeRTPServer(streamId); } catch (SipException | ParseException e) {
} catch (TransactionDoesNotExistException e) {
e.printStackTrace();
} catch (SipException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@ -641,7 +639,6 @@ public class SIPCommander implements ISIPCommander {
* 广 * 广
* *
* @param device * @param device
* @param channelId
*/ */
@Override @Override
public boolean audioBroadcastCmd(Device device) { public boolean audioBroadcastCmd(Device device) {
@ -1140,7 +1137,7 @@ public class SIPCommander implements ISIPCommander {
* @param device * @param device
* @param startPriority * @param startPriority
* @param endPriority * @param endPriority
* @param alarmMethods * @param alarmMethod
* @param alarmType * @param alarmType
* @param startTime * @param startTime
* @param endTime * @param endTime
@ -1428,5 +1425,6 @@ public class SIPCommander implements ISIPCommander {
String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId); String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
zlmrtpServerFactory.closeRTPServer(streamId); zlmrtpServerFactory.closeRTPServer(streamId);
} }
streamSession.remove(device.getDeviceId(), channelId);
} }
} }

View File

@ -58,7 +58,7 @@ public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
redisCatchStorage.deleteSendRTPServer(platformGbId, channelId); redisCatchStorage.deleteSendRTPServer(platformGbId, channelId);
if (zlmrtpServerFactory.totalReaderCount(sendRtpItem.getApp(), streamId) == 0) { if (zlmrtpServerFactory.totalReaderCount(sendRtpItem.getApp(), streamId) == 0) {
System.out.println(streamId + "无其它观看者,通知设备停止推流"); System.out.println(streamId + "无其它观看者,通知设备停止推流");
cmder.streamByeCmd(streamId); cmder.streamByeCmd(sendRtpItem.getDeviceId(), channelId);
} }
} }
} catch (SipException e) { } catch (SipException e) {

View File

@ -922,7 +922,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, "*"); StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, "*");
if (streamInfo != null) { if (streamInfo != null) {
redisCatchStorage.stopPlayback(streamInfo); redisCatchStorage.stopPlayback(streamInfo);
cmder.streamByeCmd(streamInfo.getStreamId()); cmder.streamByeCmd(streamInfo.getDeviceID(), streamInfo.getChannelId());
} }
} }
} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) { } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {

View File

@ -306,12 +306,12 @@ public class ZLMHttpHookListener {
if (redisCatchStorage.isChannelSendingRTP(streamInfo.getChannelId())) { if (redisCatchStorage.isChannelSendingRTP(streamInfo.getChannelId())) {
ret.put("close", false); ret.put("close", false);
} else { } else {
cmder.streamByeCmd(streamId); cmder.streamByeCmd(streamInfo.getDeviceID(), streamInfo.getChannelId());
redisCatchStorage.stopPlay(streamInfo); redisCatchStorage.stopPlay(streamInfo);
storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
} }
}else{ }else{
cmder.streamByeCmd(streamId); cmder.streamByeCmd(streamInfo.getDeviceID(), streamInfo.getChannelId());
streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId); streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
redisCatchStorage.stopPlayback(streamInfo); redisCatchStorage.stopPlayback(streamInfo);
} }

View File

@ -63,7 +63,16 @@ public class PlayServiceImpl implements IPlayService {
playResult.setResult(result); playResult.setResult(result);
// 录像查询以channelId作为deviceId查询 // 录像查询以channelId作为deviceId查询
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result); resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
// 超时处理
result.onTimeout(()->{
logger.warn(String.format("设备点播超时deviceId%s channelId%s", deviceId, channelId));
// 释放rtpserver
cmder.closeRTPServer(playResult.getDevice(), channelId);
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid());
msg.setData("Timeout");
resultHolder.invokeResult(msg);
});
if (streamInfo == null) { if (streamInfo == null) {
// 发送点播消息 // 发送点播消息
cmder.playStreamCmd(device, channelId, (JSONObject response) -> { cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
@ -76,6 +85,7 @@ public class PlayServiceImpl implements IPlayService {
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
Response response = event.getResponse(); Response response = event.getResponse();
cmder.closeRTPServer(playResult.getDevice(), channelId);
msg.setData(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase())); msg.setData(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
if (errorEvent != null) { if (errorEvent != null) {
@ -107,6 +117,7 @@ public class PlayServiceImpl implements IPlayService {
logger.info("收到订阅消息: " + response.toJSONString()); logger.info("收到订阅消息: " + response.toJSONString());
onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString()); onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
}, event -> { }, event -> {
cmder.closeRTPServer(playResult.getDevice(), channelId);
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
Response response = event.getResponse(); Response response = event.getResponse();

View File

@ -36,7 +36,7 @@ public interface IRedisCatchStorage {
StreamInfo queryPlaybackByStreamId(String steamId); StreamInfo queryPlaybackByStreamId(String steamId);
StreamInfo queryPlayByDevice(String deviceId, String code); StreamInfo queryPlayByDevice(String deviceId, String channelId);
/** /**
* *

View File

@ -75,11 +75,11 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
} }
@Override @Override
public StreamInfo queryPlayByDevice(String deviceId, String code) { public StreamInfo queryPlayByDevice(String deviceId, String channelId) {
// List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, // List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
deviceId, deviceId,
code)); channelId));
if (playLeys == null || playLeys.size() == 0) return null; if (playLeys == null || playLeys.size() == 0) return null;
return (StreamInfo)redis.get(playLeys.get(0).toString()); return (StreamInfo)redis.get(playLeys.get(0).toString());
} }

View File

@ -75,27 +75,19 @@ public class PlayController {
PlayResult playResult = playService.play(deviceId, channelId, null, null); PlayResult playResult = playService.play(deviceId, channelId, null, null);
// 超时处理
playResult.getResult().onTimeout(()->{
logger.warn(String.format("设备点播超时deviceId%s channelId%s", deviceId, channelId));
// 释放rtpserver
cmder.closeRTPServer(playResult.getDevice(), channelId);
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid());
msg.setData("Timeout");
resultHolder.invokeResult(msg);
});
return playResult.getResult(); return playResult.getResult();
} }
@ApiOperation("停止点播") @ApiOperation("停止点播")
@ApiImplicitParams({ @ApiImplicitParams({
@ApiImplicitParam(name = "streamId", value = "视频流ID", dataTypeClass = String.class), @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
@ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class),
}) })
@GetMapping("/stop/{streamId}") @GetMapping("/stop/{deviceId}/{channelId}")
public DeferredResult<ResponseEntity<String>> playStop(@PathVariable String streamId) { public DeferredResult<ResponseEntity<String>> playStop(@PathVariable String deviceId, @PathVariable String channelId) {
logger.debug(String.format("设备预览/回放停止API调用streamId%s", streamId)); logger.debug(String.format("设备预览/回放停止API调用streamId%s/$s", deviceId, channelId ));
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(); DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>();
@ -103,8 +95,8 @@ public class PlayController {
// 录像查询以channelId作为deviceId查询 // 录像查询以channelId作为deviceId查询
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_STOP + uuid, result); resultHolder.put(DeferredResultHolder.CALLBACK_CMD_STOP + uuid, result);
cmder.streamByeCmd(streamId, event -> { cmder.streamByeCmd(deviceId, channelId, event -> {
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
if (streamInfo == null) { if (streamInfo == null) {
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
@ -121,9 +113,10 @@ public class PlayController {
} }
}); });
if (streamId != null) { if (deviceId != null || channelId != null) {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
json.put("streamId", streamId); json.put("deviceId", deviceId);
json.put("channelId", channelId);
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
msg.setData(json.toString()); msg.setData(json.toString());
@ -138,7 +131,7 @@ public class PlayController {
// 超时处理 // 超时处理
result.onTimeout(()->{ result.onTimeout(()->{
logger.warn(String.format("设备预览/回放停止超时,streamId%s ", streamId)); logger.warn(String.format("设备预览/回放停止超时,deviceId/channelId%s/$s ", deviceId, channelId));
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_STOP + uuid); msg.setId(DeferredResultHolder.CALLBACK_CMD_STOP + uuid);
msg.setData("Timeout"); msg.setData("Timeout");

View File

@ -84,7 +84,7 @@ public class PlaybackController {
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId); StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId);
if (streamInfo != null) { if (streamInfo != null) {
// 停止之前的回放 // 停止之前的回放
cmder.streamByeCmd(streamInfo.getStreamId()); cmder.streamByeCmd(deviceId, channelId);
} }
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result); resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> { cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> {
@ -103,20 +103,22 @@ public class PlaybackController {
@ApiOperation("停止视频回放") @ApiOperation("停止视频回放")
@ApiImplicitParams({ @ApiImplicitParams({
@ApiImplicitParam(name = "ssrc", value = "视频流标识", dataTypeClass = String.class), @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
@ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class),
}) })
@GetMapping("/stop/{ssrc}") @GetMapping("/stop/{deviceId}/{channelId}")
public ResponseEntity<String> playStop(@PathVariable String ssrc) { public ResponseEntity<String> playStop(@PathVariable String deviceId, @PathVariable String channelId) {
cmder.streamByeCmd(ssrc); cmder.streamByeCmd(deviceId, channelId);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("设备录像回放停止 API调用ssrc%s", ssrc)); logger.debug(String.format("设备录像回放停止 API调用deviceId/channelId%s/%s", deviceId, channelId));
} }
if (ssrc != null) { if (deviceId != null && channelId != null) {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
json.put("ssrc", ssrc); json.put("deviceId", deviceId);
json.put("channelId", channelId);
return new ResponseEntity<String>(json.toString(), HttpStatus.OK); return new ResponseEntity<String>(json.toString(), HttpStatus.OK);
} else { } else {
logger.warn("设备录像回放停止API调用失败"); logger.warn("设备录像回放停止API调用失败");

View File

@ -163,7 +163,7 @@ public class ApiStreamController {
result.put("error","未找到流信息"); result.put("error","未找到流信息");
return result; return result;
} }
cmder.streamByeCmd(streamInfo.getStreamId()); cmder.streamByeCmd(serial, code);
redisCatchStorage.stopPlay(streamInfo); redisCatchStorage.stopPlay(streamInfo);
storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
return null; return null;

View File

@ -1,3 +1,3 @@
spring: spring:
profiles: profiles:
active: dev active: local

View File

@ -216,12 +216,12 @@ export default {
var that = this; var that = this;
this.$axios({ this.$axios({
method: 'get', method: 'get',
url: '/api/play/stop/' + itemData.streamId url: '/api/play/stop/' + this.deviceId + "/" + itemData.channelId
}).then(function (res) { }).then(function (res) {
console.log(JSON.stringify(res)); console.log(JSON.stringify(res));
that.initData(); that.initData();
}).catch(function (error) { }).catch(function (error) {
if (error.response.status == 402) { // if (error.response.status === 402) { //
that.initData(); that.initData();
}else { }else {
console.log(error) console.log(error)
@ -253,7 +253,7 @@ export default {
this.$axios({ this.$axios({
method: 'get', method: 'get',
url:`/api/device/query/sub_channels/${this.deviceId}/${this.parentChannelId}/channels`, url:`/api/device/query/sub_channels/${this.deviceId}/${this.parentChannelId}/channels`,
params: { params: {
page: that.currentPage, page: that.currentPage,
count: that.count, count: that.count,

View File

@ -415,7 +415,7 @@ export default {
this.videoUrl = ''; this.videoUrl = '';
this.$axios({ this.$axios({
method: 'get', method: 'get',
url: '/api/playback/stop/' + this.streamId url: '/api/playback/stop/' + this.deviceId + "/" + this.channelId
}).then(function (res) { }).then(function (res) {
if (callback) callback() if (callback) callback()
}); });