Merge pull request #1987 from Renbo2024/next-up-behavior-configuration

New Features: OK button on next up customization and OSD time left customization.
This commit is contained in:
Jimi 2024-10-23 06:24:41 -06:00 committed by GitHub
commit f3941710dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 214 additions and 20 deletions

View File

@ -35,6 +35,7 @@ sub updateSize()
end sub
sub loadParts(data as object)
m.extrasGrp = m.top.getParent().findNode("extrasGrp")
m.top.parentId = data.id
m.people = data.People
m.LoadAdditionalPartsTask.itemId = m.top.parentId
@ -42,6 +43,7 @@ sub loadParts(data as object)
end sub
sub loadPersonVideos(personId)
m.extrasGrp = m.top.getParent().findNode("extrasGrp")
m.personId = personId
m.LoadMoviesTask.itemId = m.personId
m.LoadMoviesTask.observeField("content", "onMoviesLoaded")
@ -114,7 +116,7 @@ sub onLikeThisLoaded()
m.SpecialFeaturesTask.control = "RUN"
end sub
function onSpecialFeaturesLoaded()
sub onSpecialFeaturesLoaded()
data = m.SpecialFeaturesTask.content
m.SpecialFeaturesTask.unobserveField("content")
if data <> invalid and data.count() > 0
@ -132,8 +134,8 @@ function onSpecialFeaturesLoaded()
addRowSize([462, 372])
end if
return m.top.content
end function
showOrHideMe()
end sub
sub onMoviesLoaded()
data = m.LoadMoviesTask.content
@ -179,6 +181,8 @@ sub onSeriesLoaded()
m.top.content.appendChild(row)
end if
m.top.visible = true
showOrHideMe()
end sub
function buildRow(rowTitle as string, items, imgWdth = 0)
@ -218,6 +222,17 @@ sub addRowSize(newRow)
m.top.rowItemSize = newSizeArray
end sub
' don't show popup panel if there is nothing to show
sub showOrHideMe()
if isValid(m.top.content)
if m.top.content.getChildCount() = 0
m.extrasGrp.visible = false
else
m.extrasGrp.visible = true
end if
end if
end sub
sub onRowItemSelected()
m.top.selectedItem = m.top.content.getChild(m.top.rowItemSelected[0]).getChild(m.top.rowItemSelected[1])
end sub

View File

@ -687,6 +687,7 @@ end sub
sub itemSelected()
m.selectedRowItem = m.top.rowItemSelected
m.global.launchSource = "home"
m.top.selectedItem = m.top.content.getChild(m.top.rowItemSelected[0]).getChild(m.top.rowItemSelected[1])
'Prevent the selected item event from double firing

View File

@ -403,7 +403,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.options.setFocus(true)
end if
if key = "down" and m.buttonGrp.isInFocusChain()
if key = "down" and m.buttonGrp.isInFocusChain() and m.extrasGrp.visible = true
m.top.lastFocus = m.extrasGrid
m.extrasGrid.setFocus(true)
m.top.findNode("VertSlider").reverse = false

View File

@ -6,11 +6,28 @@ sub init()
m.top.showRowLabel = [false]
m.top.observeField("selectItemId", "onItemSelected")
updateSize()
m.top.setFocus(true)
end sub
sub onItemSelected()
Id = m.top.selectItemId
if Id <> invalid and Id <> "" and m.top.objects <> invalid
for i = 0 to m.top.objects.items.count() - 1
item = m.top.objects.items[i]
if item.id = Id
m.top.jumpToItem = i
return
end if
end for
end if
end sub
sub updateSize()
m.top.translation = [450, 180]

View File

@ -4,5 +4,8 @@
<field id="objects" type="assocarray" onChange="setupRows" />
<field id="escapeButton" type="string" alwaysNotify="true" />
<field id="doneLoading" type="boolean" />
<field id="selectItemId" type="string" />
<function name="onItemSelected" />
</interface>
</component>

View File

@ -4,9 +4,15 @@ sub init()
m.rows = m.top.findNode("tvEpisodeRow")
m.tvListOptions = m.top.findNode("tvListOptions")
m.top.observeField("selectItemId", "onItemSelected")
m.rows.observeField("doneLoading", "rowsDoneLoading")
end sub
sub onItemSelected()
Id = m.top.selectItemId
m.rows.selectItemId = Id
end sub
sub setupRows()
objects = m.top.objects
m.rows.objects = objects

View File

@ -8,5 +8,8 @@
<field id="objects" type="assocarray" onChange="setupRows" />
<field id="itemSelected" type="integer" />
<field id="doneLoading" type="boolean" />
<field id="selectItemId" type="string" />
<function name="onItemSelected" />
</interface>
</component>

View File

@ -6,6 +6,7 @@ import "pkg:/source/api/sdk.bs"
sub init()
m.top.optionsAvailable = false
m.top.observeField("selectItemId", "onItemSelected")
m.rows = m.top.findNode("picker")
m.poster = m.top.findNode("seasonPoster")
@ -25,6 +26,11 @@ sub setSeasonLoading()
m.top.overhangTitle = tr("Loading...")
end sub
sub onItemSelected()
Id = m.top.selectItemId
m.rows.selectItemId = Id
end sub
' Updates the visibility of the Extras button based on if this season has any extra features
sub setExtraButtonVisibility()
if isValid(m.top.extrasObjects) and isValidAndNotEmpty(m.top.extrasObjects.items)
@ -73,6 +79,7 @@ end function
' OnScreenShown: Callback function when view is presented on screen
'
sub OnScreenShown()
if m.isFirstRun
m.isFirstRun = false
return
@ -80,6 +87,7 @@ sub OnScreenShown()
m.tvEpisodeRow.setFocus(true)
m.top.refreshSeasonDetailsData = not m.top.refreshSeasonDetailsData
end sub
' Handle navigation input from the remote and act on it

View File

@ -19,6 +19,9 @@
<field id="objects" alias="picker.objects" />
<field id="episodeObjects" type="assocarray" />
<field id="extrasObjects" type="assocarray" onChange="setExtraButtonVisibility" />
<field id="selectItemId" type="string" />
<function name="updateSeason" />
<function name="onItemSelected" />
</interface>
</component>

View File

@ -41,7 +41,21 @@ end sub
'
sub onProgressPercentageChanged()
m.videoPositionTime.text = secondsToHuman(m.top.positionTime, true)
m.videoRemainingTime.text = secondsToHuman(m.top.remainingPositionTime, true)
osdmode = m.global.session.user.settings["ui.general.osdremainingtime"]
if m.global.session.user.settings["ui.design.hideclock"]
' in order to honor the hide clocks setting
osdmode = "remaining"
end if
if osdmode = "remaining"
m.videoRemainingTime.text = secondsToHuman(m.top.remainingPositionTime, true)
else if osdmode = "timeofday"
m.videoRemainingTime.text = secondsToEndTime(m.top.remainingPositionTime)
else if osdmode = "both"
m.videoRemainingTime.text = secondsToHuman(m.top.remainingPositionTime, true) + " | " + secondsToEndTime(m.top.remainingPositionTime)
end if
m.progressBar.width = m.progressBarBackground.width * m.top.progressPercentage
end sub

View File

@ -34,7 +34,7 @@
</Rectangle>
<Label id="videoPositionTime" font="font:MediumSystemFont" color="0xffffffFF" translation="[103,985]" />
<Label id="videoRemainingTime" font="font:MediumSystemFont" color="0xffffffFF" horizAlign="right" width="200" translation="[1617,985]" />
<Label id="videoRemainingTime" font="font:MediumSystemFont" color="0xffffffFF" horizAlign="right" width="400" translation="[1450,985]" />
<Timer id="inactivityTimer" duration="1" repeat="true" />
</children>

View File

@ -1321,10 +1321,55 @@
<translation>Use Show Image</translation>
<extracomment>User Setting - Setting option title</extracomment>
</message>
<message>
<source>Episode Next Up Behavior</source>
<translation>Episode Next Up Behavior</translation>
<extracomment>User Setting - Setting option title</extracomment>
</message>
<message>
<source>This controls what clicking OK on a Next Up episode does.</source>
<translation>This controls what clicking OK on a Next Up episode does.</translation>
<extracomment>User Setting - Setting description</extracomment>
</message>
<message>
<source>View Episode Details</source>
<translation>View Episode Details</translation>
<extracomment>User Setting - Setting option title</extracomment>
</message>
<message>
<source>View Season Details</source>
<translation>View Season Details</translation>
<extracomment>User Setting - Setting option title</extracomment>
</message>
<message>
<source>OSD Remaining Time</source>
<translation>OSD Remaining Time</translation>
<extracomment>User Setting - Setting option title</extracomment>
</message>
<message>
<source>How is remaining time represented. Remaining time shows hours, minutes, and seconds remaining. Time of day shows what time the video will complete.</source>
<translation>How is remaining time represented. Remaining time shows hours, minutes, and seconds remaining. Time of day shows what time the video will complete.</translation>
<extracomment>User Setting - Setting description</extracomment>
</message>
<message>
<source>Remaining Time</source>
<translation>Remaining Time</translation>
<extracomment>User Setting - Setting option title</extracomment>
</message>
<message>
<source>Time of Day</source>
<translation>Time of Day</translation>
<extracomment>User Setting - Setting option title</extracomment>
</message>
<message>
<source>Both</source>
<translation>Both</translation>
<extracomment>User Setting - Setting option title</extracomment>
</message>
<message>
<source>Special</source>
<translation>Special</translation>
<extracomment>Special episode of a TV Show</extracomment>
</message>
</context>
</TS>
</TS>

View File

@ -262,6 +262,23 @@
}
]
},
{
"title": "Episode Next Up Behavior",
"description": "This controls what clicking OK on a Next Up episode does.",
"settingName": "ui.general.episodenextupbehavior",
"type": "radio",
"default": "episode",
"options": [
{
"title": "View Episode Details",
"id": "episode"
},
{
"title": "View Season Details",
"id": "season"
}
]
},
{
"title": "Hide Clock",
"description": "Hide all clocks in Jellyfin. Jellyfin will need to be closed and reopened for changes to take effect.",
@ -276,6 +293,27 @@
"type": "integer",
"default": "365"
},
{
"title": "OSD Remaining Time",
"description": "How is remaining time represented. Remaining time shows hours, minutes, and seconds remaining. Time of day shows what time the video will complete.",
"settingName": "ui.general.osdremainingtime",
"type": "radio",
"default": "remaining",
"options": [
{
"title": "Remaining Time",
"id": "remaining"
},
{
"title": "Time of Day",
"id": "timeofday"
},
{
"title": "Both",
"id": "both"
}
]
},
{
"title": "Rewatching Next Up",
"description": "Show already watched episodes in 'Next Up' sections.",

View File

@ -36,7 +36,7 @@ sub Main (args as dynamic) as void
sceneManager = CreateObject("roSGNode", "SceneManager")
sceneManager.observeField("dataReturned", m.port)
m.global.addFields({ app_loaded: false, playstateTask: playstateTask, sceneManager: sceneManager })
m.global.addFields({ app_loaded: false, playstateTask: playstateTask, sceneManager: sceneManager, launchSource: "" })
m.global.addFields({ queueManager: CreateObject("roSGNode", "QueueManager") })
m.global.addFields({ audioPlayer: CreateObject("roSGNode", "AudioPlayer") })
@ -307,22 +307,39 @@ sub Main (args as dynamic) as void
audio_stream_idx = selectedItem.selectedAudioStreamIndex
end if
launchSource = m.global.launchSource
m.global.launchSource = ""
selectedItem.selectedAudioStreamIndex = audio_stream_idx
' Display playback options dialog
if selectedItem.json.userdata.PlaybackPositionTicks > 0
m.global.queueManager.callFunc("hold", selectedItem)
playbackOptionDialog(selectedItem.json.userdata.PlaybackPositionTicks, selectedItem.json)
else
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", selectedItem)
m.global.queueManager.callFunc("playQueue")
localGlobal = m.global
' we only use this special steering logic from the home screen
if launchSource <> "home"
' Display playback options dialog
if selectedItem.json.userdata.PlaybackPositionTicks > 0
m.global.queueManager.callFunc("hold", selectedItem)
playbackOptionDialog(selectedItem.json.userdata.PlaybackPositionTicks, selectedItem.json)
else
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", selectedItem)
m.global.queueManager.callFunc("playQueue")
end if
else if localGlobal.session.user.settings["ui.general.episodenextupbehavior"] = "episode"
group = CreateMovieDetailsGroup(selectedItem)
else if localGlobal.session.user.settings["ui.general.episodenextupbehavior"] = "season"
group = CreateSeasonDetailsGroupByID(selectedItem.json.SeriesId, selectedItem.json.SeasonId, selectedItem.id)
end if
else if selectedItemType = "Series"
group = CreateSeriesDetailsGroup(selectedItem.json.id)
else if selectedItemType = "Season"
if isValid(selectedItem.json) and isValid(selectedItem.json.SeriesId) and isValid(selectedItem.id)
group = CreateSeasonDetailsGroupByID(selectedItem.json.SeriesId, selectedItem.id)
group = CreateSeasonDetailsGroupByID(selectedItem.json.SeriesId, selectedItem.id, "")
else
stopLoadingSpinner()
message_dialog(tr("Error loading Season"))
@ -863,7 +880,7 @@ sub Main (args as dynamic) as void
else if popupNode.returnData.indexselected = 3
' User chose Go to season
if isValid(selectedItem[0].json) and isValid(selectedItem[0].json.SeriesId) and isValid(selectedItem[0].json.seasonID)
CreateSeasonDetailsGroupByID(selectedItem[0].json.SeriesId, selectedItem[0].json.seasonID)
CreateSeasonDetailsGroupByID(selectedItem[0].json.SeriesId, selectedItem[0].json.seasonID, "")
else
stopLoadingSpinner()
message_dialog(tr("Error loading Season"))

View File

@ -646,7 +646,7 @@ function CreateSeriesDetailsGroup(seriesID as string) as dynamic
' Divert to season details if user setting goStraightToEpisodeListing is enabled and only one season exists.
if seasonData <> invalid and m.global.session.user.settings["ui.tvshows.goStraightToEpisodeListing"] and seasonData.Items.Count() = 1
stopLoadingSpinner()
return CreateSeasonDetailsGroupByID(seriesID, seasonData.Items[0].id)
return CreateSeasonDetailsGroupByID(seriesID, seasonData.Items[0].id, "")
end if
' start building SeriesDetails view
group = CreateObject("roSGNode", "TVShowDetails")
@ -793,7 +793,7 @@ function CreateSeasonDetailsGroup(series as object, season as object) as dynamic
return group
end function
function CreateSeasonDetailsGroupByID(seriesID as string, seasonID as string) as dynamic
function CreateSeasonDetailsGroupByID(seriesID as string, seasonID as string, selectId as string) as dynamic
' validate parameters
if seriesID = "" or seasonID = "" then return invalid
@ -805,9 +805,11 @@ function CreateSeasonDetailsGroupByID(seriesID as string, seasonID as string) as
stopLoadingSpinner()
return invalid
end if
' start building SeasonDetails view
group = CreateObject("roSGNode", "TVEpisodes")
group.optionsAvailable = false
' push scene asap (to prevent extra button presses when retriving series/movie info)
group.seasonData = seasonMetaData.json
group.objects = TVEpisodes(seriesID, seasonID)
@ -821,6 +823,10 @@ function CreateSeasonDetailsGroupByID(seriesID as string, seasonID as string) as
' check for specials/extras for this season
group.extrasObjects = TVSeasonExtras(seasonID)
group.observeField("sceneReady", "onSceneReady")
group.selectItemId = selectId
' finished building SeasonDetails view
return group
end function

View File

@ -70,6 +70,24 @@ function secondsToHuman(totalSeconds as integer, addLeadingMinuteZero as boolean
return humanTime
end function
function secondsToEndTime(totalSeconds) as string
' Get the current time in seconds since midnight UTC (Unix Epoch Time)
currentUTCTime = CreateObject("roDateTime").AsSeconds()
' Calculate the target time in seconds by adding the number of seconds
targetTimeInSeconds = currentUTCTime + totalSeconds
' Create a new roDateTime object for the target time
targetDateTime = CreateObject("roDateTime")
targetDateTime.FromSeconds(targetTimeInSeconds)
targetDateTime.ToLocalTime()
formattedTime = formatTime(targetDateTime)
return formattedTime
end function
' Format time as 12 or 24 hour format based on system clock setting
function formatTime(time) as string
hours = time.getHours()