|
|
@@ -0,0 +1,672 @@
|
|
|
+package com.usky.sas.common.onvif;
|
|
|
+
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
+import com.usky.sas.common.exception.BusinessException;
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.io.BufferedWriter;
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.io.OutputStream;
|
|
|
+import java.io.OutputStreamWriter;
|
|
|
+import java.io.StringReader;
|
|
|
+import java.io.StringWriter;
|
|
|
+import java.net.HttpURLConnection;
|
|
|
+import java.net.URL;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.security.MessageDigest;
|
|
|
+import java.security.SecureRandom;
|
|
|
+import java.time.ZonedDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Base64;
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.List;
|
|
|
+import javax.xml.namespace.NamespaceContext;
|
|
|
+import javax.xml.parsers.DocumentBuilder;
|
|
|
+import javax.xml.parsers.DocumentBuilderFactory;
|
|
|
+import javax.xml.soap.MessageFactory;
|
|
|
+import javax.xml.soap.SOAPBody;
|
|
|
+import javax.xml.soap.SOAPElement;
|
|
|
+import javax.xml.soap.SOAPEnvelope;
|
|
|
+import javax.xml.soap.SOAPException;
|
|
|
+import javax.xml.soap.SOAPHeader;
|
|
|
+import javax.xml.soap.SOAPMessage;
|
|
|
+import javax.xml.soap.SOAPPart;
|
|
|
+import javax.xml.transform.Transformer;
|
|
|
+import javax.xml.transform.TransformerFactory;
|
|
|
+import javax.xml.transform.dom.DOMSource;
|
|
|
+import javax.xml.transform.stream.StreamResult;
|
|
|
+import javax.xml.xpath.XPath;
|
|
|
+import javax.xml.xpath.XPathConstants;
|
|
|
+import javax.xml.xpath.XPathFactory;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.w3c.dom.Document;
|
|
|
+import org.w3c.dom.Element;
|
|
|
+import org.w3c.dom.Node;
|
|
|
+import org.w3c.dom.NodeList;
|
|
|
+import org.xml.sax.InputSource;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class StandardOnvifService {
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(StandardOnvifService.class);
|
|
|
+
|
|
|
+ public StandardOnvifService() {
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getRealTimeStreamUrl(String cameraIp, String username, String password) {
|
|
|
+ try {
|
|
|
+ String mediaServiceUrl = this.getServiceEndpoint(cameraIp, "http://www.onvif.org/ver10/media/wsdl");
|
|
|
+ log.info("媒体服务端点: {}", mediaServiceUrl);
|
|
|
+ String profileToken = this.getProfileToken(mediaServiceUrl, username, password);
|
|
|
+ log.info("配置文件Token: {}", profileToken);
|
|
|
+ String streamUri = this.getStreamUri(mediaServiceUrl, profileToken, username, password);
|
|
|
+ log.info("获取到实时流地址: {}", streamUri);
|
|
|
+ return streamUri;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取实时流地址失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getServiceEndpoint(String deviceIp, String serviceNamespace) {
|
|
|
+ try {
|
|
|
+ String getServicesRequest = this.buildGetServicesRequest();
|
|
|
+ String response = this.sendSoapRequest("http://" + deviceIp + "/onvif/device_service", getServicesRequest);
|
|
|
+ Document doc = this.parseXml(response);
|
|
|
+ NodeList serviceNodes = doc.getElementsByTagNameNS("http://www.onvif.org/ver10/schema", "Media");
|
|
|
+ if (serviceNodes.getLength() == 0) {
|
|
|
+ log.error("未找到 tt:Media 节点");
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ } else {
|
|
|
+ Element mediaElement = (Element)serviceNodes.item(0);
|
|
|
+ NodeList xaddrNodes = mediaElement.getElementsByTagNameNS("http://www.onvif.org/ver10/schema", "XAddr");
|
|
|
+ if (xaddrNodes.getLength() > 0) {
|
|
|
+ return xaddrNodes.item(0).getTextContent();
|
|
|
+ } else {
|
|
|
+ log.error("未找到 Media 的 XAddr");
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取服务端点失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildGetServicesRequest() throws SOAPException {
|
|
|
+ MessageFactory messageFactory = MessageFactory.newInstance();
|
|
|
+ SOAPMessage soapMessage = messageFactory.createMessage();
|
|
|
+ SOAPPart soapPart = soapMessage.getSOAPPart();
|
|
|
+ SOAPEnvelope envelope = soapPart.getEnvelope();
|
|
|
+ envelope.addNamespaceDeclaration("tds", "http://www.onvif.org/ver10/device/wsdl");
|
|
|
+ SOAPBody soapBody = envelope.getBody();
|
|
|
+ SOAPElement getServicesElement = soapBody.addChildElement("GetCapabilities", "tds");
|
|
|
+ SOAPElement includeCapabilityElement = getServicesElement.addChildElement("Category", "tds");
|
|
|
+ includeCapabilityElement.addTextNode("All");
|
|
|
+ return soapMessageToString(soapMessage);
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getProfileToken(String mediaServiceUrl, String username, String password) {
|
|
|
+ try {
|
|
|
+ String getProfilesRequest = buildGetProfilesRequest(username, password);
|
|
|
+ String response = this.sendSoapRequest(mediaServiceUrl, getProfilesRequest);
|
|
|
+ Document doc = this.parseXml(response);
|
|
|
+ NodeList profileNodes = doc.getElementsByTagNameNS("http://www.onvif.org/ver10/media/wsdl", "Profiles");
|
|
|
+ if (profileNodes.getLength() > 0) {
|
|
|
+ Element profileElement = (Element)profileNodes.item(0);
|
|
|
+ return profileElement.getAttribute("token");
|
|
|
+ } else {
|
|
|
+ log.error("获取媒体配置文件失败");
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取媒体配置文件失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String buildGetProfilesRequest(String username, String password) throws Exception {
|
|
|
+ MessageFactory messageFactory = MessageFactory.newInstance("SOAP 1.2 Protocol");
|
|
|
+ SOAPMessage soapMessage = messageFactory.createMessage();
|
|
|
+ SOAPPart soapPart = soapMessage.getSOAPPart();
|
|
|
+ SOAPEnvelope envelope = soapPart.getEnvelope();
|
|
|
+ envelope.setPrefix("soap");
|
|
|
+ SOAPHeader defaultHeader = envelope.getHeader();
|
|
|
+ if (defaultHeader != null) {
|
|
|
+ defaultHeader.detachNode();
|
|
|
+ }
|
|
|
+
|
|
|
+ SOAPHeader newHeader = envelope.addHeader();
|
|
|
+ SOAPElement securityElement = newHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
|
|
|
+ securityElement.setAttribute("mustUnderstand", "1");
|
|
|
+ SOAPElement usernameTokenElement = securityElement.addChildElement("UsernameToken", "wsse");
|
|
|
+ SOAPElement usernameElement = usernameTokenElement.addChildElement("Username", "wsse");
|
|
|
+ usernameElement.addTextNode(username);
|
|
|
+ SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", "wsse");
|
|
|
+ String nonce = generateNonce();
|
|
|
+ nonceElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
|
|
|
+ nonceElement.addTextNode(Base64.getEncoder().encodeToString(nonce.getBytes()));
|
|
|
+ String created = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
|
|
|
+ SOAPElement createdElement = usernameTokenElement.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
|
|
|
+ createdElement.addTextNode(created);
|
|
|
+ String passwordDigest = calculatePasswordDigest(nonce, created, password);
|
|
|
+ SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", "wsse");
|
|
|
+ passwordElement.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
|
|
|
+ passwordElement.addTextNode(passwordDigest);
|
|
|
+ envelope.addNamespaceDeclaration("trt", "http://www.onvif.org/ver10/media/wsdl");
|
|
|
+ SOAPBody soapBody = envelope.getBody();
|
|
|
+ soapBody.setPrefix("soap");
|
|
|
+ soapBody.addChildElement("GetProfiles", "trt");
|
|
|
+ return soapMessageToString(soapMessage);
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getStreamUri(String mediaServiceUrl, String profileToken, String username, String password) {
|
|
|
+ try {
|
|
|
+ String getStreamUriRequest = buildGetStreamUriRequest(profileToken, username, password);
|
|
|
+ String response = this.sendSoapRequest(mediaServiceUrl, getStreamUriRequest);
|
|
|
+ Document doc = this.parseXml(response);
|
|
|
+ NodeList uriNodes = doc.getElementsByTagNameNS("*", "Uri");
|
|
|
+ if (uriNodes.getLength() > 0) {
|
|
|
+ return uriNodes.item(0).getTextContent();
|
|
|
+ } else {
|
|
|
+ log.error("获取rtsp流地址失败");
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取流地址失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String buildGetStreamUriRequest(String profileToken, String username, String password) throws Exception {
|
|
|
+ MessageFactory messageFactory = MessageFactory.newInstance("SOAP 1.2 Protocol");
|
|
|
+ SOAPMessage soapMessage = messageFactory.createMessage();
|
|
|
+ SOAPPart soapPart = soapMessage.getSOAPPart();
|
|
|
+ SOAPEnvelope envelope = soapPart.getEnvelope();
|
|
|
+ SOAPHeader defaultHeader = envelope.getHeader();
|
|
|
+ if (defaultHeader != null) {
|
|
|
+ defaultHeader.detachNode();
|
|
|
+ }
|
|
|
+
|
|
|
+ SOAPHeader newHeader = envelope.addHeader();
|
|
|
+ SOAPElement securityElement = newHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
|
|
|
+ securityElement.setAttribute("mustUnderstand", "1");
|
|
|
+ SOAPElement usernameTokenElement = securityElement.addChildElement("UsernameToken", "wsse");
|
|
|
+ SOAPElement usernameElement = usernameTokenElement.addChildElement("Username", "wsse");
|
|
|
+ usernameElement.addTextNode(username);
|
|
|
+ SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", "wsse");
|
|
|
+ String nonce = generateNonce();
|
|
|
+ nonceElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
|
|
|
+ nonceElement.addTextNode(Base64.getEncoder().encodeToString(nonce.getBytes()));
|
|
|
+ String created = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
|
|
|
+ SOAPElement createdElement = usernameTokenElement.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
|
|
|
+ createdElement.addTextNode(created);
|
|
|
+ String passwordDigest = calculatePasswordDigest(nonce, created, password);
|
|
|
+ SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", "wsse");
|
|
|
+ passwordElement.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
|
|
|
+ passwordElement.addTextNode(passwordDigest);
|
|
|
+ envelope.addNamespaceDeclaration("trt", "http://www.onvif.org/ver10/media/wsdl");
|
|
|
+ envelope.addNamespaceDeclaration("tt", "http://www.onvif.org/ver10/schema");
|
|
|
+ SOAPBody soapBody = envelope.getBody();
|
|
|
+ SOAPElement getStreamUriElement = soapBody.addChildElement("GetStreamUri", "trt");
|
|
|
+ SOAPElement streamSetupElement = getStreamUriElement.addChildElement("StreamSetup", "trt");
|
|
|
+ SOAPElement streamElement = streamSetupElement.addChildElement("Stream", "tt");
|
|
|
+ streamElement.addTextNode("RTP-Unicast");
|
|
|
+ SOAPElement transportElement = streamSetupElement.addChildElement("Transport", "tt");
|
|
|
+ SOAPElement protocolElement = transportElement.addChildElement("Protocol", "tt");
|
|
|
+ protocolElement.addTextNode("RTSP");
|
|
|
+ SOAPElement profileTokenElement = getStreamUriElement.addChildElement("ProfileToken", "trt");
|
|
|
+ profileTokenElement.addTextNode(profileToken);
|
|
|
+ return soapMessageToString(soapMessage);
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getReplayStreamUrl(String cameraIp, String username, String password) {
|
|
|
+ try {
|
|
|
+ String recordingServiceUrl = this.getServiceEndpoint(cameraIp, "http://www.onvif.org/ver10/recording/wsdl");
|
|
|
+ log.info("回放录制配置服务端点: {}", recordingServiceUrl);
|
|
|
+ List<RecordingInfo> recordingToken = this.getRecordingToken(recordingServiceUrl, username, password);
|
|
|
+ log.info("录制配置Token: {}", JSON.toJSONString(recordingToken));
|
|
|
+ String replayServiceUrl = this.getServiceEndpoint(cameraIp, "http://www.onvif.org/ver10/replay/wsdl");
|
|
|
+ log.info("回放流服务端点: {}", recordingServiceUrl);
|
|
|
+ String streamUri = this.getPlayStreamUri(replayServiceUrl, ((RecordingInfo)recordingToken.get(0)).getRecordingToken(), username, password);
|
|
|
+ log.info("获取到回放流地址: {}", streamUri);
|
|
|
+ return streamUri;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取回放流地址失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<RecordingInfo> getRecordingToken(String recordingServiceUrl, String username, String password) {
|
|
|
+ try {
|
|
|
+ String getRecordingRequest = buildGetRecordingRequest(username, password);
|
|
|
+ String response = this.sendSoapRequest(recordingServiceUrl, getRecordingRequest);
|
|
|
+ return this.parseRecordingInfos(response);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取媒体配置文件失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String buildGetRecordingRequest(String username, String password) throws Exception {
|
|
|
+ MessageFactory messageFactory = MessageFactory.newInstance("SOAP 1.2 Protocol");
|
|
|
+ SOAPMessage soapMessage = messageFactory.createMessage();
|
|
|
+ SOAPPart soapPart = soapMessage.getSOAPPart();
|
|
|
+ SOAPEnvelope envelope = soapPart.getEnvelope();
|
|
|
+ envelope.setPrefix("soap");
|
|
|
+ SOAPHeader defaultHeader = envelope.getHeader();
|
|
|
+ if (defaultHeader != null) {
|
|
|
+ defaultHeader.detachNode();
|
|
|
+ }
|
|
|
+
|
|
|
+ SOAPHeader newHeader = envelope.addHeader();
|
|
|
+ SOAPElement securityElement = newHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
|
|
|
+ securityElement.setAttribute("mustUnderstand", "1");
|
|
|
+ SOAPElement usernameTokenElement = securityElement.addChildElement("UsernameToken", "wsse");
|
|
|
+ SOAPElement usernameElement = usernameTokenElement.addChildElement("Username", "wsse");
|
|
|
+ usernameElement.addTextNode(username);
|
|
|
+ SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", "wsse");
|
|
|
+ String nonce = generateNonce();
|
|
|
+ nonceElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
|
|
|
+ nonceElement.addTextNode(Base64.getEncoder().encodeToString(nonce.getBytes()));
|
|
|
+ String created = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
|
|
|
+ SOAPElement createdElement = usernameTokenElement.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
|
|
|
+ createdElement.addTextNode(created);
|
|
|
+ String passwordDigest = calculatePasswordDigest(nonce, created, password);
|
|
|
+ SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", "wsse");
|
|
|
+ passwordElement.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
|
|
|
+ passwordElement.addTextNode(passwordDigest);
|
|
|
+ envelope.addNamespaceDeclaration("trc", "http://www.onvif.org/ver10/recording/wsdl");
|
|
|
+ SOAPBody soapBody = envelope.getBody();
|
|
|
+ soapBody.setPrefix("soap");
|
|
|
+ soapBody.addChildElement("GetRecordings", "trc");
|
|
|
+ return soapMessageToString(soapMessage);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<RecordingInfo> parseRecordingInfos(String xmlResponse) throws Exception {
|
|
|
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
|
+ factory.setNamespaceAware(true);
|
|
|
+ DocumentBuilder builder = factory.newDocumentBuilder();
|
|
|
+ Document doc = builder.parse(new ByteArrayInputStream(xmlResponse.getBytes(StandardCharsets.UTF_8)));
|
|
|
+ XPathFactory xPathFactory = XPathFactory.newInstance();
|
|
|
+ XPath xpath = xPathFactory.newXPath();
|
|
|
+ xpath.setNamespaceContext(new NamespaceContext() {
|
|
|
+ public String getNamespaceURI(String prefix) {
|
|
|
+ switch (prefix) {
|
|
|
+ case "soap":
|
|
|
+ return "http://www.w3.org/2003/05/soap-envelope";
|
|
|
+ case "trec":
|
|
|
+ return "http://www.onvif.org/ver10/recording/wsdl";
|
|
|
+ case "tt":
|
|
|
+ return "http://www.onvif.org/ver10/schema";
|
|
|
+ default:
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getPrefix(String namespaceURI) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Iterator<String> getPrefixes(String namespaceURI) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ NodeList items = (NodeList)xpath.evaluate("//trec:RecordingItem", doc, XPathConstants.NODESET);
|
|
|
+ List<RecordingInfo> list = new ArrayList();
|
|
|
+
|
|
|
+ for(int i = 0; i < items.getLength(); ++i) {
|
|
|
+ Node item = items.item(i);
|
|
|
+ RecordingInfo info = new RecordingInfo();
|
|
|
+ info.recordingToken = (String)xpath.evaluate("tt:RecordingToken", item, XPathConstants.STRING);
|
|
|
+ info.sourceId = (String)xpath.evaluate(".//tt:Source/tt:SourceId", item, XPathConstants.STRING);
|
|
|
+ info.cameraName = (String)xpath.evaluate(".//tt:Source/tt:Name", item, XPathConstants.STRING);
|
|
|
+ if (info.recordingToken != null) {
|
|
|
+ info.recordingToken = info.recordingToken.trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (info.sourceId != null) {
|
|
|
+ info.sourceId = info.sourceId.trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (info.cameraName != null) {
|
|
|
+ info.cameraName = info.cameraName.trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ list.add(info);
|
|
|
+ }
|
|
|
+
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getPlayStreamUri(String replayServiceUrl, String recordingToken, String username, String password) {
|
|
|
+ try {
|
|
|
+ String getStreamUriRequest = buildGetPlayStreamUriRequest(recordingToken, username, password);
|
|
|
+ String response = this.sendSoapRequest(replayServiceUrl, getStreamUriRequest);
|
|
|
+ Document doc = this.parseXml(response);
|
|
|
+ NodeList uriNodes = doc.getElementsByTagNameNS("*", "Uri");
|
|
|
+ if (uriNodes.getLength() > 0) {
|
|
|
+ return uriNodes.item(0).getTextContent();
|
|
|
+ } else {
|
|
|
+ log.error("获取回放流rtsp地址失败");
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取回放流地址失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String buildGetPlayStreamUriRequest(String recordingToken, String username, String password) throws Exception {
|
|
|
+ MessageFactory messageFactory = MessageFactory.newInstance("SOAP 1.2 Protocol");
|
|
|
+ SOAPMessage soapMessage = messageFactory.createMessage();
|
|
|
+ SOAPPart soapPart = soapMessage.getSOAPPart();
|
|
|
+ SOAPEnvelope envelope = soapPart.getEnvelope();
|
|
|
+ SOAPHeader defaultHeader = envelope.getHeader();
|
|
|
+ if (defaultHeader != null) {
|
|
|
+ defaultHeader.detachNode();
|
|
|
+ }
|
|
|
+
|
|
|
+ SOAPHeader newHeader = envelope.addHeader();
|
|
|
+ SOAPElement securityElement = newHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
|
|
|
+ securityElement.setAttribute("mustUnderstand", "1");
|
|
|
+ SOAPElement usernameTokenElement = securityElement.addChildElement("UsernameToken", "wsse");
|
|
|
+ SOAPElement usernameElement = usernameTokenElement.addChildElement("Username", "wsse");
|
|
|
+ usernameElement.addTextNode(username);
|
|
|
+ SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", "wsse");
|
|
|
+ String nonce = generateNonce();
|
|
|
+ nonceElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
|
|
|
+ nonceElement.addTextNode(Base64.getEncoder().encodeToString(nonce.getBytes()));
|
|
|
+ String created = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
|
|
|
+ SOAPElement createdElement = usernameTokenElement.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
|
|
|
+ createdElement.addTextNode(created);
|
|
|
+ String passwordDigest = calculatePasswordDigest(nonce, created, password);
|
|
|
+ SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", "wsse");
|
|
|
+ passwordElement.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
|
|
|
+ passwordElement.addTextNode(passwordDigest);
|
|
|
+ envelope.addNamespaceDeclaration("trp", "http://www.onvif.org/ver10/replay/wsdl");
|
|
|
+ envelope.addNamespaceDeclaration("tt", "http://www.onvif.org/ver10/schema");
|
|
|
+ SOAPBody soapBody = envelope.getBody();
|
|
|
+ SOAPElement getStreamUriElement = soapBody.addChildElement("GetReplayUri", "trp");
|
|
|
+ SOAPElement streamSetupElement = getStreamUriElement.addChildElement("StreamSetup");
|
|
|
+ SOAPElement streamElement = streamSetupElement.addChildElement("Stream");
|
|
|
+ streamElement.addTextNode("RTP-Unicast");
|
|
|
+ SOAPElement transportElement = streamSetupElement.addChildElement("Transport");
|
|
|
+ SOAPElement protocolElement = transportElement.addChildElement("Protocol");
|
|
|
+ protocolElement.addTextNode("RTSP");
|
|
|
+ SOAPElement recordingTokenElement = getStreamUriElement.addChildElement("RecordingToken");
|
|
|
+ recordingTokenElement.addTextNode(recordingToken);
|
|
|
+ return soapMessageToString(soapMessage);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String sendSoapRequest(String url, String soapRequest) {
|
|
|
+ try {
|
|
|
+ URL soapUrl = new URL(url);
|
|
|
+ HttpURLConnection connection = (HttpURLConnection)soapUrl.openConnection();
|
|
|
+ connection.setRequestMethod("POST");
|
|
|
+ connection.setDoOutput(true);
|
|
|
+ connection.setDoInput(true);
|
|
|
+ connection.setUseCaches(false);
|
|
|
+ connection.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
|
|
|
+ connection.setRequestProperty("SOAPAction", "");
|
|
|
+
|
|
|
+ try (OutputStream os = connection.getOutputStream()) {
|
|
|
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
|
|
|
+ Throwable errorReader = null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ writer.write(soapRequest);
|
|
|
+ writer.flush();
|
|
|
+ } catch (Throwable var160) {
|
|
|
+ errorReader = var160;
|
|
|
+ throw var160;
|
|
|
+ } finally {
|
|
|
+ if (writer != null) {
|
|
|
+ if (errorReader != null) {
|
|
|
+ try {
|
|
|
+ writer.close();
|
|
|
+ } catch (Throwable var159) {
|
|
|
+ errorReader.addSuppressed(var159);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ writer.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ int responseCode = connection.getResponseCode();
|
|
|
+ if (responseCode == 200) {
|
|
|
+ String var12;
|
|
|
+ try (InputStream is = connection.getInputStream()) {
|
|
|
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
|
|
+ Throwable var180 = null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ StringBuilder responseBuilder = new StringBuilder();
|
|
|
+
|
|
|
+ String line;
|
|
|
+ while((line = reader.readLine()) != null) {
|
|
|
+ responseBuilder.append(line).append("\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ var12 = responseBuilder.toString();
|
|
|
+ } catch (Throwable var164) {
|
|
|
+ var180 = var164;
|
|
|
+ throw var164;
|
|
|
+ } finally {
|
|
|
+ if (reader != null) {
|
|
|
+ if (var180 != null) {
|
|
|
+ try {
|
|
|
+ reader.close();
|
|
|
+ } catch (Throwable var157) {
|
|
|
+ var180.addSuppressed(var157);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ reader.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return var12;
|
|
|
+ } else {
|
|
|
+ try (InputStream errorStream = connection.getErrorStream()) {
|
|
|
+ BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8));
|
|
|
+ Throwable var9 = null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ StringBuilder errorBuilder = new StringBuilder();
|
|
|
+
|
|
|
+ String line;
|
|
|
+ while((line = errorReader.readLine()) != null) {
|
|
|
+ errorBuilder.append(line).append("\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.error("发送SOAP请求失败");
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ } catch (Throwable var168) {
|
|
|
+ var9 = var168;
|
|
|
+ throw var168;
|
|
|
+ } finally {
|
|
|
+ if (errorReader != null) {
|
|
|
+ if (var9 != null) {
|
|
|
+ try {
|
|
|
+ errorReader.close();
|
|
|
+ } catch (Throwable var155) {
|
|
|
+ var9.addSuppressed(var155);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ errorReader.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送 SOAP 请求失败: {}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String soapMessageToString(SOAPMessage message) throws SOAPException {
|
|
|
+ try {
|
|
|
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
|
|
+ Transformer transformer = transformerFactory.newTransformer();
|
|
|
+ transformer.setOutputProperty("indent", "yes");
|
|
|
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
|
|
+ StringWriter writer = new StringWriter();
|
|
|
+ transformer.transform(new DOMSource(message.getSOAPPart()), new StreamResult(writer));
|
|
|
+ return writer.toString();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("转换SOAP消息失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<String> getRealSources(String cameraIp, String username, String password) {
|
|
|
+ try {
|
|
|
+ String mediaServiceUrl = this.getServiceEndpoint(cameraIp, "http://www.onvif.org/ver10/media/wsdl");
|
|
|
+ log.info("媒体服务端点: {}", mediaServiceUrl);
|
|
|
+ List<String> sources = this.getSources(mediaServiceUrl, username, password);
|
|
|
+ return sources;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取NVR设备通道数失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<String> getSources(String mediaServiceUrl, String username, String password) {
|
|
|
+ try {
|
|
|
+ String getProfilesRequest = buildGetSourceRequest(username, password);
|
|
|
+ String response = this.sendSoapRequest(mediaServiceUrl, getProfilesRequest);
|
|
|
+ Document doc = this.parseXml(response);
|
|
|
+ NodeList sources = doc.getElementsByTagNameNS("http://www.onvif.org/ver10/media/wsdl", "VideoSources");
|
|
|
+ if (sources.getLength() <= 0) {
|
|
|
+ log.error("获取设备source失败");
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ } else {
|
|
|
+ List<String> videoSourceTokenList = new ArrayList();
|
|
|
+
|
|
|
+ for(int i = 0; i < sources.getLength(); ++i) {
|
|
|
+ Element profileElement = (Element)sources.item(i);
|
|
|
+ String videoSourceToken = profileElement.getAttribute("token");
|
|
|
+ videoSourceTokenList.add(videoSourceToken);
|
|
|
+ }
|
|
|
+
|
|
|
+ return videoSourceTokenList;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取设备source失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getNvrRealTimeStreamUrl(String cameraIp, String username, String password, String videoSourceToken) {
|
|
|
+ try {
|
|
|
+ String mediaServiceUrl = this.getServiceEndpoint(cameraIp, "http://www.onvif.org/ver10/media/wsdl");
|
|
|
+ log.info("媒体服务端点: {}", mediaServiceUrl);
|
|
|
+ String profileToken = this.getNvrProfilesToken(mediaServiceUrl, username, password, videoSourceToken);
|
|
|
+ log.info("配置文件Token: {}", profileToken);
|
|
|
+ String streamUri = this.getStreamUri(mediaServiceUrl, profileToken, username, password);
|
|
|
+ log.info("获取到实时流地址: {}", streamUri);
|
|
|
+ return streamUri;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取实时流地址失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getNvrProfilesToken(String mediaServiceUrl, String username, String password, String videoSourceToken) {
|
|
|
+ try {
|
|
|
+ String getProfilesRequest = buildGetProfilesRequest(username, password);
|
|
|
+ String response = this.sendSoapRequest(mediaServiceUrl, getProfilesRequest);
|
|
|
+ Document doc = this.parseXml(response);
|
|
|
+ NodeList profileNodes = doc.getElementsByTagNameNS("http://www.onvif.org/ver10/media/wsdl", "Profiles");
|
|
|
+ if (profileNodes.getLength() > 0) {
|
|
|
+ for(int i = 0; i < profileNodes.getLength(); ++i) {
|
|
|
+ Element profileElement = (Element)profileNodes.item(i);
|
|
|
+ String profileToken = profileElement.getAttribute("token");
|
|
|
+ NodeList videoSourceConfigNodes = profileElement.getElementsByTagNameNS("http://www.onvif.org/ver10/schema", "VideoSourceConfiguration");
|
|
|
+ if (videoSourceConfigNodes.getLength() > 0) {
|
|
|
+ Element videoSourceConfig = (Element)videoSourceConfigNodes.item(0);
|
|
|
+ NodeList sourceTokenNodes = videoSourceConfig.getElementsByTagNameNS("http://www.onvif.org/ver10/schema", "SourceToken");
|
|
|
+ if (sourceTokenNodes.getLength() > 0) {
|
|
|
+ String sourceToken = sourceTokenNodes.item(0).getTextContent();
|
|
|
+ if (videoSourceToken.equals(sourceToken)) {
|
|
|
+ log.info("找到匹配的Profile: {} 对应 VideoSourceToken: {}", profileToken, videoSourceToken);
|
|
|
+ return profileToken;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ log.error("获取媒体配置文件失败");
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取媒体配置文件失败:{}", e.getMessage());
|
|
|
+ throw new BusinessException("播放失败", -1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String buildGetSourceRequest(String username, String password) throws Exception {
|
|
|
+ MessageFactory messageFactory = MessageFactory.newInstance("SOAP 1.2 Protocol");
|
|
|
+ SOAPMessage soapMessage = messageFactory.createMessage();
|
|
|
+ SOAPPart soapPart = soapMessage.getSOAPPart();
|
|
|
+ SOAPEnvelope envelope = soapPart.getEnvelope();
|
|
|
+ SOAPHeader defaultHeader = envelope.getHeader();
|
|
|
+ if (defaultHeader != null) {
|
|
|
+ defaultHeader.detachNode();
|
|
|
+ }
|
|
|
+
|
|
|
+ SOAPHeader newHeader = envelope.addHeader();
|
|
|
+ SOAPElement securityElement = newHeader.addChildElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
|
|
|
+ securityElement.setAttribute("mustUnderstand", "1");
|
|
|
+ SOAPElement usernameTokenElement = securityElement.addChildElement("UsernameToken", "wsse");
|
|
|
+ SOAPElement usernameElement = usernameTokenElement.addChildElement("Username", "wsse");
|
|
|
+ usernameElement.addTextNode(username);
|
|
|
+ SOAPElement nonceElement = usernameTokenElement.addChildElement("Nonce", "wsse");
|
|
|
+ String nonce = generateNonce();
|
|
|
+ nonceElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
|
|
|
+ nonceElement.addTextNode(Base64.getEncoder().encodeToString(nonce.getBytes()));
|
|
|
+ String created = ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
|
|
|
+ SOAPElement createdElement = usernameTokenElement.addChildElement("Created", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
|
|
|
+ createdElement.addTextNode(created);
|
|
|
+ String passwordDigest = calculatePasswordDigest(nonce, created, password);
|
|
|
+ SOAPElement passwordElement = usernameTokenElement.addChildElement("Password", "wsse");
|
|
|
+ passwordElement.setAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
|
|
|
+ passwordElement.addTextNode(passwordDigest);
|
|
|
+ envelope.addNamespaceDeclaration("trt", "http://www.onvif.org/ver10/media/wsdl");
|
|
|
+ SOAPBody soapBody = envelope.getBody();
|
|
|
+ SOAPElement getStreamUriElement = soapBody.addChildElement("GetVideoSources", "trt");
|
|
|
+ return soapMessageToString(soapMessage);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Document parseXml(String xmlString) throws Exception {
|
|
|
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
|
+ factory.setNamespaceAware(true);
|
|
|
+ DocumentBuilder builder = factory.newDocumentBuilder();
|
|
|
+ return builder.parse(new InputSource(new StringReader(xmlString)));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String generateNonce() {
|
|
|
+ SecureRandom random = new SecureRandom();
|
|
|
+ byte[] nonceBytes = new byte[16];
|
|
|
+ random.nextBytes(nonceBytes);
|
|
|
+ return new String(nonceBytes, StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String calculatePasswordDigest(String nonce, String created, String password) throws Exception {
|
|
|
+ String combined = nonce + created + password;
|
|
|
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
|
|
|
+ byte[] digest = md.digest(combined.getBytes(StandardCharsets.UTF_8));
|
|
|
+ return Base64.getEncoder().encodeToString(digest);
|
|
|
+ }
|
|
|
+}
|