import "pkg:/source/api/ApiClient.bs"
import "pkg:/source/api/apiPool.bs"
import "pkg:/source/api/apiRead.bs"
import "pkg:/source/constants/timeouts.bs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/utils/episodeQueue.bs"
import "pkg:/source/utils/misc.bs"
import "pkg:/source/utils/quickplayLibrary.bs"
import "pkg:/source/utils/quickplayOutcome.bs"
' QuickPlayTask: Orchestrator task for all quickplay, Play All, Instant Mix,
' Trailer, and Photo Album flows that require API calls.
'
' Input AA fields:
' action (string) - dispatch key (for example "musicalbum", "playAllSeries", "instantMix")
' id (string) - primary item ID
' seriesId (string) - optional, for season/episode context
' collectionType (string) - optional, for collection/userView dispatch
' folderType (string) - optional, for folder sub-dispatch
' movieCount (integer) - optional, for folder series vs movie heuristic
' seriesCount (integer) - optional, for folder series vs movie heuristic
' channelId (string) - optional, for program-to-channel redirect
' seasonId (string) - optional, for season Play All
'
' Output AA fields:
' action (string) - "queue", "photo", or "trailer"
' items (array) - array of raw API item AAs (or node-like AAs)
' shuffle (boolean) - whether to shuffle after queuing
' firstItemStartingPoint (LongInteger) - resume position for first item (0 = start)
' startsFresh (boolean) - the first item starts from the beginning: nobody chose it to
' resume (QueueManager.startCurrentFresh). False only when a
' builder picked the first item to resume or continue it.
' status (string) - quickplayOutcome.STATUS_*: "ok", or why nothing arrived ("failed"
' when a read did not answer, "empty" when every read did)
' kind (string) - quickplayOutcome.KIND_*, which names the launch in the viewer's message
'
' Every queue gets one copy per episode (episodeQueue.collapseQueue) except playlists and
' collections, whose entries the viewer put there.
sub init()
m.top.functionName = "executeQuickPlay"
m.log = new log.Logger("QuickPlayTask")
end sub
sub executeQuickPlay()
input = m.top.input
if not isValid(input) or not isValidAndNotEmpty(input.action) then return
action = LCase(input.action)
' Every read of this build, which remembers one that did not answer, so an empty result is
' not told as "nothing here".
m.reader = new apiRead.Reader(m.log, "quick play")
' No branch builds this action, which no retry changes.
unsupported = false
items = []
shouldShuffle = false
outputAction = "queue"
' How the first item starts: resumes = a builder chose it to resume or continue it, at
' startingPoint (its own copy's position, or 0 to let the loader decide by version).
launch = { startingPoint: 0&, resumes: false }
keepsCopies = false
' Dispatch convention: the action key is the lowercased Jellyfin item type
' (for example "musicalbum", "boxset"), optionally with a literal operation prefix
' ("playall..."). Producers build it from the raw type (QueueManager.launchItem:
' LCase(type); ItemDetails: "playAll" + type), so keys must match Jellyfin's
' BaseItemKind exactly: there is no bare "album"/"artist" type, only "MusicAlbum"/"MusicArtist".
' The else branch below logs any unmatched action so drift can't fail silently.
if action = "musicalbum"
doAlbum(input, items)
else if action = "musicartist"
doArtist(input, items)
shouldShuffle = true
else if action = "boxset"
doBoxset(input, items)
keepsCopies = true
else if action = "series"
launch = doSeries(input, items)
else if action = "season"
launch = doSeason(input, items)
else if action = "person"
doPerson(input, items)
shouldShuffle = true
else if action = "playlist"
doPlaylist(input, items)
shouldShuffle = true
keepsCopies = true
else if action = "folder"
result = doFolder(input)
if isValid(result)
items = result.items
shouldShuffle = result.shuffle
outputAction = result.action
keepsCopies = (result.keepsCopies = true)
if isValid(result.launch) then launch = result.launch
end if
else if action = "collectionfolder"
result = doCollectionFolder(input)
if isValid(result)
items = result.items
shouldShuffle = result.shuffle
outputAction = result.action
keepsCopies = (result.keepsCopies = true)
if isValid(result.launch) then launch = result.launch
end if
else if action = "userview"
result = doUserView(input)
if isValid(result)
items = result.items
shouldShuffle = result.shuffle
outputAction = result.action
keepsCopies = (result.keepsCopies = true)
if isValid(result.launch) then launch = result.launch
end if
else if action = "photoalbum"
doPhotoAlbum(input, items)
outputAction = "photo"
else if action = "playallseries"
doPlayAllSeries(input, items)
else if action = "playallseason"
doPlayAllSeason(input, items)
else if action = "playallboxset"
doPlayAllBoxset(input, items)
keepsCopies = true
else if action = "playallmusicalbum"
doPlayAllAlbum(input, items)
else if action = "playallmusicartist"
doPlayAllArtist(input, items)
else if action = "playallplaylist"
doPlayAllPlaylist(input, items)
keepsCopies = true
else if action = "instantmix"
doInstantMix(input, items)
else if action = "loadtrailers"
doLoadTrailers(input, items)
outputAction = "trailer"
else if action = "loadphotoalbum"
doPhotoAlbum(input, items)
outputAction = "photo"
else
' No dispatch branch matched: a caller built an action string no handler key matches
' (for example "playAll" + itemType, the failure mode behind #629), or a remote Play
' named a type nothing here builds. Logged loudly so a type mismatch is debuggable, and
' told to the viewer as unsupported, since trying again cannot help.
m.log.error("unhandled action — no dispatch branch matched", input.action)
unsupported = true
end if
' One copy per episode. A first item resumed at its own position stays exactly that copy.
if outputAction = "queue" and not keepsCopies
items = episodeQueue.collapseQueue(items, launch.startingPoint > 0, "qp_copies")
end if
m.top.output = {
action: outputAction,
items: items,
shuffle: shouldShuffle,
firstItemStartingPoint: launch.startingPoint,
startsFresh: not launch.resumes,
status: quickplayOutcome.statusOf(items.count(), m.reader.anyFailed, unsupported),
kind: quickplayOutcome.kindOf(outputAction)
}
end sub
' --- Requests ---
' m.reader.json for a read that searches a whole library, or every library for one person or
' artist, which a large library on a slow server answers long after an ordinary request's limit
' (timeouts.QUICKPLAY_LIBRARY_MS). Back stops this Task, which frees the request's pool slot at
' once (apiPool.bs, "Stopping a request nobody is waiting for").
function fetchLibraryJson(req as dynamic, requestId as string) as dynamic
if isValid(req) then req.timeoutMs = timeouts.QUICKPLAY_LIBRARY_MS
return m.reader.json(req, requestId)
end function
' --- QuickPlay actions (from quickplay.bs) ---
sub doAlbum(input as object, items as object)
data = m.reader.json(GetApi().BuildGetItemsByQueryRequest({
"parentId": input.id,
"imageTypeLimit": 1,
"sortBy": "SortName",
"limit": 500,
"enableUserData": false,
"EnableTotalRecordCount": false
}), "qp_album")
if isValid(data) and isValidAndNotEmpty(data.items)
items.append(data.items)
end if
end sub
sub doArtist(input as object, items as object)
data = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"artistIds": input.id,
"includeItemTypes": "Audio",
"sortBy": "Album",
"limit": 500,
"imageTypeLimit": 1,
"Recursive": true,
"enableUserData": false,
"EnableTotalRecordCount": false
}), "qp_artist")
if isValid(data) and isValidAndNotEmpty(data.items)
items.append(data.items)
end if
end sub
sub doBoxset(input as object, items as object)
data = m.reader.json(GetApi().BuildGetItemsByQueryRequest({
"parentid": input.id,
"limit": 500,
"EnableTotalRecordCount": false
}), "qp_boxset")
if isValid(data) and isValidAndNotEmpty(data.Items)
items.append(data.Items)
end if
end sub
' @return {object} { startingPoint, resumes } (see executeQuickPlay's launch)
function doSeries(input as object, items as object) as object
launch = { startingPoint: 0&, resumes: true }
localUser = m.global.user
' Check for a resumable (in-progress) episode first
resumeData = m.reader.json(GetApi().BuildGetResumeItemsRequest({
"parentId": input.id,
"Limit": 1,
"ImageTypeLimit": 1,
"EnableTotalRecordCount": false
}), "qp_seriesResume")
' A read that did not answer says nothing about where the viewer is, so the build stops
' rather than start some other episode (here and at Next Up below).
if not isValid(resumeData) then return launch
if isValidAndNotEmpty(resumeData.Items)
ep = resumeData.Items[0]
items.push(ep)
if isValid(ep.UserData) and isValid(ep.UserData.PlaybackPositionTicks)
launch.startingPoint = ep.UserData.PlaybackPositionTicks
end if
else
' Fall back to next unstarted episode
data = m.reader.json(GetApi().BuildGetNextUpRequest({
"seriesId": input.id,
"Limit": 1,
"ImageTypeLimit": 1,
"EnableRewatching": localUser.settings.uiDetailsEnableRewatchingNextUp,
"EnableTotalRecordCount": false
}), "qp_seriesNextUp")
if not isValid(data) then return launch
if isValidAndNotEmpty(data.Items)
items.push(data.Items[0])
else
' All episodes fully watched and rewatching disabled: shuffle the whole series
allEps = m.reader.json(GetApi().BuildGetEpisodesRequest(input.id, {
"SortBy": "Random",
"limit": 500,
"EnableTotalRecordCount": false
}), "qp_seriesShuffle")
if isValid(allEps) and isValidAndNotEmpty(allEps.Items)
items.append(allEps.Items)
end if
launch.resumes = false
end if
end if
return launch
end function
' @return {object} { startingPoint, resumes } (see executeQuickPlay's launch)
function doSeason(input as object, items as object) as object
launch = { startingPoint: 0&, resumes: true }
seriesId = isValidAndNotEmpty(input.seriesId) ? input.seriesId : ""
if seriesId = "" then return launch
episodesData = m.reader.json(GetApi().BuildGetEpisodesRequest(seriesId, {
"seasonId": input.id,
"limit": 500,
"EnableTotalRecordCount": false
}), "qp_seasonEps")
if isValid(episodesData) and isValidAndNotEmpty(episodesData.Items)
' Find the first unwatched EPISODE, not the first unwatched item: before 12.0 each copy
' of an episode is its own item with its own watched state, so an unwatched copy of a
' watched episode must not start the season over (episodeQueue). Tracks a position in
' the list rather than one derived from IndexNumber: the two only agree when a season
' holds episodes numbered 1..N with none missing; Jellyfin guarantees neither.
groups = episodeQueue.groupByEpisode(episodesData.Items)
firstUnwatched = -1
for g = 0 to groups.count() - 1
if not episodeQueue.isGroupPlayed(groups[g])
firstUnwatched = g
exit for
end if
end for
if firstUnwatched >= 0
' Resume the copy the viewer is partway through, at its own position. Otherwise the
' episode's copies stay together and executeQuickPlay keeps one.
firstQueued = firstUnwatched
resumeCopy = episodeQueue.inProgressCopy(groups[firstUnwatched])
if isValid(resumeCopy)
items.push(resumeCopy)
launch.startingPoint = resumeCopy.UserData.PlaybackPositionTicks
firstQueued = firstUnwatched + 1
end if
for g = firstQueued to groups.count() - 1
items.append(groups[g])
end for
else
' try to find a "continue watching" episode
continueData = m.reader.json(GetApi().BuildGetResumeItemsRequest({
"parentId": input.id,
"EnableTotalRecordCount": false
}), "qp_seasonContinue")
' Not answered: stop rather than start the season over (see doSeries).
if not isValid(continueData) then return launch
if isValidAndNotEmpty(continueData.Items)
for each item in continueData.Items
items.push(item)
end for
' Set starting point for the first resumable episode
firstEp = continueData.Items[0]
if isValid(firstEp.UserData) and isValid(firstEp.UserData.PlaybackPositionTicks)
launch.startingPoint = firstEp.UserData.PlaybackPositionTicks
end if
else
' play the whole season in order
items.append(episodesData.Items)
launch.resumes = false
end if
end if
end if
return launch
end function
sub doPerson(input as object, items as object)
' get movies by the person
personMovies = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"personIds": input.id,
"includeItemTypes": "Movie",
"excludeItemTypes": "Season,Series",
"recursive": true,
"limit": 500
}), "qp_personMovies")
if isValid(personMovies) and isValidAndNotEmpty(personMovies.Items)
items.append(personMovies.Items)
end if
' get watched episodes by the person
personEpisodes = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"personIds": input.id,
"includeItemTypes": "Episode,Recording",
"isPlayed": true,
"excludeItemTypes": "Season,Series",
"recursive": true,
"limit": 500
}), "qp_personEpisodes")
if isValid(personEpisodes) and isValidAndNotEmpty(personEpisodes.Items)
items.append(personEpisodes.Items)
end if
end sub
sub doPlaylist(input as object, items as object)
data = m.reader.json(GetApi().BuildGetPlaylistItemsRequest(input.id, {
"limit": 500
}), "qp_playlist")
if isValid(data) and isValidAndNotEmpty(data.Items)
items.append(data.Items)
end if
end sub
sub doPhotoAlbum(input as object, items as object)
data = m.reader.json(GetApi().BuildGetItemsByQueryRequest({
"parentId": input.id,
"includeItemTypes": "Photo",
"sortBy": "Random",
"Recursive": true
}), "qp_photoAlbum")
if isValid(data) and isValidAndNotEmpty(data.items)
items.append(data.items)
end if
end sub
' --- Folder / CollectionFolder / UserView (complex multi-call) ---
function doFolder(input as object) as dynamic
items = []
shouldShuffle = true
outputAction = "queue"
launch = { startingPoint: 0&, resumes: false }
paramArray = {
"includeItemTypes": "Episode,Recording,Movie,Video",
"videoTypes": "VideoFile",
"sortBy": "Random",
"limit": 500,
"imageTypeLimit": 1,
"Recursive": true,
"enableUserData": false,
"EnableTotalRecordCount": false
}
folderType = LCase(isValidAndNotEmpty(input.folderType) ? input.folderType : "")
seriesCount = isValid(input.seriesCount) ? input.seriesCount : 0
' A photo album opens as a slideshow. Set before the read, so an empty or failed one is told
' as a slideshow too (quickplayOutcome.kindOf).
if folderType = "photoalbum" and seriesCount = 0 then outputAction = "photo"
if folderType = "studio"
paramArray["studioIds"] = input.id
else if folderType = "genre"
paramArray["genreIds"] = input.id
if isValid(input.movieCount) and input.movieCount > 0
paramArray["includeItemTypes"] = "Movie"
end if
else if folderType = "musicgenre"
paramArray["genreIds"] = input.id
paramArray.delete("videoTypes")
paramArray["includeItemTypes"] = "Audio"
else if folderType = "photoalbum"
paramArray["parentId"] = input.id
paramArray["includeItemTypes"] = "Photo"
paramArray.delete("videoTypes")
paramArray.delete("Recursive")
else
paramArray["parentId"] = input.id
end if
if seriesCount > 0
paramArray["includeItemTypes"] = "Series"
paramArray.Delete("videoTypes")
end if
folderData = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest(paramArray), "qp_folder")
if isValid(folderData) and isValidAndNotEmpty(folderData.items)
if seriesCount > 0
if seriesCount = 1
' Single series: delegate to series quickplay, and carry its whole launch: a folder
' holding one series plays it exactly as the series itself would, resume included.
launch = doSeries({ id: folderData.items[0].Id }, items)
shouldShuffle = false
else
doMultipleSeries(folderData.items, items)
end if
else
items.append(folderData.items)
end if
end if
return { action: outputAction, items: items, shuffle: shouldShuffle, launch: launch }
end function
function doCollectionFolder(input as object) as dynamic
items = []
shouldShuffle = true
outputAction = "queue"
keepsCopies = false
collectionType = LCase(isValidAndNotEmpty(input.collectionType) ? input.collectionType : "")
if collectionType = "movies"
doVideoContainer(input, items)
shouldShuffle = false
else if collectionType = "music"
data = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"parentId": input.id,
"includeItemTypes": "Audio",
"sortBy": "Album",
"Recursive": true,
"limit": 500,
"imageTypeLimit": 1,
"enableUserData": false,
"EnableTotalRecordCount": false
}), "qp_collectionMusic")
if isValid(data) and isValidAndNotEmpty(data.items)
items.append(data.Items)
end if
else if collectionType = "boxsets"
' pick a random boxset and play its contents
boxsetData = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"parentId": input.id,
"limit": 500,
"imageTypeLimit": 0,
"enableUserData": false,
"EnableTotalRecordCount": false,
"enableImages": false
}), "qp_collectionBoxsets")
if isValid(boxsetData) and isValidAndNotEmpty(boxsetData.items)
arrayIndex = Rnd(boxsetData.items.count()) - 1
myBoxset = boxsetData.items[arrayIndex]
innerData = m.reader.json(GetApi().BuildGetItemsByQueryRequest({
"parentId": myBoxset.id,
"EnableTotalRecordCount": false
}), "qp_collectionBoxsetInner")
if isValid(innerData) and isValidAndNotEmpty(innerData.items)
items.append(innerData.Items)
end if
end if
shouldShuffle = false
keepsCopies = true
else if collectionType = "tvshows"
doVideoContainer(input, items)
shouldShuffle = false
else if collectionType = "" or collectionType = "collectionfolder"
' Mixed content: a library created with no type has no CollectionType.
doMixedLibrary(input, items)
else if collectionType = "musicvideos"
data = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"parentId": input.id,
"includeItemTypes": "MusicVideo",
"sortBy": "Random",
"Recursive": true,
"limit": 500,
"imageTypeLimit": 1,
"enableUserData": false,
"EnableTotalRecordCount": false
}), "qp_collectionMusicVideos")
if isValid(data) and isValidAndNotEmpty(data.items)
items.append(data.Items)
end if
shouldShuffle = false
else if collectionType = "homevideos"
' A slideshow, set before the read as in doFolder.
outputAction = "photo"
data = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"parentId": input.id,
"includeItemTypes": "Photo",
"sortBy": "Random",
"Recursive": true,
"limit": 500
}), "qp_collectionPhotos")
if isValid(data) and isValidAndNotEmpty(data.items)
items.append(data.items)
end if
end if
return { action: outputAction, items: items, shuffle: shouldShuffle, keepsCopies: keepsCopies }
end function
function doUserView(input as object) as dynamic
items = []
shouldShuffle = true
outputAction = "queue"
keepsCopies = false
collectionType = LCase(isValidAndNotEmpty(input.collectionType) ? input.collectionType : "")
if collectionType = "playlists"
' pick a random playlist and play its contents
playlistData = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"parentId": input.id,
"sortBy": "Random",
"limit": 1,
"imageTypeLimit": 0,
"enableUserData": false,
"EnableTotalRecordCount": false,
"enableImages": false
}), "qp_userViewPlaylists")
if isValid(playlistData) and isValidAndNotEmpty(playlistData.items)
myPlaylist = playlistData.items[0]
playlistItems = m.reader.json(GetApi().BuildGetPlaylistItemsRequest(myPlaylist.id, {
"EnableTotalRecordCount": false,
"limit": 500
}), "qp_userViewPlaylistItems")
if isValid(playlistItems) and isValidAndNotEmpty(playlistItems.items)
items.append(playlistItems.items)
end if
end if
keepsCopies = true
else if collectionType = "livetv"
' pick a random channel
channelData = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"includeItemTypes": "TVChannel",
"sortBy": "Random",
"limit": 1,
"Recursive": true,
"imageTypeLimit": 0,
"enableUserData": false,
"EnableTotalRecordCount": false,
"enableImages": false
}), "qp_userViewChannels")
if isValid(channelData) and isValidAndNotEmpty(channelData.items)
myChannel = channelData.items[0]
' Return channel as a queue item with TvChannel type
myChannel.Type = "TvChannel"
items.push(myChannel)
end if
shouldShuffle = false
else if collectionType = "movies" or collectionType = "tvshows"
doVideoContainer(input, items)
shouldShuffle = false
end if
return { action: outputAction, items: items, shuffle: shouldShuffle, keepsCopies: keepsCopies }
end function
' Helper: video container logic (movies or tvshows collection)
sub doVideoContainer(input as object, items as object)
collectionType = LCase(isValidAndNotEmpty(input.collectionType) ? input.collectionType : "")
if collectionType = "movies"
data = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"parentId": input.id,
"sortBy": "Random",
"recursive": true,
"includeItemTypes": "Movie,Video",
"limit": 500
}), "qp_videoContainerMovies")
if isValid(data) and isValidAndNotEmpty(data.items)
items.append(quickplayLibrary.withoutInProgress(data.items))
end if
else if collectionType = "tvshows"
tvshowsData = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest(quickplayLibrary.watchedEpisodesQuery(input.id)), "qp_videoContainerTvShows")
if isValid(tvshowsData) and isValidAndNotEmpty(tvshowsData.items)
items.append(tvshowsData.items)
end if
end if
end sub
' Helper for a mixed-content library: its movies and videos that are not in progress, and its
' watched episodes, the Movies and TV libraries' rules (quickplayLibrary), shuffled together by
' executeQuickPlay.
sub doMixedLibrary(input as object, items as object)
videoData = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"parentId": input.id,
"includeItemTypes": "Movie,Video",
"sortBy": "Random",
"recursive": true,
"limit": 500,
"imageTypeLimit": 1,
"EnableTotalRecordCount": false
}), "qp_mixedVideos")
if isValid(videoData) and isValidAndNotEmpty(videoData.items)
items.append(quickplayLibrary.withoutInProgress(videoData.items))
end if
episodeData = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest(quickplayLibrary.watchedEpisodesQuery(input.id)), "qp_mixedEpisodes")
if isValid(episodeData) and isValidAndNotEmpty(episodeData.items)
items.append(episodeData.items)
end if
end sub
' Helper: shuffle play watched episodes from multiple series
sub doMultipleSeries(tvshows as object, items as object)
numTotal = 0
numLimit = 500
for each tvshow in tvshows
showData = m.reader.json(GetApi().BuildGetEpisodesRequest(tvshow.id, {
"SortBy": "Random",
"imageTypeLimit": 0,
"EnableTotalRecordCount": false,
"enableImages": false
}), "qp_multiSeries_" + tvshow.id)
if isValid(showData) and isValidAndNotEmpty(showData.items)
for each episode in showData.items
if isValid(episode.userdata) and isValid(episode.userdata.Played)
if episode.userdata.Played
items.push(episode)
end if
end if
end for
numTotal = numTotal + showData.items.count()
if numTotal >= numLimit then exit for
end if
end for
end sub
' --- Play All actions (from Main.bs button handlers) ---
sub doPlayAllSeries(input as object, items as object)
allEpData = m.reader.json(GetApi().BuildGetEpisodesRequest(input.id, {
SortBy: "IndexNumber,SortName",
SortOrder: "Ascending"
}), "qp_playAllSeries")
if isValid(allEpData) and isValid(allEpData.Items)
' Exclude Season 0 (Specials) from Play All
for each ep in allEpData.Items
if isValid(ep.ParentIndexNumber) and ep.ParentIndexNumber > 0
items.push(ep)
end if
end for
end if
end sub
sub doPlayAllSeason(input as object, items as object)
allEpData = m.reader.json(GetApi().BuildGetEpisodesRequest(input.seriesId, {
SeasonId: input.id,
SortBy: "IndexNumber,SortName",
SortOrder: "Ascending"
}), "qp_playAllSeason")
if isValid(allEpData) and isValid(allEpData.Items)
items.append(allEpData.Items)
end if
end sub
sub doPlayAllBoxset(input as object, items as object)
data = m.reader.json(GetApi().BuildGetItemsByQueryRequest({
"ParentId": input.id,
"SortBy": "PremiereDate,ProductionYear,SortName",
"SortOrder": "Ascending",
"EnableTotalRecordCount": false
}), "qp_playAllBoxset")
if isValid(data) and isValid(data.Items)
items.append(data.Items)
end if
end sub
sub doPlayAllAlbum(input as object, items as object)
data = m.reader.json(GetApi().BuildGetItemsByQueryRequest({
"parentId": input.id,
"includeitemtypes": "Audio",
"SortBy": "ParentIndexNumber,IndexNumber,SortName",
"SortOrder": "Ascending",
"EnableTotalRecordCount": false
}), "qp_playAllAlbum")
if isValid(data) and isValid(data.Items)
items.append(data.Items)
end if
end sub
sub doPlayAllArtist(input as object, items as object)
data = fetchLibraryJson(GetApi().BuildGetItemsByQueryRequest({
"ArtistIds": input.id,
"Recursive": "true",
"MediaTypes": "Audio",
"Filters": "IsNotFolder",
"SortBy": "SortName",
"Limit": 300,
"Fields": "Chapters",
"ExcludeLocationTypes": "Virtual",
"EnableTotalRecordCount": false,
"CollapseBoxSetItems": false
}), "qp_playAllArtist")
if isValid(data) and isValid(data.Items)
items.append(data.Items)
end if
end sub
sub doPlayAllPlaylist(input as object, items as object)
data = m.reader.json(GetApi().BuildGetPlaylistItemsRequest(input.id, {
"SortBy": "ListItemOrder",
"SortOrder": "Ascending",
"EnableTotalRecordCount": false,
"limit": 500
}), "qp_playAllPlaylist")
if isValid(data) and isValid(data.Items)
items.append(data.Items)
end if
end sub
sub doInstantMix(input as object, items as object)
data = m.reader.json(GetApi().BuildGetInstantMixRequest(input.id, { "Limit": 201 }), "qp_instantMix")
if isValid(data) and isValid(data.Items)
items.append(data.Items)
end if
end sub
sub doLoadTrailers(input as object, items as object)
data = m.reader.json(GetApi().BuildGetLocalTrailersRequest(input.id), "qp_trailers")
if isValid(data)
items.append(data)
end if
end sub