完成向上级联->点播--增加了sdp解析
修复修改平台主键带来的bug
This commit is contained in:
parent
4dc4710870
commit
6d0133d27f
@ -28,6 +28,8 @@ public class VideoManagerConstants {
|
|||||||
|
|
||||||
public static final String PLATFORM_REGISTER_PREFIX = "VMP_platform_register_";
|
public static final String PLATFORM_REGISTER_PREFIX = "VMP_platform_register_";
|
||||||
|
|
||||||
|
public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_platform_register_info_";
|
||||||
|
|
||||||
public static final String Pattern_Topic = "VMP_keeplive_platform_";
|
public static final String Pattern_Topic = "VMP_keeplive_platform_";
|
||||||
|
|
||||||
public static final String EVENT_ONLINE_REGISTER = "1";
|
public static final String EVENT_ONLINE_REGISTER = "1";
|
||||||
|
@ -45,11 +45,11 @@ public class SipPlatformRunner implements CommandLineRunner {
|
|||||||
ParentPlatformCatch parentPlatformCatch = new ParentPlatformCatch();
|
ParentPlatformCatch parentPlatformCatch = new ParentPlatformCatch();
|
||||||
|
|
||||||
parentPlatformCatch.setParentPlatform(parentPlatform);
|
parentPlatformCatch.setParentPlatform(parentPlatform);
|
||||||
parentPlatformCatch.setId(parentPlatform.getDeviceGBId());
|
parentPlatformCatch.setId(parentPlatform.getServerGBId());
|
||||||
redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
|
redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
|
||||||
|
|
||||||
// 发送平台未注册消息
|
// 发送平台未注册消息
|
||||||
publisher.platformNotRegisterEventPublish(parentPlatform.getDeviceGBId());
|
publisher.platformNotRegisterEventPublish(parentPlatform.getServerGBId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ public class ParentPlatformCatch {
|
|||||||
// 注册未回复次数
|
// 注册未回复次数
|
||||||
private int registerAliveReply;
|
private int registerAliveReply;
|
||||||
|
|
||||||
|
private String callId;
|
||||||
|
|
||||||
private ParentPlatform parentPlatform;
|
private ParentPlatform parentPlatform;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@ -43,4 +45,12 @@ public class ParentPlatformCatch {
|
|||||||
public void setParentPlatform(ParentPlatform parentPlatform) {
|
public void setParentPlatform(ParentPlatform parentPlatform) {
|
||||||
this.parentPlatform = parentPlatform;
|
this.parentPlatform = parentPlatform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCallId() {
|
||||||
|
return callId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallId(String callId) {
|
||||||
|
this.callId = callId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
65
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/Codec.java
Executable file
65
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/Codec.java
Executable file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.genersoft.iot.vmp.gb28181.sdp;
|
||||||
|
|
||||||
|
public class Codec {
|
||||||
|
|
||||||
|
private int payloadType;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public int getPayloadType() {
|
||||||
|
return payloadType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPayloadType(int payloadType) {
|
||||||
|
this.payloadType = payloadType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof Codec)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Codec codec = (Codec)obj;
|
||||||
|
if (codec.getName() == null) {
|
||||||
|
return name == null;
|
||||||
|
}
|
||||||
|
return codec.getName().equalsIgnoreCase(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
buf.append(RFC4566_28181.TYPE_ATTRIBUTE).append(RFC4566_28181.SEPARATOR);
|
||||||
|
buf.append(RFC4566_28181.ATTR_RTPMAP).append(RFC4566_28181.ATTR_SEPARATOR);
|
||||||
|
buf.append(payloadType).append(" ").append(name).append("/");
|
||||||
|
buf.append(9000).append("\r\n");
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
123
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/MediaDescription.java
Executable file
123
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/MediaDescription.java
Executable file
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.genersoft.iot.vmp.gb28181.sdp;
|
||||||
|
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.Inet6Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MediaDescription {
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
private InetAddress ipAddress;
|
||||||
|
// attributes not codec-related
|
||||||
|
private Hashtable<String, String> attributes;
|
||||||
|
private int port;
|
||||||
|
private List<Codec> codecs;
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Hashtable<String, String> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Hashtable<String, String> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetAddress getIpAddress() {
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpAddress(InetAddress ipAddress) {
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPort(int port) {
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Codec> getCodecs() {
|
||||||
|
return codecs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodecs(List<Codec> codecs) {
|
||||||
|
this.codecs = codecs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
buf.append(RFC4566_28181.TYPE_MEDIA).append(RFC4566_28181.SEPARATOR);
|
||||||
|
buf.append(type).append(" ").append(port);
|
||||||
|
buf.append(" RTP/AVP");
|
||||||
|
if (codecs != null) {
|
||||||
|
for (Codec codec: codecs) {
|
||||||
|
buf.append(" ");
|
||||||
|
buf.append(codec.getPayloadType());
|
||||||
|
}
|
||||||
|
buf.append("\r\n");
|
||||||
|
}
|
||||||
|
if (ipAddress != null) {
|
||||||
|
int ipVersion;
|
||||||
|
if (ipAddress instanceof Inet4Address) {
|
||||||
|
ipVersion = 4;
|
||||||
|
} else if (ipAddress instanceof Inet6Address) {
|
||||||
|
ipVersion = 6;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("unknown ip version: " + ipAddress);
|
||||||
|
}
|
||||||
|
buf.append(RFC4566_28181.TYPE_CONNECTION).append(RFC4566_28181.SEPARATOR);
|
||||||
|
buf.append("IN IP").append(ipVersion).append(" ");
|
||||||
|
buf.append(ipAddress.getHostAddress()).append("\r\n");
|
||||||
|
}
|
||||||
|
if (codecs != null) {
|
||||||
|
for (Codec codec: codecs) {
|
||||||
|
buf.append(codec.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes != null) {
|
||||||
|
for (String attributeName: attributes.keySet()) {
|
||||||
|
buf.append(RFC4566_28181.TYPE_ATTRIBUTE).append(RFC4566_28181.SEPARATOR);
|
||||||
|
buf.append(attributeName);
|
||||||
|
String attributeValue = attributes.get(attributeName);
|
||||||
|
if (attributeValue != null && !"".equals(attributeValue.trim())) {
|
||||||
|
buf.append(":").append(attributeValue);
|
||||||
|
}
|
||||||
|
buf.append("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
51
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/RFC4566_28181.java
Executable file
51
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/RFC4566_28181.java
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.genersoft.iot.vmp.gb28181.sdp;
|
||||||
|
|
||||||
|
public class RFC4566_28181 {
|
||||||
|
|
||||||
|
public static final char VERSION = '0';
|
||||||
|
|
||||||
|
public static final char TYPE_VERSION = 'v';
|
||||||
|
public static final char TYPE_ORIGIN = 'o';
|
||||||
|
public static final char TYPE_SUBJECT = 's';
|
||||||
|
public static final char TYPE_INFO = 'i';
|
||||||
|
public static final char TYPE_URI = 'u';
|
||||||
|
public static final char TYPE_EMAIL = 'e';
|
||||||
|
public static final char TYPE_PHONE = 'p';
|
||||||
|
public static final char TYPE_CONNECTION = 'c';
|
||||||
|
public static final char TYPE_BANDWITH = 'b';
|
||||||
|
public static final char TYPE_TIME = 't';
|
||||||
|
public static final char TYPE_REPEAT = 'r';
|
||||||
|
public static final char TYPE_ZONE = 'z';
|
||||||
|
public static final char TYPE_KEY = 'k';
|
||||||
|
public static final char TYPE_ATTRIBUTE = 'a';
|
||||||
|
public static final char TYPE_MEDIA = 'm';
|
||||||
|
public static final char TYPE_SSRC = 'y';
|
||||||
|
public static final char TYPE_MEDIA_DES = 'f';
|
||||||
|
|
||||||
|
public static final char SEPARATOR = '=';
|
||||||
|
public static final char ATTR_SEPARATOR = ':';
|
||||||
|
|
||||||
|
public static final String MEDIA_AUDIO = "audio";
|
||||||
|
|
||||||
|
public static final String ATTR_RTPMAP = "rtpmap";
|
||||||
|
public static final String ATTR_SENDRECV = "sendrecv";
|
||||||
|
}
|
38
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/SdpLine.java
Executable file
38
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/SdpLine.java
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.genersoft.iot.vmp.gb28181.sdp;
|
||||||
|
|
||||||
|
public class SdpLine {
|
||||||
|
private char type;
|
||||||
|
private String value;
|
||||||
|
public char getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
public void setType(char type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
230
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/SdpParser.java
Executable file
230
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/SdpParser.java
Executable file
@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.genersoft.iot.vmp.gb28181.sdp;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SdpParser {
|
||||||
|
|
||||||
|
public SessionDescription parse(byte[] body) throws IOException {
|
||||||
|
if (body == null || body.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ByteArrayInputStream in = new ByteArrayInputStream(body);
|
||||||
|
InputStreamReader inputStreamReader = new InputStreamReader(in);
|
||||||
|
BufferedReader reader = new BufferedReader(inputStreamReader);
|
||||||
|
SessionDescription sessionDescription = new SessionDescription();
|
||||||
|
|
||||||
|
//version
|
||||||
|
|
||||||
|
String line = reader.readLine();
|
||||||
|
if (line.length() < 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (line.charAt(0) != RFC4566_28181.TYPE_VERSION
|
||||||
|
|| line.charAt(1) != RFC4566_28181.SEPARATOR
|
||||||
|
|| line.charAt(2) != RFC4566_28181.VERSION) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//origin
|
||||||
|
|
||||||
|
line = reader.readLine();
|
||||||
|
if (line.length() < 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (line.charAt(0) != RFC4566_28181.TYPE_ORIGIN
|
||||||
|
|| line.charAt(1) != RFC4566_28181.SEPARATOR) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
line = line.substring(2);
|
||||||
|
String[] originArr = line.split(" ");
|
||||||
|
if (originArr == null || originArr.length != 6) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
sessionDescription.setUsername(originArr[0]);
|
||||||
|
sessionDescription.setId(Long.parseLong(originArr[1]));
|
||||||
|
sessionDescription.setVersion(Long.parseLong(originArr[2]));
|
||||||
|
sessionDescription.setIpAddress(InetAddress.getByName(originArr[5]));
|
||||||
|
|
||||||
|
//name
|
||||||
|
|
||||||
|
line = reader.readLine();
|
||||||
|
if (line.length() < 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (line.charAt(0) != RFC4566_28181.TYPE_SUBJECT
|
||||||
|
|| line.charAt(1) != RFC4566_28181.SEPARATOR) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
sessionDescription.setName(line.substring(2));
|
||||||
|
|
||||||
|
//session connection and attributes
|
||||||
|
Hashtable<String, String> sessionAttributes = new Hashtable<String, String>();
|
||||||
|
sessionDescription.setAttributes(sessionAttributes);
|
||||||
|
|
||||||
|
while ((line = reader.readLine()) != null
|
||||||
|
&& line.charAt(0) != RFC4566_28181.TYPE_MEDIA) {
|
||||||
|
if (line.length() > 3
|
||||||
|
&& line.charAt(0) == RFC4566_28181.TYPE_CONNECTION
|
||||||
|
&& line.charAt(1) == RFC4566_28181.SEPARATOR) {
|
||||||
|
String connection = parseConnection(line.substring(2));
|
||||||
|
if (connection == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sessionDescription.setIpAddress(InetAddress.getByName(connection));
|
||||||
|
} else if (line.length() > 3
|
||||||
|
&& line.charAt(0) == RFC4566_28181.TYPE_ATTRIBUTE
|
||||||
|
&& line.charAt(1) == RFC4566_28181.SEPARATOR) {
|
||||||
|
String value = line.substring(2);
|
||||||
|
int pos = value.indexOf(RFC4566_28181.ATTR_SEPARATOR);
|
||||||
|
if (pos > -1) {
|
||||||
|
sessionAttributes.put(value.substring(0, pos),
|
||||||
|
value.substring(pos + 1));
|
||||||
|
} else {
|
||||||
|
sessionAttributes.put(value, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (line == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
//we are at the first media line
|
||||||
|
|
||||||
|
ArrayList<SdpLine> mediaLines = new ArrayList<SdpLine>();
|
||||||
|
do {
|
||||||
|
if (line.length() < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (line.charAt(1) != RFC4566_28181.SEPARATOR) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (line.charAt(0) == RFC4566_28181.TYPE_SSRC) {
|
||||||
|
sessionDescription.setSsrc(line.length() >=2 ?line.substring(2):"");
|
||||||
|
}else if (line.charAt(0) == RFC4566_28181.TYPE_MEDIA_DES) {
|
||||||
|
sessionDescription.setGbMediaDescriptions(line.length() >=2 ?line.substring(2):"");
|
||||||
|
}else {
|
||||||
|
SdpLine mediaLine = new SdpLine();
|
||||||
|
mediaLine.setType(line.charAt(0));
|
||||||
|
mediaLine.setValue(line.substring(2));
|
||||||
|
mediaLines.add(mediaLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
while ((line = reader.readLine()) != null );
|
||||||
|
|
||||||
|
ArrayList<MediaDescription> mediaDescriptions = new ArrayList<MediaDescription>();
|
||||||
|
sessionDescription.setMediaDescriptions(mediaDescriptions);
|
||||||
|
|
||||||
|
for (SdpLine sdpLine : mediaLines) {
|
||||||
|
MediaDescription mediaDescription;
|
||||||
|
if (sdpLine.getType() == RFC4566_28181.TYPE_MEDIA) {
|
||||||
|
String[] mediaArr = sdpLine.getValue().split(" ");
|
||||||
|
if (mediaArr == null || mediaArr.length < 4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
mediaDescription = new MediaDescription();
|
||||||
|
mediaDescription.setType(mediaArr[0]);
|
||||||
|
//TODO manage port range
|
||||||
|
mediaDescription.setPort(Integer.parseInt(mediaArr[1]));
|
||||||
|
mediaDescription.setAttributes(new Hashtable<String, String>());
|
||||||
|
List<Codec> codecs = new ArrayList<Codec>();
|
||||||
|
for (int i = 3; i < mediaArr.length; ++i) {
|
||||||
|
int payloadType = Integer.parseInt(mediaArr[i]);
|
||||||
|
Codec codec = new Codec();
|
||||||
|
codec.setPayloadType(payloadType);
|
||||||
|
codec.setName("unsupported");
|
||||||
|
codecs.add(codec);
|
||||||
|
}
|
||||||
|
mediaDescription.setCodecs(codecs);
|
||||||
|
mediaDescriptions.add(mediaDescription);
|
||||||
|
} else {
|
||||||
|
mediaDescription = mediaDescriptions.get(mediaDescriptions.size() - 1);
|
||||||
|
String sdpLineValue = sdpLine.getValue();
|
||||||
|
if (sdpLine.getType() == RFC4566_28181.TYPE_CONNECTION) {
|
||||||
|
String ipAddress = parseConnection(sdpLineValue);
|
||||||
|
mediaDescription.setIpAddress(InetAddress.getByName(ipAddress));
|
||||||
|
} else if (sdpLine.getType() == RFC4566_28181.TYPE_ATTRIBUTE) {
|
||||||
|
Hashtable<String, String> attributes = mediaDescription.getAttributes();
|
||||||
|
int pos = sdpLineValue.indexOf(RFC4566_28181.ATTR_SEPARATOR);
|
||||||
|
if (pos > -1) {
|
||||||
|
String name = sdpLineValue.substring(0, pos);
|
||||||
|
String value = sdpLineValue.substring(pos + 1);
|
||||||
|
pos = value.indexOf(" ");
|
||||||
|
if (pos > -1) {
|
||||||
|
int payloadType;
|
||||||
|
try {
|
||||||
|
payloadType = Integer.parseInt(value.substring(0, pos));
|
||||||
|
List<Codec> codecs = mediaDescription.getCodecs();
|
||||||
|
for (Codec codec: codecs) {
|
||||||
|
if (codec.getPayloadType() == payloadType) {
|
||||||
|
value = value.substring(pos + 1);
|
||||||
|
pos = value.indexOf("/");
|
||||||
|
if (pos > -1) {
|
||||||
|
value = value.substring(0, pos);
|
||||||
|
codec.setName(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
attributes.put(name, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attributes.put(name, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attributes.put(sdpLineValue, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessionDescription.setMediaDescriptions(mediaDescriptions);
|
||||||
|
|
||||||
|
for (MediaDescription description : mediaDescriptions) {
|
||||||
|
if (description.getIpAddress() == null) {
|
||||||
|
InetAddress sessionAddress = sessionDescription.getIpAddress();
|
||||||
|
if (sessionAddress == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
description.setIpAddress(sessionAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return sessionDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseConnection(String line) {
|
||||||
|
String[] connectionArr = line.split(" ");
|
||||||
|
if (connectionArr == null || connectionArr.length != 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return connectionArr[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
162
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/SessionDescription.java
Executable file
162
src/main/java/com/genersoft/iot/vmp/gb28181/sdp/SessionDescription.java
Executable file
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Peers, a java SIP softphone.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2007, 2008, 2009, 2010 Yohann Martineau
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.genersoft.iot.vmp.gb28181.sdp;
|
||||||
|
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.Inet6Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SessionDescription {
|
||||||
|
|
||||||
|
private long id;
|
||||||
|
private long version;
|
||||||
|
private String name;
|
||||||
|
private String username;
|
||||||
|
private InetAddress ipAddress;
|
||||||
|
private List<MediaDescription> mediaDescriptions;
|
||||||
|
private Hashtable<String, String> attributes;
|
||||||
|
private String ssrc;
|
||||||
|
private String gbMediaDescriptions;
|
||||||
|
|
||||||
|
public SessionDescription() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetAddress getIpAddress() {
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpAddress(InetAddress ipAddress) {
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MediaDescription> getMediaDescriptions() {
|
||||||
|
return mediaDescriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediaDescriptions(List<MediaDescription> mediaDescriptions) {
|
||||||
|
this.mediaDescriptions = mediaDescriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(long version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Hashtable<String, String> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(Hashtable<String, String> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSsrc() {
|
||||||
|
return ssrc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSsrc(String ssrc) {
|
||||||
|
this.ssrc = ssrc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGbMediaDescriptions() {
|
||||||
|
return gbMediaDescriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGbMediaDescriptions(String gbMediaDescriptions) {
|
||||||
|
this.gbMediaDescriptions = gbMediaDescriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
buf.append("v=0\r\n");
|
||||||
|
buf.append("o=").append(username).append(" ").append(id);
|
||||||
|
buf.append(" ").append(version);
|
||||||
|
int ipVersion;
|
||||||
|
if (ipAddress instanceof Inet4Address) {
|
||||||
|
ipVersion = 4;
|
||||||
|
} else if (ipAddress instanceof Inet6Address) {
|
||||||
|
ipVersion = 6;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("unknown ip version: " + ipAddress);
|
||||||
|
}
|
||||||
|
buf.append(" IN IP").append(ipVersion).append(" ");
|
||||||
|
String hostAddress = ipAddress.getHostAddress();
|
||||||
|
buf.append(hostAddress).append("\r\n");
|
||||||
|
buf.append("s=").append(name).append("\r\n");
|
||||||
|
buf.append("c=IN IP").append(ipVersion).append(" ");
|
||||||
|
buf.append(hostAddress).append("\r\n");
|
||||||
|
buf.append("t=0 0\r\n");
|
||||||
|
if (attributes != null){
|
||||||
|
for (String attributeName: attributes.keySet()) {
|
||||||
|
String attributeValue = attributes.get(attributeName);
|
||||||
|
buf.append("a=").append(attributeName);
|
||||||
|
if (attributeValue != null && !"".equals(attributeValue.trim())) {
|
||||||
|
buf.append(":");
|
||||||
|
buf.append(attributeValue);
|
||||||
|
buf.append("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mediaDescriptions != null){
|
||||||
|
for (MediaDescription mediaDescription: mediaDescriptions) {
|
||||||
|
buf.append(mediaDescription.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssrc != null){
|
||||||
|
buf.append("y=").append(ssrc).append("\r\n");
|
||||||
|
}
|
||||||
|
if (gbMediaDescriptions != null){
|
||||||
|
buf.append("f=").append(gbMediaDescriptions).append("\r\n");
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -119,6 +119,9 @@ public class SIPProcessorFactory {
|
|||||||
processor.setRequestEvent(evt);
|
processor.setRequestEvent(evt);
|
||||||
processor.setTcpSipProvider(getTcpSipProvider());
|
processor.setTcpSipProvider(getTcpSipProvider());
|
||||||
processor.setUdpSipProvider(getUdpSipProvider());
|
processor.setUdpSipProvider(getUdpSipProvider());
|
||||||
|
|
||||||
|
processor.setCmderFroPlatform(cmderFroPlatform);
|
||||||
|
processor.setStorager(storager);
|
||||||
return processor;
|
return processor;
|
||||||
} else if (Request.REGISTER.equals(method)) {
|
} else if (Request.REGISTER.equals(method)) {
|
||||||
RegisterRequestProcessor processor = new RegisterRequestProcessor();
|
RegisterRequestProcessor processor = new RegisterRequestProcessor();
|
||||||
|
@ -70,7 +70,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
|||||||
@Override
|
@Override
|
||||||
public boolean unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) {
|
public boolean unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) {
|
||||||
parentPlatform.setExpires("0");
|
parentPlatform.setExpires("0");
|
||||||
ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getDeviceGBId());
|
ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId());
|
||||||
if (parentPlatformCatch != null) {
|
if (parentPlatformCatch != null) {
|
||||||
parentPlatformCatch.setParentPlatform(parentPlatform);
|
parentPlatformCatch.setParentPlatform(parentPlatform);
|
||||||
redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
|
redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
|
||||||
@ -86,11 +86,21 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
|||||||
|
|
||||||
if (www == null ) {
|
if (www == null ) {
|
||||||
request = headerProviderPlarformProvider.createRegisterRequest(parentPlatform, 1L, null, null);
|
request = headerProviderPlarformProvider.createRegisterRequest(parentPlatform, 1L, null, null);
|
||||||
|
// 将 callid 写入缓存, 等注册成功可以更新状态
|
||||||
|
CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
|
||||||
|
redisCatchStorage.updatePlatformRegisterInfo(callIdHeader.getCallId(), parentPlatform.getServerGBId());
|
||||||
|
|
||||||
|
sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (event)->{
|
||||||
|
redisCatchStorage.delPlatformRegisterInfo(callIdHeader.getCallId());
|
||||||
|
if (errorEvent != null) {
|
||||||
|
errorEvent.response(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
}else {
|
}else {
|
||||||
request = headerProviderPlarformProvider.createRegisterRequest(parentPlatform, null, null, callId, www);
|
request = headerProviderPlarformProvider.createRegisterRequest(parentPlatform, null, null, callId, www);
|
||||||
}
|
}
|
||||||
|
|
||||||
transmitRequest(parentPlatform, request, errorEvent, okEvent);
|
transmitRequest(parentPlatform, request, null, okEvent);
|
||||||
return true;
|
return true;
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -1,71 +1,231 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
|
package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
|
||||||
|
|
||||||
|
import javax.sip.InvalidArgumentException;
|
||||||
import javax.sip.RequestEvent;
|
import javax.sip.RequestEvent;
|
||||||
|
import javax.sip.SipException;
|
||||||
|
import javax.sip.address.SipURI;
|
||||||
|
import javax.sip.header.ContentTypeHeader;
|
||||||
|
import javax.sip.header.FromHeader;
|
||||||
|
import javax.sip.header.SubjectHeader;
|
||||||
|
import javax.sip.message.Request;
|
||||||
|
import javax.sip.message.Response;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.sdp.Codec;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.sdp.MediaDescription;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.sdp.SdpParser;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.sdp.SessionDescription;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
|
import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
|
||||||
|
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
|
||||||
|
import gov.nist.javax.sip.address.AddressImpl;
|
||||||
|
import gov.nist.javax.sip.address.SipUri;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description:处理INVITE请求
|
* @Description:处理INVITE请求
|
||||||
* @author: swwheihei
|
* @author: panll
|
||||||
* @date: 2020年5月3日 下午4:43:52
|
* @date: 2021年1月14日
|
||||||
*/
|
*/
|
||||||
public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
|
public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class);
|
||||||
|
|
||||||
|
private SIPCommanderFroPlatform cmderFroPlatform;
|
||||||
|
|
||||||
|
private IVideoManagerStorager storager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理invite请求
|
* 处理invite请求
|
||||||
*
|
*
|
||||||
* @param request
|
* @param evt
|
||||||
* 请求消息
|
* 请求消息
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void process(RequestEvent evt) {
|
public void process(RequestEvent evt) {
|
||||||
// TODO 优先级99 Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令
|
// Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令
|
||||||
// Request request = requestEvent.getRequest();
|
try {
|
||||||
//
|
Request request = evt.getRequest();
|
||||||
// try {
|
SipURI sipURI = (SipURI) request.getRequestURI();
|
||||||
// // 发送100 Trying
|
String channelId = sipURI.getUser();
|
||||||
// ServerTransaction serverTransaction = getServerTransaction(requestEvent);
|
String platformId = null;
|
||||||
// // 查询目标地址
|
// SubjectHeader subjectHeader = (SubjectHeader)request.getHeader(SubjectHeader.NAME);
|
||||||
// URI reqUri = request.getRequestURI();
|
// // 查询通道是否存在 不存在回复404
|
||||||
// URI contactURI = currUser.get(reqUri);
|
// if (subjectHeader != null) { // 存在则从subjectHeader 获取平台信息
|
||||||
//
|
// String subject = subjectHeader.getSubject();
|
||||||
// System.out.println("processInvite rqStr=" + reqUri + " contact=" + contactURI);
|
// if (subject != null) {
|
||||||
//
|
// String[] info1 = subject.split(",");
|
||||||
// // 根据Request uri来路由,后续的响应消息通过VIA来路由
|
// if (info1 != null && info1 .length == 2) {
|
||||||
// Request cliReq = messageFactory.createRequest(request.toString());
|
// String[] info2 = info1[1].split(":");
|
||||||
// cliReq.setRequestURI(contactURI);
|
// if (info2 != null && info2.length == 2) {
|
||||||
//
|
// platformId = info2[0];
|
||||||
// HeaderFactory headerFactory = SipFactory.getInstance().createHeaderFactory();
|
// }
|
||||||
// Via callerVia = (Via) request.getHeader(Via.NAME);
|
// }
|
||||||
// Via via = (Via) headerFactory.createViaHeader(SIPMain.ip, SIPMain.port, "UDP",
|
// }
|
||||||
// callerVia.getBranch() + "sipphone");
|
// }
|
||||||
//
|
|
||||||
// cliReq.removeHeader(Via.NAME);
|
FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
|
||||||
// cliReq.addHeader(via);
|
AddressImpl address = (AddressImpl) fromHeader.getAddress();
|
||||||
//
|
SipUri uri = (SipUri) address.getURI();
|
||||||
// // 更新contact的地址
|
platformId = uri.getUser();
|
||||||
// ContactHeader contactHeader = headerFactory.createContactHeader();
|
|
||||||
// Address address = SipFactory.getInstance().createAddressFactory()
|
// if (platformId == null) { // 不存在则从fromHeader 获取平台信息
|
||||||
// .createAddress("sip:sipsoft@" + SIPMain.ip + ":" + SIPMain.port);
|
// FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
|
||||||
// contactHeader.setAddress(address);
|
// platformId = fromHeader.getName();
|
||||||
// contactHeader.setExpires(3600);
|
// }
|
||||||
// cliReq.setHeader(contactHeader);
|
if (platformId == null || channelId == null) {
|
||||||
//
|
response400Ack(evt); // 参数不全, 发400,请求错误
|
||||||
// clientTransactionId = sipProvider.getNewClientTransaction(cliReq);
|
return;
|
||||||
// clientTransactionId.sendRequest();
|
}
|
||||||
//
|
// 查询平台下是否有该通道
|
||||||
// System.out.println("processInvite clientTransactionId=" + clientTransactionId.toString());
|
DeviceChannel channel = storager.queryChannelInParentPlatform(platformId, channelId);
|
||||||
//
|
if (channel == null) {
|
||||||
// System.out.println("send invite to callee: " + cliReq);
|
response404Ack(evt); // 通道不存在,发404,资源不存在
|
||||||
// } catch (TransactionUnavailableException e1) {
|
return;
|
||||||
// e1.printStackTrace();
|
}else {
|
||||||
// } catch (SipException e) {
|
response100Ack(evt); // 通道存在,发100,trying
|
||||||
// e.printStackTrace();
|
}
|
||||||
// } catch (ParseException e) {
|
// 解析sdp消息
|
||||||
// e.printStackTrace();
|
byte[] sdpByteArray = request.getRawContent();
|
||||||
// } catch (Exception e) {
|
SdpParser sdpParser = new SdpParser(); // TODO keng
|
||||||
// e.printStackTrace();
|
SessionDescription sdp = sdpParser.parse(sdpByteArray);
|
||||||
// }
|
// 获取支持的格式
|
||||||
|
List<MediaDescription> mediaDescriptions = sdp.getMediaDescriptions();
|
||||||
|
// 查看是否支持PS 负载96
|
||||||
|
String ip = null;
|
||||||
|
int port = -1;
|
||||||
|
for (MediaDescription mediaDescription : mediaDescriptions) {
|
||||||
|
|
||||||
|
List<Codec> codecs = mediaDescription.getCodecs();
|
||||||
|
for (Codec codec : codecs) {
|
||||||
|
if("96".equals(codec.getPayloadType()) || "PS".equals(codec.getName()) || "ps".equals(codec.getName())) {
|
||||||
|
ip = mediaDescription.getIpAddress().getHostName();
|
||||||
|
port = mediaDescription.getPort();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ip == null || port == -1) { // TODO 没有合适的视频流格式, 可配置是否使用第一个media信息
|
||||||
|
if (mediaDescriptions.size() > 0) {
|
||||||
|
ip = mediaDescriptions.get(0).getIpAddress().getHostName();
|
||||||
|
port = mediaDescriptions.get(0).getPort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ip == null || port == -1) {
|
||||||
|
response488Ack(evt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String ssrc = sdp.getSsrc();
|
||||||
|
// 通知下级推流,
|
||||||
|
// 查找合适的端口推流,
|
||||||
|
// 发送 200ok
|
||||||
|
// 收到ack后调用推流接口
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("sdp解析错误");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 回复100 trying
|
||||||
|
* @param evt
|
||||||
|
* @throws SipException
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @throws ParseException
|
||||||
|
*/
|
||||||
|
private void response100Ack(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException {
|
||||||
|
Response response = getMessageFactory().createResponse(Response.TRYING, evt.getRequest());
|
||||||
|
getServerTransaction(evt).sendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 回复404
|
||||||
|
* @param evt
|
||||||
|
* @throws SipException
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @throws ParseException
|
||||||
|
*/
|
||||||
|
private void response404Ack(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException {
|
||||||
|
Response response = getMessageFactory().createResponse(Response.NOT_FOUND, evt.getRequest());
|
||||||
|
getServerTransaction(evt).sendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 回复400
|
||||||
|
* @param evt
|
||||||
|
* @throws SipException
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @throws ParseException
|
||||||
|
*/
|
||||||
|
private void response400Ack(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException {
|
||||||
|
Response response = getMessageFactory().createResponse(Response.BAD_REQUEST, evt.getRequest());
|
||||||
|
getServerTransaction(evt).sendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 回复488
|
||||||
|
* @param evt
|
||||||
|
* @throws SipException
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @throws ParseException
|
||||||
|
*/
|
||||||
|
private void response488Ack(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException {
|
||||||
|
Response response = getMessageFactory().createResponse(Response.NOT_ACCEPTABLE_HERE, evt.getRequest());
|
||||||
|
getServerTransaction(evt).sendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 回复200 OK
|
||||||
|
* @param evt
|
||||||
|
* @throws SipException
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @throws ParseException
|
||||||
|
*/
|
||||||
|
private void responseAck(RequestEvent evt, String sdp) throws SipException, InvalidArgumentException, ParseException {
|
||||||
|
Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest());
|
||||||
|
ContentTypeHeader contentTypeHeader = getHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
|
||||||
|
response.setContent(sdp, contentTypeHeader);
|
||||||
|
getServerTransaction(evt).sendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public SIPCommanderFroPlatform getCmderFroPlatform() {
|
||||||
|
return cmderFroPlatform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCmderFroPlatform(SIPCommanderFroPlatform cmderFroPlatform) {
|
||||||
|
this.cmderFroPlatform = cmderFroPlatform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IVideoManagerStorager getStorager() {
|
||||||
|
return storager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStorager(IVideoManagerStorager storager) {
|
||||||
|
this.storager = storager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ import com.genersoft.iot.vmp.gb28181.bean.*;
|
|||||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||||
import com.genersoft.iot.vmp.vmanager.platform.bean.ChannelReduce;
|
import com.genersoft.iot.vmp.vmanager.platform.bean.ChannelReduce;
|
||||||
|
import gov.nist.javax.sip.address.AddressImpl;
|
||||||
|
import gov.nist.javax.sip.address.SipUri;
|
||||||
import org.dom4j.Document;
|
import org.dom4j.Document;
|
||||||
import org.dom4j.DocumentException;
|
import org.dom4j.DocumentException;
|
||||||
import org.dom4j.Element;
|
import org.dom4j.Element;
|
||||||
@ -166,10 +168,15 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
|||||||
Element deviceIdElement = rootElement.element("DeviceID");
|
Element deviceIdElement = rootElement.element("DeviceID");
|
||||||
String deviceId = deviceIdElement.getText();
|
String deviceId = deviceIdElement.getText();
|
||||||
Element deviceListElement = rootElement.element("DeviceList");
|
Element deviceListElement = rootElement.element("DeviceList");
|
||||||
|
|
||||||
|
FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
|
||||||
|
AddressImpl address = (AddressImpl) fromHeader.getAddress();
|
||||||
|
SipUri uri = (SipUri) address.getURI();
|
||||||
|
String platformId = uri.getUser();
|
||||||
// if (deviceListElement == null) { // 存在DeviceList则为响应 catalog, 不存在DeviceList则为查询请求
|
// if (deviceListElement == null) { // 存在DeviceList则为响应 catalog, 不存在DeviceList则为查询请求
|
||||||
if (name == "Query") { // 区分是Response——查询响应,还是Query——查询请求
|
if (name == "Query") { // 区分是Response——查询响应,还是Query——查询请求
|
||||||
// TODO 后续将代码拆分
|
// TODO 后续将代码拆分
|
||||||
ParentPlatform parentPlatform = storager.queryParentPlatById(deviceId);
|
ParentPlatform parentPlatform = storager.queryParentPlatById(platformId);
|
||||||
if (parentPlatform == null) {
|
if (parentPlatform == null) {
|
||||||
response404Ack(evt);
|
response404Ack(evt);
|
||||||
return;
|
return;
|
||||||
@ -179,9 +186,8 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
|||||||
|
|
||||||
Element snElement = rootElement.element("SN");
|
Element snElement = rootElement.element("SN");
|
||||||
String sn = snElement.getText();
|
String sn = snElement.getText();
|
||||||
FromHeader fromHeader = (FromHeader)evt.getRequest().getHeader(FromHeader.NAME);
|
|
||||||
// 准备回复通道信息
|
// 准备回复通道信息
|
||||||
List<ChannelReduce> channelReduces = storager.queryChannelListInParentPlatform(parentPlatform.getDeviceGBId());
|
List<ChannelReduce> channelReduces = storager.queryChannelListInParentPlatform(parentPlatform.getServerGBId());
|
||||||
if (channelReduces.size() >0 ) {
|
if (channelReduces.size() >0 ) {
|
||||||
for (ChannelReduce channelReduce : channelReduces) {
|
for (ChannelReduce channelReduce : channelReduces) {
|
||||||
DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId());
|
DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId());
|
||||||
@ -499,7 +505,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* 回复200 OK
|
* 回复404
|
||||||
* @param evt
|
* @param evt
|
||||||
* @throws SipException
|
* @throws SipException
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
|
@ -61,10 +61,14 @@ public class RegisterResponseProcessor implements ISIPResponseProcessor {
|
|||||||
public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
|
public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
Response response = evt.getResponse();
|
Response response = evt.getResponse();
|
||||||
ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME);
|
CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME);
|
||||||
SipUri uri = (SipUri)toHeader.getAddress().getURI();
|
String callId = callIdHeader.getCallId();
|
||||||
String platformGBId = uri.getAuthority().getUser();
|
|
||||||
|
|
||||||
|
String platformGBId = redisCatchStorage.queryPlatformRegisterInfo(callId);
|
||||||
|
if (platformGBId == null) {
|
||||||
|
logger.info(String.format("未找到callId: %s 的注册/注销平台id", callId ));
|
||||||
|
return;
|
||||||
|
}
|
||||||
logger.info(String.format("收到 %s 的注册/注销%S响应", platformGBId, response.getStatusCode() ));
|
logger.info(String.format("收到 %s 的注册/注销%S响应", platformGBId, response.getStatusCode() ));
|
||||||
|
|
||||||
ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(platformGBId);
|
ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(platformGBId);
|
||||||
@ -80,18 +84,13 @@ public class RegisterResponseProcessor implements ISIPResponseProcessor {
|
|||||||
|
|
||||||
if (response.getStatusCode() == 401) {
|
if (response.getStatusCode() == 401) {
|
||||||
WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME);
|
WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME);
|
||||||
|
|
||||||
|
|
||||||
CallIdHeader callIdHeader = (CallIdHeader)response.getHeader(CallIdHeader.NAME);
|
|
||||||
String callId = callIdHeader.getCallId();
|
|
||||||
|
|
||||||
sipCommanderForPlatform.register(parentPlatform, callId, www, null, null);
|
sipCommanderForPlatform.register(parentPlatform, callId, www, null, null);
|
||||||
}else if (response.getStatusCode() == 200){
|
}else if (response.getStatusCode() == 200){
|
||||||
// 注册成功
|
// 注册成功
|
||||||
logger.info(String.format("%s 注册成功", platformGBId ));
|
logger.info(String.format("%s 注册成功", platformGBId ));
|
||||||
|
redisCatchStorage.delPlatformRegisterInfo(callId);
|
||||||
parentPlatform.setStatus(true);
|
parentPlatform.setStatus(true);
|
||||||
storager.updateParentPlatform(parentPlatform);
|
storager.updateParentPlatform(parentPlatform);
|
||||||
//
|
|
||||||
redisCatchStorage.updatePlatformRegister(parentPlatform);
|
redisCatchStorage.updatePlatformRegister(parentPlatform);
|
||||||
|
|
||||||
redisCatchStorage.updatePlatformKeepalive(parentPlatform);
|
redisCatchStorage.updatePlatformKeepalive(parentPlatform);
|
||||||
|
@ -73,4 +73,9 @@ public interface IRedisCatchStorage {
|
|||||||
|
|
||||||
void delPlatformRegister(String platformGbId);
|
void delPlatformRegister(String platformGbId);
|
||||||
|
|
||||||
|
void updatePlatformRegisterInfo(String callId, String platformGbId);
|
||||||
|
|
||||||
|
String queryPlatformRegisterInfo(String callId);
|
||||||
|
|
||||||
|
void delPlatformRegisterInfo(String callId);
|
||||||
}
|
}
|
||||||
|
@ -233,4 +233,5 @@ public interface IVideoManagerStorager {
|
|||||||
int delChannelForGB(String platformId, List<ChannelReduce> channelReduces);
|
int delChannelForGB(String platformId, List<ChannelReduce> channelReduces);
|
||||||
|
|
||||||
|
|
||||||
|
DeviceChannel queryChannelInParentPlatform(String platformId, String channelId);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ public interface ParentPlatformMapper {
|
|||||||
@Update("UPDATE parent_platform " +
|
@Update("UPDATE parent_platform " +
|
||||||
"SET enable=#{enable}, " +
|
"SET enable=#{enable}, " +
|
||||||
"name=#{name}," +
|
"name=#{name}," +
|
||||||
"serverGBId=#{serverGBId}," +
|
"deviceGBId=#{deviceGBId}," +
|
||||||
"serverGBDomain=#{serverGBDomain}, " +
|
"serverGBDomain=#{serverGBDomain}, " +
|
||||||
"serverIP=#{serverIP}," +
|
"serverIP=#{serverIP}," +
|
||||||
"serverPort=#{serverPort}, " +
|
"serverPort=#{serverPort}, " +
|
||||||
@ -39,13 +39,13 @@ public interface ParentPlatformMapper {
|
|||||||
"PTZEnable=#{PTZEnable}, " +
|
"PTZEnable=#{PTZEnable}, " +
|
||||||
"rtcp=#{rtcp}, " +
|
"rtcp=#{rtcp}, " +
|
||||||
"status=#{status} " +
|
"status=#{status} " +
|
||||||
"WHERE deviceGBId=#{deviceGBId}")
|
"WHERE serverGBId=#{serverGBId}")
|
||||||
int updateParentPlatform(ParentPlatform parentPlatform);
|
int updateParentPlatform(ParentPlatform parentPlatform);
|
||||||
|
|
||||||
@Delete("DELETE FROM parent_platform WHERE deviceGBId=#{deviceGBId}")
|
@Delete("DELETE FROM parent_platform WHERE serverGBId=#{serverGBId}")
|
||||||
int delParentPlatform(ParentPlatform parentPlatform);
|
int delParentPlatform(ParentPlatform parentPlatform);
|
||||||
|
|
||||||
@Select("SELECT *,( SELECT count(0) FROM platform_gb_channel pc WHERE pc.platformId = pp.deviceGBId) as channelCount FROM parent_platform pp ")
|
@Select("SELECT *,( SELECT count(0) FROM platform_gb_channel pc WHERE pc.platformId = pp.serverGBId) as channelCount FROM parent_platform pp ")
|
||||||
List<ParentPlatform> getParentPlatformList();
|
List<ParentPlatform> getParentPlatformList();
|
||||||
|
|
||||||
@Select("SELECT * FROM parent_platform WHERE enable=#{enable}")
|
@Select("SELECT * FROM parent_platform WHERE enable=#{enable}")
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.genersoft.iot.vmp.storager.dao;
|
package com.genersoft.iot.vmp.storager.dao;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||||
import com.genersoft.iot.vmp.vmanager.platform.bean.ChannelReduce;
|
import com.genersoft.iot.vmp.vmanager.platform.bean.ChannelReduce;
|
||||||
import org.apache.ibatis.annotations.Delete;
|
import org.apache.ibatis.annotations.Delete;
|
||||||
import org.apache.ibatis.annotations.Insert;
|
import org.apache.ibatis.annotations.Insert;
|
||||||
@ -39,4 +40,9 @@ public interface PatformChannelMapper {
|
|||||||
"DELETE FROM platform_gb_channel WHERE platformId='${platformId}'" +
|
"DELETE FROM platform_gb_channel WHERE platformId='${platformId}'" +
|
||||||
"</script>")
|
"</script>")
|
||||||
int cleanChannelForGB(String platformId);
|
int cleanChannelForGB(String platformId);
|
||||||
|
|
||||||
|
|
||||||
|
@Select("SELECT * FROM device_channel WHERE deviceId = (SELECT deviceId FROM platform_gb_channel WHERE " +
|
||||||
|
"platformId='${platformId}' AND channelId='${channelId}' ) AND channelId='${channelId}'")
|
||||||
|
DeviceChannel queryChannelInParentPlatform(String platformId, String channelId);
|
||||||
}
|
}
|
||||||
|
@ -169,13 +169,13 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updatePlatformKeepalive(ParentPlatform parentPlatform) {
|
public void updatePlatformKeepalive(ParentPlatform parentPlatform) {
|
||||||
String key = VideoManagerConstants.PLATFORM_KEEPLIVEKEY_PREFIX + parentPlatform.getDeviceGBId();
|
String key = VideoManagerConstants.PLATFORM_KEEPLIVEKEY_PREFIX + parentPlatform.getServerGBId();
|
||||||
redis.set(key, "", Integer.parseInt(parentPlatform.getKeepTimeout()));
|
redis.set(key, "", Integer.parseInt(parentPlatform.getKeepTimeout()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updatePlatformRegister(ParentPlatform parentPlatform) {
|
public void updatePlatformRegister(ParentPlatform parentPlatform) {
|
||||||
String key = VideoManagerConstants.PLATFORM_REGISTER_PREFIX + parentPlatform.getDeviceGBId();
|
String key = VideoManagerConstants.PLATFORM_REGISTER_PREFIX + parentPlatform.getServerGBId();
|
||||||
redis.set(key, "", Integer.parseInt(parentPlatform.getExpires()));
|
redis.set(key, "", Integer.parseInt(parentPlatform.getExpires()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,4 +198,22 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
|
|||||||
public void delPlatformRegister(String platformGbId) {
|
public void delPlatformRegister(String platformGbId) {
|
||||||
redis.del(VideoManagerConstants.PLATFORM_REGISTER_PREFIX + platformGbId);
|
redis.del(VideoManagerConstants.PLATFORM_REGISTER_PREFIX + platformGbId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePlatformRegisterInfo(String callId, String platformGbId) {
|
||||||
|
String key = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + callId;
|
||||||
|
redis.set(key, platformGbId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String queryPlatformRegisterInfo(String callId) {
|
||||||
|
return (String)redis.get(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + callId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delPlatformRegisterInfo(String callId) {
|
||||||
|
redis.del(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + callId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,7 +254,7 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
|
|||||||
public boolean deleteParentPlatform(ParentPlatform parentPlatform) {
|
public boolean deleteParentPlatform(ParentPlatform parentPlatform) {
|
||||||
int result = platformMapper.delParentPlatform(parentPlatform);
|
int result = platformMapper.delParentPlatform(parentPlatform);
|
||||||
// 删除关联的通道
|
// 删除关联的通道
|
||||||
patformChannelMapper.cleanChannelForGB(parentPlatform.getDeviceGBId());
|
patformChannelMapper.cleanChannelForGB(parentPlatform.getServerGBId());
|
||||||
return result > 0;
|
return result > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,4 +329,10 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceChannel queryChannelInParentPlatform(String platformId, String channelId) {
|
||||||
|
DeviceChannel channel = patformChannelMapper.queryChannelInParentPlatform(platformId, channelId);
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ public class PlatformController {
|
|||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("查询所有上级设备API调用");
|
logger.debug("查询所有上级设备API调用");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(parentPlatform.getDeviceGBId())
|
if (StringUtils.isEmpty(parentPlatform.getServerGBId())
|
||||||
){
|
){
|
||||||
return new ResponseEntity<>("missing parameters", HttpStatus.BAD_REQUEST);
|
return new ResponseEntity<>("missing parameters", HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
@ -118,14 +118,14 @@ public class PlatformController {
|
|||||||
// 发送离线消息,无论是否成功都删除缓存
|
// 发送离线消息,无论是否成功都删除缓存
|
||||||
commanderForPlatform.unregister(parentPlatform, (event -> {
|
commanderForPlatform.unregister(parentPlatform, (event -> {
|
||||||
// 清空redis缓存
|
// 清空redis缓存
|
||||||
redisCatchStorage.delPlatformCatchInfo(parentPlatform.getDeviceGBId());
|
redisCatchStorage.delPlatformCatchInfo(parentPlatform.getServerGBId());
|
||||||
redisCatchStorage.delPlatformKeepalive(parentPlatform.getDeviceGBId());
|
redisCatchStorage.delPlatformKeepalive(parentPlatform.getServerGBId());
|
||||||
redisCatchStorage.delPlatformRegister(parentPlatform.getDeviceGBId());
|
redisCatchStorage.delPlatformRegister(parentPlatform.getServerGBId());
|
||||||
}), (event -> {
|
}), (event -> {
|
||||||
// 清空redis缓存
|
// 清空redis缓存
|
||||||
redisCatchStorage.delPlatformCatchInfo(parentPlatform.getDeviceGBId());
|
redisCatchStorage.delPlatformCatchInfo(parentPlatform.getServerGBId());
|
||||||
redisCatchStorage.delPlatformKeepalive(parentPlatform.getDeviceGBId());
|
redisCatchStorage.delPlatformKeepalive(parentPlatform.getServerGBId());
|
||||||
redisCatchStorage.delPlatformRegister(parentPlatform.getDeviceGBId());
|
redisCatchStorage.delPlatformRegister(parentPlatform.getServerGBId());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
boolean deleteResult = storager.deleteParentPlatform(parentPlatform);
|
boolean deleteResult = storager.deleteParentPlatform(parentPlatform);
|
||||||
|
Loading…
Reference in New Issue
Block a user