diff --git a/README.md b/README.md index d64ae04..a637cde 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ visionLabs Roku is a private Roku app scaffold for viewing home security cameras The current starter path is Frigate-backed, with Roku reading a static inventory of cameras that are managed by Frigate instead of embedding raw camera credentials in the app. +The first playback strategy is `snapshotFirst`, which lets the Roku app render a useful camera wall before the direct live stream endpoint shape is finalized. + ## Product direction - Brand family follows the user's existing `Labs` naming pattern. @@ -36,6 +38,7 @@ Roku is not a great target for raw RTSP feeds. With Frigate in the middle, the c 1. Set the real Frigate base URL in `config/cameras.json` 2. Map the four starter cameras in Frigate using the same IDs as the Roku config -3. Load config into the UI -4. Render a static multi-camera grid -5. Add edit mode and persistence strategy +3. Verify that Frigate snapshots load at `/api//latest.jpg` +4. Render the snapshot wall on Roku +5. Choose the direct live stream path for full playback +6. Add edit mode and persistence strategy diff --git a/components/MainScene.brs b/components/MainScene.brs index d6e8748..d2a4ec6 100644 --- a/components/MainScene.brs +++ b/components/MainScene.brs @@ -2,6 +2,12 @@ sub init() m.top.setFocus(true) m.backend = m.top.findNode("backend") m.status = m.top.findNode("status") + m.cameraPosters = [ + m.top.findNode("poster1"), + m.top.findNode("poster2"), + m.top.findNode("poster3"), + m.top.findNode("poster4") + ] m.cameraNodes = [ m.top.findNode("cam1"), m.top.findNode("cam2"), @@ -31,9 +37,10 @@ sub loadConfig() end if cameras = config.cameras - m.status.text = cameras.count().toStr() + " cameras loaded from static config" + m.status.text = cameras.count().toStr() + " cameras loaded, snapshot-first prototype" for i = 0 to m.cameraNodes.count() - 1 + poster = m.cameraPosters[i] node = m.cameraNodes[i] if i < cameras.count() then @@ -48,9 +55,36 @@ sub loadConfig() label = label + chr(10) + "Frigate ID: " + camera.frigateCamera end if + poster.uri = buildSnapshotUrl(config, camera) node.text = label else + poster.uri = "" node.text = "Empty slot" end if end for 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 diff --git a/components/MainScene.xml b/components/MainScene.xml index 21242e6..677304e 100644 --- a/components/MainScene.xml +++ b/components/MainScene.xml @@ -36,35 +36,59 @@ text="Loading camera config..." font="font:MediumSystemFont" color="0x8B949EFF" /> -