enable Quick Play for boxsets, series, seasons, albums, and music artists

This commit is contained in:
Charles Ewert 2023-09-16 17:18:03 -04:00
parent d92dc42d8c
commit 1acec715b1
18 changed files with 271 additions and 26 deletions

View File

@ -780,11 +780,11 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.loadItemsTask.control = "stop"
return true
end if
else if key = "play" or key = "OK"
else if key = "play"
markupGrid = m.top.findNode("itemGrid")
itemToPlay = markupGrid.content.getChild(markupGrid.itemFocused)
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
if itemToPlay <> invalid
m.top.quickPlayNode = itemToPlay
return true
else if itemToPlay <> invalid and itemToPlay.type = "Photo"

View File

@ -869,11 +869,10 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.loadItemsTask.control = "stop"
return true
end if
else if key = "play" or key = "OK"
else if key = "play"
itemToPlay = getItemFocused()
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
if itemToPlay <> invalid
m.top.quickPlayNode = itemToPlay
return true
end if

View File

@ -750,7 +750,6 @@ function onKeyEvent(key as string, press as boolean) as boolean
alpha.setFocus(true)
return true
end if
else if key = "right" and m.Alpha.isinFocusChain()
m.top.alphaActive = false
m.Alpha.setFocus(false)
@ -760,14 +759,12 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.genreList.setFocus(m.genreList.opacity = 1)
return true
else if key = "replay" and m.itemGrid.isinFocusChain()
if m.resetGrid = true
m.itemGrid.animateToItem = 0
else
m.itemGrid.jumpToItem = 0
end if
else if key = "replay" and m.genreList.isinFocusChain()
if m.resetGrid = true
m.genreList.animateToItem = 0
@ -775,6 +772,12 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.genreList.jumpToItem = 0
end if
return true
else if key = "play"
itemToPlay = getItemFocused()
if itemToPlay <> invalid
m.top.quickPlayNode = itemToPlay
return true
end if
end if
if key = "replay"

View File

@ -3,6 +3,7 @@ sub init()
updateSize()
m.top.rowFocusAnimationStyle = "fixedFocus"
m.top.observeField("rowItemSelected", "onRowItemSelected")
m.top.observeField("rowItemFocused", "onRowItemFocused")
' Set up all Tasks
m.LoadPeopleTask = CreateObject("roSGNode", "LoadItemsTask")
@ -207,3 +208,7 @@ end sub
sub onRowItemSelected()
m.top.selectedItem = m.top.content.getChild(m.top.rowItemSelected[0]).getChild(m.top.rowItemSelected[1])
end sub
sub onRowItemFocused()
m.top.focusedItem = m.top.content.getChild(m.top.rowItemFocused[0]).getChild(m.top.rowItemFocused[1])
end sub

View File

@ -4,6 +4,7 @@
<interface>
<field id="type" type="string" />
<field id="parentId" type="string" />
<field id="focusedItem" type="node" alwaysNotify="true" />
<field id="selectedItem" type="node" alwaysNotify="true" />
<function name="loadParts" />
<function name="loadPersonVideos" />

View File

@ -440,21 +440,20 @@ sub itemSelected()
end sub
function onKeyEvent(key as string, press as boolean) as boolean
handled = false
if press
if key = "play"
print "play was pressed from homerow"
itemToPlay = m.top.content.getChild(m.top.rowItemFocused[0]).getChild(m.top.rowItemFocused[1])
if isValid(itemToPlay) and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
if isValid(itemToPlay)
m.top.quickPlayNode = itemToPlay
end if
handled = true
end if
if key = "replay"
return true
else if key = "replay"
m.top.jumpToRowItem = [m.top.rowItemFocused[0], 0]
return true
end if
end if
return handled
return false
end function
function filterNodeArray(nodeArray as object, nodeKey as string, excludeArray as object) as object

View File

@ -2,7 +2,7 @@
<component name="HomeRows" extends="RowList">
<interface>
<field id="selectedItem" type="node" alwaysNotify="true" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
<field id="quickPlayNode" type="node" />
<function name="updateHomeRows" />
<function name="loadLibraries" />
</interface>

View File

@ -137,7 +137,8 @@ sub loadItems()
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"
' also skip songs since there is limited space
if not (item.Type = "Book" or item.Type = "Audio")
tmp = CreateObject("roSGNode", "HomeData")
params = {}

View File

@ -127,6 +127,11 @@ sub playQueue()
return
end if
if nextItemMediaType = "movie"
CreateVideoPlayerView()
return
end if
if nextItemMediaType = "episode"
CreateVideoPlayerView()
return

View File

@ -383,6 +383,12 @@ function onKeyEvent(key as string, press as boolean) as boolean
audioOptionsClosed()
return true
end if
else if key = "play" and m.extrasGrid.hasFocus()
print "Play was pressed from the movie details extras slider"
if m.extrasGrid.focusedItem <> invalid
m.top.quickPlayNode = m.extrasGrid.focusedItem
return true
end if
end if
return false
end function

View File

@ -50,5 +50,6 @@
<field id="trailerAvailable" type="bool" onChange="trailerAvailableChanged" value="false" />
<field id="selectedAudioStreamIndex" type="integer" />
<field id="selectedVideoStreamId" type="string" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
</interface>
</component>

View File

@ -313,5 +313,13 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
end if
if key = "play"
itemToPlay = m.albums.MusicArtistAlbumData.items[m.albums.itemFocused]
if isValid(itemToPlay)
m.top.quickPlayNode = itemToPlay
end if
return true
end if
return false
end function

View File

@ -54,5 +54,6 @@
<field id="playArtistSelected" alias="play.selected" />
<field id="instantMixSelected" alias="instantMix.selected" />
<field id="selectedButtonIndex" type="integer" value="-1" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
</interface>
</component>

View File

@ -11,6 +11,7 @@ sub init()
m.getShuffleEpisodesTask = createObject("roSGNode", "getShuffleEpisodesTask")
m.Shuffle = m.top.findNode("Shuffle")
m.extrasSlider.visible = true
m.seasons = m.top.findNode("seasons")
end sub
sub itemContentChanged()
@ -223,6 +224,20 @@ function onKeyEvent(key as string, press as boolean) as boolean
else if key = "up" and m.Shuffle.hasFocus()
overview.setFocus(true)
return true
else if key = "play" and m.seasons.hasFocus()
print "play was pressed from the seasons row"
if isValid(m.seasons.TVSeasonData) and isValid(m.seasons.TVSeasonData.Items)
itemFocused = m.seasons.rowItemFocused
m.top.quickPlayNode = m.seasons.TVSeasonData.Items[itemFocused[1]]
return true
end if
else if key = "play" and m.extrasSlider.isInFocusChain()
print "play was pressed from the extras grid"
extrasGrid = m.top.findNode("extrasGrid")
if extrasGrid.focusedItem <> invalid
m.top.quickPlayNode = extrasGrid.focusedItem
return true
end if
end if
return false

View File

@ -32,5 +32,6 @@
<field id="itemContent" type="node" onChange="itemContentChanged" />
<field id="seasonData" type="assocarray" alias="seasons.TVSeasonData" />
<field id="seasonSelected" alias="seasons.rowItemSelected" alwaysNotify="true" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
</interface>
</component>

View File

@ -118,9 +118,30 @@ sub Main (args as dynamic) as void
else if isNodeEvent(msg, "quickPlayNode")
group = sceneManager.callFunc("getActiveScene")
reportingNode = msg.getRoSGNode()
itemNode = reportingNode.quickPlayNode
itemNode = invalid
if isValid(reportingNode)
itemNode = reportingNode.quickPlayNode
reportingNodeType = reportingNode.subtype()
' prevent double fire on continue watching home row
if isValid(reportingNodeType) and reportingNodeType = "Home"
reportingNode.quickPlayNode = invalid
end if
end if
if isValid(itemNode) and isValid(itemNode.id) and itemNode.id <> ""
if itemNode.type = "Episode" or itemNode.type = "Movie" or itemNode.type = "Video"
print "quickPlayNode=", itemNode
itemType = invalid
if isValid(itemNode.type)
itemType = Lcase(itemNode.type)
end if
' grab type from json if needed
if not isValid(itemType) or itemType = ""
if isValid(itemNode.json) and isValid(itemNode.json.type)
itemType = Lcase(itemNode.json.type)
end if
end if
print "quickPlayNode type=", itemType
if itemType = "episode" or itemType = "movie" or itemType = "video"
' attempt to play video file. resume if possible
if isValid(itemNode.selectedVideoStreamId)
itemNode.id = itemNode.selectedVideoStreamId
end if
@ -141,14 +162,184 @@ sub Main (args as dynamic) as void
m.global.queueManager.callFunc("push", itemNode)
m.global.queueManager.callFunc("playQueue")
' Prevent quick play node from double firing
reportingNode.quickPlayNode = invalid
if LCase(group.subtype()) = "tvepisodes"
if isValid(group.lastFocus)
group.lastFocus.setFocus(true)
end if
end if
else if itemType = "audio"
' attempt to play audio file
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", itemNode)
m.global.queueManager.callFunc("playQueue")
else if itemType = "musicalbum"
' attempt to play the entire album starting with track 1
m.global.queueManager.callFunc("clear")
' convert album to list of songs
albumSongs = MusicSongList(itemNode.id)
' add each song to the queue
for each song in albumSongs.items
song.type = "Audio"
m.global.queueManager.callFunc("push", song)
end for
' play queue
m.global.queueManager.callFunc("playQueue")
else if itemType = "musicartist"
' attempt to shuffle play all songs by artist
m.global.queueManager.callFunc("clear")
data = GetSongsByArtist(itemNode.id, { "sortBy": "Random" })
if isValid(data)
for each item in data.items
m.global.queueManager.callFunc("push", item)
end for
m.global.queueManager.callFunc("playQueue")
end if
else if itemType = "boxset"
' attempt to play all movies in the boxset
' play them in order of release
m.global.queueManager.callFunc("clear")
data = api.items.GetByQuery({
"userid": m.global.session.user.id,
"parentid": itemNode.id,
"EnableTotalRecordCount": false
})
if isValid(data) and isValid(data.Items) and data.Items.count() > 0
' there are videos inside
print "found videos inside boxset"
for each item in data.Items
m.global.queueManager.callFunc("push", item)
end for
m.global.queueManager.callFunc("playQueue")
end if
else if itemType = "series"
' attempt to play first unwatched episode
m.global.queueManager.callFunc("clear")
data = api.shows.GetNextUp({
"seriesId": itemNode.id,
"recursive": true,
"SortBy": "DatePlayed",
"SortOrder": "Descending",
"ImageTypeLimit": 1,
"UserId": m.global.session.user.id,
"EnableRewatching": false,
"DisableFirstEpisode": false,
"EnableTotalRecordCount": false
})
if isValid(data) and isValid(data.Items) and data.Items.count() > 0
' there are unwatched episodes
for each item in data.Items
m.global.queueManager.callFunc("push", item)
end for
m.global.queueManager.callFunc("playQueue")
else
' next up check was empty
' check for a resumable episode
data = api.users.GetResumeItemsByQuery(m.global.session.user.id, {
"parentId": itemNode.id,
"userid": m.global.session.user.id,
"SortBy": "DatePlayed",
"recursive": true,
"SortOrder": "Descending",
"Filters": "IsResumable",
"EnableTotalRecordCount": false
})
print "resumeitems data=", data
if isValid(data) and isValid(data.Items) and data.Items.count() > 0
' play the resumable episode
for each item in data.Items
if isValid(item.UserData) and isValid(item.UserData.PlaybackPositionTicks)
item.startingPoint = item.userdata.PlaybackPositionTicks
end if
m.global.queueManager.callFunc("push", item)
end for
m.global.queueManager.callFunc("playQueue")
else
' shuffle all episodes
data = api.shows.GetEpisodes(itemNode.id, {
"userid": m.global.session.user.id,
"SortBy": "Random",
"EnableTotalRecordCount": false
})
if isValid(data) and isValid(data.Items) and data.Items.count() > 0
' add all episodes found to a playlist
for each item in data.Items
m.global.queueManager.callFunc("push", item)
end for
m.global.queueManager.callFunc("playQueue")
end if
end if
end if
else if itemType = "collectionfolder"
' play depends on the kind of files inside the collectionfolder
else if itemType = "season"
' play first unwatched episode
m.global.queueManager.callFunc("clear")
unwatchedData = api.shows.GetEpisodes(itemNode.json.SeriesId, {
"seasonId": itemNode.id,
"userid": m.global.session.user.id,
"EnableTotalRecordCount": false
})
if isValid(unwatchedData) and isValid(unwatchedData.Items)
' find the first unwatched episode
firstUnwatchedEpisodeIndex = invalid
for each item in unwatchedData.Items
if isValid(item.UserData)
if isValid(item.UserData.Played) and item.UserData.Played = false
firstUnwatchedEpisodeIndex = item.IndexNumber - 1
if isValid(item.UserData.PlaybackPositionTicks)
item.startingPoint = item.UserData.PlaybackPositionTicks
end if
exit for
end if
end if
end for
if isValid(firstUnwatchedEpisodeIndex)
for i = firstUnwatchedEpisodeIndex to unwatchedData.Items.count() - 1
m.global.queueManager.callFunc("push", unwatchedData.Items[i])
end for
m.global.queueManager.callFunc("playQueue")
else
' try to find a "continue watching" episode
continueData = api.users.GetResumeItemsByQuery(m.global.session.user.id, {
"parentId": itemNode.id,
"userid": m.global.session.user.id,
"SortBy": "DatePlayed",
"recursive": true,
"SortOrder": "Descending",
"Filters": "IsResumable",
"EnableTotalRecordCount": false
})
print "resumeitems continueData=", continueData
if isValid(continueData) and isValid(continueData.Items) and continueData.Items.count() > 0
' play the resumable episode
for each item in continueData.Items
if isValid(item.UserData) and isValid(item.UserData.PlaybackPositionTicks)
item.startingPoint = item.userdata.PlaybackPositionTicks
end if
m.global.queueManager.callFunc("push", item)
end for
m.global.queueManager.callFunc("playQueue")
else
' play the whole season in order
if isValid(unwatchedData) and isValid(unwatchedData.Items) and unwatchedData.Items.count() > 0
' add all episodes found to a playlist
for each item in unwatchedData.Items
m.global.queueManager.callFunc("push", item)
end for
m.global.queueManager.callFunc("playQueue")
end if
end if
end if
end if
end if
end if
else if isNodeEvent(msg, "selectedItem")

View File

@ -565,6 +565,7 @@ function CreateMovieDetailsGroup(movie as object) as dynamic
end if
' start building MovieDetails view
group = CreateObject("roSGNode", "MovieDetails")
group.observeField("quickPlayNode", m.port)
group.overhangTitle = movie.title
group.optionsAvailable = false
group.trailerAvailable = false
@ -618,6 +619,7 @@ function CreateSeriesDetailsGroup(seriesID as string) as dynamic
group.seasonData = seasonData
' watch for button presses
group.observeField("seasonSelected", m.port)
group.observeField("quickPlayNode", m.port)
' setup and load series extras
extras = group.findNode("extrasGrid")
extras.observeField("selectedItem", m.port)
@ -670,6 +672,7 @@ function CreateArtistView(artist as object) as dynamic
group.observeField("appearsOnSelected", m.port)
end if
group.observeField("quickPlayNode", m.port)
m.global.sceneManager.callFunc("pushScene", group)
return group
@ -806,6 +809,7 @@ function CreateMusicLibraryView(libraryItem as object) as dynamic
group.parentItem = libraryItem
group.optionsAvailable = true
group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port)
return group
end function

View File

@ -223,15 +223,20 @@ function AppearsOnList(id as string)
end function
' Get list of songs belonging to an artist
function GetSongsByArtist(id as string)
function GetSongsByArtist(id as string, params = {} as object)
url = Substitute("Users/{0}/Items", m.global.session.user.id)
resp = APIRequest(url, {
paramArray = {
"AlbumArtistIds": id,
"includeitemtypes": "Audio",
"sortBy": "SortName",
"Recursive": true
})
}
' overwrite defaults with the params provided
for each param in params
paramArray.AddReplace(param, params[param])
end for
resp = APIRequest(url, paramArray)
data = getJson(resp)
results = []
@ -408,7 +413,7 @@ function TVSeasons(id as string) as dynamic
results = []
for each item in data.Items
imgParams = { "AddPlayedIndicator": item.UserData.Played }
tmp = CreateObject("roSGNode", "TVEpisodeData")
tmp = CreateObject("roSGNode", "TVSeasonData")
tmp.image = PosterImage(item.id, imgParams)
tmp.json = item
results.push(tmp)