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

This commit is contained in:
Charles Ewert 2023-10-03 11:17:26 -04:00
commit 7c2537fabf
26 changed files with 2436 additions and 330 deletions

View File

@ -12,7 +12,7 @@ jobs:
dev:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3
with:
node-version: "lts/*"

View File

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout master (the latest release)
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
with:
ref: master
- name: Install jq to parse json
@ -33,7 +33,7 @@ jobs:
- name: Save old Makefile version
run: awk 'BEGIN { FS=" = " } /^VERSION/ { print "oldMakeVersion="$2; }' Makefile >> $GITHUB_ENV
- name: Checkout PR branch
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- name: Save new package.json version
run: echo "newPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
- name: package.json version must be updated
@ -61,7 +61,7 @@ jobs:
prod:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3
with:
node-version: "lts/*"

View File

@ -11,7 +11,7 @@ jobs:
static:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3
with:
node-version: "lts/*"

View File

@ -53,6 +53,9 @@ sub itemContentChanged()
if itemData.json.UserData.UnplayedItemCount > 0
m.unplayedCount.visible = true
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
else
m.unplayedCount.visible = false
m.unplayedEpisodeCount.text = ""
end if
end if
end if

View File

@ -80,7 +80,6 @@ sub popScene()
group = m.groups.pop()
if group <> invalid
groupType = group.subtype()
if groupType = "JFGroup"
unregisterOverhangData(group)
else if groupType = "JFVideo"

View File

@ -20,9 +20,6 @@ sub loadFromRegistry(id as string)
end sub
sub saveToRegistry()
set_user_setting("username", m.top.username)
set_user_setting("token", m.top.token)
users = parseJson(get_setting("available_users", "[]"))
this_user = invalid
for each user in users
@ -57,7 +54,9 @@ function setPreference(key as string, value as string)
end function
sub setActive()
set_setting("active_user", m.top.id)
if m.global.session.user.settings["global.rememberme"]
set_setting("active_user", m.top.id)
end if
end sub
sub setServer(hostname as string)

View File

@ -38,11 +38,13 @@ end sub
sub trailerAvailableChanged()
if m.top.trailerAvailable
' add trailor button to button group
trailerButton = CreateObject("roSGNode", "JFButton")
trailerButton = CreateObject("roSGNode", "Button")
trailerButton.id = "trailer-button"
trailerButton.text = tr("Play Trailer")
trailerButton.iconUri = ""
trailerButton.focusedIconUri = ""
trailerButton.maxWidth = "300"
trailerButton.minWidth = "300"
trailerButton.minWidth = "280"
m.buttonGrp.appendChild(trailerButton)
else
' remove trailor button from button group

View File

@ -1,10 +1,10 @@
import "pkg:/source/utils/config.brs"
import "pkg:/source/utils/misc.brs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/api/sdk.bs"
sub init()
m.log = log.Logger("Settings")
m.top.overhangTitle = tr("Settings")
m.top.optionsAvailable = false
m.userLocation = []
@ -12,7 +12,6 @@ sub init()
m.settingsMenu = m.top.findNode("settingsMenu")
m.settingDetail = m.top.findNode("settingDetail")
m.settingDesc = m.top.findNode("settingDesc")
m.settingTitle = m.top.findNode("settingTitle")
m.path = m.top.findNode("path")
m.boolSetting = m.top.findNode("boolSetting")
@ -72,7 +71,7 @@ sub LoadMenu(configSection)
end if
' Set Path display
m.path.text = ""
m.path.text = tr("Settings")
for each level in m.userLocation
if level.title <> invalid then m.path.text += " / " + tr(level.title)
end for
@ -82,7 +81,7 @@ sub settingFocused()
selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused]
m.settingDesc.text = tr(selectedSetting.Description)
m.settingTitle.text = tr(selectedSetting.Title)
m.top.overhangTitle = tr(selectedSetting.Title)
' Hide Settings
m.boolSetting.visible = false
@ -160,14 +159,40 @@ end sub
sub boolSettingChanged()
if m.boolSetting.focusedChild = invalid then return
selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused]
if m.boolSetting.checkedItem
set_user_setting(selectedSetting.settingName, "true")
session.user.settings.Save(selectedSetting.settingName, "true")
if Left(selectedSetting.settingName, 7) = "global."
' global user setting
' save to main registry block
set_setting(selectedSetting.settingName, "true")
' setting specific triggers
if selectedSetting.settingName = "global.rememberme"
print "m.global.session.user.id=", m.global.session.user.id
set_setting("active_user", m.global.session.user.id)
end if
else
' regular user setting
' save to user specific registry block
set_user_setting(selectedSetting.settingName, "true")
end if
else
set_user_setting(selectedSetting.settingName, "false")
session.user.settings.Save(selectedSetting.settingName, "false")
if Left(selectedSetting.settingName, 7) = "global."
' global user setting
' save to main registry block
set_setting(selectedSetting.settingName, "false")
' setting specific triggers
if selectedSetting.settingName = "global.rememberme"
unset_setting("active_user")
end if
else
' regular user setting
' save to user specific registry block
set_user_setting(selectedSetting.settingName, "false")
end if
end if
end sub

View File

@ -7,32 +7,35 @@
<LabelList
translation="[120,250]"
id="settingsMenu"
itemSize="[440,48]"
itemSize="[510,54]"
vertFocusAnimationStyle="floatingFocus"
focusBitmapBlendColor="#006fab"
focusedColor="#ffffff"
itemSpacing="[0,5]" />
itemSpacing="[0,9]"
textVertAlign="center" />
<Poster
translation="[710,250]" id="testRectangle" width="880" height="700" uri="pkg:/images/white.9.png"
translation="[710,230]" id="testRectangle" width="1210" height="830" uri="pkg:/images/white.9.png"
blendColor="#3f3f3f" />
<LayoutGroup translation="[1150,275]" id="settingDetail" vertAlignment="top" horizAlignment="center" itemSpacings="[50]">
<LayoutGroup translation="[1278,270]" id="settingDetail" vertAlignment="top" horizAlignment="center" itemSpacings="[50]">
<Label id="settingDesc"
width="1065"
wrap="true"
horizAlign="center"
translation="[750,270]" />
<ScrollingLabel font="font:LargeSystemFont" id="settingTitle" maxWidth="750" />
<Label id="settingDesc" width="750" wrap="true" />
<RadioButtonList id="boolSetting" vertFocusAnimationStyle="floatingFocus">
<ContentNode role="content">
<ContentNode title="Disabled" />
<ContentNode title="Enabled" />
</ContentNode>
</RadioButtonList>
<RadioButtonList id="radioSetting" vertFocusAnimationStyle="floatingFocus" />
</LayoutGroup>
<RadioButtonList id="radioSetting" translation="[900, 450]" inheritParentTransform="false" vertFocusAnimationStyle="floatingFocus" />
<intkeyboard_integerKeyboard translation="[900, 520]" id="integerSetting" maxLength="3" domain="numeric" visible="false" />
<RadioButtonList id="boolSetting" vertFocusAnimationStyle="floatingFocus" translation="[1034, 510]">
<ContentNode role="content">
<ContentNode title="Disabled" />
<ContentNode title="Enabled" />
</ContentNode>
</RadioButtonList>
<intkeyboard_integerKeyboard translation="[1034, 510]" id="integerSetting" maxLength="3" domain="numeric" visible="false" />
</children>
</component>

View File

@ -23,6 +23,9 @@ sub init()
m.top.observeField("content", "onContentChange")
m.top.observeField("selectedSubtitle", "onSubtitleChange")
' Custom Caption Function
m.top.observeField("allowCaptions", "onAllowCaptionsChange")
m.playbackTimer.observeField("fire", "ReportPlayback")
m.bufferPercentage = 0 ' Track whether content is being loaded
m.playReported = false
@ -51,6 +54,51 @@ sub init()
m.top.trickPlayBar.filledBarBlendColor = m.global.constants.colors.blue
end sub
' Only setup captain items if captions are allowed
sub onAllowCaptionsChange()
if not m.top.allowCaptions then return
m.captionGroup = m.top.findNode("captionGroup")
m.captionGroup.createchildren(9, "LayoutGroup")
m.captionTask = createObject("roSGNode", "captionTask")
m.captionTask.observeField("currentCaption", "updateCaption")
m.captionTask.observeField("useThis", "checkCaptionMode")
m.top.observeField("subtitleTrack", "loadCaption")
m.top.observeField("globalCaptionMode", "toggleCaption")
if m.global.session.user.settings["playback.subs.custom"]
m.top.suppressCaptions = true
toggleCaption()
else
m.top.suppressCaptions = false
end if
end sub
' Set caption url to server subtitle track
sub loadCaption()
if m.top.suppressCaptions
m.captionTask.url = m.top.subtitleTrack
end if
end sub
' Toggles visibility of custom subtitles and sets captionTask's player state
sub toggleCaption()
m.captionTask.playerState = m.top.state + m.top.globalCaptionMode
if LCase(m.top.globalCaptionMode) = "on"
m.captionTask.playerState = m.top.state + m.top.globalCaptionMode + "w"
m.captionGroup.visible = true
else
m.captionGroup.visible = false
end if
end sub
' Removes old subtitle lines and adds new subtitle lines
sub updateCaption()
m.captionGroup.removeChildrenIndex(m.captionGroup.getChildCount(), 0)
m.captionGroup.appendChildren(m.captionTask.currentCaption)
end sub
' Event handler for when selectedSubtitle changes
sub onSubtitleChange()
' Save the current video position
m.global.queueManager.callFunc("setTopStartingPoint", int(m.top.position) * 10000000&)
@ -114,7 +162,11 @@ sub onVideoContentLoaded()
m.top.transcodeParams = videoContent[0].transcodeparams
if m.LoadMetaDataTask.isIntro
' Disable trackplay bar for intro videos
m.top.enableTrickPlay = false
else
' Allow custom captions for non intro videos
m.top.allowCaptions = true
end if
if isValid(m.top.audioIndex)
@ -132,21 +184,12 @@ sub onContentChange()
if not isValid(m.top.content) then return
m.top.observeField("position", "onPositionChanged")
' If video content type is not episode, remove position observer
if m.top.content.contenttype <> 4
m.top.unobserveField("position")
end if
end sub
sub onNextEpisodeDataLoaded()
m.checkedForNextEpisode = true
m.top.observeField("position", "onPositionChanged")
if m.getNextEpisodeTask.nextEpisodeData.Items.count() <> 2
m.top.unobserveField("position")
end if
end sub
'
@ -189,16 +232,27 @@ end sub
' When Video Player state changes
sub onPositionChanged()
if isValid(m.captionTask)
m.captionTask.currentPos = Int(m.top.position * 1000)
end if
' Check if dialog is open
m.dialog = m.top.getScene().findNode("dialogBackground")
if not isValid(m.dialog)
checkTimeToDisplayNextEpisode()
' Do not show Next Episode button for intro videos
if not m.LoadMetaDataTask.isIntro
checkTimeToDisplayNextEpisode()
end if
end if
end sub
'
' When Video Player state changes
sub onState(msg)
if isValid(m.captionTask)
m.captionTask.playerState = m.top.state + m.top.globalCaptionMode
end if
' When buffering, start timer to monitor buffering process
if m.top.state = "buffering" and m.bufferCheckTimer <> invalid
@ -319,11 +373,17 @@ function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "down"
m.top.selectSubtitlePressed = true
return true
' Do not show subtitle selection for intro videos
if not m.LoadMetaDataTask.isIntro
m.top.selectSubtitlePressed = true
return true
end if
else if key = "up"
m.top.selectPlaybackInfoPressed = true
return true
' Do not show playback info for intro videos
if not m.LoadMetaDataTask.isIntro
m.top.selectPlaybackInfoPressed = true
return true
end if
else if key = "OK"
' OK will play/pause depending on current state
' return false to allow selection during seeking

View File

@ -7,7 +7,6 @@
<field id="PlaySessionId" type="string" />
<field id="Subtitles" type="array" />
<field id="SelectedSubtitle" type="integer" value="-1" alwaysNotify="true" />
<field id="captionMode" type="string" />
<field id="container" type="string" />
<field id="directPlaySupported" type="boolean" />
<field id="systemOverlay" type="boolean" value="false" />
@ -24,9 +23,11 @@
<field id="mediaSourceId" type="string" />
<field id="fullSubtitleData" type="array" />
<field id="audioIndex" type="integer" />
<field id="allowCaptions" type="boolean" value="false" />
</interface>
<children>
<Group id="captionGroup" translation="[960,1020]" />
<timer id="playbackTimer" repeat="true" duration="30" />
<timer id="bufferCheckTimer" repeat="true" />

View File

@ -11439,5 +11439,113 @@
<translation>Aktivieren oder Deaktivieren von Direct Play für optionale Codecs</translation>
<extracomment>Settings Menu - Title for settings group related to codec support</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Fehler während der Wiedergabe</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Sign Out</source>
<translation>Abmelden</translation>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Fehler beim Laden dieses Eintrags vom Server.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Lade Kanaldaten</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Name</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Fehler beim Laden der Kanaldaten</translation>
</message>
<message>
<source>On Now</source>
<translation>Jetzt läuft</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Bei der Wiedergabe dieses Eintrags ist ein Fehler aufgetreten.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Kanaldaten konnten nicht vom Server geladen werden</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Gespeicherte löschen</translation>
</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 enthält keine Einträge</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Anmeldedaten speichern?</translation>
</message>
<message>
<source>Change Server</source>
<translation>Server wechseln</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Kritikerbewertung</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>IMDb-Bewertung</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Abspieldatum</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Hinzugefügt am</translation>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Fehler beim Empfangen des Inhalts</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Sign Out</source>
<translation>Abmelden</translation>
</message>
<message>
<source>On Now</source>
<translation>Jetzt läuft</translation>
</message>
<message>
<source>Change Server</source>
<translation>Server wechseln</translation>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Fehler beim Empfangen des Inhalts</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Fehler während der wiedergabe</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Delete Saved</source>
<translation>Gespeicherte löschen</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Zugangsdaten Speichern?</translation>
</message>
</context>
</TS>

View File

@ -1208,5 +1208,25 @@
<translation>Disable the HEVC codec on this device. This may improve playback for some devices (ultra).</translation>
<extracomment>User Setting - Setting description</extracomment>
</message>
<message>
<source>Global</source>
<translation>Global</translation>
<extracomment>User Setting - Setting title</extracomment>
</message>
<message>
<source>Global settings that affect everyone that uses this Roku device.</source>
<translation>Global settings that affect everyone that uses this Roku device.</translation>
<extracomment>User Setting - Setting description</extracomment>
</message>
<message>
<source>Remember Me?</source>
<translation>Remember Me?</translation>
<extracomment>User Setting - Setting title</extracomment>
</message>
<message>
<source>Remember the currently logged in user and try to log them in again next time you start the Jellyfin app.</source>
<translation>Remember the currently logged in user and try to log them in again next time you start the Jellyfin app.</translation>
<extracomment>User Setting - Setting description</extracomment>
</message>
</context>
</TS>

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@
</message>
<message>
<source>Favorite</source>
<translation>Favoris</translation>
<translation>Favori</translation>
</message>
<message>
<source>Loading...</source>
@ -50,7 +50,7 @@
</message>
<message>
<source>Please sign in</source>
<translation>Se connecter</translation>
<translation>Connectez-vous s&apos;il vous plaît</translation>
</message>
<message>
<source>Search</source>
@ -118,7 +118,7 @@
</message>
<message>
<source>Enter a password</source>
<translation>Entrer le mot de passe</translation>
<translation>Entrez le mot de passe</translation>
</message>
<message>
<source>Enter a value...</source>
@ -142,7 +142,7 @@
</message>
<message>
<source>Sort Order</source>
<translation>Trie par ordre</translation>
<translation>Ordre de tri</translation>
</message>
<message>
<source>Descending</source>
@ -166,7 +166,7 @@
</message>
<message>
<source>Director</source>
<translation>Direction</translation>
<translation>Réalisateur</translation>
</message>
<message>
<source>Video</source>
@ -174,7 +174,7 @@
</message>
<message>
<source>Audio</source>
<translation>Musiques</translation>
<translation>Audio</translation>
</message>
<message>
<source>Server</source>
@ -7426,5 +7426,494 @@
<translation>Nombre d&apos;éléments</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Erreur lors de la récupération du contenu</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Une erreur s&apos;est produite lors de la récupération des données de cet élément sur le serveur.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Erreur lors du chargement des données de la chaîne</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Impossible de charger les données de la chaîne depuis le serveur</translation>
</message>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Appuyez sur «OK» pour fermer</translation>
</message>
<message>
<source>Monday</source>
<translation>Lundi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Starts at</source>
<translation>Commence à</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Cancel Recording</source>
<translation>Annuler l&apos;enregistrement</translation>
</message>
<message>
<source>Connecting to Server</source>
<translation>Connexion au serveur en cours</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>Enter the server name or IP address</source>
<translation>Entrez le nom du serveur ou son adresse IP</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Sélectionnez un serveur Jellyfin 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>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>Une erreur a é rencontrée lors de la lecture de cet élément. Le serveur n&apos;a pas communiqué les informations nécessaires au transcodage.</translation>
<extracomment>Content of message box when trying to play an item which requires transcoding, and the server did not provide transcode url</extracomment>
</message>
<message>
<source>Enable or disable Direct Play for optional codecs</source>
<translation>Activer ou désactiver DirectPlay pour les codecs optionnels</translation>
<extracomment>Settings Menu - Title for settings group related to codec support</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</source>
<translation>Utiliser DirectPlay pour les contenus en MPEG-2 (par ex. TV en direct). Cela évitera la conversion des contenus en MPEG-2 mais utilisera beaucoup plus de bande passante.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</source>
<translation>Utiliser DirectPlay pour les contenus en MPEG-4. Désactiver ce paramètres pourrait être nécessaire pour la lecture de fichiers vidéo encodés en DivX.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</source>
<translation>** EXPERIMENTAL** Utiliser DirectPlay pour les contenus en AV1 si cet équipement Roku le supporte.</translation>
<extracomment>Description of a setting - should we try to direct play experimental av1 codec</extracomment>
</message>
<message>
<source>HEVC</source>
<translation>HEVC</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>H.264</source>
<translation>H.264</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Nombre de vues</translation>
</message>
<message>
<source>Saturday</source>
<translation>Samedi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Record Series</source>
<translation>Enregistrer la série</translation>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>Si aucun serveur n&apos;est dans la liste ci-dessus, vous pouvez aussi saisir l&apos;URL du serveur manuellement :</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>Disabled</source>
<translation>Désactivé</translation>
</message>
<message>
<source>Close</source>
<translation>Fermer</translation>
</message>
<message>
<source>Channels</source>
<translation>Chaines</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Une erreur s&apos;est produite lors de la lecture de cet élément.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Erreur lors de la lecture</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Save Credentials?</source>
<translation>Enregistrer les identifiants ?</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Suppression enregistrée</translation>
</message>
<message>
<source>On Now</source>
<translation>En ce moment</translation>
</message>
<message>
<source>TV Shows</source>
<translation>Séries TV</translation>
</message>
<message>
<source>Friday</source>
<translation>Vendredi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Distribution</translation>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtre</translation>
</message>
<message>
<source>yesterday</source>
<translation>hier</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>Additional Parts</source>
<translation>Contenu additionnel</translation>
<extracomment>Additional parts of a video</extracomment>
</message>
<message>
<source>Movies</source>
<translation>Films</translation>
</message>
<message>
<source>Ends at</source>
<translation>Fini</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>View Channel</source>
<translation>Voir la chaîne</translation>
</message>
<message>
<source>Died</source>
<translation>Décès</translation>
</message>
<message>
<source>TV Guide</source>
<translation>Guide des programmes</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Movies (Grid)</source>
<translation>Films (Grille)</translation>
<extracomment>Movie library view option</extracomment>
</message>
<message>
<source>Codec Support</source>
<translation>Codecs supportés</translation>
<extracomment>Settings Menu - Title for settings group related to codec support</extracomment>
</message>
<message>
<source>User Interface</source>
<translation>Interface utilisateur</translation>
<extracomment>Title for User Interface section in user setting screen.</extracomment>
</message>
<message>
<source>Set Favorite</source>
<translation>Marquer comme favori</translation>
<extracomment>Button Text - When pressed, sets item as Favorite</extracomment>
</message>
<message>
<source>Go to series</source>
<translation>Aller à la série</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Series Detail Page</extracomment>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Notation des critiques</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Classification parentale</translation>
</message>
<message>
<source>Born</source>
<translation>Date de naissance</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Trier</translation>
</message>
<message>
<source>Movies (Presentation)</source>
<translation>Films (Présentation)</translation>
<extracomment>Movie library view option</extracomment>
</message>
<message>
<source>Started at</source>
<translation>A commencé à</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>A commencé</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Rediffusion</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Annuler l&apos;enregistrement de la série</translation>
</message>
<message>
<source>Media Grid</source>
<translation>Grille de médias</translation>
<extracomment>UI -&gt; Media Grid section in user setting screen.</extracomment>
</message>
<message>
<source>Set Watched</source>
<translation>Marquer comme vu</translation>
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
</message>
<message>
<source>Special Features</source>
<translation type="unfinished">Caractéristiques spécifiques</translation>
</message>
<message>
<source>Sunday</source>
<translation>Dimanche</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Mardi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Mercredi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jeudi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Starts</source>
<translation>Commence</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>Record</source>
<translation>Enregistrer</translation>
</message>
<message>
<source>Not found</source>
<translation>Introuvable</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>Le contenu demandé n&apos;existe pas sur le serveur</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Error Getting Playback Information</source>
<translation>Erreur lors de la récupération des informations de lecture</translation>
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
</message>
<message>
<source>Go to season</source>
<translation>Aller à la saison</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</extracomment>
</message>
<message>
<source>Search now</source>
<translation>Chercher</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>Studios</source>
<translation>Studios</translation>
</message>
<message>
<source>MPEG-2</source>
<translation>MPEG-2</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>MPEG-4</source>
<translation>MPEG-4</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>AV1</source>
<translation>AV1</translation>
<extracomment>Name of a setting - should we try to direct play experimental av1 codec</extracomment>
</message>
<message>
<source>Networks</source>
<translation>Réseaux</translation>
</message>
<message>
<source>Version</source>
<translation>Version</translation>
</message>
<message>
<source>Use voice remote to search</source>
<translation>Utilisez la télécommande vocale pour chercher</translation>
<extracomment>Help text in search voice text box</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Chargement des données de la chaîne</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Titre</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Notation IMDb</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Date d&apos;ajout</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Date de lecture</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Durée</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Vue</translation>
</message>
<message>
<source>More Like This</source>
<translation>Contenu similaire</translation>
</message>
<message>
<source>today</source>
<translation>aujourd&apos;hui</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>Ended at</source>
<translation>A fini à</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Live</source>
<translation>En direct</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Date de sortie</translation>
</message>
<message>
<source>Unknown</source>
<translation>Inconnu</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>Playback</source>
<translation>Lecture</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
<message>
<source>Enabled</source>
<translation>Activé</translation>
</message>
<message>
<source>Age</source>
<translation>Age</translation>
</message>
<message>
<source>tomorrow</source>
<translation>demain</translation>
<extracomment>Next day</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 type="unfinished">Ce(tte) %1 ne contient pas d&apos;éléments</translation>
</message>
<message>
<source>Item Count</source>
<translation>Nombre d&apos;éléments</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment>
</message>
<message>
<source>Go to episode</source>
<translation>Aller à l&apos;épisode</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
</message>
<message>
<source>Shows</source>
<translation>Séries</translation>
</message>
<message>
<source>Quick Connect</source>
<translation>Connexion Rapide</translation>
</message>
<message>
<source>Show item count in the library and index of selected item.</source>
<translation>Afficher le nombre d&apos;éléments dans la librairie et l&apos;index de l&apos;élément sélectionné.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>(Dialog will close automatically)</source>
<translation>(Cette fenêtre se fermera automatiquement)</translation>
</message>
<message>
<source>Media Grid options.</source>
<translation>Options de la grille Média</translation>
</message>
<message>
<source>You can search for Titles, People, Live TV Channels and more</source>
<translation>Vous pouvez rechercher des titres, personnes, chaînes TV et plus</translation>
<extracomment>Help text in search results</extracomment>
</message>
<message>
<source>%1 of %2</source>
<translation>%1 sur %2</translation>
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
</message>
<message>
<source>There was an error authenticating via Quick Connect.</source>
<translation>Une erreur s&apos;est produite lors de la Connexion Rapide.</translation>
</message>
<message>
<source>Here is your Quick Connect code:</source>
<translation>Voici votre code de Connexion Rapide :</translation>
</message>
<message>
<source>Special Features</source>
<translation>Fonctionnalités spéciales</translation>
</message>
</context>
</TS>

View File

@ -11268,5 +11268,18 @@
<source>Direct playing</source>
<translation>Közvetlen lejátszás</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Név</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Törlés mentve</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Menti a bejelentkezési adatokat?</translation>
</message>
</context>
</TS>

View File

@ -2198,5 +2198,66 @@
<source>Sign Out</source>
<translation>Scollegamento</translation>
</message>
<message>
<source>Error During Playback</source>
<translation>Errore durante la riproduzione</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Valutazione di IMDb</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>È stato riscontrato un errore durante la riproduzione.</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>Non ci sono elementi da visualizzare</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nome</translation>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>C&apos;è stato un errore nel recupero dei dati per questo elemento dal server.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Caricamento dati del canale</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Errore nel caricamento dei dati del canale</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Valutazione della critica</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Data di aggiunta</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Visto il</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Salvare le credenziali?</translation>
</message>
<message>
<source>Change Server</source>
<translation>Cambia Server</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Impossibile recuperare i dati del canale dal server</translation>
</message>
</context>
</TS>

349
package-lock.json generated
View File

@ -12,7 +12,7 @@
"dependencies": {
"@rokucommunity/bslib": "0.1.1",
"bgv": "npm:button-group-vert@1.0.2",
"brighterscript-formatter": "1.6.32",
"brighterscript-formatter": "1.6.33",
"intKeyboard": "npm:integer-keyboard@1.0.12",
"log": "npm:roku-log@0.11.1",
"promises": "npm:@rokucommunity/promises@0.1.0",
@ -21,18 +21,114 @@
},
"devDependencies": {
"@rokucommunity/bslint": "0.8.10",
"brighterscript": "0.65.5",
"brighterscript": "0.65.7",
"jshint": "2.13.6",
"markdownlint-cli2": "0.9.2",
"rimraf": "5.0.1",
"markdownlint-cli2": "0.10.0",
"rimraf": "5.0.5",
"roku-deploy": "3.10.3",
"roku-log-bsc-plugin": "0.8.1",
"rooibos-roku": "5.7.0",
"ropm": "0.10.16",
"ropm": "0.10.17",
"spellchecker-cli": "6.1.1",
"undent": "0.1.0"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -535,9 +631,9 @@
}
},
"node_modules/brighterscript": {
"version": "0.65.5",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.5.tgz",
"integrity": "sha512-xDkWIZhjTLhp6dVZ6lX7zWRyNvjdiAwZncJRnErSbqRhteNJFL7ic2UDJew9zCOYTQDrG7B85lpPpXc/1JlV+Q==",
"version": "0.65.7",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.7.tgz",
"integrity": "sha512-cmV0JzlYuKIKkMsbHR2FyaD2YugHeHESdMmUOtK61EsEUS895Y6yXftDVnFUwij7beLiZR6s1oRpMfiS7MJ40g==",
"dependencies": {
"@rokucommunity/bslib": "^0.1.1",
"@xml-tools/parser": "^1.0.7",
@ -577,11 +673,11 @@
}
},
"node_modules/brighterscript-formatter": {
"version": "1.6.32",
"resolved": "https://registry.npmjs.org/brighterscript-formatter/-/brighterscript-formatter-1.6.32.tgz",
"integrity": "sha512-7rbNmSsj2v8iv+iSWXczSg4hC7L1zxMEwo+jZcDaMDPu0TBt4zmmCpaT0WvvvYcE8fD9I1tJ4dlTaj61QnH0QQ==",
"version": "1.6.33",
"resolved": "https://registry.npmjs.org/brighterscript-formatter/-/brighterscript-formatter-1.6.33.tgz",
"integrity": "sha512-XmirSAOBBHsprJvLOuh8nzXNiPQJXQa2NJ581OZdQrofFdBNPGH+yFhV6XXUZ+pA6fVLSxMxXA0toF83jtKjrA==",
"dependencies": {
"brighterscript": "^0.65.5",
"brighterscript": "^0.65.7",
"glob-all": "^3.3.0",
"jsonc-parser": "^3.0.0",
"source-map": "^0.7.3",
@ -592,16 +688,6 @@
"bsfmt": "dist/cli.js"
}
},
"node_modules/brighterscript-formatter/node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/brighterscript-formatter/node_modules/glob-all": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/glob-all/-/glob-all-3.3.1.tgz",
@ -614,6 +700,21 @@
"glob-all": "bin/glob-all"
}
},
"node_modules/brighterscript-formatter/node_modules/glob-all/node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/brighterscript-formatter/node_modules/glob-all/node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"node_modules/brighterscript-formatter/node_modules/glob-all/node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
@ -635,6 +736,18 @@
"node": ">=8"
}
},
"node_modules/brighterscript-formatter/node_modules/glob-all/node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/brighterscript-formatter/node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
@ -653,23 +766,6 @@
"node": ">=8"
}
},
"node_modules/brighterscript-formatter/node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"node_modules/brighterscript-formatter/node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/brighterscript/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
@ -1266,6 +1362,12 @@
"domelementtype": "1"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@ -1992,12 +2094,12 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"node_modules/jackspeak": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.1.0.tgz",
"integrity": "sha512-DiEwVPqsieUzZBNxQ2cxznmFzfg/AMgJUjYw5xl6rSmCxAQXECcbSdwcLM6Ds6T09+SBfSNCGPhYUoQ96P4h7A==",
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.5.tgz",
"integrity": "sha512-Ratx+B8WeXLAtRJn26hrhY8S1+Jz6pxPMrkrdkgb/NstTNiqMhX0/oFVu5wX+g5n6JlEu2LPsDJmY8nRP4+alw==",
"dev": true,
"dependencies": {
"cliui": "^7.0.4"
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": ">=14"
@ -2318,9 +2420,9 @@
}
},
"node_modules/markdownlint": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.30.0.tgz",
"integrity": "sha512-nInuFvI/rEzanAOArW5490Ez4EYpB5ODqVM0mcDYCPx9DKJWCQqCgejjiCvbSeE7sjbDscVtZmwr665qpF5xGA==",
"version": "0.31.1",
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.31.1.tgz",
"integrity": "sha512-CKMR2hgcIBrYlIUccDCOvi966PZ0kJExDrUi1R+oF9PvqQmCrTqjOsgIvf2403OmJ+CWomuzDoylr6KbuMyvHA==",
"dev": true,
"dependencies": {
"markdown-it": "13.0.1",
@ -2331,17 +2433,17 @@
}
},
"node_modules/markdownlint-cli2": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.9.2.tgz",
"integrity": "sha512-ndijEHIOikcs29W8068exHXlfkFviGFT/mPhREia7zSfQzHvTDkL2s+tWizvELjLHiKRO4KGTkkJyR3oeR8A5g==",
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.10.0.tgz",
"integrity": "sha512-kVxjPyKFC+eW7iqcxiNI50RDzwugpXkEX5eQlDso/0IUs9M73jXYguLFHDzgi5KatcxU/57Fu8KoGtkFft9lfA==",
"dev": true,
"dependencies": {
"globby": "13.2.2",
"markdownlint": "0.30.0",
"markdownlint": "0.31.1",
"markdownlint-cli2-formatter-default": "0.0.4",
"micromatch": "4.0.5",
"strip-json-comments": "5.0.1",
"yaml": "2.3.1"
"yaml": "2.3.2"
},
"bin": {
"markdownlint-cli2": "markdownlint-cli2.js",
@ -3484,13 +3586,13 @@
}
},
"node_modules/path-scurry": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz",
"integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
"dev": true,
"dependencies": {
"lru-cache": "^9.0.0",
"minipass": "^5.0.0"
"lru-cache": "^9.1.1 || ^10.0.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@ -4227,15 +4329,15 @@
}
},
"node_modules/rimraf": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz",
"integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
"integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==",
"dev": true,
"dependencies": {
"glob": "^10.2.5"
"glob": "^10.3.7"
},
"bin": {
"rimraf": "dist/cjs/src/bin.js"
"rimraf": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=14"
@ -4254,19 +4356,19 @@
}
},
"node_modules/rimraf/node_modules/glob": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.2.5.tgz",
"integrity": "sha512-Gj+dFYPZ5hc5dazjXzB0iHg2jKWJZYMjITXYPBRQ/xc2Buw7H0BINknRTwURJ6IC6MEFpYbLvtgVb3qD+DwyuA==",
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.0.3",
"minimatch": "^9.0.0",
"minipass": "^5.0.0 || ^6.0.2",
"path-scurry": "^1.7.0"
"jackspeak": "^2.3.5",
"minimatch": "^9.0.1",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
"path-scurry": "^1.10.1"
},
"bin": {
"glob": "dist/cjs/src/bin.js"
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@ -4276,9 +4378,9 @@
}
},
"node_modules/rimraf/node_modules/minimatch": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz",
"integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==",
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
@ -4432,14 +4534,14 @@
"dev": true
},
"node_modules/ropm": {
"version": "0.10.16",
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.16.tgz",
"integrity": "sha512-YZ49ie+dSRkyOz7RbXpku+neujhk6WfrFKUpda844W2kB0Xk/p4XtwirIdorVNtefBqEeQMXswwrVaWLNaL+vQ==",
"version": "0.10.17",
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.17.tgz",
"integrity": "sha512-JFq/PAzrC3xVweRdTt4zAYCWWdSBzMg5MP9i302L+vi4eXLp4jDYGeYKZwSucZswWNGxVpSK3gW1paHpv7GVEw==",
"dev": true,
"dependencies": {
"@xml-tools/ast": "^5.0.5",
"@xml-tools/parser": "1.0.10",
"brighterscript": "^0.65.4",
"brighterscript": "^0.65.5",
"del": "6.0.0",
"fs-extra": "9.1.0",
"glob-all": "3.2.1",
@ -4489,7 +4591,7 @@
"node": ">=10"
}
},
"node_modules/ropm/node_modules/fs-extra/node_modules/jsonfile": {
"node_modules/ropm/node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
@ -4501,7 +4603,7 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/ropm/node_modules/fs-extra/node_modules/universalify": {
"node_modules/ropm/node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
@ -4510,45 +4612,6 @@
"node": ">= 10.0.0"
}
},
"node_modules/ropm/node_modules/roku-deploy": {
"version": "3.10.3",
"resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.10.3.tgz",
"integrity": "sha512-COJSQ638QklcM+8AN1nujFuzT04rTZLFuLSww35edm8w/y0l60oF/Iu7TQ46m75DwoGFzGFfomLEmA1ltQk9mA==",
"dev": true,
"dependencies": {
"chalk": "^2.4.2",
"dateformat": "^3.0.3",
"dayjs": "^1.11.0",
"fast-glob": "^3.2.12",
"fs-extra": "^7.0.1",
"is-glob": "^4.0.3",
"jsonc-parser": "^2.3.0",
"jszip": "^3.6.0",
"micromatch": "^4.0.4",
"moment": "^2.29.1",
"parse-ms": "^2.1.0",
"postman-request": "^2.88.1-postman.32",
"temp-dir": "^2.0.0",
"xml2js": "^0.5.0"
},
"bin": {
"roku-deploy": "dist/cli.js"
}
},
"node_modules/ropm/node_modules/roku-deploy/node_modules/fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/ropm/node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
@ -4847,6 +4910,21 @@
"node": ">=8"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/stringify-object": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
@ -4872,6 +4950,19 @@
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-bom": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
@ -5465,6 +5556,24 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@ -5505,9 +5614,9 @@
"dev": true
},
"node_modules/yaml": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz",
"integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==",
"dev": true,
"engines": {
"node": ">= 14"

View File

@ -5,7 +5,7 @@
"dependencies": {
"@rokucommunity/bslib": "0.1.1",
"bgv": "npm:button-group-vert@1.0.2",
"brighterscript-formatter": "1.6.32",
"brighterscript-formatter": "1.6.33",
"intKeyboard": "npm:integer-keyboard@1.0.12",
"log": "npm:roku-log@0.11.1",
"promises": "npm:@rokucommunity/promises@0.1.0",
@ -19,14 +19,14 @@
},
"devDependencies": {
"@rokucommunity/bslint": "0.8.10",
"brighterscript": "0.65.5",
"brighterscript": "0.65.7",
"jshint": "2.13.6",
"markdownlint-cli2": "0.9.2",
"rimraf": "5.0.1",
"markdownlint-cli2": "0.10.0",
"rimraf": "5.0.5",
"roku-deploy": "3.10.3",
"roku-log-bsc-plugin": "0.8.1",
"rooibos-roku": "5.7.0",
"ropm": "0.10.16",
"ropm": "0.10.17",
"spellchecker-cli": "6.1.1",
"undent": "0.1.0"
},

View File

@ -1,4 +1,18 @@
[
{
"title": "Global",
"description": "Global settings that affect everyone that uses this Roku device.",
"children": [
{
"title": "Remember Me?",
"description": "Remember the currently logged in user and try to log them in again next time you start the Jellyfin app.",
"settingName": "global.rememberme",
"type": "bool",
"default": "false"
}
]
},
{
"title": "Playback",
"description": "Settings relating to playback and supported codec and media types.",

View File

@ -63,7 +63,8 @@ sub Main (args as dynamic) as void
end if
' Only show the Whats New popup the first time a user runs a new client version.
if m.global.app.version <> get_setting("LastRunVersion")
appLastRunVersion = get_setting("LastRunVersion")
if m.global.app.version <> appLastRunVersion
' Ensure the user hasn't disabled Whats New popups
if m.global.session.user.settings["load.allowwhatsnew"] = true
set_setting("LastRunVersion", m.global.app.version)
@ -73,6 +74,34 @@ sub Main (args as dynamic) as void
end if
end if
' Registry migrations
if isValid(appLastRunVersion) and not versionChecker(appLastRunVersion, "1.7.0")
' last app version used less than 1.7.0
' no longer saving raw password to registry
' auth token and username are now stored in user settings and not global settings
print "Running 1.7.0 registry migrations"
' remove global settings
unset_setting("token")
unset_setting("username")
unset_setting("password")
' remove user settings
unset_user_setting("password")
' remove saved credentials from saved_servers
saved = get_setting("saved_servers")
if isValid(saved)
savedServers = ParseJson(saved)
if isValid(savedServers.serverList) and savedServers.serverList.Count() > 0
newServers = { serverList: [] }
for each item in savedServers.serverList
item.Delete("username")
item.Delete("password")
newServers.serverList.Push(item)
end for
set_setting("saved_servers", FormatJson(newServers))
end if
end if
end if
' Handle input messages
input = CreateObject("roInput")
input.SetMessagePort(m.port)
@ -198,25 +227,16 @@ sub Main (args as dynamic) as void
selectedItem.selectedAudioStreamIndex = audio_stream_idx
' If we are playing a playlist, always start at the beginning
if m.global.queueManager.callFunc("getCount") > 1
selectedItem.startingPoint = 0
' Display playback options dialog
if selectedItem.json.userdata.PlaybackPositionTicks > 0
m.global.queueManager.callFunc("hold", selectedItem)
playbackOptionDialog(selectedItem.json.userdata.PlaybackPositionTicks, selectedItem.json)
else
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", selectedItem)
m.global.queueManager.callFunc("playQueue")
else
' Display playback options dialog
if selectedItem.json.userdata.PlaybackPositionTicks > 0
m.global.queueManager.callFunc("hold", selectedItem)
playbackOptionDialog(selectedItem.json.userdata.PlaybackPositionTicks, selectedItem.json)
else
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", selectedItem)
m.global.queueManager.callFunc("playQueue")
end if
end if
else if selectedItemType = "Series"
group = CreateSeriesDetailsGroup(selectedItem.json.id)
else if selectedItemType = "Season"
@ -521,7 +541,11 @@ sub Main (args as dynamic) as void
group.findNode("SearchBox").findNode("search_Key").active = true
else if button.id = "change_server"
unset_setting("server")
unset_setting("port")
session.server.Delete()
SignOut(false)
sceneManager.callFunc("clearScenes")
goto app_start
else if button.id = "change_user"
SignOut(false)
sceneManager.callFunc("clearScenes")
goto app_start

View File

@ -1,4 +1,4 @@
function LoginFlow(startOver = false as boolean)
function LoginFlow()
'Collect Jellyfin server and user information
start_login:
@ -41,9 +41,11 @@ function LoginFlow(startOver = false as boolean)
activeUser = get_setting("active_user")
if activeUser = invalid
print "No active user found in registry"
user_select:
SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting
publicUsers = GetPublicUsers()
if publicUsers.count()
numPubUsers = publicUsers.count()
if numPubUsers > 0
publicUsersNodes = []
for each item in publicUsers
user = CreateObject("roSGNode", "PublicUserData")
@ -55,18 +57,57 @@ function LoginFlow(startOver = false as boolean)
publicUsersNodes.push(user)
end for
userSelected = CreateUserSelectGroup(publicUsersNodes)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if userSelected = "backPressed"
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
return LoginFlow(true)
session.server.Delete()
unset_setting("server")
goto start_login
else
print "A public user was selected with username=" + userSelected
session.user.Update("name", userSelected)
regex = CreateObject("roRegex", "[^a-zA-Z0-9\ \-\_]", "")
session.user.Update("friendlyName", regex.ReplaceAll(userSelected, ""))
' save userid to session
for each user in publicUsersNodes
if user.name = userSelected
session.user.Update("id", user.id)
exit for
end if
end for
' try to login with token from registry
myToken = get_user_setting("token")
if myToken <> invalid
' check if token is valid
print "Auth token found in registry for selected user"
session.user.Update("authToken", myToken)
print "Attempting to use API with auth token"
currentUser = AboutMe()
if currentUser = invalid
print "Auth token is no longer valid - deleting token"
unset_user_setting("token")
unset_user_setting("username")
else
print "Success! Auth token is still valid"
session.user.Login(currentUser)
LoadUserPreferences()
LoadUserAbilities()
return true
end if
else
print "No auth token found in registry for selected user"
end if
'Try to login without password. If the token is valid, we're done
print "Attempting to login with no password"
userData = get_token(userSelected, "")
if isValid(userData)
print "login success!"
session.user.Login(userData)
LoadUserPreferences()
LoadUserAbilities()
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
return true
else
print "Auth failed. Password required"
end if
end if
else
@ -75,65 +116,52 @@ function LoginFlow(startOver = false as boolean)
passwordEntry = CreateSigninGroup(userSelected)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if passwordEntry = "backPressed"
m.global.sceneManager.callFunc("clearScenes")
return LoginFlow(true)
if numPubUsers > 0
goto user_select
else
session.server.Delete()
unset_setting("server")
goto start_login
end if
end if
else
print "Active user found in registry"
session.user.Update("id", activeUser)
myUsername = get_user_setting("username")
myAuthToken = get_user_setting("token")
if isValid(myAuthToken)
if isValid(myAuthToken) and isValid(myUsername)
print "Auth token found in registry"
session.user.Update("authToken", myAuthToken)
session.user.Update("name", myUsername)
regex = CreateObject("roRegex", "[^a-zA-Z0-9\ \-\_]", "")
session.user.Update("friendlyName", regex.ReplaceAll(myUsername, ""))
print "Attempting to use API with auth token"
currentUser = AboutMe()
if currentUser = invalid
print "Auth token is no longer valid - restart login flow"
unset_user_setting("token")
unset_setting("active_user")
session.user.Logout()
goto start_login
print "Auth token is no longer valid"
'Try to login without password. If the token is valid, we're done
print "Attempting to login with no password"
userData = get_token(userSelected, "")
if isValid(userData)
print "login success!"
session.user.Login(userData)
LoadUserPreferences()
LoadUserAbilities()
return true
else
print "Auth failed. Password required"
print "delete token and restart login flow"
unset_user_setting("token")
unset_user_setting("username")
goto start_login
end if
else
print "Success! Auth token is still valid"
session.user.Login(currentUser)
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
@ -254,11 +282,6 @@ function CreateServerGroup()
m.scene.dialog = dialog
serverUrl = standardize_jellyfin_url(screen.serverUrl)
'If this is a different server from what we know, reset username/password setting
if m.global.session.server.url <> serverUrl
set_setting("username", "")
set_setting("password", "")
end if
set_setting("server", serverUrl)
isConnected = session.server.UpdateURL(serverUrl)
@ -362,25 +385,6 @@ 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 = 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 isValid(saved)
savedServers = ParseJson(saved)
for each item in savedServers.serverList
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
end for
end if
config = group.findNode("configOptions")
username_field = CreateObject("roSGNode", "ConfigData")
username_field.label = tr("Username")
@ -447,11 +451,10 @@ function CreateSigninGroup(user = "")
activeUser = get_token(username.value, password.value)
if isValid(activeUser)
session.user.Login(activeUser)
set_setting("username", username.value)
set_setting("password", password.value)
' save credentials
if checkbox.checkedState[0] = true
'Update our saved server list, so next time the user can just click and go
UpdateSavedServerList()
set_user_setting("token", activeUser.token)
set_user_setting("username", username.value)
end if
return "true"
end if
@ -515,6 +518,7 @@ function CreateHomeGroup()
new_options = []
options_buttons = [
{ "title": "Search", "id": "goto_search" },
{ "title": "Change user", "id": "change_user" },
{ "title": "Change server", "id": "change_server" },
{ "title": "Sign out", "id": "sign_out" }
]
@ -862,34 +866,6 @@ function CreatePersonView(personData as object) as dynamic
return person
end function
sub UpdateSavedServerList()
server = m.global.session.server.url
username = get_setting("username")
password = get_setting("password")
if server = invalid or username = invalid or password = invalid
return
end if
server = LCase(server)'Saved server data is always lowercase
saved = get_setting("saved_servers")
if isValid(saved)
savedServers = ParseJson(saved)
if isValid(savedServers.serverList) and savedServers.serverList.Count() > 0
newServers = { serverList: [] }
for each item in savedServers.serverList
if item.baseUrl = server
item.username = username
item.password = password
end if
newServers.serverList.Push(item)
end for
set_setting("saved_servers", FormatJson(newServers))
end if
end if
end sub
'Opens dialog asking user if they want to resume video or start playback over only on the home screen
sub playbackOptionDialog(time as longinteger, meta as object)

View File

@ -203,12 +203,16 @@ function authRequest(request as object) as object
if m.global.session.user.id <> invalid
auth = auth + ", UserId=" + QUOTE + m.global.session.user.id + QUOTE
auth = auth + ", DeviceId=" + QUOTE + m.global.device.id + QUOTE
if m.global.session.user.authToken <> invalid
auth = auth + ", Token=" + QUOTE + m.global.session.user.authToken + QUOTE
end if
end if
if m.global.session.user <> invalid and m.global.session.user.friendlyName <> invalid
auth = auth + ", DeviceId=" + QUOTE + m.global.device.id + m.global.session.user.friendlyName + QUOTE
else
auth = auth + ", DeviceId=" + QUOTE + m.global.device.uuid + QUOTE
auth = auth + ", DeviceId=" + QUOTE + m.global.device.id + QUOTE
end if
if m.global.session.user.authToken <> invalid
auth = auth + ", Token=" + QUOTE + m.global.session.user.authToken + QUOTE
end if
request.AddHeader("Authorization", auth)

View File

@ -32,28 +32,9 @@ function AboutMe(id = "" as string)
end function
sub SignOut(deleteSavedEntry = true as boolean)
if m.global.session.user.id <> invalid
if m.global.session.user.id <> invalid and deleteSavedEntry = true
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 = m.global.session.server.url
if server <> invalid
server = LCase(server)
savedServers = ParseJson(saved)
newServers = { serverList: [] }
for each item in savedServers.serverList
if item.baseUrl = server
item.username = ""
item.password = ""
end if
newServers.serverList.Push(item)
end for
set_setting("saved_servers", FormatJson(newServers))
end if
end if
unset_user_setting("username")
end if
unset_setting("active_user")
session.user.Logout()

View File

@ -13,7 +13,7 @@ function getDeviceCapabilities() as object
"Photo"
],
"SupportedCommands": [],
"SupportsPersistentIdentifier": false,
"SupportsPersistentIdentifier": true,
"SupportsMediaControl": false,
"SupportsContentUploading": false,
"SupportsSync": false,

View File

@ -137,6 +137,10 @@ namespace session
tmpSession.AddReplace("user", userData.json.User)
tmpSession.user.AddReplace("authToken", userData.json.AccessToken)
end if
' remove special characters from name
regex = CreateObject("roRegex", "[^a-zA-Z0-9\ \-\_]", "")
friendlyName = regex.ReplaceAll(tmpSession.user.name, "")
tmpSession.user.AddReplace("friendlyName", friendlyName)
tmpSession.user.AddReplace("settings", oldUserSettings)
' update global user session
@ -154,9 +158,11 @@ namespace session
print "m.global.session.user.Policy = ", m.global.session.user.Policy
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)
if m.global.session.user.settings["global.rememberme"]
set_user_setting("token", tmpSession.user.authToken)
set_user_setting("username", tmpSession.user.name)
end if
end sub
' Empty the global user session array and reload defaults
@ -234,6 +240,20 @@ namespace session
end for
end if
end for
' load globals
session.user.settings.LoadGlobals()
end sub
' Grab global vars from registry and overwrite defaults
sub LoadGlobals()
' search main registry block for all keys that start with "global."
jfRegistry = RegistryReadAll("Jellyfin")
for each item in jfRegistry
if Left(item, 7) = "global."
session.user.settings.Save(item, get_setting(item))
end if
end for
end sub
' Saves the user setting to the global session.