Add first live-view flow from the camera wall

This commit is contained in:
OpenClaw
2026-04-19 02:49:52 +00:00
parent 85fddca6ea
commit 79b3b33f85
5 changed files with 129 additions and 1 deletions

Binary file not shown.

View File

@@ -2,13 +2,21 @@ sub init()
m.top.setFocus(true) m.top.setFocus(true)
m.backend = m.top.findNode("backend") m.backend = m.top.findNode("backend")
m.status = m.top.findNode("status") m.status = m.top.findNode("status")
m.help = m.top.findNode("help")
m.refreshTimer = m.top.findNode("refreshTimer") m.refreshTimer = m.top.findNode("refreshTimer")
m.liveVideo = m.top.findNode("liveVideo")
m.cameraPosters = [ m.cameraPosters = [
m.top.findNode("poster1"), m.top.findNode("poster1"),
m.top.findNode("poster2"), m.top.findNode("poster2"),
m.top.findNode("poster3"), m.top.findNode("poster3"),
m.top.findNode("poster4") 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.cameraNodes = [
m.top.findNode("cam1"), m.top.findNode("cam1"),
m.top.findNode("cam2"), m.top.findNode("cam2"),
@@ -16,6 +24,8 @@ sub init()
m.top.findNode("cam4") m.top.findNode("cam4")
] ]
m.refreshToken = 0 m.refreshToken = 0
m.selectedIndex = 0
m.isLiveView = false
loadConfig() loadConfig()
end sub end sub
@@ -48,6 +58,7 @@ sub loadConfig()
m.refreshTimer.control = "start" m.refreshTimer.control = "start"
renderCameras() renderCameras()
updateSelection()
end sub end sub
sub renderCameras() sub renderCameras()
@@ -74,10 +85,93 @@ sub renderCameras()
end sub end sub
sub onRefreshTimerFire() sub onRefreshTimerFire()
if m.isLiveView then
return
end if
m.refreshToken = m.refreshToken + 1 m.refreshToken = m.refreshToken + 1
renderCameras() renderCameras()
end sub 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 function buildSnapshotUrl(config as Object, camera as Object) as String
if not config.doesExist("backend") or not config.backend.doesExist("baseUrl") then if not config.doesExist("backend") or not config.backend.doesExist("baseUrl") then
return "" return ""
@@ -112,3 +206,20 @@ function addCacheBust(url as String) as String
return url + "?cb=" + token return url + "?cb=" + token
end function 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

View File

@@ -36,7 +36,16 @@
text="Refreshing..." text="Refreshing..."
font="font:SmallSystemFont" font="font:SmallSystemFont"
color="0x8B949EFF" /> color="0x8B949EFF" />
<Label
id="help"
translation="[60, 1000]"
width="1800"
height="30"
text="Use arrows to choose a camera. Press OK for live view. Press Back to return."
font="font:SmallSystemFont"
color="0x8B949EFF" />
<Timer id="refreshTimer" repeat="true" duration="15" /> <Timer id="refreshTimer" repeat="true" duration="15" />
<Rectangle id="frame1" translation="[54, 124]" width="872" height="372" color="0x2EA043FF" />
<Poster <Poster
id="poster1" id="poster1"
translation="[60, 130]" translation="[60, 130]"
@@ -52,6 +61,7 @@
text="Cam slot 1" text="Cam slot 1"
font="font:MediumSystemFont" font="font:MediumSystemFont"
color="0xFFFFFFFF" /> color="0xFFFFFFFF" />
<Rectangle id="frame2" translation="[994, 124]" width="872" height="372" color="0x1F262DFF" />
<Poster <Poster
id="poster2" id="poster2"
translation="[1000, 130]" translation="[1000, 130]"
@@ -67,6 +77,7 @@
text="Cam slot 2" text="Cam slot 2"
font="font:MediumSystemFont" font="font:MediumSystemFont"
color="0xFFFFFFFF" /> color="0xFFFFFFFF" />
<Rectangle id="frame3" translation="[54, 534]" width="872" height="372" color="0x1F262DFF" />
<Poster <Poster
id="poster3" id="poster3"
translation="[60, 540]" translation="[60, 540]"
@@ -82,6 +93,7 @@
text="Cam slot 3" text="Cam slot 3"
font="font:MediumSystemFont" font="font:MediumSystemFont"
color="0xFFFFFFFF" /> color="0xFFFFFFFF" />
<Rectangle id="frame4" translation="[994, 534]" width="872" height="372" color="0x1F262DFF" />
<Poster <Poster
id="poster4" id="poster4"
translation="[1000, 540]" translation="[1000, 540]"
@@ -97,5 +109,6 @@
text="Cam slot 4" text="Cam slot 4"
font="font:MediumSystemFont" font="font:MediumSystemFont"
color="0xFFFFFFFF" /> color="0xFFFFFFFF" />
<Video id="liveVideo" visible="false" width="1920" height="1080" translation="[0, 0]" />
</children> </children>
</component> </component>

View File

@@ -7,6 +7,10 @@
"refreshSeconds": 15, "refreshSeconds": 15,
"notes": "Keep raw camera credentials in Frigate or go2rtc, not in the Roku repo." "notes": "Keep raw camera credentials in Frigate or go2rtc, not in the Roku repo."
}, },
"live": {
"type": "go2rtcHls",
"baseUrl": "http://192.168.0.223:1984/"
},
"layout": { "layout": {
"type": "grid", "type": "grid",
"columns": 2, "columns": 2,

View File

@@ -2,5 +2,5 @@ title=visionLabs
subtitle=Labs home camera viewer subtitle=Labs home camera viewer
major_version=0 major_version=0
minor_version=1 minor_version=1
build_version=3 build_version=4
ui_resolutions=hd,fhd ui_resolutions=hd,fhd