From 79b3b33f857247fc8adc8bd154c13147ee8911c6 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Sun, 19 Apr 2026 02:49:52 +0000 Subject: [PATCH] Add first live-view flow from the camera wall --- builds/visionLabs-roku-smoketest.zip | Bin 2753 -> 3536 bytes components/MainScene.brs | 111 +++++++++++++++++++++++++++ components/MainScene.xml | 13 ++++ config/cameras.json | 4 + manifest | 2 +- 5 files changed, 129 insertions(+), 1 deletion(-) diff --git a/builds/visionLabs-roku-smoketest.zip b/builds/visionLabs-roku-smoketest.zip index 7ba17141137fd0a5e8728bd919238e0f3a3f5822..4ac1d723d6bd776ff9f5d0de4c87fb395915f55c 100644 GIT binary patch delta 3026 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 delta 2233 zcmZWrc{tPy8~rikGTB{A8oL>?Wy!ASO7>z1p~$`uMIp;zGMLJ4%4CUVY*Az1KKqj9 z+Qx_wri2kD*@=AJKkjp%`+e`9=Xsy=&w0;z&nUD@fX~_j1m*_-fDIVq$-3z}86ua# zCuMbJB#_1hCScCQW(tPH^r64+w|%jz;JqhXl&Q4}CNv*5ND7!6*3Uc)w3Gc$y5v+X zX=T8JavnxDP^O)^auS(nj6s+O?o`uhrP2{iOd051uGU}lc2x|KI1m7gvS&SKg`(W= zh7#>8La8b|?aL0nVYk=(LfAsw51-lKK}lnNE9F$Q`=2RJ4x4mddWVmfS;OK72!{@= zVVBBioYyZNJfk`smO0E5?Q&k}t0kh1uNjeo!~E8R|Dv2NL?1+(_dkLnM!`|+T&Bbn zO@A<*L+RR>jZOXWJC|~iKlX;!QTchD&}IR1TffT}G?ztp7!PJ|3bh48G=+v6 zWv~v~oG84HNEUXkCX+X64r&IVAG+ahYJA{L+ziE#b)4yy4zwhbR{0~;kU3%AAFpkV zy*9#&`)nhZalUve)y3RwPG!uQ9F^M=OIAOw>QarRdEP=0SSo@z_^W;2m{mLpa|y&% z<|SfTFpMIvC6`)5>wCw~B$eG<81WgN)gTp6CP{cg!>ud z)%MpJbS$O9Gz2pJS*JT;e&>=xlx{r+%t-3?DJ z@1nyz)ue-%31?rMwi8`BliM{b*MbmLc0zrTIw(G^eP35)|H!AqcU5nZpwrb5>nBgSK>(*QiAK9Ldb zG)PekV*c#?+@Wqf1y0cuw{7dHKPOM_v8aWtZI&gOuO-!=Y`?tI=B5}@wr+-|C#}Gn zlY*>=P9MxM?nT!9&e1u;C8k*`CR#>PagGso*l5p__SotbvR(*n)itS6J(m@Ja5yMK z1xr06_j@Qy1!hyl|4D(rY}%&^PIYGnfB}wQHkCvLe^%jX+fq<043{hnwBeOU;A|DD z%&l&%cfn+C+*=#C@3gNY;DQL@K%HL_n z@yCFi5R-aCiJNXo3BOIbR}CBl`NJ<#7~vBj*bo02Wh=!n3oozL_&R} z-%=Ifii$rl3hUq>HOhUZa!Dctn{n-hQHhpycFIM4YSqb!(4=1lcbeOoVot)8(53qMZ_q z?|9GY1ZBwvEOh@#ZLtRlO%S1gDT*wh0);LynZPW5-WjTwN7koemR6_n&TsKgy>?zc zU=_C)Z*g`Qon_#|CYwvX?yi{aebiSEUX&^f3qI<@&Nx_qTq)RI^=GJGePB=#zTvi0 z)Vj6UVr4Au%AA$PowPzz{^$P6xw3Bh#;+O2Faba+7yy0;PypZ&jP&;NQTA{P^1AC5 zdd@#I7)eZb4d_7HIA5WN|4MrsIY;B=UIq#+G54j9A{<_+0iZaJupo+_q_lhVfqDuw0u% z*pn8Y{A71D#W%L8xoBwAo-8!T%{Q$E4;H27$7rM8N)EA&pBp8=^Zc@HXb27Y7$!@cWw6u7rlok)b+2OOw^GJFHJ(j+= 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 "" @@ -112,3 +206,20 @@ function addCacheBust(url as String) as String 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 diff --git a/components/MainScene.xml b/components/MainScene.xml index e90809b..0571a8d 100644 --- a/components/MainScene.xml +++ b/components/MainScene.xml @@ -36,7 +36,16 @@ text="Refreshing..." font="font:SmallSystemFont" color="0x8B949EFF" /> +