From bdfcac74e6e0fac304d9b0b9623f5a9c309aa36c Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Thu, 1 Jun 2023 08:43:27 -0400 Subject: [PATCH] Implement global session var (#1224) Co-authored-by: Jimi --- components/GetNextEpisodeTask.brs | 2 +- components/GetPlaybackInfoTask.brs | 20 +- components/GetShuffleEpisodesTask.brs | 2 +- components/ItemGrid/GridItem.brs | 4 +- components/ItemGrid/ItemGrid.brs | 34 +-- components/ItemGrid/LoadItemsTask2.brs | 18 +- components/ItemGrid/LoadVideoContentTask.brs | 10 +- components/ItemGrid/MovieLibraryView.brs | 28 +- components/ItemGrid/MusicArtistGridItem.brs | 2 +- components/ItemGrid/MusicLibraryView.brs | 22 +- components/JFOverhang.brs | 2 +- components/JFVideo.brs | 13 +- components/ListPoster.brs | 4 +- components/data/UserData.brs | 6 +- components/home/Home.brs | 2 +- components/home/HomeItem.brs | 2 +- components/home/HomeRows.brs | 9 +- components/home/LoadItemsTask.brs | 21 +- components/liveTv/LoadChannelsTask.brs | 4 +- components/liveTv/LoadProgramDetailsTask.brs | 2 +- components/liveTv/LoadSheduleTask.brs | 2 +- components/liveTv/ProgramDetails.brs | 2 +- components/manager/QueueManager.brs | 4 +- components/movies/MovieDetails.brs | 4 +- components/photos/PhotoDetails.brs | 4 +- .../quickConnect/QuickConnectDialog.brs | 6 +- components/settings/settings.brs | 6 +- components/tvshows/TVEpisodes.brs | 2 +- components/tvshows/TVListDetails.brs | 6 +- components/tvshows/TVShowDetails.brs | 2 +- components/video/VideoPlayerView.brs | 4 +- source/Main.brs | 10 +- source/ShowScenes.brs | 168 ++++++++---- source/VideoPlayer.brs | 42 +-- source/api/Items.brs | 39 +-- source/api/UserLibrary.brs | 8 +- source/api/baserequest.brs | 45 ++-- source/api/userauth.brs | 51 ++-- source/utils/Subtitles.brs | 16 +- source/utils/config.brs | 59 ++-- source/utils/deviceCapabilities.brs | 12 +- source/utils/session.bs | 255 ++++++++++++++++++ 42 files changed, 627 insertions(+), 327 deletions(-) create mode 100644 source/utils/session.bs diff --git a/components/GetNextEpisodeTask.brs b/components/GetNextEpisodeTask.brs index 2996230d..84dc400c 100644 --- a/components/GetNextEpisodeTask.brs +++ b/components/GetNextEpisodeTask.brs @@ -7,7 +7,7 @@ end sub sub getNextEpisodeTask() m.nextEpisodeData = api.shows.GetEpisodes(m.top.showID, { - UserId: get_setting("active_user"), + UserId: m.global.session.user.id, StartItemId: m.top.videoID, Limit: 2 }) diff --git a/components/GetPlaybackInfoTask.brs b/components/GetPlaybackInfoTask.brs index f26a1ea9..e183d7e3 100644 --- a/components/GetPlaybackInfoTask.brs +++ b/components/GetPlaybackInfoTask.brs @@ -16,7 +16,7 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT "DeviceProfile": getDeviceProfile() } params = { - "UserId": get_setting("active_user"), + "UserId": m.global.session.user.id, "StartTimeTicks": currentItem.startingPoint, "IsPlayback": true, "AutoOpenLiveStream": true, @@ -46,15 +46,15 @@ sub getPlaybackInfoTask() end if end sub -function GetTranscodingStats(session) +function GetTranscodingStats(deviceSession) sessionStats = { data: [] } - if isValid(session.TranscodingInfo) and session.TranscodingInfo.Count() > 0 - transcodingReasons = session.TranscodingInfo.TranscodeReasons - videoCodec = session.TranscodingInfo.VideoCodec - audioCodec = session.TranscodingInfo.AudioCodec - totalBitrate = session.TranscodingInfo.Bitrate - audioChannels = session.TranscodingInfo.AudioChannels + if isValid(deviceSession.TranscodingInfo) and deviceSession.TranscodingInfo.Count() > 0 + transcodingReasons = deviceSession.TranscodingInfo.TranscodeReasons + videoCodec = deviceSession.TranscodingInfo.VideoCodec + audioCodec = deviceSession.TranscodingInfo.AudioCodec + totalBitrate = deviceSession.TranscodingInfo.Bitrate + audioChannels = deviceSession.TranscodingInfo.AudioChannels if isValid(transcodingReasons) and transcodingReasons.Count() > 0 sessionStats.data.push("
" + tr("Transcoding Information") + "
") @@ -65,7 +65,7 @@ function GetTranscodingStats(session) if isValid(videoCodec) data = "• " + tr("Video Codec") + ": " + videoCodec - if session.TranscodingInfo.IsVideoDirect + if deviceSession.TranscodingInfo.IsVideoDirect data = data + " (" + tr("direct") + ")" end if sessionStats.data.push(data) @@ -73,7 +73,7 @@ function GetTranscodingStats(session) if isValid(audioCodec) data = "• " + tr("Audio Codec") + ": " + audioCodec - if session.TranscodingInfo.IsAudioDirect + if deviceSession.TranscodingInfo.IsAudioDirect data = data + " (" + tr("direct") + ")" end if sessionStats.data.push(data) diff --git a/components/GetShuffleEpisodesTask.brs b/components/GetShuffleEpisodesTask.brs index 3a6b6a67..b82484cd 100644 --- a/components/GetShuffleEpisodesTask.brs +++ b/components/GetShuffleEpisodesTask.brs @@ -7,7 +7,7 @@ end sub sub getShuffleEpisodesTask() data = api.shows.GetEpisodes(m.top.showID, { - UserId: get_setting("active_user"), + UserId: m.global.session.user.id, SortBy: "Random", Limit: 200 }) diff --git a/components/ItemGrid/GridItem.brs b/components/ItemGrid/GridItem.brs index 40e0db2a..ea386b3b 100644 --- a/components/ItemGrid/GridItem.brs +++ b/components/ItemGrid/GridItem.brs @@ -18,7 +18,7 @@ sub init() m.itemText.translation = [0, m.itemPoster.height + 7] - m.gridTitles = get_user_setting("itemgrid.gridTitles") + m.gridTitles = m.global.session.user.settings["itemgrid.gridTitles"] m.itemText.visible = m.gridTitles = "showalways" ' Add some padding space when Item Titles are always showing @@ -48,7 +48,7 @@ sub itemContentChanged() m.itemIcon.uri = itemData.iconUrl m.itemText.text = itemData.Title else if itemData.type = "Series" - if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false" + if m.global.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount) if itemData.json.UserData.UnplayedItemCount > 0 m.unplayedCount.visible = true diff --git a/components/ItemGrid/ItemGrid.brs b/components/ItemGrid/ItemGrid.brs index 07174b92..70244eb8 100644 --- a/components/ItemGrid/ItemGrid.brs +++ b/components/ItemGrid/ItemGrid.brs @@ -8,7 +8,7 @@ sub init() m.log = log.Logger("ItemGrid") m.options = m.top.findNode("options") - m.showItemCount = get_user_setting("itemgrid.showItemCount") = "true" + m.showItemCount = m.global.session.user.settings["itemgrid.showItemCount"] m.tvGuide = invalid m.channelFocused = invalid @@ -71,7 +71,7 @@ sub init() m.AlphaSelected = m.top.findNode("AlphaSelected") 'Get reset folder setting - m.resetGrid = get_user_setting("itemgrid.reset") = "true" + m.resetGrid = m.global.session.user.settings["itemgrid.reset"] m.micButton = m.top.findNode("micButton") m.micButtonText = m.top.findNode("micButtonText") @@ -105,7 +105,7 @@ sub loadInitialItems() ' Read view/sort/filter settings if m.top.parentItem.collectionType = "livetv" ' Translate between app and server nomenclature - viewSetting = get_user_setting("display.livetv.landing") + viewSetting = m.global.session.user.settings["display.livetv.landing"] 'Move mic to be visiable on TV Guide screen if m.global.device.hasVoiceRemote = true m.micButton.translation = "[1540, 92]" @@ -119,19 +119,19 @@ sub loadInitialItems() else m.view = "livetv" end if - m.sortField = get_user_setting("display.livetv.sortField") - sortAscendingStr = get_user_setting("display.livetv.sortAscending") - m.filter = get_user_setting("display.livetv.filter") + m.sortField = m.global.session.user.settings["display.livetv.sortField"] + sortAscendingStr = m.global.session.user.settings["display.livetv.sortAscending"] + m.filter = m.global.session.user.settings["display.livetv.filter"] else if m.top.parentItem.collectionType = "music" - m.view = get_user_setting("display.music.view") - m.sortField = get_user_setting("display." + m.top.parentItem.Id + ".sortField") - sortAscendingStr = get_user_setting("display." + m.top.parentItem.Id + ".sortAscending") - m.filter = get_user_setting("display." + m.top.parentItem.Id + ".filter") + m.view = m.global.session.user.settings["display.music.view"] + m.sortField = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortField"] + sortAscendingStr = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortAscending"] + m.filter = m.global.session.user.settings["display." + m.top.parentItem.Id + ".filter"] else - m.sortField = get_user_setting("display." + m.top.parentItem.Id + ".sortField") - sortAscendingStr = get_user_setting("display." + m.top.parentItem.Id + ".sortAscending") - m.filter = get_user_setting("display." + m.top.parentItem.Id + ".filter") - m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing") + m.sortField = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortField"] + sortAscendingStr = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortAscending"] + m.filter = m.global.session.user.settings["display." + m.top.parentItem.Id + ".filter"] + m.view = m.global.session.user.settings["display." + m.top.parentItem.Id + ".landing"] end if if m.sortField = invalid then m.sortField = "SortName" @@ -183,7 +183,7 @@ sub loadInitialItems() m.loadItemsTask.itemType = "MusicArtist" m.loadItemsTask.itemId = m.top.parentItem.Id - m.view = get_user_setting("display.music.view") + m.view = m.global.session.user.settings["display.music.view"] if m.view = "music-album" m.loadItemsTask.itemType = "MusicAlbum" @@ -194,7 +194,7 @@ sub loadInitialItems() ' For LiveTV, we want to "Fit" the item images, not zoom m.top.imageDisplayMode = "scaleToFit" - if get_user_setting("display.livetv.landing") = "guide" and m.options.view <> "livetv" + if m.global.session.user.settings["display.livetv.landing"] = "guide" and m.options.view <> "livetv" showTvGuide() end if else if m.top.parentItem.collectionType = "CollectionFolder" or m.top.parentItem.type = "CollectionFolder" or m.top.parentItem.collectionType = "boxsets" or m.top.parentItem.Type = "Boxset" or m.top.parentItem.Type = "Boxsets" or m.top.parentItem.Type = "Folder" or m.top.parentItem.Type = "Channel" @@ -650,7 +650,7 @@ sub optionsClosed() reload = true end if else - m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing") + m.view = m.global.session.user.settings["display." + m.top.parentItem.Id + ".landing"] if m.options.view <> m.view 'reload and store new view setting m.view = m.options.view diff --git a/components/ItemGrid/LoadItemsTask2.brs b/components/ItemGrid/LoadItemsTask2.brs index 041f1369..22d1ffd1 100644 --- a/components/ItemGrid/LoadItemsTask2.brs +++ b/components/ItemGrid/LoadItemsTask2.brs @@ -12,7 +12,7 @@ sub init() m.top.functionName = "loadItems" m.top.limit = 60 - usersettingLimit = get_user_setting("itemgrid.Limit") + usersettingLimit = m.global.session.user.settings["itemgrid.Limit"] if usersettingLimit <> invalid m.top.limit = usersettingLimit @@ -105,33 +105,33 @@ sub loadItems() if m.top.ItemType = "LiveTV" url = "LiveTv/Channels" - params.append({ UserId: get_setting("active_user") }) + params.append({ UserId: m.global.session.user.id }) else if m.top.view = "Networks" url = "Studios" - params.append({ UserId: get_setting("active_user") }) + params.append({ UserId: m.global.session.user.id }) else if m.top.view = "Genres" url = "Genres" - params.append({ UserId: get_setting("active_user"), includeItemTypes: m.top.itemType }) + params.append({ UserId: m.global.session.user.id, includeItemTypes: m.top.itemType }) else if m.top.ItemType = "MusicArtist" url = "Artists" params.append({ - UserId: get_setting("active_user"), + UserId: m.global.session.user.id, Fields: "Genres" }) params.IncludeItemTypes = "MusicAlbum,Audio" else if m.top.ItemType = "AlbumArtists" url = "Artists/AlbumArtists" params.append({ - UserId: get_setting("active_user"), + UserId: m.global.session.user.id, Fields: "Genres" }) params.IncludeItemTypes = "MusicAlbum,Audio" else if m.top.ItemType = "MusicAlbum" - url = Substitute("Users/{0}/Items/", get_setting("active_user")) + url = Substitute("Users/{0}/Items/", m.global.session.user.id) params.append({ ImageTypeLimit: 1 }) params.append({ EnableImageTypes: "Primary,Backdrop,Banner,Thumb" }) else - url = Substitute("Users/{0}/Items/", get_setting("active_user")) + url = Substitute("Users/{0}/Items/", m.global.session.user.id) end if resp = APIRequest(url, params) @@ -168,7 +168,7 @@ sub loadItems() tmp = CreateObject("roSGNode", "ContentNode") tmp.title = item.name - genreData = api.users.GetItemsByQuery(get_setting("active_user"), { + genreData = api.users.GetItemsByQuery(m.global.session.user.id, { SortBy: "Random", SortOrder: "Ascending", IncludeItemTypes: m.top.itemType, diff --git a/components/ItemGrid/LoadVideoContentTask.brs b/components/ItemGrid/LoadVideoContentTask.brs index 36878d1a..1fcbf302 100644 --- a/components/ItemGrid/LoadVideoContentTask.brs +++ b/components/ItemGrid/LoadVideoContentTask.brs @@ -152,8 +152,8 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s ' transcode is that the Encoding Level is not supported, then try to direct play but silently ' fall back to the transcode if that fails. if m.playbackInfo.MediaSources[0].MediaStreams.Count() > 0 and meta.live = false - tryDirectPlay = get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264" - tryDirectPlay = tryDirectPlay or (get_user_setting("playback.tryDirect.hevcProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc") + tryDirectPlay = m.global.session.user.settings["playback.tryDirect.h264ProfileLevel"] and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264" + tryDirectPlay = tryDirectPlay or (m.global.session.user.settings["playback.tryDirect.hevcProfileLevel"] and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc") if tryDirectPlay and isValid(m.playbackInfo.MediaSources[0].TranscodingUrl) and forceTranscoding = false transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl) if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported" @@ -231,7 +231,7 @@ sub addSubtitlesToVideo(video, meta) safesubs = subtitles["all"] subtitleTracks = [] - if get_user_setting("playback.subs.onlytext") = "true" + if m.global.session.user.settings["playback.subs.onlytext"] = true safesubs = subtitles["text"] end if @@ -330,7 +330,7 @@ sub addNextEpisodesToQueue(showID) end if url = Substitute("Shows/{0}/Episodes", showID) - urlParams = { "UserId": get_setting("active_user") } + urlParams = { "UserId": m.global.session.user.id } urlParams.Append({ "StartItemId": videoID }) urlParams.Append({ "Limit": 50 }) resp = APIRequest(url, urlParams) @@ -347,7 +347,7 @@ end sub function sortSubtitles(id as string, MediaStreams) tracks = { "forced": [], "default": [], "normal": [], "text": [] } 'Too many args for using substitute - prefered_lang = m.user.Configuration.SubtitleLanguagePreference + prefered_lang = m.global.session.user.configuration.SubtitleLanguagePreference for each stream in MediaStreams if stream.type = "Subtitle" diff --git a/components/ItemGrid/MovieLibraryView.brs b/components/ItemGrid/MovieLibraryView.brs index 55143ac1..97c17439 100644 --- a/components/ItemGrid/MovieLibraryView.brs +++ b/components/ItemGrid/MovieLibraryView.brs @@ -36,7 +36,7 @@ sub init() m.overhang.isVisible = false - m.showItemCount = get_user_setting("itemgrid.showItemCount") = "true" + m.showItemCount = m.global.session.user.settings["itemgrid.showItemCount"] m.swapAnimation.observeField("state", "swapDone") @@ -86,7 +86,7 @@ sub init() m.spinner.visible = true 'Get reset folder setting - m.resetGrid = get_user_setting("itemgrid.reset") = "true" + m.resetGrid = m.global.session.user.settings["itemgrid.reset"] 'Hide voice search if device does not have voice remote if m.global.device.hasVoiceRemote = false @@ -129,15 +129,15 @@ sub loadInitialItems() SetBackground("") end if - m.sortField = get_user_setting("display." + m.top.parentItem.Id + ".sortField") - m.filter = get_user_setting("display." + m.top.parentItem.Id + ".filter") - m.filterOptions = get_user_setting("display." + m.top.parentItem.Id + ".filterOptions") - m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing") - sortAscendingStr = get_user_setting("display." + m.top.parentItem.Id + ".sortAscending") + m.sortField = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortField"] + m.filter = m.global.session.user.settings["display." + m.top.parentItem.Id + ".filter"] + m.filterOptions = m.global.session.user.settings["display." + m.top.parentItem.Id + ".filterOptions"] + m.view = m.global.session.user.settings["display." + m.top.parentItem.Id + ".landing"] + m.sortAscending = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortAscending"] ' If user has not set a preferred view for this folder, check if they've set a default view if not isValid(m.view) - m.view = get_user_setting("itemgrid.movieDefaultView") + m.view = m.global.session.user.settings["itemgrid.movieDefaultView"] end if if not isValid(m.sortField) then m.sortField = "SortName" @@ -147,12 +147,6 @@ sub loadInitialItems() m.filterOptions = ParseJson(m.filterOptions) - if sortAscendingStr = invalid or sortAscendingStr = "true" - m.sortAscending = true - else - m.sortAscending = false - end if - if m.top.parentItem.json.type = "Studio" m.loadItemsTask.studioIds = m.top.parentItem.id m.loadItemsTask.itemId = m.top.parentItem.parentFolder @@ -206,7 +200,7 @@ sub loadInitialItems() m.itemGrid.numRows = "3" m.selectedMovieOverview.visible = false m.infoGroup.visible = false - m.top.showItemTitles = get_user_setting("itemgrid.gridTitles") + m.top.showItemTitles = m.global.session.user.settings["itemgrid.gridTitles"] if LCase(m.top.showItemTitles) = "hidealways" m.itemGrid.itemSize = "[230, 315]" m.itemGrid.rowHeights = "[315]" @@ -229,7 +223,7 @@ sub loadInitialItems() m.getFiltersTask.observeField("filters", "FilterDataLoaded") m.getFiltersTask.params = { - userid: get_setting("active_user"), + userid: m.global.session.user.id, parentid: m.top.parentItem.Id, includeitemtypes: "Movie" } @@ -794,7 +788,7 @@ sub optionsClosed() set_user_setting("display." + m.top.parentItem.Id + ".filterOptions", FormatJson(m.options.filterOptions)) end if - m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing") + m.view = m.global.session.user.settings["display." + m.top.parentItem.Id + ".landing"] if m.options.view <> m.view m.view = m.options.view diff --git a/components/ItemGrid/MusicArtistGridItem.brs b/components/ItemGrid/MusicArtistGridItem.brs index 2a501fed..c639d919 100644 --- a/components/ItemGrid/MusicArtistGridItem.brs +++ b/components/ItemGrid/MusicArtistGridItem.brs @@ -18,7 +18,7 @@ sub init() m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode end if - m.gridTitles = get_user_setting("itemgrid.gridTitles") + m.gridTitles = m.global.session.user.settings["itemgrid.gridTitles"] m.posterText.visible = false m.postTextBackground.visible = false diff --git a/components/ItemGrid/MusicLibraryView.brs b/components/ItemGrid/MusicLibraryView.brs index 6af60ae9..6b06fb23 100644 --- a/components/ItemGrid/MusicLibraryView.brs +++ b/components/ItemGrid/MusicLibraryView.brs @@ -31,7 +31,7 @@ sub init() m.overhang.isVisible = false - m.showItemCount = get_user_setting("itemgrid.showItemCount") = "true" + m.showItemCount = m.global.session.user.settings["itemgrid.showItemCount"] m.swapAnimation.observeField("state", "swapDone") @@ -80,7 +80,7 @@ sub init() m.spinner.visible = true 'Get reset folder setting - m.resetGrid = get_user_setting("itemgrid.reset") = "true" + m.resetGrid = m.global.session.user.settings["itemgrid.reset"] 'Hide voice search if device does not have voice remote if m.global.device.hasVoiceRemote = false @@ -123,22 +123,16 @@ sub loadInitialItems() SetBackground("") end if - m.sortField = get_user_setting("display." + m.top.parentItem.Id + ".sortField") - sortAscendingStr = get_user_setting("display." + m.top.parentItem.Id + ".sortAscending") - m.filter = get_user_setting("display." + m.top.parentItem.Id + ".filter") - m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing") + m.sortField = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortField"] + m.sortAscending = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortAscending"] + m.filter = m.global.session.user.settings["display." + m.top.parentItem.Id + ".filter"] + m.view = m.global.session.user.settings["display." + m.top.parentItem.Id + ".landing"] if not isValid(m.sortField) then m.sortField = "SortName" if not isValid(m.filter) then m.filter = "All" if not isValid(m.view) then m.view = "ArtistsPresentation" - if sortAscendingStr = invalid or LCase(sortAscendingStr) = "true" - m.sortAscending = true - else - m.sortAscending = false - end if - - m.top.showItemTitles = get_user_setting("itemgrid.gridTitles") + m.top.showItemTitles = m.global.session.user.settings["itemgrid.gridTitles"] if LCase(m.top.parentItem.json.type) = "musicgenre" m.itemGrid.translation = "[96, 60]" @@ -660,7 +654,7 @@ sub optionsClosed() set_user_setting("display." + m.top.parentItem.Id + ".filter", m.options.filter) end if - m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing") + m.view = m.global.session.user.settings["display." + m.top.parentItem.Id + ".landing"] if m.options.view <> m.view m.view = m.options.view diff --git a/components/JFOverhang.brs b/components/JFOverhang.brs index 653d86aa..d118b22c 100644 --- a/components/JFOverhang.brs +++ b/components/JFOverhang.brs @@ -18,7 +18,7 @@ sub init() m.slideDownAnimation = m.top.findNode("slideDown") m.slideUpAnimation = m.top.findNode("slideUp") ' show clock based on user setting - m.hideClock = get_user_setting("ui.design.hideclock") = "true" + m.hideClock = m.global.session.user.settings["ui.design.hideclock"] if not m.hideClock ' save node references m.overlayHours = m.top.findNode("overlayHours") diff --git a/components/JFVideo.brs b/components/JFVideo.brs index eb00b626..788973d2 100644 --- a/components/JFVideo.brs +++ b/components/JFVideo.brs @@ -13,7 +13,7 @@ sub init() m.top.transcodeReasons = [] m.bufferCheckTimer.duration = 30 - if get_user_setting("ui.design.hideclock") = "true" + if m.global.session.user.settings["ui.design.hideclock"] = true clockNode = findNodeBySubtype(m.top, "clock") if clockNode[0] <> invalid then clockNode[0].parent.removeChild(clockNode[0].node) end if @@ -22,12 +22,7 @@ sub init() m.nextEpisodeButton = m.top.findNode("nextEpisode") m.nextEpisodeButton.text = tr("Next Episode") m.nextEpisodeButton.setFocus(false) - m.nextupbuttonseconds = get_user_setting("playback.nextupbuttonseconds", "30") - if isValid(m.nextupbuttonseconds) - m.nextupbuttonseconds = val(m.nextupbuttonseconds) - else - m.nextupbuttonseconds = 30 - end if + m.nextupbuttonseconds = m.global.session.user.settings["playback.nextupbuttonseconds"] m.showNextEpisodeButtonAnimation = m.top.findNode("showNextEpisodeButton") m.hideNextEpisodeButtonAnimation = m.top.findNode("hideNextEpisodeButton") @@ -49,7 +44,7 @@ sub onAllowCaptionsChange() m.captionTask.observeField("useThis", "checkCaptionMode") m.top.observeField("currentSubtitleTrack", "loadCaption") m.top.observeField("globalCaptionMode", "toggleCaption") - if get_user_setting("playback.subs.custom") = "false" + if m.global.session.user.settings["playback.subs.custom"] = false m.top.suppressCaptions = false else m.top.suppressCaptions = true @@ -95,7 +90,7 @@ end sub ' ' Runs Next Episode button animation and sets focus to button sub showNextEpisodeButton() - if m.global.userConfig.EnableNextEpisodeAutoPlay and not m.nextEpisodeButton.visible + if m.global.session.user.configuration.EnableNextEpisodeAutoPlay and not m.nextEpisodeButton.visible m.showNextEpisodeButtonAnimation.control = "start" m.nextEpisodeButton.setFocus(true) m.nextEpisodeButton.visible = true diff --git a/components/ListPoster.brs b/components/ListPoster.brs index 3c2ad382..6ad15ab3 100644 --- a/components/ListPoster.brs +++ b/components/ListPoster.brs @@ -56,7 +56,7 @@ sub itemContentChanged() as void itemData = m.top.itemContent m.title.text = itemData.title - if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false" + if m.global.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount) if itemData.json.UserData.UnplayedItemCount > 0 m.unplayedCount.visible = true @@ -80,7 +80,7 @@ sub itemContentChanged() as void imageUrl = itemData.posterURL - if get_user_setting("ui.tvshows.blurunwatched") = "true" + if m.global.session.user.settings["ui.tvshows.blurunwatched"] = true if itemData.json.lookup("Type") = "Episode" and isValid(itemData.json.userdata) if not itemData.json.userdata.played imageUrl = imageUrl + "&blur=15" diff --git a/components/data/UserData.brs b/components/data/UserData.brs index c2ff7882..564848a6 100644 --- a/components/data/UserData.brs +++ b/components/data/UserData.brs @@ -32,7 +32,7 @@ sub saveToRegistry() users.push({ id: m.top.id, username: m.top.username, - server: get_setting("server") + server: m.global.session.server.url }) set_setting("available_users", formatJson(users)) end if @@ -48,8 +48,8 @@ sub removeFromRegistry() set_setting("available_users", formatJson(new_users)) end sub -function getPreference(key as string, default as string) - return get_user_setting("pref-" + key, default) +function getPreference(key as string) + return get_user_setting("pref-" + key) end function function setPreference(key as string, value as string) diff --git a/components/home/Home.brs b/components/home/Home.brs index 69696670..00e056f6 100644 --- a/components/home/Home.brs +++ b/components/home/Home.brs @@ -5,7 +5,7 @@ import "pkg:/source/utils/misc.brs" sub init() m.top.overhangTitle = "Home" m.top.optionsAvailable = true - if get_user_setting("ui.home.splashBackground") = "true" + if m.global.session.user.settings["ui.home.splashBackground"] = true m.backdrop = m.top.findNode("backdrop") m.backdrop.uri = buildURL("/Branding/Splashscreen?format=jpg&foregroundLayer=0.15&fillWidth=1280&width=1280&fillHeight=720&height=720&tag=splash") end if diff --git a/components/home/HomeItem.brs b/components/home/HomeItem.brs index 43ea15e9..8fb83529 100644 --- a/components/home/HomeItem.brs +++ b/components/home/HomeItem.brs @@ -50,7 +50,7 @@ sub itemContentChanged() m.playedIndicator.visible = false if LCase(itemData.type) = "series" - if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false" + if m.global.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount) if itemData.json.UserData.UnplayedItemCount > 0 m.unplayedCount.visible = true diff --git a/components/home/HomeRows.brs b/components/home/HomeRows.brs index cd839b0a..4fce8fff 100644 --- a/components/home/HomeRows.brs +++ b/components/home/HomeRows.brs @@ -87,16 +87,14 @@ sub onLibrariesLoaded() ' validate library data if isValid(m.libraryData) and m.libraryData.count() > 0 - userConfig = m.global.userConfig - ' populate My Media row - filteredMedia = filterNodeArray(m.libraryData, "id", userConfig.MyMediaExcludes) + filteredMedia = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.MyMediaExcludes) for each item in filteredMedia mediaRow.appendChild(item) end for ' create a "Latest In" row for each library - filteredLatest = filterNodeArray(m.libraryData, "id", userConfig.LatestItemsExcludes) + filteredLatest = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.LatestItemsExcludes) for each lib in filteredLatest if lib.collectionType <> "boxsets" and lib.collectionType <> "livetv" and lib.json.CollectionType <> "Program" latestInRow = content.CreateChild("HomeRow") @@ -283,8 +281,7 @@ sub updateNextUpItems() end if ' create task nodes for "Latest In" rows - userConfig = m.global.userConfig - filteredLatest = filterNodeArray(m.libraryData, "id", userConfig.LatestItemsExcludes) + filteredLatest = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.LatestItemsExcludes) for each lib in filteredLatest if lib.collectionType <> "livetv" and lib.collectionType <> "boxsets" and lib.json.CollectionType <> "Program" loadLatest = createObject("roSGNode", "LoadItemsTask") diff --git a/components/home/LoadItemsTask.brs b/components/home/LoadItemsTask.brs index 32d8e8f2..ee42be1e 100644 --- a/components/home/LoadItemsTask.brs +++ b/components/home/LoadItemsTask.brs @@ -17,7 +17,7 @@ sub loadItems() ' Load Libraries if m.top.itemsToLoad = "libraries" - url = Substitute("Users/{0}/Views/", get_setting("active_user")) + url = Substitute("Users/{0}/Views/", m.global.session.user.id) resp = APIRequest(url) data = getJson(resp) if isValid(data) and isValid(data.Items) @@ -33,7 +33,7 @@ sub loadItems() ' Load Latest Additions to Libraries else if m.top.itemsToLoad = "latest" - activeUser = get_setting("active_user") + activeUser = m.global.session.user.id if isValid(activeUser) url = Substitute("Users/{0}/Items/Latest", activeUser) params = {} @@ -67,15 +67,14 @@ sub loadItems() params["SortBy"] = "DatePlayed" params["SortOrder"] = "Descending" params["ImageTypeLimit"] = 1 - params["UserId"] = get_setting("active_user") + params["UserId"] = m.global.session.user.id params["EnableRewatching"] = false params["DisableFirstEpisode"] = false params["limit"] = 24 params["EnableTotalRecordCount"] = false - maxDaysInNextUp = get_user_setting("ui.details.maxdaysnextup", "365") + maxDaysInNextUp = m.global.session.user.settings["ui.details.maxdaysnextup"] if isValid(maxDaysInNextUp) - maxDaysInNextUp = Val(maxDaysInNextUp) if maxDaysInNextUp > 0 dateToday = CreateObject("roDateTime") dateCutoff = CreateObject("roDateTime") @@ -97,7 +96,7 @@ sub loadItems() end if ' Load Continue Watching else if m.top.itemsToLoad = "continue" - activeUser = get_setting("active_user") + activeUser = m.global.session.user.id if isValid(activeUser) url = Substitute("Users/{0}/Items/Resume", activeUser) @@ -124,7 +123,7 @@ sub loadItems() else if m.top.itemsToLoad = "favorites" - url = Substitute("Users/{0}/Items", get_setting("active_user")) + url = Substitute("Users/{0}/Items", m.global.session.user.id) params = {} params["Filters"] = "IsFavorite" @@ -155,7 +154,7 @@ sub loadItems() else if m.top.itemsToLoad = "onNow" url = "LiveTv/Programs/Recommended" params = {} - params["userId"] = get_setting("active_user") + params["userId"] = m.global.session.user.id params["isAiring"] = true params["limit"] = 16 ' 16 to be consistent with "Latest In" params["imageTypeLimit"] = 1 @@ -190,7 +189,7 @@ sub loadItems() end for else if m.top.itemsToLoad = "specialfeatures" params = {} - url = Substitute("Users/{0}/Items/{1}/SpecialFeatures", get_setting("active_user"), m.top.itemId) + url = Substitute("Users/{0}/Items/{1}/SpecialFeatures", m.global.session.user.id, m.top.itemId) resp = APIRequest(url, params) data = getJson(resp) if data <> invalid and data.count() > 0 @@ -220,7 +219,7 @@ sub loadItems() end for end if else if m.top.itemsToLoad = "likethis" - params = { "userId": get_setting("active_user"), "limit": 16 } + params = { "userId": m.global.session.user.id, "limit": 16 } url = Substitute("Items/{0}/Similar", m.top.itemId) resp = APIRequest(url, params) data = getJson(resp) @@ -252,7 +251,7 @@ end sub sub getPersonVideos(videoType, dest, dimens) params = { personIds: m.top.itemId, recursive: true, includeItemTypes: videoType, Limit: 50, SortBy: "Random" } - url = Substitute("Users/{0}/Items", get_setting("active_user")) + url = Substitute("Users/{0}/Items", m.global.session.user.id) resp = APIRequest(url, params) data = getJson(resp) if data <> invalid and data.count() > 0 diff --git a/components/liveTv/LoadChannelsTask.brs b/components/liveTv/LoadChannelsTask.brs index aa4dea80..482188bd 100644 --- a/components/liveTv/LoadChannelsTask.brs +++ b/components/liveTv/LoadChannelsTask.brs @@ -22,7 +22,7 @@ sub loadChannels() SortBy: sort_field, SortOrder: sort_order, recursive: m.top.recursive, - UserId: get_setting("active_user") + UserId: m.global.session.user.id } ' Handle special case when getting names starting with numeral @@ -43,7 +43,7 @@ sub loadChannels() params.append({ isFavorite: true }) end if - url = Substitute("Users/{0}/Items/", get_setting("active_user")) + url = Substitute("Users/{0}/Items/", m.global.session.user.id) resp = APIRequest(url, params) data = getJson(resp) diff --git a/components/liveTv/LoadProgramDetailsTask.brs b/components/liveTv/LoadProgramDetailsTask.brs index fb075b8c..686dba86 100644 --- a/components/liveTv/LoadProgramDetailsTask.brs +++ b/components/liveTv/LoadProgramDetailsTask.brs @@ -12,7 +12,7 @@ sub loadProgramDetails() programIndex = m.top.ProgramIndex params = { - UserId: get_setting("active_user") + UserId: m.global.session.user.id } url = Substitute("LiveTv/Programs/{0}", m.top.programId) diff --git a/components/liveTv/LoadSheduleTask.brs b/components/liveTv/LoadSheduleTask.brs index 082d1ad9..d898f50a 100644 --- a/components/liveTv/LoadSheduleTask.brs +++ b/components/liveTv/LoadSheduleTask.brs @@ -10,7 +10,7 @@ sub loadSchedule() results = [] params = { - UserId: get_setting("active_user"), + UserId: m.global.session.user.id, SortBy: "startDate", EnableImages: false, EnableTotalRecordCount: false, diff --git a/components/liveTv/ProgramDetails.brs b/components/liveTv/ProgramDetails.brs index 61ba7d7c..e171d43f 100644 --- a/components/liveTv/ProgramDetails.brs +++ b/components/liveTv/ProgramDetails.brs @@ -91,7 +91,7 @@ sub setupLabels() m.recordSeriesOutline.width = recordSeriesButtonBackground.width m.recordSeriesOutline.height = recordSeriesButtonBackground.height - m.userCanRecord = get_user_setting("livetv.canrecord") + m.userCanRecord = m.global.session.user.settings["livetv.canrecord"] if m.userCanRecord = "false" m.recordButton.visible = false m.recordSeriesButton.visible = false diff --git a/components/manager/QueueManager.brs b/components/manager/QueueManager.brs index 6a0b55e3..ca9f30fa 100644 --- a/components/manager/QueueManager.brs +++ b/components/manager/QueueManager.brs @@ -12,7 +12,7 @@ sub init() m.originalQueue = [] m.queueTypes = [] ' Preroll videos only play if user has cinema mode setting enabled - m.isPrerollActive = (get_user_setting("playback.cinemamode") = "true") + m.isPrerollActive = m.global.session.user.settings["playback.cinemamode"] m.position = 0 m.shuffleEnabled = false end sub @@ -21,7 +21,7 @@ end sub sub clear() m.queue = [] m.queueTypes = [] - m.isPrerollActive = (get_user_setting("playback.cinemamode") = "true") + m.isPrerollActive = m.global.session.user.settings["playback.cinemamode"] setPosition(0) end sub diff --git a/components/movies/MovieDetails.brs b/components/movies/MovieDetails.brs index 1d34600c..7a14195d 100644 --- a/components/movies/MovieDetails.brs +++ b/components/movies/MovieDetails.brs @@ -92,7 +92,7 @@ sub itemContentChanged() if type(itemData.RunTimeTicks) = "LongInteger" setFieldText("runtime", stri(getRuntime()) + " mins") - if get_user_setting("ui.design.hideclock") <> "true" + if m.global.session.user.settings["ui.design.hideclock"] <> true setFieldText("ends-at", tr("Ends at %1").Replace("%1", getEndTime())) end if end if @@ -120,7 +120,7 @@ sub itemContentChanged() m.top.findNode("details").removeChild(m.top.findNode("director")) end if - if get_user_setting("ui.details.hidetagline") = "false" + if m.global.session.user.settings["ui.details.hidetagline"] = false if itemData.taglines.count() > 0 setFieldText("tagline", itemData.taglines[0]) end if diff --git a/components/photos/PhotoDetails.brs b/components/photos/PhotoDetails.brs index 20f474cf..436bff8e 100644 --- a/components/photos/PhotoDetails.brs +++ b/components/photos/PhotoDetails.brs @@ -10,8 +10,8 @@ sub init() m.textBackground = m.top.findNode("background") m.statusTimer = m.top.findNode("statusTimer") m.statusTimer.observeField("fire", "statusUpdate") - m.slideshow = get_user_setting("photos.slideshow") - m.random = get_user_setting("photos.random") + m.slideshow = m.global.session.user.settings["photos.slideshow"] + m.random = m.global.session.user.settings["photos.random"] m.showStatusAnimation = m.top.findNode("showStatusAnimation") m.hideStatusAnimation = m.top.findNode("hideStatusAnimation") diff --git a/components/quickConnect/QuickConnectDialog.brs b/components/quickConnect/QuickConnectDialog.brs index a26cacfb..c646b146 100644 --- a/components/quickConnect/QuickConnectDialog.brs +++ b/components/quickConnect/QuickConnectDialog.brs @@ -1,6 +1,7 @@ import "pkg:/source/api/userauth.brs" import "pkg:/source/api/baserequest.brs" import "pkg:/source/utils/config.brs" +import "pkg:/source/utils/session.bs" sub init() m.quickConnectTimer = m.top.findNode("quickConnectTimer") @@ -31,9 +32,10 @@ sub OnAuthenticated() ' We've been given the go ahead, try to authenticate via Quick Connect... authenticated = AuthenticateViaQuickConnect(m.top.quickConnectJson.secret) if authenticated <> invalid and authenticated = true - m.user = AboutMe() + currentUser = AboutMe() + session.user.Login(currentUser) LoadUserPreferences() - LoadUserAbilities(m.user) + LoadUserAbilities() m.top.close = true m.top.authenticated = true else diff --git a/components/settings/settings.brs b/components/settings/settings.brs index 36c5ec5e..5809353f 100644 --- a/components/settings/settings.brs +++ b/components/settings/settings.brs @@ -95,20 +95,20 @@ sub settingFocused() m.boolSetting.visible = true - if get_user_setting(selectedSetting.settingName) = "true" + if m.global.session.user.settings[selectedSetting.settingName] = true m.boolSetting.checkedItem = 1 else m.boolSetting.checkedItem = 0 end if else if selectedSetting.type = "integer" - integerValue = get_user_setting(selectedSetting.settingName, selectedSetting.default) + integerValue = m.global.session.user.settings[selectedSetting.settingName].ToStr() if isValid(integerValue) m.integerSetting.text = integerValue end if m.integerSetting.visible = true else if LCase(selectedSetting.type) = "radio" - selectedValue = get_user_setting(selectedSetting.settingName, selectedSetting.default) + selectedValue = m.global.session.user.settings[selectedSetting.settingName] radioContent = CreateObject("roSGNode", "ContentNode") diff --git a/components/tvshows/TVEpisodes.brs b/components/tvshows/TVEpisodes.brs index db313a26..466e5263 100644 --- a/components/tvshows/TVEpisodes.brs +++ b/components/tvshows/TVEpisodes.brs @@ -23,7 +23,7 @@ sub setSeasonLoading() end sub sub updateSeason() - if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false" + if m.global.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false if isValid(m.top.seasonData) and isValid(m.top.seasonData.UserData) and isValid(m.top.seasonData.UserData.UnplayedItemCount) if m.top.seasonData.UserData.UnplayedItemCount > 0 m.unplayedCount.visible = true diff --git a/components/tvshows/TVListDetails.brs b/components/tvshows/TVListDetails.brs index 036a3def..c688ea44 100644 --- a/components/tvshows/TVListDetails.brs +++ b/components/tvshows/TVListDetails.brs @@ -36,7 +36,7 @@ sub itemContentChanged() imageUrl = item.posterURL - if get_user_setting("ui.tvshows.blurunwatched") = "true" + if m.global.session.user.settings["ui.tvshows.blurunwatched"] = true if itemData.lookup("Type") = "Episode" if not itemData.userdata.played imageUrl = imageUrl + "&blur=15" @@ -54,12 +54,12 @@ sub itemContentChanged() m.top.findNode("runtime").text = stri(runTime).trim() + " mins" end if - if get_user_setting("ui.design.hideclock") <> "true" + if m.global.session.user.settings["ui.design.hideclock"] <> true m.top.findNode("endtime").text = tr("Ends at %1").Replace("%1", getEndTime()) end if end if - if get_user_setting("ui.tvshows.disableCommunityRating") = "false" + if m.global.session.user.settings["ui.tvshows.disableCommunityRating"] = false if isValid(itemData.communityRating) m.top.findNode("star").visible = true m.top.findNode("communityRating").text = str(int(itemData.communityRating * 10) / 10) diff --git a/components/tvshows/TVShowDetails.brs b/components/tvshows/TVShowDetails.brs index 897d1d3b..e5e1ead4 100644 --- a/components/tvshows/TVShowDetails.brs +++ b/components/tvshows/TVShowDetails.brs @@ -19,7 +19,7 @@ sub itemContentChanged() item = m.top.itemContent itemData = item.json - if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false" + if m.global.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false if isValid(itemData.UserData) and isValid(itemData.UserData.UnplayedItemCount) if itemData.UserData.UnplayedItemCount > 0 m.unplayedCount.visible = true diff --git a/components/video/VideoPlayerView.brs b/components/video/VideoPlayerView.brs index e38e5419..d3c7bd6c 100644 --- a/components/video/VideoPlayerView.brs +++ b/components/video/VideoPlayerView.brs @@ -29,7 +29,7 @@ sub init() m.top.transcodeReasons = [] m.bufferCheckTimer.duration = 30 - if get_user_setting("ui.design.hideclock") = "true" + if m.global.session.user.settings["ui.design.hideclock"] = true clockNode = findNodeBySubtype(m.top, "clock") if clockNode[0] <> invalid then clockNode[0].parent.removeChild(clockNode[0].node) end if @@ -152,7 +152,7 @@ end sub ' ' Runs Next Episode button animation and sets focus to button sub showNextEpisodeButton() - if m.global.userConfig.EnableNextEpisodeAutoPlay and not m.nextEpisodeButton.visible + if m.global.session.user.configuration.EnableNextEpisodeAutoPlay and not m.nextEpisodeButton.visible m.showNextEpisodeButtonAnimation.control = "start" m.nextEpisodeButton.setFocus(true) m.nextEpisodeButton.visible = true diff --git a/source/Main.brs b/source/Main.brs index e6261caf..948769d9 100644 --- a/source/Main.brs +++ b/source/Main.brs @@ -13,6 +13,7 @@ sub Main (args as dynamic) as void m.global = m.screen.getGlobalNode() SaveAppToGlobal() SaveDeviceToGlobal() + session.Init() m.scene = m.screen.CreateScene("JFScene") m.screen.show() ' vscode_rale_tracker_entry @@ -34,10 +35,9 @@ sub Main (args as dynamic) as void PostDeviceProfile() ' remove previous scenes from the stack sceneManager.callFunc("clearScenes") - ' save user config - m.global.addFields({ userConfig: m.user.configuration }) + ' load home page - sceneManager.currentUser = m.user.Name + sceneManager.currentUser = m.global.session.user.name group = CreateHomeGroup() group.callFunc("loadLibraries") sceneManager.callFunc("pushScene", group) @@ -64,7 +64,7 @@ sub Main (args as dynamic) as void ' Only show the Whats New popup the first time a user runs a new client version. if m.global.app.version <> get_setting("LastRunVersion") ' Ensure the user hasn't disabled Whats New popups - if get_user_setting("load.allowwhatsnew") = "true" + if m.global.session.user.settings["load.allowwhatsnew"] = true set_setting("LastRunVersion", m.global.app.version) dialog = createObject("roSGNode", "WhatsNewDialog") m.scene.dialog = dialog @@ -460,7 +460,7 @@ sub Main (args as dynamic) as void dialog.title = tr("Loading trailer") m.scene.dialog = dialog - trailerData = api.users.GetLocalTrailers(get_setting("active_user"), group.id) + trailerData = api.users.GetLocalTrailers(m.global.session.user.id, group.id) if isValid(trailerData) and isValid(trailerData[0]) and isValid(trailerData[0].id) m.global.queueManager.callFunc("clear") diff --git a/source/ShowScenes.brs b/source/ShowScenes.brs index 2be91985..237e5441 100644 --- a/source/ShowScenes.brs +++ b/source/ShowScenes.brs @@ -2,7 +2,14 @@ function LoginFlow(startOver = false as boolean) 'Collect Jellyfin server and user information start_login: - if get_setting("server") = invalid then startOver = true + serverUrl = get_setting("server") + if isValid(serverUrl) + print "Previous server connection saved to registry" + session.server.UpdateURL(serverUrl) + else + startOver = true + print "No previous server connection saved to registry" + end if invalidServer = true if not startOver @@ -28,7 +35,9 @@ function LoginFlow(startOver = false as boolean) SaveServerList() end if - if get_setting("active_user") = invalid + activeUser = get_setting("active_user") + if activeUser = invalid + print "No active user found in registry" SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting publicUsers = GetPublicUsers() if publicUsers.count() @@ -37,7 +46,7 @@ function LoginFlow(startOver = false as boolean) user = CreateObject("roSGNode", "PublicUserData") user.id = item.Id user.name = item.Name - if item.PrimaryImageTag <> invalid + if isValid(item.PrimaryImageTag) user.ImageURL = UserImageURL(user.id, { "tag": item.PrimaryImageTag }) end if publicUsersNodes.push(user) @@ -48,11 +57,11 @@ function LoginFlow(startOver = false as boolean) return LoginFlow(true) else 'Try to login without password. If the token is valid, we're done - get_token(userSelected, "") - if get_setting("active_user") <> invalid - m.user = AboutMe() + userData = get_token(userSelected, "") + if isValid(userData) + session.user.Login(userData) LoadUserPreferences() - LoadUserAbilities(m.user) + LoadUserAbilities() SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed return true end if @@ -66,17 +75,74 @@ function LoginFlow(startOver = false as boolean) m.global.sceneManager.callFunc("clearScenes") return LoginFlow(true) end if + else + print "Active user found in registry" + session.user.Update("id", activeUser) + + myAuthToken = get_user_setting("token") + if isValid(myAuthToken) + print "Auth token found in registry" + session.user.Update("authToken", myAuthToken) + print "Attempting to use API with auth token" + currentUser = AboutMe() + if currentUser = invalid + print "Auth token is no longer valid - restart login flow" + unset_user_setting("token") + unset_setting("active_user") + session.user.Logout() + goto start_login + else + print "Success! Auth token is still valid" + session.user.Login(currentUser) + end if + else + print "No auth token found in registry" + myUsername = get_setting("username") + myPassword = get_setting("password") + userData = invalid + + if isValid(myUsername) and isValid(myPassword) + if myUsername <> "" + print "Username and password found in registry. Attempting to login" + userData = get_token(myUsername, myPassword) + else + print "Username in registry is an empty string" + unset_setting("username") + unset_setting("password") + end if + else if isValid(myUsername) and not isValid(myPassword) + print "Username found in registry but no password" + if myUsername <> "" + print "Attempting to login with no password" + userData = get_token(myUsername, "") + else + print "Username in registry is an empty string" + unset_setting("username") + end if + + else if not isValid(myUsername) and not isValid(myPassword) + print "Neither username nor password found in registry - restart login flow" + unset_setting("active_user") + session.user.Logout() + goto start_login + end if + + if isValid(userData) + print "login success!" + session.user.Login(userData) + end if + end if end if - m.user = AboutMe() - if m.user = invalid or m.user.id <> get_setting("active_user") + 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 LoadUserPreferences() - LoadUserAbilities(m.user) + LoadUserAbilities() m.global.sceneManager.callFunc("clearScenes") return true @@ -84,18 +150,18 @@ end function sub SaveServerList() 'Save off this server to our list of saved servers for easier navigation between servers - server = get_setting("server") + server = m.global.session.server.url saved = get_setting("saved_servers") - if server <> invalid + if isValid(server) server = LCase(server)'Saved server data is always lowercase end if entryCount = 0 addNewEntry = true savedServers = { serverList: [] } - if saved <> invalid + if isValid(saved) savedServers = ParseJson(saved) entryCount = savedServers.serverList.Count() - if savedServers.serverList <> invalid and entryCount > 0 + if isValid(savedServers.serverList) and entryCount > 0 for each item in savedServers.serverList if item.baseUrl = server addNewEntry = false @@ -117,10 +183,10 @@ end sub sub DeleteFromServerList(urlToDelete) saved = get_setting("saved_servers") - if urlToDelete <> invalid + if isValid(urlToDelete) urlToDelete = LCase(urlToDelete) end if - if saved <> invalid + if isValid(saved) savedServers = ParseJson(saved) newServers = { serverList: [] } for each item in savedServers.serverList @@ -146,8 +212,8 @@ function CreateServerGroup() port = CreateObject("roMessagePort") m.colors = {} - if get_setting("server") <> invalid - screen.serverUrl = get_setting("server") + if isValid(m.global.session.server.url) + screen.serverUrl = m.global.session.server.url end if m.viewModel = {} button = screen.findNode("submit") @@ -181,43 +247,45 @@ function CreateServerGroup() if node = "submit" serverUrl = standardize_jellyfin_url(screen.serverUrl) 'If this is a different server from what we know, reset username/password setting - if get_setting("server") <> serverUrl + if m.global.session.server.url <> serverUrl set_setting("username", "") set_setting("password", "") end if set_setting("server", serverUrl) + session.server.UpdateURL(serverUrl) ' Show Connecting to Server spinner dialog = createObject("roSGNode", "ProgressDialog") dialog.title = tr("Connecting to Server") m.scene.dialog = dialog - m.serverInfoResult = ServerInfo() + serverInfoResult = ServerInfo() dialog.close = true - if m.serverInfoResult = invalid + if 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 m.serverInfoResult.Error <> invalid and m.serverInfoResult.Error + else if isValid(serverInfoResult.Error) and serverInfoResult.Error ' If server redirected received, update the URL - if m.serverInfoResult.UpdatedUrl <> invalid - serverUrl = m.serverInfoResult.UpdatedUrl + if isValid(serverInfoResult.UpdatedUrl) + serverUrl = serverInfoResult.UpdatedUrl set_setting("server", serverUrl) + session.server.UpdateURL(serverUrl) end if ' Display Error Message to user message = tr("Error: ") - if m.serverInfoResult.ErrorCode <> invalid - message = message + "[" + m.serverInfoResult.ErrorCode.toStr() + "] " + if isValid(serverInfoResult.ErrorCode) + message = message + "[" + serverInfoResult.ErrorCode.toStr() + "] " end if - screen.errorMessage = message + tr(m.serverInfoResult.ErrorMessage) + screen.errorMessage = message + tr(serverInfoResult.ErrorMessage) SignOut(false) else screen.visible = false - if m.serverInfoResult.serverName <> invalid - return m.serverInfoResult.ServerName + " (Saved)" + if isValid(serverInfoResult.serverName) + return serverInfoResult.ServerName + " (Saved)" else return "Saved" end if @@ -226,7 +294,7 @@ function CreateServerGroup() serverPicker = screen.findNode("serverPicker") itemToDelete = serverPicker.content.getChild(serverPicker.itemFocused) urlToDelete = itemToDelete.baseUrl - if urlToDelete <> invalid + if isValid(urlToDelete) DeleteFromServerList(urlToDelete) serverPicker.content.removeChild(itemToDelete) sidepanel.visible = false @@ -283,17 +351,18 @@ function CreateSigninGroup(user = "") group.findNode("prompt").text = tr("Sign In") 'Load in any saved server data and see if we can just log them in... - server = get_setting("server") - if server <> invalid + server = m.global.session.server.url + if isValid(server) server = LCase(server)'Saved server data is always lowercase end if saved = get_setting("saved_servers") - if saved <> invalid + if isValid(saved) savedServers = ParseJson(saved) for each item in savedServers.serverList - if item.baseUrl = server and item.username <> invalid and item.password <> invalid - get_token(item.username, item.password) - if get_setting("active_user") <> invalid + if item.baseUrl = server and isValid(item.username) and isValid(item.password) + userData = get_token(item.username, item.password) + if isValid(userData) + session.user.Login(userData) return "true" end if end if @@ -314,8 +383,9 @@ function CreateSigninGroup(user = "") password_field.label = tr("Password") password_field.field = "password" password_field.type = "password" - if get_setting("password") <> invalid - password_field.value = get_setting("password") + registryPassword = get_setting("password") + if isValid(registryPassword) + password_field.value = registryPassword end if ' Add checkbox for saving credentials checkbox = group.findNode("onOff") @@ -327,11 +397,8 @@ function CreateSigninGroup(user = "") checkbox.content = items checkbox.checkedState = [true] quickConnect = group.findNode("quickConnect") - if m.serverInfoResult = invalid - m.serverInfoResult = ServerInfo() - end if ' Quick Connect only supported for server version 10.8+ right now... - if versionChecker(m.serverInfoResult.Version, "10.8.0") + if versionChecker(m.global.session.server.version, "10.8.0") ' Add option for Quick Connect quickConnect.text = tr("Quick Connect") quickConnect.observeField("buttonSelected", port) @@ -365,8 +432,9 @@ function CreateSigninGroup(user = "") node = msg.getNode() if node = "submit" ' Validate credentials - get_token(username.value, password.value) - if get_setting("active_user") <> invalid + activeUser = get_token(username.value, password.value) + if isValid(activeUser) + session.user.Login(activeUser) set_setting("username", username.value) set_setting("password", password.value) if checkbox.checkedState[0] = true @@ -463,7 +531,7 @@ function CreateHomeGroup() user_options.push({ display: user.username + "@" + user.server, value: user.id }) end for user_node.choices = user_options - user_node.value = get_setting("active_user") + user_node.value = m.global.session.user.id new_options.push(user_node) sidepanel.options = new_options @@ -492,7 +560,7 @@ function CreateMovieDetailsGroup(movie as object) as dynamic m.global.sceneManager.callFunc("pushScene", group) group.itemContent = movieMetaData ' local trailers - trailerData = api.users.GetLocalTrailers(get_setting("active_user"), movie.id) + trailerData = api.users.GetLocalTrailers(m.global.session.user.id, movie.id) if isValid(trailerData) group.trailerAvailable = trailerData.Count() > 0 end if @@ -525,7 +593,7 @@ function CreateSeriesDetailsGroup(seriesID as string) as dynamic ' 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 get_user_setting("ui.tvshows.goStraightToEpisodeListing") = "true" and seasonData.Items.Count() = 1 + if m.global.session.user.settings["ui.tvshows.goStraightToEpisodeListing"] = true and seasonData.Items.Count() = 1 stopLoadingSpinner() return CreateSeasonDetailsGroupByID(seriesID, seasonData.Items[0].id) end if @@ -783,7 +851,7 @@ function CreatePersonView(personData as object) as dynamic end function sub UpdateSavedServerList() - server = get_setting("server") + server = m.global.session.server.url username = get_setting("username") password = get_setting("password") @@ -794,9 +862,9 @@ sub UpdateSavedServerList() server = LCase(server)'Saved server data is always lowercase saved = get_setting("saved_servers") - if saved <> invalid + if isValid(saved) savedServers = ParseJson(saved) - if savedServers.serverList <> invalid and savedServers.serverList.Count() > 0 + if isValid(savedServers.serverList) and savedServers.serverList.Count() > 0 newServers = { serverList: [] } for each item in savedServers.serverList if item.baseUrl = server diff --git a/source/VideoPlayer.brs b/source/VideoPlayer.brs index 5a696d8a..89e9c6fa 100644 --- a/source/VideoPlayer.brs +++ b/source/VideoPlayer.brs @@ -82,7 +82,7 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx params = { ids: video.Id } - url = Substitute("Users/{0}/Items/", get_setting("active_user")) + url = Substitute("Users/{0}/Items/", m.global.session.user.id) resp = APIRequest(url, params) data = getJson(resp) for each item in data.Items @@ -92,7 +92,7 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx params = { ids: m.series_id } - url = Substitute("Users/{0}/Items/", get_setting("active_user")) + url = Substitute("Users/{0}/Items/", m.global.session.user.id) resp = APIRequest(url, params) data = getJson(resp) for each item in data.Items @@ -109,7 +109,7 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx params = { ids: video.Id } - url = Substitute("Users/{0}/Items/", get_setting("active_user")) + url = Substitute("Users/{0}/Items/", m.global.session.user.id) resp = APIRequest(url, params) data = getJson(resp) for each item in data.Items @@ -120,7 +120,7 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx params = { ids: m.season_id } - url = Substitute("Users/{0}/Items/", get_setting("active_user")) + url = Substitute("Users/{0}/Items/", m.global.session.user.id) resp = APIRequest(url, params) data = getJson(resp) for each item in data.Items @@ -130,7 +130,7 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx params = { ids: m.series_id } - url = Substitute("Users/{0}/Items/", get_setting("active_user")) + url = Substitute("Users/{0}/Items/", m.global.session.user.id) resp = APIRequest(url, params) data = getJson(resp) for each item in data.Items @@ -147,7 +147,7 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx params = { ids: video.Id } - url = Substitute("Users/{0}/Items/", get_setting("active_user")) + url = Substitute("Users/{0}/Items/", m.global.session.user.id) resp = APIRequest(url, params) data = getJson(resp) for each item in data.Items @@ -217,7 +217,7 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx end if subtitles = sortSubtitles(meta.id, m.playbackInfo.MediaSources[0].MediaStreams) - if get_user_setting("playback.subs.onlytext") = "true" + if m.global.session.user.settings["playback.subs.onlytext"] = true safesubs = [] for each subtitle in subtitles["all"] if subtitle["IsTextSubtitleStream"] @@ -249,8 +249,8 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx ' transcode is that the Encoding Level is not supported, then try to direct play but silently ' fall back to the transcode if that fails. if m.playbackInfo.MediaSources[0].MediaStreams.Count() > 0 and meta.live = false - tryDirectPlay = get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264" - tryDirectPlay = tryDirectPlay or (get_user_setting("playback.tryDirect.hevcProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc") + tryDirectPlay = m.global.session.user.settings["playback.tryDirect.h264ProfileLevel"] = true and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264" + tryDirectPlay = tryDirectPlay or (m.global.session.user.settings["playback.tryDirect.hevcProfileLevel"] = true and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc") if tryDirectPlay and isValid(m.playbackInfo.MediaSources[0].TranscodingUrl) and forceTranscoding = false transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl) if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported" @@ -318,7 +318,7 @@ end sub function PlayIntroVideo(video_id, audio_stream_idx) as boolean ' Intro videos only play if user has cinema mode setting enabled - if get_user_setting("playback.cinemamode") = "true" + if m.global.session.user.settings["playback.cinemamode"] = true ' Check if server has intro videos setup and available introVideos = GetIntroVideos(video_id) @@ -441,10 +441,10 @@ end function sub autoPlayNextEpisode(videoID as string, showID as string) ' use web client setting - if m.user.Configuration.EnableNextEpisodeAutoPlay + if m.global.session.user.configuration.EnableNextEpisodeAutoPlay ' query API for next episode ID url = Substitute("Shows/{0}/Episodes", showID) - urlParams = { "UserId": get_setting("active_user") } + urlParams = { "UserId": m.global.session.user.id } urlParams.Append({ "StartItemId": videoID }) urlParams.Append({ "Limit": 2 }) resp = APIRequest(url, urlParams) @@ -481,15 +481,15 @@ function GetPlaybackInfo() return [errMsg] end function -function GetTranscodingStats(session) +function GetTranscodingStats(deviceSession) sessionStats = [] - if isValid(session.TranscodingInfo) and session.TranscodingInfo.Count() > 0 - transcodingReasons = session.TranscodingInfo.TranscodeReasons - videoCodec = session.TranscodingInfo.VideoCodec - audioCodec = session.TranscodingInfo.AudioCodec - totalBitrate = session.TranscodingInfo.Bitrate - audioChannels = session.TranscodingInfo.AudioChannels + if isValid(deviceSession.TranscodingInfo) and deviceSession.TranscodingInfo.Count() > 0 + transcodingReasons = deviceSession.TranscodingInfo.TranscodeReasons + videoCodec = deviceSession.TranscodingInfo.VideoCodec + audioCodec = deviceSession.TranscodingInfo.AudioCodec + totalBitrate = deviceSession.TranscodingInfo.Bitrate + audioChannels = deviceSession.TranscodingInfo.AudioChannels if isValid(transcodingReasons) and transcodingReasons.Count() > 0 sessionStats.push("** " + tr("Transcoding Information") + " **") @@ -500,7 +500,7 @@ function GetTranscodingStats(session) if isValid(videoCodec) data = tr("Video Codec") + ": " + videoCodec - if session.TranscodingInfo.IsVideoDirect + if deviceSession.TranscodingInfo.IsVideoDirect data = data + " (" + tr("direct") + ")" end if sessionStats.push(data) @@ -508,7 +508,7 @@ function GetTranscodingStats(session) if isValid(audioCodec) data = tr("Audio Codec") + ": " + audioCodec - if session.TranscodingInfo.IsAudioDirect + if deviceSession.TranscodingInfo.IsAudioDirect data = data + " (" + tr("direct") + ")" end if sessionStats.push(data) diff --git a/source/api/Items.brs b/source/api/Items.brs index 61af42e9..1d978656 100644 --- a/source/api/Items.brs +++ b/source/api/Items.brs @@ -1,6 +1,6 @@ function ItemGetPlaybackInfo(id as string, startTimeTicks = 0 as longinteger) params = { - "UserId": get_setting("active_user"), + "UserId": m.global.session.user.id, "StartTimeTicks": startTimeTicks, "IsPlayback": true, "AutoOpenLiveStream": true, @@ -15,7 +15,7 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT "DeviceProfile": getDeviceProfile() } params = { - "UserId": get_setting("active_user"), + "UserId": m.global.session.user.id, "StartTimeTicks": startTimeTicks, "IsPlayback": true, "AutoOpenLiveStream": true, @@ -39,7 +39,7 @@ function searchMedia(query as string) ' For each potential type, a separate query is done: ' varying item types, and artists, and people if query <> "" - resp = APIRequest(Substitute("Search/Hints", get_setting("active_user")), { + resp = APIRequest(Substitute("Search/Hints", m.global.session.user.id), { "searchTerm": query, "IncludePeople": true, "IncludeMedia": true, @@ -70,7 +70,7 @@ end function ' MetaData about an item function ItemMetaData(id as string) - url = Substitute("Users/{0}/Items/{1}", get_setting("active_user"), id) + url = Substitute("Users/{0}/Items/{1}", m.global.session.user.id, id) resp = APIRequest(url) data = getJson(resp) if data = invalid then return invalid @@ -178,7 +178,7 @@ end function ' Get list of albums belonging to an artist function MusicAlbumList(id as string) - url = Substitute("Users/{0}/Items", get_setting("active_user")) + url = Substitute("Users/{0}/Items", m.global.session.user.id) resp = APIRequest(url, { "AlbumArtistIds": id, "includeitemtypes": "MusicAlbum", @@ -200,7 +200,7 @@ end function ' Get list of albums an artist appears on function AppearsOnList(id as string) - url = Substitute("Users/{0}/Items", get_setting("active_user")) + url = Substitute("Users/{0}/Items", m.global.session.user.id) resp = APIRequest(url, { "ContributingArtistIds": id, "ExcludeItemIds": id, @@ -224,7 +224,7 @@ end function ' Get list of songs belonging to an artist function GetSongsByArtist(id as string) - url = Substitute("Users/{0}/Items", get_setting("active_user")) + url = Substitute("Users/{0}/Items", m.global.session.user.id) resp = APIRequest(url, { "AlbumArtistIds": id, "includeitemtypes": "Audio", @@ -253,7 +253,7 @@ end function function PlaylistItemList(id as string) url = Substitute("Playlists/{0}/Items", id) resp = APIRequest(url, { - "UserId": get_setting("active_user") + "UserId": m.global.session.user.id }) results = [] @@ -275,9 +275,9 @@ end function ' Get Songs that are on an Album function MusicSongList(id as string) - url = Substitute("Users/{0}/Items", get_setting("active_user"), id) + url = Substitute("Users/{0}/Items", m.global.session.user.id, id) resp = APIRequest(url, { - "UserId": get_setting("active_user"), + "UserId": m.global.session.user.id, "parentId": id, "includeitemtypes": "Audio", "sortBy": "SortName" @@ -302,9 +302,9 @@ end function ' Get Songs that are on an Album function AudioItem(id as string) - url = Substitute("Users/{0}/Items/{1}", get_setting("active_user"), id) + url = Substitute("Users/{0}/Items/{1}", m.global.session.user.id, id) resp = APIRequest(url, { - "UserId": get_setting("active_user"), + "UserId": m.global.session.user.id, "includeitemtypes": "Audio", "sortBy": "SortName" }) @@ -316,7 +316,7 @@ end function function CreateInstantMix(id as string) url = Substitute("/Items/{0}/InstantMix", id) resp = APIRequest(url, { - "UserId": get_setting("active_user"), + "UserId": m.global.session.user.id, "Limit": 201 }) @@ -325,7 +325,7 @@ end function ' Get Instant Mix based on item function CreateArtistMix(id as string) - url = Substitute("Users/{0}/Items", get_setting("active_user")) + url = Substitute("Users/{0}/Items", m.global.session.user.id) resp = APIRequest(url, { "ArtistIds": id, "Recursive": "true", @@ -344,9 +344,9 @@ end function ' Get Intro Videos for an item function GetIntroVideos(id as string) - url = Substitute("Users/{0}/Items/{1}/Intros", get_setting("active_user"), id) + url = Substitute("Users/{0}/Items/{1}/Intros", m.global.session.user.id, id) resp = APIRequest(url, { - "UserId": get_setting("active_user") + "UserId": m.global.session.user.id }) return getJson(resp) @@ -399,7 +399,8 @@ end function ' Seasons for a TV Show function TVSeasons(id as string) as dynamic url = Substitute("Shows/{0}/Seasons", id) - resp = APIRequest(url, { "UserId": get_setting("active_user") }) + resp = APIRequest(url, { "UserId": m.global.session.user.id }) + data = getJson(resp) ' validate data if data = invalid or data.Items = invalid then return invalid @@ -418,7 +419,7 @@ end function function TVEpisodes(show_id as string, season_id as string) as dynamic url = Substitute("Shows/{0}/Episodes", show_id) - resp = APIRequest(url, { "seasonId": season_id, "UserId": get_setting("active_user"), "fields": "MediaStreams" }) + resp = APIRequest(url, { "seasonId": season_id, "UserId": m.global.session.user.id, "fields": "MediaStreams" }) data = getJson(resp) ' validate data @@ -447,7 +448,7 @@ end function function TVEpisodeShuffleList(show_id as string) url = Substitute("Shows/{0}/Episodes", show_id) resp = APIRequest(url, { - "UserId": get_setting("active_user"), + "UserId": m.global.session.user.id, "Limit": 200, "sortBy": "Random" }) diff --git a/source/api/UserLibrary.brs b/source/api/UserLibrary.brs index f0ffafd4..f9746e3d 100644 --- a/source/api/UserLibrary.brs +++ b/source/api/UserLibrary.brs @@ -1,11 +1,11 @@ function MarkItemFavorite(id as string) - url = Substitute("Users/{0}/FavoriteItems/{1}", get_setting("active_user"), id) + url = Substitute("Users/{0}/FavoriteItems/{1}", m.global.session.user.id, id) resp = APIRequest(url) return postJson(resp) end function function UnmarkItemFavorite(id as string) - url = Substitute("Users/{0}/FavoriteItems/{1}", get_setting("active_user"), id) + url = Substitute("Users/{0}/FavoriteItems/{1}", m.global.session.user.id, id) resp = APIRequest(url) resp.setRequest("DELETE") return getJson(resp) @@ -14,13 +14,13 @@ end function sub MarkItemWatched(id as string) date = CreateObject("roDateTime") dateStr = date.ToISOString() - url = Substitute("Users/{0}/PlayedItems/{1}", get_setting("active_user"), id) + url = Substitute("Users/{0}/PlayedItems/{1}", m.global.session.user.id, id) req = APIRequest(url) postVoid(req, FormatJson({ "DatePlayed": dateStr })) end sub function UnmarkItemWatched(id as string) - url = Substitute("Users/{0}/PlayedItems/{1}", get_setting("active_user"), id) + url = Substitute("Users/{0}/PlayedItems/{1}", m.global.session.user.id, id) resp = APIRequest(url) resp.setRequest("DELETE") return getJson(resp) diff --git a/source/api/baserequest.brs b/source/api/baserequest.brs index 0d9c71c5..e1882b60 100644 --- a/source/api/baserequest.brs +++ b/source/api/baserequest.brs @@ -57,12 +57,14 @@ function APIRequest(url as string, params = {} as object) as dynamic full_url = buildURL(url, params) if full_url = invalid then return invalid + serverURL = m.global.session.server.url + if serverURL = invalid then return invalid + req = createObject("roUrlTransfer") req.setUrl(full_url) req = authorize_request(req) ' SSL cert - serverURL = get_setting("server") - if serverURL <> invalid and serverURL.left(8) = "https://" + if serverURL.left(8) = "https://" req.setCertificatesFile("common:/certs/ca-bundle.crt") end if @@ -156,16 +158,16 @@ function deleteVoid(req) end function function get_url() - serverURL = get_setting("server") - if serverURL = invalid then return invalid + serverURL = m.global.session.server.url + if serverURL <> invalid + if serverURL.right(1) = "/" + serverURL = serverURL.left(serverURL.len() - 1) + end if - if serverURL.right(1) = "/" - serverURL = serverURL.left(serverURL.len() - 1) - end if - - ' append http:// to the start if not specified - if serverURL.left(7) <> "http://" and serverURL.left(8) <> "https://" - serverURL = "http://" + serverURL + ' append http:// to the start if not specified + if serverURL.left(7) <> "http://" and serverURL.left(8) <> "https://" + serverURL = "http://" + serverURL + end if end if return serverURL end function @@ -188,23 +190,18 @@ function postString(req, data = "" as string) end function function authorize_request(request) - user = get_setting("active_user") - auth = "MediaBrowser" + " Client=" + Chr(34) + "Jellyfin Roku" + Chr(34) auth = auth + ", Device=" + Chr(34) + m.global.device.name + " (" + m.global.device.friendlyName + ")" + Chr(34) - - if user <> invalid and user <> "" - auth = auth + ", DeviceId=" + Chr(34) + m.global.device.id + Chr(34) - auth = auth + ", UserId=" + Chr(34) + user + Chr(34) - else - auth = auth + ", DeviceId=" + Chr(34) + m.global.device.uuid + Chr(34) - end if - auth = auth + ", Version=" + Chr(34) + m.global.app.version + Chr(34) - token = get_user_setting("token") - if token <> invalid and token <> "" - auth = auth + ", Token=" + Chr(34) + token + Chr(34) + if m.global.session.user.id <> invalid + auth = auth + ", DeviceId=" + Chr(34) + m.global.device.id + Chr(34) + auth = auth + ", UserId=" + Chr(34) + m.global.session.user.id + Chr(34) + if m.global.session.user.authToken <> invalid + auth = auth + ", Token=" + Chr(34) + m.global.session.user.authToken + Chr(34) + end if + else + auth = auth + ", DeviceId=" + Chr(34) + m.global.device.uuid + Chr(34) end if request.AddHeader("Authorization", auth) diff --git a/source/api/userauth.brs b/source/api/userauth.brs index 8636b1d4..d4de259f 100644 --- a/source/api/userauth.brs +++ b/source/api/userauth.brs @@ -1,3 +1,6 @@ +' needed for SignOut() and ServerInfo() +import "pkg:/source/utils/session.bs" + function get_token(user as string, password as string) url = "Users/AuthenticateByName?format=json" req = APIRequest(url) @@ -14,22 +17,29 @@ function get_token(user as string, password as string) return userdata end function -function AboutMe() - id = get_setting("active_user") +function AboutMe(id = "" as string) + if id = "" + if m.global.session.user.id <> invalid + id = m.global.session.user.id + else + return invalid + end if + end if + url = Substitute("Users/{0}", id) resp = APIRequest(url) return getJson(resp) end function sub SignOut(deleteSavedEntry = true as boolean) - if get_setting("active_user") <> invalid + if m.global.session.user.id <> invalid unset_user_setting("token") unset_setting("username") unset_setting("password") if deleteSavedEntry = true 'Also delete any credentials in the "saved servers" list saved = get_setting("saved_servers") - server = get_setting("server") + server = m.global.session.server.url if server <> invalid server = LCase(server) savedServers = ParseJson(saved) @@ -46,6 +56,7 @@ sub SignOut(deleteSavedEntry = true as boolean) end if end if unset_setting("active_user") + session.user.Logout() m.global.sceneManager.currentUser = "" group = m.global.sceneManager.callFunc("getActiveScene") group.optionsAvailable = false @@ -56,24 +67,6 @@ function AvailableUsers() return users end function -sub PickUser(id as string) - this_user = invalid - for each user in AvailableUsers() - if user.id = id then this_user = user - end for - if this_user = invalid then return - set_setting("active_user", this_user.id) - set_setting("server", this_user.server) -end sub - -sub RemoveUser(id as string) - user = CreateObject("roSGNode", "UserData") - user.id = id - user.callFunc("removeFromRegistry") - - if get_setting("active_user") = id then SignOut(false) -end sub - function ServerInfo() url = "System/Info/Public" req = APIRequest(url) @@ -97,6 +90,7 @@ function ServerInfo() ' set the server to new location and try again if right(headers.location, 19) = "/System/Info/Public" set_setting("server", left(headers.location, len(headers.location) - 19)) + session.server.UpdateURL(left(headers.location, len(headers.location) - 19)) info = ServerInfo() if info.Error info.UpdatedUrl = left(headers.location, len(headers.location) - 19) @@ -134,7 +128,7 @@ end function ' Load and parse Display Settings from server sub LoadUserPreferences() - id = get_setting("active_user") + id = m.global.session.user.id ' Currently using client "emby", which is what website uses so we get same Display prefs as web. ' May want to change to specific Roku display settings url = Substitute("DisplayPreferences/usersettings?userId={0}&client=emby", id) @@ -148,14 +142,13 @@ sub LoadUserPreferences() end if end sub -sub LoadUserAbilities(user) - ' Only have one thing we're checking now, but in the future it could be more... - if user.Policy.EnableLiveTvManagement = true +sub LoadUserAbilities() + if m.global.session.user.Policy.EnableLiveTvManagement = true set_user_setting("livetv.canrecord", "true") else set_user_setting("livetv.canrecord", "false") end if - if user.Policy.EnableContentDeletion = true + if m.global.session.user.Policy.EnableContentDeletion = true set_user_setting("content.candelete", "true") else set_user_setting("content.candelete", "false") @@ -197,9 +190,11 @@ function AuthenticateViaQuickConnect(secret) } req = APIRequest("Users/AuthenticateWithQuickConnect") jsonResponse = postJson(req, FormatJson(params)) - if jsonResponse <> invalid and jsonResponse.AccessToken <> invalid + if jsonResponse <> invalid and jsonResponse.AccessToken <> invalid and jsonResponse.User <> invalid userdata = CreateObject("roSGNode", "UserData") userdata.json = jsonResponse + session.user.Update("id", jsonResponse.User.Id) + session.user.Update("authToken", jsonResponse.AccessToken) userdata.callFunc("setActive") userdata.callFunc("saveToRegistry") return true diff --git a/source/utils/Subtitles.brs b/source/utils/Subtitles.brs index ba9a41a0..3bd4d90d 100644 --- a/source/utils/Subtitles.brs +++ b/source/utils/Subtitles.brs @@ -27,7 +27,7 @@ function defaultSubtitleTrackFromVid(video_id) as integer if default_text_subs <> -1 return default_text_subs else - if get_user_setting("playback.subs.onlytext") = "false" + if m.global.session.user.settings["playback.subs.onlytext"] = false return defaultSubtitleTrack(subtitles["all"]) ' if no appropriate text subs exist, allow non-text else return -1 @@ -44,23 +44,23 @@ end function ' This allows forcing text subs, since roku requires transcoding of non-text subs ' returns the server-side track index for the appriate subtitle function defaultSubtitleTrack(sorted_subtitles, require_text = false) as integer - if m.user.Configuration.SubtitleMode = "None" + if m.global.session.user.configuration.SubtitleMode = "None" return -1 ' No subtitles desired: select none end if for each item in sorted_subtitles ' Only auto-select subtitle if language matches preference - languageMatch = (m.user.Configuration.SubtitleLanguagePreference = item.Track.Language) + languageMatch = (m.global.session.user.configuration.SubtitleLanguagePreference = item.Track.Language) ' Ensure textuality of subtitle matches preferenced passed as arg matchTextReq = ((require_text and item.IsTextSubtitleStream) or not require_text) if languageMatch and matchTextReq - if m.user.Configuration.SubtitleMode = "Default" and (item.isForced or item.IsDefault or item.IsExternal) + if m.global.session.user.configuration.SubtitleMode = "Default" and (item.isForced or item.IsDefault or item.IsExternal) return item.Index ' Finds first forced, or default, or external subs in sorted list - else if m.user.Configuration.SubtitleMode = "Always" and not item.IsForced + else if m.global.session.user.configuration.SubtitleMode = "Always" and not item.IsForced return item.Index ' Select the first non-forced subtitle option in the sorted list - else if m.user.Configuration.SubtitleMode = "OnlyForced" and item.IsForced + else if m.global.session.user.configuration.SubtitleMode = "OnlyForced" and item.IsForced return item.Index ' Select the first forced subtitle option in the sorted list - else if m.user.Configuration.SubtitlePlaybackMode = "Smart" and (item.isForced or item.IsDefault or item.IsExternal) + else if m.global.session.user.configuration.SubtitlePlaybackMode = "Smart" and (item.isForced or item.IsDefault or item.IsExternal) ' Simplified "Smart" logic here mimics Default (as that is fallback behavior normally) ' Avoids detecting preferred audio language (as is utilized in main client) return item.Index @@ -192,7 +192,7 @@ end sub function sortSubtitles(id as string, MediaStreams) tracks = { "forced": [], "default": [], "normal": [] } 'Too many args for using substitute - prefered_lang = m.user.Configuration.SubtitleLanguagePreference + prefered_lang = m.global.session.user.configuration.SubtitleLanguagePreference for each stream in MediaStreams if stream.type = "Subtitle" diff --git a/source/utils/config.brs b/source/utils/config.brs index 88fd446e..3c82877f 100644 --- a/source/utils/config.brs +++ b/source/utils/config.brs @@ -1,11 +1,11 @@ -' "Registry" is where Roku stores config +' needed for set_user_setting() and unset_user_setting() +import "pkg:/source/utils/session.bs" ' Read config tree from json config file and return function GetConfigTree() return ParseJSON(ReadAsciiFile("pkg:/settings/settings.json")) end function - ' Generic registry accessors function registry_read(key, section = invalid) if section = invalid then return invalid @@ -28,6 +28,24 @@ sub registry_delete(key, section = invalid) reg.flush() end sub +' Return all data found inside a registry section +function RegistryReadAll(section as string) as dynamic + if section = "" then return invalid + + registry = CreateObject("roRegistrySection", section) + regKeyList = registry.GetKeyList() + registryData = {} + for each item in regKeyList + ' ignore session related tokens + if item <> "token" and item <> "username" and item <> "password" + if registry.Exists(item) + registryData.AddReplace(item, registry.Read(item)) + end if + end if + end for + + return registryData +end function ' "Jellyfin" registry accessors for the default global settings function get_setting(key, default = invalid) @@ -44,38 +62,25 @@ sub unset_setting(key) registry_delete(key, "Jellyfin") end sub - ' User registry accessors for the currently active user -function get_user_setting(key, default = invalid) - if get_setting("active_user") = invalid then return default - value = registry_read(key, get_setting("active_user")) - if value = invalid - - ' Check for default in Config Tree - configTree = GetConfigTree() - configKey = findConfigTreeKey(key, configTree) - - if configKey <> invalid and configKey.default <> invalid - set_user_setting(key, configKey.default) ' Set user setting to default - return configKey.default - end if - - return default - end if +function get_user_setting(key as string) as dynamic + if key = "" or m.global.session.user.id = invalid then return invalid + value = registry_read(key, m.global.session.user.id) return value end function -sub set_user_setting(key, value) - if get_setting("active_user") = invalid then return - registry_write(key, value, get_setting("active_user")) +sub set_user_setting(key as string, value as dynamic) + if m.global.session.user.id = invalid then return + session.user.settings.Save(key, value) + registry_write(key, value, m.global.session.user.id) end sub -sub unset_user_setting(key) - if get_setting("active_user") = invalid then return - registry_delete(key, get_setting("active_user")) +sub unset_user_setting(key as string) + if m.global.session.user.id = invalid then return + session.user.settings.Delete(key) + registry_delete(key, m.global.session.user.id) end sub - ' Recursivly search the config tree for entry with settingname equal to key function findConfigTreeKey(key as string, tree) for each item in tree @@ -89,5 +94,3 @@ function findConfigTreeKey(key as string, tree) return invalid end function - - diff --git a/source/utils/deviceCapabilities.brs b/source/utils/deviceCapabilities.brs index e42a8c54..f206b837 100644 --- a/source/utils/deviceCapabilities.brs +++ b/source/utils/deviceCapabilities.brs @@ -23,8 +23,8 @@ sub PostDeviceProfile() end sub function getDeviceProfile() as object - playMpeg2 = get_user_setting("playback.mpeg2") = "true" - playAv1 = get_user_setting("playback.av1") = "true" + playMpeg2 = m.global.session.user.settings["playback.mpeg2"] + playAv1 = m.global.session.user.settings["playback.av1"] 'Check if 5.1 Audio Output connected maxAudioChannels = 2 @@ -270,7 +270,7 @@ function GetDirectPlayProfiles() as object mkvAudio = "mp3,pcm,lpcm,wav" audio = "mp3,pcm,lpcm,wav" - playMpeg2 = get_user_setting("playback.mpeg2") = "true" + playMpeg2 = m.global.session.user.settings["playback.mpeg2"] di = CreateObject("roDeviceInfo") @@ -289,7 +289,7 @@ function GetDirectPlayProfiles() as object mkvVideo = mkvVideo + ",mpeg2video" end if - if get_user_setting("playback.mpeg4") = "true" + if m.global.session.user.settings["playback.mpeg4"] = true mp4Video = mp4Video + ",mpeg4" end if @@ -366,8 +366,8 @@ function GetDirectPlayProfiles() as object end function function GetBitRateLimit(codec as string) - if get_user_setting("playback.bitrate.maxlimited") = "true" - userSetLimit = get_user_setting("playback.bitrate.limit").ToInt() + if m.global.session.user.settings["playback.bitrate.maxlimited"] = true + userSetLimit = m.global.session.user.settings["playback.bitrate.limit"] userSetLimit *= 1000000 if userSetLimit > 0 diff --git a/source/utils/session.bs b/source/utils/session.bs new file mode 100644 index 00000000..ed323e6d --- /dev/null +++ b/source/utils/session.bs @@ -0,0 +1,255 @@ +' these are needed for ServerInfo() inside session.server.Populate() +import "pkg:/source/api/userauth.brs" +import "pkg:/source/api/baserequest.brs" + +namespace session + ' Initialize the global session array + sub Init() + m.global.addFields({ + session: { + server: {}, + user: { + Configuration: {}, + Policy: {}, + settings: {} + } + } + }) + session.user.settings.SaveDefaults() + end sub + + ' Empty the global session array + sub Delete() + session.server.Delete() + session.user.Logout() + end sub + + ' Update one value from the global session array (m.global.session) + sub Update(key as string, value = {} as object) + ' validate parameters + if key = "" or (key <> "user" and key <> "server") or value = invalid + print "Error in session.Update(): Invalid parameters provided" + return + end if + ' make copy of global session array + tmpSession = m.global.session + ' update the temp session array + tmpSession.AddReplace(key, value) + ' use the temp session array to update the global node + m.global.setFields({ session: tmpSession }) + ' print "m.global.session." + key + " = ", m.global.session[key] + end sub + + namespace server + ' Empty the global server session array + sub Delete() + session.Update("server") + end sub + + ' Add or update one value from the global server session array (m.global.session.server) + sub Update(key as string, value as dynamic) + ' validate parameters + if key = "" or value = invalid then return + ' make copy of global server session array + tmpSessionServer = m.global.session.server + ' update the temp server array + tmpSessionServer[key] = value + + session.Update("server", tmpSessionServer) + end sub + + ' Add or update the jellyfin server URL from the global server session array (m.global.session.server) + sub UpdateURL(value as string) + ' validate parameters + if value = "" then return + session.server.Update("url", value) + session.server.Populate() + end sub + + ' Use the saved server url to populate the global server session array (m.global.session.server) + sub Populate() + ' validate server url + if m.global.session.server.url = invalid or m.global.session.server.url = "" then return + ' get server info using API + myServerInfo = ServerInfo() + ' validate data returned from API + if myServerInfo.id = invalid then return + ' make copy of global server session + tmpSessionServer = m.global.session.server + ' update the temp array + tmpSessionServer.AddReplace("id", myServerInfo.Id) + tmpSessionServer.AddReplace("name", myServerInfo.ServerName) + tmpSessionServer.AddReplace("localURL", myServerInfo.LocalAddress) + tmpSessionServer.AddReplace("os", myServerInfo.OperatingSystem) + tmpSessionServer.AddReplace("startupWizardCompleted", myServerInfo.StartupWizardCompleted) + tmpSessionServer.AddReplace("version", myServerInfo.Version) + tmpSessionServer.AddReplace("hasError", myServerInfo.error) + ' check urls for https + isServerHTTPS = false + if tmpSessionServer.url.left(8) = "https://" then isServerHTTPS = true + tmpSessionServer.AddReplace("isHTTPS", isServerHTTPS) + isLocalServerHTTPS = false + if myServerInfo.LocalAddress <> invalid and myServerInfo.LocalAddress.left(8) = "https://" then isLocalServerHTTPS = true + tmpSessionServer.AddReplace("isLocalHTTPS", isLocalServerHTTPS) + ' update global server session using the temp array + session.Update("server", tmpSessionServer) + end sub + end namespace + + namespace user + + ' Add or update one value from the global user session array (m.global.session.user) + sub Update(key as string, value as dynamic) + ' validate parameters + if key = "" or value = invalid then return + ' make copy of global user session + tmpSessionUser = m.global.session.user + ' update the temp user array + tmpSessionUser[key] = value + ' update global user session using the temp array + session.Update("user", tmpSessionUser) + end sub + + ' Update the global session after user is authenticated. + ' Accepts a UserData.xml object from get_token() or an assocArray from AboutMe() + sub Login(userData as object) + ' validate parameters + if userData = invalid or userData.id = invalid then return + ' make copy of global user session array + tmpSession = m.global.session + oldUserSettings = tmpSession.user.settings + if userData.json = invalid + ' we were passed data from AboutMe() + myAuthToken = tmpSession.user.authToken + tmpSession.AddReplace("user", userData) + tmpSession.user.AddReplace("authToken", myAuthToken) + else + ' we were passed data from a UserData object + tmpSession.AddReplace("user", userData.json.User) + tmpSession.user.AddReplace("authToken", userData.json.AccessToken) + end if + + tmpSession.user.AddReplace("settings", oldUserSettings) + ' update global user session + session.Update("user", tmpSession.user) + + ' update user session settings with values from registry + userSettings = RegistryReadAll(tmpSession.user.id) + for each setting in userSettings + session.user.settings.Save(setting, userSettings[setting]) + end for + + if m.global.app.isDev + print "m.global.session.user.settings = ", m.global.session.user.settings + end if + ' ensure registry is updated + set_user_setting("username", tmpSession.user.name) + set_user_setting("token", tmpSession.user.authToken) + end sub + + ' Empty the global user session array and reload defaults + sub Logout() + session.Update("user", { + Configuration: {}, + Policy: {}, + settings: {} + }) + ' reload default user settings + session.user.settings.SaveDefaults() + end sub + + namespace settings + ' Delete the user setting from the global session (m.global.session.user.settings) + sub Delete(name as string) + ' validate parameters + if name = "" then return + tmpSettingArray = m.global.session.user.settings + ' update the temp user array + tmpSettingArray.Delete(name) + ' update global user session using the temp array + session.user.Update("settings", tmpSettingArray) + end sub + + ' Read the user setting from the global session (m.global.session.user.settings) + function Read(name as string) as dynamic + ' validate parameters + if name = "" then return invalid + + if m.global.session.user.settings[name] <> invalid + return m.global.session.user.settings[name] + else + return invalid + end if + end function + + ' retrieve all default user settings from Config Tree + sub SaveDefaults() + configTree = GetConfigTree() + if configTree = invalid then return + + for each item in configTree + if item.default <> invalid and item.settingName <> invalid + session.user.settings.Save(item.settingName, item.default) + else if item.children <> invalid and item.children.Count() > 0 + for each child in item.children + if child.default <> invalid and child.settingName <> invalid + session.user.settings.Save(child.settingName, child.default) + else if child.children <> invalid and child.children.Count() > 0 + for each child in child.children + if child.default <> invalid and child.settingName <> invalid + session.user.settings.Save(child.settingName, child.default) + else if child.children <> invalid and child.children.Count() > 0 + for each child in child.children + if child.default <> invalid and child.settingName <> invalid + session.user.settings.Save(child.settingName, child.default) + else if child.children <> invalid and child.children.Count() > 0 + for each child in child.children + if child.default <> invalid and child.settingName <> invalid + session.user.settings.Save(child.settingName, child.default) + else if child.children <> invalid and child.children.Count() > 0 + for each child in child.children + if child.default <> invalid and child.settingName <> invalid + session.user.settings.Save(child.settingName, child.default) + end if + end for + end if + end for + end if + end for + end if + end for + end if + end for + end if + end for + end sub + + ' Saves the user setting to the global session. + ' This also converts strings to boolean and integer as necessary before saving to global session + sub Save(name as string, value as string) + if name = invalid or value = invalid then return + tmpSettingArray = m.global.session.user.settings + convertedValue = value + + ' convert to int + valueInteger = value.ToInt() + if value = "0" or valueInteger <> 0 + convertedValue = valueInteger + end if + + ' convert to boolean + if type(value) = "String" + if value = "true" + convertedValue = true + else if value = "false" + convertedValue = false + end if + end if + + tmpSettingArray[name] = convertedValue + + session.user.Update("settings", tmpSettingArray) + end sub + end namespace + end namespace +end namespace