mirror of
https://github.com/jellyfin/jellyfin-roku.git
synced 2025-02-25 17:30:33 +00:00
Merge branch 'unstable' of https://github.com/jellyfin/jellyfin-roku into Add-Loading-ux-to-movies-details-screen
This commit is contained in:
commit
b61580c77a
13
.github/ISSUE_TEMPLATE/bug_report.md
vendored
13
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,15 +7,19 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Software Versions**
|
||||
Jellyfin Server Version:
|
||||
Roku Client Version:
|
||||
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
**How To Reproduce**
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
4. Bug occurs
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
@ -26,5 +30,10 @@ assignees: ''
|
||||
**Screenshots**
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Connection Information**
|
||||
Is server local or remote?
|
||||
|
||||
Is server connection http or https?
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
3
.github/workflows/auto-close-stale-pr.yml
vendored
3
.github/workflows/auto-close-stale-pr.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@3de2653986ebd134983c79fe2be5d45cc3d9f4e1 # tag=v6
|
||||
- uses: actions/stale@6f05e4244c9a0b2ed3401882b05d701dd0a7289b # v7
|
||||
with:
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
@ -17,4 +17,5 @@ jobs:
|
||||
close-pr-message: "This pull request has been closed because it has been inactive for 28 days. You may submit a new pull request if desired."
|
||||
days-before-pr-stale: 21
|
||||
days-before-pr-close: 7
|
||||
exempt-draft-pr: true
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@ -6,16 +6,16 @@ on:
|
||||
- 'locale/**'
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 # 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@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
|
||||
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3
|
||||
with:
|
||||
name: Jellyfin-Roku-dev-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/out/staging
|
||||
|
8
.github/workflows/master-release.yml
vendored
8
.github/workflows/master-release.yml
vendored
@ -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@2541b1294d2704b0964813337f33b291d3f8596b # tag=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@3cea5372237819ed00197afe530f5a7ea3e805c8 # 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
|
||||
|
8
.github/workflows/unstable-release.yml
vendored
8
.github/workflows/unstable-release.yml
vendored
@ -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@2541b1294d2704b0964813337f33b291d3f8596b # tag=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@3cea5372237819ed00197afe530f5a7ea3e805c8 # 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
|
||||
|
4
.github/workflows/validate.yml
vendored
4
.github/workflows/validate.yml
vendored
@ -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
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -12,11 +12,6 @@ roku_modules
|
||||
#NPM modules
|
||||
node_modules/
|
||||
|
||||
#Rooibos generated
|
||||
rooibosFunctionMap.brs
|
||||
*/buildinfo.brs
|
||||
logs
|
||||
|
||||
#Eclipse
|
||||
.buildpath
|
||||
.project
|
||||
|
25
DEVGUIDE.md
25
DEVGUIDE.md
@ -130,37 +130,12 @@ Modify code -> `make install` -> Use Roku remote to test changes -> `telnet ${RO
|
||||
|
||||
Unfortunately there is no debuger. You will need to use telnet to see log statements, warnings, and error reports. You won't always need to telnet into your device but the workflow above is typical when you are new to Brightscript or are working on tricky code.
|
||||
|
||||
### Testing
|
||||
|
||||
Testing is done with the [Rooibos](https://github.com/georgejecook/rooibos/) library. This works by including tests in the deployment and then looking at telnet
|
||||
for the test results.
|
||||
|
||||
Install necessary packages:
|
||||
|
||||
```bash
|
||||
sudo apt-get install nodejs npm
|
||||
```
|
||||
|
||||
Install [rooibos-cli](https://github.com/georgejecook/rooibos-cli):
|
||||
|
||||
```bash
|
||||
npm install -g rooibos-cli
|
||||
```
|
||||
|
||||
Deploy the application with tests:
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
View test results:
|
||||
|
||||
```bash
|
||||
telnet ${ROKU_DEV_TARGET} 8085
|
||||
```
|
||||
|
||||
To exit telnet: `CTRL + ]` and then type `quit + ENTER`
|
||||
|
||||
### Committing
|
||||
|
||||
Before commiting your code, please run:
|
||||
|
11
Makefile
11
Makefile
@ -1,8 +1,6 @@
|
||||
|
||||
#########################################################################
|
||||
# Makefile Usage:
|
||||
# > make test ' run all tests
|
||||
# > make testFailures ' run all tests and show only failures
|
||||
#
|
||||
# 1) Make sure that you have the curl command line executable in your path
|
||||
# 2) Set the variable ROKU_DEV_TARGET in your environment to the IP
|
||||
@ -12,11 +10,9 @@
|
||||
##########################################################################
|
||||
|
||||
APPNAME = Jellyfin_Roku
|
||||
VERSION = 1.4.12
|
||||
ROKU_TEST_ID = 1
|
||||
ROKU_TEST_WAIT_DURATION = 5
|
||||
VERSION = 1.6.3
|
||||
|
||||
ZIP_EXCLUDE= -x rooibos/**\* -x xml/* -x artwork/* -x \*.pkg -x storeassets\* -x keys\* -x \*/.\* -x *.git* -x *.DS* -x *.pkg* -x dist/**\* -x out/**\*
|
||||
ZIP_EXCLUDE= -x xml/* -x artwork/* -x \*.pkg -x storeassets\* -x keys\* -x \*/.\* -x *.git* -x *.DS* -x *.pkg* -x dist/**\* -x out/**\*
|
||||
|
||||
include app.mk
|
||||
|
||||
@ -29,7 +25,4 @@ beta:
|
||||
release:
|
||||
$(MAKE) BUILD='release' package
|
||||
|
||||
test: prep_staging prep_tests remove install
|
||||
echo "Running tests"
|
||||
|
||||
deploy: prep_staging remove install
|
||||
|
7
app.mk
7
app.mk
@ -163,13 +163,6 @@ package: prep_staging
|
||||
|
||||
@echo "*** packaging $(APPNAME)-$(BUILD) complete ***"
|
||||
|
||||
prep_tests:
|
||||
@mkdir -p $(STAGINGREL)/components/tests/; \
|
||||
mkdir -p $(STAGINGREL)/source/tests/; \
|
||||
cp -r $(SOURCEREL)/tests/components/* $(STAGINGREL)/components/tests/;\
|
||||
cp -r $(SOURCEREL)/tests/source/* $(STAGINGREL)/source/tests/;\
|
||||
./node_modules/.bin/rooibos-cli i tests/.rooibosrc.json
|
||||
|
||||
prep_commit:
|
||||
npm run format
|
||||
npm ci
|
||||
|
13
components/GetNextEpisodeTask.brs
Normal file
13
components/GetNextEpisodeTask.brs
Normal file
@ -0,0 +1,13 @@
|
||||
sub init()
|
||||
m.top.functionName = "getNextEpisodeTask"
|
||||
end sub
|
||||
|
||||
sub getNextEpisodeTask()
|
||||
m.nextEpisodeData = api_API().shows.getepisodes(m.top.showID, {
|
||||
UserId: get_setting("active_user"),
|
||||
StartItemId: m.top.videoID,
|
||||
Limit: 2
|
||||
})
|
||||
|
||||
m.top.nextEpisodeData = m.nextEpisodeData
|
||||
end sub
|
12
components/GetNextEpisodeTask.xml
Normal file
12
components/GetNextEpisodeTask.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
|
||||
<component name="GetNextEpisodeTask" extends="Task">
|
||||
<interface>
|
||||
<field id="videoID" type="string" />
|
||||
<field id="showID" type="string" />
|
||||
<field id="nextEpisodeData" type="assocarray" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="GetNextEpisodeTask.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
|
||||
</component>
|
@ -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
|
||||
|
@ -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>
|
||||
|
68
components/ItemGrid/GridItemSmall.brs
Normal file
68
components/ItemGrid/GridItemSmall.brs
Normal file
@ -0,0 +1,68 @@
|
||||
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")
|
||||
|
||||
'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"
|
||||
m.backdrop.visible = true
|
||||
m.posterText.visible = true
|
||||
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"
|
||||
m.backdrop.visible = false
|
||||
m.posterText.visible = false
|
||||
end if
|
||||
end sub
|
17
components/ItemGrid/GridItemSmall.xml
Normal file
17
components/ItemGrid/GridItemSmall.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="GridItemSmall" extends="Group">
|
||||
<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" alwaysNotify="true" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="GridItemSmall.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||
</component>
|
@ -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
|
||||
@ -320,13 +346,14 @@ end sub
|
||||
|
||||
' Set Photo Album view, sort, and filter options
|
||||
sub setPhotoAlbumOptions(options)
|
||||
' TODO/FIXME: Show shuffle options once implemented
|
||||
' options.views = [
|
||||
' { "Title": tr("Don't Shuffle"), "Name": "singlephoto"}
|
||||
' { "Title": tr("Shuffle"), "Name": "shufflephoto"}
|
||||
' ]
|
||||
options.views = []
|
||||
options.views = [
|
||||
{ "Title": tr("Slideshow Off"), "Name": "singlephoto" }
|
||||
{ "Title": tr("Slideshow On"), "Name": "slideshowphoto" }
|
||||
{ "Title": tr("Random Off"), "Name": "singlephoto" }
|
||||
{ "Title": tr("Random On"), "Name": "randomphoto" }
|
||||
]
|
||||
options.sort = []
|
||||
options.filter = []
|
||||
end sub
|
||||
|
||||
' Set Default view, sort, and filter options
|
||||
@ -422,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
|
||||
@ -437,6 +486,7 @@ sub ItemDataLoaded(msg)
|
||||
end if
|
||||
|
||||
m.itemGrid.setFocus(true)
|
||||
m.genreList.setFocus(false)
|
||||
m.spinner.visible = false
|
||||
end sub
|
||||
|
||||
@ -574,14 +624,17 @@ sub optionsClosed()
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.top.parentItem.Type = "CollectionFolder" or m.top.parentItem.CollectionType = "CollectionFolder"
|
||||
' Did the user just request "Shuffle" on a PhotoAlbum?
|
||||
if m.top.parentItem.Type = "CollectionFolder" or m.top.parentItem.Type = "Folder" or m.top.parentItem.CollectionType = "CollectionFolder"
|
||||
' Did the user just request "Random" on a PhotoAlbum?
|
||||
if m.options.view = "singlephoto"
|
||||
' TODO/FIXME: Stop shuffling here
|
||||
print "TODO/FIXME: Stop any shuffling here"
|
||||
else if m.options.view = "shufflephoto"
|
||||
' TODO/FIXME: Start shuffling here
|
||||
print "TODO/FIXME: Start shuffle here"
|
||||
set_user_setting("photos.slideshow", "false")
|
||||
set_user_setting("photos.random", "false")
|
||||
else if m.options.view = "slideshowphoto"
|
||||
set_user_setting("photos.slideshow", "true")
|
||||
set_user_setting("photos.random", "false")
|
||||
else if m.options.view = "randomphoto"
|
||||
set_user_setting("photos.random", "true")
|
||||
set_user_setting("photos.slideshow", "false")
|
||||
end if
|
||||
end if
|
||||
|
||||
@ -641,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
|
||||
@ -677,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
|
||||
@ -724,9 +786,10 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
return true
|
||||
else if itemToPlay <> invalid and itemToPlay.type = "Photo"
|
||||
' Spawn photo player task
|
||||
photoPlayer = CreateObject("roSgNode", "PhotoPlayerTask")
|
||||
photoPlayer.itemContent = itemToPlay
|
||||
photoPlayer.control = "RUN"
|
||||
photoPlayer = CreateObject("roSgNode", "PhotoDetails")
|
||||
photoPlayer.items = markupGrid
|
||||
photoPlayer.itemIndex = markupGrid.itemFocused
|
||||
m.global.sceneManager.callfunc("pushScene", photoPlayer)
|
||||
return true
|
||||
end if
|
||||
else if key = "left" and topGrp.isinFocusChain()
|
||||
@ -768,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
|
||||
@ -789,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
|
||||
|
||||
|
@ -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>
|
||||
|
@ -20,6 +20,16 @@ sub loadItems()
|
||||
sort_order = "Descending"
|
||||
end if
|
||||
|
||||
if m.top.ItemType = "LogoImage"
|
||||
logoImageExists = api_API().items.headimageurlbyname(m.top.itemId, "logo")
|
||||
if logoImageExists
|
||||
m.top.content = [api_API().items.getimageurl(m.top.itemId, "logo", 0, { "maxHeight": 500, "maxWidth": 500, "quality": "90" })]
|
||||
else
|
||||
m.top.content = []
|
||||
end if
|
||||
|
||||
return
|
||||
end if
|
||||
|
||||
params = {
|
||||
limit: m.top.limit,
|
||||
@ -52,16 +62,25 @@ sub loadItems()
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.top.searchTerm <> ""
|
||||
'reset data
|
||||
if LCase(m.top.searchTerm) = LCase(tr("all"))
|
||||
params.searchTerm = " "
|
||||
else if m.top.searchTerm <> ""
|
||||
params.searchTerm = m.top.searchTerm
|
||||
end if
|
||||
|
||||
filter = m.top.filter
|
||||
if filter = "All" or filter = "all"
|
||||
filter = LCase(m.top.filter)
|
||||
if filter = "all"
|
||||
' do nothing
|
||||
else if filter = "Favorites"
|
||||
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 <> ""
|
||||
@ -76,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 })
|
||||
@ -90,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
|
||||
@ -108,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")
|
||||
@ -117,7 +138,61 @@ sub loadItems()
|
||||
else if item.type = "Episode"
|
||||
tmp = CreateObject("roSGNode", "TVEpisode")
|
||||
else if item.Type = "Genre"
|
||||
tmp = CreateObject("roSGNode", "FolderData")
|
||||
tmp = CreateObject("roSGNode", "ContentNode")
|
||||
tmp.title = item.name
|
||||
|
||||
genreData = api_API().users.getitemsbyquery(get_setting("active_user"), {
|
||||
SortBy: "Random",
|
||||
SortOrder: "Ascending",
|
||||
IncludeItemTypes: m.top.itemType,
|
||||
Recursive: true,
|
||||
Fields: "PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo",
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: "Primary",
|
||||
Limit: 6,
|
||||
GenreIds: item.id,
|
||||
EnableTotalRecordCount: false,
|
||||
ParentId: m.top.itemId
|
||||
})
|
||||
|
||||
if genreData.Items.Count() > 5
|
||||
' Add View All item to the start of the row
|
||||
row = tmp.createChild("FolderData")
|
||||
row.parentFolder = m.top.itemId
|
||||
row.title = tr("View All") + " " + item.name
|
||||
item.name = tr("View All") + " " + item.name
|
||||
row.json = item
|
||||
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 genreItem in genreData.Items
|
||||
if LCase(m.top.itemType) = "movie"
|
||||
row = tmp.createChild("MovieData")
|
||||
else
|
||||
row = tmp.createChild("SeriesData")
|
||||
end if
|
||||
|
||||
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"
|
||||
tmp = CreateObject("roSGNode", "FolderData")
|
||||
else if item.Type = "MusicAlbum"
|
||||
@ -132,15 +207,27 @@ 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
|
||||
tmp.parentFolder = m.top.itemId
|
||||
tmp.json = item
|
||||
if item.UserData <> invalid and item.UserData.isFavorite <> invalid
|
||||
tmp.favorite = item.UserData.isFavorite
|
||||
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
|
||||
tmp.favorite = item.UserData.isFavorite
|
||||
end if
|
||||
end if
|
||||
|
||||
results.push(tmp)
|
||||
end if
|
||||
end for
|
||||
|
855
components/ItemGrid/MovieLibraryView.brs
Normal file
855
components/ItemGrid/MovieLibraryView.brs
Normal file
@ -0,0 +1,855 @@
|
||||
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.selectedMovieName = m.top.findNode("selectedMovieName")
|
||||
m.selectedMovieOverview = m.top.findNode("selectedMovieOverview")
|
||||
m.selectedMovieProductionYear = m.top.findNode("selectedMovieProductionYear")
|
||||
m.selectedMovieOfficialRating = m.top.findNode("selectedMovieOfficialRating")
|
||||
m.movieLogo = m.top.findNode("movieLogo")
|
||||
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.communityRatingGroup = m.top.findNode("communityRatingGroup")
|
||||
m.criticRatingIcon = m.top.findNode("criticRatingIcon")
|
||||
m.criticRatingGroup = m.top.findNode("criticRatingGroup")
|
||||
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()
|
||||
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.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 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")
|
||||
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"
|
||||
if not isValid(m.view) then m.view = "Movies"
|
||||
|
||||
if sortAscendingStr = invalid or sortAscendingStr = "true"
|
||||
m.sortAscending = true
|
||||
else
|
||||
m.sortAscending = false
|
||||
end if
|
||||
|
||||
if m.top.parentItem.json.type = "Studio"
|
||||
m.loadItemsTask.studioIds = m.top.parentItem.id
|
||||
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
|
||||
m.loadItemsTask.genreIds = ""
|
||||
else if m.top.parentItem.json.type = "Genre"
|
||||
m.loadItemsTask.genreIds = m.top.parentItem.id
|
||||
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
|
||||
m.loadItemsTask.studioIds = ""
|
||||
else if m.view = "Movies" or m.options.view = "Movies"
|
||||
m.loadItemsTask.studioIds = ""
|
||||
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() = "movies"
|
||||
m.loadItemsTask.itemType = "Movie"
|
||||
m.loadItemsTask.itemId = m.top.parentItem.Id
|
||||
end if
|
||||
|
||||
' By default we load movies
|
||||
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]"
|
||||
m.itemGrid.numRows = "3"
|
||||
m.loadItemsTask.view = "Networks"
|
||||
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"
|
||||
m.movieLogo.visible = false
|
||||
m.selectedMovieName.visible = false
|
||||
m.selectedMovieOverview.visible = false
|
||||
m.infoGroup.visible = false
|
||||
end if
|
||||
|
||||
m.loadItemsTask.observeField("content", "ItemDataLoaded")
|
||||
m.spinner.visible = true
|
||||
m.loadItemsTask.control = "RUN"
|
||||
SetUpOptions()
|
||||
end sub
|
||||
|
||||
' Set Movies view, sort, and filter options
|
||||
sub setMoviesOptions(options)
|
||||
|
||||
options.views = [
|
||||
{ "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 (Presentation)"), "Name": "Movies" },
|
||||
{ "Title": tr("Movies (Grid)"), "Name": "MoviesGrid" },
|
||||
]
|
||||
end if
|
||||
|
||||
options.sort = [
|
||||
{ "Title": tr("TITLE"), "Name": "SortName" },
|
||||
{ "Title": tr("IMDB_RATING"), "Name": "CommunityRating" },
|
||||
{ "Title": tr("CRITIC_RATING"), "Name": "CriticRating" },
|
||||
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
|
||||
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
|
||||
{ "Title": tr("OFFICIAL_RATING"), "Name": "OfficialRating" },
|
||||
{ "Title": tr("PLAY_COUNT"), "Name": "PlayCount" },
|
||||
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
|
||||
{ "Title": tr("RUNTIME"), "Name": "Runtime" }
|
||||
]
|
||||
|
||||
options.filter = [
|
||||
{ "Title": tr("All"), "Name": "All" },
|
||||
{ "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 = [{ "Title": tr("TITLE"), "Name": "SortName" }]
|
||||
options.filter = []
|
||||
end if
|
||||
|
||||
if m.options.view = "Studios" or m.view = "Studios"
|
||||
options.sort = [
|
||||
{ "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
|
||||
|
||||
' Return parent collection type
|
||||
function getCollectionType() as string
|
||||
if m.top.parentItem.collectionType = invalid
|
||||
return m.top.parentItem.Type
|
||||
else
|
||||
return 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 = []
|
||||
|
||||
setMoviesOptions(options)
|
||||
|
||||
' Set selected view option
|
||||
for each o in options.views
|
||||
if o.Name = 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 o.Name = 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 o.Name = 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.movieLogo.uri = data[0]
|
||||
m.movieLogo.visible = true
|
||||
else
|
||||
m.selectedMovieName.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 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 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
|
||||
|
||||
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.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
|
||||
|
||||
'
|
||||
'Set Selected Movie Name
|
||||
sub SetName(movieName as string)
|
||||
m.selectedMovieName.text = movieName
|
||||
end sub
|
||||
|
||||
'
|
||||
'Set Selected Movie Overview
|
||||
sub SetOverview(movieOverview as string)
|
||||
m.selectedMovieOverview.text = movieOverview
|
||||
end sub
|
||||
|
||||
'
|
||||
'Set Selected Movie OfficialRating
|
||||
sub SetOfficialRating(movieOfficialRating as string)
|
||||
m.selectedMovieOfficialRating.text = movieOfficialRating
|
||||
end sub
|
||||
|
||||
'
|
||||
'Set Selected Movie ProductionYear
|
||||
sub SetProductionYear(movieProductionYear)
|
||||
m.selectedMovieProductionYear.text = movieProductionYear
|
||||
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 m.swapAnimation.state <> "stopped" or 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.movieLogo.visible = false
|
||||
m.selectedMovieName.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()
|
||||
m.communityRatingGroup.visible = false
|
||||
m.criticRatingGroup.visible = false
|
||||
|
||||
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
|
||||
end if
|
||||
|
||||
if isValid(itemData.CriticRating)
|
||||
setFieldText("criticRatingLabel", itemData.criticRating)
|
||||
|
||||
tomato = "pkg:/images/rotten.png"
|
||||
|
||||
if itemData.CriticRating > 60
|
||||
tomato = "pkg:/images/fresh.png"
|
||||
end if
|
||||
|
||||
m.criticRatingIcon.uri = tomato
|
||||
m.criticRatingGroup.visible = true
|
||||
end if
|
||||
|
||||
if isValid(itemData.Name)
|
||||
SetName(itemData.Name)
|
||||
else
|
||||
SetName("")
|
||||
end if
|
||||
|
||||
if isValid(itemData.Overview)
|
||||
SetOverview(itemData.Overview)
|
||||
else
|
||||
SetOverview("")
|
||||
end if
|
||||
|
||||
if isValid(itemData.ProductionYear)
|
||||
SetProductionYear(str(itemData.ProductionYear))
|
||||
else
|
||||
SetProductionYear("")
|
||||
end if
|
||||
|
||||
if type(itemData.RunTimeTicks) = "LongInteger"
|
||||
setFieldText("runtime", stri(getRuntime(itemData.RunTimeTicks)) + " mins")
|
||||
else
|
||||
setFieldText("runtime", "")
|
||||
end if
|
||||
|
||||
if isValid(itemData.OfficialRating)
|
||||
SetOfficialRating(itemData.OfficialRating)
|
||||
else
|
||||
SetOfficialRating("")
|
||||
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
|
||||
|
||||
function getRuntime(runTimeTicks) as integer
|
||||
return round(runTimeTicks / 600000000.0)
|
||||
end function
|
||||
|
||||
function round(f as float) as integer
|
||||
' BrightScript only has a "floor" round
|
||||
' This compares floor to floor + 1 to find which is closer
|
||||
m = int(f)
|
||||
n = m + 1
|
||||
x = abs(f - m)
|
||||
y = abs(f - n)
|
||||
if y > x
|
||||
return m
|
||||
else
|
||||
return n
|
||||
end if
|
||||
end function
|
||||
|
||||
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 m.newBackdrop.loadStatus = "ready"
|
||||
m.swapAnimation.control = "start"
|
||||
end if
|
||||
end sub
|
||||
|
||||
'
|
||||
'Swap Complete
|
||||
sub swapDone()
|
||||
if 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.rowItemSelected[0]).getChild(m.genreList.rowItemSelected[1])
|
||||
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
|
||||
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.itemGrid.content = m.data
|
||||
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 = "play" or key = "OK"
|
||||
|
||||
itemToPlay = getItemFocused()
|
||||
|
||||
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
|
||||
m.top.quickPlayNode = itemToPlay
|
||||
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
|
||||
return true
|
||||
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
|
66
components/ItemGrid/MovieLibraryView.xml
Normal file
66
components/ItemGrid/MovieLibraryView.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="MovieLibraryView" 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,700]">
|
||||
<poster id="backdrop" loadDisplayMode="scaleToFill" width="1100" height="700" opacity="1" />
|
||||
<poster id="backdropTransition" loadDisplayMode="scaleToFill" width="1100" height="700" opacity="1" />
|
||||
</maskGroup>
|
||||
|
||||
<Label id="selectedMovieName" visible="false" translation="[120, 40]" wrap="true" font="font:LargeBoldSystemFont" width="850" height="196" horizAlign="left" vertAlign="center" />
|
||||
<Poster id="movieLogo" visible="false" translation="[120, 40]" loadDisplayMode="scaleToFit" width="384" height="196" />
|
||||
|
||||
<LayoutGroup layoutDirection="horiz" translation="[120, 270]" itemSpacings="[30]" id="infoGroup">
|
||||
<Label id="selectedMovieProductionYear" font="font:SmallestSystemFont" />
|
||||
<Label id="runtime" font="font:SmallestSystemFont" />
|
||||
<Label id="selectedMovieOfficialRating" font="font:SmallestSystemFont" />
|
||||
|
||||
<LayoutGroup id="communityRatingGroup" visible="false" layoutDirection="horiz" itemSpacings="[-5]">
|
||||
<Poster id="star" uri="pkg:/images/sharp_star_white_18dp.png" height="28" width="28" blendColor="#00a4dcFF" />
|
||||
<Label id="communityRating" font="font:SmallestSystemFont" />
|
||||
</LayoutGroup>
|
||||
|
||||
<LayoutGroup layoutDirection="horiz" id="criticRatingGroup">
|
||||
<Poster id="criticRatingIcon" height="28" width="28" />
|
||||
<Label id="criticRatingLabel" font="font:SmallestSystemFont" />
|
||||
</LayoutGroup>
|
||||
</LayoutGroup>
|
||||
|
||||
<Label id="selectedMovieOverview" font="font:SmallestSystemFont" translation="[120, 360]" wrap="true" lineSpacing="20" maxLines="5" width="850" ellipsisText="..." />
|
||||
|
||||
<MarkupGrid id="itemGrid" itemComponentName="GridItemSmall" numColumns="7" numRows="2" vertFocusAnimationStyle="fixed" itemSize="[230, 310]" itemSpacing="[20, 20]" />
|
||||
<RowList opacity="0" id="genrelist" translation="[120, 60]" 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, 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="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="showItemTitles" type="string" value="showonhover" />
|
||||
<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="MovieLibraryView.brs" />
|
||||
</component>
|
48
components/ItemGrid/MusicArtistGridItem.brs
Normal file
48
components/ItemGrid/MusicArtistGridItem.brs
Normal 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
|
17
components/ItemGrid/MusicArtistGridItem.xml
Normal file
17
components/ItemGrid/MusicArtistGridItem.xml
Normal 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>
|
778
components/ItemGrid/MusicLibraryView.brs
Normal file
778
components/ItemGrid/MusicLibraryView.brs
Normal 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
|
51
components/ItemGrid/MusicLibraryView.xml
Normal file
51
components/ItemGrid/MusicLibraryView.xml
Normal 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>
|
@ -25,19 +25,14 @@ sub init()
|
||||
' get system preference clock format (12/24hr)
|
||||
di = CreateObject("roDeviceInfo")
|
||||
m.clockFormat = di.GetClockFormat()
|
||||
|
||||
' grab current time
|
||||
currentTime = CreateObject("roDateTime")
|
||||
currentTime.ToLocalTime()
|
||||
m.currentHours = currentTime.GetHours()
|
||||
m.currentMinutes = currentTime.GetMinutes()
|
||||
m.overlayHours = m.top.findNode("overlayHours")
|
||||
m.overlayMinutes = m.top.findNode("overlayMinutes")
|
||||
m.overlayMeridian = m.top.findNode("overlayMeridian")
|
||||
|
||||
' start timer
|
||||
m.currentTimeTimer = m.top.findNode("currentTimeTimer")
|
||||
m.currentTimeTimer.control = "start"
|
||||
m.currentTimeTimer.ObserveField("fire", "updateTime")
|
||||
|
||||
updateTimeDisplay()
|
||||
end if
|
||||
|
||||
setClockVisibility()
|
||||
@ -97,64 +92,50 @@ sub updateUser()
|
||||
end sub
|
||||
|
||||
sub updateTime()
|
||||
if (m.currentMinutes + 1) > 59
|
||||
m.currentHours = m.currentHours + 1
|
||||
m.currentMinutes = 0
|
||||
else
|
||||
m.currentMinutes = m.currentMinutes + 1
|
||||
end if
|
||||
|
||||
m.currentTime = CreateObject("roDateTime")
|
||||
m.currentTime.ToLocalTime()
|
||||
m.currentTimeTimer.duration = 60 - m.currentTime.GetSeconds()
|
||||
m.currentHours = m.currentTime.GetHours()
|
||||
m.currentMinutes = m.currentTime.GetMinutes()
|
||||
updateTimeDisplay()
|
||||
end sub
|
||||
|
||||
sub resetTime()
|
||||
m.currentTimeTimer.control = "stop"
|
||||
|
||||
currentTime = CreateObject("roDateTime")
|
||||
m.currentTimeTimer.control = "start"
|
||||
|
||||
currentTime.ToLocalTime()
|
||||
|
||||
m.currentHours = currentTime.GetHours()
|
||||
m.currentMinutes = currentTime.GetMinutes()
|
||||
|
||||
updateTimeDisplay()
|
||||
updateTime()
|
||||
end sub
|
||||
|
||||
sub updateTimeDisplay()
|
||||
overlayHours = m.top.findNode("overlayHours")
|
||||
overlayMinutes = m.top.findNode("overlayMinutes")
|
||||
overlayMeridian = m.top.findNode("overlayMeridian")
|
||||
|
||||
if m.clockFormat = "24h"
|
||||
overlayMeridian.text = ""
|
||||
m.overlayMeridian.text = ""
|
||||
if m.currentHours < 10
|
||||
overlayHours.text = "0" + StrI(m.currentHours).trim()
|
||||
m.overlayHours.text = "0" + StrI(m.currentHours).trim()
|
||||
else
|
||||
overlayHours.text = m.currentHours
|
||||
m.overlayHours.text = m.currentHours
|
||||
end if
|
||||
else
|
||||
if m.currentHours < 12
|
||||
overlayMeridian.text = "AM"
|
||||
m.overlayMeridian.text = "AM"
|
||||
if m.currentHours = 0
|
||||
overlayHours.text = "12"
|
||||
m.overlayHours.text = "12"
|
||||
else
|
||||
overlayHours.text = m.currentHours
|
||||
m.overlayHours.text = m.currentHours
|
||||
end if
|
||||
else
|
||||
overlayMeridian.text = "PM"
|
||||
m.overlayMeridian.text = "PM"
|
||||
if m.currentHours = 12
|
||||
overlayHours.text = "12"
|
||||
m.overlayHours.text = "12"
|
||||
else
|
||||
overlayHours.text = m.currentHours - 12
|
||||
m.overlayHours.text = m.currentHours - 12
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.currentMinutes < 10
|
||||
overlayMinutes.text = "0" + StrI(m.currentMinutes).trim()
|
||||
m.overlayMinutes.text = "0" + StrI(m.currentMinutes).trim()
|
||||
else
|
||||
overlayMinutes.text = m.currentMinutes
|
||||
m.overlayMinutes.text = m.currentMinutes
|
||||
end if
|
||||
end sub
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -2,6 +2,8 @@ sub init()
|
||||
m.playbackTimer = m.top.findNode("playbackTimer")
|
||||
m.bufferCheckTimer = m.top.findNode("bufferCheckTimer")
|
||||
m.top.observeField("state", "onState")
|
||||
m.top.observeField("content", "onContentChange")
|
||||
|
||||
m.playbackTimer.observeField("fire", "ReportPlayback")
|
||||
m.bufferPercentage = 0 ' Track whether content is being loaded
|
||||
m.playReported = false
|
||||
@ -12,6 +14,88 @@ sub init()
|
||||
clockNode = findNodeBySubtype(m.top, "clock")
|
||||
if clockNode[0] <> invalid then clockNode[0].parent.removeChild(clockNode[0].node)
|
||||
end if
|
||||
|
||||
'Play Next Episode button
|
||||
m.nextEpisodeButton = m.top.findNode("nextEpisode")
|
||||
m.nextEpisodeButton.text = tr("Next Episode")
|
||||
m.nextEpisodeButton.setFocus(false)
|
||||
|
||||
m.showNextEpisodeButtonAnimation = m.top.findNode("showNextEpisodeButton")
|
||||
m.hideNextEpisodeButtonAnimation = m.top.findNode("hideNextEpisodeButton")
|
||||
|
||||
m.checkedForNextEpisode = false
|
||||
m.getNextEpisodeTask = createObject("roSGNode", "GetNextEpisodeTask")
|
||||
m.getNextEpisodeTask.observeField("nextEpisodeData", "onNextEpisodeDataLoaded")
|
||||
|
||||
end sub
|
||||
|
||||
' Event handler for when video content field changes
|
||||
sub onContentChange()
|
||||
if not isValid(m.top.content) then return
|
||||
|
||||
m.top.observeField("position", "onPositionChanged")
|
||||
|
||||
' If video content type is not episode, remove position observer
|
||||
if m.top.content.contenttype <> 4
|
||||
m.top.unobserveField("position")
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub onNextEpisodeDataLoaded()
|
||||
m.checkedForNextEpisode = true
|
||||
|
||||
m.top.observeField("position", "onPositionChanged")
|
||||
|
||||
if m.getNextEpisodeTask.nextEpisodeData.Items.count() <> 2
|
||||
m.top.unobserveField("position")
|
||||
end if
|
||||
end sub
|
||||
|
||||
'
|
||||
' Runs Next Episode button animation and sets focus to button
|
||||
sub showNextEpisodeButton()
|
||||
if not m.nextEpisodeButton.visible
|
||||
m.showNextEpisodeButtonAnimation.control = "start"
|
||||
m.nextEpisodeButton.setFocus(true)
|
||||
m.nextEpisodeButton.visible = true
|
||||
end if
|
||||
end sub
|
||||
|
||||
'
|
||||
'Update count down text
|
||||
sub updateCount()
|
||||
m.nextEpisodeButton.text = tr("Next Episode") + " " + Int(m.top.runTime - m.top.position).toStr()
|
||||
end sub
|
||||
|
||||
'
|
||||
' Runs hide Next Episode button animation and sets focus back to video
|
||||
sub hideNextEpisodeButton()
|
||||
m.hideNextEpisodeButtonAnimation.control = "start"
|
||||
m.nextEpisodeButton.setFocus(false)
|
||||
m.top.setFocus(true)
|
||||
end sub
|
||||
|
||||
' Checks if we need to display the Next Episode button
|
||||
sub checkTimeToDisplayNextEpisode()
|
||||
if int(m.top.position) >= (m.top.runTime - 30)
|
||||
showNextEpisodeButton()
|
||||
updateCount()
|
||||
return
|
||||
end if
|
||||
|
||||
if m.nextEpisodeButton.visible or m.nextEpisodeButton.hasFocus()
|
||||
m.nextEpisodeButton.visible = false
|
||||
m.nextEpisodeButton.setFocus(false)
|
||||
end if
|
||||
end sub
|
||||
|
||||
' When Video Player state changes
|
||||
sub onPositionChanged()
|
||||
' Check if dialog is open
|
||||
m.dialog = m.top.getScene().findNode("dialogBackground")
|
||||
if not isValid(m.dialog)
|
||||
checkTimeToDisplayNextEpisode()
|
||||
end if
|
||||
end sub
|
||||
|
||||
'
|
||||
@ -40,6 +124,16 @@ sub onState(msg)
|
||||
m.top.control = "stop"
|
||||
m.top.backPressed = true
|
||||
else if m.top.state = "playing"
|
||||
|
||||
' Check if next episde is available
|
||||
if isValid(m.top.showID)
|
||||
if m.top.showID <> "" and not m.checkedForNextEpisode and m.top.content.contenttype = 4
|
||||
m.getNextEpisodeTask.showID = m.top.showID
|
||||
m.getNextEpisodeTask.videoID = m.top.id
|
||||
m.getNextEpisodeTask.control = "RUN"
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.playReported = false
|
||||
ReportPlayback("start")
|
||||
m.playReported = true
|
||||
@ -126,17 +220,39 @@ sub dialogClosed(msg)
|
||||
sourceNode.close = true
|
||||
end sub
|
||||
|
||||
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
|
||||
if key = "OK" and m.nextEpisodeButton.hasfocus() and not m.top.trickPlayBar.visible
|
||||
m.top.state = "finished"
|
||||
hideNextEpisodeButton()
|
||||
return true
|
||||
else
|
||||
'Hide Next Episode Button
|
||||
if m.nextEpisodeButton.visible or m.nextEpisodeButton.hasFocus()
|
||||
m.nextEpisodeButton.visible = false
|
||||
m.nextEpisodeButton.setFocus(false)
|
||||
m.top.setFocus(true)
|
||||
end if
|
||||
end if
|
||||
|
||||
if not press then return false
|
||||
|
||||
if m.top.Subtitles.count() and key = "down"
|
||||
if key = "down"
|
||||
m.top.selectSubtitlePressed = true
|
||||
return true
|
||||
else if key = "up"
|
||||
m.top.selectPlaybackInfoPressed = true
|
||||
return true
|
||||
else if key = "OK"
|
||||
' OK will play/pause depending on current state
|
||||
' return false to allow selection during seeking
|
||||
if m.top.state = "paused"
|
||||
m.top.control = "resume"
|
||||
return false
|
||||
else if m.top.state = "playing"
|
||||
m.top.control = "pause"
|
||||
return false
|
||||
end if
|
||||
end if
|
||||
|
||||
return false
|
||||
|
@ -22,12 +22,24 @@
|
||||
<field id="videoId" type="string" />
|
||||
<field id="mediaSourceId" type="string" />
|
||||
<field id="audioIndex" type="integer" />
|
||||
<field id="runTime" type="integer" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="JFVideo.brs" />
|
||||
<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/roku_modules/api/api.brs" />
|
||||
|
||||
<children>
|
||||
<timer id="playbackTimer" repeat="true" duration="30" />
|
||||
<timer id="bufferCheckTimer" repeat="true" />
|
||||
<JFButton id="nextEpisode" opacity="0" textColor="#f0f0f0" focusedTextColor="#202020" focusFootprintBitmapUri="pkg:/images/option-menu-bg.9.png" focusBitmapUri="pkg:/images/white.9.png" translation="[1500, 900]" />
|
||||
|
||||
<!--animation for the play next episode button-->
|
||||
<Animation id="showNextEpisodeButton" duration="1.0" repeat="false" easeFunction="inQuad">
|
||||
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[0.0, .9]" fieldToInterp="nextEpisode.opacity" />
|
||||
</Animation>
|
||||
<Animation id="hideNextEpisodeButton" duration=".2" repeat="false" easeFunction="inQuad">
|
||||
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[.9, 0]" fieldToInterp="nextEpisode.opacity" />
|
||||
</Animation>
|
||||
</children>
|
||||
</component>
|
||||
</component>
|
@ -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
|
||||
|
||||
|
@ -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>
|
||||
|
46
components/WhatsNewDialog.brs
Normal file
46
components/WhatsNewDialog.brs
Normal 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
|
7
components/WhatsNewDialog.xml
Normal file
7
components/WhatsNewDialog.xml
Normal 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>
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
@ -30,21 +30,25 @@ sub setData()
|
||||
else if datum.type = "Episode"
|
||||
imgParams = { "AddPlayedIndicator": datum.UserData.Played }
|
||||
|
||||
if datum.UserData.PlayedPercentage <> invalid
|
||||
imgParams.Append({ "PercentPlayed": datum.UserData.PlayedPercentage })
|
||||
end if
|
||||
|
||||
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
|
||||
|
||||
@ -52,68 +56,72 @@ 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
|
||||
|
||||
else if datum.type = "Movie"
|
||||
imgParams = { AddPlayedIndicator: datum.UserData.Played }
|
||||
|
||||
if datum.UserData.PlayedPercentage <> invalid
|
||||
imgParams.Append({ "PercentPlayed": datum.UserData.PlayedPercentage })
|
||||
end if
|
||||
|
||||
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 = "Video"
|
||||
imgParams = { AddPlayedIndicator: datum.UserData.Played }
|
||||
|
||||
if datum.UserData.PlayedPercentage <> invalid
|
||||
imgParams.Append({ "PercentPlayed": datum.UserData.PlayedPercentage })
|
||||
end if
|
||||
|
||||
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"
|
||||
|
@ -11,10 +11,11 @@
|
||||
<field id="json" type="assocarray" onChange="setData" />
|
||||
<field id="collectionType" type="string" />
|
||||
<field id="imageWidth" type="integer" value="464" />
|
||||
<field id="PlayedPercentage" type="float" value="0" />
|
||||
<field id="usePoster" type="bool" value="false" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
|
||||
<script type="text/brightscript" uri="HomeData.brs" />
|
||||
<script type="text/brightscript" uri="HomeData.brs" />
|
||||
</component>
|
||||
|
@ -8,11 +8,13 @@ sub setFields()
|
||||
m.top.watched = json.UserData.played
|
||||
m.top.Type = "Movie"
|
||||
|
||||
if json.MediaSourceCount <> invalid and json.MediaSourceCount > 1
|
||||
m.top.mediaSources = []
|
||||
for each source in json.MediaSources
|
||||
m.top.mediaSources.push(source)
|
||||
end for
|
||||
if isValid(json.MediaSourceCount) and json.MediaSourceCount > 1
|
||||
if isValid(json.MediaSources)
|
||||
m.top.mediaSources = []
|
||||
for each source in json.MediaSources
|
||||
m.top.mediaSources.push(source)
|
||||
end for
|
||||
end if
|
||||
end if
|
||||
|
||||
if json.ProductionYear <> invalid
|
||||
@ -38,20 +40,22 @@ 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 }
|
||||
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
|
||||
if m.top.json.BackdropImageTags <> invalid
|
||||
if m.top.json.BackdropImageTags[0] <> invalid
|
||||
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
|
||||
|
||||
end if
|
||||
|
@ -12,4 +12,5 @@
|
||||
<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" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||
</component>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -146,7 +146,13 @@ end sub
|
||||
'
|
||||
' Clear previous scene from group stack
|
||||
sub clearPreviousScene()
|
||||
m.groups.Delete(2)
|
||||
m.groups.pop()
|
||||
end sub
|
||||
|
||||
'
|
||||
' Delete scene from group stack at passed index
|
||||
sub deleteSceneAtIndex(index = 1)
|
||||
m.groups.Delete(index)
|
||||
end sub
|
||||
|
||||
'
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -14,6 +14,9 @@ sub init()
|
||||
m.SpecialFeaturesTask = CreateObject("roSGNode", "LoadItemsTask")
|
||||
m.SpecialFeaturesTask.itemsToLoad = "specialfeatures"
|
||||
m.SpecialFeaturesTask.observeField("content", "onSpecialFeaturesLoaded")
|
||||
m.LoadAdditionalPartsTask = CreateObject("roSGNode", "LoadItemsTask")
|
||||
m.LoadAdditionalPartsTask.itemsToLoad = "additionalparts"
|
||||
m.LoadAdditionalPartsTask.observeField("content", "onAdditionalPartsLoaded")
|
||||
m.LoadMoviesTask = CreateObject("roSGNode", "LoadItemsTask")
|
||||
m.LoadMoviesTask.itemsToLoad = "personMovies"
|
||||
m.LoadShowsTask = CreateObject("roSGNode", "LoadItemsTask")
|
||||
@ -28,10 +31,11 @@ sub updateSize()
|
||||
m.top.rowItemSpacing = [36, 36]
|
||||
end sub
|
||||
|
||||
sub loadPeople(data as object)
|
||||
sub loadParts(data as object)
|
||||
m.top.parentId = data.id
|
||||
m.LoadPeopleTask.peopleList = data.People
|
||||
m.LoadPeopleTask.control = "RUN"
|
||||
m.people = data.People
|
||||
m.LoadAdditionalPartsTask.itemId = m.top.parentId
|
||||
m.LoadAdditionalPartsTask.control = "RUN"
|
||||
end sub
|
||||
|
||||
sub loadPersonVideos(personId)
|
||||
@ -41,12 +45,32 @@ sub loadPersonVideos(personId)
|
||||
m.LoadMoviesTask.control = "RUN"
|
||||
end sub
|
||||
|
||||
sub onAdditionalPartsLoaded()
|
||||
parts = m.LoadAdditionalPartsTask.content
|
||||
m.LoadAdditionalPartsTask.unobserveField("content")
|
||||
|
||||
data = CreateObject("roSGNode", "ContentNode") ' The row Node
|
||||
m.top.content = data
|
||||
if parts <> invalid and parts.count() > 0
|
||||
row = buildRow("Additional Parts", parts, 464)
|
||||
addRowSize([464, 291])
|
||||
m.top.content.appendChild(row)
|
||||
m.top.rowItemSize = [[464, 291]]
|
||||
else
|
||||
m.top.rowItemSize = [[234, 396]]
|
||||
end if
|
||||
m.top.translation = "[75,10]"
|
||||
|
||||
' Load Cast and Crew and everything else...
|
||||
m.LoadPeopleTask.peopleList = m.people
|
||||
m.LoadPeopleTask.control = "RUN"
|
||||
end sub
|
||||
|
||||
sub onPeopleLoaded()
|
||||
people = m.LoadPeopleTask.content
|
||||
m.loadPeopleTask.unobserveField("content")
|
||||
data = CreateObject("roSGNode", "ContentNode") ' The row Node
|
||||
if people <> invalid and people.count() > 0
|
||||
row = data.createChild("ContentNode")
|
||||
row = m.top.content.createChild("ContentNode")
|
||||
row.Title = tr("Cast & Crew")
|
||||
for each person in people
|
||||
if person.json.type = "Actor" and person.json.Role <> invalid
|
||||
@ -58,9 +82,6 @@ sub onPeopleLoaded()
|
||||
row.appendChild(person)
|
||||
end for
|
||||
end if
|
||||
m.top.content = data
|
||||
m.top.translation = "[75,10]"
|
||||
m.top.rowItemSize = [[234, 396]]
|
||||
m.LikeThisTask.itemId = m.top.parentId
|
||||
m.LikeThisTask.control = "RUN"
|
||||
end sub
|
||||
@ -86,6 +107,7 @@ sub onLikeThisLoaded()
|
||||
end for
|
||||
addRowSize([234, 396])
|
||||
end if
|
||||
' Special Features next...
|
||||
m.SpecialFeaturesTask.itemId = m.top.parentId
|
||||
m.SpecialFeaturesTask.control = "RUN"
|
||||
end sub
|
||||
|
@ -5,7 +5,7 @@
|
||||
<field id="type" type="string" />
|
||||
<field id="parentId" type="string" />
|
||||
<field id="selectedItem" type="node" alwaysNotify="true" />
|
||||
<function name="loadPeople" />
|
||||
<function name="loadParts" />
|
||||
<function name="loadPersonVideos" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="ExtrasRowList.brs" />
|
||||
|
@ -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>
|
||||
|
@ -2,9 +2,16 @@ sub init()
|
||||
|
||||
m.itemText = m.top.findNode("itemText")
|
||||
m.itemPoster = m.top.findNode("itemPoster")
|
||||
m.itemProgress = m.top.findNode("progress")
|
||||
m.itemProgressBackground = m.top.findNode("progressBackground")
|
||||
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")
|
||||
|
||||
' Randomize the background colors
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
@ -19,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
|
||||
@ -59,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
|
||||
@ -74,6 +92,10 @@ sub itemContentChanged()
|
||||
if itemData.type = "Episode"
|
||||
m.itemText.text = itemData.json.SeriesName
|
||||
|
||||
if itemData.PlayedPercentage > 0
|
||||
drawProgressBar(itemData)
|
||||
end if
|
||||
|
||||
if itemData.usePoster = true
|
||||
m.itemPoster.uri = itemData.widePosterURL
|
||||
else
|
||||
@ -99,6 +121,10 @@ sub itemContentChanged()
|
||||
if itemData.type = "Movie"
|
||||
m.itemText.text = itemData.name
|
||||
|
||||
if itemData.PlayedPercentage > 0
|
||||
drawProgressBar(itemData)
|
||||
end if
|
||||
|
||||
' Use best image, but fallback to secondary if it's empty
|
||||
if (itemData.imageWidth = 180 and itemData.posterURL <> "") or itemData.thumbnailURL = ""
|
||||
m.itemPoster.uri = itemData.posterURL
|
||||
@ -126,6 +152,10 @@ sub itemContentChanged()
|
||||
if itemData.type = "Video"
|
||||
m.itemText.text = itemData.name
|
||||
|
||||
if itemData.PlayedPercentage > 0
|
||||
drawProgressBar(itemData)
|
||||
end if
|
||||
|
||||
if itemData.imageWidth = 180
|
||||
m.itemPoster.uri = itemData.posterURL
|
||||
else
|
||||
@ -133,6 +163,7 @@ sub itemContentChanged()
|
||||
end if
|
||||
return
|
||||
end if
|
||||
|
||||
if itemData.type = "Series"
|
||||
|
||||
m.itemText.text = itemData.name
|
||||
@ -170,10 +201,47 @@ sub itemContentChanged()
|
||||
return
|
||||
end if
|
||||
|
||||
if itemData.type = "MusicArtist"
|
||||
m.itemText.text = itemData.name
|
||||
m.itemTextExtra.text = itemData.json.AlbumArtist
|
||||
m.itemPoster.uri = ImageURL(itemData.id)
|
||||
return
|
||||
end if
|
||||
|
||||
if itemData.type = "Audio"
|
||||
m.itemText.text = itemData.name
|
||||
m.itemTextExtra.text = itemData.json.AlbumArtist
|
||||
m.itemPoster.uri = ImageURL(itemData.id)
|
||||
return
|
||||
end if
|
||||
|
||||
if itemData.type = "TvChannel"
|
||||
m.itemText.text = itemData.name
|
||||
m.itemTextExtra.text = itemData.json.AlbumArtist
|
||||
m.itemPoster.uri = ImageURL(itemData.id)
|
||||
return
|
||||
end if
|
||||
|
||||
if itemData.type = "Season"
|
||||
m.itemText.text = itemData.json.SeriesName
|
||||
m.itemTextExtra.text = itemData.name
|
||||
m.itemPoster.uri = ImageURL(itemData.id)
|
||||
return
|
||||
end if
|
||||
|
||||
print "Unhandled Home Item Type: " + itemData.type
|
||||
|
||||
end sub
|
||||
|
||||
'
|
||||
' Draws and animates item progress bar
|
||||
sub drawProgressBar(itemData)
|
||||
m.itemProgressBackground.width = itemData.imageWidth
|
||||
m.itemProgressBackground.visible = true
|
||||
m.showProgressBarField.keyValue = [0, m.itemPoster.width * (itemData.PlayedPercentage / 100)]
|
||||
m.showProgressBarAnimation.control = "Start"
|
||||
end sub
|
||||
|
||||
'
|
||||
' Enable title scrolling based on item Focus
|
||||
sub focusChanged()
|
||||
|
@ -3,9 +3,20 @@
|
||||
<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" />
|
||||
|
||||
<Animation id="showProgressBar" delay="1" duration="1" repeat="false" easeFunction="linear">
|
||||
<FloatFieldInterpolator id="showProgressBarField" key="[0.0, 1.0]" fieldToInterp="progress.width" />
|
||||
</Animation>
|
||||
</children>
|
||||
<interface>
|
||||
<field id="itemContent" type="node" onChange="itemContentChanged" />
|
||||
|
@ -19,13 +19,19 @@ sub init()
|
||||
' Load the Libraries from API via task
|
||||
m.LoadLibrariesTask = createObject("roSGNode", "LoadItemsTask")
|
||||
m.LoadLibrariesTask.observeField("content", "onLibrariesLoaded")
|
||||
|
||||
' set up tesk nodes for other rows
|
||||
m.LoadContinueTask = createObject("roSGNode", "LoadItemsTask")
|
||||
m.LoadContinueTask.itemsToLoad = "continue"
|
||||
|
||||
m.LoadNextUpTask = createObject("roSGNode", "LoadItemsTask")
|
||||
m.LoadNextUpTask.itemsToLoad = "nextUp"
|
||||
|
||||
m.LoadOnNowTask = createObject("roSGNode", "LoadItemsTask")
|
||||
m.LoadOnNowTask.itemsToLoad = "onNow"
|
||||
|
||||
m.LoadFavoritesTask = createObject("roSGNode", "LoadItemsTask")
|
||||
m.LoadFavoritesTask.itemsToLoad = "favorites"
|
||||
end sub
|
||||
|
||||
sub loadLibraries()
|
||||
@ -55,55 +61,85 @@ 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 >")
|
||||
|
||||
favoritesRow = content.CreateChild("HomeRow")
|
||||
favoritesRow.title = tr("Favorites")
|
||||
|
||||
sizeArray = [
|
||||
[464, 311], ' My Media
|
||||
[464, 331], ' Continue Watching
|
||||
[464, 331] ' Next Up
|
||||
[464, 331], ' Next Up
|
||||
[464, 331] ' Favorites
|
||||
]
|
||||
|
||||
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
|
||||
|
||||
' populate My Media row
|
||||
filteredMedia = filterNodeArray(m.libraryData, "id", userConfig.MyMediaExcludes)
|
||||
for each item in filteredMedia
|
||||
mediaRow.appendChild(item)
|
||||
end for
|
||||
|
||||
' create a "Latest In" row for each library
|
||||
filteredLatest = filterNodeArray(m.libraryData, "id", userConfig.LatestItemsExcludes)
|
||||
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"
|
||||
|
||||
' 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()
|
||||
@ -116,6 +152,51 @@ sub updateHomeRows()
|
||||
m.LoadContinueTask.control = "RUN"
|
||||
end sub
|
||||
|
||||
sub updateFavoritesItems()
|
||||
itemData = m.LoadFavoritesTask.content
|
||||
m.LoadFavoritesTask.unobserveField("content")
|
||||
m.LoadFavoritesTask.content = []
|
||||
|
||||
if itemData = invalid then return
|
||||
|
||||
homeRows = m.top.content
|
||||
rowIndex = getRowIndex("Favorites")
|
||||
|
||||
if itemData.count() < 1
|
||||
if rowIndex <> invalid
|
||||
' remove the row
|
||||
deleteFromSizeArray(rowIndex)
|
||||
homeRows.removeChildIndex(rowIndex)
|
||||
end if
|
||||
else
|
||||
' remake row using the new data
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = tr("Favorites")
|
||||
itemSize = [464, 331]
|
||||
|
||||
for each item in itemData
|
||||
usePoster = true
|
||||
|
||||
if lcase(item.type) = "episode" or lcase(item.type) = "audio" or lcase(item.type) = "musicartist"
|
||||
usePoster = false
|
||||
end if
|
||||
|
||||
item.usePoster = usePoster
|
||||
item.imageWidth = row.imageWidth
|
||||
row.appendChild(item)
|
||||
end for
|
||||
|
||||
if rowIndex = invalid
|
||||
' insert new row under "My Media"
|
||||
updateSizeArray(itemSize, 1)
|
||||
homeRows.insertChild(row, 1)
|
||||
else
|
||||
' replace the old row
|
||||
homeRows.replaceChild(row, rowIndex)
|
||||
end if
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub updateContinueItems()
|
||||
itemData = m.LoadContinueTask.content
|
||||
m.LoadContinueTask.unobserveField("content")
|
||||
@ -138,6 +219,10 @@ sub updateContinueItems()
|
||||
row.title = tr("Continue Watching")
|
||||
itemSize = [464, 331]
|
||||
for each item in itemData
|
||||
if item.json?.UserData?.PlayedPercentage <> invalid
|
||||
item.PlayedPercentage = item.json.UserData.PlayedPercentage
|
||||
end if
|
||||
|
||||
item.usePoster = row.usePoster
|
||||
item.imageWidth = row.imageWidth
|
||||
row.appendChild(item)
|
||||
@ -152,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()
|
||||
@ -207,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)
|
||||
|
@ -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,29 @@ sub loadItems()
|
||||
params["SortBy"] = "DatePlayed"
|
||||
params["SortOrder"] = "Descending"
|
||||
params["Filters"] = "IsResumable"
|
||||
params["EnableTotalRecordCount"] = false
|
||||
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
' Skip Books for now as we don't support it (issue #558)
|
||||
if item.Type <> "Book"
|
||||
tmp = CreateObject("roSGNode", "HomeData")
|
||||
tmp.json = item
|
||||
results.push(tmp)
|
||||
end if
|
||||
end for
|
||||
|
||||
else if m.top.itemsToLoad = "favorites"
|
||||
|
||||
url = Substitute("Users/{0}/Items", get_setting("active_user"))
|
||||
|
||||
params = {}
|
||||
params["Filters"] = "IsFavorite"
|
||||
params["Limit"] = 20
|
||||
params["recursive"] = true
|
||||
params["sortby"] = "random"
|
||||
params["EnableTotalRecordCount"] = false
|
||||
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
@ -151,6 +176,20 @@ sub loadItems()
|
||||
tmp.json = specfeat
|
||||
end for
|
||||
end if
|
||||
else if m.top.itemsToLoad = "additionalparts"
|
||||
additionalParts = api_API().videos.getAdditionalParts(m.top.itemId)
|
||||
if isValid(additionalParts)
|
||||
for each part in additionalParts.items
|
||||
tmp = CreateObject("roSGNode", "ExtrasData")
|
||||
params = {}
|
||||
params["Tags"] = part.ImageTags.Primary
|
||||
params["MaxWidth"] = 450
|
||||
params["MaxHeight"] = 402
|
||||
tmp.posterURL = ImageUrl(part.Id, "Primary", params)
|
||||
tmp.json = part
|
||||
results.push(tmp)
|
||||
end for
|
||||
end if
|
||||
else if m.top.itemsToLoad = "likethis"
|
||||
params = { "userId": get_setting("active_user"), "limit": 16 }
|
||||
url = Substitute("Items/{0}/Similar", m.top.itemId)
|
||||
|
@ -15,4 +15,5 @@
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
|
||||
</component>
|
||||
|
@ -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
|
||||
|
@ -44,7 +44,14 @@ end sub
|
||||
'Voice Search set
|
||||
sub channelsearchTermSet()
|
||||
m.scheduleGrid.jumpToChannel = 0
|
||||
if m.top.searchTerm <> invalid and m.LoadChannelsTask.searchTerm <> m.top.searchTerm
|
||||
'Reset filter if user says all
|
||||
if LCase(m.top.searchTerm) = LCase(tr("all")) or m.LoadChannelsTask.searchTerm = LCase(tr("all"))
|
||||
m.top.searchTerm = " "
|
||||
m.LoadChannelsTask.searchTerm = " "
|
||||
m.spinner.visible = true
|
||||
m.LoadChannelsTask.control = "RUN"
|
||||
'filter if the searterm is not invalid
|
||||
else if m.top.searchTerm <> invalid and LCase(m.LoadChannelsTask.searchTerm) <> LCase(m.top.searchTerm)
|
||||
if m.LoadChannelsTask.state = "run" then m.LoadChannelsTask.control = "stop"
|
||||
|
||||
m.LoadChannelsTask.searchTerm = m.top.searchTerm
|
||||
@ -279,6 +286,7 @@ end sub
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
if not press then return false
|
||||
|
||||
detailsGrp = m.top.findNode("detailsPane")
|
||||
gridGrp = m.top.findNode("scheduleGrid")
|
||||
|
||||
@ -288,6 +296,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
gridGrp.setFocus(true)
|
||||
return true
|
||||
else if key = "back"
|
||||
m.LoadChannelsTask.control = "stop"
|
||||
m.global.sceneManager.callFunc("popScene")
|
||||
return true
|
||||
end if
|
||||
|
129
components/manager/QueueManager.brs
Normal file
129
components/manager/QueueManager.brs
Normal 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
|
24
components/manager/QueueManager.xml
Normal file
24
components/manager/QueueManager.xml
Normal 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>
|
6
components/manager/ViewCreator.brs
Normal file
6
components/manager/ViewCreator.brs
Normal 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
|
@ -58,12 +58,20 @@ sub itemContentChanged()
|
||||
' Handle all "As Is" fields
|
||||
m.top.overhangTitle = itemData.name
|
||||
setFieldText("releaseYear", itemData.productionYear)
|
||||
setFieldText("officialRating", itemData.officialRating)
|
||||
setFieldText("overview", itemData.overview)
|
||||
|
||||
if itemData.officialRating <> invalid
|
||||
setFieldText("officialRating", itemData.officialRating)
|
||||
else
|
||||
m.top.findNode("infoGroup").removeChild(m.top.findNode("officialRating"))
|
||||
end if
|
||||
|
||||
if itemData.communityRating <> invalid
|
||||
setFieldText("communityRating", int(itemData.communityRating * 10) / 10)
|
||||
m.top.findNode("communityRatingGroup").visible = true
|
||||
m.top.findNode("star").visible = "true"
|
||||
else
|
||||
' hide the star icon
|
||||
m.top.findNode("infoGroup").removeChild(m.top.findNode("communityRatingGroup"))
|
||||
end if
|
||||
|
||||
if itemData.CriticRating <> invalid
|
||||
@ -87,6 +95,8 @@ sub itemContentChanged()
|
||||
|
||||
if itemData.genres.count() > 0
|
||||
setFieldText("genres", tr("Genres") + ": " + itemData.genres.join(", "))
|
||||
else
|
||||
m.top.findNode("details").removeChild(m.top.findNode("genres"))
|
||||
end if
|
||||
|
||||
' show tags if there are no genres to display
|
||||
@ -102,10 +112,8 @@ sub itemContentChanged()
|
||||
end for
|
||||
if directors.count() > 0
|
||||
setFieldText("director", tr("Director") + ": " + directors.join(", "))
|
||||
end if
|
||||
|
||||
if itemData.mediaStreams[0] <> invalid
|
||||
setFieldText("video_codec", tr("Video") + ": " + itemData.mediaStreams[0].displayTitle)
|
||||
else
|
||||
m.top.findNode("details").removeChild(m.top.findNode("director"))
|
||||
end if
|
||||
|
||||
if get_user_setting("ui.details.hidetagline") = "false"
|
||||
@ -116,6 +124,15 @@ sub itemContentChanged()
|
||||
m.details.removeChild(m.tagline)
|
||||
end if
|
||||
|
||||
'set aired date if type is Episode
|
||||
if itemData.PremiereDate <> invalid and itemData.Type = "Episode"
|
||||
airDate = CreateObject("roDateTime")
|
||||
airDate.FromISO8601String(itemData.PremiereDate)
|
||||
m.top.findNode("aired").text = tr("Aired") + ": " + airDate.AsDateString("short-month-no-weekday")
|
||||
'remove movie release year label
|
||||
m.top.findNode("infoGroup").removeChild(m.top.findNode("releaseYear"))
|
||||
end if
|
||||
|
||||
setFavoriteColor()
|
||||
setWatchedColor()
|
||||
SetUpVideoOptions(itemData.mediaSources)
|
||||
@ -128,11 +145,28 @@ end sub
|
||||
sub SetUpVideoOptions(streams)
|
||||
|
||||
videos = []
|
||||
codecDetailsSet = false
|
||||
|
||||
for i = 0 to streams.Count() - 1
|
||||
if streams[i].VideoType = "VideoFile"
|
||||
codec = ""
|
||||
if streams[i].mediaStreams <> invalid and streams[i].mediaStreams.Count() > 0 then codec = streams[i].mediaStreams[0].displayTitle
|
||||
if streams[i].mediaStreams <> invalid and streams[i].mediaStreams.Count() > 0
|
||||
|
||||
' find the first (default) video track to get the codec for the details screen
|
||||
if codecDetailsSet = false
|
||||
for index = 0 to streams[i].mediaStreams.Count() - 1
|
||||
if streams[i].mediaStreams[index].Type = "Video"
|
||||
setFieldText("video_codec", tr("Video") + ": " + streams[i].mediaStreams[index].displayTitle)
|
||||
codecDetailsSet = true
|
||||
exit for
|
||||
end if
|
||||
end for
|
||||
end if
|
||||
|
||||
codec = streams[i].mediaStreams[0].displayTitle
|
||||
end if
|
||||
|
||||
' Create options for user to switch between video tracks
|
||||
videos.push({
|
||||
"Title": streams[i].Name,
|
||||
"Description": tr("Video"),
|
||||
|
@ -9,7 +9,7 @@
|
||||
<Label id="runtime" />
|
||||
<Label id="officialRating" />
|
||||
<LayoutGroup id="communityRatingGroup" layoutDirection="horiz" itemSpacings="[-5]">
|
||||
<Poster id="star" uri="pkg:/images/sharp_star_white_18dp.png" height="32" width="32" blendColor="#cb272a" />
|
||||
<Poster id="star" uri="pkg:/images/sharp_star_white_18dp.png" height="32" width="32" blendColor="#cb272a" visible="false"/>
|
||||
<Label id="communityRating" />
|
||||
</LayoutGroup>
|
||||
<LayoutGroup layoutDirection="horiz" itemSpacings="[-5]" id="criticRatingGroup">
|
||||
@ -17,6 +17,7 @@
|
||||
<Label id="criticRatingLabel" />
|
||||
</LayoutGroup>
|
||||
<Label id="ends-at" />
|
||||
<Label id="aired" />
|
||||
</LayoutGroup>
|
||||
<Label id="genres" />
|
||||
<Label id="director" />
|
||||
|
@ -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")
|
||||
@ -408,7 +446,7 @@ sub onMetaDataLoaded()
|
||||
if data <> invalid and data.count() > 0
|
||||
|
||||
' Use metadata to load backdrop image
|
||||
if isvalid(data.json)
|
||||
if isValid(data.json)
|
||||
if isValid(data.json.ArtistItems)
|
||||
if data.json.ArtistItems.count() > 0
|
||||
if isValid(data.json.ArtistItems[0].id)
|
||||
@ -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")
|
@ -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" />
|
@ -1,21 +1,123 @@
|
||||
sub init()
|
||||
m.top.optionsAvailable = false ' Change once Shuffle option is added
|
||||
m.top.optionsAvailable = true
|
||||
m.top.overhangVisible = false
|
||||
m.slideshowTimer = m.top.findNode("slideshowTimer")
|
||||
m.slideshowTimer.observeField("fire", "nextSlide")
|
||||
m.status = m.top.findNode("status")
|
||||
m.textBackground = m.top.findNode("background")
|
||||
m.statusTimer = m.top.findNode("statusTimer")
|
||||
m.statusTimer.observeField("fire", "statusUpdate")
|
||||
m.slideshow = get_user_setting("photos.slideshow")
|
||||
m.random = get_user_setting("photos.random")
|
||||
|
||||
m.showStatusAnimation = m.top.findNode("showStatusAnimation")
|
||||
m.hideStatusAnimation = m.top.findNode("hideStatusAnimation")
|
||||
|
||||
itemContentChanged()
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
m.LoadLibrariesTask = createObject("roSGNode", "LoadPhotoTask")
|
||||
m.LoadLibrariesTask.itemContent = m.top.itemContent
|
||||
m.LoadLibrariesTask.observeField("results", "onPhotoLoaded")
|
||||
m.LoadLibrariesTask.control = "RUN"
|
||||
if isValidToContinue(m.top.itemIndex)
|
||||
m.LoadLibrariesTask = createObject("roSGNode", "LoadPhotoTask")
|
||||
itemContent = m.top.items.content.getChild(m.top.itemIndex)
|
||||
m.LoadLibrariesTask.itemContent = itemContent
|
||||
m.LoadLibrariesTask.observeField("results", "onPhotoLoaded")
|
||||
m.LoadLibrariesTask.control = "RUN"
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub onPhotoLoaded()
|
||||
if m.LoadLibrariesTask.results <> invalid
|
||||
photo = m.top.findNode("photo")
|
||||
photo.uri = m.LoadLibrariesTask.results
|
||||
|
||||
if m.slideshow = "true" or m.random = "true"
|
||||
' user has requested either a slideshow or random...
|
||||
m.slideshowTimer.control = "start"
|
||||
end if
|
||||
else
|
||||
'Show user error here (for example if it's not a supported image type)
|
||||
message_dialog("This image type is not supported.")
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub nextSlide()
|
||||
m.slideshowTimer.control = "stop"
|
||||
|
||||
if m.slideshow = "true"
|
||||
if isValidToContinue(m.top.itemIndex + 1)
|
||||
m.top.itemIndex++
|
||||
m.slideshowTimer.control = "start"
|
||||
end if
|
||||
else if m.random = "true"
|
||||
index = rnd(m.top.items.content.getChildCount() - 1)
|
||||
if isValidToContinue(index)
|
||||
m.top.itemIndex = index
|
||||
m.slideshowTimer.control = "start"
|
||||
end if
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub statusUpdate()
|
||||
m.statusTimer.control = "stop"
|
||||
m.hideStatusAnimation.control = "start"
|
||||
end sub
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
if not press then return false
|
||||
|
||||
if key = "right"
|
||||
if isValidToContinue(m.top.itemIndex + 1)
|
||||
m.slideshowTimer.control = "stop"
|
||||
m.top.itemIndex++
|
||||
end if
|
||||
return true
|
||||
end if
|
||||
|
||||
if key = "left"
|
||||
if isValidToContinue(m.top.itemIndex - 1)
|
||||
m.slideshowTimer.control = "stop"
|
||||
m.top.itemIndex--
|
||||
end if
|
||||
return true
|
||||
end if
|
||||
|
||||
if key = "play"
|
||||
if m.slideshowTimer.control = "start"
|
||||
' stop the slideshow if the user hits "pause"
|
||||
m.slideshowTimer.control = "stop"
|
||||
m.status.text = tr("Slideshow Paused")
|
||||
if m.textBackground.opacity = 0
|
||||
m.showStatusAnimation.control = "start"
|
||||
end if
|
||||
m.statusTimer.control = "start"
|
||||
else
|
||||
' start the slideshow if the user hits "play"
|
||||
m.status.text = tr("Slideshow Resumed")
|
||||
if m.textBackground.opacity = 0
|
||||
m.showStatusAnimation.control = "start"
|
||||
end if
|
||||
m.slideshow = "true"
|
||||
m.statusTimer.control = "start"
|
||||
m.slideshowTimer.control = "start"
|
||||
end if
|
||||
return true
|
||||
end if
|
||||
|
||||
if key = "options"
|
||||
' Options (random etc) is done on itemGrid
|
||||
return true
|
||||
end if
|
||||
|
||||
return false
|
||||
end function
|
||||
|
||||
function isValidToContinue(index as integer)
|
||||
if isValid(m.top.items) and isValid(m.top.items.content)
|
||||
if index >= 0 and index < m.top.items.content.getChildCount()
|
||||
return true
|
||||
end if
|
||||
end if
|
||||
|
||||
return false
|
||||
end function
|
||||
|
@ -1,13 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="PhotoDetails" extends="JFGroup">
|
||||
<children>
|
||||
<LayoutGroup id="toplevel">
|
||||
<Poster id="photo" width="1920" height="1080" loadDisplayMode="scaleToFit"/>
|
||||
</LayoutGroup>
|
||||
<Poster id="photo" width="1920" height="1080" loadDisplayMode="scaleToFit"/>
|
||||
<Rectangle id="background" color="0x101010EE" height="120" width="500" Translation="[700, -150]" opacity="0">
|
||||
<Label id="status" font="font:MediumSystemFont" height="100" width="500" horizAlign="center" vertAlign="bottom"/>
|
||||
</Rectangle>
|
||||
<Timer id="slideshowTimer" duration="5" repeat="false" />
|
||||
<Timer id="statusTimer" duration="2" repeat="false" />
|
||||
|
||||
<Animation id="showStatusAnimation" duration="1" repeat="false">
|
||||
<FloatFieldInterpolator key="[0.0, 0.1]" keyValue="[0, 1]" fieldToInterp="background.opacity" />
|
||||
<Vector2DFieldInterpolator key="[0.1, 1]" keyValue="[[700, -150], [700, -5]]" fieldToInterp="background.translation" />
|
||||
</Animation>
|
||||
<Animation id="hideStatusAnimation" duration="1" repeat="false">
|
||||
<Vector2DFieldInterpolator key="[0.0, 0.9]" keyValue="[[700, -5], [700, -150]]" fieldToInterp="background.translation" />
|
||||
<FloatFieldInterpolator key="[0.9, 1]" keyValue="[1, 0]" fieldToInterp="background.opacity" />
|
||||
</Animation>
|
||||
|
||||
</children>
|
||||
<interface>
|
||||
<field id="itemContent" type="node" onChange="itemContentChanged" />
|
||||
<field id="items" type="node" />
|
||||
<field id="itemIndex" type="integer" value="-1" onChange="itemContentChanged" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="PhotoDetails.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||
</component>
|
||||
|
@ -1,17 +0,0 @@
|
||||
sub init()
|
||||
m.top.functionName = "loadItems"
|
||||
end sub
|
||||
|
||||
sub loadItems()
|
||||
item = m.top.itemContent
|
||||
|
||||
group = CreateObject("roSGNode", "PhotoDetails")
|
||||
group.optionsAvailable = false
|
||||
m.global.sceneManager.callFunc("pushScene", group)
|
||||
|
||||
group.itemContent = item
|
||||
|
||||
' TODO/FIXME:
|
||||
' Wait some time and move to the next photo...
|
||||
|
||||
end sub
|
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
|
||||
<component name="PhotoPlayerTask" extends="Task">
|
||||
<interface>
|
||||
<field id="itemContent" type="node" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="PhotoPlayerTask.brs" />
|
||||
</component>
|
@ -48,7 +48,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
m.searchAlphabox.textEditBox.translation = "[0, 0]"
|
||||
end if
|
||||
|
||||
if key = "left" and m.searchSelect.isinFocusChain() and (m.searchSelect.currFocusColumn = -1 or m.searchSelect.currFocusColumn = 0)
|
||||
if key = "left" and m.searchSelect.isinFocusChain()
|
||||
m.searchAlphabox.setFocus(true)
|
||||
return true
|
||||
else if key = "right"
|
||||
|
@ -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,14 @@ 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 = "options"
|
||||
m.global.sceneManager.callFunc("popScene")
|
||||
return true
|
||||
end if
|
||||
|
||||
if key = "right"
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -5,6 +5,14 @@ sub init()
|
||||
m.overview = m.top.findNode("overview")
|
||||
m.poster = m.top.findNode("poster")
|
||||
m.deviceInfo = CreateObject("roDeviceInfo")
|
||||
|
||||
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()
|
||||
@ -18,6 +26,12 @@ sub itemContentChanged()
|
||||
m.title.text = indexNumber + item.title
|
||||
m.overview.text = item.overview
|
||||
|
||||
if itemData.PremiereDate <> invalid
|
||||
airDate = CreateObject("roDateTime")
|
||||
airDate.FromISO8601String(itemData.PremiereDate)
|
||||
m.top.findNode("aired").text = tr("Aired") + ": " + airDate.AsDateString("short-month-no-weekday")
|
||||
end if
|
||||
|
||||
imageUrl = item.posterURL
|
||||
|
||||
if get_user_setting("ui.tvshows.blurunwatched") = "true"
|
||||
@ -30,18 +44,43 @@ sub itemContentChanged()
|
||||
|
||||
m.poster.uri = imageUrl
|
||||
|
||||
if type(itemData.RunTimeTicks) = "LongInteger"
|
||||
m.top.findNode("runtime").text = stri(getRuntime()).trim() + " mins"
|
||||
if type(itemData.RunTimeTicks) = "roInt" or type(itemData.RunTimeTicks) = "LongInteger"
|
||||
runTime = getRuntime()
|
||||
if runTime < 2
|
||||
m.top.findNode("runtime").text = "1 min"
|
||||
else
|
||||
m.top.findNode("runtime").text = stri(runTime).trim() + " mins"
|
||||
end if
|
||||
|
||||
if get_user_setting("ui.design.hideclock") <> "true"
|
||||
m.top.findNode("endtime").text = tr("Ends at %1").Replace("%1", getEndTime())
|
||||
end if
|
||||
end if
|
||||
|
||||
if itemData.communityRating <> invalid
|
||||
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
|
||||
|
@ -2,19 +2,27 @@
|
||||
<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" />
|
||||
<ScrollingLabel id="title" font="font:MediumBoldSystemFont" maxWidth="950" />
|
||||
<LayoutGroup layoutDirection="horiz" itemSpacings="[20]">
|
||||
<LayoutGroup id="infoBar" layoutDirection="horiz" itemSpacings="[20]">
|
||||
<Label id="runtime" font="font:SmallestSystemFont" />
|
||||
<LayoutGroup layoutDirection="horiz" itemSpacings="[-5]">
|
||||
<LayoutGroup id="rating" layoutDirection="horiz" itemSpacings="[-5]">
|
||||
<Poster id="star" uri="pkg:/images/sharp_star_white_18dp.png" height="26" width="26" blendColor="#cb272a" />
|
||||
<Label id="communityRating" font="font:SmallestSystemFont" />
|
||||
</LayoutGroup>
|
||||
<Label id="endtime" font="font:SmallestSystemFont" />
|
||||
<Label id="aired" font="font:SmallestSystemFont" />
|
||||
</LayoutGroup>
|
||||
<Label id="overview" font="font:SmallestSystemFont" wrap="true" height="130" width="950" maxLines="3" ellipsizeOnBoundary="true"/>
|
||||
<LayoutGroup layoutDirection="horiz" itemSpacings="[15]">
|
||||
|
@ -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,18 +15,42 @@ 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
|
||||
m.top.overhangTitle = itemData.name
|
||||
setFieldText("releaseYear", itemData.productionYear)
|
||||
setFieldText("officialRating", itemData.officialRating)
|
||||
|
||||
'Check production year, if invalid remove label
|
||||
if itemData.productionYear <> invalid
|
||||
setFieldText("releaseYear", itemData.productionYear)
|
||||
else
|
||||
m.top.findNode("main_group").removeChild(m.top.findNode("releaseYear"))
|
||||
end if
|
||||
|
||||
'Check officialRating, if invalid remove label
|
||||
if itemData.officialRating <> invalid
|
||||
setFieldText("officialRating", itemData.officialRating)
|
||||
else
|
||||
m.top.findNode("main_group").removeChild(m.top.findNode("officialRating"))
|
||||
end if
|
||||
|
||||
'Check communityRating, if invalid remove label
|
||||
if itemData.communityRating <> invalid
|
||||
m.top.findNode("star").visible = true
|
||||
setFieldText("communityRating", int(itemData.communityRating * 10) / 10)
|
||||
else
|
||||
m.top.findNode("main_group").removeChild(m.top.findNode("communityRating"))
|
||||
m.top.findNode("main_group").removeChild(m.top.findNode("star"))
|
||||
m.top.findNode("star").visible = false
|
||||
end if
|
||||
|
||||
setFieldText("overview", itemData.overview)
|
||||
|
||||
|
||||
@ -32,11 +58,17 @@ sub itemContentChanged()
|
||||
setFieldText("runtime", stri(getRuntime()) + " mins")
|
||||
end if
|
||||
|
||||
'History feild is set via the function getHistory()
|
||||
setFieldText("history", getHistory())
|
||||
|
||||
'Check genres, if invalid remove label
|
||||
if itemData.genres.count() > 0
|
||||
setFieldText("genres", itemData.genres.join(", "))
|
||||
else
|
||||
m.top.findNode("main_group").removeChild(m.top.findNode("genres"))
|
||||
end if
|
||||
|
||||
'We don't display Directors in the show page. Might want to remove this.
|
||||
for each person in itemData.people
|
||||
if person.type = "Director"
|
||||
exit for
|
||||
@ -44,6 +76,8 @@ sub itemContentChanged()
|
||||
end for
|
||||
if itemData.taglines.count() > 0
|
||||
setFieldText("tagline", itemData.taglines[0])
|
||||
else
|
||||
m.top.findNode("main_group").removeChild(m.top.findNode("tagline"))
|
||||
end if
|
||||
end sub
|
||||
|
||||
@ -105,6 +139,7 @@ function getHistory() as string
|
||||
end if
|
||||
|
||||
if studio = invalid and airwords = invalid
|
||||
m.top.findNode("main_group").removeChild(m.top.findNode("history"))
|
||||
return ""
|
||||
end if
|
||||
|
||||
|
@ -1,15 +1,19 @@
|
||||
<?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" />
|
||||
<Label id="officialRating" />
|
||||
<LayoutGroup layoutDirection="horiz" itemSpacings="[3]">
|
||||
<Poster id="star" uri="pkg:/images/sharp_star_white_18dp.png" height="32" width="32" blendColor="#cb272a" />
|
||||
<Poster id="star" uri="pkg:/images/sharp_star_white_18dp.png" height="32" width="32" blendColor="#cb272a" visible="false" />
|
||||
<Label id="communityRating" />
|
||||
</LayoutGroup>
|
||||
</LayoutGroup>
|
||||
|
BIN
images/icons/musicFolder.png
Normal file
BIN
images/icons/musicFolder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -3648,5 +3648,745 @@
|
||||
<translation>Úroveň</translation>
|
||||
<extracomment>Video profile level</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Uložit přihlašovací údaje?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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'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 "views" 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 & Crew</source>
|
||||
<translation>Obsazení & š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 'Next Up' list without watching it.</source>
|
||||
<translation>Nastavení maximálního počtu dní, pro setrvání pořadu v seznamu "Další díly", 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 'OK' to Close</source>
|
||||
<translation>Pro zavření stiskni 'OK'</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 -> Media Grid section in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Item Titles</source>
|
||||
<translation>Názvy položek</translation>
|
||||
<extracomment>UI -> Media Grid -> 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 -> Media Grid -> 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 -> Media Grid -> 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'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'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
File diff suppressed because it is too large
Load Diff
@ -285,18 +285,33 @@
|
||||
<source>More Like This</source>
|
||||
<translation>More Like This</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press 'OK' to Close</source>
|
||||
<translation>Press 'OK' to Close</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Special Features</source>
|
||||
<translation>Special Features</translation>
|
||||
<message>
|
||||
<source>Press 'OK' to Close</source>
|
||||
<translation>Press 'OK' to Close</translation>
|
||||
</message>
|
||||
</message>
|
||||
<message>
|
||||
<source>Additional Parts</source>
|
||||
<translation>Additional Parts</translation>
|
||||
<extracomment>Additional parts of a video</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
@ -479,16 +494,47 @@
|
||||
<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>
|
||||
<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>** 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>Enabled</source>
|
||||
<translation>Enabled</translation>
|
||||
@ -642,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>
|
||||
@ -703,10 +759,34 @@
|
||||
<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>Next episode</source>
|
||||
<translation>Next episode</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Play Trailer</source>
|
||||
<translation>Play Trailer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
<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>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>
|
||||
<translation>Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</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>
|
||||
@ -817,5 +897,157 @@
|
||||
<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>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>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>
|
||||
<source>all</source>
|
||||
<translation>all</translation>
|
||||
<extracomment>all will reset the searchTerm so all data will be availible</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>Slideshow On</source>
|
||||
<translation>Slideshow On</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Slideshow Paused</source>
|
||||
<translation>Slideshow Paused</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Slideshow Resumed</source>
|
||||
<translation>Slideshow Resumed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Random Off</source>
|
||||
<translation>Random Off</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Random On</source>
|
||||
<translation>Random On</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MPEG-4 Support</source>
|
||||
<translation>MPEG-4 Support</translation>
|
||||
<extracomment>Settings Menu - Title 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>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>
|
||||
|
@ -1614,5 +1614,104 @@
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Guardar credenciales?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Guardar Credenciales?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Retrieving Content</source>
|
||||
<translation>Error al recuperar contenido</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>Se ha encontrado un error mientras se reproducía este ítem.</translation>
|
||||
<extracomment>Dialog detail when error occurs during playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error loading Channel Data</source>
|
||||
<translation>Error al cargar datos del canal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Movies</source>
|
||||
<translation>Peliculas</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Shows</source>
|
||||
<translation>Programa de TV</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>today</source>
|
||||
<translation>Hoy</translation>
|
||||
<extracomment>Current day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>yesterday</source>
|
||||
<translation>Ayer</translation>
|
||||
<extracomment>Previous day</extracomment>
|
||||
</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>
|
||||
<comment>Name or Title field of media item</comment>
|
||||
<source>TITLE</source>
|
||||
<translation>Nombre</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CRITIC_RATING</source>
|
||||
<translation>Calificación de los Críticos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Monday</source>
|
||||
<translation>Lunes</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Loading Channel Data</source>
|
||||
<translation>Cargando información del canal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete Saved</source>
|
||||
<translation>Borrado confirmado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>There was an error retrieving the data for this item from the server.</source>
|
||||
<translation>Ocurrió un error al recuperar los datos para este ítem desde el servidor.</translation>
|
||||
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to load Channel Data from the server</source>
|
||||
<translation>No se pudo cargar los datos del canal desde el servidor</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
|
||||
<source>NO_ITEMS</source>
|
||||
<translation>Este %1 no contiene ítems</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>IMDB_RATING</source>
|
||||
<translation>Calificación IMDb</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Special Features</source>
|
||||
<translation>Funciones especiales</translation>
|
||||
<message>
|
||||
<source>Press 'OK' to Close</source>
|
||||
<translation>Press 'OK' to Close</translation>
|
||||
</message>
|
||||
</message>
|
||||
<message>
|
||||
<source>tomorrow</source>
|
||||
<translation>Mañana</translation>
|
||||
<extracomment>Next day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sunday</source>
|
||||
<translation>Domingo</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
@ -1328,5 +1328,22 @@
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Guardar credenciales?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Credentials?</source>
|
||||
<translation>¿Guardar credenciales?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete Saved</source>
|
||||
<translation>Eliminar guardado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>On Now</source>
|
||||
<translation>Ahora</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Retrieving Content</source>
|
||||
<translation>Error al recuperar contenido</translation>
|
||||
<extracomment>Dialog title when unable to load Content from Server</extracomment>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
@ -1884,5 +1884,816 @@
|
||||
<source>TITLE</source>
|
||||
<translation>Nombre</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error loading Channel Data</source>
|
||||
<translation>Error de Reproducción de Contenido de Canal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sign Out</source>
|
||||
<translation>Terminar Sesión</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete Saved</source>
|
||||
<translation>Borrar Credenciales</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Age</source>
|
||||
<translation>Edad</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CRITIC_RATING</source>
|
||||
<translation>Criticas Raiting</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Born</source>
|
||||
<translation>Nacido/a</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>today</source>
|
||||
<translation>hoy</translation>
|
||||
<extracomment>Current day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Loading Channel Data</source>
|
||||
<translation>Reproduciendo Contenido de Canal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Channels</source>
|
||||
<translation>Canales</translation>
|
||||
<extracomment>Menu option for showing Live TV Channel List</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Died</source>
|
||||
<translation>Muerto/a</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View Channel</source>
|
||||
<translation>Ver Canales</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>There was an error retrieving the data for this item from the server.</source>
|
||||
<translation>Ha ocurrido un error tratando de recuperar la información 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>IMDB_RATING</source>
|
||||
<translation>IMDb Raiting</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>DATE_ADDED</source>
|
||||
<translation>Fecha Agregada</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>DATE_PLAYED</source>
|
||||
<translation>Fecha Reproducida</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Movies</source>
|
||||
<translation>Películas</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>Tuesday</source>
|
||||
<translation>Martes</translation>
|
||||
<extracomment>Day of Week</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>
|
||||
<comment>Title of Tab for options to filter library content</comment>
|
||||
<source>TAB_FILTER</source>
|
||||
<translation>Filtro</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>Wednesday</source>
|
||||
<translation>Miercoles</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Saturday</source>
|
||||
<translation>Sabado</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started at</source>
|
||||
<translation>Comienza a</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started 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>Repeat</source>
|
||||
<translation>Repetir</translation>
|
||||
<extracomment>If TV Shows has previously been broadcasted</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Guide</source>
|
||||
<translation>Guia de Television</translation>
|
||||
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record</source>
|
||||
<translation>Grabar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record Series</source>
|
||||
<translation>Grabar Series</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Close</source>
|
||||
<translation>Cerrar</translation>
|
||||
</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>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>Save Credentials?</source>
|
||||
<translation>Guardar Credenciales?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Name or Title field of media item</comment>
|
||||
<source>TITLE</source>
|
||||
<translation>Nombre</translation>
|
||||
</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>Error Retrieving Content</source>
|
||||
<translation>Error Recuperando Contenido</translation>
|
||||
<extracomment>Dialog title when unable to load Content from Server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>OFFICIAL_RATING</source>
|
||||
<translation>Padres Raiting</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Change Server</source>
|
||||
<translation>Cambiar de Servidor</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 switching "views" when looking at a library</comment>
|
||||
<source>TAB_VIEW</source>
|
||||
<translation>Vista</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>RUNTIME</source>
|
||||
<translation>Tiempo de Ejecución</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>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>RELEASE_DATE</source>
|
||||
<translation>Fecha de Premiere</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PLAY_COUNT</source>
|
||||
<translation>Cuenta de Reproducción</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>More Like This</source>
|
||||
<translation>Mas de este Estilo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Special Features</source>
|
||||
<translation>Funciones Especiales</translation>
|
||||
<message>
|
||||
<source>Press 'OK' to Close</source>
|
||||
<translation>Press 'OK' to Close</translation>
|
||||
</message>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Shows</source>
|
||||
<translation>Programas de Televisión</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ends at</source>
|
||||
<translation>Termina a</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
|
||||
</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 Series</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pick a Jellyfin server from the local network</source>
|
||||
<translation>Elige un servidor Jellyfin disponible de 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>Started</source>
|
||||
<translation>Comenzó</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>Comenzó a</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>Comienza</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ó</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter the server name or IP address</source>
|
||||
<translation>Agregar el nombre del servidor o direccion 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 de 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>...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>There was an error retrieving the data for this item from the server.</source>
|
||||
<translation>Ha ocurrido un error tratando de recuperar la información desde el servidor.</translation>
|
||||
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Loading Channel Data</source>
|
||||
<translation>Reproduciendo Contenido de Canal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
|
||||
<source>NO_ITEMS</source>
|
||||
<translation>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 & 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 -> 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 "views" 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 -> Media Grid -> 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 'OK' to Close</source>
|
||||
<translation>Presiona 'OK' 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 -> Media Grid -> 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 -> Media Grid -> 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>
|
||||
<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 el botón de reproducción para animar lentamente al primer elemento de la carpeta. (Si está deshabilitado, la carpeta se restablecerá al primer elemento inmediatamente).</translation>
|
||||
<extracomment>Description for option in Setting Screen</extracomment>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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'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'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'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
@ -1718,5 +1718,239 @@
|
||||
<source>TAB_FILTER</source>
|
||||
<translation>Filtro</translation>
|
||||
</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>Cast & Crew</source>
|
||||
<translation>Cast & Crew</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Shows</source>
|
||||
<translation>Serie TV</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wednesday</source>
|
||||
<translation>Mercoledi</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Thursday</source>
|
||||
<translation>Giovedi</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started</source>
|
||||
<translation>Iniziato</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</source>
|
||||
<translation>Inizia</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>Ends at</source>
|
||||
<translation>Termina alle</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Guide</source>
|
||||
<translation>Guida TV</translation>
|
||||
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connecting to Server</source>
|
||||
<translation>In Connessione al Server</translation>
|
||||
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>The requested content does not exist on the server</source>
|
||||
<translation>Il contenuto richiesto non esiste sul server</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>Inserisci il nome o l'IP del server</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>Scegli un server Jellyfin dalla rete locale</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>...or enter server URL manually:</source>
|
||||
<translation>Se il server non è nella lista, puoi anche inserire l'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>Errore nel recupero delle informazioni di riproduzione</translation>
|
||||
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tuesday</source>
|
||||
<translation>Martedi</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>today</source>
|
||||
<translation>oggi</translation>
|
||||
<extracomment>Current day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Close</source>
|
||||
<translation>Chiudi</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press 'OK' to Close</source>
|
||||
<translation>Premi "OK" per Chiudere</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Movies</source>
|
||||
<translation>Film</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>yesterday</source>
|
||||
<translation>ieri</translation>
|
||||
<extracomment>Previous day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>tomorrow</source>
|
||||
<translation>domani</translation>
|
||||
<extracomment>Next day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sunday</source>
|
||||
<translation>Domenica</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Monday</source>
|
||||
<translation>Lunedi</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Friday</source>
|
||||
<translation>Venerdi</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Saturday</source>
|
||||
<translation>Sabato</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started at</source>
|
||||
<translation>Iniziato il</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>Inizia il</translation>
|
||||
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel Recording</source>
|
||||
<translation>Interrompi Registrazione</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel Series Recording</source>
|
||||
<translation>Interrompi Registrazione Seria</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not found</source>
|
||||
<translation>Non trovato</translation>
|
||||
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ended at</source>
|
||||
<translation>Terminato alle</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Live</source>
|
||||
<translation>In diretta</translation>
|
||||
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Repeat</source>
|
||||
<translation>Replica</translation>
|
||||
<extracomment>If TV Shows has previously been broadcasted</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Channels</source>
|
||||
<translation>Canali</translation>
|
||||
<extracomment>Menu option for showing Live TV Channel List</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>View Channel</source>
|
||||
<translation>Visione del Canale</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record</source>
|
||||
<translation>Registra</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unknown</source>
|
||||
<translation>Sconosciuto</translation>
|
||||
<extracomment>Title for a cast member for which we have no information for</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>OFFICIAL_RATING</source>
|
||||
<translation>Controllo Parentale</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pick a Jellyfin server from the local network</source>
|
||||
<translation>Scegli un server Jellyfin dalla rete locale:</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Salvare le credenziali?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Change Server</source>
|
||||
<translation>Cambia server</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error During Playback</source>
|
||||
<translation>Errore durante la riproduzione</translation>
|
||||
<extracomment>Dialog title when error occurs during playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>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'è 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>
|
||||
|
@ -2553,5 +2553,143 @@ não contém itens</translation>
|
||||
<source>Change Server</source>
|
||||
<translation>Alterar servidor</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Change Server</source>
|
||||
<translation>Mudar Servidor</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>On Now</source>
|
||||
<translation>Passando Agora</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>Error During Playback</source>
|
||||
<translation>Erro Durante Reprodução</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>Houve um erro ao coletar dados do servidor para este item.</translation>
|
||||
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Loading Channel Data</source>
|
||||
<translation>Carregando dados do canal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
|
||||
<source>NO_ITEMS</source>
|
||||
<translation>Este %1 não possui itens</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Name or Title field of media item</comment>
|
||||
<source>TITLE</source>
|
||||
<translation>Nome</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>DATE_PLAYED</source>
|
||||
<translation>Data de Reprodução</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>IMDB_RATING</source>
|
||||
<translation>Avaliação IMDb</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>RELEASE_DATE</source>
|
||||
<translation>Data de Lançamento</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An error was encountered while playing this item.</source>
|
||||
<translation>Um erro foi encontrado enquanto reproduzindo este item.</translation>
|
||||
<extracomment>Dialog detail when error occurs during playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error loading Channel Data</source>
|
||||
<translation>Erro ao carregar os dados do canal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to load Channel Data from the server</source>
|
||||
<translation>Não foi possível carregar do servidor os dados do canal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CRITIC_RATING</source>
|
||||
<translation>Avaliação de críticos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>DATE_ADDED</source>
|
||||
<translation>Data de Adição</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PLAY_COUNT</source>
|
||||
<translation>Número de Reproduções</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
|
@ -94,7 +94,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>My Media</source>
|
||||
<translation>Fișierele mele Media</translation>
|
||||
<translation>Librăria Mea</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Continue Watching</source>
|
||||
@ -1859,5 +1859,751 @@
|
||||
<translation>Suport pentru redarea directă a conținutului MPEG 2 (ex. Televiziune în direct). Conținutul MPEG 2 nu va fi transcodat, dar se va folosi significativ mai multă lățime de bandă</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete Saved</source>
|
||||
<translation>Şterge Salvarea</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Salvezi Credenţialele ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>On Now</source>
|
||||
<translation>Chiar Acum</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>IMDB_RATING</source>
|
||||
<translation>Scor IMDb</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>DATE_PLAYED</source>
|
||||
<translation>Data Redării</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>DATE_ADDED</source>
|
||||
<translation>Data Adăugării</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>RUNTIME</source>
|
||||
<translation>Timp de Funcţionare</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Title of Tab for options to sort library content</comment>
|
||||
<source>TAB_SORT</source>
|
||||
<translation>Sortare</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Title of Tab for options to filter library content</comment>
|
||||
<source>TAB_FILTER</source>
|
||||
<translation>Filtru</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>today</source>
|
||||
<translation>astăzi</translation>
|
||||
<extracomment>Current day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>yesterday</source>
|
||||
<translation>ieri</translation>
|
||||
<extracomment>Previous day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not found</source>
|
||||
<translation>Nu a fost găsit</translation>
|
||||
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error During Playback</source>
|
||||
<translation>Eroare În Timpul Redării</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>A existat o eroare la redarea datelor pentru acest element de pe server.</translation>
|
||||
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>RELEASE_DATE</source>
|
||||
<translation>Data Lansării</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Title of Tab for switching "views" when looking at a library</comment>
|
||||
<source>TAB_VIEW</source>
|
||||
<translation>Vedere</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Shows</source>
|
||||
<translation>Seriale TV</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>tomorrow</source>
|
||||
<translation>mâine</translation>
|
||||
<extracomment>Next day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Starts at</source>
|
||||
<translation>Începe la</translation>
|
||||
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ended at</source>
|
||||
<translation>S-a terminat la</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Channels</source>
|
||||
<translation>Canale</translation>
|
||||
<extracomment>Menu option for showing Live TV Channel List</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record Series</source>
|
||||
<translation>Înregistrează Serial</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel Recording</source>
|
||||
<translation>Anulează înregistrarea</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Close</source>
|
||||
<translation>Închide</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Getting Playback Information</source>
|
||||
<translation>Eroare la Primirea Informației despre conținutul selectat</translation>
|
||||
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started</source>
|
||||
<translation>A început</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ends at</source>
|
||||
<translation>Se termină la</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Live</source>
|
||||
<translation>În Direct</translation>
|
||||
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
|
||||
<translation>O eroare a fost întâlnită în timpul redării acestui element. Serverul nu a furnizat datele de transcodare necesare.</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>Sprijin Direct Play de conținut MPEG-2 (de exemplu, Live TV). Acest lucru va preveni transcodarea conținutului MPEG-2, dar utilizează semnificativ mai multă lățime de bandă.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>User Interface</source>
|
||||
<translation>Interfață Utilizator</translation>
|
||||
<extracomment>Title for User Interface section in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Item Count</source>
|
||||
<translation>Număr Elemente</translation>
|
||||
<extracomment>UI -> Media Grid -> Item Count in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>You can search for Titles, People, Live TV Channels and more</source>
|
||||
<translation>Puteți căuta titluri, persoane, canale TV în direct și multe altele</translation>
|
||||
<extracomment>Help text in search results</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quick Connect</source>
|
||||
<translation>Conectare Rapidă</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Studios</source>
|
||||
<translation>Studiouri</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Shows</source>
|
||||
<translation>Seriale</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Screensaver</source>
|
||||
<translation>Screensaver</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set Favorite</source>
|
||||
<translation>Setare ca Favorit</translation>
|
||||
<extracomment>Button Text - When pressed, sets item as Favorite</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>Utilizați butonul de reluare pentru a anima încet la primul element din folder. (Dacă este dezactivat, folderul se va reseta imediat la primul element).</translation>
|
||||
<extracomment>Description for option in Setting Screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for TV Shows.</source>
|
||||
<translation>Opțiuni pentru seriale TV.</translation>
|
||||
<extracomment>Description for TV Shows user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for Jellyfin's screensaver.</source>
|
||||
<translation>Opțiuni pentru Jellyfin screensaver.</translation>
|
||||
<extracomment>Description for Screensaver user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use Splashscreen as Screensaver Background</source>
|
||||
<translation>Utilizarea Splashscreen ca fundal Screensaver</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use generated splashscreen image as Jellyfin's screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</source>
|
||||
<translation>Utilizați imaginea generată pe splashscreen ca fundal de ecran Jellyfin . Jellyfin va trebui să fie închisă și redeschisă pentru ca schimbarea să intre în vigoare.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use generated splashscreen image as Jellyfin's home background. Jellyfin will need to be closed and reopened for change to take effect.</source>
|
||||
<translation>Utilizați imaginea generată pe splashscreen ca fundal de pornire Jellyfin. Jellyfin va trebui să fie închisă și redeschisă pentru ca schimbarea să intre în vigoare.</translation>
|
||||
<extracomment>Description for option in 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>Ascunde toate ceasurile în Jellyfin. Jellyfin va trebui să fie închisă și redeschisă pentru ca schimbarea să intre în vigoare.</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>Încercați redarea directă pentru mediu HEVC cu niveluri de profil neacceptate înainte de a reveni la trancodare dacă nu reușește.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for Home Page.</source>
|
||||
<translation>Opţiuni pentru Pagină Principală.</translation>
|
||||
<extracomment>Description for Home Page user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Text Subtitles Only</source>
|
||||
<translation>Numai subtitrări de text</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>Afișați numai subtitrări de text pentru a minimiza transcodarea.</translation>
|
||||
<extracomment>Description of a setting - should we hide subtitles that might transcode</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>Suport Direct Play de conținut MPEG-4. Acest lucru poate fi necesar să fie dezactivat pentru redarea fișierelor video codificate DIVX.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started at</source>
|
||||
<translation>A Început la</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Starts</source>
|
||||
<translation>Începe</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>Enter the server name or IP address</source>
|
||||
<translation>Introdu Numele Serverului sau Adresa IP</translation>
|
||||
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide Taglines</source>
|
||||
<translation>Ascunde tag-urile</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>An error was encountered while playing this item.</source>
|
||||
<translation>Am întâmpinat o eroare în timpul redării articolului</translation>
|
||||
<extracomment>Dialog detail when error occurs during playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>CRITIC_RATING</source>
|
||||
<translation>Scorul Criticilor</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connecting to Server</source>
|
||||
<translation>Se conectează la server</translation>
|
||||
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>(Dialog will close automatically)</source>
|
||||
<translation>(Dialogul se va închide automat)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hides tagline text on details pages.</source>
|
||||
<translation>Ascunde textul tagline-ului în paginile cu detalii.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Retrieving Content</source>
|
||||
<translation>Eroare La Afişarea Conținutului</translation>
|
||||
<extracomment>Dialog title when unable to load Content from Server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Born</source>
|
||||
<translation>Nascut</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Died</source>
|
||||
<translation>Decedat</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>More Like This</source>
|
||||
<translation>Mai Multe Ca şi Acesta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Guide</source>
|
||||
<translation>Ghid TV</translation>
|
||||
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record</source>
|
||||
<translation>Înregistrează</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel Series Recording</source>
|
||||
<translation>Anulează Înregistrarea Serialului</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MPEG-2 Support</source>
|
||||
<translation>MPEG-2 Sprijin</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Media Grid options.</source>
|
||||
<translation>Opţiuni Grilă Media.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Item Titles</source>
|
||||
<translation>Titluri Conținut</translation>
|
||||
<extracomment>UI -> Media Grid -> Item Title in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</source>
|
||||
<translation>Afișați întotdeauna titlurile de sub imaginile posterului. (Dacă este dezactivat, titlul va fi afișat numai sub elementul evidențiat).</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>Afișați numărul de elemente din bibliotecă și indexul elementului selectat.</translation>
|
||||
<extracomment>Description for option in Setting Screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use voice remote to search</source>
|
||||
<translation>Utilizarea telecomenzii vocale pentru a căuta</translation>
|
||||
<extracomment>Help text in search voice text box</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 of %2</source>
|
||||
<translation>1% din 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>S-a întâmpinat o eroare în timpul Conectării Rapide.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Networks</source>
|
||||
<translation>Rețele</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Return to Top</source>
|
||||
<translation>Întoarce-te sus</translation>
|
||||
<extracomment>UI -> Media Grid -> Item Title in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Blur Unwatched Episodes</source>
|
||||
<translation>Încețoșeaza Episoadele Nevizualizate</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Design Elements</source>
|
||||
<translation>Elemente design</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cinema Mode</source>
|
||||
<translation>Modul Cinema</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide Clock</source>
|
||||
<translation>Ascunde Ceas</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Bit Rate</source>
|
||||
<translation>Bit Rate</translation>
|
||||
<extracomment>Video streaming bit rate</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>View Channel</source>
|
||||
<translation>Vezi Canal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error loading Channel Data</source>
|
||||
<translation>Eroare la Încărcarea Datelor Canalului</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Age</source>
|
||||
<translation>Vârstă</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cast & Crew</source>
|
||||
<translation>Distribuţie & Echipă</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Stream Information</source>
|
||||
<translation>Informații despre Stream</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to find any albums or songs belonging to this artist</source>
|
||||
<translation>Imposibili de găsit orice albume sau melodii aparținând acestui artist</translation>
|
||||
<extracomment>Popup message when we find no audio data for an artist</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Loading Channel Data</source>
|
||||
<translation>Se Încarcă Datele Canalului</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PLAY_COUNT</source>
|
||||
<translation>Număr Redări</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press 'OK' to Close</source>
|
||||
<translation>Apasă 'OK' Pentru a Închide</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Additional Parts</source>
|
||||
<translation>Părţi Adiţionale</translation>
|
||||
<extracomment>Additional parts of a video</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Movies</source>
|
||||
<translation>Filme</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sunday</source>
|
||||
<translation>Duminică</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Monday</source>
|
||||
<translation>Luni</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tuesday</source>
|
||||
<translation>Marți</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wednesday</source>
|
||||
<translation>Miercuri</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Friday</source>
|
||||
<translation>Vineri</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Saturday</source>
|
||||
<translation>Sâmbătă</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Repeat</source>
|
||||
<translation>Redifuzare</translation>
|
||||
<extracomment>If TV Shows has previously been broadcasted</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>The requested content does not exist on the server</source>
|
||||
<translation>Conținutul cerut nu există pe server</translation>
|
||||
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>...or enter server URL manually:</source>
|
||||
<translation>Dacă niciun server nu este arătat mai jos , atunci adăugați URL-ul serverului manual:</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Version</source>
|
||||
<translation>Versiunea</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pick a Jellyfin server from the local network</source>
|
||||
<translation>Selectați un server Jellyfin disponibil din rețeaua locală:</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>AV1 Support</source>
|
||||
<translation>Suport AV1</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</source>
|
||||
<translation>** EXPERIMENTAL** Suport redare directă a conținutului AV1 dacă acest dispozitiv Roku îl acceptă.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disabled</source>
|
||||
<translation>Oprit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Media Grid</source>
|
||||
<translation>Grilă media</translation>
|
||||
<extracomment>UI -> Media Grid section in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set Watched</source>
|
||||
<translation>Setare ca Vizionat</translation>
|
||||
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to series</source>
|
||||
<translation>Du-te la Seriale</translation>
|
||||
<extracomment>Continue Watching Popup Menu - Navigate to the Series Detail Page</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to season</source>
|
||||
<translation>De Sezon</translation>
|
||||
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to episode</source>
|
||||
<translation>Du-te la Episoade</translation>
|
||||
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Search now</source>
|
||||
<translation>Caută acum</translation>
|
||||
<extracomment>Help text in search Box</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Here is your Quick Connect code:</source>
|
||||
<translation>Acesta este codul dumneavoastră de Conectare Rapidă:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Details Page</source>
|
||||
<translation>Pagină cu Detalii</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for Details pages.</source>
|
||||
<translation>Opţiuni pentru Pagină cu Detalii.</translation>
|
||||
<extracomment>Description for Details page user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>If enabled, images of unwatched episodes will be blurred.</source>
|
||||
<translation>Dacă este activată, imaginile episoadelor nevizionate vor fi înceţoşate.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options that alter the design of Jellyfin.</source>
|
||||
<translation>Opțiuni ce modifică designul Jellyfin.</translation>
|
||||
<extracomment>Description for Design Elements user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use Splashscreen as Home Background</source>
|
||||
<translation>Foloseşte Splashscreen ca Imagine de Fundal pentru Acasa</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>Modul Cinema aduce experiența de teatru direct în camera de zi, cu posibilitatea de a reda intr-o-uri personalizate înainte de caracteristica principală.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Next episode</source>
|
||||
<translation>Episodul urmator</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Play Trailer</source>
|
||||
<translation>Porneste Trailer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Direct Play H.264 Unsupported Profile Levels</source>
|
||||
<translation>Redare directă H.264 Niveluri de profil neacceptate</translation>
|
||||
<extracomment>Settings Menu - Title for option</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>Încercați redare directă pentru suportul media H.264 cu niveluri de profil neacceptate înainte de a reveni la transcodare dacă nu reușește.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Direct Play HEVC Unsupported Profile Levels</source>
|
||||
<translation>Redare Directă niveluri de profil HEVC Neacceptate</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings relating to playback and supported codec and media types.</source>
|
||||
<translation>Setări referitoare la redare și tipuri de codecuri suportate si acceptate.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings relating to how the application looks.</source>
|
||||
<translation>Setări referitoare la modul în care arată aplicația.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Home Page</source>
|
||||
<translation>Pagină Principală</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Max Days Next Up</source>
|
||||
<translation>Zilele maxime ce vor urma</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.</source>
|
||||
<translation>Setați numărul maxim de zile în care o emisiune ar trebui să rămână în lista "Next Up" fără a o urmări.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Playback Information</source>
|
||||
<translation>Informații de redare</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Transcoding Information</source>
|
||||
<translation>Informații de transcodare</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reason</source>
|
||||
<translation>Motiv</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Video Codec</source>
|
||||
<translation>Video Codec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Audio Codec</source>
|
||||
<translation>Audio Codec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>direct</source>
|
||||
<translation>direct</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Total Bitrate</source>
|
||||
<translation>Total Bitrate</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Audio Channels</source>
|
||||
<translation>Canale Audio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Codec</source>
|
||||
<translation>Codec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Codec Tag</source>
|
||||
<translation>Codec Tag</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Level</source>
|
||||
<translation>Nivel</translation>
|
||||
<extracomment>Video profile level</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Container</source>
|
||||
<translation>Container</translation>
|
||||
<extracomment>Video streaming container</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Size</source>
|
||||
<translation>Mărime</translation>
|
||||
<extracomment>Video size</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Video range type</source>
|
||||
<translation>Tipul intervalului video</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pixel format</source>
|
||||
<translation>Formatul pixelului</translation>
|
||||
<extracomment>Video pixel format</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>WxH</source>
|
||||
<translation>WxH</translation>
|
||||
<extracomment>Video width x height</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Aired</source>
|
||||
<translation>Difuzat</translation>
|
||||
<extracomment>Aired date label</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>all</source>
|
||||
<translation>tot</translation>
|
||||
<extracomment>all will reset the searchTerm so all data will be availible</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Slideshow On</source>
|
||||
<translation>Slideshow On</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Slideshow Paused</source>
|
||||
<translation>Slideshow Paused</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Slideshow Resumed</source>
|
||||
<translation>Slideshow Resumed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Slideshow Off</source>
|
||||
<translation>Slideshow Off</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Random Off</source>
|
||||
<translation>Aleatoriu Oprit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MPEG-4 Support</source>
|
||||
<translation>MPEG-4 Sprijin</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Random On</source>
|
||||
<translation>Aleatoriu Pornit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to load Channel Data from the server</source>
|
||||
<translation>Nu se pot încărca Datele Canalului de pe server</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Thursday</source>
|
||||
<translation>Joi</translation>
|
||||
<extracomment>Day of Week</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>Acest 1% nu conţine articole</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Name or Title field of media item</comment>
|
||||
<source>TITLE</source>
|
||||
<translation>Nume</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Special Features</source>
|
||||
<translation>Caracteristici Speciale</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>OFFICIAL_RATING</source>
|
||||
<translation>Scor Parental</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unknown</source>
|
||||
<translation>Necunoscut</translation>
|
||||
<extracomment>Title for a cast member for which we have no information for</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Playback</source>
|
||||
<translation>Redare conținut</translation>
|
||||
<extracomment>Title for Playback section in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enabled</source>
|
||||
<translation>Pornit</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
@ -671,5 +671,456 @@
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Uložiť poverenia?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Uložiť prihlasovacie údaje?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Monday</source>
|
||||
<translation>Pondelok</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started at</source>
|
||||
<translation>Začalo o</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>View Channel</source>
|
||||
<translation>Zobraziť kanál</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record Series</source>
|
||||
<translation>Séria nahrávok</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel Series Recording</source>
|
||||
<translation>Zrušiť nahrávanie série</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connecting to Server</source>
|
||||
<translation>Pripája sa k serveru</translation>
|
||||
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter the server name or IP address</source>
|
||||
<translation>Zadajte názov servera alebo IP adresu</translation>
|
||||
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Getting Playback Information</source>
|
||||
<translation>Chyba pri získavaní informácií o prehrávaní</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>Pri prehrávaní tejto položky sa vyskytla chyba. Server neposkytol požadované údaje na prekódovanie.</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>Live</source>
|
||||
<translation>Naživo</translation>
|
||||
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Friday</source>
|
||||
<translation>Piatok</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete Saved</source>
|
||||
<translation>Odstrániť uložené</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>On Now</source>
|
||||
<translation>Teraz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>DATE_PLAYED</source>
|
||||
<translation>Dátum hrania</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Guide</source>
|
||||
<translation>TV sprievodca</translation>
|
||||
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Born</source>
|
||||
<translation>Narodený</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cast & Crew</source>
|
||||
<translation>Herci & štáb</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>More Like This</source>
|
||||
<translation>Viac takých</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Movies</source>
|
||||
<translation>Filmy</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tuesday</source>
|
||||
<translation>Utorok</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Starts</source>
|
||||
<translation>Začína</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>Skončil o</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>The requested content does not exist on the server</source>
|
||||
<translation>Požadovaný obsah na serveri neexistuje</translation>
|
||||
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Name or Title field of media item</comment>
|
||||
<source>TITLE</source>
|
||||
<translation>Meno</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PLAY_COUNT</source>
|
||||
<translation>Počet prehrania</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Died</source>
|
||||
<translation>Zomrel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Age</source>
|
||||
<translation>Vek</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Special Features</source>
|
||||
<translation>Špeciálne vlastnosti</translation>
|
||||
<message>
|
||||
<source>Press 'OK' to Close</source>
|
||||
<translation>Press 'OK' to Close</translation>
|
||||
</message>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press 'OK' to Close</source>
|
||||
<translation>Zatvorte stlačením tlačidla „OK“</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>tomorrow</source>
|
||||
<translation>zajtra</translation>
|
||||
<extracomment>Next day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sunday</source>
|
||||
<translation>Nedeľa</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wednesday</source>
|
||||
<translation>Streda</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Saturday</source>
|
||||
<translation>Sobota</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</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čne</translation>
|
||||
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ends at</source>
|
||||
<translation>Končí o</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not found</source>
|
||||
<translation>Nenájdené</translation>
|
||||
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Title of Tab for options to sort library content</comment>
|
||||
<source>TAB_SORT</source>
|
||||
<translation>Triediť</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>RUNTIME</source>
|
||||
<translation>Beh programu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Version</source>
|
||||
<translation>Verzia</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
|
||||
<source>NO_ITEMS</source>
|
||||
<translation>Tento %1 neobsahuje žiadne položky</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>IMDB_RATING</source>
|
||||
<translation>IMDB hodnotenie</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>RELEASE_DATE</source>
|
||||
<translation>Dátum vydania</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Title of Tab for switching "views" when looking at a library</comment>
|
||||
<source>TAB_VIEW</source>
|
||||
<translation>Vyhliadka</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Title of Tab for options to filter library content</comment>
|
||||
<source>TAB_FILTER</source>
|
||||
<translation>Filter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Shows</source>
|
||||
<translation>TV relácie</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>Thursday</source>
|
||||
<translation>Štvrtok</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Repeat</source>
|
||||
<translation>Opakujte</translation>
|
||||
<extracomment>If TV Shows has previously been broadcasted</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Channels</source>
|
||||
<translation>Kanály</translation>
|
||||
<extracomment>Menu option for showing Live TV Channel List</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record</source>
|
||||
<translation>Záznam</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel Recording</source>
|
||||
<translation>Zrušiť nahrávanie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Close</source>
|
||||
<translation>Zavrieť</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pick a Jellyfin server from the local network</source>
|
||||
<translation>Vyberte dostupný server Jellyfin z vašej lokálnej siete:</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>...or enter server URL manually:</source>
|
||||
<translation>Ak vyššie nie je uvedený žiadny server, adresu URL servera môžete zadať aj ručne:</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>CRITIC_RATING</source>
|
||||
<translation>Hodnotenie kritikov</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unknown</source>
|
||||
<translation>Neznámy</translation>
|
||||
<extracomment>Title for a cast member for which we have no information for</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Playback</source>
|
||||
<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 "views" 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 & 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 'OK' to Close</source>
|
||||
<translation>Zatvorte stlačením tlačidla 'OK'</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>
|
||||
|
4
manifest
4
manifest
@ -1,8 +1,8 @@
|
||||
## Channel Details
|
||||
title=Jellyfin
|
||||
major_version=1
|
||||
minor_version=5
|
||||
build_version=0
|
||||
minor_version=6
|
||||
build_version=3
|
||||
|
||||
### Main Menu Icons / Channel Poster Artwork
|
||||
mm_icon_focus_fhd=pkg:/images/channel-poster_fhd.png
|
||||
|
7782
package-lock.json
generated
7782
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@ -1,16 +1,12 @@
|
||||
{
|
||||
"name": "jellyfin-roku",
|
||||
"version": "1.4.12",
|
||||
"version": "1.6.3",
|
||||
"description": "Roku app for Jellyfin media server",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"test": "tests"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rokucommunity/bslint": "0.7.5",
|
||||
"brighterscript": "0.57.2",
|
||||
"rooibos-cli": "1.4.0",
|
||||
"ropm": "0.10.10"
|
||||
"@rokucommunity/bslint": "0.8.1",
|
||||
"brighterscript": "0.61.3",
|
||||
"ropm": "0.10.11"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "npx ropm copy",
|
||||
@ -41,4 +37,4 @@
|
||||
"sob": "npm:slide-out-button@^1.0.1",
|
||||
"intKeyboard": "npm:integer-keyboard@^1.0.12"
|
||||
}
|
||||
}
|
||||
}
|
@ -4,18 +4,51 @@
|
||||
"description": "Settings relating to playback and supported codec and media types.",
|
||||
"children": [
|
||||
{
|
||||
"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": "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": "Attempt Direct Play (Profile Lvl)",
|
||||
"description": "Attempt Direct Play for H.264 media with unsupported profile levels (> 4.2) before falling back to transcoding if it fails.",
|
||||
"settingName": "playback.tryDirect.h264ProfileLevel",
|
||||
"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",
|
||||
@ -23,9 +56,23 @@
|
||||
"settingName": "playback.cinemamode",
|
||||
"type": "bool",
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"title": "Text Subtitles Only",
|
||||
"description": "Only display text subtitles to minimize transcoding.",
|
||||
"settingName": "playback.subs.onlytext",
|
||||
"type": "bool",
|
||||
"default": "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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.",
|
||||
@ -40,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"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -66,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"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -92,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"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -106,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.",
|
||||
|
131
source/Main.brs
131
source/Main.brs
@ -1,8 +1,6 @@
|
||||
sub Main (args as dynamic) as void
|
||||
|
||||
' If the Rooibos files are included in deployment, run tests
|
||||
'bs:disable-next-line
|
||||
if type(Rooibos__Init) = "Function" then Rooibos__Init()
|
||||
appInfo = CreateObject("roAppInfo")
|
||||
|
||||
' The main function that runs when the application is launched.
|
||||
m.screen = CreateObject("roSGScreen")
|
||||
@ -13,13 +11,6 @@ sub Main (args as dynamic) as void
|
||||
WriteAsciiFile("tmp:/scene.temp", "")
|
||||
MoveFile("tmp:/scene.temp", "tmp:/scene")
|
||||
|
||||
' Temporary code to migrate MPEG2 setting from device setting to user setting
|
||||
' Added for 1.4.13 release and should probably be removed for 1.4.15
|
||||
if get_setting("playback.mpeg2") <> invalid and registry_read("playback.mpeg2", get_setting("active_user")) = invalid
|
||||
set_user_setting("playback.mpeg2", get_setting("playback.mpeg2"))
|
||||
end if
|
||||
' End Temporary code
|
||||
|
||||
m.port = CreateObject("roMessagePort")
|
||||
m.screen.setMessagePort(m.port)
|
||||
m.scene = m.screen.CreateScene("JFScene")
|
||||
@ -34,6 +25,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
|
||||
@ -49,6 +41,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)
|
||||
@ -59,10 +62,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")
|
||||
@ -94,6 +97,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
|
||||
@ -106,12 +110,42 @@ 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" or selectedItem.type = "UserView" or selectedItem.type = "Folder" or selectedItem.type = "Channel" or selectedItem.type = "Boxset"
|
||||
|
||||
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"
|
||||
' 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)
|
||||
sceneManager.callFunc("pushScene", group)
|
||||
else if selectedItem.type = "Episode"
|
||||
@ -128,6 +162,8 @@ sub Main (args as dynamic) as void
|
||||
end if
|
||||
else if selectedItem.type = "Series"
|
||||
group = CreateSeriesDetailsGroup(selectedItem.json)
|
||||
else if selectedItem.type = "Season"
|
||||
group = CreateSeasonDetailsGroupByID(selectedItem.json.SeriesId, selectedItem.id)
|
||||
else if selectedItem.type = "Movie"
|
||||
' open movie detail page
|
||||
group = CreateMovieDetailsGroup(selectedItem)
|
||||
@ -142,7 +178,12 @@ sub Main (args as dynamic) as void
|
||||
dialog.title = tr("Loading Channel Data")
|
||||
m.scene.dialog = dialog
|
||||
|
||||
video = CreateVideoPlayerGroup(video_id)
|
||||
if LCase(selectedItem.subtype()) = "extrasdata"
|
||||
video = CreateVideoPlayerGroup(video_id, invalid, 1, false, true, false)
|
||||
else
|
||||
video = CreateVideoPlayerGroup(video_id)
|
||||
end if
|
||||
|
||||
dialog.close = true
|
||||
|
||||
if video <> invalid and video.errorMsg <> "introaborted"
|
||||
@ -166,7 +207,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 + ".")
|
||||
@ -202,17 +245,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
|
||||
@ -222,20 +275,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")
|
||||
@ -280,7 +339,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"
|
||||
@ -292,7 +353,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 + ".")
|
||||
@ -324,9 +387,17 @@ sub Main (args as dynamic) as void
|
||||
dialog.close = true
|
||||
end if
|
||||
|
||||
if group.lastfocus.id = "main_group"
|
||||
buttons = group.findNode("buttons")
|
||||
if isValid(buttons)
|
||||
group.lastfocus = group.findNode("buttons")
|
||||
end if
|
||||
end if
|
||||
|
||||
if group.lastFocus <> invalid
|
||||
group.lastFocus.setFocus(true)
|
||||
end if
|
||||
|
||||
else if btn <> invalid and btn.id = "trailer-button"
|
||||
dialog = createObject("roSGNode", "ProgressDialog")
|
||||
dialog.title = tr("Loading trailer")
|
||||
@ -339,7 +410,7 @@ sub Main (args as dynamic) as void
|
||||
|
||||
video_id = trailerData[0].id
|
||||
|
||||
video = CreateVideoPlayerGroup(video_id, mediaSourceId, audio_stream_idx)
|
||||
video = CreateVideoPlayerGroup(video_id, mediaSourceId, audio_stream_idx, false, false)
|
||||
if video <> invalid and video.errorMsg <> "introaborted"
|
||||
sceneManager.callFunc("pushScene", video)
|
||||
dialog.close = true
|
||||
@ -428,7 +499,7 @@ sub Main (args as dynamic) as void
|
||||
if m.selectedItemType = "TvChannel" and node.state = "finished"
|
||||
video = CreateVideoPlayerGroup(node.id)
|
||||
m.global.sceneManager.callFunc("pushScene", video)
|
||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||
m.global.sceneManager.callFunc("deleteSceneAtIndex", 2)
|
||||
else if node.state = "finished"
|
||||
node.control = "stop"
|
||||
|
||||
@ -445,12 +516,6 @@ sub Main (args as dynamic) as void
|
||||
autoPlayNextEpisode(node.id, node.showID)
|
||||
end if
|
||||
end if
|
||||
'else if isNodeEvent(msg, "selectedExtra")
|
||||
'rl = msg.getData()
|
||||
'sel = rl.rowItemSelected
|
||||
'? "msg.getfield():" + msg.getField()
|
||||
'stop
|
||||
'CreatePersonView(msg.getData())
|
||||
else if type(msg) = "roDeviceInfoEvent"
|
||||
event = msg.GetInfo()
|
||||
group = sceneManager.callFunc("getActiveScene")
|
||||
|
@ -352,24 +352,30 @@ function CreateMovieDetailsGroup(movie)
|
||||
|
||||
extras = group.findNode("extrasGrid")
|
||||
extras.observeField("selectedItem", m.port)
|
||||
extras.callFunc("loadPeople", movie.json)
|
||||
extras.callFunc("loadParts", movie.json)
|
||||
|
||||
return group
|
||||
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)
|
||||
|
||||
extras = group.findNode("extrasGrid")
|
||||
extras.observeField("selectedItem", m.port)
|
||||
extras.callFunc("loadPeople", group.itemcontent.json)
|
||||
extras.callFunc("loadParts", group.itemcontent.json)
|
||||
|
||||
return group
|
||||
end function
|
||||
@ -453,6 +459,20 @@ function CreateSeasonDetailsGroup(series, season)
|
||||
return group
|
||||
end function
|
||||
|
||||
function CreateSeasonDetailsGroupByID(seriesID, seasonID)
|
||||
group = CreateObject("roSGNode", "TVEpisodes")
|
||||
group.optionsAvailable = false
|
||||
m.global.sceneManager.callFunc("pushScene", group)
|
||||
|
||||
group.seasonData = ItemMetaData(seasonID).json
|
||||
group.objects = TVEpisodes(seriesID, seasonID)
|
||||
|
||||
group.observeField("episodeSelected", m.port)
|
||||
group.observeField("quickPlayNode", m.port)
|
||||
|
||||
return group
|
||||
end function
|
||||
|
||||
function CreateItemGrid(libraryItem)
|
||||
group = CreateObject("roSGNode", "ItemGrid")
|
||||
group.parentItem = libraryItem
|
||||
@ -461,6 +481,22 @@ function CreateItemGrid(libraryItem)
|
||||
return group
|
||||
end function
|
||||
|
||||
function CreateMovieLibraryView(libraryItem)
|
||||
group = CreateObject("roSGNode", "MovieLibraryView")
|
||||
group.parentItem = libraryItem
|
||||
group.optionsAvailable = true
|
||||
group.observeField("selectedItem", m.port)
|
||||
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")
|
||||
@ -476,10 +512,10 @@ sub CreateSidePanel(buttons, options)
|
||||
group.options = options
|
||||
end sub
|
||||
|
||||
function CreateVideoPlayerGroup(video_id, mediaSourceId = invalid, audio_stream_idx = 1, forceTranscoding = false, showIntro = true)
|
||||
function CreateVideoPlayerGroup(video_id, mediaSourceId = invalid, audio_stream_idx = 1, forceTranscoding = false, showIntro = true, allowResumeDialog = true)
|
||||
|
||||
' Video is Playing
|
||||
video = VideoPlayer(video_id, mediaSourceId, audio_stream_idx, defaultSubtitleTrackFromVid(video_id), forceTranscoding, showIntro)
|
||||
video = VideoPlayer(video_id, mediaSourceId, audio_stream_idx, defaultSubtitleTrackFromVid(video_id), forceTranscoding, showIntro, allowResumeDialog)
|
||||
|
||||
if video = invalid then return invalid
|
||||
if video.errorMsg = "introaborted" then return video
|
||||
@ -490,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)
|
||||
@ -570,17 +540,6 @@ function CreatePersonView(personData as object) as object
|
||||
return person
|
||||
end function
|
||||
|
||||
function CreatePhotoPage(photo)
|
||||
group = CreateObject("roSGNode", "PhotoDetails")
|
||||
group.optionsAvailable = true
|
||||
m.global.sceneManager.callFunc("pushScene", group)
|
||||
|
||||
group.itemContent = photo
|
||||
|
||||
return group
|
||||
|
||||
end function
|
||||
|
||||
sub UpdateSavedServerList()
|
||||
server = get_setting("server")
|
||||
username = get_setting("username")
|
||||
|
@ -1,8 +1,8 @@
|
||||
function VideoPlayer(id, mediaSourceId = invalid, audio_stream_idx = 1, subtitle_idx = -1, forceTranscoding = false, showIntro = true)
|
||||
function VideoPlayer(id, mediaSourceId = invalid, audio_stream_idx = 1, subtitle_idx = -1, forceTranscoding = false, showIntro = true, allowResumeDialog = true)
|
||||
' Get video controls and UI
|
||||
video = CreateObject("roSGNode", "JFVideo")
|
||||
video.id = id
|
||||
AddVideoContent(video, mediaSourceId, audio_stream_idx, subtitle_idx, -1, forceTranscoding, showIntro)
|
||||
AddVideoContent(video, mediaSourceId, audio_stream_idx, subtitle_idx, -1, forceTranscoding, showIntro, allowResumeDialog)
|
||||
|
||||
if video.errorMsg = "introaborted"
|
||||
return video
|
||||
@ -19,18 +19,18 @@ function VideoPlayer(id, mediaSourceId = invalid, audio_stream_idx = 1, subtitle
|
||||
return video
|
||||
end function
|
||||
|
||||
sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -1, playbackPosition = -1, forceTranscoding = false, showIntro = true)
|
||||
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
|
||||
@ -45,108 +45,115 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.videotype = "Episode" or m.videotype = "Series"
|
||||
video.runTime = (meta.json.RunTimeTicks / 10000000.0)
|
||||
video.content.contenttype = "episode"
|
||||
end if
|
||||
|
||||
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 playbackPosition > 0
|
||||
dialogResult = startPlayBackOver(playbackPosition)
|
||||
'Dialog returns -1 when back pressed, 0 for resume, and 1 for start over
|
||||
if dialogResult = -1
|
||||
'User pressed back, return invalid and don't load video
|
||||
video.content = invalid
|
||||
return
|
||||
else if dialogResult = 1
|
||||
'Start Over selected, change position to 0
|
||||
playbackPosition = 0
|
||||
else if dialogResult = 2
|
||||
'Mark this item as watched, refresh the page, and return invalid so we don't load the video
|
||||
MarkItemWatched(video.id)
|
||||
video.content.watched = not video.content.watched
|
||||
group = m.scene.focusedChild
|
||||
group.timeLastRefresh = CreateObject("roDateTime").AsSeconds()
|
||||
group.callFunc("refresh")
|
||||
video.content = invalid
|
||||
return
|
||||
else if dialogResult = 3
|
||||
'get series ID based off episiode ID
|
||||
params = {
|
||||
ids: video.Id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.series_id = item.SeriesId
|
||||
end for
|
||||
'Get series json data
|
||||
params = {
|
||||
ids: m.series_id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.tmp = item
|
||||
end for
|
||||
'Create Series Scene
|
||||
CreateSeriesDetailsGroup(m.tmp)
|
||||
video.content = invalid
|
||||
return
|
||||
if allowResumeDialog
|
||||
if playbackPosition > 0
|
||||
dialogResult = startPlayBackOver(playbackPosition)
|
||||
'Dialog returns -1 when back pressed, 0 for resume, and 1 for start over
|
||||
if dialogResult = -1
|
||||
'User pressed back, return invalid and don't load video
|
||||
video.content = invalid
|
||||
return
|
||||
else if dialogResult = 1
|
||||
'Start Over selected, change position to 0
|
||||
playbackPosition = 0
|
||||
else if dialogResult = 2
|
||||
'Mark this item as watched, refresh the page, and return invalid so we don't load the video
|
||||
MarkItemWatched(video.id)
|
||||
video.content.watched = not video.content.watched
|
||||
group = m.scene.focusedChild
|
||||
group.timeLastRefresh = CreateObject("roDateTime").AsSeconds()
|
||||
group.callFunc("refresh")
|
||||
video.content = invalid
|
||||
return
|
||||
else if dialogResult = 3
|
||||
'get series ID based off episiode ID
|
||||
params = {
|
||||
ids: video.Id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.series_id = item.SeriesId
|
||||
end for
|
||||
'Get series json data
|
||||
params = {
|
||||
ids: m.series_id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.tmp = item
|
||||
end for
|
||||
'Create Series Scene
|
||||
CreateSeriesDetailsGroup(m.tmp)
|
||||
video.content = invalid
|
||||
return
|
||||
|
||||
else if dialogResult = 4
|
||||
'get Season/Series ID based off episiode ID
|
||||
params = {
|
||||
ids: video.Id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.season_id = item.SeasonId
|
||||
m.series_id = item.SeriesId
|
||||
end for
|
||||
'Get Series json data
|
||||
params = {
|
||||
ids: m.season_id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.Season_tmp = item
|
||||
end for
|
||||
'Get Season json data
|
||||
params = {
|
||||
ids: m.series_id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.Series_tmp = item
|
||||
end for
|
||||
'Create Season Scene
|
||||
CreateSeasonDetailsGroup(m.Series_tmp, m.Season_tmp)
|
||||
video.content = invalid
|
||||
return
|
||||
else if dialogResult = 4
|
||||
'get Season/Series ID based off episiode ID
|
||||
params = {
|
||||
ids: video.Id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.season_id = item.SeasonId
|
||||
m.series_id = item.SeriesId
|
||||
end for
|
||||
'Get Series json data
|
||||
params = {
|
||||
ids: m.season_id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.Season_tmp = item
|
||||
end for
|
||||
'Get Season json data
|
||||
params = {
|
||||
ids: m.series_id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.Series_tmp = item
|
||||
end for
|
||||
'Create Season Scene
|
||||
CreateSeasonDetailsGroup(m.Series_tmp, m.Season_tmp)
|
||||
video.content = invalid
|
||||
return
|
||||
|
||||
else if dialogResult = 5
|
||||
'get episiode ID
|
||||
params = {
|
||||
ids: video.Id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.episode_id = item
|
||||
end for
|
||||
'Create Episode Scene
|
||||
CreateMovieDetailsGroup(m.episode_id)
|
||||
video.content = invalid
|
||||
return
|
||||
else if dialogResult = 5
|
||||
'get episiode ID
|
||||
params = {
|
||||
ids: video.Id
|
||||
}
|
||||
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
|
||||
resp = APIRequest(url, params)
|
||||
data = getJson(resp)
|
||||
for each item in data.Items
|
||||
m.episode_id = item
|
||||
end for
|
||||
'Create Episode Scene
|
||||
CreateMovieDetailsGroup(m.episode_id)
|
||||
video.content = invalid
|
||||
return
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
@ -168,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
|
||||
@ -190,12 +207,22 @@ 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
|
||||
|
||||
subtitles = sortSubtitles(meta.id, m.playbackInfo.MediaSources[0].MediaStreams)
|
||||
video.Subtitles = subtitles["all"]
|
||||
if get_user_setting("playback.subs.onlytext") = "true"
|
||||
safesubs = []
|
||||
for each subtitle in subtitles["all"]
|
||||
if subtitle["IsTextSubtitleStream"]
|
||||
safesubs.push(subtitle)
|
||||
end if
|
||||
end for
|
||||
video.Subtitles = safesubs
|
||||
else
|
||||
video.Subtitles = subtitles["all"]
|
||||
end if
|
||||
|
||||
if meta.live
|
||||
video.transcodeParams = {
|
||||
@ -213,16 +240,20 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
|
||||
fully_external = false
|
||||
|
||||
|
||||
' For h264 video, Roku spec states that it supports and Encoding level 4.1 and 4.2.
|
||||
' For h264/hevc video, Roku spec states that it supports specfic encoding levels
|
||||
' The device can decode content with a Higher Encoding level but may play it back with certain
|
||||
' artifacts. If the user preference is set, and the only reason the server says we need to
|
||||
' transcode is that the Envoding Level is not supported, then try to direct play but silently
|
||||
' transcode is that the Encoding Level is not supported, then try to direct play but silently
|
||||
' fall back to the transcode if that fails.
|
||||
if meta.live = false and get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].TranscodingUrl <> invalid and forceTranscoding = false and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
|
||||
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
|
||||
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
|
||||
video.directPlaySupported = true
|
||||
video.transcodeAvailable = true
|
||||
if m.playbackInfo.MediaSources[0].MediaStreams.Count() > 0 and meta.live = false
|
||||
tryDirectPlay = get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
|
||||
tryDirectPlay = tryDirectPlay or (get_user_setting("playback.tryDirect.hevcProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc")
|
||||
if tryDirectPlay and m.playbackInfo.MediaSources[0].TranscodingUrl <> invalid and forceTranscoding = false
|
||||
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
|
||||
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
|
||||
video.directPlaySupported = true
|
||||
video.transcodeAvailable = true
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
|
||||
@ -374,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
|
||||
@ -420,7 +451,7 @@ sub autoPlayNextEpisode(videoID as string, showID as string)
|
||||
if data <> invalid and data.Items.Count() = 2
|
||||
' setup new video node
|
||||
nextVideo = CreateVideoPlayerGroup(data.Items[1].Id, invalid, 1, false, false)
|
||||
' remove last video scene
|
||||
' remove last videoplayer scene
|
||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||
if nextVideo <> invalid
|
||||
m.global.sceneManager.callFunc("pushScene", nextVideo)
|
||||
|
@ -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
|
||||
@ -329,26 +333,32 @@ function AudioStream(id as string)
|
||||
songData = AudioItem(id)
|
||||
|
||||
content = createObject("RoSGNode", "ContentNode")
|
||||
|
||||
params = {}
|
||||
|
||||
params.append({
|
||||
"Static": "true",
|
||||
"Container": songData.mediaSources[0].container
|
||||
})
|
||||
|
||||
params.MediaSourceId = songData.mediaSources[0].id
|
||||
|
||||
content.url = buildURL(Substitute("Audio/{0}/stream", songData.id), params)
|
||||
content.title = songData.title
|
||||
content.streamformat = songData.mediaSources[0].container
|
||||
|
||||
playbackInfo = ItemPostPlaybackInfo(songData.id, params.MediaSourceId)
|
||||
playbackInfo = ItemPostPlaybackInfo(songData.id, songData.mediaSources[0].id)
|
||||
content.id = playbackInfo.PlaySessionId
|
||||
|
||||
if useTranscodeAudioStream(playbackInfo)
|
||||
' Transcode the audio
|
||||
content.url = buildURL(playbackInfo.mediaSources[0].TranscodingURL)
|
||||
else
|
||||
' Direct Stream the audio
|
||||
params = {
|
||||
"Static": "true",
|
||||
"Container": songData.mediaSources[0].container,
|
||||
"MediaSourceId": songData.mediaSources[0].id
|
||||
}
|
||||
content.streamformat = songData.mediaSources[0].container
|
||||
content.url = buildURL(Substitute("Audio/{0}/stream", songData.id), params)
|
||||
end if
|
||||
|
||||
return content
|
||||
end function
|
||||
|
||||
function useTranscodeAudioStream(playbackInfo)
|
||||
return playbackInfo.mediaSources[0] <> invalid and playbackInfo.mediaSources[0].TranscodingURL <> invalid
|
||||
end function
|
||||
|
||||
function BackdropImage(id as string)
|
||||
imgParams = { "maxHeight": "720", "maxWidth": "1280" }
|
||||
return ImageURL(id, "Backdrop", imgParams)
|
||||
@ -363,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
|
||||
@ -383,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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user