Android Question I need help on UPnP Subscription Service

GuyBooth

Active Member
Licensed User
I'm writing a control point for streaming from a NAS to an Audio player. I have succeeded in writing the code for loading and playing tracks, stopping and starting the player, but I don't understand how to handle the "Subscription Service" that is required for receiving events (presumably these will tell the Control Point when a track has finished playing or the player has stopped, for example).
The format for playing etc in b4a is, for example:
B4X:
    Dim UPnPReq As HttpJob
    UPnPReq.Initialize("AVTAction " & CodeName, Me)
    UPnPReq.PostString(ActionURL, Action)
    UPnPReq.GetRequest.SetHeader("SOAPAction", sSOAPHeader)
    UPnPReq.GetRequest.SetContentEncoding("utf-8")
    UPnPReq.GetRequest.SetContentType("text/xml; charset=""utf-8""")
    UPnPReq.GetRequest.Timeout = 5000
I have seen documents that refer to the following:

B4X:
SUBSCRIBE publisher path HTTP/1.1
HOST: publisher host:publisher port
CALLBACK:
NT: upnp:event
TIMEOUT: Second-requested subscription duration
Are these headers? How to I write this in b4a?
How do I listen for the events? Is there something special I have to set up in http2utils?
Do I use the word "SUBSCRIBE" in place of "POST", or is it totally different?

I'm pretty proud of myself for having made it this far with the UPnP coding, but now feeling lost again!
Any help would be appreciated.
 

RandomCoder

Well-Known Member
Licensed User
@GuyBooth sorry for the late reply. I too have delved into UPNP control with B4A, although my application is somewhat simpler in that I only browser UPNP devices and play content locally on the device. My source code can be found here.... UPNP Browser - Source Files Included
The app is also published on the play store here... https://play.google.com/store/apps/details?id=b4a.upnpBrowser&hl=en_GB (it has close to 800 downloads and a 30% retention rate. Only 2 ANR's to date both linked to obtaining device information that wasn't present - I think!)

Anyhow, with regard to your question. I think that to subscribe to events you just need to repeat the process, or at least a similar process to what you used when requesting all media devices to respond to a search request.
Here's the section of code I used to find devices...
B4X:
'Send a multicast message to search for devices of device type defined by 'st'
'mx - Maximum wait time in seconds
'st - Search target as defined by the UPNP device architecture
'
'Example: <code>
'M_Search(3, "ssdp:all")</code>
Private Sub M_Search(mx As Int, st As String)
    ' Compile HTTP broadcast message
    Dim msg As String       
    msg = "M-SEARCH * HTTP/1.1~" & _
          "HOST: 239.255.255.250:1900~" & _
          "MAN: " & Chr(34) & "ssdp:discover" & Chr(34) & "~" & _
          "MX: " & mx & "~" & _
          "ST: " & st & "~~"
    ' Replace "~" with "\r\n" [NOTE:- CRLF only sends Chr(13) eg."\r"]
    msg = msg.Replace("~", Chr(13) & Chr(10))
    Debug.debugLog(msg) 'DEBUG
    ' Convert string to UTF8
    Dim data() As Byte
    data = msg.GetBytes("UTF8")
    ' Initialise packet and send
    Dim packet As UDPPacket
    packet.Initialize(data, "239.255.255.250", 1900)
    udpSocket1.Send(packet)
End Sub

Private Sub UDP_PacketArrived (Packet As UDPPacket)
    ' Restart 3s timer each time a new packet arrives
    EOT_Delay_Reset(3000)
    ' Convert received data to UTF8
    serverResponse = BytesToString(Packet.Data, Packet.Offset, Packet.Length, "UTF8")
    Debug.debugLog(CRLF & serverResponse) 'DEBUG
    ' Extract device uuid and description file url
    Get_Details
End Sub
I also found the attached document particularly useful (you've probably already read it?)

kind regards,
RandomCoder
 

Attachments

GuyBooth

Active Member
Licensed User
RandomCoder,

I actually found your code a couple of weeks ago - thank heavens! It put me on the right track to get started. I built on it, and had to deal with a few anomolies (it worked fine for my servers, but the XML parser choked on my Renderer, so I built a new parser using SAX parsing which worked fine). Thank you for publishing that code on the b4a forums!

@GuyBooth sorry for the late reply. I too have delved into UPNP control with B4A, although my application is somewhat simpler in that I only browser UPNP devices and play content locally on the device. My source code can be found here.... UPNP Browser - Source Files Included
The app is also published on the play store here... https://play.google.com/store/apps/details?id=b4a.upnpBrowser&hl=en_GB (it has close to 800 downloads and a 30% retention rate. Only 2 ANR's to date both linked to obtaining device information that wasn't present - I think!)

Anyhow, with regard to your question. I think that to subscribe to events you just need to repeat the process, or at least a similar process to what you used when requesting all media devices to respond to a search request.
Here's the section of code I used to find devices...

I also found the attached document particularly useful (you've probably already read it?)

kind regards,
RandomCoder
I actually have my app working as far as choosing a file to play, loading it into the Renderer and playing it, and loading the Next file into the renderer so it will automatically follow on. The trick now is "Eventing", receiving events from the renderer so I know when a track has finished playing and I can load a new track into the "NextURL" on the renderer. For this there is a special UPnP approach, and this is the one I am trying to implement now. Since my HTTP and XML skills are weak, and UPnP is so complex, I am struggling to understand exactly what message I need to send to start the "Subscription" service they use for this. It isn't the same as repeating the search request, and requires giving the renderer information so it can broadcast event messages to the Control Point. The best information I have found so far is in the Sonos Forums but they are working in C (I don't speak C :() and I don't know enough about HTTP headers to completely follow what they are saying.

To me, the message I have to send (see the start of this thread) is ALL header (in the discussion they actually state there is no "Body") - but am I missing something at the very beginning? Is it in fact a POST message (sorry if my terminology is all wrong)? Yesterday I succeeded in sending the message on an Async Stream but the reply that came back was "412 - Precondition Failed".

My next step may be to try to sniff another control points messages, but I need to solve a problem I have with a local server (that one's on another thread in this forum).
Guy
 

RandomCoder

Well-Known Member
Licensed User
Thank you for publishing that code on the b4a forums!
I'm glad to hear it's helped someone, that was my intention. I'm sure there are better ways of programming it as I'm only really an amateur/hobbiest but hopefully its not too far off the mark.

Since my HTTP and XML skills are weak, and UPnP is so complex, I am struggling to understand exactly what message I need to send to start the "Subscription" service they use for this.
I can fully sympathise with you, it was a first for me to play with networking protocols in B4A and I also knew nothing about UPNP other than it allowed streaming content over a network.

It isn't the same as repeating the search request, and requires giving the renderer information so it can broadcast event messages to the Control Point. The best information I have found so far is in the Sonos Forums but they are working in C (I don't speak C :() and I don't know enough about HTTP headers to completely follow what they are saying.
I think that you have misunderstood what I previously said. I was only suggesting that the approach is similar to what you previously did to search for devices. At least that's how I have interpreted it. Check page 60 of the document I uploaded, it has plenty of detail about eventing. It states that you need to send the following network message...
B4X:
SUBSCRIBE publisher path HTTP/1.1
HOST: publisher host:publisher port
CALLBACK: <delivery URL>
NT: upnp:event
TIMEOUT: Second-requested subscription duration
My next step may be to try to sniff another control points messages.....
I too had to do a little WireShark'ing, one of the problems that I stumbled upon is that CRLF does not actually send a Carriage Return and a Line Feed, you only get the Carriage Return and so I had to send Chr(13) & Chr(10) for my search request to be accepted by all devices (some would respond but other wouldn't when the Line Feed was missing).
Another trick I used a lot was to convert all result strings to LowerCase because of the subtle differences between devices which kept breaking my code. It is certainly a challenge getting your head around UPNP protocols but I also think its highly rewarding once you get it to work. If you need further help just let me know, maybe I could attempt to add UPNP control and Eventing to my App. I've no idea how long it will take though as I only do a little coding each evening alongside checking the latest Forum posts.

Good luck,
RandomCoder ;)
 
Top