Merge pull request #465 from TwitchBronBron/ssdp-scan
This commit is contained in:
commit
312a1ce818
24
components/JFButton.brs
Normal file
24
components/JFButton.brs
Normal file
|
@ -0,0 +1,24 @@
|
|||
sub init()
|
||||
m.top.observeFieldScoped("text", "onTextChanged")
|
||||
m.top.iconUri = ""
|
||||
m.top.focusedIconUri = ""
|
||||
m.top.showFocusFootprint = true
|
||||
m.top.minWidth = 0
|
||||
end sub
|
||||
|
||||
'
|
||||
' Whenever the text changes, pad both sides with whitespace so we can center the button text
|
||||
'
|
||||
sub onTextChanged()
|
||||
addSpaceAfter = true
|
||||
minChars = m.top.minChars
|
||||
if minChars = invalid then minChars = 50
|
||||
while m.top.text.Len() < minChars
|
||||
if addSpaceAfter
|
||||
m.top.text = m.top.text + Chr(160)
|
||||
else
|
||||
m.top.text = Chr(160) + m.top.text
|
||||
end if
|
||||
addSpaceAfter = addSpaceAfter = false
|
||||
end while
|
||||
end sub
|
7
components/JFButton.xml
Normal file
7
components/JFButton.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="JFButton" extends="Button">
|
||||
<interface>
|
||||
<field id="minChars" type="int" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="JFButton.brs" />
|
||||
</component>
|
6
components/Spinner.brs
Normal file
6
components/Spinner.brs
Normal file
|
@ -0,0 +1,6 @@
|
|||
sub init()
|
||||
m.top.poster.uri = "pkg:/images/spinner.png"
|
||||
m.top.control = "start"
|
||||
m.top.clockwise = true
|
||||
m.top.spinInterval = 2
|
||||
end sub
|
4
components/Spinner.xml
Normal file
4
components/Spinner.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="Spinner" extends="BusySpinner">
|
||||
<script type="text/brightscript" uri="Spinner.brs" />
|
||||
</component>
|
35
components/config/JFServer.brs
Normal file
35
components/config/JFServer.brs
Normal file
|
@ -0,0 +1,35 @@
|
|||
sub init() as void
|
||||
m.poster = m.top.findNode("poster")
|
||||
m.name = m.top.findNode("name")
|
||||
m.baseUrl = m.top.findNode("baseUrl")
|
||||
m.labels = m.top.findNode("labels")
|
||||
setTextColor(0)
|
||||
end sub
|
||||
|
||||
sub itemContentChanged() as void
|
||||
server = m.top.itemContent
|
||||
|
||||
m.poster.uri = server.iconUrl
|
||||
m.name.text = server.name
|
||||
m.baseUrl.text = server.baseUrl
|
||||
end sub
|
||||
|
||||
sub onFocusPercentChange(event)
|
||||
'print "focusPercentChange: " ; event.getData()
|
||||
setTextColor(event.getData())
|
||||
end sub
|
||||
|
||||
sub setTextColor(percentFocused)
|
||||
white = "0xffffffff"
|
||||
black = "0x00000099"
|
||||
if percentFocused > .4
|
||||
color = black
|
||||
else
|
||||
color = white
|
||||
end if
|
||||
|
||||
children = m.labels.getChildren(-1, 0)
|
||||
for each child in children
|
||||
child.color = color
|
||||
end for
|
||||
end sub
|
22
components/config/JFServer.xml
Normal file
22
components/config/JFServer.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component name="JFServer" extends="Group">
|
||||
|
||||
<interface>
|
||||
<field id="itemContent" type="node" onchange="itemContentChanged" />
|
||||
<field id="focusPercent" type="float" onchange="onFocusPercentChange" />
|
||||
</interface>
|
||||
|
||||
<script type="text/brightscript" uri="JFServer.brs" />
|
||||
|
||||
<children>
|
||||
<Poster id="poster" translation="[0,5]" width="90" height="90" />
|
||||
<Group id="labels" translation="[100,0]">
|
||||
<Label text="Name:" horizAlign="left" font="font:MediumBoldSystemFont" width="130" height="65" translation="[0,5]" />
|
||||
<Label text="URL:" horizAlign="left" font="font:MediumBoldSystemFont" width="130" height="65" translation="[0,55]" />
|
||||
|
||||
<Label id="name" horizAlign="left" font="font:MediumSystemFont" height="65" translation="[125,5]" />
|
||||
<Label id="baseUrl" horizAlign="left" font="font:MediumSystemFont" height="65" translation="[125,55]" />
|
||||
</Group>
|
||||
</children>
|
||||
|
||||
</component>
|
168
components/config/ServerDiscoveryTask.brs
Normal file
168
components/config/ServerDiscoveryTask.brs
Normal file
|
@ -0,0 +1,168 @@
|
|||
'
|
||||
' Task used to discover jellyfin servers on the local network
|
||||
'
|
||||
sub init()
|
||||
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
|
||||
print 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
|
||||
})
|
||||
print "Found Jellyfin server using client discovery at " + server.Address
|
||||
catch e
|
||||
print "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
|
||||
print "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)
|
||||
print "Found jellyfin server using SSDP and DLNA at " + server.baseUrl
|
||||
end if
|
||||
end if
|
||||
end sub
|
7
components/config/ServerDiscoveryTask.xml
Normal file
7
components/config/ServerDiscoveryTask.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component name="ServerDiscoveryTask" extends="Task">
|
||||
<interface>
|
||||
<field id="content" type="array" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="ServerDiscoveryTask.brs" />
|
||||
</component>
|
111
components/config/SetServerScreen.brs
Normal file
111
components/config/SetServerScreen.brs
Normal file
|
@ -0,0 +1,111 @@
|
|||
sub init()
|
||||
m.top.setFocus(true)
|
||||
m.top.optionsAvailable = false
|
||||
|
||||
m.spinner = m.top.findNode("spinner")
|
||||
m.serverPicker = m.top.findNode("serverPicker")
|
||||
m.serverUrlTextbox = m.top.findNode("serverUrlTextbox")
|
||||
m.serverUrlContainer = m.top.findNode("serverUrlContainer")
|
||||
m.serverUrlOutline = m.top.findNode("serverUrlOutline")
|
||||
m.submit = m.top.findNode("submit")
|
||||
|
||||
m.top.observeField("serverUrl", "clearErrorMessage")
|
||||
|
||||
ScanForServers()
|
||||
end sub
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
print "onKeyEvent", key, press
|
||||
|
||||
if not press then return true
|
||||
handled = true
|
||||
|
||||
if key = "OK" and m.serverPicker.hasFocus()
|
||||
m.top.serverUrl = m.serverPicker.content.getChild(m.serverPicker.itemFocused).baseUrl
|
||||
m.submit.setFocus(true)
|
||||
'if the user pressed the down key and we are already at the last child of server picker, then change focus to the url textbox
|
||||
else if key = "down" and m.serverPicker.hasFocus() and m.serverPicker.content.getChildCount() > 0 and m.serverPicker.itemFocused = m.serverPicker.content.getChildCount() - 1
|
||||
m.serverUrlContainer.setFocus(true)
|
||||
|
||||
'user navigating up to the server picker from the input box (it's only focusable if it has items)
|
||||
else if key = "up" and m.serverUrlContainer.hasFocus() and m.servers.Count() > 0
|
||||
m.serverPicker.setFocus(true)
|
||||
else if key = "OK" and m.serverUrlContainer.hasFocus()
|
||||
ShowKeyboard()
|
||||
'focus the serverUrl input from submit button
|
||||
else if key = "up" and m.submit.hasFocus()
|
||||
m.serverUrlContainer.setFocus(true)
|
||||
'focus the submit button from serverUrl
|
||||
else if key = "down" and m.serverUrlContainer.hasFocus()
|
||||
m.submit.setFocus(true)
|
||||
else
|
||||
handled = false
|
||||
end if
|
||||
'show/hide input box outline
|
||||
m.serverUrlOutline.visible = m.serverUrlContainer.isInFocusChain()
|
||||
|
||||
return handled
|
||||
end function
|
||||
|
||||
sub ScanForServers()
|
||||
m.ssdpScanner = CreateObject("roSGNode", "ServerDiscoveryTask")
|
||||
'run the task
|
||||
m.ssdpScanner.observeField("content", "ScanForServersComplete")
|
||||
m.ssdpScanner.control = "RUN"
|
||||
end sub
|
||||
|
||||
sub ScanForServersComplete(event)
|
||||
m.servers = event.getData()
|
||||
|
||||
items = CreateObject("roSGNode", "ContentNode")
|
||||
for each server in m.servers
|
||||
server.subtype = "ContentNode"
|
||||
'add new fields for every server property onto the ContentNode (rather than making a dedicated component just to hold data...)
|
||||
items.update([server], true)
|
||||
end for
|
||||
m.serverPicker.content = items
|
||||
m.spinner.visible = false
|
||||
|
||||
'if we have at least one server, focus on the server picker
|
||||
if m.servers.Count() > 0
|
||||
m.serverPicker.setFocus(true)
|
||||
'no servers found...focus on the input textbox
|
||||
else
|
||||
m.serverUrlContainer.setFocus(true)
|
||||
'show/hide input box outline
|
||||
m.serverUrlOutline.visible = true
|
||||
end if
|
||||
|
||||
end sub
|
||||
|
||||
sub ShowKeyboard()
|
||||
dialog = createObject("roSGNode", "KeyboardDialog")
|
||||
dialog.title = tr("Enter the server name or ip address")
|
||||
dialog.buttons = [tr("OK"), tr("Cancel")]
|
||||
dialog.text = m.serverUrlTextbox.text
|
||||
|
||||
m.top.getscene().dialog = dialog
|
||||
m.dialog = dialog
|
||||
|
||||
dialog.observeField("buttonSelected", "onDialogButton")
|
||||
end sub
|
||||
|
||||
function onDialogButton()
|
||||
d = m.dialog
|
||||
button_text = d.buttons[d.buttonSelected]
|
||||
|
||||
if button_text = tr("OK")
|
||||
m.serverUrlTextbox.text = d.text
|
||||
m.dialog.close = true
|
||||
return true
|
||||
else if button_text = tr("Cancel")
|
||||
m.dialog.close = true
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end if
|
||||
end function
|
||||
|
||||
sub clearErrorMessage()
|
||||
m.top.errorMessage = ""
|
||||
end sub
|
40
components/config/SetServerScreen.xml
Normal file
40
components/config/SetServerScreen.xml
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component name="SetServerScreen" extends="JFGroup">
|
||||
<interface>
|
||||
<field id="serverUrl" type="string" alias="serverUrlTextbox.text" />
|
||||
<field id="serverWidth" alias="serverUrlOutline.width,serverUrlTextbox.width,serverUrlContainer.width,submitSizer.width" value="1620" />
|
||||
<field id="serverHeight" alias="serverUrlOutline.height,serverUrlTextbox.height,serverUrlContainer.height" value="60" />
|
||||
<field id="errorMessage" type="string" alias="errorMessage.text"/>
|
||||
</interface>
|
||||
|
||||
<children>
|
||||
<LayoutGroup translation="[150,150]" itemSpacings="40">
|
||||
<LayoutGroup>
|
||||
<label text="Connect to Server" id="prompt" font="font:LargeBoldSystemFont" />
|
||||
<label text="Pick a Jellyfin server from the local network" />
|
||||
</LayoutGroup>
|
||||
<!--background for server picker-->
|
||||
<Rectangle color="0x00000020" width="1620" height="400">
|
||||
<Spinner id="spinner" translation="[717, 136]" />
|
||||
<MarkupList id="serverPicker" translation="[50, 20]" itemComponentName="JFServer" itemSpacing="[0, 10]" itemSize="[1520, 100]" numRows="3" vertFocusAnimationStyle="floatingFocus" />
|
||||
</Rectangle>
|
||||
|
||||
<label text="...or enter server URL manually:" translation="[0, 690]" />
|
||||
|
||||
<Rectangle id="serverUrlContainer" color="0x00000000">
|
||||
<TextEditBox id="serverUrlTextbox" hintText="e.g. 192.168.1.100:8096 or https://example.com/jellyfin"></TextEditBox>
|
||||
<Poster id="serverUrlOutline" visible="false" uri="pkg:/images/hd_focus.9.png" />
|
||||
</Rectangle>
|
||||
<label id="errorMessage" text="" font="font:MediumSystemFont" color="#ff0000FF" />
|
||||
<LayoutGroup horizAlignment="center">
|
||||
<JFButton id="submit" minChars="30" text="Submit"></JFButton>
|
||||
<!--add a known width invisibile element to allow the button to be centered-->
|
||||
<Rectangle id="submitSizer" width="1620" height="0" color="#00000000" />
|
||||
</LayoutGroup>
|
||||
</LayoutGroup>
|
||||
|
||||
</children>
|
||||
|
||||
<script type="text/brightscript" uri="SetServerScreen.brs" />
|
||||
|
||||
</component>
|
BIN
images/fhd_focus.9.png
Normal file
BIN
images/fhd_focus.9.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 885 B |
BIN
images/hd_focus.9.png
Normal file
BIN
images/hd_focus.9.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 B |
BIN
images/logo-icon120.jpg
Normal file
BIN
images/logo-icon120.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
images/spinner.png
Normal file
BIN
images/spinner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
|
@ -414,5 +414,20 @@
|
|||
<translation>The requested content does not exist on the server</translation>
|
||||
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter the server name or ip address</source>
|
||||
<translation>Enter the server name or ip address</translation>
|
||||
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pick a Jellyfin server from the local network</source>
|
||||
<translation>Pick a Jellyfin server from the local network</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>...or enter server URL manually:</source>
|
||||
<translation>...or enter server URL manually:</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
179
package-lock.json
generated
179
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "jellyfin-roku",
|
||||
"version": "1.4.8",
|
||||
"version": "1.4.9",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -258,6 +258,15 @@
|
|||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
|
@ -851,15 +860,17 @@
|
|||
}
|
||||
},
|
||||
"brighterscript": {
|
||||
"version": "0.30.9",
|
||||
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.30.9.tgz",
|
||||
"integrity": "sha512-4Raf4Mjdzi6D+14UVtIuW4r5RZaS5uli5AWzGZhfH87pd4jP89dJgANqxrstA5Pseo7xGPg0QxVKoAC13icayg==",
|
||||
"version": "0.39.4",
|
||||
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.39.4.tgz",
|
||||
"integrity": "sha512-VJR+6A+bMyRu4Fd+5xlSFAVSv4NtqNpkROZuS9x7pBGRqAjy6BBPjmSPOeRjK4TGv9CvaH3OD5ZYtsi2kaY1IA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rokucommunity/bslib": "^0.1.1",
|
||||
"@xml-tools/parser": "^1.0.7",
|
||||
"array-flat-polyfill": "^1.0.1",
|
||||
"chalk": "^2.4.2",
|
||||
"chokidar": "^3.0.2",
|
||||
"chevrotain": "^7.0.1",
|
||||
"chokidar": "^3.5.1",
|
||||
"clear": "^0.1.0",
|
||||
"cross-platform-clear-console": "^2.3.0",
|
||||
"debounce-promise": "^3.1.0",
|
||||
|
@ -874,23 +885,17 @@
|
|||
"moment": "^2.23.0",
|
||||
"p-settle": "^2.1.0",
|
||||
"parse-ms": "^2.1.0",
|
||||
"roku-deploy": "^3.2.4",
|
||||
"roku-deploy": "^3.4.1",
|
||||
"serialize-error": "^7.0.1",
|
||||
"source-map": "^0.7.3",
|
||||
"vscode-languageserver": "^6.1.1",
|
||||
"vscode-languageserver-protocol": "~3.15.3",
|
||||
"vscode-languageserver": "7.0.0",
|
||||
"vscode-languageserver-protocol": "3.16.0",
|
||||
"vscode-languageserver-textdocument": "^1.0.1",
|
||||
"vscode-uri": "^2.1.1",
|
||||
"xml2js": "^0.4.19",
|
||||
"yargs": "^15.4.0"
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
|
@ -937,38 +942,21 @@
|
|||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
|
||||
"integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "~3.1.1",
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.1",
|
||||
"glob-parent": "~5.1.0",
|
||||
"fsevents": "~2.3.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.5.0"
|
||||
"readdirp": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
|
@ -1054,15 +1042,6 @@
|
|||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
@ -1081,101 +1060,19 @@
|
|||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"vscode-jsonrpc": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz",
|
||||
"integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==",
|
||||
"dev": true
|
||||
},
|
||||
"vscode-languageserver": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz",
|
||||
"integrity": "sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"vscode-languageserver-protocol": "^3.15.3"
|
||||
}
|
||||
},
|
||||
"vscode-languageserver-protocol": {
|
||||
"version": "3.15.3",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz",
|
||||
"integrity": "sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"vscode-jsonrpc": "^5.0.1",
|
||||
"vscode-languageserver-types": "3.15.1"
|
||||
}
|
||||
},
|
||||
"vscode-languageserver-types": {
|
||||
"version": "3.15.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz",
|
||||
"integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==",
|
||||
"dev": true
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^20.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3716,9 +3613,9 @@
|
|||
}
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@rokucommunity/bslint": "^0.4.0",
|
||||
"brighterscript": "^0.30.9",
|
||||
"brighterscript": "^0.39.4",
|
||||
"rooibos-cli": "^1.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
sub Main (args as Dynamic) as Void
|
||||
|
||||
|
||||
' If the Rooibos files are included in deployment, run tests
|
||||
'bs:disable-next-line
|
||||
if type(Rooibos__Init) = "Function" then Rooibos__Init()
|
||||
|
@ -21,7 +21,7 @@ sub Main (args as Dynamic) as Void
|
|||
|
||||
m.overhang = CreateObject("roSGNode", "JFOverhang")
|
||||
m.scene.insertChild(m.overhang, 0)
|
||||
|
||||
|
||||
app_start:
|
||||
m.overhang.title = ""
|
||||
' First thing to do is validate the ability to use the API
|
||||
|
@ -64,7 +64,7 @@ sub Main (args as Dynamic) as Void
|
|||
group.control = "play"
|
||||
ReportPlayback(group, "start")
|
||||
m.overhang.visible = false
|
||||
else
|
||||
else
|
||||
dialog = createObject("roSGNode", "Dialog")
|
||||
dialog.id = "OKDialog"
|
||||
dialog.title = tr("Not found")
|
||||
|
@ -197,7 +197,7 @@ sub Main (args as Dynamic) as Void
|
|||
group.control = "play"
|
||||
ReportPlayback(group, "start")
|
||||
m.overhang.visible = false
|
||||
else
|
||||
else
|
||||
dialog = createObject("roSGNode", "Dialog")
|
||||
dialog.id = "OKDialog"
|
||||
dialog.title = tr("Error loading Channel Data")
|
||||
|
@ -344,7 +344,7 @@ sub Main (args as Dynamic) as Void
|
|||
movie.favorite = not movie.favorite
|
||||
else
|
||||
' If there are no other button matches, check if this is a simple "OK" Dialog & Close if so
|
||||
dialog = msg.getRoSGNode()
|
||||
dialog = msg.getRoSGNode()
|
||||
if dialog.id = "OKDialog"
|
||||
dialog.unobserveField("buttonSelected")
|
||||
dialog.close = true
|
||||
|
@ -433,7 +433,7 @@ sub Main (args as Dynamic) as Void
|
|||
group.control = "play"
|
||||
ReportPlayback(group, "start")
|
||||
m.overhang.visible = false
|
||||
else
|
||||
else
|
||||
dialog = createObject("roSGNode", "Dialog")
|
||||
dialog.id = "OKDialog"
|
||||
dialog.title = tr("Not found")
|
||||
|
@ -607,4 +607,3 @@ sub SendPerformanceBeacon(signalName as string)
|
|||
m.scene.signalBeacon(signalName)
|
||||
end if
|
||||
end sub
|
||||
|
||||
|
|
|
@ -1,30 +1,20 @@
|
|||
function CreateServerGroup()
|
||||
' Get and Save Jellyfin Server Information
|
||||
group = CreateObject("roSGNode", "ConfigScene")
|
||||
m.scene.appendChild(group)
|
||||
port = CreateObject("roMessagePort")
|
||||
group.findNode("prompt").text = tr("Connect to Server")
|
||||
screen = CreateObject("roSGNode", "SetServerScreen")
|
||||
m.scene.appendChild(screen)
|
||||
port = CreateObject("roMessagePort")
|
||||
m.colors = {}
|
||||
|
||||
|
||||
config = group.findNode("configOptions")
|
||||
server_field = CreateObject("roSGNode", "ConfigData")
|
||||
server_field.label = tr("Server")
|
||||
server_field.field = "server"
|
||||
server_field.type = "string"
|
||||
if get_setting("server") <> invalid
|
||||
server_field.value = get_setting("server")
|
||||
screen.serverUrl = get_setting("server")
|
||||
end if
|
||||
group.findNode("example").text = tr("192.168.1.100:8096 or https://example.com/jellyfin")
|
||||
items = [ server_field ]
|
||||
config.configItems = items
|
||||
|
||||
button = group.findNode("submit")
|
||||
m.viewModel = {}
|
||||
button = screen.findNode("submit")
|
||||
button.observeField("buttonSelected", port)
|
||||
server_hostname = config.content.getChild(0)
|
||||
group.observeField("backPressed", port)
|
||||
screen.observeField("backPressed", port)
|
||||
|
||||
while true
|
||||
msg = wait(0, port)
|
||||
print type(msg), msg
|
||||
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed()
|
||||
return "false"
|
||||
else if isNodeEvent(msg, "backPressed")
|
||||
|
@ -32,28 +22,13 @@ function CreateServerGroup()
|
|||
else if type(msg) = "roSGNodeEvent"
|
||||
node = msg.getNode()
|
||||
if node = "submit"
|
||||
'Append default ports
|
||||
maxSlashes = 0
|
||||
if left(lcase(server_hostname.value),8) = "https://" or left(lcase(server_hostname.value),7) = "http://" then maxSlashes = 2
|
||||
'Check to make sure entry has no extra slashes before adding default ports.
|
||||
if Instr(0, server_hostname.value, "/") = maxSlashes
|
||||
if server_hostname.value.len() > 5 and mid(server_hostname.value, server_hostname.value.len()-4,1) <> ":" and mid(server_hostname.value, server_hostname.value.len()-5,1) <> ":"
|
||||
if left(lcase(server_hostname.value) ,5) = "https"
|
||||
server_hostname.value = server_hostname.value + ":8920"
|
||||
else
|
||||
server_hostname.value = server_hostname.value + ":8096"
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
'Append http:// to server
|
||||
if left(lcase(server_hostname.value),4) <> "http" then server_hostname.value = "http://" + server_hostname.value
|
||||
serverUrl = standardize_jellyfin_url(screen.serverUrl)
|
||||
'If this is a different server from what we know, reset username/password setting
|
||||
if get_setting("server") <> server_hostname.value
|
||||
if get_setting("server") <> serverUrl
|
||||
set_setting("username", "")
|
||||
set_setting("password", "")
|
||||
end if
|
||||
set_setting("server", server_hostname.value)
|
||||
|
||||
set_setting("server", serverUrl)
|
||||
' Show Connecting to Server spinner
|
||||
dialog = createObject("roSGNode", "ProgressDialog")
|
||||
dialog.title = tr("Connecting to Server")
|
||||
|
@ -67,30 +42,31 @@ function CreateServerGroup()
|
|||
' Maybe don't unset setting, but offer as a prompt
|
||||
' Server not found, is it online? New values / Retry
|
||||
print "Server not found, is it online? New values / Retry"
|
||||
group.findNode("alert").text = tr("Server not found, is it online?")
|
||||
screen.errorMessage = tr("Server not found, is it online?")
|
||||
SignOut()
|
||||
else if serverInfoResult.Error <> invalid and serverInfoResult.Error
|
||||
' If server redirected received, update the URL
|
||||
if serverInfoResult.UpdatedUrl <> invalid
|
||||
server_hostname.value = serverInfoResult.UpdatedUrl
|
||||
serverUrl = serverInfoResult.UpdatedUrl
|
||||
set_setting("server", serverUrl)
|
||||
end if
|
||||
' Display Error Message to user
|
||||
message = tr("Error: ")
|
||||
if serverInfoResult.ErrorCode <> invalid
|
||||
message = message + "[" + serverInfoResult.ErrorCode.toStr() + "] "
|
||||
end if
|
||||
group.findNode("alert").text = message + tr(serverInfoResult.ErrorMessage)
|
||||
screen.errorMessage = message + tr(serverInfoResult.ErrorMessage)
|
||||
SignOut()
|
||||
else
|
||||
group.visible = false
|
||||
screen.visible = false
|
||||
return "true"
|
||||
endif
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
end while
|
||||
|
||||
' Just hide it when done, in case we need to come back
|
||||
group.visible = false
|
||||
screen.visible = false
|
||||
return ""
|
||||
end function
|
||||
|
||||
|
@ -100,7 +76,7 @@ function CreateUserSelectGroup(users = [])
|
|||
end if
|
||||
group = CreateObject("roSGNode", "UserSelect")
|
||||
m.scene.appendChild(group)
|
||||
port = CreateObject("roMessagePort")
|
||||
port = CreateObject("roMessagePort")
|
||||
|
||||
group.itemContent = users
|
||||
group.findNode("userRow").observeField("userSelected", port)
|
||||
|
@ -131,7 +107,7 @@ function CreateSigninGroup(user = "")
|
|||
' Get and Save Jellyfin user login credentials
|
||||
group = CreateObject("roSGNode", "ConfigScene")
|
||||
m.scene.appendChild(group)
|
||||
port = CreateObject("roMessagePort")
|
||||
port = CreateObject("roMessagePort")
|
||||
|
||||
group.findNode("prompt").text = tr("Sign In")
|
||||
|
||||
|
@ -152,7 +128,7 @@ function CreateSigninGroup(user = "")
|
|||
if get_setting("password") <> invalid
|
||||
password_field.value = get_setting("password")
|
||||
end if
|
||||
items = [ username_field, password_field ]
|
||||
items = [username_field, password_field]
|
||||
config.configItems = items
|
||||
|
||||
button = group.findNode("submit")
|
||||
|
@ -206,9 +182,9 @@ function CreateHomeGroup()
|
|||
sidepanel.observeField("closeSidePanel", m.port)
|
||||
new_options = []
|
||||
options_buttons = [
|
||||
{"title": "Search", "id": "goto_search"},
|
||||
{"title": "Change server", "id": "change_server"},
|
||||
{"title": "Sign out", "id": "sign_out"}
|
||||
{ "title": "Search", "id": "goto_search" },
|
||||
{ "title": "Change server", "id": "change_server" },
|
||||
{ "title": "Sign out", "id": "sign_out" }
|
||||
]
|
||||
for each opt in options_buttons
|
||||
o = CreateObject("roSGNode", "OptionsButton")
|
||||
|
@ -225,7 +201,7 @@ function CreateHomeGroup()
|
|||
user_node.base_title = tr("Profile")
|
||||
user_options = []
|
||||
for each user in AvailableUsers()
|
||||
user_options.push({display: user.username + "@" + user.server, value: user.id})
|
||||
user_options.push({ display: user.username + "@" + user.server, value: user.id })
|
||||
end for
|
||||
user_node.choices = user_options
|
||||
user_node.value = get_setting("active_user")
|
||||
|
|
|
@ -31,11 +31,11 @@ end function
|
|||
function ticksToHuman(ticks as longinteger) as string
|
||||
totalSeconds = int(ticks / 10000000)
|
||||
hours = stri(int(totalSeconds / 3600)).trim()
|
||||
minutes = stri(int((totalSeconds - (val(hours)*3600))/60)).trim()
|
||||
seconds = stri(totalSeconds - (val(hours)*3600) - (val(minutes)*60)).trim()
|
||||
minutes = stri(int((totalSeconds - (val(hours) * 3600)) / 60)).trim()
|
||||
seconds = stri(totalSeconds - (val(hours) * 3600) - (val(minutes) * 60)).trim()
|
||||
if val(hours) > 0 and val(minutes) < 10 then minutes = "0" + minutes
|
||||
if val(seconds) < 10 then seconds = "0" + seconds
|
||||
r=""
|
||||
r = ""
|
||||
if val(hours) > 0 then r = hours + ":"
|
||||
r = r + minutes + ":" + seconds
|
||||
return r
|
||||
|
@ -70,10 +70,10 @@ end function
|
|||
|
||||
function div_ceiling(a as integer, b as integer) as integer
|
||||
if a < b then return 1
|
||||
if int(a/b) = a/b
|
||||
return a/b
|
||||
if int(a / b) = a / b
|
||||
return a / b
|
||||
end if
|
||||
return a/b + 1
|
||||
return a / b + 1
|
||||
end function
|
||||
|
||||
'Returns the item selected or -1 on backpress or other unhandled closure of dialog.
|
||||
|
@ -83,7 +83,7 @@ function get_dialog_result(dialog, port)
|
|||
if isNodeEvent(msg, "backPressed")
|
||||
return -1
|
||||
else if isNodeEvent(msg, "itemSelected")
|
||||
return dialog.findNode("optionList").itemSelected
|
||||
return dialog.findNode("optionList").itemSelected
|
||||
end if
|
||||
end while
|
||||
'Dialog has closed outside of this loop, return -1 for failure
|
||||
|
@ -95,8 +95,8 @@ function lastFocusedChild(obj as object) as object
|
|||
for i = 0 to obj.getChildCount()
|
||||
if obj.focusedChild <> invalid
|
||||
child = child.focusedChild
|
||||
end if
|
||||
end for
|
||||
end if
|
||||
end for
|
||||
return child
|
||||
end function
|
||||
|
||||
|
@ -137,9 +137,36 @@ function show_dialog(message as string, options = [], defaultSelection = 0) as i
|
|||
end function
|
||||
|
||||
function message_dialog(message = "" as string)
|
||||
return show_dialog(message,["OK"])
|
||||
return show_dialog(message, ["OK"])
|
||||
end function
|
||||
|
||||
function option_dialog(options, message = "", defaultSelection = 0) as integer
|
||||
return show_dialog(message, options, defaultSelection)
|
||||
end function
|
||||
|
||||
'
|
||||
' Take a jellyfin hostname and ensure it's a full url.
|
||||
' prepend http or https and append default ports, and remove excess slashes
|
||||
'
|
||||
function standardize_jellyfin_url(url as string)
|
||||
'Append default ports
|
||||
maxSlashes = 0
|
||||
if left(url, 8) = "https://" or left(url, 7) = "http://"
|
||||
maxSlashes = 2
|
||||
end if
|
||||
'Check to make sure entry has no extra slashes before adding default ports.
|
||||
if Instr(0, url, "/") = maxSlashes
|
||||
if url.len() > 5 and mid(url, url.len() - 4, 1) <> ":" and mid(url, url.len() - 5, 1) <> ":"
|
||||
if left(url, 5) = "https"
|
||||
url = url + ":8920"
|
||||
else
|
||||
url = url + ":8096"
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
'Append http:// to server
|
||||
if left(url, 4) <> "http"
|
||||
url = "http://" + url
|
||||
end if
|
||||
return url
|
||||
end function
|
||||
|
|
Loading…
Reference in New Issue
Block a user