Add first live-view flow from the camera wall
This commit is contained in:
Binary file not shown.
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user