172 lines
5.9 KiB
Plaintext
172 lines
5.9 KiB
Plaintext
import "pkg:/source/roku_modules/log/LogMixin.brs"
|
|
|
|
'
|
|
' Task used to discover jellyfin servers on the local network
|
|
'
|
|
sub init()
|
|
m.log = log.Logger("ServerDiscoveryTask")
|
|
m.top.functionName = "execute"
|
|
end sub
|
|
|
|
sub execute()
|
|
m.servers = []
|
|
m.serverUrlMap = {}
|
|
m.locationUrlMap = {}
|
|
'send both requests at the same time
|
|
SendSSDPBroadcast()
|
|
SendClientDiscoveryBroadcast()
|
|
|
|
ts = CreateObject("roTimespan")
|
|
maxTimeMs = 2200
|
|
|
|
'monitor each port and collect messages
|
|
while True
|
|
elapsed = ts.TotalMilliseconds()
|
|
if elapsed >= maxTimeMs
|
|
exit while
|
|
end if
|
|
|
|
msg = Wait(100, m.ssdp.port)
|
|
if msg <> invalid
|
|
ProcessSSDPResponse(msg)
|
|
end if
|
|
|
|
msg = Wait(100, m.clientDiscovery.port)
|
|
if msg <> invalid
|
|
ProcessClientDiscoveryResponse(msg)
|
|
end if
|
|
|
|
end while
|
|
|
|
m.top.content = m.servers
|
|
m.log.debug("Jellyfin servers found", m.servers[0], m.servers[1], m.servers[2])
|
|
end sub
|
|
|
|
sub AddServer(server)
|
|
if m.serverUrlMap[server.baseUrl] = invalid
|
|
m.serverUrlMap[server.baseUrl] = true
|
|
m.servers.push(server)
|
|
end if
|
|
end sub
|
|
|
|
sub SendClientDiscoveryBroadcast()
|
|
m.clientDiscovery = {
|
|
port: CreateObject("roMessagePort"),
|
|
address: CreateObject("roSocketAddress"),
|
|
socket: CreateObject("roDatagramSocket"),
|
|
urlTransfer: CreateObject("roUrlTransfer")
|
|
}
|
|
m.clientDiscovery.address.SetAddress("255.255.255.255:7359")
|
|
m.clientDiscovery.urlTransfer.SetPort(m.clientDiscoveryPort)
|
|
m.clientDiscovery.socket.SetMessagePort(m.clientDiscovery.port)
|
|
m.clientDiscovery.socket.SetSendToAddress(m.clientDiscovery.address)
|
|
m.clientDiscovery.socket.NotifyReadable(true)
|
|
m.clientDiscovery.socket.SetBroadcast(true)
|
|
m.clientDiscovery.socket.SendStr("Who is JellyfinServer?")
|
|
end sub
|
|
|
|
sub ProcessClientDiscoveryResponse(message)
|
|
if Type(message) = "roSocketEvent" and message.GetSocketId() = m.clientDiscovery.socket.GetId() and m.clientDiscovery.socket.IsReadable()
|
|
try
|
|
responseJson = m.clientDiscovery.socket.ReceiveStr(4096)
|
|
server = ParseJson(responseJson)
|
|
AddServer({
|
|
name: server.Name,
|
|
baseUrl: server.Address,
|
|
'hardcoded icon since this service doesn't include them
|
|
iconUrl: "pkg:/images/logo-icon120.jpg",
|
|
iconWidth: 120,
|
|
iconHeight: 120
|
|
})
|
|
m.log.info("Found Jellyfin server using client discovery", server.Address)
|
|
catch e
|
|
m.log.error("Error scanning for jellyfin server", message)
|
|
end try
|
|
end if
|
|
end sub
|
|
|
|
sub SendSSDPBroadcast()
|
|
m.ssdp = {
|
|
port: CreateObject("roMessagePort"),
|
|
address: CreateObject("roSocketAddress"),
|
|
socket: CreateObject("roDatagramSocket"),
|
|
urlTransfer: CreateObject("roUrlTransfer")
|
|
}
|
|
m.ssdp.address.SetAddress("239.255.255.250:1900")
|
|
m.ssdp.socket.SetMessagePort(m.ssdp.port)
|
|
m.ssdp.socket.SetSendToAddress(m.ssdp.address)
|
|
m.ssdp.socket.NotifyReadable(true)
|
|
m.ssdp.urlTransfer.SetPort(m.ssdp.port)
|
|
|
|
'brightscript can't escape characters in strings, so create a few vars here so we can use them in the strings below
|
|
Q = Chr(34)
|
|
CRLF = Chr(13) + Chr(10)
|
|
|
|
ssdpStr = "M-SEARCH * HTTP/1.1" + CRLF
|
|
ssdpStr += "HOST: 239.255.255.250:1900" + CRLF
|
|
ssdpStr += "MAN: " + Q + "ssdp:discover" + Q + CRLF
|
|
ssdpStr += "ST:urn:schemas-upnp-org:device:MediaServer:1" + CRLF
|
|
ssdpStr += "MX: 2" + CRLF
|
|
ssdpStr += CRLF
|
|
|
|
m.ssdp.socket.SendStr(ssdpStr)
|
|
end sub
|
|
|
|
sub ProcessSSDPResponse(message)
|
|
locationUrl = invalid
|
|
if Type (message) = "roSocketEvent" and message.GetSocketId() = m.ssdp.socket.GetId() and m.ssdp.socket.IsReadable()
|
|
recvStr = m.ssdp.socket.ReceiveStr(4096)
|
|
match = CreateObject("roRegex", "\r\nLocation:\s*(.*?)\s*\r\n", "i").Match(recvStr)
|
|
if match.Count() = 2
|
|
locationUrl = match[1]
|
|
end if
|
|
end if
|
|
|
|
if locationUrl = invalid
|
|
return
|
|
else if m.locationUrlMap[locationUrl] <> invalid
|
|
m.log.warn("Already discovered this location", locationUrl)
|
|
return
|
|
end if
|
|
|
|
m.locationUrlMap[locationUrl] = true
|
|
|
|
http = CreateObject("roUrlTransfer")
|
|
http.SetUrl(locationUrl)
|
|
responseText = http.GetToString()
|
|
xml = CreateObject("roXMLElement")
|
|
'if we successfully parsed the response, process it
|
|
if xml.Parse(responseText)
|
|
deviceNode = xml.GetNamedElementsCi("device")[0]
|
|
manufacturer = deviceNode.GetNamedElementsCi("manufacturer").GetText()
|
|
'only process jellyfin servers
|
|
if lcase(manufacturer) = "jellyfin"
|
|
'find the largest icon
|
|
width = 0
|
|
server = invalid
|
|
icons = deviceNode.GetNamedElementsCi("iconList")[0].GetNamedElementsCi("icon")
|
|
for each iconNode in icons
|
|
iconUrl = iconNode.GetNamedElementsCi("url").GetText()
|
|
baseUrl = invalid
|
|
match = CreateObject("roRegex", "(.*?)\/dlna\/", "i").Match(iconUrl)
|
|
if match.Count() = 2
|
|
baseUrl = match[1]
|
|
end if
|
|
loopResult = {
|
|
name: deviceNode.GetNamedElementsCi("friendlyName").GetText(),
|
|
baseUrl: baseUrl,
|
|
iconUrl: iconUrl,
|
|
iconWidth: iconNode.GetNamedElementsCi("width")[0].GetText().ToInt(),
|
|
iconHeight: iconNode.GetNamedElementsCi("height")[0].GetText().ToInt()
|
|
}
|
|
if baseUrl <> invalid and loopResult.iconWidth > width
|
|
width = loopResult.iconWidth
|
|
server = loopResult
|
|
end if
|
|
end for
|
|
AddServer(server)
|
|
m.log.info("Found jellyfin server using SSDP and DLNA", server.baseUrl)
|
|
end if
|
|
end if
|
|
end sub
|