diff --git a/builds/visionLabs-roku-smoketest.zip b/builds/visionLabs-roku-smoketest.zip
index 7ba1714..4ac1d72 100644
Binary files a/builds/visionLabs-roku-smoketest.zip and b/builds/visionLabs-roku-smoketest.zip differ
diff --git a/components/MainScene.brs b/components/MainScene.brs
index 229640d..365c141 100644
--- a/components/MainScene.brs
+++ b/components/MainScene.brs
@@ -2,13 +2,21 @@ 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"),
@@ -16,6 +24,8 @@ sub init()
m.top.findNode("cam4")
]
m.refreshToken = 0
+ m.selectedIndex = 0
+ m.isLiveView = false
loadConfig()
end sub
@@ -48,6 +58,7 @@ sub loadConfig()
m.refreshTimer.control = "start"
renderCameras()
+ updateSelection()
end sub
sub renderCameras()
@@ -74,10 +85,93 @@ sub renderCameras()
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 ""
@@ -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" />
+
+
+
+
+
+
diff --git a/config/cameras.json b/config/cameras.json
index 6b2c6be..15964d0 100644
--- a/config/cameras.json
+++ b/config/cameras.json
@@ -7,6 +7,10 @@
"refreshSeconds": 15,
"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": {
"type": "grid",
"columns": 2,
diff --git a/manifest b/manifest
index 90318bb..158a068 100644
--- a/manifest
+++ b/manifest
@@ -2,5 +2,5 @@ title=visionLabs
subtitle=Labs home camera viewer
major_version=0
minor_version=1
-build_version=3
+build_version=4
ui_resolutions=hd,fhd