SOAP
控制点和服务之间使用简单对象访问协议(Simple Object Access Protocol)的格式。SOAP的底层协议一般也是HTTP。在UPnP中把SOAP控制/响应信息分成3种: UPnP Action Request、UPnP Action Response-Success 和 UPnP Action Response-Error。SOAP和SSDP不一样,所使用的HTTP消息是有Body内容,Body部分可以写想要调用的动作,叫做Action invocation,可能还要传递参数,如想播放一个网络上的视频,就要把视频的URL传过去;服务收到后要response,回答能不能执行调用,如果出错则返回一个错误代码。
动作调用(UPnP Action Request)
HTTPHeader
POST <control URL> HTTP/1.0
Host: hostname:portNumber
Content-Lenght: byte in body
Content-Type: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:serviceType:v#actionName"
control URL:设备描述文件中相应服务的<controlURL>SOAPACTION:urn:schemas-upnp-org:service:serviceType:v对应该设备描述文件相应服务的<serviceType>字段。actionName需要调用动作的名称,对应相应服务的服务描述文件<SCPDURL>中的<action>的<name>字段。 HTTPBody
<!--必有字段-->
<?xml version="1.0" encoding="utf-8"?>
<!--SOAP必有字段-->
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<!--Body内部分根据不同动作不同-->
<!--动作名称-->
<u:actionName xmlns:u="urn:schemas-upnp-org:service:serviceType:v">
<!--输入参数名称和值-->
<argumentName>in arg values</argumentName>
<!--若有多个参数则需要提供-->
</u:actionName>
</s:Body>
</s:Envelope>
actionName:需要调用动作的名称,对应相应服务的服务描述文件<SCPDURL>中的<action>的<name>字段。argumentName:输入参数名称,对应相应服务的服务描述文件<SCPDURL>中的<action>``<argument>``<name>字段。in arg values:输入参数值,具体的可以通过,可以通过服务描述文件<SCPDURL>``<action>``<relatedStateVariable>提到的状态变量来得知值得类型。
动作响应(UPnP Action Response-Success)
收到控制点发来的动作调用请求后,设备上的服务必须执行动作调用。并在30s内响应。如果需要超过30s才能完成执行的动作,则可以先返回一个应答消息,等动作执行完成再利用事件机制返回动作响应。
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<!--之前部分为固定字段-->
<u:actionNameResponse xmlns:u="urn:schemas-upnp-org:service:serviceType:v">
<!--输出变量名称和值-->
<arugumentName>out arg value</arugumentName>
<!--若有多个输出变量则继续写,没有可以不存在输出变量-->
</u:actionNameResponse>
</s:Body>
</s:Envelope>
actionNameResponse: 响应的动作名称arugumentName: 当动作带有输出变量时必选,输出变量名称out arg values: 输出变量名称值
动作错误响应(UPnP Action Response-Error)
如果处理动作过程中出现错误,则返回一个错误响应。
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:Fault>
<!--之前部分为固定字段-->
<faultcode>s:Client</faultcode>
<faultstring>UPnPError</faultstring>
<detail>
<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
<errorCode>402</errorCode>
<errorDescription>Invalid or Missing Args</errorDescription>
</UPnPError>
</detail>
</u:actionNameResponse>
</s:Body>
</s:Envelope>
faultcode:SOAP规定使用元素,调用动作遇到的错误类型,一般为s:Client。faultstring:SOAP规定使用元素,值必须为UPnPError。detail:SOAP规定使用元素,错误的详细描述信息。UPnPError:UPnP规定元素。errorCode:UPnP规定元素,整数。详见下表。errorDescription:UPnP规定元素,简短错误描述。
投屏基本命令及其响应
设置播放资源URI
/// 请求
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<CurrentURI>http://125.39.35.130/mp4files/4100000003406F25/clips.vorwaerts-gmbh.de/big_buck_bunny.mp4</CurrentURI>
<CurrentURIMetaData />
</u:SetAVTransportURI>
</s:Body>
</s:Envelope>
/// 响应
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:SetAVTransportURIResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"/>
</s:Body>
</s:Envelope>
SOAPACTION:urn:upnp-org:serviceId:AVTransport#SetAVTransportURIHTTPHeaderInstanceID:设置当前播放时期时为0即可。CurrentURI:播放资源URI。CurrentURIMetaData:媒体meta数据,可以为空。
有些设备传递播放URI后就能直接播放,有些设备设置URI后需要发送播放命令,可以在接收到
SetAVTransportURIResponse响应后调用播放动作来解决。
播放
/// 请求
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<Speed>1</Speed>
</u:Play>
</s:Body>
</s:Envelope>
/// 响应
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:PlayResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1" />
</s:Body>
</s:Envelope>
SOAPACTION:urn:upnp-org:serviceId:AVTransport#PlayHTTPHeaderInstanceID:设置当前播放时期时为0即可。Speed:播放速度,默认传1。
暂停
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:Pause xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
</u:Pause>
</s:Body>
</s:Envelope>
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:PauseResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1" />
</s:Body>
</s:Envelope>
SOAPACTION:urn:upnp-org:serviceId:AVTransport#PauseHTTPHeaderInstanceID:设置当前播放时期时为0即可。
跳转至特定进度或视频
/// 请求
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:Seek xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<Unit>REL_TIME</Unit>
<Target>00:02:21</Target>
</u:Seek>
</s:Body>
</s:Envelope>
/// 响应
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:SeekResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1" />
</s:Body>
</s:Envelope>
SOAPACTION:urn:upnp-org:serviceId:AVTransport#SeekHTTPHeaderInstanceID: 一般为 0 。Unit:REL_TIME(跳转到某个进度)或TRACK_NR(跳转到某个视频)。Target:目标值,可以是00:02:21格式的进度或者整数的TRACK_NR。
核心代码如下
/// 播放
- (void)play{
CLUPnPAction *action = [[CLUPnPAction alloc] initWithAction:@"Play"];
[action setArgumentValue:@"0" forName:@"InstanceID"];
[action setArgumentValue:@"1" forName:@"Speed"];
[self postRequestWith:action];
}
/// 获取动作请求的xml
- (NSString *)getPostXMLFile{
GDataXMLElement *xmlEle = [GDataXMLElement elementWithName:@"s:Envelope"];
[xmlEle addChild:[GDataXMLElement attributeWithName:@"s:encodingStyle" stringValue:@"http://schemas.xmlsoap.org/soap/encoding/"]];
[xmlEle addChild:[GDataXMLElement attributeWithName:@"xmlns:s" stringValue:@"http://schemas.xmlsoap.org/soap/envelope/"]];
[xmlEle addChild:[GDataXMLElement attributeWithName:@"xmlns:u" stringValue:[self getServiceType]]];
GDataXMLElement *command = [GDataXMLElement elementWithName:@"s:Body"];
[command addChild:self.XMLElement];
[xmlEle addChild:command];
return xmlEle.XMLString;
}
/// 获取动作请求,Header的参数SOAPACTION的值格式为:SOAPACTION: "urn:schemas-upnp-org:service:serviceType:v#actionName"
- (NSString *)getSOAPAction{
if (_serviceType == CLUPnPServiceAVTransport) {
return [NSString stringWithFormat:@"\"%@#%@\"", serviceType_AVTransport, _action];
}else{
return [NSString stringWithFormat:@"\"%@#%@\"", serviceType_RenderingControl, _action];
}
}
/// 请求动作
- (void)postRequestWith:(CLUPnPAction *)action{
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:[action getPostUrlStrWith:_model]];
NSString *postXML = [action getPostXMLFile];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request addValue:@"text/xml" forHTTPHeaderField:@"Content-Type"];
[request addValue:[action getSOAPAction] forHTTPHeaderField:@"SOAPAction"];
request.HTTPBody = [postXML dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error || data == nil) {
/// 请求回调数据异常
[self undefinedResponse:nil postXML:postXML];
}else{
/// 请求回调数据解析
[self parseRequestResponseData:data postXML:postXML];
}
}];
[dataTask resume];
}
/// 请求回调
- (void)resultsWith:(NSArray *)array postXML:(NSString *)postXML{
for (int i = 0; i < array.count; i++) {
GDataXMLElement *ele = [array objectAtIndex:i];
/** 设置资源url后的响应 */
if ([[ele name] hasSuffix:@"SetAVTransportURIResponse"]) {
[self _SetAVTransportURIResponse];
[self getTransportInfo];
}
。。。。。。
}
}