Merge remote-tracking branch 'upstream/unstable' into update-device-profile

This commit is contained in:
Charles Ewert 2023-06-01 09:42:50 -04:00
commit 90ca547020
47 changed files with 11417 additions and 10879 deletions

View File

@ -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
})

View File

@ -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("<header>" + tr("Transcoding Information") + "</header>")
@ -65,7 +65,7 @@ function GetTranscodingStats(session)
if isValid(videoCodec)
data = "<b>• " + tr("Video Codec") + ":</b> " + 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 = "<b>• " + tr("Audio Codec") + ":</b> " + audioCodec
if session.TranscodingInfo.IsAudioDirect
if deviceSession.TranscodingInfo.IsAudioDirect
data = data + " (" + tr("direct") + ")"
end if
sessionStats.data.push(data)

View File

@ -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
})

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -6365,5 +6365,18 @@
<translation>Sélectionnez un serveur disponible sur votre réseau local:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>Cinema Mode</source>
<translation>Mode cinéma</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Playback Information</source>
<translation>Informations de lecture</translation>
</message>
<message>
<source>Play Trailer</source>
<translation>Lire la bande annonce</translation>
</message>
</context>
</TS>

View File

@ -10179,5 +10179,137 @@
<source>Unplayed</source>
<translation>Nem játszott</translation>
</message>
<message>
<source>Age</source>
<translation>Életkór</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Mented a hitelesítő adatokat?</translation>
</message>
<message>
<source>Died</source>
<translation>Meghalt</translation>
</message>
<message>
<source>Monday</source>
<translation>Hétfő</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Starts at</source>
<translation>Kezdődik majd</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Vége volt</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Szerda</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Started</source>
<translation>Kezdődött</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Born</source>
<translation>Született</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Szereplők és stáb</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Mentés Törölése</translation>
</message>
<message>
<source>On Now</source>
<translation>Mostantól</translation>
</message>
<message>
<source>today</source>
<translation>ma</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Vasárnap</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Kedd</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Csütörtök</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Szombat</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Started at</source>
<translation>Ekkor kezdődött</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Kezdődni fog</translation>
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Live</source>
<translation>É</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Vége lett</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Műsorújság</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Péntek</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>tegnap</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>holnap</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Ismétlés</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Csatornák</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>View Channel</source>
<translation>Csatorna Megtekintése</translation>
</message>
</context>
</TS>

View File

@ -382,5 +382,98 @@
<source>Save Credentials?</source>
<translation>Saglabāt akreditācijas datus?</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Saglabāt akreditācijas datus?</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Izdzēst Saglabātos</translation>
</message>
<message>
<source>On Now</source>
<translation>Tagad</translation>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Kļūda Saņemot Saturu</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Kļūda Atskaņošanas Laikā</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Notika kļūda, atskaņojot šo vienumu.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>%1 nesatur vienumus</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nosaukums</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Pievienošanas Datums</translation>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtrēt</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Kārtot</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Izkārtojums</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Ilgums</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Atskaņošanas Datums</translation>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Notika kļūda, no servera saņemot datus par šo vienumu.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Izlaiduma Datums</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>IMDb Vērtējums</translation>
</message>
<message>
<source>Born</source>
<translation>Dzimšanas datums</translation>
</message>
<message>
<source>Died</source>
<translation>Nāves datums</translation>
</message>
<message>
<source>Age</source>
<translation>Vecums</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Kritiķu Vērtējums</translation>
</message>
</context>
</TS>

8
package-lock.json generated
View File

@ -25,7 +25,7 @@
"rimraf": "5.0.1",
"roku-deploy": "3.10.2",
"roku-log-bsc-plugin": "0.8.1",
"rooibos-roku": "5.5.2",
"rooibos-roku": "5.5.3",
"ropm": "0.10.15",
"spellchecker-cli": "6.1.1",
"undent": "0.1.0"
@ -4376,9 +4376,9 @@
"dev": true
},
"node_modules/rooibos-roku": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/rooibos-roku/-/rooibos-roku-5.5.2.tgz",
"integrity": "sha512-KBHdQb2qSD1WCzs7P9qxQt4YgPf3hc1CA4nIq9nHeW09+77Ksw9+GW1pjVABW+ip1kZ3Tf8+Bimb8pa4FN5Zmw==",
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/rooibos-roku/-/rooibos-roku-5.5.3.tgz",
"integrity": "sha512-h6iDQPlxEguyBp5bO0mm9qPuckWEH2+h7KDzSgO0ue2pNzwKcAm+tf0wH5gwXFTu9vHgEctpdbv0gr71FZSw1w==",
"dev": true,
"dependencies": {
"source-map": "^0.7.3",

View File

@ -18,7 +18,7 @@
"rimraf": "5.0.1",
"roku-deploy": "3.10.2",
"roku-log-bsc-plugin": "0.8.1",
"rooibos-roku": "5.5.2",
"rooibos-roku": "5.5.3",
"ropm": "0.10.15",
"spellchecker-cli": "6.1.1",
"undent": "0.1.0"

View File

@ -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")

View File

@ -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
m.user = AboutMe()
if m.user = invalid or m.user.id <> get_setting("active_user")
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
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

View File

@ -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)

View File

@ -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"
})

View File

@ -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)

View File

@ -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,9 +158,8 @@ 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
@ -167,6 +168,7 @@ function get_url()
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)

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -48,8 +48,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"]
di = CreateObject("roDeviceInfo")
maxAudioChannels = "2" ' Currently Jellyfin server expects this as a string
@ -390,10 +390,10 @@ function GetDirectPlayProfiles() as object
videoCodecs = ["h264", "vp8", "hevc", "vp9"]
audioCodecs = ["mp3", "pcm", "lpcm", "wav", "ac3", "wma", "flac", "alac", "aac", "opus", "dts", "wmapro", "vorbis", "eac3"]
' respect user settings
if get_user_setting("playback.mpeg4") = "true"
if m.global.session.user.settings["playback.mpeg4"]
videoCodecs.push("mpeg4")
end if
if get_user_setting("playback.mpeg2") = "true"
if m.global.session.user.settings["playback.mpeg2"]
videoCodecs.push("mpeg2")
end if
' check video codecs for each container
@ -449,8 +449,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

255
source/utils/session.bs Normal file
View File

@ -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