Merge branch 'unstable' into Next-UP-Button

This commit is contained in:
candry7731 2022-10-11 09:57:02 -05:00 committed by GitHub
commit a35f476bfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 6283 additions and 892 deletions

View File

@ -0,0 +1,20 @@
name: "Close stale Pull Requests"
on:
schedule:
- cron: "30 1 * * *"
jobs:
stale:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/stale@3de2653986ebd134983c79fe2be5d45cc3d9f4e1 # tag=v6
with:
days-before-issue-stale: -1
days-before-issue-close: -1
stale-pr-message: "This pull request has been inactive for 21 days and will be automatically closed in 7 days if there is no further activity."
close-pr-message: "This pull request has been closed because it has been inactive for 28 days. You may submit a new pull request if desired."
days-before-pr-stale: 21
days-before-pr-close: 7
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -8,8 +8,8 @@ jobs:
run:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
- uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 # tag=v3
with:
node-version: "14.12.0"
- run: npm ci

View File

@ -9,11 +9,11 @@ sub init()
m.top.observeField("height", "onHeightChanged")
m.top.observeField("width", "onWidthChanged")
m.top.observeField("padding", "onPaddingChanged")
m.top.observeField("focusedChild", "onFocusChanged")
m.top.observeField("focus", "onFocusChanged")
end sub
sub onFocusChanged()
if m.top.hasFocus()
if m.top.focus
m.buttonBackground.blendColor = m.top.focusBackground
else
m.buttonBackground.blendColor = m.top.background
@ -69,10 +69,12 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "OK" and m.top.hasFocus()
' Simply toggle the selected field to trigger the next event
m.top.selected = not m.top.selected
return true
if key = "right" and m.top.focus
m.top.escape = "right"
end if
if key = "left" and m.top.focus
m.top.escape = "left"
end if
return false

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="IconButton" extends="Group">
<children>
<Poster id="buttonBackground" uri="pkg:/images/white.9.png" />
<Poster id="buttonIcon" />
<Label id="buttonText" color="#aaaaaa" font="font:SmallestSystemFont" horizAlign="center" />
<Poster id="buttonBackground" uri="pkg:/images/white.9.png" />
<Poster id="buttonIcon" />
<Label id="buttonText" color="#aaaaaa" font="font:SmallestSystemFont" horizAlign="center" />
</children>
<interface>
<field id="background" type="color" value="" />
@ -14,6 +14,8 @@
<field id="width" type="integer" value="" />
<field id="icon" type="string" value="" />
<field id="selected" type="boolean" value="false" />
<field id="focus" type="boolean" />
<field id="escape" type="string" value="" />
</interface>
<script type="text/brightscript" uri="IconButton.brs" />
</component>

View File

@ -80,6 +80,20 @@ sub itemContentChanged()
m.backdrop.height = 290
m.backdrop.width = 290
m.posterText.height = 200
m.posterText.width = 280
else if itemData.json.type = "MusicAlbum"
m.itemPoster.uri = itemData.PosterUrl
m.itemText.text = itemData.Title
m.itemPoster.height = 290
m.itemPoster.width = 290
m.itemText.translation = [0, m.itemPoster.height + 7]
m.backdrop.height = 290
m.backdrop.width = 290
m.posterText.height = 200
m.posterText.width = 280
else

View File

@ -163,25 +163,16 @@ sub loadInitialItems()
m.loadItemsTask.itemId = m.top.parentItem.Id
else if getCollectionType() = "music"
' Default Settings
m.loadItemsTask.recursive = true
m.itemGrid.itemSize = "[290, 290]"
if m.voiceBox.text <> ""
m.loadItemsTask.recursive = true
else
m.loadItemsTask.recursive = false
m.itemGrid.itemSize = "[290, 290]"
end if
m.loadItemsTask.itemType = "MusicArtist,MusicAlbum"
m.loadItemsTask.itemType = "MusicArtist"
m.loadItemsTask.itemId = m.top.parentItem.Id
m.view = get_user_setting("display.music.view")
if m.view = "music-artist"
m.loadItemsTask.recursive = true
m.loadItemsTask.itemType = "MusicArtist"
else if m.view = "music-album"
if m.view = "music-album"
m.loadItemsTask.itemType = "MusicAlbum"
m.loadItemsTask.recursive = true
end if
else if m.top.parentItem.collectionType = "livetv"
m.loadItemsTask.itemType = "TvChannel"
@ -312,7 +303,6 @@ end sub
' Set Music view, sort, and filter options
sub setMusicOptions(options)
options.views = [
{ "Title": tr("Default"), "Name": "music-default" },
{ "Title": tr("Artists"), "Name": "music-artist" },
{ "Title": tr("Albums"), "Name": "music-album" },
]
@ -599,13 +589,7 @@ sub optionsClosed()
if m.top.parentItem.collectionType = "music"
if m.options.view <> m.view
if m.options.view = "music-artist"
m.view = "music-artist"
else if m.options.view = "music-album"
m.view = "music-album"
else
m.view = "music-default"
end if
m.view = m.options.view
set_user_setting("display.music.view", m.view)
reload = true
end if
@ -796,6 +780,12 @@ sub updateTitle()
m.top.overhangTitle = m.top.parentItem.title + tr(" (Filtered by ") + m.loadItemsTask.nameStartsWith + ")"
end if
if m.view = "music-artist"
m.top.overhangTitle = "%s (%s)".Format(m.top.parentItem.title, tr("Artists"))
else if m.view = "music-album"
m.top.overhangTitle = "%s (%s)".Format(m.top.parentItem.title, tr("Albums"))
end if
if m.options.view = "Networks" or m.view = "Networks"
m.top.overhangTitle = "%s (%s)".Format(m.top.parentItem.title, tr("Networks"))
end if

View File

@ -77,6 +77,16 @@ sub loadItems()
else if m.top.view = "Genres"
url = "Genres"
params.append({ UserId: get_setting("active_user") })
else if m.top.ItemType = "MusicArtist"
url = "Artists"
params.append({
UserId: get_setting("active_user")
})
params.IncludeItemTypes = ""
else if m.top.ItemType = "MusicAlbum"
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
params.append({ ImageTypeLimit: 1 })
params.append({ EnableImageTypes: "Primary,Backdrop,Banner,Thumb" })
else
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
end if
@ -88,7 +98,7 @@ sub loadItems()
for each item in data.Items
tmp = invalid
if item.Type = "Movie"
if item.Type = "Movie" or item.Type = "MusicVideo"
tmp = CreateObject("roSGNode", "MovieData")
else if item.Type = "Series"
tmp = CreateObject("roSGNode", "SeriesData")
@ -110,7 +120,15 @@ sub loadItems()
tmp = CreateObject("roSGNode", "FolderData")
else if item.Type = "Studio"
tmp = CreateObject("roSGNode", "FolderData")
else if item.Type = "MusicArtist" or item.Type = "MusicAlbum"
else if item.Type = "MusicAlbum"
tmp = CreateObject("roSGNode", "MusicAlbumData")
tmp.type = "MusicAlbum"
if api_API().items.headimageurlbyname(item.id, "primary")
tmp.posterURL = ImageURL(item.id, "Primary")
else
tmp.posterURL = ImageURL(item.id, "backdrop")
end if
else if item.Type = "MusicArtist"
tmp = CreateObject("roSGNode", "MusicArtistData")
else if item.Type = "Audio"
tmp = CreateObject("roSGNode", "MusicSongData")

View File

@ -22,6 +22,7 @@
</interface>
<script type="text/brightscript" uri="LoadItemsTask2.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />

View File

@ -18,6 +18,9 @@ sub init()
m.overlayRightGroup = m.top.findNode("overlayRightGroup")
m.overlayTimeGroup = m.top.findNode("overlayTimeGroup")
m.slideDownAnimation = m.top.findNode("slideDown")
m.slideUpAnimation = m.top.findNode("slideUp")
if not m.hideClock
' get system preference clock format (12/24hr)
di = CreateObject("roDeviceInfo")
@ -40,6 +43,19 @@ sub init()
setClockVisibility()
end sub
sub onVisibleChange()
if m.top.disableMoveAnimation
m.top.translation = [0, 0]
return
end if
if m.top.isVisible
m.slideDownAnimation.control = "start"
return
end if
m.slideUpAnimation.control = "start"
end sub
sub updateTitle()
leftSeperator = m.top.findNode("overlayLeftSeperator")
if m.top.title <> ""

View File

@ -1,27 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="JFOverhang" extends="Group">
<children>
<Poster id="overlayLogo"
uri="pkg:/images/logo.png"
translation="[70, 53]"
width="270" height="72" />
<LayoutGroup id="overlayLeftGroup" layoutDirection="horiz" translation="[375, 53]" itemSpacings="30" >
<Rectangle
id="overlayLeftSeperator"
color="#666666"
width="2"
height="64"/>
<Poster id="overlayLogo" uri="pkg:/images/logo.png" translation="[70, 53]" width="270" height="72" />
<LayoutGroup id="overlayLeftGroup" layoutDirection="horiz" translation="[375, 53]" itemSpacings="30">
<Rectangle id="overlayLeftSeperator" color="#666666" width="2" height="64"/>
<ScrollingLabel id="overlayTitle" font="font:LargeSystemFont" vertAlign="center" height="64" maxWidth="1100" repeatCount="0" />
</LayoutGroup>
<LayoutGroup id="overlayRightGroup" layoutDirection="horiz" itemSpacings="30" translation="[1820, 53]" horizAlignment="right" >
<LayoutGroup id="overlayRightGroup" layoutDirection="horiz" itemSpacings="30" translation="[1820, 53]" horizAlignment="right">
<Label id="overlayCurrentUser" font="font:MediumSystemFont" width="300" horizAlign="right" vertAlign="center" height="64" />
<Rectangle
id="overlayRightSeperator"
color="#666666"
width="2"
height="64"
visible="false"/>
<LayoutGroup id="overlayTimeGroup" layoutDirection="horiz" horizAlignment="right" itemSpacings="0" >
<Rectangle id="overlayRightSeperator" color="#666666" width="2" height="64" visible="false"/>
<LayoutGroup id="overlayTimeGroup" layoutDirection="horiz" horizAlignment="right" itemSpacings="0">
<Label id="overlayHours" font="font:MediumSystemFont" vertAlign="center" height="64" />
<Label font="font:MediumSystemFont" text=":" vertAlign="center" height="64" />
<Label id="overlayMinutes" font="font:MediumSystemFont" vertAlign="center" height="64" />
@ -30,19 +18,26 @@
</LayoutGroup>
<LayoutGroup layoutDirection="horiz" horizAlignment="right" translation="[1820, 125]" vertAlignment="custom">
<Label id="overlayOptionsStar" font="font:LargeSystemFont" text="*" />
<Label id="overlayOptionsText" font="font:SmallSystemFont" text="Options" translation="[0,6]" />
<Label id="overlayOptionsStar" font="font:LargeSystemFont" text="*" />
<Label id="overlayOptionsText" font="font:SmallSystemFont" text="Options" translation="[0,6]" />
</LayoutGroup>
<Timer
id="currentTimeTimer"
repeat="true"
duration="60" />
<Timer id="currentTimeTimer" repeat="true" duration="60" />
<Animation id="slideUp" duration=".5" repeat="false">
<Vector2DFieldInterpolator key="[0.0, .5]" keyValue="[[0, 0], [0, -200]]" fieldToInterp="overhang.translation" />
</Animation>
<Animation id="slideDown" delay=".2" duration=".5" repeat="false">
<Vector2DFieldInterpolator key="[0.0, .5]" keyValue="[[0, -200], [0, 0]]" fieldToInterp="overhang.translation" />
</Animation>
</children>
<interface>
<field id="id" type="string" />
<field id="currentUser" type="string" onChange="updateUser" />
<field id="title" type="string" onChange="updateTitle" />
<field id="showOptions" value="true" type="boolean" onChange="updateOptions" />
<field id="isVisible" value="true" type="boolean" onChange="onVisibleChange" />
<field id="disableMoveAnimation" value="false" type="boolean" />
<function name="resetTime" />
</interface>
<script type="text/brightscript" uri="JFOverhang.brs" />

View File

@ -2,29 +2,18 @@ sub init()
m.playbackTimer = m.top.findNode("playbackTimer")
m.bufferCheckTimer = m.top.findNode("bufferCheckTimer")
m.top.observeField("state", "onState")
m.top.observeField("position", "onPositionChanged")
m.top.trickPlayBar.observeField("visible", "onTrickPlayBarVisibilityChange")
m.playbackTimer.observeField("fire", "ReportPlayback")
m.bufferPercentage = 0 ' Track whether content is being loaded
m.playReported = false
m.top.transcodeReasons = []
m.bufferCheckTimer.duration = 10
m.bufferCheckTimer.duration = 30
if get_user_setting("ui.design.hideclock") = "true"
clockNode = findNodeBySubtype(m.top, "clock")
if clockNode[0] <> invalid then clockNode[0].parent.removeChild(clockNode[0].node)
end if
' Skip Intro Button
m.skipIntroButton = m.top.findNode("skipIntro")
m.skipIntroButton.text = tr("Skip Intro")
m.introCompleted = false
m.showskipIntroButtonAnimation = m.top.findNode("showskipIntroButton")
m.hideskipIntroButtonAnimation = m.top.findNode("hideskipIntroButton")
m.moveUpskipIntroButtonAnimation = m.top.findNode("moveUpskipIntroButton")
m.moveDownskipIntroButtonAnimation = m.top.findNode("moveDownskipIntroButton")
'Play Next Episode button
'Play Next Episode button
m.nextEpisodeButton = m.top.findNode("nextEpisode")
m.nextEpisodeButton.text = tr("Next Episode")
m.nextEpisodeButton.setFocus(false)
@ -34,107 +23,6 @@ sub init()
m.moveDownnextEpisodeButtonAnimation = m.top.findNode("moveDownnextEpisodeButton")
end sub
'
' Checks if we have valid skip intro param data
function haveSkipIntroParams() as boolean
'check current position
if int(m.top.position) >= (m.top.runTime - 30)
shownextEpisode()
updateCount()
end if
' Intro data is invalid, skip
if not isValid(m.top.skipIntroParams?.Valid)
return false
end if
' Returned intro data is not valid, return
if not m.top.skipIntroParams.Valid
return false
end if
return true
end function
'
' Handles showing / hiding the skip intro button
sub handleSkipIntro()
' We've already shown the intro, return
if m.introCompleted then return
' We don't have valid data, return
if not haveSkipIntroParams() then return
' Check if it's time to hide the skip prompt
if m.top.position >= m.top.skipIntroParams.HideSkipPromptAt
if skipIntroButtonVisible()
hideSkipIntro()
end if
return
end if
' Check if it's time to show the skip prompt
if m.top.position >= m.top.skipIntroParams.ShowSkipPromptAt
if not skipIntroButtonVisible()
showSkipIntro()
end if
return
end if
end sub
'
' When Trick Playbar Visibility changes
sub onTrickPlayBarVisibilityChange()
' Skip Intro button isn't visible, return
if not skipIntroButtonVisible() then return
' Trick Playbar is visible, move the skip intro button up and fade it out
if m.top.trickPlayBar.visible
m.moveUpskipIntroButtonAnimation.control = "start"
m.skipIntroButton.setFocus(false)
m.top.setFocus(true)
return
end if
' Trick Playbar is not visible, move the skip intro button down and fade it in
m.moveDownskipIntroButtonAnimation.control = "start"
m.skipIntroButton.setFocus(true)
end sub
'
' When Video Player state changes
sub onPositionChanged()
' Check if content is episode
if m.top.content.contenttype = 4
handleSkipIntro()
end if
end sub
'
' Returns if skip intro button is currently visible
function skipIntroButtonVisible() as boolean
return m.skipIntroButton.opacity > 0
end function
'
' Runs skip intro button animation and sets focus to button
sub showSkipIntro()
m.showskipIntroButtonAnimation.control = "start"
m.skipIntroButton.setFocus(true)
end sub
'
' Runs hide intro button animation and sets focus back to video
sub hideSkipIntro()
m.top.trickPlayBar.unobserveField("visible")
m.hideskipIntroButtonAnimation.control = "start"
m.introCompleted = true
m.skipIntroButton.setFocus(false)
m.top.setFocus(true)
end sub
'
' Runs Next Episode button animation and sets focus to button
sub shownextEpisode()
@ -163,7 +51,6 @@ end sub
'
' When Video Player state changes
sub onState(msg)
' When buffering, start timer to monitor buffering process
if m.top.state = "buffering" and m.bufferCheckTimer <> invalid
@ -191,9 +78,9 @@ sub onState(msg)
ReportPlayback("start")
m.playReported = true
else
m.playbackTimer.control = "start"
ReportPlayback()
end if
m.playbackTimer.control = "start"
else if m.top.state = "paused"
m.playbackTimer.control = "stop"
ReportPlayback()
@ -276,13 +163,8 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if key = "OK"
if not m.top.trickPlayBar.visible
if m.skipIntroButton.hasFocus()
m.top.seek = m.top.skipIntroParams.IntroEnd
hideSkipIntro()
return true
else if m.nextEpisodeButton.hasFocus()
if key = "OK" and m.nextEpisodeButton.hasFocus()
m.top.state = "finished"
hidenextEpisode()
return true
@ -295,6 +177,9 @@ function onKeyEvent(key as string, press as boolean) as boolean
if key = "down"
m.top.selectSubtitlePressed = true
return true
else if key = "up"
m.top.selectPlaybackInfoPressed = true
return true
end if
return false

View File

@ -3,6 +3,7 @@
<interface>
<field id="backPressed" type="boolean" alwaysNotify="true" />
<field id="selectSubtitlePressed" type="boolean" alwaysNotify="true" />
<field id="selectPlaybackInfoPressed" type="boolean" alwaysNotify="true" />
<field id="PlaySessionId" type="string" />
<field id="Subtitles" type="array" />
<field id="SelectedSubtitle" type="integer" />
@ -17,12 +18,10 @@
<field id="retryWithTranscoding" type="boolean" value="false" />
<field id="isTranscoded" type="boolean" />
<field id="transcodeReasons" type="array" />
<field id="videoId" type="string" />
<field id="mediaSourceId" type="string" />
<field id="audioIndex" type="integer" />
<field id="skipIntroParams" type="assocarray" />
<field id="runTime" type="integer" />
</interface>
<script type="text/brightscript" uri="JFVideo.brs" />
@ -30,29 +29,9 @@
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<children>
<JFButton id="skipIntro" opacity="0" textColor="#f0f0f0" focusedTextColor="#202020" focusFootprintBitmapUri="pkg:/images/option-menu-bg.9.png" focusBitmapUri="pkg:/images/white.9.png" translation="[1575, 900]" />
<JFButton id="nextEpisode" opacity="0" textColor="#f0f0f0" focusedTextColor="#202020" focusFootprintBitmapUri="pkg:/images/option-menu-bg.9.png" focusBitmapUri="pkg:/images/white.9.png" translation="[1500, 900]" />
<timer id="playbackTimer" repeat="true" duration="30" />
<timer id="bufferCheckTimer" repeat="true" />
<timer id="episodeButton" repeat="false" duration="15" />
<Animation id="moveUpskipIntroButton" duration=".1" repeat="false" easeFunction="linear">
<Vector2DFieldInterpolator key="[0.0,1.0]" keyValue="[[1575, 900], [1575, 825]]" fieldToInterp="skipIntro.translation"/>
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[.9, .1]" fieldToInterp="skipIntro.opacity" />
</Animation>
<Animation id="moveDownskipIntroButton" duration=".1" repeat="false" easeFunction="linear">
<Vector2DFieldInterpolator key="[0.0,1.0]" keyValue="[[1575, 825], [1575, 900]]" fieldToInterp="skipIntro.translation"/>
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[.1, .9]" fieldToInterp="skipIntro.opacity" />
</Animation>
<Animation id="showskipIntroButton" duration="1.0" repeat="false" easeFunction="inQuad">
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[0.0, .9]" fieldToInterp="skipIntro.opacity" />
</Animation>
<Animation id="hideskipIntroButton" duration=".2" repeat="false" easeFunction="inQuad">
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[.9, 0]" fieldToInterp="skipIntro.opacity" />
</Animation>
<!--same animation but for the play next episode button-->
<!--animation for the play next episode button-->
<Animation id="moveUpnextEpisodeButton" duration=".1" repeat="false" easeFunction="linear">
<Vector2DFieldInterpolator key="[0.0,1.0]" keyValue="[[1500, 900], [1500, 825]]" fieldToInterp="nextEpisode.translation"/>
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[.9, .1]" fieldToInterp="nextEpisode.opacity" />
@ -68,5 +47,8 @@
<Animation id="hidenextEpisodeButton" duration=".2" repeat="false" easeFunction="inQuad">
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[.9, 0]" fieldToInterp="nextEpisode.opacity" />
</Animation>
<timer id="episodeButton" repeat="false" duration="15" />
<timer id="playbackTimer" repeat="true" duration="30" />
<timer id="bufferCheckTimer" repeat="true" />
</children>
</component>

View File

@ -2,5 +2,5 @@ sub init()
m.top.poster.uri = "pkg:/images/spinner.png"
m.top.control = "start"
m.top.clockwise = true
m.top.spinInterval = 2
m.top.spinInterval = 3
end sub

View File

@ -8,7 +8,7 @@ sub init()
m.name.vertAlign = "center"
m.name.horizAlign = "center"
m.value.hintText = tr("Enter a value...")
m.value.hintText = tr("Enter a username")
m.value.maxTextLength = 120
end sub
@ -17,6 +17,7 @@ sub itemContentChanged()
m.name.text = data.label
if data.type = "password"
m.value.hintText = tr("Enter a password")
m.value.secureMode = true
end if

View File

@ -47,7 +47,7 @@ end function
sub show_dialog(configField)
dialog = createObject("roSGNode", "StandardKeyboardDialog")
m.configField = configField
dialog.title = "Enter the " + configField.label
dialog.title = configField.label
dialog.buttons = [tr("OK"), tr("Cancel")]
m.greenPalette = createObject("roSGNode", "RSGPalette")
m.greenPalette.colors = {

View File

@ -23,12 +23,18 @@ function onKeyEvent(key as string, press as boolean) as boolean
else if key = "down" and submit.focusedChild = invalid
submit.setFocus(true)
return true
else if key = "up" and submit.focusedChild <> invalid or quickConnect.focusedChild <> invalid
else if key = "up" and submit.focusedChild <> invalid
checkbox.setFocus(true)
return true
else if key = "up" and quickConnect.focusedChild <> invalid
checkbox.setFocus(true)
return true
else if key = "up" and checkbox.focusedChild <> invalid
list.setFocus(true)
return true
else if key = "right" and checkbox.focusedChild <> invalid
quickConnect.setFocus(true)
return true
else if key = "right" and submit.focusedChild <> invalid
quickConnect.setFocus(true)
return true

View File

@ -29,9 +29,21 @@ function onKeyEvent(key as string, press as boolean) as boolean
'user navigating up to the server picker from the input box (it's only focusable if it has items)
else if key = "up" and m.serverUrlContainer.hasFocus() and m.servers.Count() > 0
m.serverPicker.setFocus(true)
else if key = "up" and m.serverUrlContainer.hasFocus() and m.servers.Count() = 0
ScanForServers()
else if key = "back" and m.serverUrlContainer.hasFocus() and m.servers.Count() > 0
m.serverPicker.setFocus(true)
else if key = "OK" and m.serverUrlContainer.hasFocus()
ShowKeyboard()
'focus the serverUrl input from submit button
else if key = "back" and m.submit.hasFocus() and m.servers.Count() > 0
m.serverPicker.setFocus(true)
else if key = "back" and m.submit.hasFocus() and m.servers.Count() = 0
m.serverUrlContainer.setFocus(true)
else if key = "back" and m.serverUrlContainer.hasFocus() and m.servers.Count() = 0
ScanForServers()
else if key = "back" and m.serverPicker.hasFocus() and m.servers.Count() > 0
ScanForServers()
' On "back" with or without available local servers, will rescan for servers
else if key = "up" and m.submit.hasFocus()
m.serverUrlContainer.setFocus(true)
'focus the submit button from serverUrl
@ -60,6 +72,7 @@ sub ScanForServers()
'run the task
m.ssdpScanner.observeField("content", "ScanForServersComplete")
m.ssdpScanner.control = "RUN"
m.spinner.visible = true
end sub
sub ScanForServersComplete(event)

View File

@ -1,13 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="MusicAlbumData" extends="ContentNode">
<component name="MusicAlbumData" extends="JFContentItem">
<interface>
<field id="id" type="string" />
<field id="title" type="string" />
<field id="image" type="node" onChange="setPoster" />
<field id="posterURL" type="string" />
<field id="overview" type="string" />
<field id="type" type="string" value="Episode" />
<field id="json" type="assocarray" onChange="setFields" />
</interface>
<script type="text/brightscript" uri="MusicAlbumData.brs" />
</component>

View File

@ -1,15 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="MusicSongData" extends="ContentNode">
<component name="MusicSongData" extends="JFContentItem">
<interface>
<field id="id" type="string" />
<field id="title" type="string" />
<field id="trackNumber" type="integer" />
<field id="image" type="node" onChange="setPoster" />
<field id="posterURL" type="string" />
<field id="overview" type="string" />
<field id="type" type="string" value="Song" />
<field id="json" type="assocarray" onChange="setFields" />
<field id="favorite" type="boolean" />
</interface>
<script type="text/brightscript" uri="MusicSongData.brs" />
</component>

View File

@ -59,6 +59,7 @@ sub onPeopleLoaded()
end for
end if
m.top.content = data
m.top.translation = "[75,10]"
m.top.rowItemSize = [[234, 396]]
m.LikeThisTask.itemId = m.top.parentId
m.LikeThisTask.control = "RUN"

View File

@ -107,6 +107,11 @@ sub onLibrariesLoaded()
end sub
sub updateHomeRows()
if m.global.playstateTask.state = "run"
m.global.playstateTask.observeField("state", "updateHomeRows")
else
m.global.playstateTask.unobserveField("state")
end if
m.LoadContinueTask.observeField("content", "updateContinueItems")
m.LoadContinueTask.control = "RUN"
end sub

View File

@ -1,6 +1,5 @@
sub init()
getData()
m.top.infocus = false
end sub
function getData()
@ -16,10 +15,15 @@ function getData()
for each album in albumData.items
gridAlbum = CreateObject("roSGNode", "ContentNode")
if not isValid(album.posterURL) or album.posterURL = ""
album.posterURL = "pkg:/images/icons/album.png"
end if
gridAlbum.shortdescriptionline1 = album.title
gridAlbum.HDGRIDPOSTERURL = album.posterURL
gridAlbum.hdposterurl = album.posterURL
gridAlbum.SDGRIDPOSTERURL = album.SDGRIDPOSTERURL
gridAlbum.SDGRIDPOSTERURL = album.posterURL
gridAlbum.sdposterurl = album.posterURL
data.appendChild(gridAlbum)
@ -35,7 +39,26 @@ function onKeyEvent(key as string, press as boolean) as boolean
if key = "up"
if m.top.itemFocused <= 4
m.top.infocus = false
m.top.escape = key
return true
end if
else if key = "left"
if m.top.itemFocused mod 5 = 0
m.top.escape = key
return true
end if
else if key = "right"
if m.top.itemFocused + 1 mod 5 = 0
m.top.escape = key
return true
end if
else if key = "down"
totalCount = m.top.MusicArtistAlbumData.items.count()
totalRows = div_ceiling(totalCount, 5)
currentRow = div_ceiling(m.top.itemFocused + 1, 5)
if currentRow = totalRows
m.top.escape = key
return true
end if
end if

View File

@ -2,7 +2,8 @@
<component name="AlbumGrid" extends="PosterGrid">
<interface>
<field id="MusicArtistAlbumData" type="assocarray" onChange="getData" />
<field id="infocus" type="boolean" />
<field id="escape" type="string" alwaysNotify="true" />
</interface>
<script type="text/brightscript" uri="AlbumGrid.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
</component>

View File

@ -3,22 +3,33 @@ sub init()
setupMainNode()
setupButtons()
m.remoteButtonsActive = true
m.albumHeader = m.top.findNode("albumHeader")
m.albumHeader.text = tr("Albums")
m.appearsOnHeader = m.top.findNode("appearsOnHeader")
m.appearsOnHeader.text = tr("AppearsOn")
m.appearsOn = m.top.findNode("appearsOn")
m.appearsOn.observeField("escape", "onAppearsOnEscape")
m.appearsOn.observeField("MusicArtistAlbumData", "onAppearsOnData")
m.albums = m.top.findNode("albums")
m.albums.observeField("infocus", "onAlbumFocusChange")
m.albums.observeField("escape", "onAlbumsEscape")
m.albums.observeField("MusicArtistAlbumData", "onAlbumsData")
m.pageLoadAnimation = m.top.findNode("pageLoad")
m.pageLoadAnimation.control = "start"
m.showAlbumsAnimation = m.top.findNode("showAlbums")
m.hideAlbumsAnimation = m.top.findNode("hideAlbums")
m.sectionNavigation = m.top.findNode("sectionNavigation")
m.sectionNavigation.observeField("escape", "onSectionNavigationEscape")
m.sectionNavigation.observeField("selected", "onSectionNavigationSelected")
m.sectionScroller = m.top.findNode("sectionScroller")
m.sectionScroller.observeField("displayedIndex", "onSectionScrollerChange")
m.overhang = m.top.getScene().findNode("overhang")
' Load background image
m.LoadBackdropImageTask = CreateObject("roSGNode", "LoadItemsTask")
m.LoadBackdropImageTask.itemsToLoad = "backdropImage"
@ -30,6 +41,70 @@ sub init()
createDialogPallete()
end sub
sub onAlbumsData()
' We have no album data
if m.albums.MusicArtistAlbumData.TotalRecordCount = 0
m.sectionScroller.removeChild(m.top.findNode("albumsSlide"))
m.sectionNavigation.removeChild(m.top.findNode("albumsLink"))
m.top.findNode("appearsOnSlide").callFunc("scrollUpToOnDeck")
end if
end sub
sub onAppearsOnData()
' We have no appears on data
if m.appearsOn.MusicArtistAlbumData.TotalRecordCount = 0
m.sectionScroller.removeChild(m.top.findNode("appearsOnSlide"))
m.sectionNavigation.removeChild(m.top.findNode("appearsOnLink"))
end if
end sub
sub onSectionScrollerChange()
m.overhang.isVisible = (m.sectionScroller.displayedIndex = 0)
end sub
sub OnScreenShown()
m.sectionScroller.focus = true
if m.sectionScroller.displayedIndex = 0
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
m.top.selectedButtonIndex = 0
m.buttonGrp.setFocus(true)
else
m.overhang.opacity = "0"
m.overhang.isVisible = false
m.overhang.opacity = "1"
end if
end sub
sub OnScreenHidden()
if not m.overhang.isVisible
m.overhang.disableMoveAnimation = true
m.overhang.isVisible = true
m.overhang.disableMoveAnimation = false
m.overhang.opacity = "1"
end if
end sub
sub onAlbumsEscape()
if m.albums.escape = "up"
m.sectionNavigation.selected = m.sectionScroller.displayedIndex - 1
else if m.albums.escape = "left"
m.sectionNavigation.setFocus(true)
else if m.albums.escape = "down"
if m.sectionScroller.displayedIndex + 1 < m.sectionNavigation.getChildCount()
m.sectionNavigation.selected = m.sectionScroller.displayedIndex + 1
end if
end if
end sub
sub onAppearsOnEscape()
if m.appearsOn.escape = "up"
m.sectionNavigation.selected = m.sectionScroller.displayedIndex - 1
else if m.appearsOn.escape = "left"
m.sectionNavigation.setFocus(true)
end if
end sub
' Setup playback buttons, default to Play button selected
sub setupButtons()
m.buttonGrp = m.top.findNode("buttons")
@ -46,13 +121,13 @@ end sub
sub onButtonSelectedChange()
' Change previously selected button back to default image
if m.previouslySelectedButtonIndex > -1
selectedButton = m.buttonGrp.getChild(m.previouslySelectedButtonIndex)
selectedButton.setFocus(false)
previousSelectedButton = m.buttonGrp.getChild(m.previouslySelectedButtonIndex)
previousSelectedButton.focus = false
end if
' Change selected button image to selected image
selectedButton = m.buttonGrp.getChild(m.top.selectedButtonIndex)
selectedButton.setFocus(true)
selectedButton.focus = true
end sub
sub setupMainNode()
@ -72,7 +147,6 @@ sub pageContentChanged()
' Populate scene data
setScreenTitle(item.json)
setPosterImage(item.posterURL)
setOnScreenTextValues(item.json)
end sub
sub setScreenTitle(json)
@ -106,10 +180,12 @@ sub setBackdropImage(data)
end if
end sub
' Populate on screen text variables
sub setOnScreenTextValues(json)
if isValid(json)
setFieldTextValue("overview", json.overview)
' Event fired when page data is loaded
sub artistOverviewChanged()
overviewContent = m.top.artistOverview
if isValid(overviewContent)
setFieldTextValue("overview", overviewContent)
end if
end sub
@ -119,36 +195,16 @@ sub onEllipsisChanged()
end if
end sub
sub onAlbumFocusChange()
if m.albums.infocus
m.albums.setFocus(true)
m.showAlbumsAnimation.control = "start"
return
end if
' Change selected button image to selected image
selectedButton = m.buttonGrp.getChild(m.top.selectedButtonIndex)
selectedButton.setFocus(true)
m.albums.setFocus(false)
m.hideAlbumsAnimation.control = "start"
end sub
sub onSectionNavigationEscape()
if m.sectionNavigation.escape = "right"
if m.albums.infocus
m.albums.setFocus(true)
return
end if
selectedButton = m.buttonGrp.getChild(0)
selectedButton.setFocus(true)
m.sectionNavigation.setFocus(false)
m.remoteButtonsActive = false
m.sectionScroller.focus = true
end if
end sub
sub onSectionNavigationSelected()
m.albums.infocus = (m.sectionNavigation.selected = 1)
m.sectionScroller.displayedIndex = m.sectionNavigation.selected
end sub
sub dscrShowFocus()
@ -192,8 +248,17 @@ sub createDialogPallete()
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if key = "left"
if m.buttonGrp.isInFocusChain()
if m.buttonGrp.isInFocusChain()
if key = "OK"
if press
selectedButton = m.buttonGrp.getChild(m.top.selectedButtonIndex)
selectedButton.selected = not selectedButton.selected
return true
end if
end if
if key = "left"
if m.top.selectedButtonIndex > 0
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
m.top.selectedButtonIndex = m.top.selectedButtonIndex - 1
@ -201,7 +266,9 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
if press
m.buttonGrp.setFocus(false)
selectedButton = m.buttonGrp.getChild(m.top.selectedButtonIndex)
selectedButton.focus = false
m.sectionNavigation.setFocus(true)
return true
end if
@ -209,29 +276,29 @@ function onKeyEvent(key as string, press as boolean) as boolean
return false
end if
if m.albums.isInFocusChain()
if m.albums.itemFocused mod 5 = 0
m.sectionNavigation.setFocus(true)
if key = "right"
if m.top.pageContent.count() = 1 then return false
if m.buttonGrp.getChild(m.top.selectedButtonIndex).escape = "right"
m.buttonGrp.getChild(m.top.selectedButtonIndex).escape = ""
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
if m.top.selectedButtonIndex < m.buttonCount - 1
m.top.selectedButtonIndex = m.top.selectedButtonIndex + 1
end if
return true
end if
end if
end if
if m.buttonGrp.isInFocusChain()
if key = "down"
m.albums.infocus = true
return true
else if key = "right"
if m.sectionNavigation.escape = "right"
m.sectionNavigation.escape = ""
return true
if m.sectionNavigation.getChildCount() > 1
selectedButton = m.buttonGrp.getChild(m.top.selectedButtonIndex)
selectedButton.focus = false
m.top.selectedButtonIndex = 0
m.sectionNavigation.selected = m.sectionScroller.displayedIndex + 1
end if
if m.top.pageContent.count() = 1 then return false
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
if m.top.selectedButtonIndex < m.buttonCount - 1 then m.top.selectedButtonIndex = m.top.selectedButtonIndex + 1
return true
end if
end if

View File

@ -1,54 +1,56 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="ArtistView" extends="JFGroup">
<component name="ArtistView" extends="JFScreen">
<children>
<Poster id="backdrop" opacity=".4" loadDisplayMode="scaleToZoom" width="1920" height="1200" blendColor="#3f3f3f" />
<LayoutGroup id="toplevel" layoutDirection="vert" itemSpacings="[75]">
<LayoutGroup id="main_group" layoutDirection="horiz" itemSpacings="[125]">
<LayoutGroup layoutDirection="vert" itemSpacings="[75]">
<Label id="overview" wrap="true" lineSpacing="25" maxLines="6" width="1080" ellipsisText=" ... (Press * to read more)" />
<ButtonGroupHoriz id="buttons" itemSpacings="[20]">
<IconButton id="play" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/play.png" text="Play" height="85" width="150" />
<IconButton id="instantMix" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/instantMix.png" text="Instant Mix" height="85" width="150" />
</ButtonGroupHoriz>
<SectionScroller id="sectionScroller">
<Section id="slide-1" defaultFocusID="play">
<LayoutGroup id="toplevel" layoutDirection="vert" itemSpacings="[75]">
<LayoutGroup id="main_group" layoutDirection="horiz" itemSpacings="[125]">
<LayoutGroup layoutDirection="vert" itemSpacings="[75]">
<Label id="overview" wrap="true" lineSpacing="25" maxLines="6" width="1080" ellipsisText=" ... (Press * to read more)" />
<ButtonGroupHoriz id="buttons" itemSpacings="[20]">
<IconButton id="play" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/play.png" text="Play" height="85" width="150" />
<IconButton id="instantMix" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/instantMix.png" text="Instant Mix" height="85" width="150" />
</ButtonGroupHoriz>
</LayoutGroup>
<Poster id="artistImage" width="500" height="500" />
</LayoutGroup>
</LayoutGroup>
<Poster id="artistImage" width="500" height="500" />
</LayoutGroup>
</LayoutGroup>
<Rectangle id='albumRect' translation="[0, 1050]" width="1920" height="1080" color="#000000" opacity=".75" />
<Label id="albumHeader" translation="[120, 1100]" font="font:LargeSystemFont" />
<AlbumGrid id="albums" translation="[120, 1200]" vertFocusAnimationStyle="fixedFocus" basePosterSize="[300, 300]" numColumns="5" numRows="99" caption1NumLines="1" itemSpacing="[50, 50]" />
</Section>
<Section id="albumsSlide" translation="[0, 950]" defaultFocusID="albums">
<Rectangle id='albumRect' translation="[0, 0]" width="1920" height="1080" color="#000000" opacity=".75" />
<Label id="albumHeader" translation="[120, 50]" font="font:LargeSystemFont" />
<AlbumGrid id="albums" translation="[120, 150]" vertFocusAnimationStyle="fixedFocus" basePosterSize="[300, 300]" numColumns="5" numRows="99" caption1NumLines="1" itemSpacing="[50, 50]" />
</Section>
<Section id="appearsOnSlide" translation="[0, 1100]" defaultFocusID="appearsOn">
<Rectangle id='appearsOnRect' translation="[0, 0]" width="1920" height="1080" color="#000000" opacity=".75" />
<Label id="appearsOnHeader" translation="[120, 50]" font="font:LargeSystemFont" />
<AlbumGrid id="appearsOn" translation="[120, 150]" vertFocusAnimationStyle="fixedFocus" basePosterSize="[300, 300]" numColumns="5" numRows="99" caption1NumLines="1" itemSpacing="[50, 50]" />
</Section>
</SectionScroller>
<bgv_ButtonGroupVert id="sectionNavigation" translation="[-100, 175]" itemSpacings="[10]">
<sob_SlideOutButton background="#070707" focusBackground="#00a4dc" highlightBackground="#555555" padding="20" icon="pkg:/images/icons/details.png" text="Details" height="50" width="60" />
<sob_SlideOutButton background="#070707" focusBackground="#00a4dc" highlightBackground="#555555" padding="20" icon="pkg:/images/icons/cd.png" text="Albums" height="50" width="60" />
<sob_SlideOutButton id="albumsLink" background="#070707" focusBackground="#00a4dc" highlightBackground="#555555" padding="20" icon="pkg:/images/icons/cd.png" text="Albums" height="50" width="60" />
<sob_SlideOutButton id="appearsOnLink" background="#070707" focusBackground="#00a4dc" highlightBackground="#555555" padding="20" icon="pkg:/images/icons/cassette.png" text="Appears On" height="50" width="60" />
</bgv_ButtonGroupVert>
<Animation id="pageLoad" duration=".5" repeat="false">
<Vector2DFieldInterpolator key="[0.0, 1.0]" keyValue="[[0, 1050], [0, 750]]" fieldToInterp="albumRect.translation" />
<Vector2DFieldInterpolator key="[0.0, 1.0]" keyValue="[[120, 1100], [120, 800]]" fieldToInterp="albumHeader.translation" />
<Vector2DFieldInterpolator key="[0.0, 1.0]" keyValue="[[120, 1200], [120, 900]]" fieldToInterp="albums.translation" />
<Animation id="pageLoad" duration="1" repeat="false">
<Vector2DFieldInterpolator key="[0.5, 1.0]" keyValue="[[-100, 175], [40, 175]]" fieldToInterp="sectionNavigation.translation" />
</Animation>
<Animation id="showAlbums" duration="0.5" repeat="false">
<Vector2DFieldInterpolator key="[0.0, 1.0]" keyValue="[[0, 750], [0, 0]]" fieldToInterp="albumRect.translation" />
<Vector2DFieldInterpolator key="[0.0, 1.0]" keyValue="[[120, 800], [120, 175]]" fieldToInterp="albumHeader.translation" />
<Vector2DFieldInterpolator key="[0.0, 1.0]" keyValue="[[120, 900], [120, 275]]" fieldToInterp="albums.translation" />
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[0.75, 0.95]" fieldToInterp="albumRect.opacity" />
</Animation>
<Animation id="hideAlbums" duration="0.5" repeat="false">
<Vector2DFieldInterpolator key="[0.0, 1.0]" keyValue="[[0, 0], [0, 750]]" fieldToInterp="albumRect.translation" />
<Vector2DFieldInterpolator key="[0.0, 1.0]" keyValue="[[120, 175], [120, 800]]" fieldToInterp="albumHeader.translation" />
<Vector2DFieldInterpolator key="[0.0, 1.0]" keyValue="[[120, 275], [120, 900]]" fieldToInterp="albums.translation" />
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[0.95, 0.75]" fieldToInterp="albumRect.opacity" />
</Animation>
</children>
<interface>
<field id="pageContent" type="node" onChange="pageContentChanged" />
<field id="musicArtistAlbumData" type="assocarray" alias="albums.MusicArtistAlbumData" />
<field id="musicArtistAppearsOnData" type="assocarray" alias="appearsOn.MusicArtistAlbumData" />
<field id="artistOverview" type="string" onChange="artistOverviewChanged" />
<field id="musicAlbumSelected" alias="albums.itemSelected" />
<field id="appearsOnSelected" alias="appearsOn.itemSelected" />
<field id="playArtistSelected" alias="play.selected" />
<field id="instantMixSelected" alias="instantMix.selected" />
<field id="selectedButtonIndex" type="integer" value="-1" />

View File

@ -408,10 +408,16 @@ sub onMetaDataLoaded()
if data <> invalid and data.count() > 0
' Use metadata to load backdrop image
if isvalid(data?.json?.ArtistItems?[0]?.id)
m.LoadBackdropImageTask.itemId = data.json.ArtistItems[0].id
m.LoadBackdropImageTask.observeField("content", "onBackdropImageLoaded")
m.LoadBackdropImageTask.control = "RUN"
if isvalid(data.json)
if isValid(data.json.ArtistItems)
if data.json.ArtistItems.count() > 0
if isValid(data.json.ArtistItems[0].id)
m.LoadBackdropImageTask.itemId = data.json.ArtistItems[0].id
m.LoadBackdropImageTask.observeField("content", "onBackdropImageLoaded")
m.LoadBackdropImageTask.control = "RUN"
end if
end if
end if
end if
setPosterImage(data.posterURL)

View File

@ -0,0 +1,95 @@
sub init()
m.showFromBottomAnimation = m.top.findNode("showFromBottomAnimation")
m.showFromBottomPosition = m.top.findNode("showFromBottomPosition")
m.showFromBottomOpacity = m.top.findNode("showFromBottomOpacity")
m.showFromTopAnimation = m.top.findNode("showFromTopAnimation")
m.showFromTopPosition = m.top.findNode("showFromTopPosition")
m.showFromTopOpacity = m.top.findNode("showFromTopOpacity")
m.scrollOffTopAnimation = m.top.findNode("scrollOffTopAnimation")
m.scrollOffTopPosition = m.top.findNode("scrollOffTopPosition")
m.scrollOffTopOpacity = m.top.findNode("scrollOffTopOpacity")
m.scrollOffBottomAnimation = m.top.findNode("scrollOffBottomAnimation")
m.scrollOffBottomPosition = m.top.findNode("scrollOffBottomPosition")
m.scrollOffBottomOpacity = m.top.findNode("scrollOffBottomOpacity")
m.scrollUpToOnDeckAnimation = m.top.findNode("scrollUpToOnDeckAnimation")
m.scrollUpToOnDeckPosition = m.top.findNode("scrollUpToOnDeckPosition")
m.scrollDownToOnDeckAnimation = m.top.findNode("scrollDownToOnDeckAnimation")
m.scrollDownToOnDeckPosition = m.top.findNode("scrollDownToOnDeckPosition")
m.scrollOffOnDeckAnimation = m.top.findNode("scrollOffOnDeckAnimation")
m.scrollOffOnDeckPosition = m.top.findNode("scrollOffOnDeckPosition")
m.top.observeField("translation", "onTranslationChange")
m.top.observeField("id", "onIDChange")
m.top.observeField("focusedChild", "onFocusChange")
end sub
sub onIDChange()
m.showFromBottomPosition.fieldToInterp = m.top.id + ".translation"
m.showFromBottomOpacity.fieldToInterp = m.top.id + ".opacity"
m.showFromTopPosition.fieldToInterp = m.top.id + ".translation"
m.showFromTopOpacity.fieldToInterp = m.top.id + ".opacity"
m.scrollOffTopPosition.fieldToInterp = m.top.id + ".translation"
m.scrollOffTopOpacity.fieldToInterp = m.top.id + ".opacity"
m.scrollOffBottomPosition.fieldToInterp = m.top.id + ".translation"
m.scrollOffBottomOpacity.fieldToInterp = m.top.id + ".opacity"
m.scrollUpToOnDeckPosition.fieldToInterp = m.top.id + ".translation"
m.scrollDownToOnDeckPosition.fieldToInterp = m.top.id + ".translation"
m.scrollOffOnDeckPosition.fieldToInterp = m.top.id + ".translation"
end sub
sub onTranslationChange()
m.startingPosition = m.top.translation
m.scrollOffBottomPosition.keyValue = "[[0, 0], [" + str(m.startingPosition[0]) + ", " + str(m.startingPosition[1]) + "]]"
m.top.unobserveField("translation")
end sub
sub showFromTop()
m.showFromTopAnimation.control = "start"
end sub
sub showFromBottom()
m.showFromBottomAnimation.control = "start"
end sub
sub scrollOffBottom()
m.scrollOffBottomAnimation.control = "start"
end sub
sub scrollOffTop()
m.scrollOffTopAnimation.control = "start"
end sub
sub scrollUpToOnDeck()
m.scrollUpToOnDeckAnimation.control = "start"
end sub
sub scrollDownToOnDeck()
m.scrollDownToOnDeckAnimation.control = "start"
end sub
sub scrollOffOnDeck()
m.scrollOffOnDeckAnimation.control = "start"
end sub
sub onFocusChange()
defaultFocusElement = m.top.findNode(m.top.defaultFocusID)
if isValid(defaultFocusElement)
defaultFocusElement.setFocus(m.top.isInFocusChain())
if isValid(defaultFocusElement.focus)
defaultFocusElement.focus = m.top.isInFocusChain()
end if
end if
end sub

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="Section" extends="JFGroup">
<children>
<Animation id="showFromBottomAnimation" duration="0.5" repeat="false">
<Vector2DFieldInterpolator id="showFromBottomPosition" key="[0.0, 1.0]" keyValue="[[0, 750], [0, 0]]" fieldToInterp="" />
<FloatFieldInterpolator id="showFromBottomOpacity" key="[0.0, 1.0]" keyValue="[0.55, 0.95]" fieldToInterp="" />
</Animation>
<Animation id="showFromTopAnimation" duration="0.5" repeat="false">
<Vector2DFieldInterpolator id="showFromTopPosition" key="[0.0, 1.0]" keyValue="[[0, -1080], [0, 0]]" fieldToInterp="" />
<FloatFieldInterpolator id="showFromTopOpacity" key="[0.0, 1.0]" keyValue="[0.55, 0.95]" fieldToInterp="" />
</Animation>
<Animation id="scrollOffBottomAnimation" duration="0.5" repeat="false">
<Vector2DFieldInterpolator id="scrollOffBottomPosition" key="[0.0, 1.0]" keyValue="[]" fieldToInterp="" />
<FloatFieldInterpolator id="scrollOffBottomOpacity" key="[0.0, 1.0]" keyValue="[0.95, 0.55]" fieldToInterp="" />
</Animation>
<Animation id="scrollOffTopAnimation" duration="0.5" repeat="false">
<Vector2DFieldInterpolator id="scrollOffTopPosition" key="[0.0, 1.0]" keyValue="[[0, 0], [0, -1080]]" fieldToInterp="" />
<FloatFieldInterpolator id="scrollOffTopOpacity" key="[0.0, 1.0]" keyValue="[0.95, 0.55]" fieldToInterp="" />
</Animation>
<Animation id="scrollUpToOnDeckAnimation" duration="0.5" repeat="false">
<Vector2DFieldInterpolator id="scrollUpToOnDeckPosition" key="[0.0, 1.0]" keyValue="[[0, 1100], [0, 950]]" fieldToInterp="" />
</Animation>
<Animation id="scrollDownToOnDeckAnimation" duration="0.5" repeat="false">
<Vector2DFieldInterpolator id="scrollDownToOnDeckPosition" key="[0.0, 1.0]" keyValue="[[0, 0], [0, 950]]" fieldToInterp="" />
</Animation>
<Animation id="scrollOffOnDeckAnimation" duration="0.5" repeat="false">
<Vector2DFieldInterpolator id="scrollOffOnDeckPosition" key="[0.0, 1.0]" keyValue="[[0, 950], [0, 1080]]" fieldToInterp="" />
</Animation>
</children>
<interface>
<field id="defaultFocusID" type="string" />
<function name="showFromTop" />
<function name="showFromBottom" />
<function name="scrollOffTop" />
<function name="scrollOffBottom" />
<function name="scrollUpToOnDeck" />
<function name="scrollDownToOnDeck" />
<function name="scrollOffOnDeck" />
</interface>
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="section.brs" />
</component>

View File

@ -0,0 +1,58 @@
sub init()
m.previouslyDisplayedSection = 0
end sub
sub onFocusChange()
if m.top.focus
m.top.getChild(m.top.displayedIndex).setFocus(true)
end if
end sub
sub displayedIndexChanged()
if not m.top.affectsFocus then return
if m.top.displayedIndex < 0
return
end if
if m.top.displayedIndex > (m.top.getChildCount() - 1)
return
end if
m.top.getChild(m.previouslyDisplayedSection).setFocus(false)
displayedSection = m.top.getChild(m.top.displayedIndex)
displayedSection.setFocus(true)
onDeckSection = invalid
previouslyOnDeckSection = invalid
if m.top.displayedIndex + 1 <= (m.top.getChildCount() - 1)
onDeckSection = m.top.getChild(m.top.displayedIndex + 1)
end if
if m.top.displayedIndex + 2 <= (m.top.getChildCount() - 1)
previouslyOnDeckSection = m.top.getChild(m.top.displayedIndex + 2)
end if
' Move sections either up or down depending on what index we're moving to
if m.top.displayedIndex > m.previouslyDisplayedSection
for i = m.previouslyDisplayedSection to m.top.displayedIndex - 1
m.top.getChild(i).callFunc("scrollOffTop")
end for
displayedSection.callFunc("showFromBottom")
if isValid(onDeckSection)
onDeckSection.callFunc("scrollUpToOnDeck")
end if
else if m.top.displayedIndex < m.previouslyDisplayedSection
m.top.getChild(m.top.displayedIndex + 1).callFunc("scrollDownToOnDeck")
displayedSection.callFunc("showFromTop")
if isValid(previouslyOnDeckSection)
previouslyOnDeckSection.callFunc("scrollOffOnDeck")
end if
end if
m.previouslyDisplayedSection = m.top.displayedIndex
end sub

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="SectionScroller" extends="JFGroup">
<children>
</children>
<interface>
<field id="focus" type="boolean" value="true" alwaysNotify="true" onChange="onFocusChange" />
<field id="affectsFocus" type="boolean" value="true" />
<field id="displayedIndex" type="integer" value="0" onChange="displayedIndexChanged" />
</interface>
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="sectionScroller.brs" />
</component>

View File

@ -20,7 +20,7 @@ sub updateSize()
m.top.visible = true
' size of the whole row
m.top.itemSize = [1800, (itemHeight + 40)]
m.top.itemSize = [1680, (itemHeight + 40)]
' spacing between rows
m.top.itemSpacing = [0, 0]

BIN
images/icons/album.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
images/icons/cassette.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -112,9 +112,17 @@
<source>Home</source>
<translation>Основен</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Въведете потребителско име</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Въведете паролата</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Въведете стойност</translation>
<translation>Въведете стойност...</translation>
</message>
<message>
<source>Sort Field</source>

View File

@ -112,9 +112,17 @@
<source>Home</source>
<translation>Domů</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Zadejte jméno</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Zadejte heslo</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Zadejte hodnotu</translation>
<translation>Zadejte hodnotu...</translation>
</message>
<message>
<source>Sort Field</source>
@ -3471,5 +3479,169 @@
<source>Save Credentials?</source>
<translation>Uložit přihlašovací údaje?</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>Tato %1 neobsahuje žádné položky</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Honocení kritiků</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Hodnocení IMDb</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Název</translation>
</message>
<message>
<source>On Now</source>
<translation>Nyní</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Smazat uložené</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Uložit přihlašovací údaje?</translation>
</message>
<message>
<source>direct</source>
<translation>přímý</translation>
</message>
<message>
<source>Audio Codec</source>
<translation>Audio kodek</translation>
</message>
<message>
<source>Video Codec</source>
<translation>Video kodek</translation>
</message>
<message>
<source>Reason</source>
<translation>Důvod</translation>
</message>
<message>
<source>Transcoding Information</source>
<translation>Informace o překódování</translation>
</message>
<message>
<source>Playback Information</source>
<translation>Informace o přehrávání</translation>
</message>
<message>
<source>Set the maximum amount of days a show should stay in the &apos;Next Up&apos; list without watching it.</source>
<translation>Nastavení maximálního počtu dní, pro setrvání pořadu v seznamu &quot;Další díly&quot;, aniž by byl zhlédnut.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Max Days Next Up</source>
<translation>Maximum dní pro další díly</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Home Page.</source>
<translation>Možností domovské stránky.</translation>
<extracomment>Description for Home Page user settings.</extracomment>
</message>
<message>
<source>Home Page</source>
<translation>Domovská stránka</translation>
</message>
<message>
<source>Settings relating to how the application looks.</source>
<translation>Nastavení související se vzhledem aplikace.</translation>
</message>
<message>
<source>Settings relating to playback and supported codec and media types.</source>
<translation>Nastavení, která se týkají přehrávání, podporovanému kodeku a typům médií.</translation>
</message>
<message>
<source>Play Trailer</source>
<translation>Přehrát trailer</translation>
</message>
<message>
<source>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
<translation>Režim Cinema přináší zážitek z kina přímo do vašeho obývacího pokoje díky možnosti přehrávání vlastních úvodů před hlavním titulem.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Cinema Mode</source>
<translation>Režim Cinema</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Použijte vygenerovaný obrázek úvodní obrazovky jako pozadí domovské stránky Jellyfin. Aby se změna projevila, bude třeba Jellyfin zavřít a znovu otevřít.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Použijte vygenerovaný obrázek úvodní obrazovky jako pozadí spořiče obrazovky Jellyfin. Aby se změna projevila, bude třeba Jellyfin zavřít a znovu otevřít.</translation>
</message>
<message>
<source>If enabled, images of unwatched episodes will be blurred.</source>
<translation>Pokud je povoleno, obrázky nezobrazených epizod budou zobrazeny rozmazaně.</translation>
</message>
<message>
<source>Hide Taglines</source>
<translation>Skrýt slogany</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Details pages.</source>
<translation>Možnosti pro stránky s podrobnostmi.</translation>
<extracomment>Description for Details page user settings.</extracomment>
</message>
<message>
<source>Details Page</source>
<translation>Stránka s podrobnostmi</translation>
</message>
<message>
<source>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</source>
<translation>Pomocí tlačítka pro přehrávání se můžete pomalu pohybovat k první položce ve složce. (Pokud je vypnuto, složka se znovu hned nastaví na první položku).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>(Dialog will close automatically)</source>
<translation>(Dialog se automaticky zavře)</translation>
</message>
<message>
<source>Here is your Quick Connect code:</source>
<translation>Zde je Váš kód pro rychlé připojení:</translation>
</message>
<message>
<source>Quick Connect</source>
<translation>Rychle připojit</translation>
</message>
<message>
<source>%1 of %2</source>
<translation>%1 z %2</translation>
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
</message>
<message>
<source>You can search for Titles, People, Live TV Channels and more</source>
<translation>Můžete hledat názvy, osoby, kanály živého vysílání a mnohem více</translation>
<extracomment>Help text in search results</extracomment>
</message>
<message>
<source>Search now</source>
<translation>Hledat teď</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>Use voice remote to search</source>
<translation>K hledání použít hlasové ovládání</translation>
<extracomment>Help text in search voice text box</extracomment>
</message>
<message>
<source>Go to episode</source>
<translation>Přejít k epizodě</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
</message>
</context>
</TS>

File diff suppressed because it is too large Load Diff

View File

@ -113,8 +113,12 @@
<translation>Home</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Enter a value</translation>
<source>Enter a username</source>
<translation>Enter a username</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Enter a password</translation>
</message>
<message>
<source>Name</source>
@ -374,7 +378,7 @@
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Pick a Jellyfin server from the local network</translation>
<translation>Select an available Jellyfin server from your local network:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
@ -384,7 +388,7 @@
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>or enter server URL manually:</translation>
<translation>If no server is listed above, you may also enter the server URL manually:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
@ -844,5 +848,823 @@
<source>Save Credentials?</source>
<translation>Save Credentials?</translation>
</message>
<message>
<source>Set the maximum amount of days a show should stay in the &apos;Next Up&apos; list without watching it.</source>
<translation>Set the maximum amount of days a show should stay in the &apos;Next Up&apos; list without watching it.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Max Days Next Up</source>
<translation>Max Days Next Up</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Home Page.</source>
<translation>Options for Home Page.</translation>
<extracomment>Description for Home Page user settings.</extracomment>
</message>
<message>
<source>Home Page</source>
<translation>Home Page</translation>
</message>
<message>
<source>Settings relating to how the application looks.</source>
<translation>Settings relating to how the application looks.</translation>
</message>
<message>
<source>Settings relating to playback and supported codec and media types.</source>
<translation>Settings relating to playback and supported codec and media types.</translation>
</message>
<message>
<source>Play Trailer</source>
<translation>Play Trailer</translation>
</message>
<message>
<source>Skip Intro</source>
<translation>Skip Intro</translation>
</message>
<message>
<source>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Hide Clock</source>
<translation>Hide Clock</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
<translation>Cinema Mode brings the theatre experience straight to your living room with the ability to play custom intros before the main feature.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Cinema Mode</source>
<translation>Cinema Mode</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Use Splashscreen as Home Background</source>
<translation>Use Splashscreen as Home Background</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options that alter the design of Jellyfin.</source>
<translation>Options that alter the design of Jellyfin.</translation>
<extracomment>Description for Design Elements user settings.</extracomment>
</message>
<message>
<source>Design Elements</source>
<translation>Design Elements</translation>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
</message>
<message>
<source>Use Splashscreen as Screensaver Background</source>
<translation>Use Splashscreen as Screensaver Background</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Jellyfin&apos;s screensaver.</source>
<translation>Options for Jellyfin&apos;s screensaver.</translation>
<extracomment>Description for Screensaver user settings.</extracomment>
</message>
<message>
<source>Screensaver</source>
<translation>Screensaver</translation>
</message>
<message>
<source>If enabled, images of unwatched episodes will be blurred.</source>
<translation>If enabled, images of unwatched episodes will be blurred.</translation>
</message>
<message>
<source>Blur Unwatched Episodes</source>
<translation>Blur Unwatched Episodes</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for TV Shows.</source>
<translation>Options for TV Shows.</translation>
<extracomment>Description for TV Shows user settings.</extracomment>
</message>
<message>
<source>Hides tagline text on details pages.</source>
<translation>Hides tagline text on details pages.</translation>
</message>
<message>
<source>Hide Taglines</source>
<translation>Hide Taglines</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Details pages.</source>
<translation>Options for Details pages.</translation>
<extracomment>Description for Details page user settings.</extracomment>
</message>
<message>
<source>Details Page</source>
<translation>Details Page</translation>
</message>
<message>
<source>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</source>
<translation>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Return to Top</source>
<translation>Return to Top</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Shows</source>
<translation>Shows</translation>
</message>
<message>
<source>Studios</source>
<translation>Studios</translation>
</message>
<message>
<source>Networks</source>
<translation>Networks</translation>
</message>
<message>
<source>There was an error authenticating via Quick Connect.</source>
<translation>There was an error authenticating via Quick Connect.</translation>
</message>
<message>
<source>(Dialog will close automatically)</source>
<translation>(Dialog will close automatically)</translation>
</message>
<message>
<source>Here is your Quick Connect code:</source>
<translation>Here is your Quick Connect code:</translation>
</message>
<message>
<source>Quick Connect</source>
<translation>Quick Connect</translation>
</message>
<message>
<source>%1 of %2</source>
<translation>%1 of %2</translation>
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
</message>
<message>
<source>You can search for Titles, People, Live TV Channels and more</source>
<translation>You can search for Titles, People, Live TV Channels and more</translation>
<extracomment>Help text in search results</extracomment>
</message>
<message>
<source>Search now</source>
<translation>Search now</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>Use voice remote to search</source>
<translation>Use voice remote to search</translation>
<extracomment>Help text in search voice text box</extracomment>
</message>
<message>
<source>Go to episode</source>
<translation>Go to episode</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
</message>
<message>
<source>Go to season</source>
<translation>Go to season</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</extracomment>
</message>
<message>
<source>Go to series</source>
<translation>Go to series</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Series Detail Page</extracomment>
</message>
<message>
<source>Set Watched</source>
<translation>Set Watched</translation>
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
</message>
<message>
<source>Set Favorite</source>
<translation>Set Favourite</translation>
<extracomment>Button Text - When pressed, sets item as Favorite</extracomment>
</message>
<message>
<source>Show item count in the library and index of selected item.</source>
<translation>Show item count in the library and index of selected item.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Item Count</source>
<translation>Item Count</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment>
</message>
<message>
<source>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</source>
<translation>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Item Titles</source>
<translation>Item Titles</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Media Grid options.</source>
<translation>Media Grid options.</translation>
</message>
<message>
<source>Media Grid</source>
<translation>Media Grid</translation>
<extracomment>UI -&gt; Media Grid section in user setting screen.</extracomment>
</message>
<message>
<source>User Interface</source>
<translation>User Interface</translation>
<extracomment>Title for User Interface section in user setting screen.</extracomment>
</message>
<message>
<source>Disabled</source>
<translation>Disabled</translation>
</message>
<message>
<source>Enabled</source>
<translation>Enabled</translation>
</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>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>MPEG-2 Support</source>
<translation>MPEG-2 Support</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Playback</source>
<translation>Playback</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
<message>
<source>Version</source>
<translation>Version</translation>
</message>
<message>
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>An error was encountered while playing this item. Server did not provide required transcoding data.</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>Error Getting Playback Information</source>
<translation>Error Getting Playback Information</translation>
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>If no server is listed above, you may also enter the server URL manually:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Select an available Jellyfin server from your local network:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>Enter the server name or ip address</source>
<translation>Enter the server name or ip address</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
</message>
<message>
<source>Unknown</source>
<translation>Unknown</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>Close</source>
<translation>Close</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Cancel Series Recording</translation>
</message>
<message>
<source>Cancel Recording</source>
<translation>Cancel Recording</translation>
</message>
<message>
<source>Record Series</source>
<translation>Record Series</translation>
</message>
<message>
<source>Record</source>
<translation>Record</translation>
</message>
<message>
<source>View Channel</source>
<translation>View Channel</translation>
</message>
<message>
<source>TV Shows</source>
<translation>TV Shows</translation>
</message>
<message>
<source>Movies</source>
<translation>Movies</translation>
</message>
<message>
<source>Special Features</source>
<translation>Special Features</translation>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
</message>
<message>
<source>More Like This</source>
<translation>More Like This</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Cast &amp; Crew</translation>
</message>
<message>
<source>Age</source>
<translation>Age</translation>
</message>
<message>
<source>Died</source>
<translation>Died</translation>
</message>
<message>
<source>Born</source>
<translation>Born</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Enter a value...</translation>
</message>
<message>
<source>On Now</source>
<translation>On Now</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Delete Saved</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Save Credentials?</translation>
</message>
<message>
<source>WxH</source>
<translation>WxH</translation>
<extracomment>Video width x height</extracomment>
</message>
<message>
<source>Pixel format</source>
<translation>Pixel format</translation>
<extracomment>Video pixel format</extracomment>
</message>
<message>
<source>Video range type</source>
<translation>Video range type</translation>
</message>
<message>
<source>Size</source>
<translation>Size</translation>
<extracomment>Video size</extracomment>
</message>
<message>
<source>Container</source>
<translation>Container</translation>
<extracomment>Video streaming container</extracomment>
</message>
<message>
<source>Bit Rate</source>
<translation>Bit Rate</translation>
<extracomment>Video streaming bit rate</extracomment>
</message>
<message>
<source>Level</source>
<translation>Level</translation>
<extracomment>Video profile level</extracomment>
</message>
<message>
<source>Codec Tag</source>
<translation>Codec Tag</translation>
</message>
<message>
<source>Codec</source>
<translation>Codec</translation>
</message>
<message>
<source>Stream Information</source>
<translation>Stream Information</translation>
</message>
<message>
<source>Audio Channels</source>
<translation>Audio Channels</translation>
</message>
<message>
<source>Total Bitrate</source>
<translation>Total Bitrate</translation>
</message>
<message>
<source>direct</source>
<translation>direct</translation>
</message>
<message>
<source>Audio Codec</source>
<translation>Audio Codec</translation>
</message>
<message>
<source>Video Codec</source>
<translation>Video Codec</translation>
</message>
<message>
<source>Reason</source>
<translation>Reason</translation>
</message>
<message>
<source>Transcoding Information</source>
<translation>Transcoding Information</translation>
</message>
<message>
<source>Playback Information</source>
<translation>Playback Information</translation>
</message>
<message>
<source>Set the maximum amount of days a show should stay in the &apos;Next Up&apos; list without watching it.</source>
<translation>Set the maximum amount of days a show should stay in the &apos;Next Up&apos; list without watching it.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Max Days Next Up</source>
<translation>Max Days Next Up</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Home Page.</source>
<translation>Options for Home Page.</translation>
<extracomment>Description for Home Page user settings.</extracomment>
</message>
<message>
<source>Home Page</source>
<translation>Home Page</translation>
</message>
<message>
<source>Settings relating to how the application looks.</source>
<translation>Settings relating to how the application looks.</translation>
</message>
<message>
<source>Settings relating to playback and supported codec and media types.</source>
<translation>Settings relating to playback and supported codec and media types.</translation>
</message>
<message>
<source>Play Trailer</source>
<translation>Play Trailer</translation>
</message>
<message>
<source>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Hide Clock</source>
<translation>Hide Clock</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
<translation>Cinema Mode brings the theatre experience straight to your living room with the ability to play custom intros before the main feature.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Cinema Mode</source>
<translation>Cinema Mode</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Use Splashscreen as Home Background</source>
<translation>Use Splashscreen as Home Background</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options that alter the design of Jellyfin.</source>
<translation>Options that alter the design of Jellyfin.</translation>
<extracomment>Description for Design Elements user settings.</extracomment>
</message>
<message>
<source>Design Elements</source>
<translation>Design Elements</translation>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
</message>
<message>
<source>Use Splashscreen as Screensaver Background</source>
<translation>Use Splashscreen as Screensaver Background</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Jellyfin&apos;s screensaver.</source>
<translation>Options for Jellyfin&apos;s screensaver.</translation>
<extracomment>Description for Screensaver user settings.</extracomment>
</message>
<message>
<source>Screensaver</source>
<translation>Screensaver</translation>
</message>
<message>
<source>If enabled, images of unwatched episodes will be blurred.</source>
<translation>If enabled, images of unwatched episodes will be blurred.</translation>
</message>
<message>
<source>Blur Unwatched Episodes</source>
<translation>Blur Unwatched Episodes</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for TV Shows.</source>
<translation>Options for TV Shows.</translation>
<extracomment>Description for TV Shows user settings.</extracomment>
</message>
<message>
<source>Hides tagline text on details pages.</source>
<translation>Hides tagline text on details pages.</translation>
</message>
<message>
<source>Hide Taglines</source>
<translation>Hide Taglines</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Details pages.</source>
<translation>Options for Details pages.</translation>
<extracomment>Description for Details page user settings.</extracomment>
</message>
<message>
<source>Details Page</source>
<translation>Details Page</translation>
</message>
<message>
<source>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</source>
<translation>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Return to Top</source>
<translation>Return to Top</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Shows</source>
<translation>Shows</translation>
</message>
<message>
<source>Studios</source>
<translation>Studios</translation>
</message>
<message>
<source>Networks</source>
<translation>Networks</translation>
</message>
<message>
<source>There was an error authenticating via Quick Connect.</source>
<translation>There was an error authenticating via Quick Connect.</translation>
</message>
<message>
<source>(Dialog will close automatically)</source>
<translation>(Dialog will close automatically)</translation>
</message>
<message>
<source>Here is your Quick Connect code:</source>
<translation>Here is your Quick Connect code:</translation>
</message>
<message>
<source>Quick Connect</source>
<translation>Quick Connect</translation>
</message>
<message>
<source>%1 of %2</source>
<translation>%1 of %2</translation>
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
</message>
<message>
<source>You can search for Titles, People, Live TV Channels and more</source>
<translation>You can search for Titles, People, Live TV Channels and more</translation>
<extracomment>Help text in search results</extracomment>
</message>
<message>
<source>Search now</source>
<translation>Search now</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>Use voice remote to search</source>
<translation>Use voice remote to search</translation>
<extracomment>Help text in search voice text box</extracomment>
</message>
<message>
<source>Go to episode</source>
<translation>Go to episode</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
</message>
<message>
<source>Go to season</source>
<translation>Go to season</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</extracomment>
</message>
<message>
<source>Go to series</source>
<translation>Go to series</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Series Detail Page</extracomment>
</message>
<message>
<source>Set Watched</source>
<translation>Set Watched</translation>
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
</message>
<message>
<source>Set Favorite</source>
<translation>Set Favourite</translation>
<extracomment>Button Text - When pressed, sets item as Favorite</extracomment>
</message>
<message>
<source>Show item count in the library and index of selected item.</source>
<translation>Show item count in the library and index of selected item.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Item Count</source>
<translation>Item Count</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment>
</message>
<message>
<source>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</source>
<translation>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Item Titles</source>
<translation>Item Titles</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Media Grid options.</source>
<translation>Media Grid options.</translation>
</message>
<message>
<source>Media Grid</source>
<translation>Media Grid</translation>
<extracomment>UI -&gt; Media Grid section in user setting screen.</extracomment>
</message>
<message>
<source>User Interface</source>
<translation>User Interface</translation>
<extracomment>Title for User Interface section in user setting screen.</extracomment>
</message>
<message>
<source>Disabled</source>
<translation>Disabled</translation>
</message>
<message>
<source>Enabled</source>
<translation>Enabled</translation>
</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>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>MPEG-2 Support</source>
<translation>MPEG-2 Support</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Playback</source>
<translation>Playback</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
<message>
<source>Version</source>
<translation>Version</translation>
</message>
<message>
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>An error was encountered while playing this item. Server did not provide required transcoding data.</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>Error Getting Playback Information</source>
<translation>Error Getting Playback Information</translation>
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>If no server is listed above, you may also enter the server URL manually:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Select an available Jellyfin server from your local network:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>Enter the server name or ip address</source>
<translation>Enter the server name or IP address</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
</message>
<message>
<source>Unknown</source>
<translation>Unknown</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>Close</source>
<translation>Close</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Cancel Series Recording</translation>
</message>
<message>
<source>Cancel Recording</source>
<translation>Cancel Recording</translation>
</message>
<message>
<source>Record Series</source>
<translation>Record Series</translation>
</message>
<message>
<source>Record</source>
<translation>Record</translation>
</message>
<message>
<source>View Channel</source>
<translation>View Channel</translation>
</message>
<message>
<source>TV Shows</source>
<translation>TV Shows</translation>
</message>
<message>
<source>Movies</source>
<translation>Movies</translation>
</message>
<message>
<source>Special Features</source>
<translation>Special Features</translation>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
</message>
<message>
<source>More Like This</source>
<translation>More Like This</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Cast &amp; Crew</translation>
</message>
<message>
<source>Age</source>
<translation>Age</translation>
</message>
<message>
<source>Died</source>
<translation>Died</translation>
</message>
<message>
<source>Born</source>
<translation>Born</translation>
</message>
<message>
<source>On Now</source>
<translation>On Now</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Delete Saved</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Save Credentials?</translation>
</message>
</context>
</TS>

View File

@ -125,8 +125,12 @@
<translation>Home</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Enter a value</translation>
<source>Enter a username</source>
<translation>Enter a username</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Enter a password</translation>
</message>
<message>
<source>Name</source>
@ -350,32 +354,32 @@
<message>
<source>Started at</source>
<translation>Started at</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Started</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Starts at</source>
<translation>Starts at</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Starts</translation>
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Ended at</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Ends at</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Live</source>
@ -442,18 +446,18 @@
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Enter the server name or ip address</source>
<translation>Enter the server name or ip address</translation>
<source>Enter the server name or IP address</source>
<translation>Enter the server name or IP address</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>Pick a Jellyfin server from the local network</translation>
<translation>Select an available Jellyfin server from your local network:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>or enter server URL manually:</translation>
<translation>If no server is listed above, you may also enter the server URL manually:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
@ -462,8 +466,8 @@
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
</message>
<message>
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>An error was encountered while playing this item. Server did not provide required transcoding data.</translation>
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>An error was encountered while playing this item. Server did not provide required transcoding data.</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>
@ -570,7 +574,7 @@
<message>
<source>%1 of %2</source>
<translation>%1 of %2</translation>
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
</message>
<message>
<source>Quick Connect</source>
@ -703,11 +707,6 @@
<source>Next episode</source>
<translation>Next episode</translation>
</message>
<message>
<source>Skip Intro</source>
<translation>Skip Intro</translation>
</message>
<message>
<source>Play Trailer</source>
<translation>Play Trailer</translation>
</message>
@ -738,5 +737,88 @@
<translation>Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Playback Information</source>
<translation>Playback Information</translation>
</message>
<message>
<source>Transcoding Information</source>
<translation>Transcoding Information</translation>
</message>
<message>
<source>Reason</source>
<translation>Reason</translation>
</message>
<message>
<source>Video Codec</source>
<translation>Video Codec</translation>
</message>
<message>
<source>Audio Codec</source>
<translation>Audio Codec</translation>
</message>
<message>
<source>direct</source>
<translation>direct</translation>
</message>
<message>
<source>Total Bitrate</source>
<translation>Total Bitrate</translation>
</message>
<message>
<source>Audio Channels</source>
<translation>Audio Channels</translation>
</message>
<message>
<source>Stream Information</source>
<translation>Stream Information</translation>
</message>
<message>
<source>Codec</source>
<translation>Codec</translation>
</message>
<message>
<source>Codec Tag</source>
<translation>Codec Tag</translation>
</message>
<message>
<source>Level</source>
<translation>Level</translation>
<extracomment>Video profile level</extracomment>
</message>
<message>
<source>Bit Rate</source>
<translation>Bit Rate</translation>
<extracomment>Video streaming bit rate</extracomment>
</message>
<message>
<source>Container</source>
<translation>Container</translation>
<extracomment>Video streaming container</extracomment>
</message>
<message>
<source>Size</source>
<translation>Size</translation>
<extracomment>Video size</extracomment>
</message>
<message>
<source>Video range type</source>
<translation>Video range type</translation>
</message>
<message>
<source>Pixel format</source>
<translation>Pixel format</translation>
<extracomment>Video pixel format</extracomment>
</message>
<message>
<source>WxH</source>
<translation>WxH</translation>
<extracomment>Video width x height</extracomment>
</message>
<message>
<source>Unable to find any albums or songs belonging to this artist</source>
<translation>Unable to find any albums or songs belonging to this artist</translation>
<extracomment>Popup message when we find no audio data for an artist</extracomment>
</message>
</context>
</TS>

View File

@ -112,9 +112,17 @@
<source>Home</source>
<translation>Inicio</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Ingresar nombre de usuario</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Ingresar la contraseña</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Ingresar un valor</translation>
<translation>Ingresar un valor...</translation>
</message>
<message>
<source>Sort Field</source>

View File

@ -112,9 +112,17 @@
<source>Home</source>
<translation>Inicio</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Ingres nombre de usuario</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Ingres la contraseña</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Ingrese un valor</translation>
<translation>Ingres un valor...</translation>
</message>
<message>
<source>Sort Field</source>

View File

@ -113,8 +113,12 @@
<translation>Inicio</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Enter a value...</translation>
<source>Enter a username</source>
<translation>Ingres nombre de usuario</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Ingres la contraseña</translation>
</message>
<message>
<source>Sort Field</source>
@ -168,9 +172,17 @@
<source>Audio</source>
<translation>Audio</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Ingresar nombre de usuario</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Ingresar la contraseña</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Introduce un valor</translation>
<translation>Ingresar un valor...</translation>
</message>
<message>
<source>Sort Field</source>
@ -1867,5 +1879,10 @@
<source>Change Server</source>
<translation>Cambiar de Servidor</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nombre</translation>
</message>
</context>
</TS>

View File

@ -112,6 +112,14 @@
<source>Home</source>
<translation>Accueil</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Entrez votre nom d&apos;utilisateur</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Entrer le mot de passe</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Entrer une valeur</translation>
@ -2639,7 +2647,7 @@
<translation>Bonus</translation>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
<translation>Appuyez sur &apos;OK&apos; pour fermer</translation>
</message>
</message>
<message>
@ -2717,5 +2725,653 @@
<source>Save Credentials?</source>
<translation>Enregistrer les identifiants&#xa0;?</translation>
</message>
<message>
<source>More Like This</source>
<translation>Similaires</translation>
</message>
<message>
<source>Age</source>
<translation>Âge</translation>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtrer</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Trier</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Durée</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Date de sortie</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Nombre de lecture</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Classification parentale</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Date de lecture</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Date d&apos;ajout</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Classement IMDb</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nom</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Impossible de charger les données de la chaîne à partir du serveur</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Erreur lors du chargement des données de la chaîne</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Chargement des données de la chaîne</translation>
</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>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 à partir du serveur.</translation>
<extracomment>Dialog detail when unable to load Content from Server</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>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>On Now</source>
<translation>Maintenant</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Enregistrer les identifiants ?</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Supprimer les informations enregistrées</translation>
</message>
<message>
<source>Special Features</source>
<translation>Caractéristiques spéciales</translation>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
</message>
<message>
<source>WxH</source>
<translation>WxH</translation>
<extracomment>Video width x height</extracomment>
</message>
<message>
<source>Pixel format</source>
<translation>Format pixel</translation>
<extracomment>Video pixel format</extracomment>
</message>
<message>
<source>Video range type</source>
<translation>Type de plage vidéo</translation>
</message>
<message>
<source>Size</source>
<translation>Taille</translation>
<extracomment>Video size</extracomment>
</message>
<message>
<source>Container</source>
<translation>Conteneur</translation>
<extracomment>Video streaming container</extracomment>
</message>
<message>
<source>Bit Rate</source>
<translation>Débit Binaire</translation>
<extracomment>Video streaming bit rate</extracomment>
</message>
<message>
<source>Level</source>
<translation>Niveau</translation>
<extracomment>Video profile level</extracomment>
</message>
<message>
<source>Codec Tag</source>
<translation>Balise de Codec</translation>
</message>
<message>
<source>Codec</source>
<translation>Codec</translation>
</message>
<message>
<source>Stream Information</source>
<translation>Informations sur le Flux</translation>
</message>
<message>
<source>Audio Channels</source>
<translation>Canaux Audio</translation>
</message>
<message>
<source>Total Bitrate</source>
<translation>Débit Total</translation>
</message>
<message>
<source>direct</source>
<translation>direct</translation>
</message>
<message>
<source>Video Codec</source>
<translation>Codec Vidéo</translation>
</message>
<message>
<source>Audio Codec</source>
<translation>Codec Audio</translation>
</message>
<message>
<source>Reason</source>
<translation>Raison</translation>
</message>
<message>
<source>Transcoding Information</source>
<translation>Informations de transcodage</translation>
</message>
<message>
<source>Playback Information</source>
<translation>Informations de lecture</translation>
</message>
<message>
<source>Set the maximum amount of days a show should stay in the &apos;Next Up&apos; list without watching it.</source>
<translation>Définissez le nombre maximum de jours pendant lesquels une émission doit rester dans la liste &quot;Next Up&quot; sans la regarder.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Max Days Next Up</source>
<translation>Max jours suivant</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Home Page.</source>
<translation>Options pour la page d&apos;accueil.</translation>
<extracomment>Description for Home Page user settings.</extracomment>
</message>
<message>
<source>Home Page</source>
<translation>Page d&apos;accueil</translation>
</message>
<message>
<source>Settings relating to how the application looks.</source>
<translation>Paramètres relatifs à l&apos;apparence de l&apos;application.</translation>
</message>
<message>
<source>Settings relating to playback and supported codec and media types.</source>
<translation>Paramètres relatifs à la lecture et aux types de codec et de média pris en charge.</translation>
</message>
<message>
<source>Play Trailer</source>
<translation>Lire la bande-annonce</translation>
</message>
<message>
<source>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Masque toutes les horloges de Jellyfin. Jellyfin devra être fermé et rouvert pour que le changement prenne effet.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Hide Clock</source>
<translation>Masquer l&apos;horloge</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
<translation>Le Mode Cinéma apporte l&apos;expérience théâtrale directement dans votre salon avec la possibilité de jouer des intros personnalisées avant la fonctionnalité principale.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Cinema Mode</source>
<translation>Mode Cinéma</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Utilisez générer une image d&apos;écran de démarrage comme arrière-plan d&apos;accueil de Jellyfin. Jellyfin devra être fermé et rouvert pour que le changement prenne effet.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Use Splashscreen as Home Background</source>
<translation>Utiliser l&apos;écran de démarrage comme arrière-plan d&apos;accueil</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options that alter the design of Jellyfin.</source>
<translation>Options qui modifient la conception de Jellyfin.</translation>
<extracomment>Description for Design Elements user settings.</extracomment>
</message>
<message>
<source>Design Elements</source>
<translation>Éléments de design</translation>
</message>
<message>
<source>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Utilisez générer une image d&apos;écran de démarrage comme fond d&apos;écran de Jellyfin. Jellyfin devra être fermé et rouvert pour que le changement prenne effet.</translation>
</message>
<message>
<source>Use Splashscreen as Screensaver Background</source>
<translation>Utiliser Splashscreen comme fond d&apos;écran de veille</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Jellyfin&apos;s screensaver.</source>
<translation>Options de l&apos;écran de veille de Jellyfin.</translation>
<extracomment>Description for Screensaver user settings.</extracomment>
</message>
<message>
<source>Screensaver</source>
<translation>Écran de veille</translation>
</message>
<message>
<source>If enabled, images of unwatched episodes will be blurred.</source>
<translation>Si activé, les images des épisodes non regardés seront floues.</translation>
</message>
<message>
<source>Blur Unwatched Episodes</source>
<translation>Flouter les épisodes non visionnés</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for TV Shows.</source>
<translation>Options pour les Séries Télé.</translation>
<extracomment>Description for TV Shows user settings.</extracomment>
</message>
<message>
<source>Hides tagline text on details pages.</source>
<translation>Masque le texte du slogan sur les pages de détails.</translation>
</message>
<message>
<source>Hide Taglines</source>
<translation>Masquer les slogans</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for Details pages.</source>
<translation>Options des pages Détails.</translation>
<extracomment>Description for Details page user settings.</extracomment>
</message>
<message>
<source>Return to Top</source>
<translation>Retourner en Haut</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Details Page</source>
<translation>Page de Détails</translation>
</message>
<message>
<source>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</source>
<translation>Utilisez le bouton de relecture pour animer lentement le premier élément du dossier. (Si désactivé, le dossier sera immédiatement réinitialisé au premier élément).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Shows</source>
<translation>Série</translation>
</message>
<message>
<source>Studios</source>
<translation>Studios</translation>
</message>
<message>
<source>Networks</source>
<translation>Réseaux</translation>
</message>
<message>
<source>There was an error authenticating via Quick Connect.</source>
<translation>Une erreur s&apos;est produite lors de l&apos;authentification à l&apos;aide de connexion rapide.</translation>
</message>
<message>
<source>(Dialog will close automatically)</source>
<translation>(La boîte de dialogue se fermera automatiquement)</translation>
</message>
<message>
<source>Here is your Quick Connect code:</source>
<translation>Voici votre code de connexion rapide&#xa0;:</translation>
</message>
<message>
<source>Quick Connect</source>
<translation>Connexion rapide</translation>
</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>You can search for Titles, People, Live TV Channels and more</source>
<translation>Vous pouvez rechercher des titres, des personnes, des chaînes de télévision en direct et plus encore</translation>
<extracomment>Help text in search results</extracomment>
</message>
<message>
<source>Search now</source>
<translation>Rechercher maintenant</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>Use voice remote to search</source>
<translation>Utilisez la télécommande vocale pour rechercher</translation>
<extracomment>Help text in search voice text box</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>Go to season</source>
<translation>Aller à la saison</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</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>Set Watched</source>
<translation>Définir regardé</translation>
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
</message>
<message>
<source>Set Favorite</source>
<translation>Définir le favori</translation>
<extracomment>Button Text - When pressed, sets item as Favorite</extracomment>
</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 bibliothèque et l&apos;index de l&apos;élément sélectionné.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</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>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</source>
<translation>Affichez toujours les titres sous les images des affiches. (Si désactivé, le titre s&apos;affichera uniquement sous l&apos;élément en surbrillance).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Item Titles</source>
<translation>Titres des éléments</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Media Grid options.</source>
<translation>Options de la grille média.</translation>
</message>
<message>
<source>Media Grid</source>
<translation>Grille des médias</translation>
<extracomment>UI -&gt; Media Grid section in user setting screen.</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>Disabled</source>
<translation>Désactivée</translation>
</message>
<message>
<source>Enabled</source>
<translation>Activé</translation>
</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>Prise en charge de la lecture directe du contenu MPEG-2 (par exemple, Live TV). Cela empêchera le transcodage du contenu MPEG-2, mais utilisera beaucoup plus de bande passante.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>MPEG-2 Support</source>
<translation>Prise en charge MPEG-2</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Playback</source>
<translation>Lecture</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
<message>
<source>Version</source>
<translation>Version</translation>
</message>
<message>
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>Une erreur s&apos;est produite lors de la lecture de cet élément. Le serveur n&apos;a pas fourni les données de transcodage requises.</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>Error Getting Playback Information</source>
<translation>Erreur lors de l&apos;obtention 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>...or enter server URL manually:</source>
<translation>Si aucun serveur n&apos;est répertorié ci-dessus, vous pouvez également saisir manuellement l&apos;URL du serveur&#xa0;:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter 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&#xa0;:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>Enter the server name or ip address</source>
<translation>Entrez le nom du serveur ou l&apos;adresse IP</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</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>Unknown</source>
<translation>Inconnue</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>Not found</source>
<translation>Pas trouvé</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Connexion au Serveur</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>Close</source>
<translation>Fermer</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Annuler l&apos;enregistrement de la série</translation>
</message>
<message>
<source>Cancel Recording</source>
<translation>Annuler l&apos;enregistrement</translation>
</message>
<message>
<source>Record Series</source>
<translation>Record la série</translation>
</message>
<message>
<source>Record</source>
<translation>Record</translation>
</message>
<message>
<source>View Channel</source>
<translation>Voir le canal</translation>
</message>
<message>
<source>TV Guide</source>
<translation>Guide télé</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Canals</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Répéter</translation>
<extracomment>If TV Shows has previously been broadcasted</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>Ended at</source>
<translation>Fini à</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</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>Starts</source>
<translation>Débute</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>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>Started</source>
<translation>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>Started at</source>
<translation>Commencé à</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Samedi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Vendredi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jeudi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Mercredi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Mardi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Lundi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Dimanche</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>demain</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>hier</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>aujourd&apos;hui</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>TV Shows</source>
<translation>Séries télé</translation>
</message>
<message>
<source>Movies</source>
<translation>Films</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Casting et équipe</translation>
</message>
<message>
<source>Born</source>
<translation>\Née</translation>
</message>
<message>
<source>Died</source>
<translation>Mort</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Visualiser</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Note des critiques</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>Ce %1 ne contient pas d&apos;éléments</translation>
</message>
</context>
</TS>

View File

@ -112,6 +112,14 @@
<source>Home</source>
<translation>Accueil</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Entrez votre nom d&apos;utilisateur</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Entrer le mot de passe</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Entrez une valeur</translation>
@ -1076,5 +1084,45 @@
<translation>...ou entrer l&apos;adresse 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>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>There was an error retrieving the data for this item from the server.</source>
<translation>Il y a eu une erreur pendant la recherche de donnnées pour cet item provenant du serveur.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Erreur durant la lecture</translation>
<extracomment>Dialog title when error occurs during playback</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>On Now</source>
<translation type="unfinished">Jouant maintenant</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Effacer sauvegardés</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Enregistrer les identifiants ?</translation>
</message>
<message>
<source>Sign Out</source>
<translation>Se déconnecter</translation>
</message>
<message>
<source>Change Server</source>
<translation>Changer de serveur</translation>
</message>
</context>
</TS>

File diff suppressed because it is too large Load Diff

View File

@ -112,6 +112,14 @@
<source>Home</source>
<translation>Home</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Inserisci il tuo cognome</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Inserisci la password</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Inserire un valore</translation>

View File

@ -112,6 +112,14 @@
<source>Home</source>
<translation>Mājas</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Ievadiet lietotājvārdu</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Ievadiet paroli</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Ievadi vērtību</translation>

View File

@ -112,6 +112,14 @@
<source>Home</source>
<translation>Início</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Insira nome de usuário</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Insira um senha</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Insira um valor</translation>

View File

@ -112,6 +112,14 @@
<source>Home</source>
<translation>Acasă</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Introduceți un nume de utilizator</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Introduceți o parolă</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Introduceți o valoare</translation>

View File

@ -112,6 +112,14 @@
<source>Home</source>
<translation>Domov</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Zadajte používateľské meno</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Zadajte heslo</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Zadajte hodnotu</translation>

View File

@ -112,6 +112,14 @@
<source>Home</source>
<translation>Domov</translation>
</message>
<message>
<source>Enter a username</source>
<translation>Vnesite svoje uporabniško ime</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Vnesite geslo</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Vnesite vrednost</translation>

View File

@ -112,9 +112,17 @@
<source>Home</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Enter a username</source>
<translation>Faka igama lomsebenzisi</translation>
</message>
<message>
<source>Enter a password</source>
<translation>Faka iphasiwedi</translation>
</message>
<message>
<source>Enter a value...</source>
<translation type="unfinished"></translation>
<translation>Faka inani...</translation>
</message>
<message>
<source>Sort Field</source>

517
package-lock.json generated
View File

@ -7,19 +7,20 @@
"": {
"name": "jellyfin-roku",
"version": "1.4.12",
"hasInstallScript": true,
"license": "GPL-2.0",
"dependencies": {
"api": "npm:jellyfin-api-bs-client@^1.0.5",
"bgv": "npm:button-group-vert@^1.0.1",
"bgv": "npm:button-group-vert@^1.0.2",
"brighterscript-formatter": "^1.6.8",
"sob": "npm:slide-out-button@^1.0.1",
"intKeyboard": "npm:integer-keyboard@^1.0.12"
"intKeyboard": "npm:integer-keyboard@^1.0.12",
"sob": "npm:slide-out-button@^1.0.1"
},
"devDependencies": {
"@rokucommunity/bslint": "0.7.5",
"brighterscript": "0.57.0",
"brighterscript": "0.57.2",
"rooibos-cli": "1.4.0",
"ropm": "0.10.9"
"ropm": "0.10.10"
}
},
"node_modules/@nodelib/fs.scandir": {
@ -783,9 +784,9 @@
},
"node_modules/bgv": {
"name": "button-group-vert",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/button-group-vert/-/button-group-vert-1.0.1.tgz",
"integrity": "sha512-PtOAglZ7w1ebPR5PVxtPNfADydhwU9pJ8X4KKkaqvuDwNMOcD2LkwpgCH0nuGm/yzrws9Kqkqf86IC5mZh8xsQ=="
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/button-group-vert/-/button-group-vert-1.0.2.tgz",
"integrity": "sha512-pfrUYI/aFubtjhA8I08qNCtDluyIScksldR15icR7Pj24tNELYCYXE7M0jaU7xgdiFAhZJcYuB3aCXzyI1CoMw=="
},
"node_modules/binary-extensions": {
"version": "1.13.1",
@ -828,9 +829,9 @@
}
},
"node_modules/brighterscript": {
"version": "0.57.0",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.57.0.tgz",
"integrity": "sha512-lv7/qIBLrF62fnukTQUR7OZlzKugMSDkSpdtMTZKElTCY/CqU3Kueprg+fKC4zuQeFdZlKnVrD5fpSYZiOhe2A==",
"version": "0.57.2",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.57.2.tgz",
"integrity": "sha512-idvz7lVLSN1mM/VoDt4/uJPFqdydSgCro2eIwT9vqV8z/1iNLpUtvXCWfeAbWxsbJkXWtmQq4GPkklCxc4OjrQ==",
"dev": true,
"dependencies": {
"@rokucommunity/bslib": "^0.1.1",
@ -5235,14 +5236,14 @@
}
},
"node_modules/ropm": {
"version": "0.10.9",
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.9.tgz",
"integrity": "sha512-HuYCFi90rCsiBYe8+0I6ym2QGeWbmfZkSv3ubL/eAmZQoJl0ebGXcjg6P44IeOIR5tZRyJ6TDiiST+6m2GyUNg==",
"version": "0.10.10",
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.10.tgz",
"integrity": "sha512-tkPuDwP/Mva9IXIuTf4pnH1DC27WLeLfu8QJ70WwaX9tepNMZeDi4eEQdWQ7kalXxxlwXGnG4jUaaA1B4v8zWw==",
"dev": true,
"dependencies": {
"@xml-tools/ast": "^5.0.5",
"@xml-tools/parser": "1.0.10",
"brighterscript": "^0.57.0",
"brighterscript": "^0.57.2",
"del": "6.0.0",
"fs-extra": "9.1.0",
"glob-all": "3.2.1",
@ -5268,121 +5269,6 @@
"chevrotain": "7.1.1"
}
},
"node_modules/ropm/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ropm/node_modules/anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/ropm/node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/ropm/node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ropm/node_modules/brighterscript": {
"version": "0.57.0",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.57.0.tgz",
"integrity": "sha512-lv7/qIBLrF62fnukTQUR7OZlzKugMSDkSpdtMTZKElTCY/CqU3Kueprg+fKC4zuQeFdZlKnVrD5fpSYZiOhe2A==",
"dev": true,
"dependencies": {
"@rokucommunity/bslib": "^0.1.1",
"@xml-tools/parser": "^1.0.7",
"array-flat-polyfill": "^1.0.1",
"chalk": "^2.4.2",
"chevrotain": "^7.0.1",
"chokidar": "^3.5.1",
"clear": "^0.1.0",
"cross-platform-clear-console": "^2.3.0",
"debounce-promise": "^3.1.0",
"eventemitter3": "^4.0.0",
"fast-glob": "^3.2.11",
"file-url": "^3.0.0",
"fs-extra": "^8.0.0",
"jsonc-parser": "^2.3.0",
"long": "^3.2.0",
"luxon": "^1.8.3",
"minimatch": "^3.0.4",
"moment": "^2.23.0",
"p-settle": "^2.1.0",
"parse-ms": "^2.1.0",
"require-relative": "^0.8.7",
"roku-deploy": "^3.8.1",
"serialize-error": "^7.0.1",
"source-map": "^0.7.3",
"vscode-languageserver": "7.0.0",
"vscode-languageserver-protocol": "3.16.0",
"vscode-languageserver-textdocument": "^1.0.1",
"vscode-uri": "^2.1.1",
"xml2js": "^0.4.19",
"yargs": "^16.2.0"
},
"bin": {
"bsc": "dist/cli.js"
}
},
"node_modules/ropm/node_modules/brighterscript/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/ropm/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ropm/node_modules/chevrotain": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz",
@ -5392,45 +5278,6 @@
"regexp-to-ast": "0.5.0"
}
},
"node_modules/ropm/node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/ropm/node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ropm/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@ -5446,7 +5293,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==",
@ -5458,7 +5305,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==",
@ -5467,102 +5314,6 @@
"node": ">= 10.0.0"
}
},
"node_modules/ropm/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/ropm/node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ropm/node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ropm/node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ropm/node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/ropm/node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ropm/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/ropm/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ropm/node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/ropm/node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
@ -7176,9 +6927,9 @@
}
},
"bgv": {
"version": "npm:button-group-vert@1.0.1",
"resolved": "https://registry.npmjs.org/button-group-vert/-/button-group-vert-1.0.1.tgz",
"integrity": "sha512-PtOAglZ7w1ebPR5PVxtPNfADydhwU9pJ8X4KKkaqvuDwNMOcD2LkwpgCH0nuGm/yzrws9Kqkqf86IC5mZh8xsQ=="
"version": "npm:button-group-vert@1.0.2",
"resolved": "https://registry.npmjs.org/button-group-vert/-/button-group-vert-1.0.2.tgz",
"integrity": "sha512-pfrUYI/aFubtjhA8I08qNCtDluyIScksldR15icR7Pj24tNELYCYXE7M0jaU7xgdiFAhZJcYuB3aCXzyI1CoMw=="
},
"binary-extensions": {
"version": "1.13.1",
@ -7215,9 +6966,9 @@
}
},
"brighterscript": {
"version": "0.57.0",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.57.0.tgz",
"integrity": "sha512-lv7/qIBLrF62fnukTQUR7OZlzKugMSDkSpdtMTZKElTCY/CqU3Kueprg+fKC4zuQeFdZlKnVrD5fpSYZiOhe2A==",
"version": "0.57.2",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.57.2.tgz",
"integrity": "sha512-idvz7lVLSN1mM/VoDt4/uJPFqdydSgCro2eIwT9vqV8z/1iNLpUtvXCWfeAbWxsbJkXWtmQq4GPkklCxc4OjrQ==",
"dev": true,
"requires": {
"@rokucommunity/bslib": "^0.1.1",
@ -10677,14 +10428,14 @@
}
},
"ropm": {
"version": "0.10.9",
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.9.tgz",
"integrity": "sha512-HuYCFi90rCsiBYe8+0I6ym2QGeWbmfZkSv3ubL/eAmZQoJl0ebGXcjg6P44IeOIR5tZRyJ6TDiiST+6m2GyUNg==",
"version": "0.10.10",
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.10.tgz",
"integrity": "sha512-tkPuDwP/Mva9IXIuTf4pnH1DC27WLeLfu8QJ70WwaX9tepNMZeDi4eEQdWQ7kalXxxlwXGnG4jUaaA1B4v8zWw==",
"dev": true,
"requires": {
"@xml-tools/ast": "^5.0.5",
"@xml-tools/parser": "1.0.10",
"brighterscript": "^0.57.0",
"brighterscript": "^0.57.2",
"del": "6.0.0",
"fs-extra": "9.1.0",
"glob-all": "3.2.1",
@ -10704,102 +10455,6 @@
"chevrotain": "7.1.1"
}
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
}
},
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"brighterscript": {
"version": "0.57.0",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.57.0.tgz",
"integrity": "sha512-lv7/qIBLrF62fnukTQUR7OZlzKugMSDkSpdtMTZKElTCY/CqU3Kueprg+fKC4zuQeFdZlKnVrD5fpSYZiOhe2A==",
"dev": true,
"requires": {
"@rokucommunity/bslib": "^0.1.1",
"@xml-tools/parser": "^1.0.7",
"array-flat-polyfill": "^1.0.1",
"chalk": "^2.4.2",
"chevrotain": "^7.0.1",
"chokidar": "^3.5.1",
"clear": "^0.1.0",
"cross-platform-clear-console": "^2.3.0",
"debounce-promise": "^3.1.0",
"eventemitter3": "^4.0.0",
"fast-glob": "^3.2.11",
"file-url": "^3.0.0",
"fs-extra": "^8.0.0",
"jsonc-parser": "^2.3.0",
"long": "^3.2.0",
"luxon": "^1.8.3",
"minimatch": "^3.0.4",
"moment": "^2.23.0",
"p-settle": "^2.1.0",
"parse-ms": "^2.1.0",
"require-relative": "^0.8.7",
"roku-deploy": "^3.8.1",
"serialize-error": "^7.0.1",
"source-map": "^0.7.3",
"vscode-languageserver": "7.0.0",
"vscode-languageserver-protocol": "3.16.0",
"vscode-languageserver-textdocument": "^1.0.1",
"vscode-uri": "^2.1.1",
"xml2js": "^0.4.19",
"yargs": "^16.2.0"
},
"dependencies": {
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
}
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"chevrotain": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz",
@ -10809,31 +10464,6 @@
"regexp-to-ast": "0.5.0"
}
},
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"requires": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"fsevents": "~2.3.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@ -10844,95 +10474,24 @@
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"dependencies": {
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true
}
}
},
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
"yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",

View File

@ -8,9 +8,9 @@
},
"devDependencies": {
"@rokucommunity/bslint": "0.7.5",
"brighterscript": "0.57.0",
"brighterscript": "0.57.2",
"rooibos-cli": "1.4.0",
"ropm": "0.10.9"
"ropm": "0.10.10"
},
"scripts": {
"postinstall": "npx ropm copy",
@ -36,7 +36,7 @@
"homepage": "https://github.com/jellyfin/jellyfin-roku#readme",
"dependencies": {
"api": "npm:jellyfin-api-bs-client@^1.0.5",
"bgv": "npm:button-group-vert@^1.0.1",
"bgv": "npm:button-group-vert@^1.0.2",
"brighterscript-formatter": "^1.6.8",
"sob": "npm:slide-out-button@^1.0.1",
"intKeyboard": "npm:integer-keyboard@^1.0.12"

View File

@ -9,7 +9,6 @@ sub Main (args as dynamic) as void
' Set global constants
setConstants()
' Write screen tracker for screensaver
WriteAsciiFile("tmp:/scene.temp", "")
MoveFile("tmp:/scene.temp", "tmp:/scene")
@ -160,6 +159,9 @@ sub Main (args as dynamic) as void
' Nothing to do here, handled in ItemGrid
else if selectedItem.type = "MusicArtist"
group = CreateArtistView(selectedItem.json)
if not isValid(group)
message_dialog(tr("Unable to find any albums or songs belonging to this artist"))
end if
else if selectedItem.type = "MusicAlbum"
group = CreateAlbumView(selectedItem.json)
else if selectedItem.type = "Audio"
@ -189,6 +191,12 @@ sub Main (args as dynamic) as void
albums = msg.getRoSGNode()
node = albums.musicArtistAlbumData.items[ptr]
group = CreateAlbumView(node)
else if isNodeEvent(msg, "appearsOnSelected")
' If you select a Music Album from ANYWHERE, follow this flow
ptr = msg.getData()
albums = msg.getRoSGNode()
node = albums.musicArtistAppearsOnData.items[ptr]
group = CreateAlbumView(node)
else if isNodeEvent(msg, "playSong")
' User has selected audio they want us to play
selectedIndex = msg.getData()
@ -212,11 +220,23 @@ sub Main (args as dynamic) as void
if isValid(m.spinner)
m.spinner.visible = true
end if
group = invalid
' Create instant mix based on selected album
if isValid(screenContent.albumData)
group = CreateInstantMixGroup(screenContent.albumData.items)
else if isValid(screenContent.pageContent)
group = CreateInstantMixGroup([{ id: screenContent.musicArtistAlbumData.items[0].json.id }])
if isValid(screenContent.albumData.items)
if screenContent.albumData.items.count() > 0
group = CreateInstantMixGroup(screenContent.albumData.items)
end if
end if
end if
' Create instant mix based on selected artist
if not isValid(group)
group = CreateInstantMixGroup([{ id: screenContent.pageContent.id }])
end if
else if isNodeEvent(msg, "episodeSelected")
' If you select a TV Episode from ANYWHERE, follow this flow
node = getMsgPicker(msg, "picker")
@ -386,9 +406,19 @@ sub Main (args as dynamic) as void
changeSubtitleDuringPlayback(trackSelected)
end if
end if
else if isNodeEvent(msg, "selectPlaybackInfoPressed")
node = m.scene.focusedChild
if node.focusedChild <> invalid and node.focusedChild.isSubType("JFVideo")
info = GetPlaybackInfo()
show_dialog(tr("Playback Information"), info)
end if
else if isNodeEvent(msg, "state")
node = msg.getRoSGNode()
if node.state = "finished"
if selectedItem.Type = "TvChannel" and node.state = "finished"
video = CreateVideoPlayerGroup(node.id)
m.global.sceneManager.callFunc("pushScene", video)
m.global.sceneManager.callFunc("clearPreviousScene")
else if node.state = "finished"
node.control = "stop"
' If node allows retrying using Transcode Url, give that shot

View File

@ -377,13 +377,26 @@ end function
' Shows details on selected artist. Bio, image, and list of available albums
function CreateArtistView(musicartist)
musicData = MusicAlbumList(musicartist.id)
appearsOnData = AppearsOnList(musicartist.id)
' User only has songs under artists
if musicData = invalid or musicData.Items.Count() = 0
if (musicData = invalid or musicData.Items.Count() = 0) and (appearsOnData = invalid or appearsOnData.Items.Count() = 0)
' Just songs under artists...
group = CreateObject("roSGNode", "AlbumView")
group.pageContent = ItemMetaData(musicartist.id)
group.albumData = MusicSongList(musicartist.id)
' Lookup songs based on artist id
songList = GetSongsByArtist(musicartist.id)
if not isValid(songList)
' Lookup songs based on folder parent / child relationship
songList = MusicSongList(musicartist.id)
end if
if not isValid(songList)
return invalid
end if
group.albumData = songList
group.observeField("playSong", m.port)
group.observeField("playAllSelected", m.port)
group.observeField("instantMixSelected", m.port)
@ -392,9 +405,13 @@ function CreateArtistView(musicartist)
group = CreateObject("roSGNode", "ArtistView")
group.pageContent = ItemMetaData(musicartist.id)
group.musicArtistAlbumData = musicData
group.musicArtistAppearsOnData = appearsOnData
group.artistOverview = ArtistOverview(musicartist.name)
group.observeField("musicAlbumSelected", m.port)
group.observeField("playArtistSelected", m.port)
group.observeField("instantMixSelected", m.port)
group.observeField("appearsOnSelected", m.port)
end if
m.global.sceneManager.callFunc("pushScene", group)
@ -467,6 +484,7 @@ function CreateVideoPlayerGroup(video_id, mediaSourceId = invalid, audio_stream_
if video = invalid then return invalid
if video.errorMsg = "introaborted" then return video
video.observeField("selectSubtitlePressed", m.port)
video.observeField("selectPlaybackInfoPressed", m.port)
video.observeField("state", m.port)
return video
@ -530,8 +548,6 @@ function CreateArtistMixGroup(artistID)
songIDArray.push(song.id)
end for
songIDArray.shift()
group.pageContent = songIDArray
group.musicArtistAlbumData = songList.items

View File

@ -45,13 +45,6 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
end if
end if
if m.videotype = "Episode" or m.videotype = "Series"
video.skipIntroParams = api_API().introskipper.get(video.id)
'print (meta.json.RunTimeTicks / 10000000) / 60
video.runTime = (meta.json.RunTimeTicks / 10000000.0)
video.content.contenttype = "episode"
end if
video.content.title = meta.title
video.showID = meta.showID
@ -177,18 +170,18 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
end if
if meta.live then mediaSourceId = "" ' Don't send mediaSourceId for Live media
playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
video.videoId = video.id
video.mediaSourceId = mediaSourceId
video.audioIndex = audio_stream_idx
if playbackInfo = invalid
if m.playbackInfo = invalid
video.content = invalid
return
end if
params = {}
video.PlaySessionId = playbackInfo.PlaySessionId
video.PlaySessionId = m.playbackInfo.PlaySessionId
if meta.live
video.content.live = true
@ -197,17 +190,17 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
video.container = getContainerType(meta)
if playbackInfo.MediaSources[0] = invalid
playbackInfo = meta.json
if m.playbackInfo.MediaSources[0] = invalid
m.playbackInfo = meta.json
end if
subtitles = sortSubtitles(meta.id, playbackInfo.MediaSources[0].MediaStreams)
subtitles = sortSubtitles(meta.id, m.playbackInfo.MediaSources[0].MediaStreams)
video.Subtitles = subtitles["all"]
if meta.live
video.transcodeParams = {
"MediaSourceId": playbackInfo.MediaSources[0].Id,
"LiveStreamId": playbackInfo.MediaSources[0].LiveStreamId,
"MediaSourceId": m.playbackInfo.MediaSources[0].Id,
"LiveStreamId": m.playbackInfo.MediaSources[0].LiveStreamId,
"PlaySessionId": video.PlaySessionId
}
end if
@ -216,7 +209,7 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
' 'TODO: allow user selection of subtitle track before playback initiated, for now set to no subtitles
video.directPlaySupported = playbackInfo.MediaSources[0].SupportsDirectPlay
video.directPlaySupported = m.playbackInfo.MediaSources[0].SupportsDirectPlay
fully_external = false
@ -225,8 +218,8 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
' artifacts. If the user preference is set, and the only reason the server says we need to
' transcode is that the Envoding Level is not supported, then try to direct play but silently
' fall back to the transcode if that fails.
if meta.live = false and get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and playbackInfo.MediaSources[0].TranscodingUrl <> invalid and forceTranscoding = false and playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
transcodingReasons = getTranscodeReasons(playbackInfo.MediaSources[0].TranscodingUrl)
if meta.live = false and get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].TranscodingUrl <> invalid and forceTranscoding = false and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
video.directPlaySupported = true
video.transcodeAvailable = true
@ -234,10 +227,10 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
end if
if video.directPlaySupported
protocol = LCase(playbackInfo.MediaSources[0].Protocol)
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
if protocol <> "file"
uriRegex = CreateObject("roRegex", "^(.*:)//([A-Za-z0-9\-\.]+)(:[0-9]+)?(.*)$", "")
uri = uriRegex.Match(playbackInfo.MediaSources[0].Path)
uri = uriRegex.Match(m.playbackInfo.MediaSources[0].Path)
' proto $1, host $2, port $3, the-rest $4
localhost = CreateObject("roRegex", "^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$", "i")
' https://stackoverflow.com/questions/8426171/what-regex-will-match-all-loopback-addresses
@ -248,7 +241,7 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
video.content.url = buildURL(uri[4])
else
fully_external = true
video.content.url = playbackInfo.MediaSources[0].Path
video.content.url = m.playbackInfo.MediaSources[0].Path
end if
else:
params.append({
@ -265,15 +258,15 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
end if
video.isTranscoded = false
else
if playbackInfo.MediaSources[0].TranscodingUrl = invalid
if m.playbackInfo.MediaSources[0].TranscodingUrl = invalid
' If server does not provide a transcode URL, display a message to the user
m.global.sceneManager.callFunc("userMessage", tr("Error Getting Playback Information"), tr("An error was encountered while playing this item. Server did not provide required transcoding data."))
video.content = invalid
return
end if
' Get transcoding reason
video.transcodeReasons = getTranscodeReasons(playbackInfo.MediaSources[0].TranscodingUrl)
video.content.url = buildURL(playbackInfo.MediaSources[0].TranscodingUrl)
video.transcodeReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
video.content.url = buildURL(m.playbackInfo.MediaSources[0].TranscodingUrl)
video.isTranscoded = true
end if
@ -284,10 +277,6 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
' is enabled/will be enabled, indexed on the provided list of subtitles
video.SelectedSubtitle = setupSubtitle(video, video.Subtitles, subtitle_idx)
video.content.SDBifUrl = api_API().jellyscrub.get(video.id)
video.content.HDBifUrl = api_API().jellyscrub.get(video.id)
video.content.FHDBifUrl = api_API().jellyscrub.get(video.id)
if not fully_external
video.content = authorize_request(video.content)
end if
@ -346,9 +335,9 @@ function getTranscodeReasons(url as string) as object
return []
end function
'Opens dialog asking user if they want to resume video or start playback over
'Opens dialog asking user if they want to resume video or start playback over only on the home screen
function startPlayBackOver(time as longinteger) as integer
if m.videotype = "Episode" or m.videotype = "Series"
if m.scene.focusedChild.focusedChild.overhangTitle = tr("Home") and (m.videotype = "Episode" or m.videotype = "Series")
return option_dialog([tr("Resume playing at ") + ticksToHuman(time) + ".", tr("Start over from the beginning."), tr("Watched"), tr("Go to series"), tr("Go to season"), tr("Go to episode")])
else
return option_dialog(["Resume playing at " + ticksToHuman(time) + ".", "Start over from the beginning."])
@ -432,6 +421,7 @@ sub autoPlayNextEpisode(videoID as string, showID as string)
' setup new video node
nextVideo = CreateVideoPlayerGroup(data.Items[1].Id, invalid, 1, false, false)
' remove last videoplayer scene
m.global.sceneManager.callFunc("clearPreviousScene")
if nextVideo <> invalid
m.global.sceneManager.callFunc("pushScene", nextVideo)
@ -446,3 +436,135 @@ sub autoPlayNextEpisode(videoID as string, showID as string)
m.global.sceneManager.callFunc("popScene")
end if
end sub
' Returns an array of playback info to be displayed during playback.
' In the future, with a custom playback info view, we can return an associated array.
function GetPlaybackInfo()
sessions = api_API().sessions.get()
if sessions <> invalid and sessions.Count() > 0
return GetTranscodingStats(sessions[0])
end if
errMsg = tr("Unable to get playback information")
return [errMsg]
end function
function GetTranscodingStats(session)
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(transcodingReasons) and transcodingReasons.Count() > 0
sessionStats.push("** " + tr("Transcoding Information") + " **")
for each item in transcodingReasons
sessionStats.push(tr("Reason") + ": " + item)
end for
end if
if isValid(videoCodec)
data = tr("Video Codec") + ": " + videoCodec
if session.TranscodingInfo.IsVideoDirect
data = data + " (" + tr("direct") + ")"
end if
sessionStats.push(data)
end if
if isValid(audioCodec)
data = tr("Audio Codec") + ": " + audioCodec
if session.TranscodingInfo.IsAudioDirect
data = data + " (" + tr("direct") + ")"
end if
sessionStats.push(data)
end if
if isValid(totalBitrate)
data = tr("Total Bitrate") + ": " + getDisplayBitrate(totalBitrate)
sessionStats.push(data)
end if
if isValid(audioChannels)
data = tr("Audio Channels") + ": " + Str(audioChannels)
sessionStats.push(data)
end if
end if
if havePlaybackInfo()
stream = m.playbackInfo.mediaSources[0].MediaStreams[0]
sessionStats.push("** " + tr("Stream Information") + " **")
if isValid(stream.Container)
data = tr("Container") + ": " + stream.Container
sessionStats.push(data)
end if
if isValid(stream.Size)
data = tr("Size") + ": " + stream.Size
sessionStats.push(data)
end if
if isValid(stream.BitRate)
data = tr("Bit Rate") + ": " + getDisplayBitrate(stream.BitRate)
sessionStats.push(data)
end if
if isValid(stream.Codec)
data = tr("Codec") + ": " + stream.Codec
sessionStats.push(data)
end if
if isValid(stream.CodecTag)
data = tr("Codec Tag") + ": " + stream.CodecTag
sessionStats.push(data)
end if
if isValid(stream.VideoRangeType)
data = tr("Video range type") + ": " + stream.VideoRangeType
sessionStats.push(data)
end if
if isValid(stream.PixelFormat)
data = tr("Pixel format") + ": " + stream.PixelFormat
sessionStats.push(data)
end if
if isValid(stream.Width) and isValid(stream.Height)
data = tr("WxH") + ": " + Str(stream.Width) + " x " + Str(stream.Height)
sessionStats.push(data)
end if
if isValid(stream.Level)
data = tr("Level") + ": " + Str(stream.Level)
sessionStats.push(data)
end if
end if
return sessionStats
end function
function havePlaybackInfo()
if not isValid(m.playbackInfo)
return false
end if
if not isValid(m.playbackInfo.mediaSources)
return false
end if
if m.playbackInfo.mediaSources.Count() <= 0
return false
end if
if not isValid(m.playbackInfo.mediaSources[0].MediaStreams)
return false
end if
if m.playbackInfo.mediaSources[0].MediaStreams.Count() <= 0
return false
end if
return true
end function
function getDisplayBitrate(bitrate)
if bitrate > 1000000
return Str(Fix(bitrate / 1000000)) + " Mbps"
else
return Str(Fix(bitrate / 1000)) + " Kbps"
end if
end function

View File

@ -78,12 +78,12 @@ function ItemMetaData(id as string)
if data = invalid then return invalid
imgParams = {}
if data.type <> "Audio"
if data.UserData.PlayedPercentage <> invalid
if data?.UserData?.PlayedPercentage <> invalid
param = { "PercentPlayed": data.UserData.PlayedPercentage }
imgParams.Append(param)
end if
end if
if data.type = "Movie"
if data.type = "Movie" or data.type = "MusicVideo"
tmp = CreateObject("roSGNode", "MovieData")
tmp.image = PosterImage(data.id, imgParams)
tmp.json = data
@ -163,21 +163,82 @@ function ItemMetaData(id as string)
end if
end function
' Music Artist Data
function ArtistOverview(name as string)
req = createObject("roUrlTransfer")
url = Substitute("Artists/{0}", req.escape(name))
resp = APIRequest(url)
data = getJson(resp)
if data = invalid then return invalid
return data.overview
end function
' Get list of albums belonging to an artist
function MusicAlbumList(id as string)
url = Substitute("Users/{0}/Items", get_setting("active_user"), id)
url = Substitute("Users/{0}/Items", get_setting("active_user"))
resp = APIRequest(url, {
"UserId": get_setting("active_user"),
"parentId": id,
"AlbumArtistIds": id,
"includeitemtypes": "MusicAlbum",
"sortBy": "SortName"
"sortBy": "SortName",
"Recursive": true
})
data = getJson(resp)
results = []
for each item in data.Items
tmp = CreateObject("roSGNode", "MusicAlbumData")
tmp.image = PosterImage(item.id, { "maxHeight": "500", "maxWidth": "500" })
tmp.image = PosterImage(item.id)
tmp.json = item
results.push(tmp)
end for
data.Items = results
return data
end function
' Get list of albums an artist appears on
function AppearsOnList(id as string)
url = Substitute("Users/{0}/Items", get_setting("active_user"))
resp = APIRequest(url, {
"ContributingArtistIds": id,
"ExcludeItemIds": id,
"includeitemtypes": "MusicAlbum",
"sortBy": "PremiereDate,ProductionYear,SortName",
"SortOrder": "Descending",
"Recursive": true
})
data = getJson(resp)
results = []
for each item in data.Items
tmp = CreateObject("roSGNode", "MusicAlbumData")
tmp.image = PosterImage(item.id)
tmp.json = item
results.push(tmp)
end for
data.Items = results
return data
end function
' Get list of songs belonging to an artist
function GetSongsByArtist(id as string)
url = Substitute("Users/{0}/Items", get_setting("active_user"))
resp = APIRequest(url, {
"AlbumArtistIds": id,
"includeitemtypes": "Audio",
"sortBy": "SortName",
"Recursive": true
})
data = getJson(resp)
results = []
if data = invalid then return invalid
if data.Items = invalid then return invalid
if data.Items.Count() = 0 then return invalid
for each item in data.Items
tmp = CreateObject("roSGNode", "MusicAlbumData")
tmp.image = PosterImage(item.id)
tmp.json = item
results.push(tmp)
end for
@ -195,8 +256,13 @@ function MusicSongList(id as string)
"sortBy": "SortName"
})
data = getJson(resp)
results = []
data = getJson(resp)
if data = invalid then return invalid
if data.Items = invalid then return invalid
if data.Items.Count() = 0 then return invalid
for each item in data.Items
tmp = CreateObject("roSGNode", "MusicSongData")
tmp.image = PosterImage(item.id)
@ -232,15 +298,18 @@ end function
' Get Instant Mix based on item
function CreateArtistMix(id as string)
url = Substitute("Users/{0}/Items", get_setting("active_user"), id)
url = Substitute("Users/{0}/Items", get_setting("active_user"))
resp = APIRequest(url, {
"UserId": get_setting("active_user"),
"parentId": id,
"Filters": "IsNotFolder",
"Recursive": true,
"SortBy": "SortName",
"ArtistIds": id,
"Recursive": "true",
"MediaTypes": "Audio",
"Limit": 300
"Filters": "IsNotFolder",
"SortBy": "SortName",
"Limit": 300,
"Fields": "Chapters",
"ExcludeLocationTypes": "Virtual",
"EnableTotalRecordCount": false,
"CollapseBoxSetItems": false
})
return getJson(resp)

View File

@ -177,7 +177,7 @@ end function
function GetDirectPlayProfiles() as object
mp4Video = "h264"
mp4Video = "h264,mpeg4"
mp4Audio = "mp3,pcm,lpcm,wav"
mkvVideo = "h264,vp8"
mkvAudio = "mp3,pcm,lpcm,wav"