function LoginFlow()
'Collect Jellyfin server and user information
start_login:
serverUrl = get_setting("server")
if isValid(serverUrl)
print "Previous server connection saved to registry"
startOver = not session.server.UpdateURL(serverUrl)
if startOver
print "Could not connect to previously saved server."
end if
else
startOver = true
print "No previous server connection saved to registry"
end if
invalidServer = true
if not startOver
m.scene.isLoading = true
invalidServer = ServerInfo().Error
m.scene.isLoading = false
end if
m.serverSelection = "Saved"
if startOver or invalidServer
print "Get server details"
SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting
m.serverSelection = CreateServerGroup()
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if m.serverSelection = "backPressed"
print "backPressed"
m.global.sceneManager.callFunc("clearScenes")
return false
end if
SaveServerList()
end if
activeUser = get_setting("active_user")
if activeUser = invalid
print "No active user found in registry"
user_select:
SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting
publicUsers = GetPublicUsers()
numPubUsers = 0
if isValid(publicUsers) then numPubUsers = publicUsers.count()
savedUsers = getSavedUsers()
numSavedUsers = savedUsers.count()
if numPubUsers > 0 or numSavedUsers > 0
publicUsersNodes = []
publicUserIds = []
' load public users
if numPubUsers > 0
for each item in publicUsers
user = CreateObject("roSGNode", "PublicUserData")
user.id = item.Id
user.name = item.Name
if isValid(item.PrimaryImageTag)
user.ImageURL = UserImageURL(user.id, { "tag": item.PrimaryImageTag })
end if
publicUsersNodes.push(user)
publicUserIds.push(user.id)
end for
end if
' load saved users for this server id
if numSavedUsers > 0
for each savedUser in savedUsers
if isValid(savedUser.serverId) and savedUser.serverId = m.global.session.server.id
' only show unique userids on screen.
if not arrayHasValue(publicUserIds, savedUser.Id)
user = CreateObject("roSGNode", "PublicUserData")
user.id = savedUser.Id
if isValid(savedUser.username)
user.name = savedUser.username
end if
if isValid(savedUser.primaryImageTag)
user.ImageURL = UserImageURL(user.id, { "tag": savedUser.primaryImageTag })
end if
publicUsersNodes.push(user)
end if
end if
end for
end if
' push all users to the user select view
userSelected = CreateUserSelectGroup(publicUsersNodes)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if userSelected = "backPressed"
session.server.Delete()
unset_setting("server")
goto start_login
else if userSelected <> ""
startLoadingSpinner()
print "A public user was selected with username=" + userSelected
session.user.Update("name", userSelected)
' save userid to session
for each user in publicUsersNodes
if user.name = userSelected
session.user.Update("id", user.id)
exit for
end if
end for
' try to login with token from registry
myToken = get_user_setting("token")
myPrimaryImageTag = get_user_setting("primaryimagetag")
if myToken <> invalid
' check if token is valid
print "Auth token found in registry for selected user"
session.user.Update("authToken", myToken)
print "Attempting to use API with auth token"
currentUser = AboutMe()
if currentUser = invalid
print "Auth token is no longer valid - deleting token"
unset_user_setting("token")
unset_user_setting("username")
if isValid(myPrimaryImageTag)
unset_user_setting("primaryimagetag")
end if
else
print "Success! Auth token is still valid"
session.user.Login(currentUser, true)
LoadUserAbilities()
return true
end if
else
print "No auth token found in registry for selected user"
end if
'Try to login without password. If the token is valid, we're done
print "Attempting to login with no password"
userData = get_token(userSelected, "")
if isValid(userData)
print "login success!"
session.user.Login(userData, true)
LoadUserAbilities()
return true
else
print "Auth failed. Password required"
end if
end if
else
userSelected = ""
end if
stopLoadingSpinner()
passwordEntry = CreateSigninGroup(userSelected)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if passwordEntry = "backPressed"
if numPubUsers > 0
goto user_select
else
session.server.Delete()
unset_setting("server")
goto start_login
end if
end if
else
print "Active user found in registry"
session.user.Update("id", activeUser)
myUsername = get_user_setting("username")
myAuthToken = get_user_setting("token")
myPrimaryImageTag = get_user_setting("primaryimagetag")
if isValid(myAuthToken) and isValid(myUsername)
print "Auth token found in registry"
session.user.Update("authToken", myAuthToken)
session.user.Update("name", myUsername)
if isValid(myPrimaryImageTag)
session.user.Update("primaryImageTag", myPrimaryImageTag)
end if
print "Attempting to use API with auth token"
currentUser = AboutMe()
if currentUser = invalid
print "Auth token is no longer valid"
'Try to login without password. If the token is valid, we're done
print "Attempting to login with no password"
userData = get_token(myUsername, "")
if isValid(userData)
print "login success!"
session.user.Login(userData, true)
LoadUserAbilities()
return true
else
print "Auth failed. Password required"
print "delete token and restart login flow"
unset_user_setting("token")
unset_user_setting("username")
if isValid(myPrimaryImageTag)
unset_user_setting("primaryimagetag")
end if
goto start_login
end if
else
print "Success! Auth token is still valid"
session.user.Login(currentUser, true)
end if
else
print "No auth token found in registry"
end if
end if
if m.global.session.user.id = invalid or m.global.session.user.authToken = invalid
print "Login failed, restart flow"
unset_setting("active_user")
session.user.Logout()
goto start_login
end if
LoadUserAbilities()
m.global.sceneManager.callFunc("clearScenes")
return true
end function
sub SaveServerList()
'Save off this server to our list of saved servers for easier navigation between servers
server = m.global.session.server.url
saved = get_setting("saved_servers")
if isValid(server)
server = LCase(server)'Saved server data is always lowercase
end if
entryCount = 0
addNewEntry = true
savedServers = { serverList: [] }
if isValid(saved)
savedServers = ParseJson(saved)
entryCount = savedServers.serverList.Count()
if isValid(savedServers.serverList) and entryCount > 0
for each item in savedServers.serverList
if item.baseUrl = server
addNewEntry = false
exit for
end if
end for
end if
end if
if addNewEntry
if entryCount = 0
set_setting("saved_servers", FormatJson({ serverList: [{ name: m.serverSelection, baseUrl: server, iconUrl: "pkg:/images/logo-icon120.jpg", iconWidth: 120, iconHeight: 120 }] }))
else
savedServers.serverList.Push({ name: m.serverSelection, baseUrl: server, iconUrl: "pkg:/images/logo-icon120.jpg", iconWidth: 120, iconHeight: 120 })
set_setting("saved_servers", FormatJson(savedServers))
end if
end if
end sub
sub DeleteFromServerList(urlToDelete)
saved = get_setting("saved_servers")
if isValid(urlToDelete)
urlToDelete = LCase(urlToDelete)
end if
if isValid(saved)
savedServers = ParseJson(saved)
newServers = { serverList: [] }
for each item in savedServers.serverList
if item.baseUrl <> urlToDelete
newServers.serverList.Push(item)
end if
end for
set_setting("saved_servers", FormatJson(newServers))
end if
end sub
' Roku Performance monitoring
sub SendPerformanceBeacon(signalName as string)
if m.global.app_loaded = false
m.scene.signalBeacon(signalName)
end if
end sub
function CreateServerGroup()
screen = CreateObject("roSGNode", "SetServerScreen")
screen.optionsAvailable = true
m.global.sceneManager.callFunc("pushScene", screen)
port = CreateObject("roMessagePort")
m.colors = {}
if isValid(m.global.session.server.url)
screen.serverUrl = m.global.session.server.url
end if
m.viewModel = {}
button = screen.findNode("submit")
button.observeField("buttonSelected", port)
'create delete saved server option
new_options = []
sidepanel = screen.findNode("options")
opt = CreateObject("roSGNode", "OptionsButton")
opt.title = tr("Delete Saved")
opt.id = "delete_saved"
opt.observeField("optionSelected", port)
new_options.push(opt)
sidepanel.options = new_options
sidepanel.observeField("closeSidePanel", port)
screen.observeField("backPressed", port)
while true
msg = wait(0, port)
print type(msg), msg
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed()
return "false"
else if isNodeEvent(msg, "backPressed")
return "backPressed"
else if isNodeEvent(msg, "closeSidePanel")
screen.setFocus(true)
serverPicker = screen.findNode("serverPicker")
serverPicker.setFocus(true)
else if type(msg) = "roSGNodeEvent"
node = msg.getNode()
if node = "submit"
m.scene.isLoading = true
serverUrl = inferServerUrl(screen.serverUrl)
isConnected = session.server.UpdateURL(serverUrl)
serverInfoResult = invalid
if isConnected
set_setting("server", serverUrl)
serverInfoResult = ServerInfo()
'If this is a different server from what we know, reset username/password setting
if m.global.session.server.url <> serverUrl
set_setting("username", "")
set_setting("password", "")
end if
set_setting("server", serverUrl)
end if
m.scene.isLoading = false
if isConnected = false or serverInfoResult = invalid
' Maybe don't unset setting, but offer as a prompt
' Server not found, is it online? New values / Retry
print "Server not found, is it online? New values / Retry"
screen.errorMessage = tr("Server not found, is it online?")
SignOut(false)
else
if isValid(serverInfoResult.Error) and serverInfoResult.Error
' If server redirected received, update the URL
if isValid(serverInfoResult.UpdatedUrl)
serverUrl = serverInfoResult.UpdatedUrl
isConnected = session.server.UpdateURL(serverUrl)
if isConnected
set_setting("server", serverUrl)
screen.visible = false
return ""
end if
end if
' Display Error Message to user
message = tr("Error: ")
if isValid(serverInfoResult.ErrorCode)
message = message + "[" + serverInfoResult.ErrorCode.toStr() + "] "
end if
screen.errorMessage = message + tr(serverInfoResult.ErrorMessage)
SignOut(false)
else
screen.visible = false
if isValid(serverInfoResult.serverName)
return serverInfoResult.ServerName + " (Saved)"
else
return "Saved"
end if
end if
end if
else if node = "delete_saved"
serverPicker = screen.findNode("serverPicker")
itemToDelete = serverPicker.content.getChild(serverPicker.itemFocused)
urlToDelete = itemToDelete.baseUrl
if isValid(urlToDelete)
DeleteFromServerList(urlToDelete)
serverPicker.content.removeChild(itemToDelete)
sidepanel.visible = false
serverPicker.setFocus(true)
end if
end if
end if
end while
' Just hide it when done, in case we need to come back
screen.visible = false
return ""
end function
function CreateUserSelectGroup(users = [])
if users.count() = 0
return ""
end if
group = CreateObject("roSGNode", "UserSelect")
m.global.sceneManager.callFunc("pushScene", group)
port = CreateObject("roMessagePort")
group.itemContent = users
group.findNode("userRow").observeField("userSelected", port)
group.findNode("alternateOptions").observeField("itemSelected", port)
group.observeField("backPressed", port)
while true
msg = wait(0, port)
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed()
group.visible = false
return -1
else if isNodeEvent(msg, "backPressed")
return "backPressed"
else if type(msg) = "roSGNodeEvent" and msg.getField() = "userSelected"
return msg.GetData()
else if type(msg) = "roSGNodeEvent" and msg.getField() = "itemSelected"
if msg.getData() = 0
return ""
end if
end if
end while
' Just hide it when done, in case we need to come back
group.visible = false
return ""
end function
function CreateSigninGroup(user = "")
' Get and Save Jellyfin user login credentials
group = CreateObject("roSGNode", "LoginScene")
m.global.sceneManager.callFunc("pushScene", group)
port = CreateObject("roMessagePort")
group.findNode("prompt").text = tr("Sign In")
config = group.findNode("configOptions")
username_field = CreateObject("roSGNode", "ConfigData")
username_field.label = tr("Username")
username_field.field = "username"
username_field.type = "string"
if user = "" and get_setting("username") <> invalid
username_field.value = get_setting("username")
else
username_field.value = user
end if
password_field = CreateObject("roSGNode", "ConfigData")
password_field.label = tr("Password")
password_field.field = "password"
password_field.type = "password"
registryPassword = get_setting("password")
if isValid(registryPassword)
password_field.value = registryPassword
end if
' Add checkbox for saving credentials
checkbox = group.findNode("onOff")
items = CreateObject("roSGNode", "ContentNode")
items.role = "content"
saveCheckBox = CreateObject("roSGNode", "ContentNode")
saveCheckBox.title = tr("Save Credentials?")
items.appendChild(saveCheckBox)
checkbox.content = items
checkbox.checkedState = [true]
quickConnect = group.findNode("quickConnect")
' Quick Connect only supported for server version 10.8+ right now...
if versionChecker(m.global.session.server.version, "10.8.0")
' Add option for Quick Connect
quickConnect.text = tr("Quick Connect")
quickConnect.observeField("buttonSelected", port)
else
quickConnect.visible = false
end if
items = [username_field, password_field]
config.configItems = items
button = group.findNode("submit")
button.observeField("buttonSelected", port)
config = group.findNode("configOptions")
username = config.content.getChild(0)
password = config.content.getChild(1)
group.observeField("backPressed", port)
while true
msg = wait(0, port)
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed()
group.visible = false
return "false"
else if isNodeEvent(msg, "backPressed")
group.unobserveField("backPressed")
group.backPressed = false
return "backPressed"
else if type(msg) = "roSGNodeEvent"
node = msg.getNode()
if node = "submit"
startLoadingSpinner()
' Validate credentials
activeUser = get_token(username.value, password.value)
if isValid(activeUser)
print "activeUser=", activeUser
if checkbox.checkedState[0] = true
' save credentials
session.user.Login(activeUser, true)
set_user_setting("token", activeUser.token)
set_user_setting("username", username.value)
if isValid(activeUser.json.PrimaryImageTag)
set_user_setting("primaryimagetag", activeUser.json.PrimaryImageTag)
end if
else
session.user.Login(activeUser)
end if
return "true"
end if
stopLoadingSpinner()
print "Login attempt failed..."
group.findNode("alert").text = tr("Login attempt failed.")
else if node = "quickConnect"
json = initQuickConnect()
if json = invalid
group.findNode("alert").text = tr("Quick Connect not available.")
else
' Server user is talking to is at least 10.8 and has quick connect enabled...
m.quickConnectDialog = createObject("roSGNode", "QuickConnectDialog")
m.quickConnectDialog.saveCredentials = checkbox.checkedState[0]
m.quickConnectDialog.quickConnectJson = json
m.quickConnectDialog.title = tr("Quick Connect")
m.quickConnectDialog.message = [tr("Here is your Quick Connect code: ") + json.Code, tr("(Dialog will close automatically)")]
m.quickConnectDialog.buttons = [tr("Cancel")]
m.quickConnectDialog.observeField("authenticated", port)
m.scene.dialog = m.quickConnectDialog
end if
else if msg.getField() = "authenticated"
authenticated = msg.getData()
if authenticated = true
' Quick connect authentication was successful...
return "true"
else
dialog = createObject("roSGNode", "Dialog")
dialog.id = "QuickConnectError"
dialog.title = tr("Quick Connect")
dialog.buttons = [tr("OK")]
dialog.message = tr("There was an error authenticating via Quick Connect.")
m.scene.dialog = dialog
m.scene.dialog.observeField("buttonSelected", port)
end if
else
' If there are no other button matches, check if this is a simple "OK" Dialog & Close if so
dialog = msg.getRoSGNode()
if dialog.id = "QuickConnectError"
dialog.unobserveField("buttonSelected")
dialog.close = true
end if
end if
end if
end while
' Just hide it when done, in case we need to come back
group.visible = false
return ""
end function
function CreateHomeGroup()
' Main screen after logging in. Shows the user's libraries
group = CreateObject("roSGNode", "Home")
group.overhangTitle = tr("Home")
group.optionsAvailable = true
group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port)
sidepanel = group.findNode("options")
sidepanel.observeField("closeSidePanel", m.port)
new_options = []
options_buttons = [
{ "title": "Search", "id": "goto_search" },
{ "title": "Change user", "id": "change_user" },
{ "title": "Change server", "id": "change_server" },
{ "title": "Sign out", "id": "sign_out" }
]
for each opt in options_buttons
o = CreateObject("roSGNode", "OptionsButton")
o.title = tr(opt.title)
o.id = opt.id
o.observeField("optionSelected", m.port)
new_options.push(o)
end for
' Add settings option to menu
o = CreateObject("roSGNode", "OptionsButton")
o.title = "Settings"
o.id = "settings"
o.observeField("optionSelected", m.port)
new_options.push(o)
' And a profile button
user_node = CreateObject("roSGNode", "OptionsData")
user_node.id = "active_user"
user_node.title = tr("Profile")
user_node.base_title = tr("Profile")
user_options = []
for each user in AvailableUsers()
user_options.push({ display: user.username + "@" + user.server, value: user.id })
end for
user_node.choices = user_options
user_node.value = m.global.session.user.id
new_options.push(user_node)
sidepanel.options = new_options
return group
end function
function CreateMovieDetailsGroup(movie as object) as dynamic
' validate movie node
if not isValid(movie) or not isValid(movie.id) then return invalid
startLoadingSpinner()
' get movie meta data
movieMetaData = ItemMetaData(movie.id)
' validate movie meta data
if not isValid(movieMetaData)
stopLoadingSpinner()
return invalid
end if
' start building MovieDetails view
group = CreateObject("roSGNode", "MovieDetails")
group.observeField("quickPlayNode", m.port)
group.observeField("refreshMovieDetailsData", m.port)
group.overhangTitle = movie.title
group.optionsAvailable = false
group.trailerAvailable = false
' push scene asap (to prevent extra button presses when retriving series/movie info)
m.global.sceneManager.callFunc("pushScene", group)
group.itemContent = movieMetaData
' local trailers
trailerData = api.users.GetLocalTrailers(m.global.session.user.id, movie.id)
if isValid(trailerData)
group.trailerAvailable = trailerData.Count() > 0
end if
' watch for button presses
buttons = group.findNode("buttons")
for each b in buttons.getChildren(-1, 0)
b.observeField("buttonSelected", m.port)
end for
' setup and load movie extras
extras = group.findNode("extrasGrid")
extras.observeField("selectedItem", m.port)
extras.callFunc("loadParts", movieMetaData.json)
' done building MovieDetails view
stopLoadingSpinner()
return group
end function
function CreateSeriesDetailsGroup(seriesID as string) as dynamic
' validate series node
if not isValid(seriesID) or seriesID = "" then return invalid
startLoadingSpinner()
' get series meta data
seriesMetaData = ItemMetaData(seriesID)
' validate series meta data
if not isValid(seriesMetaData)
stopLoadingSpinner()
return invalid
end if
' Get season data early in the function so we can check number of seasons.
seasonData = TVSeasons(seriesID)
' 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, "")
end if
' start building SeriesDetails view
group = CreateObject("roSGNode", "TVShowDetails")
group.optionsAvailable = false
' push scene asap (to prevent extra button presses when retriving series/movie info)
m.global.sceneManager.callFunc("pushScene", group)
group.itemContent = seriesMetaData
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)
extras.callFunc("loadParts", seriesMetaData.json)
' done building SeriesDetails view
stopLoadingSpinner()
return group
end function
' Shows details on selected artist. Bio, image, and list of available albums
function CreateArtistView(artist as object) as dynamic
' validate artist node
if not isValid(artist) or not isValid(artist.id) then return invalid
musicData = MusicAlbumList(artist.id)
appearsOnData = AppearsOnList(artist.id)
if (musicData = invalid or musicData.Items.Count() = 0) and (appearsOnData = invalid or appearsOnData.Items.Count() = 0)
' Just songs under artists...
group = CreateObject("roSGNode", "AlbumView")
group.pageContent = ItemMetaData(artist.id)
' Lookup songs based on artist id
songList = GetSongsByArtist(artist.id)
if not isValid(songList)
' Lookup songs based on folder parent / child relationship
songList = MusicSongList(artist.id)
end if
if not isValid(songList)
return invalid
end if
group.albumData = songList
group.observeField("playSong", m.port)
group.observeField("playAllSelected", m.port)
group.observeField("instantMixSelected", m.port)
else
' User has albums under artists
group = CreateObject("roSGNode", "ArtistView")
group.pageContent = ItemMetaData(artist.id)
group.musicArtistAlbumData = musicData
group.musicArtistAppearsOnData = appearsOnData
group.artistOverview = ArtistOverview(artist.name)
group.observeField("musicAlbumSelected", m.port)
group.observeField("playArtistSelected", m.port)
group.observeField("instantMixSelected", m.port)
group.observeField("appearsOnSelected", m.port)
end if
group.observeField("quickPlayNode", m.port)
m.global.sceneManager.callFunc("pushScene", group)
return group
end function
' Shows details on selected album. Description text, image, and list of available songs
function CreateAlbumView(album as object) as dynamic
' validate album node
if not isValid(album) or not isValid(album.id) then return invalid
group = CreateObject("roSGNode", "AlbumView")
m.global.sceneManager.callFunc("pushScene", group)
group.pageContent = ItemMetaData(album.id)
group.albumData = MusicSongList(album.id)
' Watch for user clicking on a song
group.observeField("playSong", m.port)
' Watch for user click on Play button on album
group.observeField("playAllSelected", m.port)
' Watch for user click on Instant Mix button on album
group.observeField("instantMixSelected", m.port)
return group
end function
' Shows details on selected playlist. Description text, image, and list of available items
function CreatePlaylistView(playlist as object) as dynamic
' validate playlist node
if not isValid(playlist) or not isValid(playlist.id) then return invalid
group = CreateObject("roSGNode", "PlaylistView")
m.global.sceneManager.callFunc("pushScene", group)
group.pageContent = ItemMetaData(playlist.id)
group.albumData = PlaylistItemList(playlist.id)
' Watch for user clicking on an item
group.observeField("playItem", m.port)
' Watch for user click on Play button
group.observeField("playAllSelected", m.port)
return group
end function
function CreateSeasonDetailsGroup(series as object, season as object) as dynamic
' validate series node
if not isValid(series) or not isValid(series.id) then return invalid
' validate season node
if not isValid(season) or not isValid(season.id) then return invalid
startLoadingSpinner()
' get season meta data
seasonMetaData = ItemMetaData(season.id)
' validate season meta data
if not isValid(seasonMetaData)
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)
m.global.sceneManager.callFunc("pushScene", group)
group.seasonData = seasonMetaData.json
group.objects = TVEpisodes(series.id, season.id)
group.episodeObjects = group.objects
group.extrasObjects = TVSeasonExtras(season.id)
group.observeField("refreshSeasonDetailsData", m.port)
' watch for button presses
group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port)
' finished building SeasonDetails view
stopLoadingSpinner()
return group
end function
function CreateSeasonDetailsGroupByID(seriesID as string, seasonID as string, selectId as string) as dynamic
' validate parameters
if seriesID = "" or seasonID = "" then return invalid
startLoadingSpinner()
' get season meta data
seasonMetaData = ItemMetaData(seasonID)
' validate season meta data
if not isValid(seasonMetaData)
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)
group.episodeObjects = group.objects
' watch for button presses
group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port)
' don't wait for the extras button
stopLoadingSpinner()
m.global.sceneManager.callFunc("pushScene", group)
' 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
function CreateItemGrid(libraryItem as object) as dynamic
' validate libraryItem
if not isValid(libraryItem) then return invalid
group = CreateObject("roSGNode", "ItemGrid")
group.parentItem = libraryItem
group.optionsAvailable = true
group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port)
return group
end function
function CreateMovieLibraryView(libraryItem as object) as dynamic
' validate libraryItem
if not isValid(libraryItem) then return invalid
group = CreateObject("roSGNode", "MovieLibraryView")
group.parentItem = libraryItem
group.optionsAvailable = true
group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port)
return group
end function
function CreateMusicLibraryView(libraryItem as object) as dynamic
' validate libraryItem
if not isValid(libraryItem) then return invalid
group = CreateObject("roSGNode", "MusicLibraryView")
group.parentItem = libraryItem
group.optionsAvailable = true
group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port)
return group
end function
function CreateSearchPage()
' Search + Results Page
group = CreateObject("roSGNode", "searchResults")
group.observeField("quickPlayNode", m.port)
options = group.findNode("searchSelect")
options.observeField("itemSelected", m.port)
return group
end function
function CreateVideoPlayerGroup(video_id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean, showIntro = true as boolean, allowResumeDialog = true as boolean)
' validate video_id
if not isValid(video_id) or video_id = "" then return invalid
startLoadingSpinner()
' Video is Playing
video = VideoPlayer(video_id, mediaSourceId, audio_stream_idx, defaultSubtitleTrackFromVid(video_id), forceTranscoding, showIntro, allowResumeDialog)
if video = invalid then return invalid
video.allowCaptions = true
if video.errorMsg = "introaborted" then return video
video.observeField("selectSubtitlePressed", m.port)
video.observeField("selectPlaybackInfoPressed", m.port)
video.observeField("state", m.port)
stopLoadingSpinner()
return video
end function
function CreatePersonView(personData as object) as dynamic
' validate personData node
if not isValid(personData) or not isValid(personData.id) then return invalid
startLoadingSpinner()
' get person meta data
personMetaData = ItemMetaData(personData.id)
' validate season meta data
if not isValid(personMetaData)
stopLoadingSpinner()
return invalid
end if
' start building Person View
person = CreateObject("roSGNode", "PersonDetails")
' push scene asap (to prevent extra button presses when retriving series/movie info)
m.global.SceneManager.callFunc("pushScene", person)
person.itemContent = personMetaData
person.setFocus(true)
' watch for button presses
person.observeField("selectedItem", m.port)
person.findNode("favorite-button").observeField("buttonSelected", m.port)
' finished building Person View
stopLoadingSpinner()
return person
end function
'Opens dialog asking user if they want to resume video or start playback over only on the home screen
sub playbackOptionDialog(time as longinteger, meta as object)
resumeData = [
tr("Resume playing at ") + ticksToHuman(time) + ".",
tr("Start over from the beginning.")
]
group = m.global.sceneManager.callFunc("getActiveScene")
if LCase(group.subtype()) = "home"
if LCase(meta.type) = "episode"
resumeData.push(tr("Go to series"))
resumeData.push(tr("Go to season"))
resumeData.push(tr("Go to episode"))
end if
end if
stopLoadingSpinner()
m.global.sceneManager.callFunc("optionDialog", tr("Playback Options"), [], resumeData)
end sub