diff --git a/components/extras/ExtrasRowList.bs b/components/extras/ExtrasRowList.bs
index 70b886c3c..7a0cf1268 100644
--- a/components/extras/ExtrasRowList.bs
+++ b/components/extras/ExtrasRowList.bs
@@ -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
diff --git a/components/home/HomeRows.bs b/components/home/HomeRows.bs
index d691cb154..b41c7ed1c 100644
--- a/components/home/HomeRows.bs
+++ b/components/home/HomeRows.bs
@@ -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
diff --git a/components/movies/MovieDetails.bs b/components/movies/MovieDetails.bs
index 5812f4c47..56ab0ba03 100644
--- a/components/movies/MovieDetails.bs
+++ b/components/movies/MovieDetails.bs
@@ -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
diff --git a/components/tvshows/TVEpisodeRow.bs b/components/tvshows/TVEpisodeRow.bs
index 5782723f9..62cfb143e 100644
--- a/components/tvshows/TVEpisodeRow.bs
+++ b/components/tvshows/TVEpisodeRow.bs
@@ -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]
diff --git a/components/tvshows/TVEpisodeRow.xml b/components/tvshows/TVEpisodeRow.xml
index 0e14bc3a8..9885529c9 100644
--- a/components/tvshows/TVEpisodeRow.xml
+++ b/components/tvshows/TVEpisodeRow.xml
@@ -4,5 +4,8 @@
+
+
+
\ No newline at end of file
diff --git a/components/tvshows/TVEpisodeRowWithOptions.bs b/components/tvshows/TVEpisodeRowWithOptions.bs
index d982d82c7..6c68fde79 100644
--- a/components/tvshows/TVEpisodeRowWithOptions.bs
+++ b/components/tvshows/TVEpisodeRowWithOptions.bs
@@ -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
diff --git a/components/tvshows/TVEpisodeRowWithOptions.xml b/components/tvshows/TVEpisodeRowWithOptions.xml
index 008720972..6b6c904d4 100644
--- a/components/tvshows/TVEpisodeRowWithOptions.xml
+++ b/components/tvshows/TVEpisodeRowWithOptions.xml
@@ -8,5 +8,8 @@
+
+
+
\ No newline at end of file
diff --git a/components/tvshows/TVEpisodes.bs b/components/tvshows/TVEpisodes.bs
index 95b98f02e..916a1fca6 100644
--- a/components/tvshows/TVEpisodes.bs
+++ b/components/tvshows/TVEpisodes.bs
@@ -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
diff --git a/components/tvshows/TVEpisodes.xml b/components/tvshows/TVEpisodes.xml
index 1c0cfefc8..2213b49dc 100644
--- a/components/tvshows/TVEpisodes.xml
+++ b/components/tvshows/TVEpisodes.xml
@@ -19,6 +19,9 @@
+
+
+
\ No newline at end of file
diff --git a/components/video/OSD.bs b/components/video/OSD.bs
index 95928db45..6d9d0d480 100644
--- a/components/video/OSD.bs
+++ b/components/video/OSD.bs
@@ -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
diff --git a/components/video/OSD.xml b/components/video/OSD.xml
index 4093701d0..e80eb64e6 100644
--- a/components/video/OSD.xml
+++ b/components/video/OSD.xml
@@ -34,7 +34,7 @@
-
+
diff --git a/locale/en_US/translations.ts b/locale/en_US/translations.ts
index 50aad734f..14445ef91 100644
--- a/locale/en_US/translations.ts
+++ b/locale/en_US/translations.ts
@@ -1321,10 +1321,55 @@
Use Show ImageUser Setting - Setting option title
+
+
+ Episode Next Up Behavior
+ User Setting - Setting option title
+
+
+
+ This controls what clicking OK on a Next Up episode does.
+ User Setting - Setting description
+
+
+
+ View Episode Details
+ User Setting - Setting option title
+
+
+
+ View Season Details
+ User Setting - Setting option title
+
+
+
+ OSD Remaining Time
+ User Setting - Setting option title
+
+
+
+ How is remaining time represented. Remaining time shows hours, minutes, and seconds remaining. Time of day shows what time the video will complete.
+ User Setting - Setting description
+
+
+
+ Remaining Time
+ User Setting - Setting option title
+
+
+
+ Time of Day
+ User Setting - Setting option title
+
+
+
+ Both
+ User Setting - Setting option title
+ SpecialSpecial episode of a TV Show
-
+
\ No newline at end of file
diff --git a/settings/settings.json b/settings/settings.json
index e3b01eee9..836a59bb2 100644
--- a/settings/settings.json
+++ b/settings/settings.json
@@ -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.",
diff --git a/source/Main.bs b/source/Main.bs
index 3b541dc2b..c70018199 100644
--- a/source/Main.bs
+++ b/source/Main.bs
@@ -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"))
diff --git a/source/ShowScenes.bs b/source/ShowScenes.bs
index 4aa14f8e6..1829e928b 100644
--- a/source/ShowScenes.bs
+++ b/source/ShowScenes.bs
@@ -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
diff --git a/source/utils/misc.bs b/source/utils/misc.bs
index 24c44f569..c8eece447 100644
--- a/source/utils/misc.bs
+++ b/source/utils/misc.bs
@@ -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()