Files
visionLabs---roku/components/MainScene.brs
2026-04-19 02:49:52 +00:00

226 lines
6.2 KiB
Plaintext

sub init()
m.top.setFocus(true)
m.backend = m.top.findNode("backend")
m.status = m.top.findNode("status")
m.help = m.top.findNode("help")
m.refreshTimer = m.top.findNode("refreshTimer")
m.liveVideo = m.top.findNode("liveVideo")
m.cameraPosters = [
m.top.findNode("poster1"),
m.top.findNode("poster2"),
m.top.findNode("poster3"),
m.top.findNode("poster4")
]
m.cameraFrames = [
m.top.findNode("frame1"),
m.top.findNode("frame2"),
m.top.findNode("frame3"),
m.top.findNode("frame4")
]
m.cameraNodes = [
m.top.findNode("cam1"),
m.top.findNode("cam2"),
m.top.findNode("cam3"),
m.top.findNode("cam4")
]
m.refreshToken = 0
m.selectedIndex = 0
m.isLiveView = false
loadConfig()
end sub
sub loadConfig()
raw = ReadAsciiFile("pkg:/config/cameras.json")
config = ParseJson(raw)
if type(config) <> "roAssociativeArray" then
m.status.text = "Unable to parse cameras.json"
return
end if
m.config = config
if config.doesExist("backend") and config.backend.doesExist("baseUrl") then
m.backend.text = "Direct Frigate"
end if
if not config.doesExist("cameras") then
m.status.text = "No cameras found in config"
return
end if
if config.doesExist("backend") and config.backend.doesExist("refreshSeconds") then
m.refreshTimer.duration = config.backend.refreshSeconds
end if
m.refreshTimer.observeField("fire", "onRefreshTimerFire")
m.refreshTimer.control = "start"
renderCameras()
updateSelection()
end sub
sub renderCameras()
if m.config = invalid or not m.config.doesExist("cameras") then
return
end if
cameras = m.config.cameras
m.status.text = "Refreshing every " + m.refreshTimer.duration.toStr() + "s"
for i = 0 to m.cameraNodes.count() - 1
poster = m.cameraPosters[i]
node = m.cameraNodes[i]
if i < cameras.count() then
camera = cameras[i]
poster.uri = addCacheBust(buildSnapshotUrl(m.config, camera))
node.text = camera.name
else
poster.uri = ""
node.text = "Empty slot"
end if
end for
end sub
sub onRefreshTimerFire()
if m.isLiveView then
return
end if
m.refreshToken = m.refreshToken + 1
renderCameras()
end sub
function onKeyEvent(key as String, press as Boolean) as Boolean
if not press then
return false
end if
lower = LCase(key)
if m.isLiveView then
if lower = "back" then
closeLiveView()
return true
end if
return false
end if
if lower = "right" and m.selectedIndex mod 2 = 0 and m.selectedIndex + 1 < m.config.cameras.count() then
m.selectedIndex = m.selectedIndex + 1
updateSelection()
return true
else if lower = "left" and m.selectedIndex mod 2 = 1 then
m.selectedIndex = m.selectedIndex - 1
updateSelection()
return true
else if lower = "down" and m.selectedIndex < 2 and m.selectedIndex + 2 < m.config.cameras.count() then
m.selectedIndex = m.selectedIndex + 2
updateSelection()
return true
else if lower = "up" and m.selectedIndex >= 2 then
m.selectedIndex = m.selectedIndex - 2
updateSelection()
return true
else if lower = "ok" then
openLiveView()
return true
end if
return false
end function
sub updateSelection()
for i = 0 to m.cameraFrames.count() - 1
if i = m.selectedIndex then
m.cameraFrames[i].color = "0x2EA043FF"
else
m.cameraFrames[i].color = "0x1F262DFF"
end if
end for
end sub
sub openLiveView()
if m.config = invalid or not m.config.doesExist("cameras") or m.selectedIndex >= m.config.cameras.count() then
return
end if
camera = m.config.cameras[m.selectedIndex]
liveUrl = buildLiveUrl(m.config, camera)
content = CreateObject("roSGNode", "ContentNode")
content.url = liveUrl
content.streamformat = "hls"
content.title = camera.name
m.liveVideo.content = content
m.liveVideo.visible = true
m.liveVideo.control = "play"
m.isLiveView = true
m.status.text = "Live view: " + camera.name
m.help.text = "Press Back to return to the snapshot wall."
end sub
sub closeLiveView()
m.liveVideo.control = "stop"
m.liveVideo.visible = false
m.isLiveView = false
m.status.text = "Refreshing every " + m.refreshTimer.duration.toStr() + "s"
m.help.text = "Use arrows to choose a camera. Press OK for live view. Press Back to return."
m.top.setFocus(true)
end sub
function buildSnapshotUrl(config as Object, camera as Object) as String
if not config.doesExist("backend") or not config.backend.doesExist("baseUrl") then
return ""
end if
baseUrl = config.backend.baseUrl
path = ""
if camera.doesExist("snapshotPath") then
path = camera.snapshotPath
else if camera.doesExist("frigateCamera") then
path = "/api/" + camera.frigateCamera + "/latest.jpg"
else
return ""
end if
if right(baseUrl, 1) = "/" and left(path, 1) = "/" then
return left(baseUrl, len(baseUrl) - 1) + path
else if right(baseUrl, 1) <> "/" and left(path, 1) <> "/" then
return baseUrl + "/" + path
end if
return baseUrl + path
end function
function addCacheBust(url as String) as String
token = m.refreshToken.toStr()
if instr(1, url, "?") > 0 then
return url + "&cb=" + token
end if
return url + "?cb=" + token
end function
function buildLiveUrl(config as Object, camera as Object) as String
if not config.doesExist("live") or not config.live.doesExist("baseUrl") or not camera.doesExist("frigateCamera") then
return ""
end if
baseUrl = config.live.baseUrl
path = "/api/stream.m3u8?src=" + camera.frigateCamera
if right(baseUrl, 1) = "/" and left(path, 1) = "/" then
return left(baseUrl, len(baseUrl) - 1) + path
else if right(baseUrl, 1) <> "/" and left(path, 1) <> "/" then
return baseUrl + "/" + path
end if
return baseUrl + path
end function