Merge branch 'unstable' into CreateUnitTestSuite

This commit is contained in:
1hitsong 2023-02-01 18:26:23 -05:00 committed by GitHub
commit d8bc511640
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 10217 additions and 580 deletions

View File

@ -6,16 +6,16 @@ on:
- 'locale/**'
jobs:
run:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
- uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
with:
node-version: "14.12.0"
node-version: "18.13.0"
- run: npm ci
- run: npx ropm install
- run: make dev
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3
with:
name: Jellyfin-Roku-dev-${{ github.sha }}
path: ${{ github.workspace }}/out/staging

View File

@ -6,12 +6,12 @@ on:
jobs:
test-build-release:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@master
- uses: actions/setup-node@master
with:
node-version: "14.12.0"
node-version: "18.13.0"
- run: npm ci
- run: npx ropm install
- run: npm run validate
@ -22,7 +22,7 @@ jobs:
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "MINOR="$2; }' manifest >> $GITHUB_ENV
- name: "Find and save build_version from manifest"
run: awk 'BEGIN { FS="=" } /^build_version/ { print "BUILD="$2; }' manifest >> $GITHUB_ENV
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
- uses: vimtor/action-zip@5f1c4aa587ea41db1110df6a99981dbe19cee310 # tag=v1
with:
recursive: false
@ -36,7 +36,7 @@ jobs:
prerelease: false
title: v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}
files: ${{ github.workspace }}/jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3
with:
name: jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip
path: ${{ github.workspace }}/jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip

View File

@ -6,12 +6,12 @@ on:
jobs:
test-build-release:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@master
- uses: actions/setup-node@master
with:
node-version: "14.12.0"
node-version: "18.13.0"
- run: npm ci
- run: npx ropm install
- run: npm run validate
@ -22,7 +22,7 @@ jobs:
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "MINOR="$2; }' manifest >> $GITHUB_ENV
- name: "Find and save build_version from manifest"
run: awk 'BEGIN { FS="=" } /^build_version/ { print "BUILD="$2; }' manifest >> $GITHUB_ENV
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # v3
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
- uses: vimtor/action-zip@5f1c4aa587ea41db1110df6a99981dbe19cee310 # tag=v1
with:
recursive: false
@ -35,7 +35,7 @@ jobs:
prerelease: true
title: v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}
files: ${{ github.workspace }}/jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3
with:
name: jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip
path: ${{ github.workspace }}/jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip

View File

@ -3,12 +3,12 @@ on: [push, pull_request]
jobs:
run:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@master
- uses: actions/setup-node@master
with:
node-version: "14.12.0"
node-version: "18.13.0"
- run: npm ci
- run: npx ropm install
- run: npm run validate

View File

@ -8,6 +8,9 @@ sub init()
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
m.unplayedCount = m.top.findNode("unplayedCount")
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
m.itemText.translation = [0, m.itemPoster.height + 7]
m.alwaysShowTitles = get_user_setting("itemgrid.alwaysShowTitles") = "true"
@ -40,6 +43,13 @@ sub itemContentChanged()
m.itemIcon.uri = itemData.iconUrl
m.itemText.text = itemData.Title
else if itemData.type = "Series"
if itemData?.json?.UserData?.UnplayedItemCount <> invalid
if itemData.json.UserData.UnplayedItemCount > 0
m.unplayedCount.visible = true
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
end if
end if
m.itemPoster.uri = itemData.PosterUrl
m.itemIcon.uri = itemData.iconUrl
m.itemText.text = itemData.Title

View File

@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="GridItem" extends="Group">
<children>
<maskGroup id="posterMask" maskUri="pkg:/images/postermask.png" scaleRotateCenter="[145, 212.5]" scale="[0.85,0.85]" >
<maskGroup id="posterMask" maskUri="pkg:/images/postermask.png" scaleRotateCenter="[145, 212.5]" scale="[0.85,0.85]">
<Poster id="backdrop" width="290" height="425" loadDisplayMode="scaleToZoom" uri="pkg:/images/white.9.png" />
<Poster id="itemPoster" width="290" height="425" loadDisplayMode="scaleToZoom" />
<Poster id="itemPoster" width="290" height="425" loadDisplayMode="scaleToZoom">
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[201, 0]">
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:SmallestBoldSystemFont" horizAlign="center" vertAlign="center" />
</Rectangle>
</Poster>
<Poster id="itemIcon" width="50" height="50" translation="[230,10]" />
<Label id="posterText" width="280" height="415" translation="[5,5]" horizAlign="center" vertAlign="center" ellipsizeOnBoundary="true" wrap="true" />
</maskGroup>

View File

@ -1,7 +1,9 @@
sub init()
m.itemPoster = m.top.findNode("itemPoster")
m.posterText = m.top.findNode("posterText")
m.title = m.top.findNode("title")
m.posterText.font.size = 30
m.title.font.size = 25
m.backdrop = m.top.findNode("backdrop")
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
@ -9,22 +11,32 @@ sub init()
'Parent is MarkupGrid and it's parent is the ItemGrid
m.topParent = m.top.GetParent().GetParent()
m.title.visible = false
'Get the imageDisplayMode for these grid items
if m.topParent.imageDisplayMode <> invalid
m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode
end if
end sub
sub itemContentChanged()
m.backdrop.blendColor = "#101010"
m.title.visible = false
if isValid(m.topParent.showItemTitles)
if LCase(m.topParent.showItemTitles) = "showalways"
m.title.visible = true
end if
end if
itemData = m.top.itemContent
if not isValid(itemData) then return
m.itemPoster.uri = itemData.PosterUrl
m.posterText.text = itemData.title
m.title.text = itemData.title
'If Poster not loaded, ensure "blue box" is shown until loaded
if m.itemPoster.loadStatus <> "ready"
@ -33,6 +45,20 @@ sub itemContentChanged()
end if
end sub
sub focusChanged()
if m.top.itemHasFocus = true
m.title.repeatCount = -1
else
m.title.repeatCount = 0
end if
if isValid(m.topParent.showItemTitles)
if LCase(m.topParent.showItemTitles) = "showonhover"
m.title.visible = m.top.itemHasFocus
end if
end if
end sub
'Hide backdrop and text when poster loaded
sub onPosterLoadStatusChanged()
if m.itemPoster.loadStatus = "ready"

View File

@ -3,12 +3,13 @@
<children>
<Poster id="backdrop" translation="[0,15]" width="230" height="320" loadDisplayMode="scaleToZoom" uri="pkg:/images/white.9.png" />
<Poster id="itemPoster" translation="[0,15]" width="230" height="320" loadDisplayMode="scaleToZoom" />
<ScrollingLabel translation="[0,340]" id="title" horizAlign="center" font="font:SmallSystemFont" repeatCount="0" maxWidth="230" />
<Poster id="itemIcon" width="50" height="50" translation="[230,10]" />
<Label id="posterText" width="230" height="320" translation="[5,5]" horizAlign="center" vertAlign="center" ellipsizeOnBoundary="true" wrap="true" />
</children>
<interface>
<field id="itemContent" type="node" onChange="itemContentChanged" />
<field id="itemHasFocus" type="boolean" onChange="focusChanged" />
<field id="itemHasFocus" type="boolean" onChange="focusChanged" alwaysNotify="true" />
</interface>
<script type="text/brightscript" uri="GridItemSmall.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />

View File

@ -12,6 +12,11 @@ sub init()
m.newBackdrop = m.top.findNode("backdropTransition")
m.emptyText = m.top.findNode("emptyText")
m.genreList = m.top.findNode("genrelist")
m.genreList.observeField("itemSelected", "onGenreItemSelected")
m.genreData = CreateObject("roSGNode", "ContentNode")
m.genreList.content = m.genreData
m.swapAnimation = m.top.findNode("backroundSwapAnimation")
m.swapAnimation.observeField("state", "swapDone")
@ -74,6 +79,12 @@ sub init()
end if
end sub
'
'Genre Item Selected
sub onGenreItemSelected()
m.top.selectedItem = m.genreList.content.getChild(m.genreList.rowItemSelected[0]).getChild(m.genreList.rowItemSelected[1])
end sub
'
'Load initial set of Data
sub loadInitialItems()
@ -241,7 +252,10 @@ sub setMoviesOptions(options)
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },
{ "Title": tr("Favorites"), "Name": "Favorites" }
{ "Title": tr("Favorites"), "Name": "Favorites" },
{ "Title": tr("Played"), "Name": "Played" },
{ "Title": tr("Unplayed"), "Name": "Unplayed" },
{ "Title": tr("Resumable"), "Name": "Resumable" }
]
end sub
@ -256,7 +270,9 @@ sub setBoxsetsOptions(options)
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },
{ "Title": tr("Favorites"), "Name": "Favorites" }
{ "Title": tr("Favorites"), "Name": "Favorites" },
{ "Title": tr("Played"), "Name": "Played" },
{ "Title": tr("Unplayed"), "Name": "Unplayed" }
]
end sub
@ -278,8 +294,18 @@ sub setTvShowsOptions(options)
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },
{ "Title": tr("Favorites"), "Name": "Favorites" }
{ "Title": tr("Favorites"), "Name": "Favorites" },
{ "Title": tr("Played"), "Name": "Played" },
{ "Title": tr("Unplayed"), "Name": "Unplayed" }
]
if isValid(m.view)
if LCase(m.options.view) = "genres" or LCase(m.view) = "genres"
options.sort = [{ "Title": tr("TITLE"), "Name": "SortName" }]
options.filter = []
end if
end if
end sub
' Set Live TV view, sort, and filter options
@ -423,10 +449,32 @@ sub ItemDataLoaded(msg)
return
end if
if m.loadItemsTask.view = "Genres"
' Reset genre list data
m.genreData.removeChildren(m.genreData.getChildren(-1, 0))
for each item in itemData
m.genreData.appendChild(item)
end for
m.itemGrid.opacity = "0"
m.genreList.opacity = "1"
m.itemGrid.setFocus(false)
m.genreList.setFocus(true)
m.loading = false
m.spinner.visible = false
return
end if
for each item in itemData
m.data.appendChild(item)
end for
m.itemGrid.opacity = "1"
m.genreList.opacity = "0"
'Update the stored counts
m.loadedItems = m.itemGrid.content.getChildCount()
m.loadedRows = m.loadedItems / m.itemGrid.numColumns
@ -438,6 +486,7 @@ sub ItemDataLoaded(msg)
end if
m.itemGrid.setFocus(true)
m.genreList.setFocus(false)
m.spinner.visible = false
end sub
@ -645,7 +694,10 @@ sub optionsClosed()
m.itemGrid.content = m.data
loadInitialItems()
end if
m.itemGrid.setFocus(true)
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
if m.tvGuide <> invalid
m.tvGuide.lastFocus.setFocus(true)
end if
@ -681,13 +733,19 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
topGrp = m.top.findNode("itemGrid")
if m.itemGrid.opacity = 1
topGrp = m.itemGrid
else
topGrp = m.genreList
end if
searchGrp = m.top.findNode("voiceBox")
if key = "left" and searchGrp.isinFocusChain()
topGrp.setFocus(true)
searchGrp.setFocus(false)
end if
if key = "options"
if m.options.visible = true
m.options.visible = false
@ -773,14 +831,16 @@ function onKeyEvent(key as string, press as boolean) as boolean
end function
sub updateTitle()
if m.filter = "All"
m.top.overhangTitle = m.top.parentItem.title
else if m.filter = "Favorites"
m.top.overhangTitle = m.top.parentItem.title
if m.filter = "Favorites"
m.top.overhangTitle = m.top.parentItem.title + " " + tr("(Favorites)")
end if
if m.voiceBox.text <> ""
m.top.overhangTitle = m.top.parentItem.title + tr(" (Filtered by ") + m.loadItemsTask.searchTerm + ")"
end if
if m.top.alphaSelected <> ""
m.top.overhangTitle = m.top.parentItem.title + tr(" (Filtered by ") + m.loadItemsTask.nameStartsWith + ")"
end if
@ -794,14 +854,18 @@ sub updateTitle()
if m.options.view = "Networks" or m.view = "Networks"
m.top.overhangTitle = "%s (%s)".Format(m.top.parentItem.title, tr("Networks"))
end if
if m.options.view = "Studios" or m.view = "Studios"
m.top.overhangTitle = "%s (%s)".Format(m.top.parentItem.title, tr("Studios"))
end if
if m.options.view = "Genres" or m.view = "Genres"
m.top.overhangTitle = "%s (%s)".Format(m.top.parentItem.title, tr("Genres"))
end if
actInt = m.itemGrid.itemFocused + 1
if m.showItemCount and m.loadItemsTask.totalRecordCount > 0
if m.showItemCount and m.loadItemsTask.totalRecordCount > 0 and m.options.view <> "Genres" and m.view <> "Genres"
m.top.overhangTitle += " (" + tr("%1 of %2").Replace("%1", actInt.toStr()).Replace("%2", m.loadItemsTask.totalRecordCount.toStr()) + ")"
end if

View File

@ -3,18 +3,8 @@
<children>
<VoiceTextEditBox id="VoiceBox" visible="true" width = "40" translation = "[52, 120]" />
<Rectangle id="VoiceBoxCover" height="240" width="100" color="0x262626ff" translation = "[25, 75]" />
<poster id="backdrop"
loadDisplayMode="scaleToFill"
width="1920"
height="1080"
opacity="0.25"
/>
<poster id="backdropTransition"
loadDisplayMode="scaleToFill"
width="1920"
height="1080"
opacity="0.25"
/>
<poster id="backdrop" loadDisplayMode="scaleToFill" width="1920" height="1080" opacity="0.25" />
<poster id="backdropTransition" loadDisplayMode="scaleToFill" width="1920" height="1080" opacity="0.25" />
<MarkupGrid
id = "itemGrid"
translation = "[ 96, 160 ]"
@ -25,14 +15,17 @@
itemSize = "[ 290, 425 ]"
itemSpacing = "[ 0, 45 ]"
drawFocusFeedback = "false" />
<Label id="micButtonText" font="font:SmallSystemFont" visible="false" />
<Button id = "micButton" maxWidth = "20" translation = "[20, 120]" iconUri = "pkg:/images/icons/mic_icon.png"/>
<RowList opacity="0" id="genrelist" translation="[120, 160]" showRowLabel="true" itemComponentName="GridItemSmall" numColumns="1" numRows="3" vertFocusAnimationStyle="fixed" itemSize = "[1900, 360]" rowItemSize="[ [230, 320] ]" rowItemSpacing="[ [20, 0] ]" itemSpacing="[0, 60]" />
<Label id="micButtonText" font="font:SmallSystemFont" visible="false" />
<Button id = "micButton" maxWidth = "20" translation = "[20, 120]" iconUri = "pkg:/images/icons/mic_icon.png"/>
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1910" horizAlign="center" vertAlign="center" height="64" visible="false" />
<ItemGridOptions id="options" visible="false" />
<Spinner id="spinner" translation="[900, 450]" />
<Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear" >
<FloatFieldInterpolator id = "fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 0.25 ]" fieldToInterp="backdropTransition.opacity" />
<FloatFieldInterpolator id = "fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 0.25, 0.00 ]" fieldToInterp="backdrop.opacity" />
<Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear">
<FloatFieldInterpolator id = "fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 0.25 ]" fieldToInterp="backdropTransition.opacity" />
<FloatFieldInterpolator id = "fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 0.25, 0.00 ]" fieldToInterp="backdrop.opacity" />
</Animation>
<Alpha id="AlphaMenu" />
</children>

View File

@ -75,6 +75,12 @@ sub loadItems()
else if filter = "favorites"
params.append({ Filters: "IsFavorite" })
params.append({ isFavorite: true })
else if filter = "unplayed"
params.append({ Filters: "IsUnplayed" })
else if filter = "played"
params.append({ Filters: "IsPlayed" })
else if filter = "resumable"
params.append({ Filters: "IsResumable" })
end if
if m.top.ItemType <> ""
@ -89,13 +95,14 @@ sub loadItems()
params.append({ UserId: get_setting("active_user") })
else if m.top.view = "Genres"
url = "Genres"
params.append({ UserId: get_setting("active_user") })
params.append({ UserId: get_setting("active_user"), includeItemTypes: m.top.itemType })
else if m.top.ItemType = "MusicArtist"
url = "Artists"
params.append({
UserId: get_setting("active_user")
UserId: get_setting("active_user"),
Fields: "Genres"
})
params.IncludeItemTypes = ""
params.IncludeItemTypes = "MusicAlbum,Audio"
else if m.top.ItemType = "MusicAlbum"
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
params.append({ ImageTypeLimit: 1 })
@ -103,6 +110,7 @@ sub loadItems()
else
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
end if
resp = APIRequest(url, params)
data = getJson(resp)
if data <> invalid
@ -121,7 +129,7 @@ sub loadItems()
tmp = CreateObject("roSGNode", "ChannelData")
else if item.Type = "Folder" or item.Type = "ChannelFolderItem" or item.Type = "CollectionFolder"
tmp = CreateObject("roSGNode", "FolderData")
else if item.Type = "Video"
else if item.Type = "Video" or item.Type = "Recording"
tmp = CreateObject("roSGNode", "VideoData")
else if item.Type = "Photo"
tmp = CreateObject("roSGNode", "PhotoData")
@ -136,7 +144,7 @@ sub loadItems()
genreData = api_API().users.getitemsbyquery(get_setting("active_user"), {
SortBy: "Random",
SortOrder: "Ascending",
IncludeItemTypes: "Movie",
IncludeItemTypes: m.top.itemType,
Recursive: true,
Fields: "PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo",
ImageTypeLimit: 1,
@ -151,26 +159,38 @@ sub loadItems()
' Add View All item to the start of the row
row = tmp.createChild("FolderData")
row.parentFolder = m.top.itemId
genreMovieImage = api_API().items.getimageurl(item.id)
row.title = item.name
row.title = tr("View All") + " " + item.name
item.name = tr("View All") + " " + item.name
row.json = item
row.FHDPOSTERURL = genreMovieImage
row.HDPOSTERURL = genreMovieImage
row.SDPOSTERURL = genreMovieImage
row.type = "Folder"
if LCase(m.top.itemType) = "movie"
genreItemImage = api_API().items.getimageurl(item.id)
else
genreItemImage = invalid
row.posterURL = invalid
end if
row.FHDPOSTERURL = genreItemImage
row.HDPOSTERURL = genreItemImage
row.SDPOSTERURL = genreItemImage
end if
for each genreMovie in genreData.Items
row = tmp.createChild("MovieData")
for each genreItem in genreData.Items
if LCase(m.top.itemType) = "movie"
row = tmp.createChild("MovieData")
else
row = tmp.createChild("SeriesData")
end if
genreMovieImage = api_API().items.getimageurl(genreMovie.id)
row.title = genreMovie.name
row.FHDPOSTERURL = genreMovieImage
row.HDPOSTERURL = genreMovieImage
row.SDPOSTERURL = genreMovieImage
row.json = genreMovie
row.id = genreMovie.id
row.type = genreMovie.type
genreItemImage = api_API().items.getimageurl(genreItem.id)
row.title = genreItem.name
row.FHDPOSTERURL = genreItemImage
row.HDPOSTERURL = genreItemImage
row.SDPOSTERURL = genreItemImage
row.json = genreItem
row.id = genreItem.id
row.type = genreItem.type
end for
else if item.Type = "Studio"
@ -187,12 +207,20 @@ sub loadItems()
tmp = CreateObject("roSGNode", "MusicArtistData")
else if item.Type = "Audio"
tmp = CreateObject("roSGNode", "MusicSongData")
else if item.Type = "MusicGenre"
tmp = CreateObject("roSGNode", "FolderData")
tmp.title = item.name
tmp.parentFolder = m.top.itemId
tmp.json = item
tmp.type = "Folder"
tmp.posterUrl = api_API().items.getimageurl(item.id, "primary", 0, { "maxHeight": 280, "maxWidth": 280, "quality": "90" })
else
print "[LoadItems] Unknown Type: " item.Type
end if
if tmp <> invalid
if item.Type <> "Genre"
if item.Type <> "Genre" and item.Type <> "MusicGenre"
tmp.parentFolder = m.top.itemId
tmp.json = item
if item.UserData <> invalid and item.UserData.isFavorite <> invalid

View File

@ -22,6 +22,7 @@ sub setupNodes()
m.overhang = m.top.getScene().findNode("overhang")
m.genreList = m.top.findNode("genrelist")
m.infoGroup = m.top.findNode("infoGroup")
m.star = m.top.findNode("star")
end sub
sub init()
@ -124,9 +125,14 @@ sub loadInitialItems()
end if
m.sortField = get_user_setting("display." + m.top.parentItem.Id + ".sortField")
sortAscendingStr = get_user_setting("display." + m.top.parentItem.Id + ".sortAscending")
m.filter = get_user_setting("display." + m.top.parentItem.Id + ".filter")
m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing")
sortAscendingStr = get_user_setting("display." + m.top.parentItem.Id + ".sortAscending")
' If user has not set a preferred view for this folder, check if they've set a default view
if not isValid(m.view)
m.view = get_user_setting("itemgrid.movieDefaultView")
end if
if not isValid(m.sortField) then m.sortField = "SortName"
if not isValid(m.filter) then m.filter = "All"
@ -171,9 +177,12 @@ sub loadInitialItems()
m.loadItemsTask.studioIds = ""
m.loadItemsTask.view = "Movies"
m.itemGrid.translation = "[96, 650]"
m.itemGrid.itemSize = "[230, 310]"
m.itemGrid.rowHeights = "[310]"
m.itemGrid.numRows = "2"
m.selectedMovieOverview.visible = true
m.infoGroup.visible = true
m.top.showItemTitles = "hidealways"
if m.options.view = "Studios" or m.view = "Studios"
m.itemGrid.translation = "[96, 60]"
@ -182,6 +191,19 @@ sub loadInitialItems()
m.top.imageDisplayMode = "scaleToFit"
m.selectedMovieOverview.visible = false
m.infoGroup.visible = false
else if LCase(m.options.view) = "moviesgrid" or LCase(m.view) = "moviesgrid"
m.itemGrid.translation = "[96, 60]"
m.itemGrid.numRows = "3"
m.selectedMovieOverview.visible = false
m.infoGroup.visible = false
m.top.showItemTitles = get_user_setting("itemgrid.movieGridTitles")
if LCase(m.top.showItemTitles) = "hidealways"
m.itemGrid.itemSize = "[230, 315]"
m.itemGrid.rowHeights = "[315]"
else
m.itemGrid.itemSize = "[230, 350]"
m.itemGrid.rowHeights = "[350]"
end if
else if m.options.view = "Genres" or m.view = "Genres"
m.loadItemsTask.StudioIds = m.top.parentItem.Id
m.loadItemsTask.view = "Genres"
@ -201,14 +223,16 @@ end sub
sub setMoviesOptions(options)
options.views = [
{ "Title": tr("Movies"), "Name": "Movies" },
{ "Title": tr("Movies (Presentation)"), "Name": "Movies" },
{ "Title": tr("Movies (Grid)"), "Name": "MoviesGrid" },
{ "Title": tr("Studios"), "Name": "Studios" },
{ "Title": tr("Genres"), "Name": "Genres" }
]
if m.top.parentItem.json.type = "Genre"
options.views = [
{ "Title": tr("Movies"), "Name": "Movies" }
{ "Title": tr("Movies (Presentation)"), "Name": "Movies" },
{ "Title": tr("Movies (Grid)"), "Name": "MoviesGrid" },
]
end if
@ -226,11 +250,14 @@ sub setMoviesOptions(options)
options.filter = [
{ "Title": tr("All"), "Name": "All" },
{ "Title": tr("Favorites"), "Name": "Favorites" }
{ "Title": tr("Favorites"), "Name": "Favorites" },
{ "Title": tr("Played"), "Name": "Played" },
{ "Title": tr("Unplayed"), "Name": "Unplayed" },
{ "Title": tr("Resumable"), "Name": "Resumable" }
]
if m.options.view = "Genres" or m.view = "Genres"
options.sort = []
options.sort = [{ "Title": tr("TITLE"), "Name": "SortName" }]
options.filter = []
end if
@ -239,6 +266,10 @@ sub setMoviesOptions(options)
{ "Title": tr("TITLE"), "Name": "SortName" },
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },
{ "Title": tr("Favorites"), "Name": "Favorites" }
]
end if
end sub
@ -340,6 +371,10 @@ sub ItemDataLoaded(msg)
m.loading = false
m.spinner.visible = false
' Return focus to options menu if it was opened while library was loading
if m.options.visible
m.options.setFocus(true)
end if
return
end if
@ -359,11 +394,33 @@ sub ItemDataLoaded(msg)
m.Loading = false
'If there are no items to display, show message
if m.loadedItems = 0
m.selectedMovieOverview.visible = false
m.infoGroup.visible = false
m.movieLogo.visible = false
m.movieLogo.uri = ""
m.selectedMovieName.visible = false
SetName("")
SetOverview("")
SetOfficialRating("")
SetProductionYear("")
setFieldText("runtime", "")
setFieldText("communityRating", "")
setFieldText("criticRatingLabel", "")
m.criticRatingIcon.uri = ""
m.star.uri = ""
m.emptyText.text = tr("NO_ITEMS").Replace("%1", m.top.parentItem.Type)
m.emptyText.visible = true
end if
m.spinner.visible = false
' Return focus to options menu if it was opened while library was loading
if m.options.visible
m.options.setFocus(true)
end if
end sub
'
@ -430,12 +487,20 @@ sub onItemFocused()
m.communityRatingGroup.visible = false
m.criticRatingGroup.visible = false
if m.options.view = "Studios" or m.view = "Studios"
if not isValid(m.selectedFavoriteItem)
return
end if
if LCase(m.options.view) = "studios" or LCase(m.view) = "studios"
return
else if LCase(m.options.view) = "moviesgrid" or LCase(m.view) = "moviesgrid"
return
end if
itemData = m.selectedFavoriteItem.json
m.star.uri = "pkg:/images/sharp_star_white_18dp.png"
if isValid(itemData.communityRating)
setFieldText("communityRating", int(itemData.communityRating * 10) / 10)
m.communityRatingGroup.visible = true
@ -762,7 +827,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
else
m.itemGrid.jumpToItem = 0
end if
return true
else if key = "replay" and m.genreList.isinFocusChain()
if m.resetGrid = true
m.genreList.animateToItem = 0

View File

@ -54,6 +54,7 @@
<field id="imageDisplayMode" type="string" value="scaleToZoom" />
<field id="AlphaSelected" type="string" alias="AlphaMenu.itemAlphaSelected" alwaysNotify="true" onChange="onItemAlphaSelected" />
<field id="alphaActive" type="boolean" value="false" />
<field id="showItemTitles" type="string" value="showonhover" />
<field id="jumpToItem" type="integer" value="" />
</interface>
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />

View File

@ -0,0 +1,48 @@
sub init()
m.itemPoster = m.top.findNode("itemPoster")
m.posterText = m.top.findNode("posterText")
m.posterText.font.size = 30
m.backdrop = m.top.findNode("backdrop")
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
'Parent is MarkupGrid and it's parent is the ItemGrid
m.topParent = m.top.GetParent().GetParent()
'Get the imageDisplayMode for these grid items
if m.topParent.imageDisplayMode <> invalid
m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode
end if
end sub
sub itemContentChanged()
m.backdrop.blendColor = "#101010"
itemData = m.top.itemContent
if not isValid(itemData) then return
if LCase(itemData.type) = "musicalbum"
m.backdrop.uri = "pkg:/images/icons/album.png"
else if LCase(itemData.type) = "musicartist"
m.backdrop.uri = "pkg:/images/missingArtist.png"
else if LCase(itemData.json.type) = "musicgenre"
m.backdrop.uri = "pkg:/images/icons/musicFolder.png"
end if
m.itemPoster.uri = itemData.PosterUrl
m.posterText.text = itemData.title
'If Poster not loaded, ensure "blue box" is shown until loaded
if m.itemPoster.loadStatus <> "ready"
m.backdrop.visible = true
end if
end sub
'Hide backdrop and text when poster loaded
sub onPosterLoadStatusChanged()
if m.itemPoster.loadStatus = "ready"
m.backdrop.visible = false
end if
end sub

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="MusicArtistGridItem" extends="Group">
<children>
<Poster id="backdrop" translation="[0,15]" width="280" height="280" loadDisplayMode="scaleToZoom" uri="pkg:/images/white.9.png" />
<Poster id="itemPoster" translation="[0,15]" width="280" height="280" loadDisplayMode="scaleToZoom" />
<Rectangle id="postTextBackground" height="50" width="270" color="0x000000DD" translation = "[5, 240]">
<ScrollingLabel id="posterText" color="#FFFFFF" maxWidth="270" height="50" horizAlign="center" vertAlign="center" />
</Rectangle>
</children>
<interface>
<field id="itemContent" type="node" onChange="itemContentChanged" />
<field id="itemHasFocus" type="boolean" onChange="focusChanged" />
</interface>
<script type="text/brightscript" uri="MusicArtistGridItem.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>

View File

@ -0,0 +1,778 @@
sub setupNodes()
m.options = m.top.findNode("options")
m.itemGrid = m.top.findNode("itemGrid")
m.voiceBox = m.top.findNode("voiceBox")
m.backdrop = m.top.findNode("backdrop")
m.newBackdrop = m.top.findNode("backdropTransition")
m.emptyText = m.top.findNode("emptyText")
m.selectedArtistName = m.top.findNode("selectedArtistName")
m.selectedArtistSongCount = m.top.findNode("selectedArtistSongCount")
m.selectedArtistAlbumCount = m.top.findNode("selectedArtistAlbumCount")
m.selectedArtistGenres = m.top.findNode("selectedArtistGenres")
m.artistLogo = m.top.findNode("artistLogo")
m.swapAnimation = m.top.findNode("backroundSwapAnimation")
m.spinner = m.top.findNode("spinner")
m.Alpha = m.top.findNode("AlphaMenu")
m.AlphaSelected = m.top.findNode("AlphaSelected")
m.micButton = m.top.findNode("micButton")
m.micButtonText = m.top.findNode("micButtonText")
m.overhang = m.top.getScene().findNode("overhang")
m.genreList = m.top.findNode("genrelist")
end sub
sub init()
setupNodes()
m.overhang.isVisible = false
m.showItemCount = get_user_setting("itemgrid.showItemCount") = "true"
m.swapAnimation.observeField("state", "swapDone")
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.genreData = CreateObject("roSGNode", "ContentNode")
m.genreList.observeField("itemSelected", "onGenreItemSelected")
m.genreList.observeField("itemFocused", "onGenreItemFocused")
m.genreList.content = m.genreData
m.itemGrid.observeField("itemFocused", "onItemFocused")
m.itemGrid.observeField("itemSelected", "onItemSelected")
m.itemGrid.observeField("alphaSelected", "onItemalphaSelected")
'Voice filter setup
m.voiceBox.voiceEnabled = true
m.voiceBox.active = true
m.voiceBox.observeField("text", "onvoiceFilter")
'set voice help text
m.voiceBox.hintText = tr("Use voice remote to search")
'backdrop
m.newBackdrop.observeField("loadStatus", "newBGLoaded")
'Background Image Queued for loading
m.queuedBGUri = ""
'Item sort - maybe load defaults from user prefs?
m.sortField = "SortName"
m.sortAscending = true
m.filter = "All"
m.favorite = "Favorite"
m.loadItemsTask = createObject("roSGNode", "LoadItemsTask2")
m.loadLogoTask = createObject("roSGNode", "LoadItemsTask2")
'set inital counts for overhang before content is loaded.
m.loadItemsTask.totalRecordCount = 0
m.spinner.visible = true
'Get reset folder setting
m.resetGrid = get_user_setting("itemgrid.reset") = "true"
'Check if device has voice remote
devinfo = CreateObject("roDeviceInfo")
'Hide voice search if device does not have voice remote
if devinfo.HasFeature("voice_remote") = false
m.micButton.visible = false
m.micButtonText.visible = false
end if
end sub
sub OnScreenHidden()
if not m.overhang.isVisible
m.overhang.disableMoveAnimation = true
m.overhang.isVisible = true
m.overhang.disableMoveAnimation = false
end if
end sub
sub OnScreenShown()
m.overhang.isVisible = false
if m.top.lastFocus <> invalid
m.top.lastFocus.setFocus(true)
else
m.top.setFocus(true)
end if
end sub
'
'Load initial set of Data
sub loadInitialItems()
m.loadItemsTask.control = "stop"
m.spinner.visible = true
if LCase(m.top.parentItem.json.Type) = "collectionfolder"
m.top.HomeLibraryItem = m.top.parentItem.Id
end if
if m.top.parentItem.backdropUrl <> invalid
SetBackground(m.top.parentItem.backdropUrl)
else
SetBackground("")
end if
m.sortField = get_user_setting("display." + m.top.parentItem.Id + ".sortField")
sortAscendingStr = get_user_setting("display." + m.top.parentItem.Id + ".sortAscending")
m.filter = get_user_setting("display." + m.top.parentItem.Id + ".filter")
m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing")
if not isValid(m.sortField) then m.sortField = "SortName"
if not isValid(m.filter) then m.filter = "All"
if not isValid(m.view) then m.view = "ArtistsPresentation"
if sortAscendingStr = invalid or LCase(sortAscendingStr) = "true"
m.sortAscending = true
else
m.sortAscending = false
end if
if LCase(m.top.parentItem.json.type) = "musicgenre"
m.itemGrid.translation = "[96, 60]"
m.loadItemsTask.itemType = "MusicAlbum"
m.loadItemsTask.recursive = true
m.loadItemsTask.genreIds = m.top.parentItem.id
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
else if LCase(m.view) = "artistspresentation" or LCase(m.options.view) = "artistspresentation"
m.loadItemsTask.genreIds = ""
else if LCase(m.view) = "artistsgrid" or LCase(m.options.view) = "artistsgrid"
m.loadItemsTask.genreIds = ""
else
m.loadItemsTask.itemId = m.top.parentItem.Id
end if
m.loadItemsTask.nameStartsWith = m.top.alphaSelected
m.loadItemsTask.searchTerm = m.voiceBox.text
m.emptyText.visible = false
m.loadItemsTask.sortField = m.sortField
m.loadItemsTask.sortAscending = m.sortAscending
m.loadItemsTask.filter = m.filter
m.loadItemsTask.startIndex = 0
' Load Item Types
if getCollectionType() = "music"
m.loadItemsTask.itemType = "MusicArtist"
m.loadItemsTask.itemId = m.top.parentItem.Id
end if
' By default we load Artists
m.loadItemsTask.view = "Artists"
m.itemGrid.translation = "[96, 420]"
m.itemGrid.numRows = "3"
if LCase(m.options.view) = "albums" or LCase(m.view) = "albums"
m.itemGrid.translation = "[96, 60]"
m.itemGrid.numRows = "4"
m.loadItemsTask.itemType = "MusicAlbum"
m.top.imageDisplayMode = "scaleToFit"
else if LCase(m.options.view) = "artistsgrid" or LCase(m.view) = "artistsgrid"
m.itemGrid.translation = "[96, 60]"
m.itemGrid.numRows = "4"
else if LCase(m.options.view) = "genres" or LCase(m.view) = "genres"
m.loadItemsTask.itemType = ""
m.loadItemsTask.recursive = true
m.loadItemsTask.view = "Genres"
m.artistLogo.visible = false
m.selectedArtistName.visible = false
end if
if LCase(m.top.parentItem.json.type) = "musicgenre"
m.itemGrid.translation = "[96, 60]"
m.itemGrid.numRows = "4"
m.artistLogo.visible = false
m.selectedArtistName.visible = false
end if
m.loadItemsTask.observeField("content", "ItemDataLoaded")
m.spinner.visible = true
m.loadItemsTask.control = "RUN"
SetUpOptions()
end sub
' Set Music view, sort, and filter options
sub setMusicOptions(options)
options.views = [
{ "Title": tr("Artists (Presentation)"), "Name": "ArtistsPresentation" },
{ "Title": tr("Artists (Grid)"), "Name": "ArtistsGrid" },
{ "Title": tr("Albums"), "Name": "Albums" },
{ "Title": tr("Genres"), "Name": "Genres" }
]
if LCase(m.top.parentItem.json.type) = "musicgenre"
options.views = [
{ "Title": tr("Albums"), "Name": "Albums" }
]
end if
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" },
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },
{ "Title": tr("Favorites"), "Name": "Favorites" }
]
if LCase(m.options.view) = "genres" or LCase(m.view) = "genres"
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" },
]
options.filter = []
end if
if LCase(m.options.view) = "albums" or LCase(m.view) = "albums"
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" },
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
]
end if
end sub
' Return parent collection type
function getCollectionType() as string
if m.top.parentItem.collectionType = invalid
return LCase(m.top.parentItem.Type)
else
return LCase(m.top.parentItem.CollectionType)
end if
end function
' Search string array for search value. Return if it's found
function inStringArray(array, searchValue) as boolean
for each item in array
if lcase(item) = lcase(searchValue) then return true
end for
return false
end function
' Data to display when options button selected
sub SetUpOptions()
options = {}
options.filter = []
options.favorite = []
setMusicOptions(options)
' Set selected view option
for each o in options.views
if LCase(o.Name) = LCase(m.view)
o.Selected = true
o.Ascending = m.sortAscending
m.options.view = o.Name
end if
end for
' Set selected sort option
for each o in options.sort
if LCase(o.Name) = LCase(m.sortField)
o.Selected = true
o.Ascending = m.sortAscending
m.options.sortField = o.Name
end if
end for
' Set selected filter option
for each o in options.filter
if LCase(o.Name) = LCase(m.filter)
o.Selected = true
m.options.filter = o.Name
end if
end for
m.options.options = options
end sub
'
' Logo Image Loaded Event Handler
sub LogoImageLoaded(msg)
data = msg.GetData()
m.loadLogoTask.unobserveField("content")
m.loadLogoTask.content = []
if data.Count() > 0
m.artistLogo.uri = data[0]
m.artistLogo.visible = true
else
m.selectedArtistName.visible = true
end if
end sub
'
'Handle loaded data, and add to Grid
sub ItemDataLoaded(msg)
m.top.alphaActive = false
itemData = msg.GetData()
m.loadItemsTask.unobserveField("content")
m.loadItemsTask.content = []
if itemData = invalid
m.Loading = false
return
end if
if LCase(m.loadItemsTask.view) = "genres"
for each item in itemData
m.genreData.appendChild(item)
end for
m.itemGrid.opacity = "0"
m.genreList.opacity = "1"
m.itemGrid.setFocus(false)
m.genreList.setFocus(true)
m.loadedItems = m.genreList.content.getChildCount()
m.loadedRows = m.loadedItems / m.genreList.numColumns
m.loading = false
m.spinner.visible = false
return
end if
m.itemGrid.opacity = "1"
m.genreList.opacity = "0"
m.itemGrid.setFocus(true)
m.genreList.setFocus(false)
for each item in itemData
m.data.appendChild(item)
end for
'Update the stored counts
m.loadedItems = m.itemGrid.content.getChildCount()
m.loadedRows = m.loadedItems / m.itemGrid.numColumns
m.Loading = false
'If there are no items to display, show message
if m.loadedItems = 0
m.emptyText.text = tr("NO_ITEMS").Replace("%1", m.top.parentItem.Type)
m.emptyText.visible = true
end if
m.spinner.visible = false
end sub
'
'Set Selected Artist Name
sub SetName(artistName as string)
m.selectedArtistName.text = artistName
end sub
'
'Set Selected Artist Song Count
sub SetSongCount(totalCount)
appendText = " " + tr("Songs")
if totalCount = 1
appendText = " " + tr("Song")
end if
m.selectedArtistSongCount.text = totalCount.tostr() + appendText
end sub
'
'Set Selected Artist Album Count
sub SetAlbumCount(totalCount)
appendText = " " + tr("Albums")
if totalCount = 1
appendText = " " + tr("Album")
end if
m.selectedArtistAlbumCount.text = totalCount.tostr() + appendText
end sub
'
'Set Selected Artist Genres
sub SetGenres(artistGenres)
m.selectedArtistGenres.text = artistGenres.join(", ")
end sub
'
'Set Background Image
sub SetBackground(backgroundUri as string)
if backgroundUri = ""
m.backdrop.opacity = 0
end if
'If a new image is being loaded, or transitioned to, store URL to load next
if LCase(m.swapAnimation.state) <> "stopped" or LCase(m.newBackdrop.loadStatus) = "loading"
m.queuedBGUri = backgroundUri
return
end if
m.newBackdrop.uri = backgroundUri
end sub
'
'Handle new item being focused
sub onItemFocused()
focusedRow = m.itemGrid.currFocusRow
itemInt = m.itemGrid.itemFocused
' If no selected item, set background to parent backdrop
if itemInt = -1
return
end if
m.artistLogo.visible = false
m.selectedArtistName.visible = false
m.selectedArtistGenres.visible = false
m.selectedArtistSongCount.visible = false
m.selectedArtistAlbumCount.visible = false
' Load more data if focus is within last 5 rows, and there are more items to load
if focusedRow >= m.loadedRows - 5 and m.loadeditems < m.loadItemsTask.totalRecordCount
loadMoreData()
end if
m.selectedFavoriteItem = getItemFocused()
if LCase(m.options.view) = "albums" or LCase(m.view) = "albums" or LCase(m.top.parentItem.json.type) = "musicgenre"
return
end if
if LCase(m.options.view) = "artistsgrid" or LCase(m.view) = "artistsgrid"
return
end if
if not m.selectedArtistGenres.visible
m.selectedArtistGenres.visible = true
end if
if not m.selectedArtistSongCount.visible
m.selectedArtistSongCount.visible = true
end if
if not m.selectedArtistAlbumCount.visible
m.selectedArtistAlbumCount.visible = true
end if
itemData = m.selectedFavoriteItem.json
if isValid(itemData.SongCount)
SetSongCount(itemData.SongCount)
else
SetSongCount("")
end if
if isValid(itemData.AlbumCount)
SetAlbumCount(itemData.AlbumCount)
else
SetAlbumCount("")
end if
if isValid(itemData.Genres)
SetGenres(itemData.Genres)
else
SetGenres([])
end if
if isValid(itemData.Name)
SetName(itemData.Name)
else
SetName("")
end if
m.loadLogoTask.itemId = itemData.id
m.loadLogoTask.itemType = "LogoImage"
m.loadLogoTask.observeField("content", "LogoImageLoaded")
m.loadLogoTask.control = "RUN"
' Set Background to item backdrop
SetBackground(m.selectedFavoriteItem.backdropUrl)
end sub
sub setFieldText(field, value)
node = m.top.findNode(field)
if node = invalid or value = invalid then return
' Handle non strings... Which _shouldn't_ happen, but hey
if type(value) = "roInt" or type(value) = "Integer"
value = str(value)
else if type(value) = "roFloat" or type(value) = "Float"
value = str(value)
else if type(value) <> "roString" and type(value) <> "String"
value = ""
end if
node.text = value
end sub
'
'When Image Loading Status changes
sub newBGLoaded()
'If image load was sucessful, start the fade swap
if LCase(m.newBackdrop.loadStatus) = "ready"
m.swapAnimation.control = "start"
end if
end sub
'
'Swap Complete
sub swapDone()
if LCase(m.swapAnimation.state) = "stopped"
'Set main BG node image and hide transitioning node
m.backdrop.uri = m.newBackdrop.uri
m.backdrop.opacity = 1
m.newBackdrop.opacity = 0
'If there is another one to load
if m.newBackdrop.uri <> m.queuedBGUri and m.queuedBGUri <> ""
SetBackground(m.queuedBGUri)
m.queuedBGUri = ""
end if
end if
end sub
'
'Load next set of items
sub loadMoreData()
m.spinner.visible = true
if m.Loading = true then return
m.Loading = true
m.loadItemsTask.startIndex = m.loadedItems
m.loadItemsTask.observeField("content", "ItemDataLoaded")
m.loadItemsTask.control = "RUN"
end sub
'
'Item Selected
sub onItemSelected()
m.top.selectedItem = m.itemGrid.content.getChild(m.itemGrid.itemSelected)
end sub
'
'Returns Focused Item
function getItemFocused()
return m.itemGrid.content.getChild(m.itemGrid.itemFocused)
end function
'
'Genre Item Selected
sub onGenreItemSelected()
m.top.selectedItem = m.genreList.content.getChild(m.genreList.itemSelected)
end sub
'
'Genre Item Focused
sub onGenreItemFocused()
focusedRow = m.genreList.currFocusRow
' Load more data if focus is within last 5 rows, and there are more items to load
if focusedRow >= m.loadedRows - 5 and m.loadeditems < m.loadItemsTask.totalRecordCount
loadMoreData()
end if
end sub
sub onItemalphaSelected()
if m.top.alphaSelected <> ""
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.genreData = CreateObject("roSGNode", "ContentNode")
m.genreList.content = m.genreData
m.loadItemsTask.searchTerm = ""
m.VoiceBox.text = ""
m.loadItemsTask.nameStartsWith = m.alpha.itemAlphaSelected
m.spinner.visible = true
loadInitialItems()
end if
end sub
sub onvoiceFilter()
if m.VoiceBox.text <> ""
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.top.alphaSelected = ""
m.loadItemsTask.NameStartsWith = " "
m.loadItemsTask.searchTerm = m.voiceBox.text
m.loadItemsTask.recursive = true
m.spinner.visible = true
loadInitialItems()
end if
end sub
'
'Check if options updated and any reloading required
sub optionsClosed()
reload = false
if m.options.sortField <> m.sortField or m.options.sortAscending <> m.sortAscending
m.sortField = m.options.sortField
m.sortAscending = m.options.sortAscending
reload = true
sortAscendingStr = "true"
'Store sort settings
if not m.sortAscending
sortAscendingStr = "false"
end if
set_user_setting("display." + m.top.parentItem.Id + ".sortField", m.sortField)
set_user_setting("display." + m.top.parentItem.Id + ".sortAscending", sortAscendingStr)
end if
if m.options.filter <> m.filter
m.filter = m.options.filter
reload = true
set_user_setting("display." + m.top.parentItem.Id + ".filter", m.options.filter)
end if
m.view = get_user_setting("display." + m.top.parentItem.Id + ".landing")
if m.options.view <> m.view
m.view = m.options.view
m.top.view = m.view
set_user_setting("display." + m.top.parentItem.Id + ".landing", m.view)
' Reset any filtering or search terms
m.top.alphaSelected = ""
m.loadItemsTask.NameStartsWith = " "
m.loadItemsTask.searchTerm = ""
m.filter = "All"
m.sortField = "SortName"
m.sortAscending = true
' Reset view to defaults
set_user_setting("display." + m.top.parentItem.Id + ".sortField", m.sortField)
set_user_setting("display." + m.top.parentItem.Id + ".sortAscending", "true")
set_user_setting("display." + m.top.parentItem.Id + ".filter", m.filter)
reload = true
end if
if reload
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.genreData = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.genreList.content = m.genreData
loadInitialItems()
end if
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
end sub
sub onChannelSelected(msg)
node = msg.getRoSGNode()
m.top.lastFocus = lastFocusedChild(node)
if node.watchChannel <> invalid
' Clone the node when it's reused/update in the TimeGrid it doesn't automatically start playing
m.top.selectedItem = node.watchChannel.clone(false)
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "left" and m.voiceBox.isinFocusChain()
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
m.voiceBox.setFocus(false)
end if
if key = "options"
if m.options.visible = true
m.options.visible = false
m.top.removeChild(m.options)
optionsClosed()
else
itemSelected = m.selectedFavoriteItem
if itemSelected <> invalid
m.options.selectedFavoriteItem = itemSelected
end if
m.options.visible = true
m.top.appendChild(m.options)
m.options.setFocus(true)
end if
return true
else if key = "back"
if m.options.visible = true
m.options.visible = false
optionsClosed()
return true
else
m.global.sceneManager.callfunc("popScene")
m.loadItemsTask.control = "stop"
return true
end if
else if key = "left"
if m.itemGrid.isinFocusChain()
m.top.alphaActive = true
m.itemGrid.setFocus(false)
alpha = m.alpha.getChild(0).findNode("Alphamenu")
alpha.setFocus(true)
return true
else if m.genreList.isinFocusChain()
m.top.alphaActive = true
m.genreList.setFocus(false)
alpha = m.alpha.getChild(0).findNode("Alphamenu")
alpha.setFocus(true)
return true
end if
else if key = "right" and m.Alpha.isinFocusChain()
m.top.alphaActive = false
m.Alpha.setFocus(false)
m.Alpha.visible = true
m.itemGrid.setFocus(m.itemGrid.opacity = 1)
m.genreList.setFocus(m.genreList.opacity = 1)
return true
else if key = "replay" and m.itemGrid.isinFocusChain()
if m.resetGrid = true
m.itemGrid.animateToItem = 0
else
m.itemGrid.jumpToItem = 0
end if
else if key = "replay" and m.genreList.isinFocusChain()
if m.resetGrid = true
m.genreList.animateToItem = 0
else
m.genreList.jumpToItem = 0
end if
return true
end if
if key = "replay"
m.spinner.visible = true
m.loadItemsTask.searchTerm = ""
m.loadItemsTask.nameStartsWith = ""
m.voiceBox.text = ""
m.top.alphaSelected = ""
m.loadItemsTask.filter = "All"
m.filter = "All"
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
loadInitialItems()
return true
end if
return false
end function

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="MusicLibraryView" extends="JFScreen">
<children>
<Rectangle id="screenSaverBackground" width="1920" height="1080" color="#000000" />
<VoiceTextEditBox id="VoiceBox" visible="true" width = "40" translation = "[52, 120]" />
<Rectangle id="VoiceBoxCover" height="240" width="100" color="0x000000ff" translation = "[25, 75]" />
<maskGroup translation="[820, 0]" id="backgroundMask" maskUri="pkg:/images/backgroundmask.png" maskSize="[1220,445]">
<poster id="backdrop" loadDisplayMode="scaleToFill" width="1100" height="450" opacity="1" />
<poster id="backdropTransition" loadDisplayMode="scaleToFill" width="1100" height="450" opacity="1" />
</maskGroup>
<Label id="selectedArtistName" visible="false" translation="[120, 40]" wrap="true" font="font:LargeBoldSystemFont" width="850" height="196" horizAlign="left" vertAlign="center" />
<Poster id="artistLogo" visible="false" translation="[120, 40]" loadDisplayMode="scaleToFit" width="384" height="196" />
<Label id="selectedArtistSongCount" translation="[120, 270]" wrap="true" font="font:SmallestSystemFont" width="850" height="30" horizAlign="left" />
<Label id="selectedArtistAlbumCount" translation="[120, 310]" wrap="true" font="font:SmallestSystemFont" width="850" height="30" horizAlign="left" />
<Label id="selectedArtistGenres" translation="[120, 350]" wrap="true" font="font:SmallestSystemFont" width="850" height="30" horizAlign="left" />
<MarkupGrid id="itemGrid" itemComponentName="MusicArtistGridItem" numColumns="6" numRows="2" vertFocusAnimationStyle="fixed" itemSize="[280, 280]" itemSpacing="[20, 20]" />
<MarkupGrid id="genrelist" itemComponentName="MusicArtistGridItem" numColumns="6" numRows="4" vertFocusAnimationStyle="fixed" translation="[96, 60]" itemSize="[280, 280]" itemSpacing="[20, 20]" opacity="0" />
<Label id="micButtonText" font="font:SmallSystemFont" visible="false" />
<Button id = "micButton" maxWidth = "20" translation = "[20, 120]" iconUri = "pkg:/images/icons/mic_icon.png"/>
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1910" horizAlign="center" vertAlign="center" height="64" visible="false" />
<ItemGridOptions id="options" visible="false" />
<Spinner id="spinner" translation="[900, 450]" />
<Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear">
<FloatFieldInterpolator id = "fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 1.00 ]" fieldToInterp="backdropTransition.opacity" />
<FloatFieldInterpolator id = "fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 1.00, 0.00 ]" fieldToInterp="backdrop.opacity" />
</Animation>
<Alpha id="AlphaMenu" />
</children>
<interface>
<field id="HomeLibraryItem" type="string"/>
<field id="View" type="string"/>
<field id="parentItem" type="node" onChange="loadInitialItems" />
<field id="selectedItem" type="node" alwaysNotify="true" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
<field id="imageDisplayMode" type="string" value="scaleToZoom" />
<field id="AlphaSelected" type="string" alias="AlphaMenu.itemAlphaSelected" alwaysNotify="true" onChange="onItemAlphaSelected" />
<field id="alphaActive" type="boolean" value="false" />
<field id="jumpToItem" type="integer" value="" />
</interface>
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
<script type="text/brightscript" uri="MusicLibraryView.brs" />
</component>

View File

@ -11,7 +11,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
return true
else if key = "options"
group = m.global.sceneManager.callFunc("getActiveScene")
if group <> invalid and group.optionsAvailable
if isValid(group) and isValid(group.optionsAvailable) and group.optionsAvailable
group.lastFocus = group.focusedChild
panel = group.findNode("options")
panel.visible = true

View File

@ -8,4 +8,5 @@
<field id="exit" type="boolean" alwaysNotify="true" />
</interface>
<script type="text/brightscript" uri="JFScene.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
</component>

View File

@ -3,6 +3,8 @@ sub init()
m.staticTitle = m.top.findNode("staticTitle")
m.series = m.top.findNode("Series")
m.poster = m.top.findNode("poster")
m.unplayedCount = m.top.findNode("unplayedCount")
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
m.backdrop = m.top.findNode("backdrop")
@ -55,6 +57,13 @@ sub itemContentChanged() as void
itemData = m.top.itemContent
m.title.text = itemData.title
if itemData?.json?.UserData?.UnplayedItemCount <> invalid
if itemData.json.UserData.UnplayedItemCount > 0
m.unplayedCount.visible = true
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
end if
end if
if itemData.json.lookup("Type") = "Episode" and itemData.json.IndexNumber <> invalid
m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text

View File

@ -2,32 +2,20 @@
<component name="ListPoster" extends="Group">
<children>
<Rectangle id="backdrop" />
<ScrollingLabel id="Series"
horizAlign="center"
font="font:SmallSystemFont"
repeatCount="0"
visible="false"
/>
<Poster id="poster" translation="[2,0]" loadDisplayMode="scaleToFit" />
<ScrollingLabel id="title"
horizAlign="center"
font="font:SmallSystemFont"
repeatCount="0"
visible="false"
/>
<Label id="staticTitle"
horizAlign="center"
font="font:SmallSystemFont"
wrap="false"
/>
<ScrollingLabel id="Series" horizAlign="center" font="font:SmallSystemFont" repeatCount="0" visible="false" />
<Poster id="poster" translation="[2,0]" loadDisplayMode="scaleToFit">
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[104, 0]">
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:SmallestBoldSystemFont" horizAlign="center" vertAlign="center" />
</Rectangle>
</Poster>
<ScrollingLabel id="title" horizAlign="center" font="font:SmallSystemFont" repeatCount="0" visible="false" />
<Label id="staticTitle" horizAlign="center" font="font:SmallSystemFont" wrap="false" />
</children>
<interface>
<field id="itemContent" type="node" onChange="itemContentChanged"/>
<field id="itemWidth" type="integer" />
<field id="itemHasFocus" type="boolean" onChange="focusChanged" />
<!-- mediatype -->
</interface>
<script type="text/brightscript" uri="ListPoster.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>

View File

@ -0,0 +1,46 @@
sub init()
m.content = m.top.findNode("content")
setPalette()
m.top.id = "OKDialog"
m.top.height = 900
m.top.title = "What's New?"
m.top.buttons = [tr("OK")]
dialogStyles = {
"default": {
"fontSize": 27,
"fontUri": "font:SystemFontFile",
"color": "#EFEFEFFF"
},
"author": {
"fontSize": 27,
"fontUri": "font:SystemFontFile",
"color": "#00a4dcFF"
}
}
whatsNewList = ParseJSON(ReadAsciiFile("pkg:/source/static/whatsNew.json"))
for each item in whatsNewList
textLine = m.content.CreateChild("StdDlgMultiStyleTextItem")
textLine.drawingStyles = dialogStyles
textLine.text = "• " + item.description + " <author>" + item.author + "</author>"
end for
end sub
sub setPalette()
dlgPalette = createObject("roSGNode", "RSGPalette")
dlgPalette.colors = {
DialogBackgroundColor: "0x262828FF",
DialogFocusColor: "0xcececeFF",
DialogFocusItemColor: "0x202020FF",
DialogSecondaryTextColor: "0xf8f8f8ff",
DialogSecondaryItemColor: "#00a4dcFF",
DialogTextColor: "0xeeeeeeFF"
}
m.top.palette = dlgPalette
end sub

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="WhatsNewDialog" extends="StandardMessageDialog">
<children>
<StdDlgContentArea id="content" />
</children>
<script type="text/brightscript" uri="WhatsNewDialog.brs" />
</component>

View File

@ -11,10 +11,10 @@ sub setPoster()
if m.top.image <> invalid
m.top.posterURL = m.top.image.url
else if m.top.json.ImageTags <> invalid and m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 60 }
imgParams = { "maxHeight": 60, "Tag": m.top.json.ImageTags.Primary }
m.top.hdsmalliconurl = ImageURL(m.top.json.id, "Primary", imgParams)
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
end if
end sub

View File

@ -18,16 +18,16 @@ sub setPoster()
else
if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
else if m.top.json.BackdropImageTags <> invalid
imgParams = { "maxHeight": 440 }
imgParams = { "maxHeight": 440, "Tag": m.top.json.BackdropImageTags[0] }
m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if
' Add Backdrop Image
if m.top.json.BackdropImageTags <> invalid
imgParams = { "maxHeight": 720, "maxWidth": 1280 }
imgParams = { "maxHeight": 720, "maxWidth": 1280, "Tag": m.top.json.BackdropImageTags[0] }
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if

View File

@ -21,7 +21,7 @@ sub setPoster()
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ParentThumbImageTag }
m.top.posterURL = ImageURL(m.top.json.id, "Thumb", imgParams)
else if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
end if
end sub

View File

@ -16,7 +16,7 @@ sub setData()
' Set appropriate Images for Wide and Tall based on type
if datum.type = "CollectionFolder" or datum.type = "UserView"
params = { "maxHeight": 261, "maxWidth": 464 }
params = { "Tag": datum.ImageTags.Primary, "maxHeight": 261, "maxWidth": 464 }
m.top.thumbnailURL = ImageURL(datum.id, "Primary", params)
m.top.widePosterUrl = m.top.thumbnailURL
@ -33,14 +33,22 @@ sub setData()
imgParams.Append({ "maxHeight": 261 })
imgParams.Append({ "maxWidth": 464 })
if datum.ImageTags.Primary <> invalid
param = { "Tag": datum.ImageTags.Primary }
imgParams.Append(param)
end if
m.top.thumbnailURL = ImageURL(datum.id, "Primary", imgParams)
' Add Wide Poster (Series Backdrop)
if datum.ParentThumbImageTag <> invalid
imgParams["Tag"] = datum.ParentThumbImageTag
m.top.widePosterUrl = ImageURL(datum.ParentThumbItemId, "Thumb", imgParams)
else if datum.ParentBackdropImageTags <> invalid
imgParams["Tag"] = datum.ParentBackdropImageTags[0]
m.top.widePosterUrl = ImageURL(datum.ParentBackdropItemId, "Backdrop", imgParams)
else if datum.ImageTags.Primary <> invalid
imgParams["Tag"] = datum.SeriesPrimaryImageTag
m.top.widePosterUrl = ImageURL(datum.id, "Primary", imgParams)
end if
@ -48,16 +56,14 @@ sub setData()
imgParams = { "maxHeight": 261 }
imgParams.Append({ "maxWidth": 464 })
if datum.UserData.UnplayedItemCount > 0
imgParams["UnplayedCount"] = datum.UserData.UnplayedItemCount
end if
m.top.posterURL = ImageURL(datum.id, "Primary", imgParams)
' Add Wide Poster (Series Backdrop)
if datum.ImageTags <> invalid and datum.imageTags.Thumb <> invalid
imgParams["Tag"] = datum.imageTags.Thumb
m.top.widePosterUrl = ImageURL(datum.Id, "Thumb", imgParams)
else if datum.BackdropImageTags <> invalid
imgParams["Tag"] = datum.BackdropImageTags[0]
m.top.widePosterUrl = ImageURL(datum.Id, "Backdrop", imgParams)
end if
@ -67,14 +73,21 @@ sub setData()
imgParams.Append({ "maxHeight": 261 })
imgParams.Append({ "maxWidth": 175 })
if datum.ImageTags.Primary <> invalid
param = { "Tag": datum.ImageTags.Primary }
imgParams.Append(param)
end if
m.top.posterURL = ImageURL(datum.id, "Primary", imgParams)
' For wide image, use backdrop
imgParams["maxWidth"] = 464
if datum.ImageTags <> invalid and datum.imageTags.Thumb <> invalid
imgParams["Tag"] = datum.imageTags.Thumb
m.top.thumbnailUrl = ImageURL(datum.Id, "Thumb", imgParams)
else if datum.BackdropImageTags[0] <> invalid
imgParams["Tag"] = datum.BackdropImageTags[0]
m.top.thumbnailUrl = ImageURL(datum.id, "Backdrop", imgParams)
end if
@ -84,24 +97,31 @@ sub setData()
imgParams.Append({ "maxHeight": 261 })
imgParams.Append({ "maxWidth": 175 })
if datum.ImageTags.Primary <> invalid
param = { "Tag": datum.ImageTags.Primary }
imgParams.Append(param)
end if
m.top.posterURL = ImageURL(datum.id, "Primary", imgParams)
' For wide image, use backdrop
imgParams["maxWidth"] = 464
if datum.ImageTags <> invalid and datum.imageTags.Thumb <> invalid
imgParams["Tag"] = datum.imageTags.Thumb
m.top.thumbnailUrl = ImageURL(datum.Id, "Thumb", imgParams)
else if datum.BackdropImageTags[0] <> invalid
imgParams["Tag"] = datum.BackdropImageTags[0]
m.top.thumbnailUrl = ImageURL(datum.id, "Backdrop", imgParams)
end if
else if datum.type = "MusicAlbum"
params = { "maxHeight": 261, "maxWidth": 261 }
params = { "Tag": datum.ImageTags.Primary, "maxHeight": 261, "maxWidth": 261 }
m.top.thumbnailURL = ImageURL(datum.id, "Primary", params)
m.top.widePosterUrl = m.top.thumbnailURL
m.top.posterUrl = m.top.thumbnailURL
else if datum.type = "TvChannel" or datum.type = "Channel"
params = { "maxHeight": 261, "maxWidth": 464 }
params = { "Tag": datum.ImageTags.Primary, "maxHeight": 261, "maxWidth": 464 }
m.top.thumbnailURL = ImageURL(datum.id, "Primary", params)
m.top.widePosterUrl = m.top.thumbnailURL
m.top.iconUrl = "pkg:/images/media_type_icons/live_tv_white.png"

View File

@ -40,20 +40,20 @@ sub setPoster()
else
if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
else if m.top.json.BackdropImageTags[0] <> invalid
imgParams = { "maxHeight": 440 }
imgParams = { "maxHeight": 440, "Tag": m.top.json.BackdropImageTags[0] }
m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
else if m.top.json.ParentThumbImageTag <> invalid and m.top.json.ParentThumbItemId <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ParentThumbImageTag }
m.top.posterURL = ImageURL(m.top.json.ParentThumbItemId, "Thumb", imgParams)
end if
' Add Backdrop Image
if m.top.json.BackdropImageTags <> invalid
if m.top.json.BackdropImageTags[0] <> invalid
imgParams = { "maxHeight": 720, "maxWidth": 1280 }
imgParams = { "maxHeight": 720, "maxWidth": 1280, "Tag": m.top.json.BackdropImageTags[0] }
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if
end if

View File

@ -12,19 +12,19 @@ sub setPoster()
else
if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
else if m.top.json.BackdropImageTags[0] <> invalid
imgParams = { "maxHeight": 440 }
imgParams = { "maxHeight": 440, "Tag": m.top.json.BackdropImageTags[0] }
m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
else if m.top.json.ParentThumbImageTag <> invalid and m.top.json.ParentThumbItemId <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ParentThumbImageTag }
m.top.posterURL = ImageURL(m.top.json.ParentThumbItemId, "Thumb", imgParams)
end if
' Add Backdrop Image
if m.top.json.BackdropImageTags[0] <> invalid
imgParams = { "maxHeight": 720, "maxWidth": 1280 }
imgParams = { "maxHeight": 720, "maxWidth": 1280, "Tag": m.top.json.BackdropImageTags[0] }
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if

View File

@ -14,19 +14,19 @@ sub setPoster()
else
if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
else if m.top.json.BackdropImageTags[0] <> invalid
imgParams = { "maxHeight": 440 }
imgParams = { "maxHeight": 440, "Tag": m.top.json.BackdropImageTags[0] }
m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
else if m.top.json.ParentThumbImageTag <> invalid and m.top.json.ParentThumbItemId <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ParentThumbImageTag }
m.top.posterURL = ImageURL(m.top.json.ParentThumbItemId, "Thumb", imgParams)
end if
' Add Backdrop Image
if m.top.json.BackdropImageTags[0] <> invalid
imgParams = { "maxHeight": 720, "maxWidth": 1280 }
imgParams = { "maxHeight": 720, "maxWidth": 1280, "Tag": m.top.json.BackdropImageTags[0] }
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if

View File

@ -36,7 +36,7 @@ sub setPoster()
m.top.posterURL = m.top.image.url
else
if m.top.json.ImageTags <> invalid and m.top.json.ImageTags.Thumb <> invalid
imgParams = { "maxHeight": 500, "maxWidth": 500 }
imgParams = { "maxHeight": 500, "maxWidth": 500, "Tag": m.top.json.ImageTags.Thumb }
m.top.posterURL = ImageURL(m.top.json.id, "Thumb", imgParams)
end if
end if

View File

@ -32,16 +32,16 @@ sub setPoster()
if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
else if m.top.json.BackdropImageTags <> invalid
imgParams = { "maxHeight": 440 }
imgParams = { "maxHeight": 440, "Tag": m.top.json.BackdropImageTags[0] }
m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if
' Add Backdrop Image
if m.top.json.BackdropImageTags <> invalid
imgParams = { "maxHeight": 720, "maxWidth": 1280 }
imgParams = { "maxHeight": 720, "maxWidth": 1280, "Tag": m.top.json.BackdropImageTags[0] }
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if

View File

@ -15,7 +15,7 @@ sub setPoster()
if m.top.image <> invalid
m.top.posterURL = m.top.image.url
else if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295 }
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
end if
end sub

View File

@ -16,4 +16,5 @@
<script type="text/brightscript" uri="Home.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/utils/misc.brs" />
</component>

View File

@ -7,6 +7,8 @@ sub init()
m.itemIcon = m.top.findNode("itemIcon")
m.itemTextExtra = m.top.findNode("itemTextExtra")
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
m.unplayedCount = m.top.findNode("unplayedCount")
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
m.showProgressBarAnimation = m.top.findNode("showProgressBar")
m.showProgressBarField = m.top.findNode("showProgressBarField")
@ -24,19 +26,26 @@ sub itemContentChanged()
if itemData = invalid then return
itemData.Title = itemData.name ' Temporarily required while we move from "HomeItem" to "JFContentItem"
m.itemPoster.width = itemData.imageWidth
m.itemText.maxWidth = itemData.imageWidth
m.itemTextExtra.width = itemData.imageWidth
m.itemTextExtra.visible = true
m.backdrop.width = itemData.imageWidth
if itemData.iconUrl <> invalid
m.itemIcon.uri = itemData.iconUrl
end if
if LCase(itemData.type) = "series"
if itemData?.json?.UserData?.UnplayedItemCount <> invalid
if itemData.json.UserData.UnplayedItemCount > 0
m.unplayedCount.visible = true
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
end if
end if
end if
' Format the Data based on the type of Home Data
if itemData.type = "CollectionFolder" or itemData.type = "UserView" or itemData.type = "Channel"
m.itemText.text = itemData.name
@ -64,8 +73,12 @@ sub itemContentChanged()
' "Program" is from clicking on an "On Now" item on the Home Screen
if itemData.type = "Program"
m.itemText.Text = itemData.json.name
if itemData.json.ImageURL <> invalid
m.itemPoster.uri = itemData.json.ImageURL
m.itemTextExtra.Text = itemData.json.ChannelName
if itemData.widePosterURL <> ""
m.itemPoster.uri = ImageURL(itemData.widePosterURL)
else
m.itemPoster.uri = ImageURL(itemData.json.ChannelId)
m.itemPoster.loadDisplayMode = "scaleToFill"
end if
' Set Episode title if available

View File

@ -3,12 +3,14 @@
<children>
<Rectangle id="backdrop" width="464" height="261" translation="[8,5]" />
<Poster id="itemIcon" width="100" height="100" translation="[190,85]" loadDisplayMode="scaleToFit" />
<Poster id="itemPoster" width="464" height="261" translation="[8,5]" loadDisplayMode="scaleToZoom" />
<Poster id="itemPoster" width="464" height="261" translation="[8,5]" loadDisplayMode="scaleToZoom">
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[375, 0]">
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:SmallestBoldSystemFont" horizAlign="center" vertAlign="center" />
</Rectangle>
</Poster>
<Rectangle id="progressBackground" visible="false" color="0x00000098" width="464" height="8" translation="[8,260]">
<Rectangle id="progress" color="#00a4dcFF" width="0" height="8" />
</Rectangle>
<ScrollingLabel id="itemText" horizAlign="center" vertAlign="center" font="font:SmallBoldSystemFont" height="64" maxWidth="456" translation="[8,267]" repeatCount="0" />
<Label id="itemTextExtra" horizAlign="left" vertAlign="center" font="font:SmallBoldSystemFont" height="32" width="456" translation="[8,300]" visible="false" color="#777777FF" />

View File

@ -61,10 +61,13 @@ sub onLibrariesLoaded()
m.LoadLibrariesTask.content = []
' create My Media, Continue Watching, and Next Up rows
content = CreateObject("roSGNode", "ContentNode")
mediaRow = content.CreateChild("HomeRow")
mediaRow.title = tr("My Media")
continueRow = content.CreateChild("HomeRow")
continueRow.title = tr("Continue Watching")
nextUpRow = content.CreateChild("HomeRow")
nextUpRow.title = tr("Next Up >")
@ -79,6 +82,19 @@ sub onLibrariesLoaded()
]
haveLiveTV = false
' Load the NextUp Data
m.LoadNextUpTask.observeField("content", "updateNextUpItems")
m.LoadNextUpTask.control = "RUN"
' Load the Continue Watching Data
m.LoadContinueTask.observeField("content", "updateContinueItems")
m.LoadContinueTask.control = "RUN"
' Load the Favorites Data
m.LoadFavoritesTask.observeField("content", "updateFavoritesItems")
m.LoadFavoritesTask.control = "RUN"
' validate library data
if m.libraryData <> invalid and m.libraryData.count() > 0
userConfig = m.top.userConfig
@ -92,36 +108,38 @@ sub onLibrariesLoaded()
' create a "Latest In" row for each library
filteredLatest = filterNodeArray(m.libraryData, "id", userConfig.LatestItemsExcludes)
for each lib in filteredLatest
if lib.collectionType <> "boxsets" and lib.collectionType <> "livetv"
if lib.collectionType <> "boxsets" and lib.collectionType <> "livetv" and lib.json.CollectionType <> "Program"
latestInRow = content.CreateChild("HomeRow")
latestInRow.title = tr("Latest in") + " " + lib.name + " >"
sizeArray.Push([464, 331])
loadLatest = createObject("roSGNode", "LoadItemsTask")
loadLatest.itemsToLoad = "latest"
loadLatest.itemId = lib.id
metadata = { "title": lib.name }
metadata.Append({ "contentType": lib.json.CollectionType })
loadLatest.metadata = metadata
loadLatest.observeField("content", "updateLatestItems")
loadLatest.control = "RUN"
else if lib.collectionType = "livetv"
' If we have Live TV, add "On Now"
onNowRow = content.CreateChild("HomeRow")
onNowRow.title = tr("On Now")
sizeArray.Push([464, 331])
haveLiveTV = true
' If we have Live TV access, load "On Now" data
if haveLiveTV
m.LoadOnNowTask.observeField("content", "updateOnNowItems")
m.LoadOnNowTask.control = "RUN"
end if
end if
end for
end if
m.top.rowItemSize = sizeArray
m.top.content = content
' Load the Continue Watching Data
m.LoadContinueTask.observeField("content", "updateContinueItems")
m.LoadContinueTask.control = "RUN"
' Load the Favorites Data
m.LoadFavoritesTask.observeField("content", "updateFavoritesItems")
m.LoadFavoritesTask.control = "RUN"
' If we have Live TV access, load "On Now" data
if haveLiveTV
m.LoadOnNowTask.observeField("content", "updateOnNowItems")
m.LoadOnNowTask.control = "RUN"
end if
end sub
sub updateHomeRows()
@ -219,9 +237,6 @@ sub updateContinueItems()
homeRows.replaceChild(row, continueRowIndex)
end if
end if
m.LoadNextUpTask.observeField("content", "updateNextUpItems")
m.LoadNextUpTask.control = "RUN"
end sub
sub updateNextUpItems()
@ -274,24 +289,6 @@ sub updateNextUpItems()
m.global.app_loaded = true
end if
' create task nodes for "Latest In" rows
userConfig = m.top.userConfig
filteredLatest = filterNodeArray(m.libraryData, "id", userConfig.LatestItemsExcludes)
for each lib in filteredLatest
if lib.collectionType <> "livetv" and lib.collectionType <> "boxsets" and lib.json.CollectionType <> "Program"
loadLatest = createObject("roSGNode", "LoadItemsTask")
loadLatest.itemsToLoad = "latest"
loadLatest.itemId = lib.id
metadata = { "title": lib.name }
metadata.Append({ "contentType": lib.json.CollectionType })
loadLatest.metadata = metadata
loadLatest.observeField("content", "updateLatestItems")
loadLatest.control = "RUN"
end if
end for
end sub
sub updateLatestItems(msg)

View File

@ -30,6 +30,7 @@ sub loadItems()
params["ParentId"] = m.top.itemId
params["EnableImageTypes"] = "Primary,Backdrop,Thumb"
params["ImageTypeLimit"] = 1
params["EnableTotalRecordCount"] = false
resp = APIRequest(url, params)
data = getJson(resp)
@ -53,6 +54,10 @@ sub loadItems()
params["SortOrder"] = "Descending"
params["ImageTypeLimit"] = 1
params["UserId"] = get_setting("active_user")
params["EnableRewatching"] = false
params["DisableFirstEpisode"] = false
params["limit"] = 24
params["EnableTotalRecordCount"] = false
maxDaysInNextUp = get_user_setting("ui.details.maxdaysnextup", "365")
if isValid(maxDaysInNextUp)
@ -64,9 +69,6 @@ sub loadItems()
dateCutoff.FromSeconds(dateToday.AsSeconds() - (maxDaysInNextUp * 86400))
params["NextUpDateCutoff"] = dateCutoff.ToISOString()
params["EnableRewatching"] = false
params["DisableFirstEpisode"] = false
params["limit"] = 24
end if
end if
@ -88,6 +90,7 @@ sub loadItems()
params["SortBy"] = "DatePlayed"
params["SortOrder"] = "Descending"
params["Filters"] = "IsResumable"
params["EnableTotalRecordCount"] = false
resp = APIRequest(url, params)
data = getJson(resp)
@ -109,6 +112,7 @@ sub loadItems()
params["Limit"] = 20
params["recursive"] = true
params["sortby"] = "random"
params["EnableTotalRecordCount"] = false
resp = APIRequest(url, params)
data = getJson(resp)

View File

@ -243,8 +243,8 @@ function getRelativeDayName(date) as string
end if
' Check for Yesterday
todayMidnight = now.AsSeconds() - (now.AsSeconds() MOD 86400)
dateMidnight = date.AsSeconds() - (date.AsSeconds() MOD 86400)
todayMidnight = now.AsSeconds() - (now.AsSeconds() mod 86400)
dateMidnight = date.AsSeconds() - (date.AsSeconds() mod 86400)
if todayMidnight - dateMidnight = 86400
return "yesterday"
@ -266,8 +266,8 @@ function getDurationStringFromSeconds(seconds) as string
minutes = seconds / 60.0
if minutes > 60
hours = (minutes - (minutes MOD 60)) / 60
minutes = minutes MOD 60
hours = (minutes - (minutes mod 60)) / 60
minutes = minutes mod 60
end if
if hours > 0

View File

@ -0,0 +1,129 @@
sub init()
m.queue = []
m.position = 0
end sub
'
' Clear all content from play queue
sub clear()
m.queue = []
setPosition(0)
end sub
'
' Delete item from play queue at passed index
sub deleteAtIndex(index)
m.queue.Delete(index)
end sub
'
' Return the number of items in the play queue
function getCount()
return m.queue.count()
end function
'
' Return the item currently in focus from the play queue
function getCurrentItem()
return getItemByIndex(m.position)
end function
'
' Return the item in the passed index from the play queue
function getItemByIndex(index)
return m.queue[index]
end function
'
' Returns current playback position within the queue
function getPosition()
return m.position
end function
'
' Move queue position back one
sub moveBack()
m.position--
end sub
'
' Move queue position ahead one
sub moveForward()
m.position++
end sub
'
' Return the current play queue
function getQueue()
return m.queue
end function
'
' Return item at end of play queue without removing
function peek()
return m.queue.peek()
end function
'
' Play items in queue
sub playQueue()
nextItem = top()
nextItemMediaType = invalid
if isValid(nextItem?.json?.mediatype) and nextItem.json.mediatype <> ""
nextItemMediaType = LCase(nextItem.json.mediatype)
else if isValid(nextItem?.type) and nextItem.type <> ""
nextItemMediaType = LCase(nextItem.type)
end if
if not isValid(nextItemMediaType) then return
if nextItemMediaType = "audio"
CreateAudioPlayerView()
end if
end sub
'
' Remove item at end of play queue
sub pop()
m.queue.pop()
end sub
'
' Push new items to the play queue
sub push(newItem)
m.queue.push(newItem)
end sub
'
' Set the queue position
sub setPosition(newPosition)
m.position = newPosition
end sub
'
' Return the fitst item in the play queue
function top()
return getItemByIndex(0)
end function
'
' Replace play queue with passed array
sub set(items)
setPosition(0)
m.queue = items
end sub

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="QueueManager" extends="Group">
<interface>
<function name="clear" />
<function name="deleteAtIndex" />
<function name="getCount" />
<function name="getCurrentItem" />
<function name="getItemByIndex" />
<function name="getPosition" />
<function name="getQueue" />
<function name="moveBack" />
<function name="moveForward" />
<function name="peek" />
<function name="playQueue" />
<function name="pop" />
<function name="push" />
<function name="set" />
<function name="setPosition" />
<function name="top" />
</interface>
<script type="text/brightscript" uri="QueueManager.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="ViewCreator.brs" />
</component>

View File

@ -0,0 +1,6 @@
' Play Audio
sub CreateAudioPlayerView()
view = CreateObject("roSGNode", "AudioPlayerView")
view.observeField("state", m.port)
m.global.sceneManager.callFunc("pushScene", view)
end sub

View File

@ -8,11 +8,8 @@ sub init()
setupDataTasks()
setupScreenSaver()
m.currentSongIndex = 0
m.buttonsNeedToBeLoaded = true
m.shuffleEnabled = false
m.loopMode = ""
m.shuffleEvent = ""
m.buttonCount = m.buttons.getChildCount()
m.playReported = false
@ -26,6 +23,9 @@ sub init()
' Write screen tracker for screensaver
WriteAsciiFile("tmp:/scene.temp", "nowplaying")
MoveFile("tmp:/scene.temp", "tmp:/scene")
loadButtons()
pageContentChanged()
end sub
sub onScreensaverTimeoutLoaded()
@ -62,7 +62,7 @@ sub setupAnimationTasks()
m.screenSaverStartAnimation = m.top.FindNode("screenSaverStartAnimation")
end sub
' Creates tasks to gather data needed to renger NowPlaying Scene and play song
' Creates tasks to gather data needed to render Scene and play song
sub setupDataTasks()
' Load meta data
m.LoadMetaDataTask = CreateObject("roSGNode", "LoadItemsTask")
@ -219,18 +219,25 @@ sub audioStateChanged()
' Song Finished, attempt to move to next song
if m.top.audio.state = "finished"
' User has enabled single song loop, play current song again
if m.loopMode = "one"
playAction()
return
else if m.loopMode = "all"
m.currentSongIndex = -1
LoadNextSong()
return
end if
if m.currentSongIndex < m.top.pageContent.count() - 1
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
' We are not at the end of the song queue, advance to next song
LoadNextSong()
else
' We are at the end of the song queue
' User has enabled loop for entire song queue, move back to first song
if m.loopMode = "all"
m.global.queueManager.callFunc("setPosition", -1)
LoadNextSong()
return
end if
' Return to previous screen
m.top.state = "finished"
end if
@ -263,8 +270,8 @@ function previousClicked() as boolean
m.top.audio.control = "stop"
end if
if m.currentSongIndex > 0
m.currentSongIndex--
if m.global.queueManager.callFunc("getPosition") > 0
m.global.queueManager.callFunc("moveBack")
pageContentChanged()
end if
@ -289,7 +296,7 @@ function loopClicked() as boolean
end function
function nextClicked() as boolean
if m.currentSongIndex < m.top.pageContent.count() - 1
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
LoadNextSong()
end if
@ -302,7 +309,7 @@ end sub
function findCurrentSongIndex(songList) as integer
for i = 0 to songList.count() - 1
if songList[i] = m.top.pageContent[m.currentSongIndex]
if songList[i].id = m.global.queueManager.callFunc("getCurrentItem").id
return i
end if
end for
@ -318,10 +325,10 @@ function shuffleClicked() as boolean
m.shuffleIndicator.opacity = ".4"
m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-on", "-off")
m.shuffleEvent = "enabled"
m.currentSongIndex = findCurrentSongIndex(m.originalSongList)
m.top.pageContent = m.originalSongList
setFieldTextValue("numberofsongs", "Track " + stri(m.currentSongIndex + 1) + "/" + stri(m.top.pageContent.count()))
currentSongIndex = findCurrentSongIndex(m.originalSongList)
m.global.queueManager.callFunc("set", m.originalSongList)
m.global.queueManager.callFunc("setPosition", currentSongIndex)
setFieldTextValue("numberofsongs", "Track " + stri(m.global.queueManager.callFunc("getPosition") + 1) + "/" + stri(m.global.queueManager.callFunc("getCount")))
return true
end if
@ -329,14 +336,14 @@ function shuffleClicked() as boolean
m.shuffleIndicator.opacity = "1"
m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-off", "-on")
m.originalSongList = m.top.pageContent
m.originalSongList = m.global.queueManager.callFunc("getQueue")
songIDArray = m.top.pageContent
songIDArray = m.global.queueManager.callFunc("getQueue")
' Move the currently playing song to the front of the queue
temp = m.top.pageContent[0]
songIDArray[0] = m.top.pageContent[m.currentSongIndex]
songIDArray[m.currentSongIndex] = temp
temp = m.global.queueManager.callFunc("top")
songIDArray[0] = m.global.queueManager.callFunc("getCurrentItem")
songIDArray[m.global.queueManager.callFunc("getPosition")] = temp
for i = 1 to songIDArray.count() - 1
j = Rnd(songIDArray.count() - 1)
@ -345,10 +352,7 @@ function shuffleClicked() as boolean
songIDArray[j] = temp
end for
m.currentSongIndex = 0
m.shuffleEvent = "enabled"
m.top.pageContent = songIDArray
m.global.queueManager.callFunc("set", songIDArray)
return true
end function
@ -360,30 +364,64 @@ sub LoadNextSong()
' Reset playPosition bar without animation
m.playPosition.width = 0
m.currentSongIndex++
m.global.queueManager.callFunc("moveForward")
pageContentChanged()
end sub
' Update values on screen when page content changes
sub pageContentChanged()
' pageContent Changed due to shuffle event, don't update screen values
if m.shuffleEvent = "enabled"
m.shuffleEvent = ""
return
end if
' Reset buffer bar without animation
m.bufferPosition.width = 0
m.LoadMetaDataTask.itemId = m.top.pageContent[m.currentSongIndex]
m.LoadMetaDataTask.observeField("content", "onMetaDataLoaded")
m.LoadMetaDataTask.control = "RUN"
useMetaTask = false
currentItem = m.global.queueManager.callFunc("getCurrentItem")
m.LoadAudioStreamTask.itemId = m.top.pageContent[m.currentSongIndex]
if not isValid(currentItem.RunTimeTicks)
useMetaTask = true
end if
if not isValid(currentItem.AlbumArtist)
useMetaTask = true
end if
if not isValid(currentItem.name)
useMetaTask = true
end if
if not isValid(currentItem.Artists)
useMetaTask = true
end if
if useMetaTask
m.LoadMetaDataTask.itemId = currentItem.id
m.LoadMetaDataTask.observeField("content", "onMetaDataLoaded")
m.LoadMetaDataTask.control = "RUN"
else
if isValid(currentItem.ParentBackdropItemId)
setBackdropImage(ImageURL(currentItem.ParentBackdropItemId, "Backdrop", { "maxHeight": "720", "maxWidth": "1280" }))
end if
setPosterImage(ImageURL(currentItem.id, "Primary", { "maxHeight": 500, "maxWidth": 500 }))
setScreenTitle(currentItem)
setOnScreenTextValues(currentItem)
m.songDuration = currentItem.RunTimeTicks / 10000000.0
end if
m.LoadAudioStreamTask.itemId = currentItem.id
m.LoadAudioStreamTask.observeField("content", "onAudioStreamLoaded")
m.LoadAudioStreamTask.control = "RUN"
end sub
' If we have more and 1 song to play, fade in the next and previous controls
sub loadButtons()
if m.global.queueManager.callFunc("getCount") > 1
m.shuffleIndicator.opacity = ".4"
m.loopIndicator.opacity = ".4"
m.displayButtonsAnimation.control = "start"
end if
end sub
sub onAudioStreamLoaded()
data = m.LoadAudioStreamTask.content[0]
m.LoadAudioStreamTask.unobserveField("content")
@ -425,16 +463,6 @@ sub onMetaDataLoaded()
setOnScreenTextValues(data.json)
m.songDuration = data.json.RunTimeTicks / 10000000.0
' If we have more and 1 song to play, fade in the next and previous controls
if m.buttonsNeedToBeLoaded
if m.top.pageContent.count() > 1
m.shuffleIndicator.opacity = ".4"
m.loopIndicator.opacity = ".4"
m.displayButtonsAnimation.control = "start"
end if
m.buttonsNeedToBeLoaded = false
end if
end if
end sub
@ -471,12 +499,12 @@ end sub
' Populate on screen text variables
sub setOnScreenTextValues(json)
if isValid(json)
currentSongIndex = m.currentSongIndex
currentSongIndex = m.global.queueManager.callFunc("getPosition")
if m.shuffleEnabled
currentSongIndex = findCurrentSongIndex(m.originalSongList)
end if
setFieldTextValue("numberofsongs", "Track " + stri(currentSongIndex + 1) + "/" + stri(m.top.pageContent.count()))
setFieldTextValue("numberofsongs", "Track " + stri(currentSongIndex + 1) + "/" + stri(m.global.queueManager.callFunc("getCount")))
setFieldTextValue("artist", json.Artists[0])
setFieldTextValue("song", json.name)
end if
@ -511,7 +539,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
else if key = "fastforward"
return nextClicked()
else if key = "left"
if m.top.pageContent.count() = 1 then return false
if m.global.queueManager.callFunc("getCount") = 1 then return false
if m.top.selectedButtonIndex > 0
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
@ -519,7 +547,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
return true
else if key = "right"
if m.top.pageContent.count() = 1 then return false
if m.global.queueManager.callFunc("getCount") = 1 then return false
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
if m.top.selectedButtonIndex < m.buttonCount - 1 then m.top.selectedButtonIndex = m.top.selectedButtonIndex + 1
@ -554,7 +582,7 @@ sub ReportPlayback(state = "update" as string)
if m.top.audio.position = invalid then return
params = {
"ItemId": m.top.pageContent[m.currentSongIndex],
"ItemId": m.global.queueManager.callFunc("getCurrentItem").id,
"PlaySessionId": m.top.audio.content.id,
"PositionTicks": int(m.top.audio.position) * 10000000&, 'Ensure a LongInteger is used
"IsPaused": (m.top.audio.state = "paused")

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="NowPlaying" extends="JFScreen">
<component name="AudioPlayerView" extends="JFScreen">
<children>
<Poster id="backdrop" opacity=".5" loadDisplayMode="scaleToZoom" width="1920" height="1200" blendColor="#3f3f3f" />
<Poster id="shuffleIndicator" width="64" height="64" uri="pkg:/images/icons/shuffleIndicator-off.png" translation="[1150,775]" opacity="0" />
@ -8,7 +8,7 @@
<LayoutGroup id="main_group" layoutDirection="vert" horizAlignment="center" itemSpacings="[15]">
<Poster id="albumCover" width="500" height="500" />
<Label id="artist" width="900" height="25" horizAlign="center" />
<Label id="song" width="900" height="25" horizAlign="center" />
<Label id="song" width="900" height="25" horizAlign="center" />
<Label id="numberofsongs" width="500" height="25" horizAlign="center" font="font:SmallestSystemFont" color="#999999" />
</LayoutGroup>
<Rectangle id="seekBar" color="0x00000099" width="500" height="10">
@ -77,7 +77,7 @@
<Vector2DFieldInterpolator id="OneInterp" key="[0.0,1.0]" keyValue="[[450,30],[960,575]]" fieldToInterp="screenSaverAlbumCover.translation" />
</Animation>
</SequentialAnimation>
<!-- Jellyfin Logo ScreenSaver -->
<SequentialAnimation id="BounceAnimation" repeat="true">
<Animation id="AnimOne" repeat="false" easeFunction="linear" duration="7.2">
@ -119,13 +119,12 @@
<Poster width="0" height="0" uri="pkg:/images/icons/loopIndicator1-on.png" visible="false" />
</children>
<interface>
<field id="pageContent" type="array" onChange="pageContentChanged" />
<field id="audio" type="node" />
<field id="state" type="string" />
<field id="selectedButtonIndex" type="integer" />
</interface>
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="NowPlaying.brs" />
<script type="text/brightscript" uri="AudioPlayerView.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />

View File

@ -13,6 +13,8 @@ sub init()
m.boolSetting = m.top.findNode("boolSetting")
m.integerSetting = m.top.findNode("integerSetting")
m.radioSetting = m.top.findNode("radioSetting")
m.integerSetting.observeField("submit", "onKeyGridSubmit")
m.integerSetting.observeField("escape", "onKeyGridEscape")
@ -21,6 +23,7 @@ sub init()
m.settingsMenu.observeField("itemSelected", "settingSelected")
m.boolSetting.observeField("checkedItem", "boolSettingChanged")
m.radioSetting.observeField("checkedItem", "radioSettingChanged")
' Load Configuration Tree
m.configTree = GetConfigTree()
@ -40,7 +43,6 @@ sub onKeyGridEscape()
end sub
sub LoadMenu(configSection)
if configSection.children = invalid
' Load parent menu
m.userLocation.pop()
@ -81,6 +83,7 @@ sub settingFocused()
' Hide Settings
m.boolSetting.visible = false
m.integerSetting.visible = false
m.radioSetting.visible = false
if selectedSetting.type = invalid
return
@ -99,6 +102,26 @@ sub settingFocused()
m.integerSetting.text = integerValue
end if
m.integerSetting.visible = true
else if LCase(selectedSetting.type) = "radio"
selectedValue = get_user_setting(selectedSetting.settingName, selectedSetting.default)
radioContent = CreateObject("roSGNode", "ContentNode")
itemIndex = 0
for each item in m.userLocation.peek().children[m.settingsMenu.itemFocused].options
listItem = radioContent.CreateChild("ContentNode")
listItem.title = tr(item.title)
listItem.id = item.id
if selectedValue = item.id
m.radioSetting.checkedItem = itemIndex
end if
itemIndex++
end for
m.radioSetting.content = radioContent
m.radioSetting.visible = true
else
print "Unknown setting type " + selectedSetting.type
end if
@ -117,6 +140,9 @@ sub settingSelected()
if selectedItem.type = "integer"
m.integerSetting.setFocus(true)
end if
if (selectedItem.type) = "radio"
m.radioSetting.setFocus(true)
end if
else if selectedItem.children <> invalid and selectedItem.children.Count() > 0 ' Show sub menu
LoadMenu(selectedItem)
m.settingsMenu.setFocus(true)
@ -139,9 +165,13 @@ sub boolSettingChanged()
else
set_user_setting(selectedSetting.settingName, "false")
end if
end sub
sub radioSettingChanged()
if m.radioSetting.focusedChild = invalid then return
selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused]
set_user_setting(selectedSetting.settingName, m.radioSetting.content.getChild(m.radioSetting.checkedItem).id)
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
@ -152,6 +182,9 @@ function onKeyEvent(key as string, press as boolean) as boolean
else if (key = "back" or key = "left") and m.settingDetail.focusedChild <> invalid
m.settingsMenu.setFocus(true)
return true
else if (key = "back" or key = "left") and m.radioSetting.hasFocus()
m.settingsMenu.setFocus(true)
return true
end if
if key = "right"

View File

@ -31,6 +31,7 @@
</RadioButtonList>
</LayoutGroup>
<RadioButtonList id="radioSetting" translation="[900, 450]" inheritParentTransform="false" vertFocusAnimationStyle="floatingFocus" />
<intkeyboard_integerKeyboard translation="[900, 520]" id="integerSetting" maxLength="3" domain="numeric" visible="false" />
</children>

View File

@ -6,6 +6,8 @@ sub init()
m.Random = m.top.findNode("Random")
m.tvEpisodeRow = m.top.findNode("tvEpisodeRow")
m.unplayedCount = m.top.findNode("unplayedCount")
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
m.rows.observeField("doneLoading", "updateSeason")
end sub
@ -15,6 +17,13 @@ sub setSeasonLoading()
end sub
sub updateSeason()
if m.top.seasonData?.UserData?.UnplayedItemCount <> invalid
if m.top.seasonData.UserData.UnplayedItemCount > 0
m.unplayedCount.visible = true
m.unplayedEpisodeCount.text = m.top.seasonData.UserData.UnplayedItemCount
end if
end if
imgParams = { "maxHeight": 450, "maxWidth": 300 }
m.poster.uri = ImageURL(m.top.seasonData.Id, "Primary", imgParams)
m.Random.visible = true
@ -54,8 +63,10 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
if press and key = "play" or proceed = true
m.top.lastFocus = focusedChild
itemToPlay = focusedChild.content.getChild(focusedChild.rowItemFocused[0]).getChild(0)
if itemToPlay <> invalid and itemToPlay.id <> ""
itemToPlay.type = "Episode"
m.top.quickPlayNode = itemToPlay
end if
handled = true

View File

@ -1,7 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="TVEpisodes" extends="JFGroup">
<children>
<Poster id="seasonPoster" width="300" height="450" translation="[95,175]" />
<Poster id="seasonPoster" width="300" height="450" translation="[95,175]">
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[210, 0]">
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:SmallestBoldSystemFont" horizAlign="center" vertAlign="center" />
</Rectangle>
</Poster>
<JFButton id="Random" text="Play Random" translation="[90, 640]" visible="false"></JFButton>
<TVEpisodeRowWithOptions id="picker" visible="true" />
</children>

View File

@ -8,6 +8,11 @@ sub init()
m.rating = m.top.findnode("rating")
m.infoBar = m.top.findnode("infoBar")
m.progressBackground = m.top.findNode("progressBackground")
m.progressBar = m.top.findnode("progressBar")
m.playedIndicator = m.top.findNode("playedIndicator")
m.checkmark = m.top.findNode("checkmark")
m.checkmark.font.size = 35
end sub
sub itemContentChanged()
@ -52,11 +57,30 @@ sub itemContentChanged()
end if
end if
if isValid(itemData.communityRating)
m.top.findNode("star").visible = true
m.top.findNode("communityRating").text = str(int(itemData.communityRating * 10) / 10)
if get_user_setting("ui.tvshows.disableCommunityRating") = "false"
if isValid(itemData.communityRating)
m.top.findNode("star").visible = true
m.top.findNode("communityRating").text = str(int(itemData.communityRating * 10) / 10)
else
m.top.findNode("star").visible = false
end if
else
m.top.findNode("star").visible = false
m.rating.visible = false
m.infoBar.itemSpacings = [20, -25, 20, 20]
end if
' Add checkmark in corner (if applicable)
if isValid(itemData?.UserData?.Played) and itemData.UserData.Played = true
m.playedIndicator.visible = true
end if
' Add progress bar on bottom (if applicable)
if isValid(itemData?.UserData?.PlayedPercentage) and itemData?.UserData?.PlayedPercentage > 0
m.progressBackground.width = m.poster.width
m.progressBackground.visible = true
progressWidthInPixels = int(m.progressBackground.width * itemData.UserData.PlayedPercentage / 100)
m.progressBar.width = progressWidthInPixels
m.progressBar.visible = true
end if
videoIdx = invalid

View File

@ -2,8 +2,15 @@
<component name="TVListDetails" extends="Group">
<children>
<LayoutGroup id="toplevel" layoutDirection="vert" itemSpacings="[40]">
<LayoutGroup id="main_group" layoutDirection="horiz" itemSpacings="[30]">
<Poster id="poster" width="350" height="300" />
<LayoutGroup id="main_group" layoutDirection="horiz" itemSpacings="[30]">
<Poster id="poster" width="350" height="300" loadDisplayMode="scaleToZoom">
<Rectangle id="playedIndicator" color="#00a4dcFF" width="60" height="46" visible="false" translation="[290, 0]">
<Label id="checkmark" width="60" height="42" font="font:SmallestBoldSystemFont" horizAlign="center" vertAlign="bottom" text="✓"/>
</Rectangle>
<Rectangle id="progressBackground" visible="false" color="0x00000098" width="350" height="16" translation="[0,286]">
<Rectangle id="progressBar" color="#00a4dcFF" width="0" height="16" visible="false"/>
</Rectangle>
</Poster>
<LayoutGroup id="text" layoutDirection="vert" itemSpacings="[15]">
<!-- Using poster of 1 length to get spacing. Not successful with adding translation to title -->
<Poster id="null" height="1" />

View File

@ -3,6 +3,8 @@ sub init()
main = m.top.findNode("toplevel")
main.translation = [96, 175]
m.extrasSlider = m.top.findNode("tvSeasonExtras")
m.unplayedCount = m.top.findNode("unplayedCount")
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
'm.extrasSlider.translation = [30,1014]
m.extrasSlider.visible = true
end sub
@ -13,6 +15,13 @@ sub itemContentChanged()
item = m.top.itemContent
itemData = item.json
if itemData?.UserData?.UnplayedItemCount <> invalid
if itemData.UserData.UnplayedItemCount > 0
m.unplayedCount.visible = true
m.unplayedEpisodeCount.text = itemData.UserData.UnplayedItemCount
end if
end if
m.top.findNode("tvshowPoster").uri = m.top.itemContent.posterURL
' Handle all "As Is" fields

View File

@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="TVShowDetails" extends="JFGroup">
<children>
<LayoutGroup id="toplevel" layoutDirection="vert" itemSpacings="[-10]" >
<LayoutGroup id="main_group" layoutDirection="horiz" itemSpacings="[15]" >
<Poster id="tvshowPoster" width="300" height="450" />
<LayoutGroup id="toplevel" layoutDirection="vert" itemSpacings="[-10]">
<LayoutGroup id="main_group" layoutDirection="horiz" itemSpacings="[15]">
<Poster id="tvshowPoster" width="300" height="450">
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[210, 0]">
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:SmallestBoldSystemFont" horizAlign="center" vertAlign="center" />
</Rectangle>
</Poster>
<LayoutGroup layoutDirection="vert" itemSpacings="[15]">
<LayoutGroup layoutDirection="horiz" itemSpacings="[150]">
<Label id="releaseYear" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -3656,5 +3656,737 @@
<source>Save Credentials?</source>
<translation>Uložit přihlašovací údaje?</translation>
</message>
<message>
<source>Unable to find any albums or songs belonging to this artist</source>
<translation>K tomuto interpretovi nebyly nalezeny žádné skladby ani alba</translation>
<extracomment>Popup message when we find no audio data for an artist</extracomment>
</message>
<message>
<source>Text Subtitles Only</source>
<translation>Pouze textové titulky</translation>
<extracomment>Name of a setting - should we hide subtitles that might transcode</extracomment>
</message>
<message>
<source>Only display text subtitles to minimize transcoding.</source>
<translation>Zobrazit pouze textové titulky pro minimalizaci překódování.</translation>
<extracomment>Description of a setting - should we hide subtitles that might transcode</extracomment>
</message>
<message>
<source>Aired</source>
<translation>Vysíláno</translation>
<extracomment>Aired date label</extracomment>
</message>
<message>
<source>Audio Channels</source>
<translation>Audio kanály</translation>
</message>
<message>
<source>Level</source>
<translation>Úroveň</translation>
<extracomment>Video profile level</extracomment>
</message>
<message>
<source>Bit Rate</source>
<translation>Datový tok</translation>
<extracomment>Video streaming bit rate</extracomment>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Název</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Hodnocení IMDb</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Datum přehrání</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Délka</translation>
</message>
<message>
<source>Ended at</source>
<translation>Skončilo v</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Error Getting Playback Information</source>
<translation>Nepodařilo se získat informace o přehrávání</translation>
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
</message>
<message>
<source>Go to season</source>
<translation>Přejít k sérii</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</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>Quick Connect</source>
<translation>Rychle připojit</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>(Dialog will close automatically)</source>
<translation>(Dialog se automaticky zavře)</translation>
</message>
<message>
<source>all</source>
<translation>vše</translation>
<extracomment>all will reset the searchTerm so all data will be availible</extracomment>
</message>
<message>
<source>Slideshow On</source>
<translation>Slideshow zapnuto</translation>
</message>
<message>
<source>Random On</source>
<translation>Náhodné zapnout</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>Hides tagline text on details pages.</source>
<translation>Skrýt text sloganu na stránce s podrobnostmi.</translation>
</message>
<message>
<source>Blur Unwatched Episodes</source>
<translation>Rozmazat neshlédnuté epizody</translation>
<extracomment>Option Title in user setting screen</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>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>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Skrýt všechny hodiny v Jellyfinu. Aby se změna projevila, je potřeba zavřít a znovu otevřít Jellyfin.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Enter the server name or IP address</source>
<translation>Zadejte název serveru nebo IP adresu</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Tato %1 neobsahuje žádné položky</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Datum přidání</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Rodičovské hodnocení</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Pohled</translation>
</message>
<message>
<source>Born</source>
<translation>Narozen</translation>
</message>
<message>
<source>Died</source>
<translation>Úmrtí</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Obsazení &amp; štáb</translation>
</message>
<message>
<source>Tuesday</source>
<translation>Úterý</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>Při přehrávání této položky došlo k chybě. Server neposkytl požadovaná data pro překódování.</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>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</source>
<translation>**EXPERIMENTÁLNÍ** Podpora přímého přehrávání obsahu AV1, pokud to toto zařízení Roku podporuje.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>direct</source>
<translation>přímý</translation>
</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>Vždy zobrazovat názvy pod obrázky plakátů. (Pokud je zakázáno, název se zobrazí pouze pod zvýrazněnou položkou).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Hide Clock</source>
<translation>Skrýt hodiny</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.</source>
<translation>Dříve než selže překódování pokusit se o přímé přehrání H.264 média s nepodporovanými úrovněmi profilu.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Save Credentials?</source>
<translation>Uložit přihlašovací údaje?</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Datum vydání</translation>
</message>
<message>
<source>Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.</source>
<translation>Dříve než selže překódování pokusit se o přímé přehrání HEVC média s nepodporovanými úrovněmi profilu.</translation>
<extracomment>Settings Menu - Description for option</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>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>Slideshow Off</source>
<translation>Slideshow vypnuto</translation>
</message>
<message>
<source>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</source>
<translation>Podpora přímého přehrávání obsahu MPEG-4. Toto může být nutné deaktivovat pro přehrávání videí kódovaných v DIVX.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>MPEG-4 Support</source>
<translation>Podpora MPEG-4</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</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>Cinema Mode</source>
<translation>Režim Cinema</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Use Splashscreen as Home Background</source>
<translation>Použít Splashscreen jako pozadí domovské stránky</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Honocení kritiků</translation>
</message>
<message>
<source>More Like This</source>
<translation>Více podobných</translation>
</message>
<message>
<source>Record</source>
<translation>Nahrávat</translation>
</message>
<message>
<source>Playback</source>
<translation>Přehrávání</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
<message>
<source>User Interface</source>
<translation>Uživatelské prostředí</translation>
<extracomment>Title for User Interface section in user setting screen.</extracomment>
</message>
<message>
<source>Enabled</source>
<translation>Povoleno</translation>
</message>
<message>
<source>Studios</source>
<translation>Studia</translation>
</message>
<message>
<source>Age</source>
<translation>Věk</translation>
</message>
<message>
<source>On Now</source>
<translation>Nyní</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Smazat uložené</translation>
</message>
<message>
<source>Networks</source>
<translation>Sítě</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Počet přehrání</translation>
</message>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Pro zavření stiskni &apos;OK&apos;</translation>
</message>
<message>
<source>Special Features</source>
<translation>Speciální funkce</translation>
</message>
<message>
<source>Additional Parts</source>
<translation>Další části</translation>
<extracomment>Additional parts of a video</extracomment>
</message>
<message>
<source>Movies</source>
<translation>FIlmy</translation>
</message>
<message>
<source>Started</source>
<translation>Začalo</translation>
<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>Začíná v</translation>
<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>Začíná</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>Channels</source>
<translation>Kanály</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Programový průvodce</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Record Series</source>
<translation>Nahrávat řady</translation>
</message>
<message>
<source>Not found</source>
<translation>Nenalezeno</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Unknown</source>
<translation>Neznámý</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Vyberte server Jellyfin z místní sítě:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>MPEG-2 Support</source>
<translation>Podpora MPEG-2</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</source>
<translation>Podpora přímého přehrávání obsahu MPEG-2 (např. živé TV). To zabrání překódování obsahu MPEG-2, ale využívá podstatně větší šířku pásma.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>AV1 Support</source>
<translation>Podpora AV1</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Media Grid</source>
<translation>Mřížka s médii</translation>
<extracomment>UI -&gt; Media Grid section in user setting screen.</extracomment>
</message>
<message>
<source>Item Titles</source>
<translation>Názvy položek</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Media Grid options.</source>
<translation>Volby pro mřížku s médii.</translation>
</message>
<message>
<source>Item Count</source>
<translation>Počet položek</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment>
</message>
<message>
<source>Show item count in the library and index of selected item.</source>
<translation>Zobrazit počet položek v knihovně a ukazatel označených položek.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Set Watched</source>
<translation>Označit jako shlédnuté</translation>
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
</message>
<message>
<source>Set Favorite</source>
<translation>Nastavit jako oblíbené</translation>
<extracomment>Button Text - When pressed, sets item as Favorite</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>
<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>Search now</source>
<translation>Hledat teď</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>There was an error authenticating via Quick Connect.</source>
<translation>Při ověřování přes rychlé připojení se vyskytla chyba.</translation>
</message>
<message>
<source>Shows</source>
<translation>Pořady</translation>
</message>
<message>
<source>Return to Top</source>
<translation>Návrat nahoru</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item 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>Hide Taglines</source>
<translation>Skrýt slogany</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Options for TV Shows.</source>
<translation>Předvolby pro TV pořady.</translation>
<extracomment>Description for TV Shows user settings.</extracomment>
</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>Screensaver</source>
<translation>Spořič obrazovky</translation>
</message>
<message>
<source>Options for Jellyfin&apos;s screensaver.</source>
<translation>Možnosti spořiče obrazovky Jellyfin.</translation>
<extracomment>Description for Screensaver user settings.</extracomment>
</message>
<message>
<source>Use Splashscreen as Screensaver Background</source>
<translation>Použít Splashscreen jako pozadí spořiče obrazovky</translation>
<extracomment>Option Title in user 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>Design Elements</source>
<translation>Prvky vzhledu</translation>
</message>
<message>
<source>Options that alter the design of Jellyfin.</source>
<translation>Možnosti, které mění vzhled Jellyfin.</translation>
<extracomment>Description for Design Elements user settings.</extracomment>
</message>
<message>
<source>Next episode</source>
<translation>Další epizoda</translation>
</message>
<message>
<source>Play Trailer</source>
<translation>Přehrát trailer</translation>
</message>
<message>
<source>Direct Play H.264 Unsupported Profile Levels</source>
<translation>Přímé přehrávání nepodporovaných úrovní profilu H.264</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Direct Play HEVC Unsupported Profile Levels</source>
<translation>Přímé přehrání nepodporovaných úrovní profilu HEVC</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</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>Settings relating to how the application looks.</source>
<translation>Nastavení související se vzhledem aplikace.</translation>
</message>
<message>
<source>Home Page</source>
<translation>Domovská stránka</translation>
</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>Total Bitrate</source>
<translation>Celkový datový tok</translation>
</message>
<message>
<source>Playback Information</source>
<translation>Informace o přehrávání</translation>
</message>
<message>
<source>Audio Codec</source>
<translation>Audio kodek</translation>
</message>
<message>
<source>Stream Information</source>
<translation>Informace o streamu</translation>
</message>
<message>
<source>Codec</source>
<translation>Kodek</translation>
</message>
<message>
<source>Codec Tag</source>
<translation>Tag kodeku</translation>
</message>
<message>
<source>Container</source>
<translation>Kontejner</translation>
<extracomment>Video streaming container</extracomment>
</message>
<message>
<source>Size</source>
<translation>Velikost</translation>
<extracomment>Video size</extracomment>
</message>
<message>
<source>Video range type</source>
<translation>Typ rozsahu videa</translation>
</message>
<message>
<source>Pixel format</source>
<translation>Formát pixelu</translation>
<extracomment>Video pixel format</extracomment>
</message>
<message>
<source>WxH</source>
<translation>ŠxV</translation>
<extracomment>Video width x height</extracomment>
</message>
<message>
<source>Slideshow Resumed</source>
<translation>Slideshow obnoveno</translation>
</message>
<message>
<source>Slideshow Paused</source>
<translation>Slideshow pozastaveno</translation>
</message>
<message>
<source>Random Off</source>
<translation>Náhodné vypnout</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>Go to series</source>
<translation>Přejít k seriálům</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Series Detail Page</extracomment>
</message>
<message>
<source>Details Page</source>
<translation>Stránka s podrobnostmi</translation>
</message>
<message>
<source>Video Codec</source>
<translation>Video kodek</translation>
</message>
<message>
<source>Version</source>
<translation>Verze</translation>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>nebo zadejte URL serveru ručně:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>Požadovaný obsah na serveru neexistuje</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Close</source>
<translation>Zavřít</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Třídit</translation>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtr</translation>
</message>
<message>
<source>Thursday</source>
<translation>Čtvrtek</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Pátek</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Live</source>
<translation>Živě</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Skončilo v</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Opakovat</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Připojování k serveru</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>Disabled</source>
<translation>Zakázáno</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Zrušit nahrávání sérií</translation>
</message>
<message>
<source>Cancel Recording</source>
<translation>Zrušit nahrávání</translation>
</message>
<message>
<source>View Channel</source>
<translation>Zobrazit kanál</translation>
</message>
<message>
<source>Sunday</source>
<translation>Neděle</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Started at</source>
<translation>Začátek v</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>Sobota</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Středa</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Pondělí</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>TV Shows</source>
<translation>TV pořady</translation>
</message>
<message>
<source>today</source>
<translation>dnes</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>včera</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>zítra</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</source>
<translation>Podpora přímého přehrávání obsahu MPEG-4. Toto může být nutné deaktivovat pro přehrávání videí kódovaných v DIVX.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>AV1</source>
<translation>AV1</translation>
<extracomment>Name of a setting - should we try to direct play experimental av1 codec</extracomment>
</message>
<message>
<source>Delete Saved</source>
<translation>Smazat uložené</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Uložit přihlašovací údaje?</translation>
</message>
</context>
</TS>

File diff suppressed because it is too large Load Diff

View File

@ -2188,5 +2188,832 @@
<source>Cancel Series Recording</source>
<translation>Cancel Series Recording</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Save Credentials?</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Delete Saved</translation>
</message>
<message>
<source>On Now</source>
<translation>On Now</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>Cast &amp; Crew</source>
<translation>Cast &amp; Crew</translation>
</message>
<message>
<source>More Like This</source>
<translation>More Like This</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Delete Saved</translation>
</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>Additional Parts</source>
<translation>Additional Parts</translation>
<extracomment>Additional parts of a video</extracomment>
</message>
<message>
<source>Died</source>
<translation>Died</translation>
</message>
<message>
<source>Born</source>
<translation>Born</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Save Credentials?</translation>
</message>
<message>
<source>On Now</source>
<translation>On Now</translation>
</message>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
<message>
<source>Special Features</source>
<translation>Special Features</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Delete Saved</translation>
</message>
<message>
<source>Special Features</source>
<translation>Special Features</translation>
</message>
<message>
<source>More Like This</source>
<translation>More Like This</translation>
</message>
<message>
<source>Age</source>
<translation>Age</translation>
</message>
<message>
<source>Additional Parts</source>
<translation>Additional Parts</translation>
<extracomment>Additional parts of a video</extracomment>
</message>
<message>
<source>Died</source>
<translation>Died</translation>
</message>
<message>
<source>Born</source>
<translation>Born</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Save Credentials?</translation>
</message>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
<message>
<source>On Now</source>
<translation>On Now</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Cast &amp; Crew</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Delete Saved</translation>
</message>
<message>
<source>On Now</source>
<translation>On Now</translation>
</message>
<message>
<source>More Like This</source>
<translation>More Like This</translation>
</message>
<message>
<source>Age</source>
<translation>Age</translation>
</message>
<message>
<source>Additional Parts</source>
<translation>Additional Parts</translation>
<extracomment>Additional parts of a video</extracomment>
</message>
<message>
<source>Died</source>
<translation>Died</translation>
</message>
<message>
<source>Born</source>
<translation>Born</translation>
</message>
<message>
<source>Special Features</source>
<translation>Special Features</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Save Credentials?</translation>
</message>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Cast &amp; Crew</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Delete Saved</translation>
</message>
<message>
<source>On Now</source>
<translation>On Now</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>Save Credentials?</source>
<translation>Save Credentials?</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Cast &amp; Crew</translation>
</message>
<message>
<source>Additional Parts</source>
<translation>Additional Parts</translation>
<extracomment>Additional parts of a video</extracomment>
</message>
<message>
<source>More Like This</source>
<translation>More Like This</translation>
</message>
<message>
<source>Special Features</source>
<translation>Special Features</translation>
</message>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation>
</message>
<message>
<source>TV Shows</source>
<translation>TV Shows</translation>
</message>
<message>
<source>View Channel</source>
<translation>View Channel</translation>
</message>
<message>
<source>Record</source>
<translation>Record</translation>
</message>
<message>
<source>Cancel Recording</source>
<translation>Cancel Recording</translation>
</message>
<message>
<source>Record Series</source>
<translation>Record Series</translation>
</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>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>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>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>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-4</source>
<translation>MPEG-4</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</source>
<translation>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>AV1</source>
<translation>AV1</translation>
<extracomment>Name of a setting - should we try to direct play experimental av1 codec</extracomment>
</message>
<message>
<source>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>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>If enabled, selecting a TV series with only one season will go straight to the episode list rather than the show details and season list.</source>
<translation>If enabled, selecting a TV series with only one season will go straight to the episode list rather than the show details and season list.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</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>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>Use Splashscreen as Screensaver Background</source>
<translation>Use Splashscreen as Screensaver Background</translation>
<extracomment>Option Title in user 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>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 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>Hide Clock</source>
<translation>Hide Clock</translation>
<extracomment>Option Title in user setting screen</extracomment>
</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>Options for Home Page.</source>
<translation>Options for Home Page.</translation>
<extracomment>Description for Home Page user settings.</extracomment>
</message>
<message>
<source>direct</source>
<translation>direct</translation>
</message>
<message>
<source>Details Page</source>
<translation>Details Page</translation>
</message>
<message>
<source>Video Codec</source>
<translation>Video Codec</translation>
</message>
<message>
<source>Level</source>
<translation>Level</translation>
<extracomment>Video profile level</extracomment>
</message>
<message>
<source>Size</source>
<translation>Size</translation>
<extracomment>Video size</extracomment>
</message>
<message>
<source>WxH</source>
<translation>WxH</translation>
<extracomment>Video width x height</extracomment>
</message>
<message>
<source>Codec Tag</source>
<translation>Codec Tag</translation>
</message>
<message>
<source>Movies (Presentation)</source>
<translation>Movies (Presentation)</translation>
</message>
<message>
<source>Movies (Grid)</source>
<translation>Movies (Grid)</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Cancel Series Recording</translation>
</message>
<message>
<source>Close</source>
<translation>Close</translation>
</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>...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>Media Grid</source>
<translation>Media Grid</translation>
<extracomment>UI -&gt; Media Grid section in user setting screen.</extracomment>
</message>
<message>
<source>Media Grid options.</source>
<translation>Media Grid options.</translation>
</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>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>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>Quick Connect</source>
<translation>Quick Connect</translation>
</message>
<message>
<source>(Dialog will close automatically)</source>
<translation>(Dialogue will close automatically)</translation>
</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>Max Days Next Up</source>
<translation>Max Days Next Up</translation>
<extracomment>Option Title in user setting screen</extracomment>
</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>Playback Information</source>
<translation>Playback Information</translation>
</message>
<message>
<source>Audio Codec</source>
<translation>Audio Codec</translation>
</message>
<message>
<source>Total Bitrate</source>
<translation>Total Bitrate</translation>
</message>
<message>
<source>Bit Rate</source>
<translation>Bit Rate</translation>
<extracomment>Video streaming bit rate</extracomment>
</message>
<message>
<source>Pixel format</source>
<translation>Pixel format</translation>
<extracomment>Video pixel format</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>
<message>
<source>Movies</source>
<translation>Films</translation>
</message>
<message>
<source>Version</source>
<translation>Version</translation>
</message>
<message>
<source>Playback</source>
<translation>Playback</translation>
<extracomment>Title for Playback 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>Item Count</source>
<translation>Item Count</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment>
</message>
<message>
<source>Set Watched</source>
<translation>Set Watched</translation>
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
</message>
<message>
<source>Here is your Quick Connect code:</source>
<translation>Here is your Quick Connect code:</translation>
</message>
<message>
<source>Networks</source>
<translation>Networks</translation>
</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>Play Trailer</source>
<translation>Play Trailer</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>Home Page</source>
<translation>Home Page</translation>
</message>
<message>
<source>Transcoding Information</source>
<translation>Transcoding Information</translation>
</message>
<message>
<source>Reason</source>
<translation>Reason</translation>
</message>
<message>
<source>Audio Channels</source>
<translation>Audio Channels</translation>
</message>
<message>
<source>Codec Support</source>
<translation>Codec Support</translation>
<extracomment>Settings Menu - Title for settings group related to codec support</extracomment>
</message>
<message>
<source>MPEG-2</source>
<translation>MPEG-2</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>Enable or disable Direct Play for optional codecs</source>
<translation>Enable or disable Direct Play for optional codecs</translation>
<extracomment>Settings Menu - Title for settings group related to codec support</extracomment>
</message>
<message>
<source>Disabled</source>
<translation>Disabled</translation>
</message>
<message>
<source>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</source>
<translation>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</translation>
<extracomment>Description of a setting - should we try to direct play experimental av1 codec</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>Set Favorite</source>
<translation>Set Favourite</translation>
<extracomment>Button Text - When pressed, sets item as Favorite</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>Go to season</source>
<translation>Go to season</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</extracomment>
</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>There was an error authenticating via Quick Connect.</source>
<translation>There was an error authenticating via Quick Connect.</translation>
</message>
<message>
<source>Studios</source>
<translation>Studios</translation>
</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>Hide Taglines</source>
<translation>Hide Taglines</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Hides tagline text on details pages.</source>
<translation>Hides tagline text on details pages.</translation>
</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>Skip Details for Single Seasons</source>
<translation>Skip Details for Single Seasons</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Blur Unwatched Episodes</source>
<translation>Blur Unwatched Episodes</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 cinema 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>Settings relating to how the application looks.</source>
<translation>Settings relating to how the application looks.</translation>
</message>
<message>
<source>Stream Information</source>
<translation>Stream Information</translation>
</message>
<message>
<source>Codec</source>
<translation>Codec</translation>
</message>
<message>
<source>Container</source>
<translation>Container</translation>
<extracomment>Video streaming container</extracomment>
</message>
<message>
<source>Video range type</source>
<translation>Video range type</translation>
</message>
<message>
<source>Cinema Mode</source>
<translation>Cinema Mode</translation>
<extracomment>Settings Menu - Title for option</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>Design Elements</source>
<translation>Design Elements</translation>
</message>
<message>
<source>Screensaver</source>
<translation>Screensaver</translation>
</message>
<message>
<source>Search now</source>
<translation>Search now</translation>
<extracomment>Help text in search 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>Shows</source>
<translation>Shows</translation>
</message>
<message>
<source>Enabled</source>
<translation>Enabled</translation>
</message>
<message>
<source>Text Subtitles Only</source>
<translation>Text Subtitles Only</translation>
<extracomment>Name of a setting - should we hide subtitles that might transcode</extracomment>
</message>
<message>
<source>Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.</source>
<translation type="unfinished">Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Next episode</source>
<translation>Next episode</translation>
</message>
<message>
<source>HEVC</source>
<translation>HEVC</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>H.264</source>
<translation type="unfinished">H.264</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.</source>
<translation>Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.</source>
<translation>Attempt Direct Play for HEVC media with unsupported profile levels before falling back to transcoding if it fails.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Only display text subtitles to minimize transcoding.</source>
<translation>Only display text subtitles to minimise transcoding.</translation>
<extracomment>Description of a setting - should we hide subtitles that might transcode</extracomment>
</message>
<message>
<source>Aired</source>
<translation>Aired</translation>
<extracomment>Aired date label</extracomment>
</message>
<message>
<source>Slideshow Off</source>
<translation>Slideshow Off</translation>
</message>
<message>
<source>Show What&apos;s New Popup</source>
<translation>Show What&apos;s New Popup</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Show What&apos;s New popup when Jellyfin is updated to a new version.</source>
<translation>Show What&apos;s New popup when Jellyfin is updated to a new version.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Unplayed</source>
<translation>Unplayed</translation>
</message>
<message>
<source>Resumable</source>
<translation>Resumable</translation>
</message>
<message>
<source>Slideshow On</source>
<translation>Slideshow On</translation>
</message>
<message>
<source>Slideshow Resumed</source>
<translation>Slideshow Resumed</translation>
</message>
<message>
<source>Random On</source>
<translation>Random On</translation>
</message>
<message>
<source>Random Off</source>
<translation>Random Off</translation>
</message>
<message>
<source>all</source>
<translation>all</translation>
<extracomment>all will reset the searchTerm so all data will be availible</extracomment>
</message>
<message>
<source>Slideshow Paused</source>
<translation>Slideshow Paused</translation>
</message>
<message>
<source>MPEG-4 Support</source>
<translation>MPEG-4 Support</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Played</source>
<translation>Played</translation>
</message>
<message>
<source>If enabled, the star and community rating for episodes of a TV show will be removed. This is to prevent spoilers of an upcoming good/bad episode.</source>
<translation>If enabled, the star and community rating for episodes of a TV show will be removed. This is to prevent spoilers of an upcoming good/bad episode.</translation>
</message>
<message>
<source>H.264</source>
<translation>H.264</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>Disable Community Rating for Episodes</source>
<translation>Disable Community Rating for Episodes</translation>
</message>
<message>
<source>View All</source>
<translation>View All</translation>
</message>
<message>
<source>Select when to show titles.</source>
<translation>Select when to show titles.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Show On Hover</source>
<translation>Show On Hover</translation>
</message>
<message>
<source>Always Show</source>
<translation>Always Show</translation>
</message>
<message>
<source>Always Hide</source>
<translation>Always Hide</translation>
</message>
<message>
<source>Album</source>
<translation>Album</translation>
</message>
<message>
<source>Artists (Grid)</source>
<translation>Artists (Grid)</translation>
</message>
<message>
<source>Song</source>
<translation>Song</translation>
</message>
<message>
<source>Artists (Presentation)</source>
<translation>Artists (Presentation)</translation>
</message>
<message>
<source>Songs</source>
<translation>Songs</translation>
</message>
<message>
<source>Albums</source>
<translation>Albums</translation>
</message>
</context>
</TS>

View File

@ -302,6 +302,16 @@
<source>Movies</source>
<translation>Movies</translation>
</message>
<message>
<source>Movies (Presentation)</source>
<translation>Movies (Presentation)</translation>
<extracomment>Movie library view option</extracomment>
</message>
<message>
<source>Movies (Grid)</source>
<translation>Movies (Grid)</translation>
<extracomment>Movie library view option</extracomment>
</message>
<message>
<source>TV Shows</source>
<translation>TV Shows</translation>
@ -484,10 +494,21 @@
<translation>Playback</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
<message>
<source>MPEG-2 Support</source>
<translation>MPEG-2 Support</translation>
<extracomment>Settings Menu - Title for option</extracomment>
<source>Codec Support</source>
<translation>Codec Support</translation>
<extracomment>Settings Menu - Title for settings group related to codec support</extracomment>
</message>
<message>
<source>Enable or disable Direct Play for optional codecs</source>
<translation>Enable or disable Direct Play for optional codecs</translation>
<extracomment>Settings Menu - Title for settings group related to codec support</extracomment>
</message>
<message>
<source>MPEG-2</source>
<translation>MPEG-2</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</source>
@ -495,14 +516,24 @@
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>AV1 Support</source>
<translation>AV1 Support</translation>
<extracomment>Settings Menu - Title for option</extracomment>
<source>MPEG-4</source>
<translation>MPEG-4</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</source>
<translation>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>AV1</source>
<translation>AV1</translation>
<extracomment>Name of a setting - should we try to direct play experimental av1 codec</extracomment>
</message>
<message>
<source>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</source>
<translation>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
<extracomment>Description of a setting - should we try to direct play experimental av1 codec</extracomment>
</message>
<message>
<source>Enabled</source>
@ -657,6 +688,16 @@
<translation>Blur Unwatched Episodes</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Skip Details for Single Seasons</source>
<translation>Skip Details for Single Seasons</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>If enabled, selecting a TV series with only one season will go straight to the episode list rather than the show details and season list.</source>
<translation>If enabled, selecting a TV series with only one season will go straight to the episode list rather than the show details and season list.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>If enabled, images of unwatched episodes will be blurred.</source>
<translation>If enabled, images of unwatched episodes will be blurred.</translation>
@ -727,9 +768,9 @@
<translation>Play Trailer</translation>
</message>
<message>
<source>Direct Play H.264 Unsupported Profile Levels</source>
<translation>Direct Play H.264 Unsupported Profile Levels</translation>
<extracomment>Settings Menu - Title for option</extracomment>
<source>H.264</source>
<translation>H.264</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.</source>
@ -737,9 +778,9 @@
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Direct Play HEVC Unsupported Profile Levels</source>
<translation>Direct Play HEVC Unsupported Profile Levels</translation>
<extracomment>Settings Menu - Title for option</extracomment>
<source>HEVC</source>
<translation>HEVC</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.</source>
@ -865,7 +906,7 @@
<source>Only display text subtitles to minimize transcoding.</source>
<translation>Only display text subtitles to minimize transcoding.</translation>
<extracomment>Description of a setting - should we hide subtitles that might transcode</extracomment>
</message>
</message>
<message>
<source>all</source>
<translation>all</translation>
@ -910,5 +951,103 @@
<translation>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Show What's New Popup</source>
<translation>Show What's New Popup</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Show What's New popup when Jellyfin is updated to a new version.</source>
<translation>Show What's New popup when Jellyfin is updated to a new version.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Unplayed</source>
<translation>Unplayed</translation>
</message>
<message>
<source>Played</source>
<translation>Played</translation>
</message>
<message>
<source>Resumable</source>
<translation>Resumable</translation>
</message>
<message>
<source>Movie Library Default View</source>
<translation>Movie Library Default View</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Default view for Movie Libraries.</source>
<translation>Default view for Movie Libraries.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Movies (Presentation)</source>
<translation>Movies (Presentation)</translation>
</message>
<message>
<source>Movies (Grid)</source>
<translation>Movies (Grid)</translation>
</message>
<message>
<source>Movie Library Grid Titles</source>
<translation>Movie Library Grid Titles</translation>
<extracomment>Settings Menu - Title for option</extracomment>
</message>
<message>
<source>Select when to show titles.</source>
<translation>Select when to show titles.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Show On Hover</source>
<translation>Show On Hover</translation>
</message>
<message>
<source>Always Show</source>
<translation>Always Show</translation>
</message>
<message>
<source>Always Hide</source>
<translation>Always Hide</translation>
</message>
<message>
<source>Artists (Presentation)</source>
<translation>Artists (Presentation)</translation>
</message>
<message>
<source>Artists (Grid)</source>
<translation>Artists (Grid)</translation>
</message>
<message>
<source>Song</source>
<translation>Song</translation>
</message>
<message>
<source>Songs</source>
<translation>Songs</translation>
</message>
<message>
<source>Album</source>
<translation>Album</translation>
</message>
<message>
<source>Albums</source>
<translation>Albums</translation>
</message>
<message>
<source>View All</source>
<translation>View All</translation>
</message>
<message>
<source>Disable Community Rating for Episodes</source>
<translation>Disable Community Rating for Episodes</translation>
</message>
<message>
<source>If enabled, the star and community rating for episodes of a TV show will be removed. This is to prevent spoilers of an upcoming good/bad episode.</source>
<translation>If enabled, the star and community rating for episodes of a TV show will be removed. This is to prevent spoilers of an upcoming good/bad episode.</translation>
</message>
</context>
</TS>

View File

@ -2199,5 +2199,496 @@
<source>NO_ITEMS</source>
<translation>Este %1 no contiene elementos</translation>
</message>
<message>
<source>Change Server</source>
<translation>Cambiar Servidor</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Borrar Credenciales</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Puntuación de la crítica</translation>
</message>
<message>
<source>Age</source>
<translation>Edad</translation>
</message>
<message>
<source>today</source>
<translation>hoy</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>Died</source>
<translation>Muerto/a</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Puntuación de IMDb</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Reparto y equipo</translation>
</message>
<message>
<source>Saturday</source>
<translation>Sábado</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Repetir</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Media Grid</source>
<translation>Cuadrícula de medios</translation>
<extracomment>UI -&gt; Media Grid section in user setting screen.</extracomment>
</message>
<message>
<source>Sign Out</source>
<translation>Cerrar sesión</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>¿Guardar credenciales?</translation>
</message>
<message>
<source>Error During Playback</source>
<translation>Error durante la reproducción</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Ha ocurrido un error al reproducir este contenido.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Reproduciendo contenido del canal</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Error de reproducción de contenido del canal</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nombre</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Fecha en que se agregó</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Fecha de reproducción</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Control Parental</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Fecha de estreno</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Tiempo de duración</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Vista</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Clasificar</translation>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtro</translation>
</message>
<message>
<source>Special Features</source>
<translation>Funciones especiales</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>No se ha podido reproducir el contenido del canal de este servidor</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Conteo de reproducción</translation>
</message>
<message>
<source>On Now</source>
<translation>En directo ahora</translation>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Error recuperando contenido</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Ha ocurrido un error al tratar de recuperar la información de este contenido desde el servidor.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1 no contiene elementos</translation>
</message>
<message>
<source>Born</source>
<translation>Nacido/a</translation>
</message>
<message>
<source>More Like This</source>
<translation>Mas de este estilo</translation>
</message>
<message>
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
<translation>Se ha encontrado un error al reproducir este elemento. El servidor no proveyó la información necesaria para la transcodificación.</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>Use voice remote to search</source>
<translation>Utilizar la búsqueda remota por voz</translation>
<extracomment>Help text in search voice text box</extracomment>
</message>
<message>
<source>Movies (Presentation)</source>
<translation>Películas (presentación)</translation>
</message>
<message>
<source>Movies (Grid)</source>
<translation>Películas (cuadrícula)</translation>
</message>
<message>
<source>TV Shows</source>
<translation>Programas de televisión</translation>
</message>
<message>
<source>yesterday</source>
<translation>ayer</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>mañana</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jueves</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Viernes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Started at</source>
<translation>Comenzó a las</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Starts at</source>
<translation>Comienza a las</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Live</source>
<translation>En vivo</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guía de televisión</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Record Series</source>
<translation>Grabar serie</translation>
</message>
<message>
<source>Cancel Recording</source>
<translation>Cancelar la grabación</translation>
</message>
<message>
<source>Cancel Series Recording</source>
<translation>Cancelar la grabación de la serie</translation>
</message>
<message>
<source>Close</source>
<translation>Cerrar</translation>
</message>
<message>
<source>Connecting to Server</source>
<translation>Conectando con el servidor</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>Not found</source>
<translation>No se ha encontrado</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Unknown</source>
<translation>Desconocido</translation>
<extracomment>Title for a cast member for which we have no information for</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>El contenido solicitado no existe en el servidor</translation>
<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>Agregar el nombre del servidor o su dirección de IP</translation>
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
</message>
<message>
<source>Pick a Jellyfin server from the local network</source>
<translation>Elige un servidor Jellyfin disponible en la red local:</translation>
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</source>
<translation>Soporte de reproducción directa para contenido MPEG-2 (ej., televisión en vivo). Esto previene la transcodificación de contenido MPEG-2, pero a mayor uso de ancho de banda.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</source>
<translation>Soporte de reproducción directa para contenido MPEG-4. Esto podría requerir ser deshabilitado para poder reproducir los archivos de video con encodificación DIVX.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message>
<source>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</source>
<translation>**EXPERIMENTAL** Soporte de reproducción directa para contenido AV1 si este dispositivo Roku es compatible.</translation>
<extracomment>Description of a setting - should we try to direct play experimental av1 codec</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>Siempre mostrar los títulos por debajo de las imágenes de cartelera. (Si se deshabilita, el título se mostrará debajo del elemento resaltado solamente).</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Item Count</source>
<translation>Conteo de elementos</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment>
</message>
<message>
<source>Show item count in the library and index of selected item.</source>
<translation>Mostrar el conteo de elementos en la biblioteca y en el índice del elemento seleccionado.</translation>
<extracomment>Description for option in Setting Screen</extracomment>
</message>
<message>
<source>Set Favorite</source>
<translation>Agregar a favoritos</translation>
<extracomment>Button Text - When pressed, sets item as Favorite</extracomment>
</message>
<message>
<source>Set Watched</source>
<translation>Marcar como visto</translation>
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
</message>
<message>
<source>Go to series</source>
<translation>Ir a serie</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Series Detail Page</extracomment>
</message>
<message>
<source>Go to season</source>
<translation>Ir a la temporada</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</extracomment>
</message>
<message>
<source>Go to episode</source>
<translation>Ir al episodio</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
</message>
<message>
<source>Search now</source>
<translation>Buscar ahora</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Presiona &apos;OK&apos; para cerrar</translation>
</message>
<message>
<source>Additional Parts</source>
<translation>Partes adicionales</translation>
<extracomment>Additional parts of a video</extracomment>
</message>
<message>
<source>Movies</source>
<translation>Películas</translation>
</message>
<message>
<source>Sunday</source>
<translation>Domingo</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Lunes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Martes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Miércoles</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Starts</source>
<translation>Comienza a las</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>Ended at</source>
<translation>Terminó a las</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>Termina a las</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Channels</source>
<translation>Canales</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>View Channel</source>
<translation>Ver Canal</translation>
</message>
<message>
<source>Record</source>
<translation>Grabar</translation>
</message>
<message>
<source>...or enter server URL manually:</source>
<translation>Si no hay servidores disponibles, puedes agregar manualmente la URL:</translation>
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
</message>
<message>
<source>Error Getting Playback Information</source>
<translation>Error obteniendo la Información de reproducción</translation>
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
</message>
<message>
<source>Version</source>
<translation>Versión</translation>
</message>
<message>
<source>Playback</source>
<translation>Reproducción</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
<message>
<source>User Interface</source>
<translation>Interfaz de usuario</translation>
<extracomment>Title for User Interface section in user setting screen.</extracomment>
</message>
<message>
<source>Media Grid options.</source>
<translation>Opciones de la cuadrícula de medios.</translation>
</message>
<message>
<source>Codec Support</source>
<translation>Soporte de Codec</translation>
<extracomment>Settings Menu - Title for settings group related to codec support</extracomment>
</message>
<message>
<source>Enable or disable Direct Play for optional codecs</source>
<translation>Habilitar o desactivar la reproducción directa para codecs opcionales</translation>
<extracomment>Settings Menu - Title for settings group related to codec support</extracomment>
</message>
<message>
<source>MPEG-2</source>
<translation>MPEG-2</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>MPEG-4</source>
<translation>MPEG-4</translation>
<extracomment>Name of codec used in settings menu</extracomment>
</message>
<message>
<source>AV1</source>
<translation>AV1</translation>
<extracomment>Name of a setting - should we try to direct play experimental av1 codec</extracomment>
</message>
<message>
<source>Item Titles</source>
<translation>Títulos de elementos</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>Started</source>
<translation>Comenzó a las</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Enabled</source>
<translation>Activado</translation>
</message>
<message>
<source>Disabled</source>
<translation>Desactivado</translation>
</message>
<message>
<source>Shows</source>
<translation>espectáculos</translation>
</message>
<message>
<source>Quick Connect</source>
<translation>Conexión rápida</translation>
</message>
<message>
<source>(Dialog will close automatically)</source>
<translation>(El diálogo se cerrará automáticamente)</translation>
</message>
<message>
<source>Return to Top</source>
<translation>Vuelva a la parte superior</translation>
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message>
<message>
<source>You can search for Titles, People, Live TV Channels and more</source>
<translation>Puede buscar títulos, personas, canales de TV en vivo y más</translation>
<extracomment>Help text in search results</extracomment>
</message>
<message>
<source>Here is your Quick Connect code:</source>
<translation>Aquí está su código de conexión rápida:</translation>
</message>
<message>
<source>There was an error authenticating via Quick Connect.</source>
<translation>Hubo un error al autenticarse a través de Quick Connect.</translation>
</message>
<message>
<source>Networks</source>
<translation>Redes</translation>
</message>
<message>
<source>Studios</source>
<translation>Estudios</translation>
</message>
</context>
</TS>

File diff suppressed because it is too large Load Diff

View File

@ -1124,5 +1124,53 @@
<source>Change Server</source>
<translation>Changer de serveur</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Chargement des données de la chaîne</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>On Now</source>
<translation>En ce moment</translation>
</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>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>Change Server</source>
<translation>Changer de serveur</translation>
</message>
<message>
<source>Sign Out</source>
<translation>Se déconnecter</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Sauvegarder les informations d&apos;authentification?</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Supprimer les valeurs enregistrées</translation>
</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>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 depuis le serveur.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
</context>
</TS>

File diff suppressed because it is too large Load Diff

View File

@ -1921,5 +1921,36 @@
<translation>Errore durante la riproduzione</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Errore durante la riproduzione</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Sign Out</source>
<translation>Esci</translation>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>C&apos;è stato un errore nel recupero dei dati per questo elemento dal server.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>È stato riscontrato un errore durante la riproduzione di questo oggetto.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Caricamento dati del canale</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Salvare le credenziali?</translation>
</message>
<message>
<source>Change Server</source>
<translation>Cambia server</translation>
</message>
</context>
</TS>

View File

@ -2643,5 +2643,53 @@ não contém itens</translation>
<source>OFFICIAL_RATING</source>
<translation>Classificação Etária</translation>
</message>
<message>
<source>Sign Out</source>
<translation>Sair</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Salvar Credenciais?</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Deletar Salvos</translation>
</message>
<message>
<source>Error During Playback</source>
<translation>Erro Durante a Reprodução</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Change Server</source>
<translation>Mudar Servidor</translation>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Erro ao Carregar Conteúdo</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Carregando Dados do Canal</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Foi encontrado um erro na reprodução deste item.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>On Now</source>
<translation>Em Exibição</translation>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Houve um erro ao recuperar os dados deste item do servidor.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Erro ao carregar os Dados do Canal</translation>
</message>
</context>
</TS>

View File

@ -958,5 +958,169 @@
<translation>Prehrávanie</translation>
<extracomment>Title for Playback section in user setting screen.</extracomment>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Tento %1 neobsahuje žiadne položky</translation>
</message>
<message>
<source>Tuesday</source>
<translation>utorok</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>piatok</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Hodnotenie IMDb</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Dátum vydania</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Zobrazenie</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Uložiť prihlasovacie údaje?</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Dátum prehrania</translation>
</message>
<message>
<source>Cast &amp; Crew</source>
<translation>Obsadenie a štáb</translation>
</message>
<message>
<source>Movies</source>
<translation>Filmy</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Meno</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Odstrániť uložené</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Dátum pridania</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Rodičovské hodnotenie</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Počet prehraní</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Dĺžka</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Zoradenie</translation>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filter</translation>
</message>
<message>
<source>Born</source>
<translation>Dátum narodenia</translation>
</message>
<message>
<source>Died</source>
<translation>Dátum úmrtia</translation>
</message>
<message>
<source>Age</source>
<translation>Vek</translation>
</message>
<message>
<source>More Like This</source>
<translation>Viac podobných</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Hodnotenie kritikov</translation>
</message>
<message>
<source>Press &apos;OK&apos; to Close</source>
<translation>Zatvorte stlačením tlačidla &apos;OK&apos;</translation>
</message>
<message>
<source>Special Features</source>
<translation>Špeciálne Funkcie</translation>
</message>
<message>
<source>Additional Parts</source>
<translation>Dodatočné Časti</translation>
<extracomment>Additional parts of a video</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>včera</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>nedeľa</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>pondelok</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>streda</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>štvrtok</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Saturday</source>
<translation>sobota</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Movies (Presentation)</source>
<translation>Filmy (prezentácia)</translation>
</message>
<message>
<source>Movies (Grid)</source>
<translation>Filmy (mriežka)</translation>
</message>
<message>
<source>TV Shows</source>
<translation>TV Seriály</translation>
</message>
<message>
<source>today</source>
<translation>dnes</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>zajtra</translation>
<extracomment>Next day</extracomment>
</message>
</context>
</TS>

240
package-lock.json generated
View File

@ -17,9 +17,9 @@
"sob": "npm:slide-out-button@^1.0.1"
},
"devDependencies": {
"@rokucommunity/bslint": "0.8.0",
"brighterscript": "0.61.2",
"ropm": "0.10.10"
"@rokucommunity/bslint": "0.8.1",
"brighterscript": "0.61.3",
"ropm": "0.10.11"
}
},
"node_modules/@nodelib/fs.scandir": {
@ -60,9 +60,9 @@
"integrity": "sha512-2ox6EUL+UTtccTbD4dbVjZK3QHa0PHCqpoKMF8lZz9ayzzEP3iVPF8KZR6hOi6bxsIcbGXVjqmtCVkpC4P9SrA=="
},
"node_modules/@rokucommunity/bslint": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@rokucommunity/bslint/-/bslint-0.8.0.tgz",
"integrity": "sha512-fqTiBMczZa6YmJix0b81cDm2n9c7GlumFirwLGJSHpCEvnxwNTf0tIo3efnd4N5aJq7kpg8Y3GEbWnKs58BhFg==",
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@rokucommunity/bslint/-/bslint-0.8.1.tgz",
"integrity": "sha512-G4XhWHMZzq6HJiNazd3Vpg8mstYkbOk6dQC4JD7vmBlSVqm0KkITmZRNlumn/x1v60E9dGONP89MkoBz3/DHaA==",
"dev": true,
"dependencies": {
"fs-extra": "^10.0.0",
@ -371,9 +371,9 @@
}
},
"node_modules/brighterscript": {
"version": "0.61.2",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.61.2.tgz",
"integrity": "sha512-QNawTRD9DHlyVc/lwD0pSJQIzFf1JsjL9CHu4jmv+41/o+/DzSAHAjQhgIa9dQWroIAwHPUVKMya+0jXlIrIvw==",
"version": "0.61.3",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.61.3.tgz",
"integrity": "sha512-8BDpOSCdmkS/QcTdPTUW/99nCBypuoa/Zz6PZHI6OiVylqTBidtrGI7lBZotqY6yvQ3KJl24thhLHK5XuIT/6w==",
"dev": true,
"dependencies": {
"@rokucommunity/bslib": "^0.1.1",
@ -391,15 +391,15 @@
"fs-extra": "^8.0.0",
"jsonc-parser": "^2.3.0",
"long": "^3.2.0",
"luxon": "^1.8.3",
"luxon": "^2.5.2",
"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.9.2",
"roku-deploy": "^3.9.3",
"serialize-error": "^7.0.1",
"source-map": "^0.7.3",
"source-map": "^0.7.4",
"vscode-languageserver": "7.0.0",
"vscode-languageserver-protocol": "3.16.0",
"vscode-languageserver-textdocument": "^1.0.1",
@ -520,6 +520,15 @@
"node": ">=6 <7 || >=8"
}
},
"node_modules/brighterscript/node_modules/luxon": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-2.5.2.tgz",
"integrity": "sha512-Yg7/RDp4nedqmLgyH0LwgGRvMEKVzKbUdkBYyCosbHgJ+kaOUx0qzSiSatVc3DFygnirTPYnMM2P5dg2uH1WvA==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/brighterscript/node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
@ -1398,9 +1407,9 @@
}
},
"node_modules/luxon": {
"version": "1.17.2",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.17.2.tgz",
"integrity": "sha512-qELKtIj3HD41N+MvgoxArk8DZGUb4Gpiijs91oi+ZmKJzRlxY6CoyTwNoUwnogCVs4p8HuxVJDik9JbnYgrCng==",
"version": "1.28.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==",
"engines": {
"node": "*"
}
@ -1794,9 +1803,9 @@
}
},
"node_modules/roku-deploy": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.9.2.tgz",
"integrity": "sha512-2LZyR4EhaFrka1gVmcuJO/f42tqz4clGImboVLCNem1q/PcFV5cnXNZRfqDI+MZ/n8eJto71JlZkcc7TDLt/EQ==",
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.9.3.tgz",
"integrity": "sha512-cjTx5ffZNt07rQS+0s2sTBHkZKUk283y9f6UnbI77X03lQ60vYlCnqsKswWisFYMHPIdvsTLLSfKsshAPwKHEQ==",
"dependencies": {
"chalk": "^2.4.2",
"dateformat": "^3.0.3",
@ -1831,20 +1840,20 @@
}
},
"node_modules/ropm": {
"version": "0.10.10",
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.10.tgz",
"integrity": "sha512-tkPuDwP/Mva9IXIuTf4pnH1DC27WLeLfu8QJ70WwaX9tepNMZeDi4eEQdWQ7kalXxxlwXGnG4jUaaA1B4v8zWw==",
"version": "0.10.11",
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.11.tgz",
"integrity": "sha512-D9Nfi2tuB32d9gJFdyBdUw6+sBo4lwG2trkL9S0jHJCKqAQWs6tgkmDRd+pp6ZrKEKSK1V2y0bi8xFOi2Rjr+Q==",
"dev": true,
"dependencies": {
"@xml-tools/ast": "^5.0.5",
"@xml-tools/parser": "1.0.10",
"brighterscript": "^0.57.2",
"brighterscript": "^0.61.3",
"del": "6.0.0",
"fs-extra": "9.1.0",
"glob-all": "3.2.1",
"latinize": "0.5.0",
"npm-packlist": "2.1.4",
"roku-deploy": "^3.8.1",
"roku-deploy": "^3.9.3",
"semver": "7.3.4",
"yargs": "16.2.0"
},
@ -1864,79 +1873,6 @@
"chevrotain": "7.1.1"
}
},
"node_modules/ropm/node_modules/brighterscript": {
"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",
"@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/brighterscript/node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/ropm/node_modules/brighterscript/node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true,
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/ropm/node_modules/chevrotain": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz",
@ -2438,9 +2374,9 @@
"integrity": "sha512-2ox6EUL+UTtccTbD4dbVjZK3QHa0PHCqpoKMF8lZz9ayzzEP3iVPF8KZR6hOi6bxsIcbGXVjqmtCVkpC4P9SrA=="
},
"@rokucommunity/bslint": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@rokucommunity/bslint/-/bslint-0.8.0.tgz",
"integrity": "sha512-fqTiBMczZa6YmJix0b81cDm2n9c7GlumFirwLGJSHpCEvnxwNTf0tIo3efnd4N5aJq7kpg8Y3GEbWnKs58BhFg==",
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@rokucommunity/bslint/-/bslint-0.8.1.tgz",
"integrity": "sha512-G4XhWHMZzq6HJiNazd3Vpg8mstYkbOk6dQC4JD7vmBlSVqm0KkITmZRNlumn/x1v60E9dGONP89MkoBz3/DHaA==",
"dev": true,
"requires": {
"fs-extra": "^10.0.0",
@ -2695,9 +2631,9 @@
}
},
"brighterscript": {
"version": "0.61.2",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.61.2.tgz",
"integrity": "sha512-QNawTRD9DHlyVc/lwD0pSJQIzFf1JsjL9CHu4jmv+41/o+/DzSAHAjQhgIa9dQWroIAwHPUVKMya+0jXlIrIvw==",
"version": "0.61.3",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.61.3.tgz",
"integrity": "sha512-8BDpOSCdmkS/QcTdPTUW/99nCBypuoa/Zz6PZHI6OiVylqTBidtrGI7lBZotqY6yvQ3KJl24thhLHK5XuIT/6w==",
"dev": true,
"requires": {
"@rokucommunity/bslib": "^0.1.1",
@ -2715,15 +2651,15 @@
"fs-extra": "^8.0.0",
"jsonc-parser": "^2.3.0",
"long": "^3.2.0",
"luxon": "^1.8.3",
"luxon": "^2.5.2",
"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.9.2",
"roku-deploy": "^3.9.3",
"serialize-error": "^7.0.1",
"source-map": "^0.7.3",
"source-map": "^0.7.4",
"vscode-languageserver": "7.0.0",
"vscode-languageserver-protocol": "3.16.0",
"vscode-languageserver-textdocument": "^1.0.1",
@ -2743,6 +2679,12 @@
"universalify": "^0.1.0"
}
},
"luxon": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-2.5.2.tgz",
"integrity": "sha512-Yg7/RDp4nedqmLgyH0LwgGRvMEKVzKbUdkBYyCosbHgJ+kaOUx0qzSiSatVc3DFygnirTPYnMM2P5dg2uH1WvA==",
"dev": true
},
"yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
@ -3533,9 +3475,9 @@
}
},
"luxon": {
"version": "1.17.2",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.17.2.tgz",
"integrity": "sha512-qELKtIj3HD41N+MvgoxArk8DZGUb4Gpiijs91oi+ZmKJzRlxY6CoyTwNoUwnogCVs4p8HuxVJDik9JbnYgrCng=="
"version": "1.28.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw=="
},
"merge2": {
"version": "1.4.1",
@ -3813,9 +3755,9 @@
}
},
"roku-deploy": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.9.2.tgz",
"integrity": "sha512-2LZyR4EhaFrka1gVmcuJO/f42tqz4clGImboVLCNem1q/PcFV5cnXNZRfqDI+MZ/n8eJto71JlZkcc7TDLt/EQ==",
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.9.3.tgz",
"integrity": "sha512-cjTx5ffZNt07rQS+0s2sTBHkZKUk283y9f6UnbI77X03lQ60vYlCnqsKswWisFYMHPIdvsTLLSfKsshAPwKHEQ==",
"requires": {
"chalk": "^2.4.2",
"dateformat": "^3.0.3",
@ -3846,20 +3788,20 @@
}
},
"ropm": {
"version": "0.10.10",
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.10.tgz",
"integrity": "sha512-tkPuDwP/Mva9IXIuTf4pnH1DC27WLeLfu8QJ70WwaX9tepNMZeDi4eEQdWQ7kalXxxlwXGnG4jUaaA1B4v8zWw==",
"version": "0.10.11",
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.11.tgz",
"integrity": "sha512-D9Nfi2tuB32d9gJFdyBdUw6+sBo4lwG2trkL9S0jHJCKqAQWs6tgkmDRd+pp6ZrKEKSK1V2y0bi8xFOi2Rjr+Q==",
"dev": true,
"requires": {
"@xml-tools/ast": "^5.0.5",
"@xml-tools/parser": "1.0.10",
"brighterscript": "^0.57.2",
"brighterscript": "^0.61.3",
"del": "6.0.0",
"fs-extra": "9.1.0",
"glob-all": "3.2.1",
"latinize": "0.5.0",
"npm-packlist": "2.1.4",
"roku-deploy": "^3.8.1",
"roku-deploy": "^3.9.3",
"semver": "7.3.4",
"yargs": "16.2.0"
},
@ -3873,72 +3815,6 @@
"chevrotain": "7.1.1"
}
},
"brighterscript": {
"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",
"@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"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true
}
}
},
"chevrotain": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz",

View File

@ -4,9 +4,9 @@
"description": "Roku app for Jellyfin media server",
"main": "index.js",
"devDependencies": {
"@rokucommunity/bslint": "0.8.0",
"brighterscript": "0.61.2",
"ropm": "0.10.10"
"@rokucommunity/bslint": "0.8.1",
"brighterscript": "0.61.3",
"ropm": "0.10.11"
},
"scripts": {
"postinstall": "npx ropm copy",

View File

@ -4,39 +4,51 @@
"description": "Settings relating to playback and supported codec and media types.",
"children": [
{
"title": "AV1 Support",
"description": "** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.",
"settingName": "playback.av1",
"type": "bool",
"default": "false"
"title": "Codec Support",
"description": "Enable or disable Direct Play support for certain codecs",
"children": [
{
"title": "AV1",
"description": "** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.",
"settingName": "playback.av1",
"type": "bool",
"default": "false"
},
{
"title": "MPEG-2",
"description": "Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.",
"settingName": "playback.mpeg2",
"type": "bool",
"default": "false"
},
{
"title": "MPEG-4",
"description": "Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.",
"settingName": "playback.mpeg4",
"type": "bool",
"default": "true"
}
]
},
{
"title": "MPEG-2 Support",
"description": "Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.",
"settingName": "playback.mpeg2",
"type": "bool",
"default": "false"
},
{
"title": "MPEG-4 Support",
"description": "Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.",
"settingName": "playback.mpeg4",
"type": "bool",
"default": "true"
},
{
"title": "Direct Play H.264 Unsupported Profile Levels",
"description": "Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.",
"settingName": "playback.tryDirect.h264ProfileLevel",
"type": "bool",
"default": "true"
},
{
"title": "Direct Play HEVC Unsupported Profile Levels",
"description": "Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.",
"settingName": "playback.tryDirect.hevcProfileLevel",
"type": "bool",
"default": "true"
"title": "Profile Level Support",
"description": "Attempt Direct Play of potentially unsupported profile levels",
"children": [
{
"title": "H.264",
"description": "Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.",
"settingName": "playback.tryDirect.h264ProfileLevel",
"type": "bool",
"default": "true"
},
{
"title": "HEVC",
"description": "Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.",
"settingName": "playback.tryDirect.hevcProfileLevel",
"type": "bool",
"default": "true"
}
]
},
{
"title": "Cinema Mode",
@ -54,6 +66,13 @@
}
]
},
{
"title": "Show What's New Popup",
"description": "Show What's New popup when Jellyfin is updated to a new version.",
"settingName": "load.allowwhatsnew",
"type": "bool",
"default": "true"
},
{
"title": "User Interface",
"description": "Settings relating to how the application looks.",
@ -68,6 +87,13 @@
"settingName": "ui.details.maxdaysnextup",
"type": "integer",
"default": "365"
},
{
"title": "Use Splashscreen as Home Background",
"description": "Use generated splashscreen image as Jellyfin's home background. Jellyfin will need to be closed and reopened for change to take effect.",
"settingName": "ui.home.splashBackground",
"type": "bool",
"default": "false"
}
]
},
@ -94,6 +120,20 @@
"settingName": "ui.tvshows.blurunwatched",
"type": "bool",
"default": "false"
},
{
"title": "Skip Details for Single Seasons",
"description": "If enabled, selecting a TV series with only one season will go straight to the episode list rather than the show details and season list.",
"settingName": "ui.tvshows.goStraightToEpisodeListing",
"type": "bool",
"default": "false"
},
{
"title":"Disable Community Rating for Episodes",
"description": "If enabled, the star and community rating for episodes of a TV show will be removed. This is to prevent spoilers of an upcoming good/bad episode.",
"settingName": "ui.tvshows.disableCommunityRating",
"type":"bool",
"default":"false"
}
]
},
@ -120,13 +160,6 @@
"settingName": "ui.design.hideclock",
"type": "bool",
"default": "false"
},
{
"title": "Use Splashscreen as Home Background",
"description": "Use generated splashscreen image as Jellyfin's home background. Jellyfin will need to be closed and reopened for change to take effect.",
"settingName": "ui.home.splashBackground",
"type": "bool",
"default": "false"
}
]
},
@ -134,6 +167,44 @@
"title": "Media Grid",
"description": "Media Grid options.",
"children": [
{
"title": "Movie Library Default View",
"description": "Default view for Movie Libraries.",
"settingName": "itemgrid.movieDefaultView",
"type": "radio",
"default": "movies",
"options": [
{
"title": "Movies (Presentation)",
"id": "Movies"
},
{
"title": "Movies (Grid)",
"id": "MoviesGrid"
}
]
},
{
"title": "Movie Library Grid Titles",
"description": "Select when to show titles.",
"settingName": "itemgrid.movieGridTitles",
"type": "radio",
"default": "showonhover",
"options": [
{
"title": "Show On Hover",
"id": "showonhover"
},
{
"title": "Always Show",
"id": "showalways"
},
{
"title": "Always Hide",
"id": "hidealways"
}
]
},
{
"title": "Item Count",
"description": "Show item count in the library and index of selected item.",

View File

@ -41,6 +41,7 @@ sub Main (args as dynamic) as void
sceneManager = CreateObject("roSGNode", "SceneManager")
m.global.addFields({ app_loaded: false, playstateTask: playstateTask, sceneManager: sceneManager })
m.global.addFields({ queueManager: CreateObject("roSGNode", "QueueManager") })
app_start:
' First thing to do is validate the ability to use the API
@ -56,6 +57,17 @@ sub Main (args as dynamic) as void
m.scene.observeField("exit", m.port)
' Only show the Whats New popup the first time a user runs a new client version.
if appInfo.GetVersion() <> get_setting("LastRunVersion")
' Ensure the user hasn't disabled Whats New popups
if get_user_setting("load.allowwhatsnew") = "true"
set_setting("LastRunVersion", appInfo.GetVersion())
dialog = createObject("roSGNode", "WhatsNewDialog")
m.scene.dialog = dialog
m.scene.dialog.observeField("buttonSelected", m.port)
end if
end if
' Handle input messages
input = CreateObject("roInput")
input.SetMessagePort(m.port)
@ -66,10 +78,10 @@ sub Main (args as dynamic) as void
m.device.EnableAppFocusEvent(false)
' Check if we were sent content to play with the startup command (Deep Link)
if (args.mediaType <> invalid) and (args.contentId <> invalid)
if isValidAndNotEmpty(args.mediaType) and isValidAndNotEmpty(args.contentId)
video = CreateVideoPlayerGroup(args.contentId)
if video <> invalid and video.errorMsg <> "introaborted"
if isValid(video) and video.errorMsg <> "introaborted"
sceneManager.callFunc("pushScene", video)
else
dialog = createObject("roSGNode", "Dialog")
@ -101,6 +113,7 @@ sub Main (args as dynamic) as void
group.setFocus(true)
end if
else if isNodeEvent(msg, "quickPlayNode")
group = sceneManager.callFunc("getActiveScene")
reportingNode = msg.getRoSGNode()
itemNode = reportingNode.quickPlayNode
if itemNode = invalid or itemNode.id = "" then return
@ -113,22 +126,40 @@ sub Main (args as dynamic) as void
if video <> invalid and video.errorMsg <> "introaborted"
sceneManager.callFunc("pushScene", video)
end if
if LCase(group.subtype()) = "tvepisodes"
if isValid(group.lastFocus)
group.lastFocus.setFocus(true)
end if
end if
reportingNode.quickPlayNode.type = ""
end if
else if isNodeEvent(msg, "selectedItem")
' If you select a library from ANYWHERE, follow this flow
selectedItem = msg.getData()
m.selectedItemType = selectedItem.type
'
if selectedItem.type = "CollectionFolder"
if selectedItem.collectionType = "movies"
group = CreateMovieLibraryView(selectedItem)
else if selectedItem.collectionType = "music"
group = CreateMusicLibraryView(selectedItem)
else
group = CreateItemGrid(selectedItem)
end if
sceneManager.callFunc("pushScene", group)
else if selectedItem.type = "Folder" and selectedItem.json.type = "Genre"
group = CreateMovieLibraryView(selectedItem)
' User clicked on a genre folder
if selectedItem.json.MovieCount > 0
group = CreateMovieLibraryView(selectedItem)
else
group = CreateItemGrid(selectedItem)
end if
sceneManager.callFunc("pushScene", group)
else if selectedItem.type = "Folder" and selectedItem.json.type = "MusicGenre"
group = CreateMusicLibraryView(selectedItem)
sceneManager.callFunc("pushScene", group)
else if selectedItem.type = "UserView" or selectedItem.type = "Folder" or selectedItem.type = "Channel" or selectedItem.type = "Boxset"
group = CreateItemGrid(selectedItem)
@ -192,7 +223,9 @@ sub Main (args as dynamic) as void
else if selectedItem.type = "MusicAlbum"
group = CreateAlbumView(selectedItem.json)
else if selectedItem.type = "Audio"
group = CreateAudioPlayerGroup([selectedItem.json])
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", selectedItem.json)
m.global.queueManager.callFunc("playQueue")
else
' TODO - switch on more node types
message_dialog("This type is not yet supported: " + selectedItem.type + ".")
@ -228,17 +261,27 @@ sub Main (args as dynamic) as void
' User has selected audio they want us to play
selectedIndex = msg.getData()
screenContent = msg.getRoSGNode()
group = CreateAudioPlayerGroup([screenContent.albumData.items[selectedIndex]])
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", screenContent.albumData.items[selectedIndex])
m.global.queueManager.callFunc("playQueue")
else if isNodeEvent(msg, "playAllSelected")
' User has selected playlist of of audio they want us to play
screenContent = msg.getRoSGNode()
m.spinner = screenContent.findNode("spinner")
m.spinner.visible = true
group = CreateAudioPlayerGroup(screenContent.albumData.items)
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("set", screenContent.albumData.items)
m.global.queueManager.callFunc("playQueue")
else if isNodeEvent(msg, "playArtistSelected")
' User has selected playlist of of audio they want us to play
screenContent = msg.getRoSGNode()
group = CreateArtistMixGroup(screenContent.pageContent.id)
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("set", CreateArtistMix(screenContent.pageContent.id).Items)
m.global.queueManager.callFunc("playQueue")
else if isNodeEvent(msg, "instantMixSelected")
' User has selected instant mix
' User has selected playlist of of audio they want us to play
@ -248,20 +291,26 @@ sub Main (args as dynamic) as void
m.spinner.visible = true
end if
group = invalid
viewHandled = false
' Create instant mix based on selected album
if isValid(screenContent.albumData)
if isValid(screenContent.albumData.items)
if screenContent.albumData.items.count() > 0
group = CreateInstantMixGroup(screenContent.albumData.items)
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("set", CreateInstantMix(screenContent.albumData.items[0].id).Items)
m.global.queueManager.callFunc("playQueue")
viewHandled = true
end if
end if
end if
' Create instant mix based on selected artist
if not isValid(group)
group = CreateInstantMixGroup([{ id: screenContent.pageContent.id }])
if not viewHandled
' Create instant mix based on selected artist
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("set", CreateInstantMix(screenContent.pageContent.id).Items)
m.global.queueManager.callFunc("playQueue")
end if
else if isNodeEvent(msg, "episodeSelected")
@ -306,7 +355,9 @@ sub Main (args as dynamic) as void
else if node.type = "MusicAlbum"
group = CreateAlbumView(node.json)
else if node.type = "Audio"
group = CreateAudioPlayerGroup([node.json])
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", node.json)
m.global.queueManager.callFunc("playQueue")
else if node.type = "Person"
group = CreatePersonView(node)
else if node.type = "TvChannel"
@ -318,7 +369,9 @@ sub Main (args as dynamic) as void
else if node.type = "Audio"
selectedIndex = msg.getData()
screenContent = msg.getRoSGNode()
group = CreateAudioPlayerGroup([screenContent.albumData.items[node.id]])
m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", screenContent.albumData.items[node.id])
m.global.queueManager.callFunc("playQueue")
else
' TODO - switch on more node types
message_dialog("This type is not yet supported: " + node.type + ".")

View File

@ -358,12 +358,18 @@ function CreateMovieDetailsGroup(movie)
end function
function CreateSeriesDetailsGroup(series)
' Get season data early in the function so we can check number of seasons.
seasonData = TVSeasons(series.id)
' Divert to season details if user setting goStraightToEpisodeListing is enabled and only one season exists.
if get_user_setting("ui.tvshows.goStraightToEpisodeListing") = "true" and seasonData.Items.Count() = 1
return CreateSeasonDetailsGroupByID(series.id, seasonData.Items[0].id)
end if
group = CreateObject("roSGNode", "TVShowDetails")
group.optionsAvailable = false
m.global.sceneManager.callFunc("pushScene", group)
group.itemContent = ItemMetaData(series.id)
group.seasonData = TVSeasons(series.id)
group.seasonData = seasonData ' Re-use variable from beginning of function
group.observeField("seasonSelected", m.port)
@ -483,6 +489,14 @@ function CreateMovieLibraryView(libraryItem)
return group
end function
function CreateMusicLibraryView(libraryItem)
group = CreateObject("roSGNode", "MusicLibraryView")
group.parentItem = libraryItem
group.optionsAvailable = true
group.observeField("selectedItem", m.port)
return group
end function
function CreateSearchPage()
' Search + Results Page
group = CreateObject("roSGNode", "searchResults")
@ -512,72 +526,6 @@ function CreateVideoPlayerGroup(video_id, mediaSourceId = invalid, audio_stream_
return video
end function
' Play Audio
function CreateAudioPlayerGroup(audiodata)
group = CreateObject("roSGNode", "NowPlaying")
group.observeField("state", m.port)
songIDArray = CreateObject("roArray", 0, true)
' All we need is an array of Song IDs the user selected to play.
for each song in audiodata
songIDArray.push(song.id)
end for
group.pageContent = songIDArray
group.musicArtistAlbumData = audiodata
m.global.sceneManager.callFunc("pushScene", group)
return group
end function
' Play Instant Mix
function CreateInstantMixGroup(audiodata)
songList = CreateInstantMix(audiodata[0].id)
group = CreateObject("roSGNode", "NowPlaying")
group.observeField("state", m.port)
songIDArray = CreateObject("roArray", 0, true)
' All we need is an array of Song IDs the user selected to play.
for each song in songList.items
songIDArray.push(song.id)
end for
songIDArray.shift()
group.pageContent = songIDArray
group.musicArtistAlbumData = songList.items
m.global.sceneManager.callFunc("pushScene", group)
return group
end function
' Play Artist
function CreateArtistMixGroup(artistID)
songList = CreateArtistMix(artistID)
group = CreateObject("roSGNode", "NowPlaying")
group.observeField("state", m.port)
songIDArray = CreateObject("roArray", 0, true)
' All we need is an array of Song IDs the user selected to play.
for each song in songList.items
songIDArray.push(song.id)
end for
group.pageContent = songIDArray
group.musicArtistAlbumData = songList.items
m.global.sceneManager.callFunc("pushScene", group)
return group
end function
function CreatePersonView(personData as object) as object
person = CreateObject("roSGNode", "PersonDetails")
m.global.SceneManager.callFunc("pushScene", person)

View File

@ -22,15 +22,15 @@ end function
sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -1, playbackPosition = -1, forceTranscoding = false, showIntro = true, allowResumeDialog = true)
video.content = createObject("RoSGNode", "ContentNode")
meta = ItemMetaData(video.id)
m.videotype = meta.type
if meta = invalid
video.content = invalid
return
end if
m.videotype = meta.type
' Special handling for "Programs" or "Vidoes" launched from "On Now" or elsewhere on the home screen...
' basically anything that is a Live Channel.
if meta.json.ChannelId <> invalid
if isValid(meta?.json?.ChannelId)
if meta.json.EpisodeTitle <> invalid
meta.title = meta.json.EpisodeTitle
else if meta.json.Name <> invalid
@ -53,7 +53,7 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
video.content.title = meta.title
video.showID = meta.showID
if playbackPosition = -1
if playbackPosition = -1 and isValid(meta.json)
playbackPosition = meta.json.UserData.PlaybackPositionTicks
if allowResumeDialog
if playbackPosition > 0
@ -175,7 +175,17 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
if mediaSourceId = invalid
mediaSourceId = video.id
end if
if meta.live then mediaSourceId = "" ' Don't send mediaSourceId for Live media
' Don't send mediaSourceId for Live Media
' Note: Recordings in progress will have meta.live = invalid, but we still don't want to send mediaSourceId
if not isValid(meta.live)
meta.live = false
mediaSourceId = ""
else
if meta.live
mediaSourceId = ""
end if
end if
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
video.videoId = video.id
@ -197,7 +207,7 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
video.container = getContainerType(meta)
if m.playbackInfo.MediaSources[0] = invalid
if not isValid(m.playbackInfo.MediaSources[0]) and isValid(meta.json)
m.playbackInfo = meta.json
end if
@ -395,7 +405,7 @@ end function
function getContainerType(meta as object) as string
' Determine the file type of the video file source
if meta.json.mediaSources = invalid then return ""
if not IsValid(meta.json) or not isValid(meta.json.mediaSources) then return ""
container = meta.json.mediaSources[0].container
if container = invalid

View File

@ -147,15 +147,19 @@ function ItemMetaData(id as string)
tmp = CreateObject("roSGNode", "MusicSongData")
' Try using song's parent for poster image
tmp.image = PosterImage(data.ParentId)
tmp.image = PosterImage(data.ParentId, { "MaxWidth": 500, "MaxHeight": 500 })
' Song's parent poster image is no good, try using the song's poster image
if tmp.image = invalid
tmp.image = PosterImage(data.id)
tmp.image = PosterImage(data.id, { "MaxWidth": 500, "MaxHeight": 500 })
end if
tmp.json = data
return tmp
else if data.type = "Recording"
' We know it's "Recording", but we don't do any special preprocessing
' for this data type at the moment, so just return the json.
return data
else
print "Items.brs::ItemMetaData processed unhandled type: " data.type
' Return json if we don't know what it is
@ -369,10 +373,6 @@ function TVSeasons(id as string)
results = []
for each item in data.Items
imgParams = { "AddPlayedIndicator": item.UserData.Played }
if item.UserData.UnplayedItemCount > 0
param = { "UnplayedCount": item.UserData.UnplayedItemCount }
imgParams.Append(param)
end if
tmp = CreateObject("roSGNode", "TVEpisodeData")
tmp.image = PosterImage(item.id, imgParams)
tmp.json = item
@ -389,11 +389,7 @@ function TVEpisodes(show_id as string, season_id as string)
data = getJson(resp)
results = []
for each item in data.Items
imgParams = { "AddPlayedIndicator": item.UserData.Played, "maxWidth": 400, "maxheight": 250 }
if item.UserData.PlayedPercentage <> invalid
param = { "PercentPlayed": item.UserData.PlayedPercentage }
imgParams.Append(param)
end if
imgParams = { "maxWidth": 400, "maxheight": 250 }
tmp = CreateObject("roSGNode", "TVEpisodeData")
tmp.image = PosterImage(item.id, imgParams)
if tmp.image <> invalid

View File

@ -0,0 +1,98 @@
[
{
"description": "Bug Fix: Don't crash when viewing in progress recording.",
"author": "jimdogx"
},
{
"description": "New Setting: Disable Community Rating for Episodes of TV shows.",
"author": "dezchai"
},
{
"description": "New View: Music Grid View",
"author": "1hitsong"
},
{
"description": "New Setting: Movie Grid Title Display",
"author": "1hitsong"
},
{
"description": "Updated View: TV Library Genre View",
"author": "1hitsong"
},
{
"description": "Bug Fix: Fix Crash When Options Button Pressed, but Options Are Not Available",
"author": "ApexArray"
},
{
"description": "Bug Fix: Fix Crash When Invalid Deep Link Arguments Are Passed",
"author": "ApexArray"
},
{
"description": "New Setting: Go straight to episode listing (if only 1 season available)",
"author": "ApexArray"
},
{
"description": "Feature: Create What's New Popup that tells users what's changed the first time they run a new version.",
"author": "1hitsong"
},
{
"description": "Feature: Toggle video playback when OK button is pressed",
"author": "ApexArray"
},
{
"description": "Bug Fix: Fix TV Season List Crash when showing 27+ Episodes",
"author": "1hitsong"
},
{
"description": "New View: Create Movie Grid View and new default setting",
"author": "1hitsong"
},
{
"description": "Core: Set Maximum Bitrates values based on Roku Guidelines",
"author": "ApexArray"
},
{
"description": "Core: Reorganize Roku Settings",
"author": "sevenrats"
},
{
"description": "Updated View: Add resumable filter to Movies",
"author": "1hitsong"
},
{
"description": "Updated View: Filter Movies & Series by Played/Unplayed status",
"author": "1hitsong"
},
{
"description": "New Setting: Hide subtitles that might transcode",
"author": "sevenrats"
},
{
"description": "Bug Fix: Show Unwatched Episode Count",
"author": "1hitsong"
},
{
"description": "New View: Add Artist Presentation View & Genre View to Music Library",
"author": "1hitsong"
},
{
"description": "Core: Draw Homepage Continue Watching percent complete bar instead of using image API",
"author": "1hitsong"
},
{
"description": "Bug Fix: Fix TV Episode focus issues",
"author": "1hitsong"
},
{
"description": "Bug Fix: Fix Movie Detail View lost focus bug when going back from resume dialog",
"author": "1hitsong"
},
{
"description": "Core: Add new questions to GitHub bug template",
"author": "1hitsong"
},
{
"description": "Core: Create Queue Manager and use for Music",
"author": "1hitsong"
}
]

View File

@ -21,7 +21,7 @@ end function
' returns the server-side track index for the appriate subtitle
function defaultSubtitleTrackFromVid(video_id) as integer
meta = ItemMetaData(video_id)
if meta.json.mediaSources <> invalid
if meta?.json?.mediaSources <> invalid
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
default_text_subs = defaultSubtitleTrack(subtitles["all"], true) ' Find correct subtitle track (forced text)
if default_text_subs <> -1

View File

@ -170,6 +170,13 @@ function getDeviceProfile() as object
"Property": "VideoLevel",
"Value": "41",
"IsRequired": false
},
' Roku only supports h264 up to 10Mpbs
{
"Condition": "LessThanEqual",
"Property": "VideoBitrate",
"Value": "10000000",
IsRequired: true
}
]
}
@ -203,6 +210,13 @@ function getDeviceProfile() as object
"Property": "VideoRangeType",
"Value": av1VideoRangeTypes,
"IsRequired": false
},
' Roku only supports AVI up to 40Mpbs
{
"Condition": "LessThanEqual",
"Property": "VideoBitrate",
"Value": "40000000",
IsRequired: true
}
]
})
@ -229,6 +243,13 @@ function getDeviceProfile() as object
"Property": "VideoLevel",
"Value": (120 * 5.1).ToStr(),
"IsRequired": false
},
' Roku only supports h265 up to 40Mpbs
{
"Condition": "LessThanEqual",
"Property": "VideoBitrate",
"Value": "40000000",
IsRequired: true
}
]
})
@ -243,6 +264,13 @@ function getDeviceProfile() as object
"Property": "VideoRangeType",
"Value": vp9VideoRangeTypes,
"IsRequired": false
},
' Roku only supports VP9 up to 40Mpbs
{
"Condition": "LessThanEqual",
"Property": "VideoBitrate",
"Value": "40000000",
IsRequired: true
}
]
})

View File

@ -91,6 +91,12 @@ function get_dialog_result(dialog, port)
end function
function lastFocusedChild(obj as object) as object
if LCase(obj.focusedChild.focusedChild.subType()) = "tvepisodes"
if isValid(obj?.focusedChild?.focusedChild?.lastFocus)
return obj.focusedChild.focusedChild.lastFocus
end if
end if
child = obj
for i = 0 to obj.getChildCount()
if obj.focusedChild <> invalid
@ -189,6 +195,24 @@ function isValid(input) as boolean
return input <> invalid
end function
' Returns whether or not passed value is valid and not empty
' Accepts a string, or any countable type (arrays and lists)
function isValidAndNotEmpty(input) as boolean
if not isValid(input) then return false
' Use roAssociativeArray instead of list so we get access to the doesExist() method
countableTypes = { "array": 1, "list": 1, "roarray": 1, "roassociativearray": 1, "rolist": 1 }
inputType = LCase(type(input))
if inputType = "string" or inputType = "rostring"
trimmedInput = input.trim()
return trimmedInput <> ""
else if countableTypes.doesExist(inputType)
return input.count() > 0
else
print "Called isValidAndNotEmpty() with invalid type: ", inputType
return false
end if
end function
' Rounds number to nearest integer
function roundNumber(f as float) as integer
' BrightScript only has a "floor" round