components_home_LoadItemsTask.bs
import "pkg:/source/api/ApiClient.bs"
import "pkg:/source/api/baserequest.bs"
import "pkg:/source/api/Image.bs"
import "pkg:/source/api/Items.bs"
import "pkg:/source/data/JellyfinDataTransformer.bs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/utils/config.bs"
import "pkg:/source/utils/deviceCapabilities.bs"
import "pkg:/source/utils/misc.bs"
sub init()
m.log = log.Logger("LoadItemsTask")
m.top.functionName = "loadItems"
end sub
sub loadItems()
globalUser = m.global.user
m.transformer = JellyfinDataTransformer()
results = []
' Load Libraries
if m.top.itemsToLoad = "libraries"
data = GetApi().GetViews()
if isValid(data) and isValid(data.Items)
for each item in data.Items
' Skip Books for now as we don't support it (issue #525)
if item.CollectionType <> "books"
results.push(m.transformer.transformBaseItem(item))
end if
end for
end if
' Load Latest Additions to Libraries
else if m.top.itemsToLoad = "latest"
activeUser = globalUser.id
if isValid(activeUser)
params = {}
params["Limit"] = 16
params["ParentId"] = m.top.itemId
params["EnableImageTypes"] = "Primary,Backdrop,Thumb"
params["ImageTypeLimit"] = 1
params["EnableTotalRecordCount"] = false
data = GetApi().GetLatestMedia(params)
if isValid(data)
for each item in data
' Skip Books for now as we don't support it (issue #525)
if item.Type <> "Book"
results.push(m.transformer.transformBaseItem(item))
end if
end for
end if
end if
' Load Next Up
else if m.top.itemsToLoad = "nextUp"
userSettings = globalUser.settings
url = "Shows/NextUp"
params = {}
params["recursive"] = true
params["SortBy"] = "DatePlayed"
params["SortOrder"] = "Descending"
params["ImageTypeLimit"] = 1
params["UserId"] = globalUser.id
params["EnableRewatching"] = userSettings.uiDetailsEnableRewatchingNextUp
params["DisableFirstEpisode"] = false
params["limit"] = 69
params["EnableTotalRecordCount"] = false
params["EnableResumable"] = false
maxDaysInNextUp = userSettings.uiDetailsMaxDaysNextUp
if isValid(maxDaysInNextUp)
if maxDaysInNextUp > 0
dateToday = CreateObject("roDateTime")
dateCutoff = CreateObject("roDateTime")
dateCutoff.FromSeconds(dateToday.AsSeconds() - (maxDaysInNextUp * 86400))
params["NextUpDateCutoff"] = dateCutoff.ToISOString()
end if
end if
resp = APIRequest(url, params)
data = getJson(resp)
if isValid(data) and isValid(data.Items)
for each item in data.Items
results.push(m.transformer.transformBaseItem(item))
end for
end if
' Load Continue Watching
else if m.top.itemsToLoad = "continue"
activeUser = globalUser.id
if isValid(activeUser)
params = {}
params["recursive"] = true
params["SortBy"] = "DatePlayed"
params["SortOrder"] = "Descending"
params["Filters"] = "IsResumable"
params["EnableTotalRecordCount"] = false
data = GetApi().GetResumeItems(params)
if isValid(data) and isValid(data.Items)
for each item in data.Items
' Skip Books for now as we don't support it (issue #558)
if item.Type <> "Book"
results.push(m.transformer.transformBaseItem(item))
end if
end for
end if
end if
else if m.top.itemsToLoad = "favorites"
params = {}
params["Filters"] = "IsFavorite"
params["Limit"] = 20
params["recursive"] = true
params["sortby"] = "random"
params["EnableTotalRecordCount"] = false
data = GetApi().GetItemsByQuery(params)
if isValid(data) and isValid(data.Items)
for each item in data.Items
' Skip Books for now as we don't support it (issue #558)
' also skip songs since there is limited space
if not (item.Type = "Book" or item.Type = "Audio")
results.push(m.transformer.transformBaseItem(item))
end if
end for
end if
else if m.top.itemsToLoad = "onNow"
url = "LiveTv/Programs/Recommended"
params = {}
params["userId"] = globalUser.id
params["isAiring"] = true
params["limit"] = 16 ' 16 to be consistent with "Latest In"
params["imageTypeLimit"] = 1
params["enableImageTypes"] = "Primary,Thumb,Backdrop"
params["enableTotalRecordCount"] = false
params["fields"] = "ChannelInfo,PrimaryImageAspectRatio"
resp = APIRequest(url, params)
data = getJson(resp)
if isValid(data) and isValid(data.Items)
for each item in data.Items
results.push(m.transformer.transformBaseItem(item))
end for
end if
' Extract array of persons from Views and download full metadata for each
else if m.top.itemsToLoad = "people"
if not isValid(m.top.peopleList)
m.top.content = results
return
end if
for each person in m.top.peopleList
node = m.transformer.transformPerson(person)
if isValid(node) then results.push(node)
end for
else if m.top.itemsToLoad = "specialfeatures"
data = GetApi().GetSpecialFeatures(m.top.itemId)
if isValid(data) and data.count() > 0
for each specfeat in data
node = m.transformer.transformBaseItem(specfeat)
if isValid(node) then results.push(node)
end for
end if
else if m.top.itemsToLoad = "additionalparts"
additionalParts = GetApi().GetAdditionalParts(m.top.itemId)
if isValid(additionalParts)
for each part in additionalParts.items
node = m.transformer.transformBaseItem(part)
if isValid(node) then results.push(node)
end for
end if
else if m.top.itemsToLoad = "likethis"
params = { "userId": globalUser.id, "limit": 16 }
url = Substitute("Items/{0}/Similar", m.top.itemId)
resp = APIRequest(url, params)
data = getJson(resp)
if isValid(data) and isValid(data.Items)
for each item in data.items
node = m.transformer.transformBaseItem(item)
if isValid(node) then results.push(node)
end for
end if
else if m.top.itemsToLoad = "personMovies"
getPersonVideos("Movie", results)
else if m.top.itemsToLoad = "personTVShows"
getPersonVideos("Episode", results)
else if m.top.itemsToLoad = "personSeries"
getPersonVideos("Series", results)
else if m.top.itemsToLoad = "seasons"
data = GetApi().GetSeasons(m.top.itemId, { Fields: "PrimaryImageAspectRatio,Overview", EnableImages: true })
if isValid(data) and isValid(data.Items)
for each item in data.Items
node = m.transformer.transformBaseItem(item)
if isValid(node) then results.push(node)
end for
end if
else if m.top.itemsToLoad = "seasonEpisodes"
' itemId = seriesId, metadata.seasonId = season to load
if not isValid(m.top.metadata) or not isValidAndNotEmpty(m.top.metadata.seasonId)
m.log.warn("seasonEpisodes requested but metadata.seasonId is missing or empty; skipping GetEpisodes call")
m.top.content = results
return
end if
data = GetApi().GetEpisodes(m.top.itemId, {
UserId: globalUser.id,
SeasonId: m.top.metadata.seasonId,
Fields: "PrimaryImageAspectRatio,Overview",
EnableImages: true
})
if isValid(data) and isValid(data.Items)
for each item in data.Items
node = m.transformer.transformBaseItem(item)
if isValid(node) then results.push(node)
end for
end if
else if m.top.itemsToLoad = "seriesFirstEpisode"
data = GetApi().GetEpisodes(m.top.itemId, { Limit: 1, SortBy: "IndexNumber,SortName", SortOrder: "Ascending" })
if isValid(data) and isValid(data.Items) and data.Items.count() > 0
node = m.transformer.transformBaseItem(data.Items[0])
if isValid(node) then results.push(node)
end if
else if m.top.itemsToLoad = "seriesResume"
' For the series Resume button, prefer an in-progress (resumable) episode and fall back to the next unstarted episode.
' Uses the same endpoint as the Continue Watching home row so the Resume button is consistent with it.
resumeData = GetApi().GetResumeItems({
ParentId: m.top.itemId,
Filters: "IsResumable",
SortBy: "DatePlayed",
SortOrder: "Descending",
Limit: 1,
recursive: true,
Fields: "PrimaryImageAspectRatio",
EnableTotalRecordCount: false
})
if isValid(resumeData) and isValid(resumeData.Items) and resumeData.Items.Count() > 0
node = m.transformer.transformBaseItem(resumeData.Items[0])
if isValid(node) then results.push(node)
else
' No resumable episode — fall back to next unstarted episode.
' DisableFirstEpisode: true so a Resume button never appears for a completely unwatched series.
nextUpData = GetApi().GetNextUp({ SeriesId: m.top.itemId, Limit: 1, DisableFirstEpisode: true, Fields: "PrimaryImageAspectRatio" })
if isValid(nextUpData) and isValid(nextUpData.Items) and nextUpData.Items.Count() > 0
node = m.transformer.transformBaseItem(nextUpData.Items[0])
if isValid(node) then results.push(node)
end if
end if
else if m.top.itemsToLoad = "metaData"
results.push(ItemMetaData(m.top.itemId))
else if m.top.itemsToLoad = "metaDataDetails"
results.push(ItemDetailsMetaData(m.top.itemId))
else if m.top.itemsToLoad = "audioStream"
results.push(AudioStream(m.top.itemId))
else if m.top.itemsToLoad = "backdropImage"
results.push(BackdropImage(m.top.itemId))
else if m.top.itemsToLoad = "boxsetitems"
data = GetApi().GetItemsByQuery({
"ParentId": m.top.itemId,
"SortBy": "SortName",
"SortOrder": "Ascending",
"Fields": "PrimaryImageAspectRatio,Overview",
"EnableTotalRecordCount": false
})
if isValid(data) and isValid(data.Items)
for each item in data.Items
node = m.transformer.transformBaseItem(item)
if isValid(node) then results.push(node)
end for
end if
end if
m.top.content = results
end sub
sub getPersonVideos(videoType as string, dest as object)
params = { personIds: m.top.itemId, recursive: true, includeItemTypes: videoType, Limit: 50, SortBy: "Random" }
data = GetApi().GetItemsByQuery(params)
if isValid(data) and data.count() > 0
for each item in data.items
node = m.transformer.transformBaseItem(item)
if isValid(node) then dest.push(node)
end for
end if
end sub