Merge pull request #1376 from cewert/fix-quickplay

This commit is contained in:
Charles Ewert 2023-10-28 17:25:40 -04:00 committed by GitHub
commit dd3972cff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 821 additions and 106 deletions

View File

@ -725,6 +725,7 @@ sub showTVGuide()
m.tvGuide.filter = m.filter
m.tvGuide.searchTerm = m.voiceBox.text
m.top.appendChild(m.tvGuide)
m.scheduleGrid = m.top.findNode("scheduleGrid")
m.tvGuide.lastFocus.setFocus(true)
end sub
@ -742,6 +743,18 @@ sub onChannelFocused(msg)
m.channelFocused = node.focusedChannel
end sub
'Returns Focused Item
function getItemFocused()
if m.itemGrid.isinFocusChain() and isValid(m.itemGrid.itemFocused)
return m.itemGrid.content.getChild(m.itemGrid.itemFocused)
else if m.genreList.isinFocusChain() and isValid(m.genreList.rowItemFocused)
return m.genreList.content.getChild(m.genreList.rowItemFocused[0]).getChild(m.genreList.rowItemFocused[1])
else if m.scheduleGrid.isinFocusChain() and isValid(m.scheduleGrid.itemFocused)
return m.scheduleGrid.content.getChild(m.scheduleGrid.itemFocused)
end if
return invalid
end function
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
@ -788,11 +801,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)
itemToPlay = getItemFocused()
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

@ -708,7 +708,12 @@ end sub
'
'Returns Focused Item
function getItemFocused()
return m.itemGrid.content.getChild(m.itemGrid.itemFocused)
if m.itemGrid.isinFocusChain() and isValid(m.itemGrid.itemFocused)
return m.itemGrid.content.getChild(m.itemGrid.itemFocused)
else if m.genreList.isinFocusChain() and isValid(m.genreList.rowItemFocused)
return m.genreList.content.getChild(m.genreList.rowItemFocused[0]).getChild(m.genreList.rowItemFocused[1])
end if
return invalid
end function
'
@ -870,11 +875,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

@ -573,7 +573,12 @@ end sub
'
'Returns Focused Item
function getItemFocused()
return m.itemGrid.content.getChild(m.itemGrid.itemFocused)
if m.itemGrid.isinFocusChain() and isValid(m.itemGrid.itemFocused)
return m.itemGrid.content.getChild(m.itemGrid.itemFocused)
else if m.genreList.isinFocusChain() and isValid(m.genreList.itemFocused)
return m.genreList.content.getChild(m.genreList.itemFocused)
end if
return invalid
end function
'
@ -751,7 +756,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)
@ -761,14 +765,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
@ -776,6 +778,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

@ -31,7 +31,7 @@ sub setData()
m.top.iconUrl = "pkg:/images/media_type_icons/folder_white.png"
end if
else if datum.type = "Episode"
else if datum.type = "Episode" or datum.type = "MusicVideo"
m.top.isWatched = datum.UserData.Played
imgParams = {}
@ -72,32 +72,7 @@ sub setData()
m.top.widePosterUrl = ImageURL(datum.Id, "Backdrop", imgParams)
end if
else if datum.type = "Movie"
m.top.isWatched = datum.UserData.Played
imgParams = {}
imgParams.Append({ "maxHeight": 261 })
imgParams.Append({ "maxWidth": 175 })
if datum.ImageTags.Primary <> invalid
param = { "Tag": datum.ImageTags.Primary }
imgParams.Append(param)
end if
m.top.posterURL = ImageURL(datum.id, "Primary", imgParams)
' For wide image, use backdrop
imgParams["maxWidth"] = 464
if datum.ImageTags <> invalid and datum.imageTags.Thumb <> invalid
imgParams["Tag"] = datum.imageTags.Thumb
m.top.thumbnailUrl = ImageURL(datum.Id, "Thumb", imgParams)
else if datum.BackdropImageTags[0] <> invalid
imgParams["Tag"] = datum.BackdropImageTags[0]
m.top.thumbnailUrl = ImageURL(datum.id, "Backdrop", imgParams)
end if
else if datum.type = "Video"
else if datum.type = "Movie" or datum.type = "Video"
m.top.isWatched = datum.UserData.Played
imgParams = {}
@ -126,12 +101,10 @@ sub setData()
m.top.thumbnailURL = ImageURL(datum.id, "Primary", params)
m.top.widePosterUrl = m.top.thumbnailURL
m.top.posterUrl = m.top.thumbnailURL
else if datum.type = "TvChannel" or datum.type = "Channel"
params = { "Tag": datum.ImageTags.Primary, "maxHeight": 261, "maxWidth": 464 }
m.top.thumbnailURL = ImageURL(datum.id, "Primary", params)
m.top.widePosterUrl = m.top.thumbnailURL
m.top.iconUrl = "pkg:/images/media_type_icons/live_tv_white.png"
end if
end sub

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

@ -30,6 +30,7 @@ end sub
sub itemContentChanged()
itemData = m.top.itemContent
if itemData = invalid then return
itemData.Title = itemData.name ' Temporarily required while we move from "HomeItem" to "JFContentItem"
m.itemPoster.width = itemData.imageWidth
@ -135,7 +136,7 @@ sub itemContentChanged()
return
end if
if itemData.type = "Movie"
if itemData.type = "Movie" or itemData.type = "MusicVideo"
m.itemText.text = itemData.name
if itemData.PlayedPercentage > 0

View File

@ -522,21 +522,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

@ -11,6 +11,7 @@ sub init()
m.queue = []
m.originalQueue = []
m.queueTypes = []
m.isPlaying = false
' Preroll videos only play if user has cinema mode setting enabled
m.isPrerollActive = m.global.session.user.settings["playback.cinemamode"]
m.position = 0
@ -19,6 +20,7 @@ end sub
' Clear all content from play queue
sub clear()
m.isPlaying = false
m.queue = []
m.queueTypes = []
m.isPrerollActive = m.global.session.user.settings["playback.cinemamode"]
@ -111,6 +113,7 @@ end function
' Play items in queue
sub playQueue()
m.isPlaying = true
nextItem = getCurrentItem()
if not isValid(nextItem) then return
@ -122,11 +125,21 @@ sub playQueue()
return
end if
if nextItemMediaType = "musicvideo"
CreateVideoPlayerView()
return
end if
if nextItemMediaType = "video"
CreateVideoPlayerView()
return
end if
if nextItemMediaType = "movie"
CreateVideoPlayerView()
return
end if
if nextItemMediaType = "episode"
CreateVideoPlayerView()
return
@ -196,21 +209,25 @@ end function
sub shuffleQueueItems()
' By calling getQueue 2 different ways, Roku avoids needing to do a deep copy
m.originalQueue = m.global.queueManager.callFunc("getQueue")
songIDArray = getQueue()
itemIDArray = getQueue()
temp = invalid
' Move the currently playing song to the front of the queue
temp = top()
songIDArray[0] = getCurrentItem()
songIDArray[getPosition()] = temp
if m.isPlaying
' Save the currently playing item
temp = getCurrentItem()
' remove currently playing item from itemIDArray
itemIDArray.Delete(m.position)
end if
for i = 1 to songIDArray.count() - 1
j = Rnd(songIDArray.count() - 1)
temp = songIDArray[i]
songIDArray[i] = songIDArray[j]
songIDArray[j] = temp
end for
' shuffle all items
itemIDArray = shuffleArray(itemIDArray)
set(songIDArray)
if m.isPlaying
' Put currently playing item in front of itemIDArray
itemIDArray.Unshift(temp)
end if
set(itemIDArray)
end sub
' Return the fitst item in the play queue

View File

@ -385,6 +385,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,21 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
end if
if key = "play"
print "play button pressed from ArtistView"
itemToPlay = invalid
if isValid(m.albums) and m.albums.isInFocusChain()
itemToPlay = m.albums.MusicArtistAlbumData.items[m.albums.itemFocused]
else if isValid(m.appearsOn) and m.appearsOn.isInFocusChain()
itemToPlay = m.appearsOn.MusicArtistAlbumData.items[m.appearsOn.itemFocused]
end if
if isValid(itemToPlay)
m.top.quickPlayNode = itemToPlay
return true
end if
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

@ -12,7 +12,7 @@
</children>
<interface>
<field id="episodeSelected" alias="picker.itemSelected" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
<field id="quickPlayNode" type="node" />
<field id="seasonData" type="assocarray" onChange="setSeasonLoading" />
<field id="objects" alias="picker.objects" />
<field id="episodeObjects" type="assocarray" />

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

@ -125,48 +125,89 @@ sub Main (args as dynamic) as void
group.setFocus(true)
end if
else if isNodeEvent(msg, "quickPlayNode")
' measure processing time
timeSpan = CreateObject("roTimespan")
startMediaLoadingSpinner()
group = sceneManager.callFunc("getActiveScene")
reportingNode = msg.getRoSGNode()
itemNode = reportingNode.quickPlayNode
if isValid(itemNode) and isValid(itemNode.id) and itemNode.id <> ""
if itemNode.type = "Episode" or itemNode.type = "Movie" or itemNode.type = "Video"
if isValid(itemNode.selectedVideoStreamId)
itemNode.id = itemNode.selectedVideoStreamId
end if
audio_stream_idx = 0
if isValid(itemNode.selectedAudioStreamIndex) and itemNode.selectedAudioStreamIndex > 0
audio_stream_idx = itemNode.selectedAudioStreamIndex
end if
itemNode.selectedAudioStreamIndex = audio_stream_idx
playbackPosition = 0
' Display playback options dialog
if isValid(itemNode.json) and isValid(itemNode.json.userdata) and isValid(itemNode.json.userdata.PlaybackPositionTicks)
playbackPosition = itemNode.json.userdata.PlaybackPositionTicks
end if
if playbackPosition > 0
m.global.queueManager.callFunc("hold", itemNode)
playbackOptionDialog(playbackPosition, itemNode.json)
else
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", itemNode)
m.global.queueManager.callFunc("playQueue")
end if
' Prevent quick play node from double firing
itemNode = invalid
if isValid(reportingNode)
itemNode = reportingNode.quickPlayNode
reportingNodeType = reportingNode.subtype()
print "Quick Play reporting node type=", reportingNodeType
' prevent double fire bug
if isValid(reportingNodeType) and (reportingNodeType = "Home" or reportingNodeType = "TVEpisodes")
reportingNode.quickPlayNode = invalid
if LCase(group.subtype()) = "tvepisodes"
if isValid(group.lastFocus)
group.lastFocus.setFocus(true)
end if
end if
end if
end if
print "Quick Play started. itemNode=", itemNode
' if itemNode.json <> invalid
' print "itemNode.json=", itemNode.json
' end if
if isValid(itemNode) and isValid(itemNode.id) and itemNode.id <> ""
' make sure there is a type and convert type to lowercase
itemType = invalid
if isValid(itemNode.type) and itemNode.type <> ""
itemType = Lcase(itemNode.type)
else
' grab type from json and convert to lowercase
if isValid(itemNode.json) and isValid(itemNode.json.type)
itemType = Lcase(itemNode.json.type)
end if
end if
print "Quick Play itemNode type=", itemType
' can't play the item without knowing what type it is
if isValid(itemType)
m.global.queueManager.callFunc("clear") ' empty queue/playlist
m.global.queueManager.callFunc("resetShuffle") ' turn shuffle off
if itemType = "episode" or itemType = "movie" or itemType = "video"
quickplay.video(itemNode)
' restore focus
if LCase(group.subtype()) = "tvepisodes"
if isValid(group.lastFocus)
group.lastFocus.setFocus(true)
end if
end if
else if itemType = "audio"
quickplay.audio(itemNode)
else if itemType = "musicalbum"
quickplay.album(itemNode)
else if itemType = "musicartist"
quickplay.artist(itemNode)
else if itemType = "series"
quickplay.series(itemNode)
else if itemType = "season"
quickplay.season(itemNode)
else if itemType = "boxset"
quickplay.boxset(itemNode)
else if itemType = "collectionfolder"
quickplay.collectionFolder(itemNode)
else if itemType = "playlist"
quickplay.playlist(itemNode)
else if itemType = "userview"
quickplay.userView(itemNode)
else if itemType = "folder"
quickplay.folder(itemNode)
else if itemType = "musicvideo"
quickplay.musicVideo(itemNode)
else if itemType = "person"
quickplay.person(itemNode)
else if itemType = "tvchannel"
quickplay.tvChannel(itemNode)
else if itemType = "program"
quickplay.program(itemNode)
end if
m.global.queueManager.callFunc("playQueue")
end if
end if
stopLoadingSpinner()
elapsed = timeSpan.TotalMilliseconds() / 1000
print "Quick Play finished loading in " + elapsed.toStr() + " seconds."
else if isNodeEvent(msg, "selectedItem")
' If you select a library from ANYWHERE, follow this flow
selectedItem = msg.getData()
@ -259,6 +300,8 @@ sub Main (args as dynamic) as void
end if
else if selectedItemType = "MusicAlbum"
group = CreateAlbumView(selectedItem.json)
else if selectedItemType = "MusicVideo"
group = CreateMovieDetailsGroup(selectedItem)
else if selectedItemType = "Playlist"
group = CreatePlaylistView(selectedItem.json)
else if selectedItemType = "Audio"
@ -397,6 +440,8 @@ sub Main (args as dynamic) as void
group = CreateArtistView(node.json)
else if node.type = "MusicAlbum"
group = CreateAlbumView(node.json)
else if node.type = "MusicVideo"
group = CreateMovieDetailsGroup(node)
else if node.type = "Audio"
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("resetShuffle")
@ -621,6 +666,7 @@ sub Main (args as dynamic) as void
' - "low" means that the general memory is below acceptable levels but not critical
' - "critical" means that general memory are at dangerously low level and that the OS may force terminate the application
print "event.generalMemoryLevel = ", event.generalMemoryLevel
session.Update("memoreyLevel", event.generalMemoryLevel)
else if isValid(event.audioCodecCapabilityChanged)
' The audio codec capability has changed if true.
print "event.audioCodecCapabilityChanged = ", event.audioCodecCapabilityChanged

View File

@ -598,6 +598,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
@ -651,6 +652,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)
@ -703,6 +705,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
@ -821,6 +824,7 @@ function CreateItemGrid(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
@ -832,6 +836,7 @@ function CreateMovieLibraryView(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
@ -843,6 +848,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

@ -225,15 +225,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 = []
@ -410,7 +415,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)

View File

@ -1200,8 +1200,8 @@ namespace api
end function
' Gets the original items of a playlist.
function GetItems(id as string, params = {} as object)
req = APIRequest(Substitute("/playlists/{0}/items", id), params)
function GetItems(playlistID as string, params = {} as object)
req = APIRequest(Substitute("/playlists/{0}/items", playlistID), params)
return getJson(req)
end function

View File

@ -392,3 +392,13 @@ function arrayHasValue(arr as object, value as dynamic) as boolean
end for
return false
end function
' Takes an array of data, shuffles the order, then returns the array
' uses the Fisher-Yates shuffling algorithm
function shuffleArray(array as object) as object
for i = array.count() - 1 to 1 step -1
j = Rnd(i + 1) - 1
t = array[i] : array[i] = array[j] : array[j] = t
end for
return array
end function

585
source/utils/quickplay.bs Normal file
View File

@ -0,0 +1,585 @@
' All of the Quick Play logic seperated by media type
namespace quickplay
' Takes an array of items and adds to global queue.
' Also shuffles the playlist if asked
sub pushToQueue(queueArray as object, shufflePlay = false as boolean)
if isValidAndNotEmpty(queueArray)
' load everything
for each item in queueArray
m.global.queueManager.callFunc("push", item)
end for
' shuffle the playlist if asked
if shufflePlay and m.global.queueManager.callFunc("getCount") > 1
m.global.queueManager.callFunc("toggleShuffle")
end if
end if
end sub
' A single video file.
sub video(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) or not isValid(itemNode.json) then return
' attempt to play video file. resume if possible
if isValid(itemNode.selectedVideoStreamId)
itemNode.id = itemNode.selectedVideoStreamId
end if
audio_stream_idx = 0
if isValid(itemNode.selectedAudioStreamIndex) and itemNode.selectedAudioStreamIndex > 0
audio_stream_idx = itemNode.selectedAudioStreamIndex
end if
itemNode.selectedAudioStreamIndex = audio_stream_idx
playbackPosition = 0
if isValid(itemNode.json.userdata) and isValid(itemNode.json.userdata.PlaybackPositionTicks)
playbackPosition = itemNode.json.userdata.PlaybackPositionTicks
end if
itemNode.startingPoint = playbackPosition
m.global.queueManager.callFunc("push", itemNode)
end sub
' A single audio file.
sub audio(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
m.global.queueManager.callFunc("push", itemNode)
end sub
' A single music video file.
sub musicVideo(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) or not isValid(itemNode.json) then return
m.global.queueManager.callFunc("push", itemNode)
end sub
' A music album.
' Play the entire album starting with track 1.
sub album(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
' grab list of songs in the album
albumSongs = api.users.GetItemsByQuery(m.global.session.user.id, {
"parentId": itemNode.id,
"imageTypeLimit": 1,
"sortBy": "SortName",
"limit": 2000,
"enableUserData": false,
"EnableTotalRecordCount": false
})
if isValid(albumSongs) and isValidAndNotEmpty(albumSongs.items)
quickplay.pushToQueue(albumSongs.items)
end if
end sub
' A music artist.
' Shuffle play all songs by artist.
sub artist(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
' get all songs by artist
artistSongs = api.users.GetItemsByQuery(m.global.session.user.id, {
"artistIds": itemNode.id,
"includeItemTypes": "Audio",
"sortBy": "Album",
"limit": 2000,
"imageTypeLimit": 1,
"Recursive": true,
"enableUserData": false,
"EnableTotalRecordCount": false
})
print "artistSongs=", artistSongs
if isValid(artistSongs) and isValidAndNotEmpty(artistSongs.items)
quickplay.pushToQueue(artistSongs.items, true)
end if
end sub
' A boxset.
' Play all items inside.
sub boxset(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
data = api.items.GetByQuery({
"userid": m.global.session.user.id,
"parentid": itemNode.id,
"limit": 2000,
"EnableTotalRecordCount": false
})
if isValid(data) and isValidAndNotEmpty(data.Items)
quickplay.pushToQueue(data.items)
end if
end sub
' A TV Show Series.
' Play the first unwatched episode.
' If none, shuffle play the whole series.
sub series(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
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 isValidAndNotEmpty(data.Items)
' there are unwatched episodes
m.global.queueManager.callFunc("push", data.Items[0])
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 isValidAndNotEmpty(data.Items)
' play the resumable episode
if isValid(data.Items[0].UserData) and isValid(data.Items[0].UserData.PlaybackPositionTicks)
data.Items[0].startingPoint = data.Items[0].userdata.PlaybackPositionTicks
end if
m.global.queueManager.callFunc("push", data.Items[0])
else
' shuffle all episodes
data = api.shows.GetEpisodes(itemNode.id, {
"userid": m.global.session.user.id,
"SortBy": "Random",
"limit": 2000,
"EnableTotalRecordCount": false
})
if isValid(data) and isValidAndNotEmpty(data.Items)
' add all episodes found to a playlist
quickplay.pushToQueue(data.Items)
end if
end if
end if
end sub
' More than one TV Show Series.
' Shuffle play all watched episodes
sub multipleSeries(itemNodes as object)
if isValidAndNotEmpty(itemNodes)
numTotal = 0
numLimit = 2000
for each tvshow in itemNodes
' grab all watched episodes for each series
showData = api.shows.GetEpisodes(tvshow.id, {
"userId": m.global.session.user.id,
"SortBy": "Random",
"imageTypeLimit": 0,
"EnableTotalRecordCount": false,
"enableImages": false
})
if isValid(showData) and isValidAndNotEmpty(showData.items)
playedEpisodes = []
' add all played episodes to queue
for each episode in showData.items
if isValid(episode.userdata) and isValid(episode.userdata.Played)
if episode.userdata.Played
playedEpisodes.push(episode)
end if
end if
end for
quickplay.pushToQueue(playedEpisodes)
' keep track of how many items we've seen
numTotal = numTotal + showData.items.count()
if numTotal >= numLimit
' stop grabbing more items if we hit our limit
exit for
end if
end if
end for
if m.global.queueManager.callFunc("getCount") > 1
m.global.queueManager.callFunc("toggleShuffle")
end if
end if
end sub
' A TV Show Season.
' Play the first unwatched episode.
' If none, play the whole season starting with episode 1.
sub season(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
unwatchedData = api.shows.GetEpisodes(itemNode.json.SeriesId, {
"seasonId": itemNode.id,
"userid": m.global.session.user.id,
"limit": 2000,
"EnableTotalRecordCount": false
})
if isValid(unwatchedData) and isValidAndNotEmpty(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)
' add the first unwatched episode and the rest of the season to a playlist
for i = firstUnwatchedEpisodeIndex to unwatchedData.Items.count() - 1
m.global.queueManager.callFunc("push", unwatchedData.Items[i])
end for
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
})
if isValid(continueData) and isValidAndNotEmpty(continueData.Items)
' 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
else
' play the whole season in order
if isValid(unwatchedData) and isValidAndNotEmpty(unwatchedData.Items)
' add all episodes found to a playlist
pushToQueue(unwatchedData.Items)
end if
end if
end if
end if
end sub
' Quick Play A Person.
' Shuffle play all videos found
sub person(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
' get movies and videos by the person
personMovies = api.users.GetItemsByQuery(m.global.session.user.id, {
"personIds": itemNode.id,
"includeItemTypes": "Movie,Video",
"excludeItemTypes": "Season,Series",
"recursive": true,
"limit": 2000
})
print "personMovies=", personMovies
if isValid(personMovies) and isValidAndNotEmpty(personMovies.Items)
' add each item to the queue
quickplay.pushToQueue(personMovies.Items)
end if
' get watched episodes by the person
personEpisodes = api.users.GetItemsByQuery(m.global.session.user.id, {
"personIds": itemNode.id,
"includeItemTypes": "Episode",
"isPlayed": true,
"excludeItemTypes": "Season,Series",
"recursive": true,
"limit": 2000
})
print "personEpisodes=", personEpisodes
if isValid(personEpisodes) and isValidAndNotEmpty(personEpisodes.Items)
' add each item to the queue
quickplay.pushToQueue(personEpisodes.Items)
end if
if m.global.queueManager.callFunc("getCount") > 1
m.global.queueManager.callFunc("toggleShuffle")
end if
end sub
' Quick Play A TVChannel
sub tvChannel(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
stopLoadingSpinner()
group = CreateVideoPlayerGroup(itemNode.id)
m.global.sceneManager.callFunc("pushScene", group)
end sub
' Quick Play A Live Program
sub program(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.json) or not isValid(itemNode.json.ChannelId) then return
stopLoadingSpinner()
group = CreateVideoPlayerGroup(itemNode.json.ChannelId)
m.global.sceneManager.callFunc("pushScene", group)
end sub
' Quick Play A Playlist.
' Play the first unwatched episode.
' If none, play the whole season starting with episode 1.
sub playlist(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
' get playlist items
myPlaylist = api.playlists.GetItems(itemNode.id, {
"userId": m.global.session.user.id,
"limit": 2000
})
if isValid(myPlaylist) and isValidAndNotEmpty(myPlaylist.Items)
' add each item to the queue
quickplay.pushToQueue(myPlaylist.Items)
if m.global.queueManager.callFunc("getCount") > 1
m.global.queueManager.callFunc("toggleShuffle")
end if
end if
end sub
' Quick Play A folder.
' Shuffle play all items found
sub folder(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
paramArray = {
"includeItemTypes": ["Episode", "Movie", "Video"],
"videoTypes": "VideoFile",
"sortBy": "Random",
"limit": 2000,
"imageTypeLimit": 1,
"Recursive": true,
"enableUserData": false,
"EnableTotalRecordCount": false
}
' modify api query based on folder type
folderType = Lcase(itemNode.json.type)
if folderType = "studio"
paramArray["studioIds"] = itemNode.id
else if folderType = "genre"
paramArray["genreIds"] = itemNode.id
if isValid(itemNode.json.MovieCount) and itemNode.json.MovieCount > 0
paramArray["includeItemTypes"] = "Movie"
end if
else if folderType = "musicgenre"
paramArray["genreIds"] = itemNode.id
paramArray.delete("videoTypes")
paramArray["includeItemTypes"] = "Audio"
else
paramArray["parentId"] = itemNode.id
end if
' look for tv series instead of video files
if isValid(itemNode.json.SeriesCount) and itemNode.json.SeriesCount > 0
paramArray["includeItemTypes"] = "Series"
paramArray.Delete("videoTypes")
end if
' get folder items
folderData = api.users.GetItemsByQuery(m.global.session.user.id, paramArray)
print "folderData=", folderData
if isValid(folderData) and isValidAndNotEmpty(folderData.items)
if isValid(itemNode.json.SeriesCount) and itemNode.json.SeriesCount > 0
if itemNode.json.SeriesCount = 1
quickplay.series(folderData.items[0])
else
quickplay.multipleSeries(folderData.items)
end if
else
quickplay.pushToQueue(folderData.items, true)
end if
end if
end sub
' Quick Play A CollectionFolder.
' Shuffle play the items inside
' with some differences based on collectionType.
sub collectionFolder(itemNode as object)
if not isValid(itemNode) or not isValid(itemNode.id) then return
' play depends on the kind of files inside the collectionfolder
print "attempting to quickplay a collection folder"
collectionType = LCase(itemNode.collectionType)
print "collectionType=", collectionType
if collectionType = "movies"
' get randomized list of movies inside
data = api.users.GetItemsByQuery(m.global.session.user.id, {
"parentId": itemNode.id,
"sortBy": "Random",
"limit": 2000
})
if isValid(data) and isValidAndNotEmpty(data.items)
movieList = []
' add each item to the queue
for each item in data.Items
' only add movies we're not currently watching
if isValid(item.userdata) and isValid(item.userdata.PlaybackPositionTicks)
if item.userdata.PlaybackPositionTicks = 0
movieList.push(item)
end if
end if
end for
quickplay.pushToQueue(movieList)
end if
else if collectionType = "music"
' get audio files from under this collection
' sort songs by album then artist
songsData = api.users.GetItemsByQuery(m.global.session.user.id, {
"parentId": itemNode.id,
"includeItemTypes": "Audio",
"sortBy": "Album",
"Recursive": true,
"limit": 2000,
"imageTypeLimit": 1,
"enableUserData": false,
"EnableTotalRecordCount": false
})
print "songsData=", songsData
if isValid(songsData) and isValidAndNotEmpty(songsData.items)
quickplay.pushToQueue(songsData.Items, true)
end if
else if collectionType = "boxsets"
' get list of all boxsets inside
boxsetData = api.users.GetItemsByQuery(m.global.session.user.id, {
"parentId": itemNode.id,
"limit": 2000,
"imageTypeLimit": 0,
"enableUserData": false,
"EnableTotalRecordCount": false,
"enableImages": false
})
print "boxsetData=", boxsetData
if isValid(boxsetData) and isValidAndNotEmpty(boxsetData.items)
' pick a random boxset
arrayIndex = Rnd(boxsetData.items.count()) - 1
myBoxset = boxsetData.items[arrayIndex]
' grab list of items from boxset
print "myBoxset=", myBoxset
boxsetData = api.users.GetItemsByQuery(m.global.session.user.id, {
"parentId": myBoxset.id,
"EnableTotalRecordCount": false
})
if isValid(boxsetData) and isValidAndNotEmpty(boxsetData.items)
' add all boxset items to queue
quickplay.pushToQueue(boxsetData.Items)
end if
end if
else if collectionType = "tvshows" or collectionType = "collectionfolder"
' get list of tv shows inside
tvshowsData = api.users.GetItemsByQuery(m.global.session.user.id, {
"parentId": itemNode.id,
"sortBy": "Random",
"imageTypeLimit": 0,
"enableUserData": false,
"EnableTotalRecordCount": false,
"enableImages": false
})
print "tvshowsData=", tvshowsData
if isValid(tvshowsData) and isValidAndNotEmpty(tvshowsData.items)
quickplay.multipleSeries(tvshowsData.items)
end if
else if collectionType = "musicvideos"
' get randomized list of videos inside
data = api.users.GetItemsByQuery(m.global.session.user.id, {
"parentId": itemNode.id,
"includeItemTypes": "MusicVideo",
"sortBy": "Random",
"Recursive": true,
"limit": 2000,
"imageTypeLimit": 1,
"enableUserData": false,
"EnableTotalRecordCount": false
})
print "data=", data
if isValid(data) and isValidAndNotEmpty(data.items)
quickplay.pushToQueue(data.Items)
end if
' else if collectionType = "homevideos" ' also used for a "Photo" library
else
print "Quick Play WARNING: Unknown collection type"
end if
end sub
' Quick Play A UserView.
' Play logic depends on "collectionType".
sub userView(itemNode as object)
' play depends on the kind of files inside the collectionfolder
collectionType = LCase(itemNode.collectionType)
print "collectionType=", collectionType
if collectionType = "playlists"
' get list of all playlists inside
playlistData = api.users.GetItemsByQuery(m.global.session.user.id, {
"parentId": itemNode.id,
"imageTypeLimit": 0,
"enableUserData": false,
"EnableTotalRecordCount": false,
"enableImages": false
})
print "playlistData=", playlistData
if isValid(playlistData) and isValidAndNotEmpty(playlistData.items)
' pick a random playlist
arrayIndex = Rnd(playlistData.items.count()) - 1
myPlaylist = playlistData.items[arrayIndex]
' grab list of items from playlist
print "myPlaylist=", myPlaylist
playlistItems = api.playlists.GetItems(myPlaylist.id, {
"userId": m.global.session.user.id,
"EnableTotalRecordCount": false,
"limit": 2000
})
' validate api results
if isValid(playlistItems) and isValidAndNotEmpty(playlistItems.items)
quickplay.pushToQueue(playlistItems.items, true)
end if
end if
else if collectionType = "livetv"
' get list of all tv channels
channelData = api.users.GetItemsByQuery(m.global.session.user.id, {
"includeItemTypes": "TVChannel",
"sortBy": "Random",
"Recursive": true,
"imageTypeLimit": 0,
"enableUserData": false,
"EnableTotalRecordCount": false,
"enableImages": false
})
print "channelData=", channelData
if isValid(channelData) and isValidAndNotEmpty(channelData.items)
' pick a random channel
arrayIndex = Rnd(channelData.items.count()) - 1
myChannel = channelData.items[arrayIndex]
print "myChannel=", myChannel
' play channel
quickplay.tvChannel(myChannel)
end if
else
print "Quick Play CollectionFolder WARNING: Unknown collection type"
end if
end sub
end namespace

View File

@ -8,6 +8,7 @@ namespace session
sub Init()
m.global.addFields({
session: {
"memoryLevel": "normal",
server: {},
user: {
Configuration: {},