Merge pull request #515 from jimdogx/feature/jf-roku-404-record-livetv

This commit is contained in:
Neil Burrows 2022-01-20 17:42:55 +00:00 committed by GitHub
commit e976c83f90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 346 additions and 18 deletions

View File

@ -27,6 +27,15 @@ sub loadProgramDetails()
program.channelIndex = channelIndex
program.programIndex = programIndex
program.fullyLoaded = true
' Are we currently recording this program?
if program.json.TimerId <> invalid and program.json.TimerId <> ""
' This is needed here because the callee (onProgramDetailsLoaded) replaces the grid item with
' this newly created item from the server, without this, the red icon
' disappears when the user focuses on the program in question
program.hdSmallIconUrl = "pkg:/images/red.png"
else
program.hdSmallIconUrl = invalid
end if
m.top.programDetails = program
end sub

View File

@ -32,10 +32,16 @@ sub loadSchedule()
for each item in data.Items
program = createObject("roSGNode", "ScheduleProgramData")
program.json = item
' Are we currently recording this program?
if program.json.TimerId <> invalid and program.json.TimerId <> ""
program.hdSmallIconUrl = "pkg:/images/red.png"
else
program.hdSmallIconUrl = invalid
end if
results.push(program)
end for
m.top.schedule = results
end sub
end sub

View File

@ -21,10 +21,26 @@ sub init()
m.channelName = m.top.findNode("channelName")
m.image = m.top.findNode("image")
m.focusAnimationOpacity = m.top.findNode("focusAnimationOpacity")
m.viewChannelFocusAnimationOpacity = m.top.findNode("viewChannelFocusAnimationOpacity")
m.recordFocusAnimationOpacity = m.top.findNode("recordFocusAnimationOpacity")
m.recordSeriesFocusAnimationOpacity = m.top.findNode("recordSeriesFocusAnimationOpacity")
m.focusAnimation = m.top.findNode("focusAnimation")
m.viewChannelButton = m.top.findNode("viewChannelButton")
m.recordButton = m.top.findNode("recordButton")
m.recordSeriesButton = m.top.findNode("recordSeriesButton")
m.viewChannelOutline = m.top.findNode("viewChannelOutline")
m.recordOutline = m.top.findNode("recordOutline")
m.recordSeriesOutline = m.top.findNode("recordSeriesOutline")
m.viewChannelLabel = m.top.findNode("viewChannelButtonLabel")
m.recordLabel = m.top.findNode("recordButtonLabel")
m.recordSeriesLabel = m.top.findNode("recordSeriesButtonLabel")
m.viewChannelButtonBackground = m.top.findNode("viewChannelButtonBackground")
m.recordButtonBackground = m.top.findNode("recordButtonBackground")
m.recordSeriesButtonBackground = m.top.findNode("recordSeriesButtonBackground")
m.focusAnimation.observeField("state", "onAnimationComplete")
@ -47,10 +63,54 @@ sub setupLabels()
isRepeatBackground.height = boundingRect.height + 8
m.episodeDetailsGroup.removeChildIndex(0)
m.viewChannelLabel.text = tr("View Channel")
boundingRect = m.viewChannelButton.boundingRect()
buttonBackground = m.top.findNode("viewChannelButtonBackground")
buttonBackground.width = boundingRect.width + 20
buttonBackground.height = boundingRect.height + 20
viewButtonBackground = m.top.findNode("viewChannelButtonBackground")
viewButtonBackground.width = boundingRect.width + 20
viewButtonBackground.height = boundingRect.height + 20
m.viewChannelOutline.width = viewButtonBackground.width
m.viewChannelOutline.height = viewButtonBackground.height
m.recordLabel.text = tr("Record")
boundingRect = m.recordButton.boundingRect()
recordButtonBackground = m.top.findNode("recordButtonBackground")
recordButtonBackground.width = boundingRect.width + 20
recordButtonBackground.height = boundingRect.height + 20
m.recordOutline.width = recordButtonBackground.width
m.recordOutline.height = recordButtonBackground.height
m.recordSeriesLabel.text = tr("Record Series")
boundingRect = m.recordSeriesButton.boundingRect()
recordSeriesButtonBackground = m.top.findNode("recordSeriesButtonBackground")
recordSeriesButtonBackground.width = boundingRect.width + 20
recordSeriesButtonBackground.height = boundingRect.height + 20
m.recordSeriesOutline.width = recordSeriesButtonBackground.width
m.recordSeriesOutline.height = recordSeriesButtonBackground.height
m.userCanRecord = get_user_setting("livetv.canrecord")
if m.userCanRecord = "false"
m.recordButton.visible = false
m.recordSeriesButton.visible = false
end if
end sub
sub updateLabels(recordText = "Record", recordSeriesText = "Record Series")
m.recordLabel.text = tr(recordText)
m.recordSeriesLabel.text = tr(recordSeriesText)
boundingRect = m.recordButton.boundingRect()
recordButtonBackground = m.top.findNode("recordButtonBackground")
recordButtonBackground.width = boundingRect.width
recordButtonBackground.height = boundingRect.height
m.recordOutline.width = recordButtonBackground.width
m.recordOutline.height = recordButtonBackground.height
boundingRect = m.recordSeriesButton.boundingRect()
recordSeriesButtonBackground = m.top.findNode("recordSeriesButtonBackground")
recordSeriesButtonBackground.width = boundingRect.width
recordSeriesButtonBackground.height = boundingRect.height
m.recordSeriesOutline.width = recordSeriesButtonBackground.width
m.recordSeriesOutline.height = recordSeriesButtonBackground.height
end sub
sub channelUpdated()
@ -69,6 +129,8 @@ end sub
sub programUpdated()
m.top.watchSelectedChannel = false
m.top.recordSelectedChannel = false
m.top.recordSeriesSelectedChannel = false
m.overview.maxLines = m.maxDetailLines
prog = m.top.programDetails
@ -142,6 +204,23 @@ sub programUpdated()
m.image.uri = prog.PosterURL
' If currently being recorded, change button to "Stop Recording"
if prog.json.TimerId <> invalid
if prog.json.SeriesTimerId <> invalid
updateLabels("Cancel Recording", "Cancel Series Recording")
else
updateLabels("Cancel Recording", "Record Series")
end if
else
updateLabels()
end if
' If not a series, hide Record Series button
if prog.json.isSeries <> true ' could be invalid or false
m.recordSeriesButton.visible = false
else
m.recordSeriesButton.visible = true
end if
m.detailsView.visible = "true"
m.noInfoView.visible = "false"
@ -202,10 +281,23 @@ end function
sub focusChanged()
if m.top.hasFocus = true
m.overview.maxLines = m.maxDetailLines
m.focusAnimationOpacity.keyValue = [0, 1]
m.viewChannelFocusAnimationOpacity.keyValue = [0, 1]
m.recordFocusAnimationOpacity.keyValue = [0, 1]
m.recordSeriesFocusAnimationOpacity.keyValue = [0, 1]
m.viewChannelButton.setFocus(true)
m.viewChannelOutline.visible = true
m.recordOutline.visible = false
m.recordSeriesOutline.visible = false
m.viewChannelButtonBackground.blendColor = "#006fab"
m.recordButtonBackground.blendColor = "#000000"
m.recordSeriesButtonBackground.blendColor = "#000000"
else
m.top.watchSelectedChannel = false
m.focusAnimationOpacity.keyValue = [1, 0]
m.top.recordSelectedChannel = false
m.top.recordSeriesSelectedChannel = false
m.viewChannelFocusAnimationOpacity.keyValue = [1, 0]
m.recordFocusAnimationOpacity.keyValue = [1, 0]
m.recordSeriesFocusAnimationOpacity.keyValue = [1, 0]
end if
m.focusAnimation.control = "start"
@ -221,12 +313,54 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "OK"
if key = "OK" and m.viewChannelButton.hasFocus()
m.top.watchSelectedChannel = true
return true
else if key = "OK" and m.recordButton.hasFocus()
m.top.recordSelectedChannel = true
return true
else if key = "OK" and m.recordSeriesButton.hasFocus()
m.top.recordSeriesSelectedChannel = true
return true
end if
if key = "left" or key = "right" or key = "up" or key = "down"
if m.userCanRecord = "true"
if key = "right" and m.viewChannelButton.hasFocus()
m.recordButton.setFocus(true)
m.viewChannelOutline.visible = false
m.recordOutline.visible = true
m.viewChannelButtonBackground.blendColor = "#000000"
m.recordButtonBackground.blendColor = "#006fab"
m.recordSeriesButtonBackground.blendColor = "#000000"
return true
else if key = "right" and m.recordButton.hasFocus()
m.recordSeriesButton.setFocus(true)
m.recordOutline.visible = false
m.recordSeriesOutline.visible = true
m.viewChannelButtonBackground.blendColor = "#000000"
m.recordButtonBackground.blendColor = "#000000"
m.recordSeriesButtonBackground.blendColor = "#006fab"
return true
else if key = "left" and m.recordSeriesButton.hasFocus()
m.recordButton.setFocus(true)
m.recordOutline.visible = true
m.recordSeriesOutline.visible = false
m.viewChannelButtonBackground.blendColor = "#000000"
m.recordButtonBackground.blendColor = "#006fab"
m.recordSeriesButtonBackground.blendColor = "#000000"
return true
else if key = "left" and m.recordButton.hasFocus()
m.viewChannelButton.setFocus(true)
m.viewChannelOutline.visible = true
m.recordOutline.visible = false
m.viewChannelButtonBackground.blendColor = "#006fab"
m.recordButtonBackground.blendColor = "#000000"
m.recordSeriesButtonBackground.blendColor = "#000000"
return true
end if
end if
if key = "up" or key = "down"
return true
end if

View File

@ -33,12 +33,29 @@
</LayoutGroup>
<label id="overview" wrap="true" width="1210" font="font:SmallestSystemFont" />
<!-- View Channel button -->
<Group id="viewChannelButton" opacity="0">
<Poster id="viewChannelButtonBackground" uri="pkg:/images/white.9.png" blendColor="#006fab" />
<Label text="View Channel" translation="[20,20]" />
</Group>
<LayoutGroup layoutDirection="horiz" itemSpacings="[30]">
<!-- View Channel button -->
<Group id="viewChannelButton" opacity="0">
<Poster id="viewChannelButtonBackground" uri="pkg:/images/white.9.png" blendColor="#000000" />
<Poster id="viewChannelOutline" visible="false" uri="pkg:/images/hd_focus.9.png" />
<Label id="viewChannelButtonLabel" text="View Channel" translation="[20,20]" />
</Group>
<!-- Record button -->
<Group id="recordButton" opacity="0">
<Poster id="recordButtonBackground" uri="pkg:/images/white.9.png" blendColor="#000000" />
<Poster id="recordOutline" visible="false" uri="pkg:/images/hd_focus.9.png" />
<Label id="recordButtonLabel" text="Record" translation="[20,20]" />
</Group>
<!-- Record Series button-->
<Group id="recordSeriesButton" opacity="0">
<Poster id="recordSeriesButtonBackground" uri="pkg:/images/white.9.png" blendColor="#000000" />
<Poster id="recordSeriesOutline" visible="false" uri="pkg:/images/hd_focus.9.png" />
<Label id="recordSeriesButtonLabel" text="Record Series" translation="[20,20]" />
</Group>
</LayoutGroup>
</LayoutGroup>
@ -50,11 +67,15 @@
<Label font="font:SmallSystemFont" text="No schedule information" />
</LayoutGroup>
<Animation id="focusAnimation" duration="0.66" repeat="false" easeFunction="linear" >
<FloatFieldInterpolator id="focusAnimationOpacity" key="[0.0, 1]" fieldToInterp="viewChannelButton.opacity" />
<FloatFieldInterpolator id="viewChannelFocusAnimationOpacity" key="[0.0, 1]" fieldToInterp="viewChannelButton.opacity" />
<FloatFieldInterpolator id="recordFocusAnimationOpacity" key="[0.0, 1]" fieldToInterp="recordButton.opacity" />
<FloatFieldInterpolator id="recordSeriesFocusAnimationOpacity" key="[0.0, 1]" fieldToInterp="recordSeriesButton.opacity" />
</Animation>
</children>
<interface>
<field id="WatchSelectedChannel" type="boolean" value="false" />
<field id="recordSelectedChannel" type="boolean" value="false" />
<field id="recordSeriesSelectedChannel" type="boolean" value="false" />
<field id="channel" type="node" onchange="channelUpdated" />
<field id="programDetails" type="node" onchange="programUpdated" />
<field id="height" type="integer" />
@ -62,4 +83,5 @@
</interface>
<script type="text/brightscript" uri="ProgramDetails.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>

View File

@ -0,0 +1,55 @@
sub init()
m.top.functionName = "RecordOrCancelProgram"
end sub
sub RecordOrCancelProgram()
if m.top.programDetails <> invalid
' Are we setting up a recording or canceling one?
TimerId = invalid
if m.top.programDetails.json.TimerId <> invalid and m.top.programDetails.json.TimerId <> ""
TimerId = m.top.programDetails.json.TimerId
end if
if TimerId = invalid
' Setting up a recording...
programId = m.top.programDetails.Id
' Get Live TV default params from server...
url = "LiveTv/Timers/Defaults"
params = {
programId: programId
}
resp = APIRequest(url, params)
data = getJson(resp)
if data <> invalid
' Create recording timer...
if m.top.recordSeries = true
url = "LiveTv/SeriesTimers"
else
url = "LiveTv/Timers"
end if
resp = APIRequest(url)
postJson(resp, FormatJson(data))
m.top.programDetails.hdSmallIconUrl = "pkg:/images/red.png"
else
' Error msg to user?
print "Error getting Live TV Defaults from Server"
end if
else
' Cancelling a recording...
if m.top.recordSeries = true
TimerId = m.top.programDetails.json.SeriesTimerId
url = Substitute("LiveTv/SeriesTimers/{0}", TimerId)
else
url = Substitute("LiveTv/Timers/{0}", TimerId)
end if
resp = APIRequest(url)
deleteVoid(resp)
m.top.programDetails.hdSmallIconUrl = invalid
end if
end if
m.top.recordOperationDone = true
end sub

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="RecordProgramTask" extends="Task">
<interface>
<field id="programDetails" type="node" value="" />
<field id="recordSeries" type="boolean" value="false" />
<field id="recordOperationDone" type="boolean" value="" />
</interface>
<script type="text/brightscript" uri="RecordProgramTask.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>

View File

@ -5,6 +5,8 @@ sub init()
m.detailsPane = m.top.findNode("detailsPane")
m.detailsPane.observeField("watchSelectedChannel", "onWatchChannelSelected")
m.detailsPane.observeField("recordSelectedChannel", "onRecordChannelSelected")
m.detailsPane.observeField("recordSeriesSelectedChannel", "onRecordSeriesChannelSelected")
m.gridStartDate = CreateObject("roDateTime")
m.scheduleGrid.contentStartTime = m.gridStartDate.AsSeconds() - 1800
@ -133,6 +135,7 @@ sub onProgramDetailsLoaded()
channel.ReplaceChild(m.LoadProgramDetailsTask.programDetails, m.LoadProgramDetailsTask.programDetails.programIndex)
m.LoadProgramDetailsTask.programDetails = invalid
m.scheduleGrid.showLoadingDataFeedback = false
end sub
@ -180,6 +183,53 @@ sub onWatchChannelSelected()
m.top.watchChannel = m.detailsPane.channel
end sub
' Handle user selecting "Record Channel" from Program Details
sub onRecordChannelSelected()
if m.detailsPane.recordSelectedChannel = false then return
' Set focus back to grid before showing channel, to ensure grid has focus when we return
focusProgramDetails(false)
m.scheduleGrid.showLoadingDataFeedback = true
m.RecordProgramTask = createObject("roSGNode", "RecordProgramTask")
m.RecordProgramTask.programDetails = m.detailsPane.programDetails
m.RecordProgramTask.recordSeries = false
m.RecordProgramTask.observeField("recordOperationDone", "onRecordOperationDone")
m.RecordProgramTask.control = "RUN"
end sub
' Handle user selecting "Record Series" from Program Details
sub onRecordSeriesChannelSelected()
if m.detailsPane.recordSeriesSelectedChannel = false then return
' Set focus back to grid before showing channel, to ensure grid has focus when we return
focusProgramDetails(false)
m.scheduleGrid.showLoadingDataFeedback = true
m.RecordProgramTask = createObject("roSGNode", "RecordProgramTask")
m.RecordProgramTask.programDetails = m.detailsPane.programDetails
m.RecordProgramTask.recordSeries = true
m.RecordProgramTask.observeField("recordOperationDone", "onRecordOperationDone")
m.RecordProgramTask.control = "RUN"
end sub
sub onRecordOperationDone()
if m.RecordProgramTask.recordSeries = true and m.LoadScheduleTask.state <> "run"
m.LoadScheduleTask.control = "RUN"
else
' This reloads just the details for the currently selected program, so that we don't have to
' reload the entire grid...
channel = m.scheduleGrid.content.GetChild(m.scheduleGrid.programFocusedDetails.focusChannelIndex)
prog = channel.GetChild(m.scheduleGrid.programFocusedDetails.focusIndex)
m.LoadProgramDetailsTask.programId = prog.Id
m.LoadProgramDetailsTask.channelIndex = m.scheduleGrid.programFocusedDetails.focusChannelIndex
m.LoadProgramDetailsTask.programIndex = m.scheduleGrid.programFocusedDetails.focusIndex
m.LoadProgramDetailsTask.control = "RUN"
end if
end sub
' As user scrolls grid, check if more data requries to be loaded
sub onGridScrolled()

View File

@ -9,7 +9,7 @@
<TimeGrid id="scheduleGrid" translation="[0,600]"
automaticLoadingDataFeedback="false" showLoadingDataFeedback="true"
focusBitmapUri="pkg:/images/white.9.png" focusBitmapBlendColor="#006fab"
programTitleFocusedColor="#ffffff"
programTitleFocusedColor="#ffffff" iconFocusedColor="#ff0000"
showPastTimeScreen="true" pastTimeScreenBlendColor="#555555"
/>
<Animation id="gridMoveAnimation" duration="1" repeat="false" easeFunction="outQuad" >

BIN
images/red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -361,6 +361,26 @@
<translation>TV Guide</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>View Channel</source>
<translation>View Channel</translation>
</message>
<message>
<source>Record</source>
<translation>Record</translation>
</message>
<message>
<source>Record Series</source>
<translation>Record Series</translation>
</message>
<message>
<source>Cancel Recording</source>
<translation>Cancel Recording</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Cancel Series Recording</translation>
</message>
<message>
<source>Connecting to Server</source>
<translation>Connecting to Server</translation>

View File

@ -376,6 +376,7 @@ function LoginFlow(startOver = false as boolean)
if get_setting("active_user") <> invalid
m.user = AboutMe()
LoadUserPreferences()
LoadUserAbilities(m.user)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
return true
end if
@ -399,6 +400,7 @@ function LoginFlow(startOver = false as boolean)
end if
LoadUserPreferences()
LoadUserAbilities(m.user)
m.global.sceneManager.callFunc("clearScenes")
'Send Device Profile information to server

View File

@ -110,6 +110,15 @@ function postJson(req, data = "" as string)
return json
end function
function deleteVoid(req)
req.setMessagePort(CreateObject("roMessagePort"))
req.AddHeader("Content-Type", "application/json")
req.SetRequest("DELETE")
req.GetToString()
return true
end function
function get_url()
base = get_setting("server")
if base.right(1) = "/"

View File

@ -146,4 +146,13 @@ sub LoadUserPreferences()
else
unset_user_setting("display.livetv.landing")
end if
end sub
end sub
sub LoadUserAbilities(user)
' Only have one thing we're checking now, but in the future it could be more...
if user.Policy.EnableLiveTvManagement = true
set_user_setting("livetv.canrecord", "true")
else
set_user_setting("livetv.canrecord", "false")
end if
end sub