iOS-DLNA(UPnP-SOAP)

1,380 阅读4分钟

SOAP

控制点和服务之间使用简单对象访问协议(Simple Object Access Protocol)的格式。SOAP的底层协议一般也是HTTP。在UPnP中把SOAP控制/响应信息分成3种: UPnP Action RequestUPnP Action Response-SuccessUPnP Action Response-ErrorSOAPSSDP不一样,所使用的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>
  • SOAPACTIONurn: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>
  • faultcodeSOAP规定使用元素,调用动作遇到的错误类型,一般为s:Client
  • faultstringSOAP规定使用元素,值必须为UPnPError
  • detailSOAP规定使用元素,错误的详细描述信息。
  • UPnPErrorUPnP规定元素。
  • errorCodeUPnP规定元素,整数。详见下表。
  • errorDescriptionUPnP规定元素,简短错误描述。

image.png

投屏基本命令及其响应

设置播放资源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>
  • SOAPACTIONurn:upnp-org:serviceId:AVTransport#SetAVTransportURIHTTPHeader
  • InstanceID:设置当前播放时期时为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>
  • SOAPACTIONurn:upnp-org:serviceId:AVTransport#PlayHTTPHeader
  • InstanceID:设置当前播放时期时为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#PauseHTTPHeader
  • InstanceID:设置当前播放时期时为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>
  • SOAPACTIONurn:upnp-org:serviceId:AVTransport#SeekHTTPHeader
  • InstanceID: 一般为 0 。
  • UnitREL_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];
        }
        。。。。。。
    }
}