jf-roku/source/ShowScenes.bs

942 lines
35 KiB
Plaintext
Raw Normal View History

function LoginFlow()
2023-04-22 13:03:44 +00:00
'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
2023-04-22 13:03:44 +00:00
invalidServer = true
if not startOver
' Show Connecting to Server spinner
dialog = createObject("roSGNode", "ProgressDialog")
dialog.title = tr("Connecting to Server")
m.scene.dialog = dialog
invalidServer = ServerInfo().Error
dialog.close = true
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:
2023-04-22 13:03:44 +00:00
SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting
2023-04-22 13:03:44 +00:00
publicUsers = GetPublicUsers()
savedUsers = getSavedUsers()
numPubUsers = publicUsers.count()
numSavedUsers = savedUsers.count()
if numPubUsers > 0 or numSavedUsers > 0
2023-04-22 13:03:44 +00:00
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
publicUsersNodes.push(user)
end if
end if
end for
end if
' push all users to the user select view
2023-04-22 13:03:44 +00:00
userSelected = CreateUserSelectGroup(publicUsersNodes)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
2023-04-22 13:03:44 +00:00
if userSelected = "backPressed"
session.server.Delete()
unset_setting("server")
goto start_login
else if userSelected <> ""
startMediaLoadingSpinner()
print "A public user was selected with username=" + userSelected
session.user.Update("name", userSelected)
regex = CreateObject("roRegex", "[^a-zA-Z0-9\ \-\_]", "")
session.user.Update("friendlyName", regex.ReplaceAll(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")
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")
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
2023-04-22 13:03:44 +00:00
'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()
2023-04-22 13:03:44 +00:00
return true
else
print "Auth failed. Password required"
2023-04-22 13:03:44 +00:00
end if
end if
else
userSelected = ""
end if
stopLoadingSpinner()
2023-04-22 13:03:44 +00:00
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
2023-04-22 13:03:44 +00:00
end if
else
print "Active user found in registry"
session.user.Update("id", activeUser)
myUsername = get_user_setting("username")
myAuthToken = get_user_setting("token")
if isValid(myAuthToken) and isValid(myUsername)
print "Auth token found in registry"
session.user.Update("authToken", myAuthToken)
session.user.Update("name", myUsername)
regex = CreateObject("roRegex", "[^a-zA-Z0-9\ \-\_]", "")
session.user.Update("friendlyName", regex.ReplaceAll(myUsername, ""))
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"
2023-10-26 15:27:45 +00:00
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")
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
2023-04-22 13:03:44 +00:00
end if
if m.global.session.user.id = invalid or m.global.session.user.authToken = invalid
2023-04-22 13:03:44 +00:00
print "Login failed, restart flow"
unset_setting("active_user")
session.user.Logout()
2023-04-22 13:03:44 +00:00
goto start_login
end if
LoadUserAbilities()
2023-04-22 13:03:44 +00:00
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
2023-04-22 13:03:44 +00:00
saved = get_setting("saved_servers")
if isValid(server)
2023-04-22 13:03:44 +00:00
server = LCase(server)'Saved server data is always lowercase
end if
entryCount = 0
addNewEntry = true
savedServers = { serverList: [] }
if isValid(saved)
2023-04-22 13:03:44 +00:00
savedServers = ParseJson(saved)
entryCount = savedServers.serverList.Count()
if isValid(savedServers.serverList) and entryCount > 0
2023-04-22 13:03:44 +00:00
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)
2023-04-22 13:03:44 +00:00
urlToDelete = LCase(urlToDelete)
end if
if isValid(saved)
2023-04-22 13:03:44 +00:00
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()
2021-07-09 20:08:32 +00:00
screen = CreateObject("roSGNode", "SetServerScreen")
2021-12-23 01:00:47 +00:00
screen.optionsAvailable = true
m.global.sceneManager.callFunc("pushScene", screen)
2021-07-09 20:08:32 +00:00
port = CreateObject("roMessagePort")
m.colors = {}
if isValid(m.global.session.server.url)
screen.serverUrl = m.global.session.server.url
end if
2021-07-09 20:08:32 +00:00
m.viewModel = {}
button = screen.findNode("submit")
button.observeField("buttonSelected", port)
2021-12-23 01:00:47 +00:00
'create delete saved server option
new_options = []
sidepanel = screen.findNode("options")
opt = CreateObject("roSGNode", "OptionsButton")
opt.title = tr("Delete Saved")
2021-12-24 04:08:43 +00:00
opt.id = "delete_saved"
2021-12-23 01:00:47 +00:00
opt.observeField("optionSelected", port)
new_options.push(opt)
sidepanel.options = new_options
sidepanel.observeField("closeSidePanel", port)
2021-12-24 04:08:43 +00:00
2021-07-09 20:08:32 +00:00
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"
2021-12-23 01:00:47 +00:00
else if isNodeEvent(msg, "closeSidePanel")
screen.setFocus(true)
serverPicker = screen.findNode("serverPicker")
serverPicker.setFocus(true)
2021-07-09 20:08:32 +00:00
else if type(msg) = "roSGNodeEvent"
node = msg.getNode()
if node = "submit"
' Show Connecting to Server spinner
dialog = createObject("roSGNode", "ProgressDialog")
dialog.title = tr("Connecting to Server")
m.scene.dialog = dialog
serverUrl = inferServerUrl(screen.serverUrl)
2021-07-09 20:08:32 +00:00
isConnected = session.server.UpdateURL(serverUrl)
serverInfoResult = invalid
if isConnected
set_setting("server", serverUrl)
serverInfoResult = ServerInfo()
2023-09-11 02:09:16 +00:00
'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
2021-07-09 20:08:32 +00:00
dialog.close = true
if isConnected = false or serverInfoResult = invalid
2021-07-09 20:08:32 +00:00
' 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?")
2021-12-30 01:00:13 +00:00
SignOut(false)
2021-07-09 20:08:32 +00:00
else
2023-09-11 02:16:28 +00:00
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)
2021-12-26 18:52:43 +00:00
else
screen.visible = false
if isValid(serverInfoResult.serverName)
return serverInfoResult.ServerName + " (Saved)"
else
return "Saved"
end if
2021-12-26 18:52:43 +00:00
end if
2021-07-09 20:08:32 +00:00
end if
2021-12-23 01:00:47 +00:00
else if node = "delete_saved"
serverPicker = screen.findNode("serverPicker")
itemToDelete = serverPicker.content.getChild(serverPicker.itemFocused)
urlToDelete = itemToDelete.baseUrl
if isValid(urlToDelete)
2021-12-23 01:00:47 +00:00
DeleteFromServerList(urlToDelete)
serverPicker.content.removeChild(itemToDelete)
sidepanel.visible = false
serverPicker.setFocus(true)
2021-12-24 04:08:43 +00:00
end if
2021-07-09 20:08:32 +00:00
end if
end if
end while
2019-10-13 19:33:14 +00:00
2021-07-09 20:08:32 +00:00
' Just hide it when done, in case we need to come back
screen.visible = false
return ""
end function
function CreateUserSelectGroup(users = [])
2021-07-09 20:08:32 +00:00
if users.count() = 0
return ""
end if
2021-07-09 20:08:32 +00:00
group = CreateObject("roSGNode", "UserSelect")
m.global.sceneManager.callFunc("pushScene", group)
2021-07-09 20:08:32 +00:00
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
2021-07-09 20:08:32 +00:00
' Just hide it when done, in case we need to come back
group.visible = false
return ""
end function
function CreateSigninGroup(user = "")
2021-07-09 20:08:32 +00:00
' Get and Save Jellyfin user login credentials
2022-05-29 20:00:38 +00:00
group = CreateObject("roSGNode", "LoginScene")
m.global.sceneManager.callFunc("pushScene", group)
2021-07-09 20:08:32 +00:00
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
2021-07-09 20:08:32 +00:00
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
2021-07-09 20:08:32 +00:00
end if
2021-12-30 03:51:39 +00:00
' Add checkbox for saving credentials
2021-12-30 03:55:02 +00:00
checkbox = group.findNode("onOff")
2021-12-30 03:51:39 +00:00
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]
2022-05-29 20:00:38 +00:00
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
2021-12-30 03:51:39 +00:00
2021-07-09 20:08:32 +00:00
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"
startMediaLoadingSpinner()
2021-07-09 20:08:32 +00:00
' Validate credentials
activeUser = get_token(username.value, password.value)
if isValid(activeUser)
print "activeUser=", activeUser
2021-12-30 03:51:39 +00:00
if checkbox.checkedState[0] = true
' save credentials
session.user.Login(activeUser, true)
set_user_setting("token", activeUser.token)
set_user_setting("username", username.value)
else
session.user.Login(activeUser)
2021-12-30 03:51:39 +00:00
end if
2021-07-09 20:08:32 +00:00
return "true"
end if
stopLoadingSpinner()
2021-07-09 20:08:32 +00:00
print "Login attempt failed..."
group.findNode("alert").text = tr("Login attempt failed.")
2022-05-29 20:00:38 +00:00
else if node = "quickConnect"
json = initQuickConnect()
if json = invalid
group.findNode("alert").text = tr("Quick Connect not available.")
2022-05-30 05:00:43 +00:00
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]
2022-05-30 05:00:43 +00:00
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
2022-05-29 20:00:38 +00:00
end if
else if msg.getField() = "authenticated"
2022-05-30 12:12:43 +00:00
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
2022-05-30 12:13:53 +00:00
end if
2021-07-09 20:08:32 +00:00
end if
end if
end while
2019-10-13 19:33:14 +00:00
2021-07-09 20:08:32 +00:00
' Just hide it when done, in case we need to come back
group.visible = false
return ""
end function
function CreateHomeGroup()
2021-07-09 20:08:32 +00:00
' Main screen after logging in. Shows the user's libraries
group = CreateObject("roSGNode", "Home")
group.overhangTitle = tr("Home")
group.optionsAvailable = true
2021-07-09 20:08:32 +00:00
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" },
2021-07-09 20:08:32 +00:00
{ "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
2022-05-01 10:51:28 +00:00
o = CreateObject("roSGNode", "OptionsButton")
o.title = "Settings"
o.id = "settings"
o.observeField("optionSelected", m.port)
new_options.push(o)
2021-07-09 20:08:32 +00:00
' 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
2021-07-09 20:08:32 +00:00
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
2023-02-04 06:39:09 +00:00
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
2021-07-09 20:08:32 +00:00
group = CreateObject("roSGNode", "MovieDetails")
group.observeField("quickPlayNode", 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)
2022-09-03 07:31:15 +00:00
if isValid(trailerData)
group.trailerAvailable = trailerData.Count() > 0
end if
' watch for button presses
2021-07-09 20:08:32 +00:00
buttons = group.findNode("buttons")
for each b in buttons.getChildren(-1, 0)
b.observeField("buttonSelected", m.port)
end for
' setup and load movie extras
2022-03-13 08:46:03 +00:00
extras = group.findNode("extrasGrid")
extras.observeField("selectedItem", m.port)
extras.callFunc("loadParts", movieMetaData.json)
' done building MovieDetails view
2023-02-04 06:39:09 +00:00
stopLoadingSpinner()
2021-07-09 20:08:32 +00:00
return group
end function
2019-03-08 03:47:10 +00:00
function CreateSeriesDetailsGroup(seriesID as string) as dynamic
' validate series node
if not isValid(seriesID) or seriesID = "" then return invalid
2023-02-04 06:39:09 +00:00
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 m.global.session.user.settings["ui.tvshows.goStraightToEpisodeListing"] = true and seasonData.Items.Count() = 1
2023-02-04 06:39:09 +00:00
stopLoadingSpinner()
return CreateSeasonDetailsGroupByID(seriesID, seasonData.Items[0].id)
end if
' start building SeriesDetails view
2021-07-09 20:08:32 +00:00
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
2021-07-09 20:08:32 +00:00
group.observeField("seasonSelected", m.port)
group.observeField("quickPlayNode", m.port)
' setup and load series extras
2022-03-13 08:46:03 +00:00
extras = group.findNode("extrasGrid")
extras.observeField("selectedItem", m.port)
extras.callFunc("loadParts", seriesMetaData.json)
' done building SeriesDetails view
2023-02-04 06:39:09 +00:00
stopLoadingSpinner()
2021-07-09 20:08:32 +00:00
return group
end function
2019-04-14 04:47:27 +00:00
' 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)
2022-09-27 01:26:17 +00:00
if (musicData = invalid or musicData.Items.Count() = 0) and (appearsOnData = invalid or appearsOnData.Items.Count() = 0)
' Just songs under artists...
2022-07-19 00:42:22 +00:00
group = CreateObject("roSGNode", "AlbumView")
group.pageContent = ItemMetaData(artist.id)
2022-10-02 18:23:42 +00:00
' Lookup songs based on artist id
songList = GetSongsByArtist(artist.id)
2022-10-02 18:23:42 +00:00
if not isValid(songList)
' Lookup songs based on folder parent / child relationship
songList = MusicSongList(artist.id)
2022-10-02 18:23:42 +00:00
end if
if not isValid(songList)
return invalid
end if
group.albumData = songList
2022-05-21 20:45:01 +00:00
group.observeField("playSong", m.port)
group.observeField("playAllSelected", m.port)
2022-06-08 13:08:05 +00:00
group.observeField("instantMixSelected", m.port)
else
' User has albums under artists
2022-07-19 00:42:22 +00:00
group = CreateObject("roSGNode", "ArtistView")
group.pageContent = ItemMetaData(artist.id)
2022-05-15 12:30:55 +00:00
group.musicArtistAlbumData = musicData
2022-09-27 01:26:17 +00:00
group.musicArtistAppearsOnData = appearsOnData
group.artistOverview = ArtistOverview(artist.name)
2022-09-27 01:26:17 +00:00
group.observeField("musicAlbumSelected", m.port)
2022-07-19 02:28:06 +00:00
group.observeField("playArtistSelected", m.port)
group.observeField("instantMixSelected", m.port)
2022-09-27 01:26:17 +00:00
group.observeField("appearsOnSelected", m.port)
end if
2022-05-14 02:35:50 +00:00
group.observeField("quickPlayNode", m.port)
m.global.sceneManager.callFunc("pushScene", group)
2022-05-14 03:46:05 +00:00
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
2022-07-19 00:42:22 +00:00
group = CreateObject("roSGNode", "AlbumView")
2022-05-14 03:46:05 +00:00
m.global.sceneManager.callFunc("pushScene", group)
2022-05-21 20:45:01 +00:00
group.pageContent = ItemMetaData(album.id)
group.albumData = MusicSongList(album.id)
2022-05-14 03:46:05 +00:00
2022-05-15 02:30:29 +00:00
' Watch for user clicking on a song
2022-05-21 20:45:01 +00:00
group.observeField("playSong", m.port)
2022-05-14 02:35:50 +00:00
2022-05-15 19:10:21 +00:00
' Watch for user click on Play button on album
group.observeField("playAllSelected", m.port)
2022-06-08 13:08:05 +00:00
' Watch for user click on Instant Mix button on album
group.observeField("instantMixSelected", m.port)
2022-05-14 02:35:50 +00:00
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
2023-02-04 06:39:09 +00:00
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
2021-07-09 20:08:32 +00:00
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
2021-07-09 20:08:32 +00:00
group.objects = TVEpisodes(series.id, season.id)
group.episodeObjects = group.objects
group.extrasObjects = TVSeasonExtras(season.id)
' watch for button presses
2021-07-09 20:08:32 +00:00
group.observeField("episodeSelected", m.port)
group.observeField("quickPlayNode", m.port)
' finished building SeasonDetails view
stopLoadingSpinner()
2021-07-09 20:08:32 +00:00
return group
end function
function CreateSeasonDetailsGroupByID(seriesID as string, seasonID as string) as dynamic
' validate parameters
if seriesID = "" or seasonID = "" then return invalid
2023-02-04 06:39:09 +00:00
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
2022-11-05 00:37:54 +00:00
group = CreateObject("roSGNode", "TVEpisodes")
group.optionsAvailable = false
' push scene asap (to prevent extra button presses when retriving series/movie info)
2022-11-05 00:37:54 +00:00
m.global.sceneManager.callFunc("pushScene", group)
group.seasonData = seasonMetaData.json
2022-11-05 00:37:54 +00:00
group.objects = TVEpisodes(seriesID, seasonID)
group.episodeObjects = group.objects
group.extrasObjects = TVSeasonExtras(seasonID)
' watch for button presses
2022-11-05 00:37:54 +00:00
group.observeField("episodeSelected", m.port)
group.observeField("quickPlayNode", m.port)
' finished building SeasonDetails view
2023-02-04 06:39:09 +00:00
stopLoadingSpinner()
2022-11-05 00:37:54 +00:00
return group
end function
function CreateItemGrid(libraryItem as object) as dynamic
' validate libraryItem
if not isValid(libraryItem) then return invalid
2021-07-09 20:08:32 +00:00
group = CreateObject("roSGNode", "ItemGrid")
group.parentItem = libraryItem
group.optionsAvailable = true
2021-07-09 20:08:32 +00:00
group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port)
2021-07-09 20:08:32 +00:00
return group
end function
function CreateMovieLibraryView(libraryItem as object) as dynamic
' validate libraryItem
if not isValid(libraryItem) then return invalid
2022-09-24 00:16:52 +00:00
group = CreateObject("roSGNode", "MovieLibraryView")
group.parentItem = libraryItem
group.optionsAvailable = true
2021-07-09 20:08:32 +00:00
group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port)
2021-07-09 20:08:32 +00:00
return group
end function
function CreateMusicLibraryView(libraryItem as object) as dynamic
' validate libraryItem
if not isValid(libraryItem) then return invalid
2022-12-10 05:06:56 +00:00
group = CreateObject("roSGNode", "MusicLibraryView")
group.parentItem = libraryItem
group.optionsAvailable = true
group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port)
2021-07-09 20:08:32 +00:00
return group
end function
2019-10-13 20:52:34 +00:00
function CreateSearchPage()
2021-07-09 20:08:32 +00:00
' Search + Results Page
group = CreateObject("roSGNode", "searchResults")
group.observeField("quickPlayNode", m.port)
options = group.findNode("searchSelect")
2021-07-09 20:08:32 +00:00
options.observeField("itemSelected", m.port)
2019-03-14 22:50:20 +00:00
2021-07-09 20:08:32 +00:00
return group
2019-10-13 20:52:34 +00:00
end function
2019-03-14 22:50:20 +00:00
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
2019-10-13 22:10:23 +00:00
2023-02-04 06:39:09 +00:00
startMediaLoadingSpinner()
2021-07-09 20:08:32 +00:00
' Video is Playing
video = VideoPlayer(video_id, mediaSourceId, audio_stream_idx, defaultSubtitleTrackFromVid(video_id), forceTranscoding, showIntro, allowResumeDialog)
2021-07-09 20:08:32 +00:00
if video = invalid then return invalid
video.allowCaptions = true
2022-07-09 08:28:15 +00:00
if video.errorMsg = "introaborted" then return video
2021-07-09 20:08:32 +00:00
video.observeField("selectSubtitlePressed", m.port)
2022-09-06 04:38:37 +00:00
video.observeField("selectPlaybackInfoPressed", m.port)
2021-07-09 20:08:32 +00:00
video.observeField("state", m.port)
2023-02-04 06:39:09 +00:00
stopLoadingSpinner()
2021-07-09 20:08:32 +00:00
return video
end function
2021-12-24 04:07:35 +00:00
function CreatePersonView(personData as object) as dynamic
' validate personData node
if not isValid(personData) or not isValid(personData.id) then return invalid
2023-02-04 06:39:09 +00:00
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
2022-03-13 08:46:03 +00:00
person = CreateObject("roSGNode", "PersonDetails")
' push scene asap (to prevent extra button presses when retriving series/movie info)
2022-03-13 08:46:03 +00:00
m.global.SceneManager.callFunc("pushScene", person)
person.itemContent = personMetaData
2022-03-13 08:46:03 +00:00
person.setFocus(true)
' watch for button presses
2022-03-13 08:46:03 +00:00
person.observeField("selectedItem", m.port)
person.findNode("favorite-button").observeField("buttonSelected", m.port)
' finished building Person View
stopLoadingSpinner()
2022-03-13 08:46:03 +00:00
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
m.global.sceneManager.callFunc("optionDialog", tr("Playback Options"), [], resumeData)
end sub