Merge branch 'unstable' into V1.6.2
This commit is contained in:
commit
28eabb819f
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@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 # tag=v6
|
||||
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 }}
|
||||
|
|
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
|
@ -8,14 +8,14 @@ jobs:
|
|||
run:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
|
||||
- uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 # tag=v3
|
||||
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3
|
||||
- uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3
|
||||
with:
|
||||
node-version: "14.12.0"
|
||||
- run: npm ci
|
||||
- run: npx ropm install
|
||||
- run: make dev
|
||||
- uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
|
||||
- uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3
|
||||
with:
|
||||
name: Jellyfin-Roku-dev-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/out/staging
|
||||
|
|
4
.github/workflows/master-release.yml
vendored
4
.github/workflows/master-release.yml
vendored
|
@ -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@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # 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@83fd05a356d7e2593de66fc9913b3002723633cb # tag=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/unstable-release.yml
vendored
4
.github/workflows/unstable-release.yml
vendored
|
@ -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@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # 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@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3
|
||||
with:
|
||||
name: jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip
|
||||
path: ${{ github.workspace }}/jellyfin_v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.BUILD }}.zip
|
||||
|
|
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.6.1
|
||||
ROKU_TEST_ID = 1
|
||||
ROKU_TEST_WAIT_DURATION = 5
|
||||
VERSION = 1.6.2
|
||||
|
||||
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>
|
42
components/ItemGrid/GridItemSmall.brs
Normal file
42
components/ItemGrid/GridItemSmall.brs
Normal file
|
@ -0,0 +1,42 @@
|
|||
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
|
||||
|
||||
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
|
||||
m.posterText.visible = true
|
||||
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
|
16
components/ItemGrid/GridItemSmall.xml
Normal file
16
components/ItemGrid/GridItemSmall.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?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" />
|
||||
<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" />
|
||||
</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>
|
|
@ -320,13 +320,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
|
||||
|
@ -574,14 +575,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
|
||||
|
||||
|
@ -724,9 +728,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()
|
||||
|
|
|
@ -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,14 +62,17 @@ 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 })
|
||||
end if
|
||||
|
@ -117,7 +130,49 @@ 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: "Movie",
|
||||
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
|
||||
genreMovieImage = api_API().items.getimageurl(item.id)
|
||||
row.title = item.name
|
||||
row.json = item
|
||||
row.FHDPOSTERURL = genreMovieImage
|
||||
row.HDPOSTERURL = genreMovieImage
|
||||
row.SDPOSTERURL = genreMovieImage
|
||||
row.type = "Folder"
|
||||
end if
|
||||
|
||||
for each genreMovie in genreData.Items
|
||||
row = tmp.createChild("MovieData")
|
||||
|
||||
genreMovieImage = api_API().items.getimageurl(genreMovie.id)
|
||||
row.title = genreMovie.name
|
||||
row.FHDPOSTERURL = genreMovieImage
|
||||
row.HDPOSTERURL = genreMovieImage
|
||||
row.SDPOSTERURL = genreMovieImage
|
||||
row.json = genreMovie
|
||||
row.id = genreMovie.id
|
||||
row.type = genreMovie.type
|
||||
end for
|
||||
|
||||
else if item.Type = "Studio"
|
||||
tmp = CreateObject("roSGNode", "FolderData")
|
||||
else if item.Type = "MusicAlbum"
|
||||
|
@ -135,12 +190,16 @@ sub loadItems()
|
|||
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"
|
||||
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
|
||||
|
|
790
components/ItemGrid/MovieLibraryView.brs
Normal file
790
components/ItemGrid/MovieLibraryView.brs
Normal file
|
@ -0,0 +1,790 @@
|
|||
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")
|
||||
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")
|
||||
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 = "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.numRows = "2"
|
||||
m.selectedMovieOverview.visible = true
|
||||
m.infoGroup.visible = true
|
||||
|
||||
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 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"), "Name": "Movies" },
|
||||
{ "Title": tr("Studios"), "Name": "Studios" },
|
||||
{ "Title": tr("Genres"), "Name": "Genres" }
|
||||
]
|
||||
|
||||
if m.top.parentItem.json.type = "Genre"
|
||||
options.views = [
|
||||
{ "Title": tr("Movies"), "Name": "Movies" }
|
||||
]
|
||||
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" }
|
||||
]
|
||||
|
||||
if m.options.view = "Genres" or m.view = "Genres"
|
||||
options.sort = []
|
||||
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" },
|
||||
]
|
||||
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
|
||||
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 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 m.options.view = "Studios" or m.view = "Studios"
|
||||
return
|
||||
end if
|
||||
|
||||
itemData = m.selectedFavoriteItem.json
|
||||
|
||||
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
|
||||
|
||||
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
|
65
components/ItemGrid/MovieLibraryView.xml
Normal file
65
components/ItemGrid/MovieLibraryView.xml
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?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="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>
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,12 +220,24 @@ 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"
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
@ -49,9 +51,11 @@ sub setPoster()
|
|||
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 }
|
||||
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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -133,6 +133,7 @@ sub itemContentChanged()
|
|||
end if
|
||||
return
|
||||
end if
|
||||
|
||||
if itemData.type = "Series"
|
||||
|
||||
m.itemText.text = itemData.name
|
||||
|
@ -170,6 +171,34 @@ 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
|
||||
|
|
|
@ -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()
|
||||
|
@ -61,20 +67,28 @@ sub onLibrariesLoaded()
|
|||
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
|
||||
' 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
|
||||
|
@ -99,6 +113,10 @@ sub onLibrariesLoaded()
|
|||
m.LoadContinueTask.observeField("content", "updateContinueItems")
|
||||
m.LoadContinueTask.control = "RUN"
|
||||
|
||||
' Load the Favorites Data
|
||||
m.LoadFavoritesTask.observeField("content", "updateFavoritesItems")
|
||||
m.LoadFavoritesTask.control = "RUN"
|
||||
|
||||
' If we have Live TV access, load "On Now" data
|
||||
if haveLiveTV
|
||||
m.LoadOnNowTask.observeField("content", "updateOnNowItems")
|
||||
|
@ -116,6 +134,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")
|
||||
|
|
|
@ -100,6 +100,27 @@ sub loadItems()
|
|||
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"
|
||||
|
||||
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 = "onNow"
|
||||
url = "LiveTv/Programs/Recommended"
|
||||
params = {}
|
||||
|
@ -151,6 +172,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>
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -55,14 +55,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("star").visible = "true"
|
||||
else
|
||||
' hide the star icon
|
||||
m.top.findNode("communityRatingGroup").visible = false
|
||||
m.top.findNode("infoGroup").removeChild(m.top.findNode("communityRatingGroup"))
|
||||
end if
|
||||
|
||||
if itemData.CriticRating <> invalid
|
||||
|
@ -86,6 +92,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
|
||||
|
@ -101,10 +109,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"
|
||||
|
@ -115,6 +121,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)
|
||||
|
@ -125,11 +140,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" />
|
||||
|
|
|
@ -408,7 +408,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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -18,6 +18,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,8 +36,14 @@ 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
|
||||
|
@ -41,7 +53,8 @@ sub itemContentChanged()
|
|||
m.top.findNode("star").visible = true
|
||||
m.top.findNode("communityRating").text = str(int(itemData.communityRating * 10) / 10)
|
||||
else
|
||||
m.top.findNode("star").visible = false
|
||||
|
||||
m.top.findnode("infoBar").removeChild(m.top.findnode("rating"))
|
||||
end if
|
||||
|
||||
videoIdx = invalid
|
||||
|
|
|
@ -8,13 +8,14 @@
|
|||
<!-- 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]">
|
||||
|
|
|
@ -17,14 +17,31 @@ sub itemContentChanged()
|
|||
|
||||
' 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 +49,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 +67,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 +130,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
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<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>
|
||||
|
|
|
@ -8295,5 +8295,134 @@
|
|||
<source>Change Server</source>
|
||||
<translation>Server wechseln</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Zugangsdaten speichern?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Retrieving Content</source>
|
||||
<translation>Fehler beim Abrufen des Inhaltes</translation>
|
||||
<extracomment>Dialog title when unable to load Content from Server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error During Playback</source>
|
||||
<translation>Fehler bei der Wiedergabe</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>Es ist ein Fehler beim Abrufen der Daten vom Server aufgetreten.</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>Bei der Wiedergabe dieses Elements ist ein Fehler aufgetreten.</translation>
|
||||
<extracomment>Dialog detail when error occurs during playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to load Channel Data from the server</source>
|
||||
<translation>Kanaldaten können nicht vom Server geladen werden</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>Dieser %1 enthält keine Dateien</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Name or Title field of media item</comment>
|
||||
<source>TITLE</source>
|
||||
<translation>Name</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CRITIC_RATING</source>
|
||||
<translation>Kritikerbewertung</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>RELEASE_DATE</source>
|
||||
<translation>Veröffentlichungsdatum</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Title of Tab for options to sort library content</comment>
|
||||
<source>TAB_SORT</source>
|
||||
<translation>Sortierung</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Born</source>
|
||||
<translation>Geboren am</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Age</source>
|
||||
<translation>Alter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cast & Crew</source>
|
||||
<translation>Besetzung & Mitwirkende</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Died</source>
|
||||
<translation>Gestorben am</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sign Out</source>
|
||||
<translation>Abmelden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>On Now</source>
|
||||
<translation>Jetzt verfügbar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error loading Channel Data</source>
|
||||
<translation>Fehler beim Laden der Kanalinformationen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Title of Tab for switching "views" when looking at a library</comment>
|
||||
<source>TAB_VIEW</source>
|
||||
<translation>Übersicht</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>More Like This</source>
|
||||
<translation>Ähnliches</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Movies</source>
|
||||
<translation>Filme</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>RUNTIME</source>
|
||||
<translation>Laufzeit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>OFFICIAL_RATING</source>
|
||||
<translation>Altersfreigabe</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Loading Channel Data</source>
|
||||
<translation>Lade Daten des Kanals</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>IMDB_RATING</source>
|
||||
<translation>IMDb Bewertung</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PLAY_COUNT</source>
|
||||
<translation>Wiedergabezähler</translation>
|
||||
</message>
|
||||
<message>
|
||||
<comment>Title of Tab for options to filter library content</comment>
|
||||
<source>TAB_FILTER</source>
|
||||
<translation>Filter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Change Server</source>
|
||||
<translation>Server wechseln</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>DATE_PLAYED</source>
|
||||
<translation>Abgespielt am</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>DATE_ADDED</source>
|
||||
<translation>Hinzugefügt am</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -1844,5 +1844,276 @@
|
|||
<source>Disabled</source>
|
||||
<translation>Disabled</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use voice remote to search</source>
|
||||
<translation>Use voice remote to search</translation>
|
||||
<extracomment>Help text in search voice text box</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</source>
|
||||
<translation>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</translation>
|
||||
<extracomment>Description for option in Setting Screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for TV Shows.</source>
|
||||
<translation>Options for TV Shows.</translation>
|
||||
<extracomment>Description for TV Shows user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
|
||||
<translation>Cinema Mode brings the cinema experience straight to your living room with the ability to play custom intros before the main feature.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</source>
|
||||
<translation>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings relating to playback and supported codec and media types.</source>
|
||||
<translation>Settings relating to playback and supported codec and media types.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings relating to how the application looks.</source>
|
||||
<translation>Settings relating to how the application looks.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Studios</source>
|
||||
<translation>Studios</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Return to Top</source>
|
||||
<translation>Return to Top</translation>
|
||||
<extracomment>UI -> Media Grid -> Item Title in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Details Page</source>
|
||||
<translation>Details Page</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide Taglines</source>
|
||||
<translation>Hide Taglines</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use Splashscreen as Screensaver Background</source>
|
||||
<translation>Use Splashscreen as Screensaver Background</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Playback Information</source>
|
||||
<translation>Playback Information</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Transcoding Information</source>
|
||||
<translation>Transcoding Information</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>Level</source>
|
||||
<translation>Level</translation>
|
||||
<extracomment>Video profile level</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Bit Rate</source>
|
||||
<translation>Bit Rate</translation>
|
||||
<extracomment>Video streaming bit rate</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Size</source>
|
||||
<translation>Size</translation>
|
||||
<extracomment>Video size</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>WxH</source>
|
||||
<translation>WxH</translation>
|
||||
<extracomment>Video width x height</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quick Connect</source>
|
||||
<translation>Quick Connect</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Here is your Quick Connect code:</source>
|
||||
<translation>Here is your Quick Connect code:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>(Dialog will close automatically)</source>
|
||||
<translation>(Dialogue will close automatically)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Play Trailer</source>
|
||||
<translation>Play Trailer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Home Page</source>
|
||||
<translation>Home Page</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for Home Page.</source>
|
||||
<translation>Options for Home Page.</translation>
|
||||
<extracomment>Description for Home Page user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cinema Mode</source>
|
||||
<translation>Cinema Mode</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use Splashscreen as Home Background</source>
|
||||
<translation>Use Splashscreen as Home Background</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Design Elements</source>
|
||||
<translation>Design Elements</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Screensaver</source>
|
||||
<translation>Screensaver</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Search now</source>
|
||||
<translation>Search now</translation>
|
||||
<extracomment>Help text in search Box</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to episode</source>
|
||||
<translation>Go to episode</translation>
|
||||
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to season</source>
|
||||
<translation>Go to season</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>You can search for Titles, People, Live TV Channels and more</translation>
|
||||
<extracomment>Help text in search results</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 of %2</source>
|
||||
<translation>%1 of %2</translation>
|
||||
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>There was an error authenticating via Quick Connect.</source>
|
||||
<translation>There was an error authenticating via Quick Connect.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Networks</source>
|
||||
<translation>Networks</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Shows</source>
|
||||
<translation>Shows</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for Details pages.</source>
|
||||
<translation>Options for Details pages.</translation>
|
||||
<extracomment>Description for Details page user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hides tagline text on details pages.</source>
|
||||
<translation>Hides tagline text on details pages.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Blur Unwatched Episodes</source>
|
||||
<translation>Blur Unwatched Episodes</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>If enabled, images of unwatched episodes will be blurred.</source>
|
||||
<translation>If enabled, images of unwatched episodes will be blurred.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for Jellyfin's screensaver.</source>
|
||||
<translation>Options for Jellyfin's screensaver.</translation>
|
||||
<extracomment>Description for Screensaver user settings.</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>Use generated splashscreen image as Jellyfin's screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options that alter the design of Jellyfin.</source>
|
||||
<translation>Options that alter the design of Jellyfin.</translation>
|
||||
<extracomment>Description for Design Elements user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use generated splashscreen image as Jellyfin's home background. Jellyfin will need to be closed and reopened for change to take effect.</source>
|
||||
<translation>Use generated splashscreen image as Jellyfin's home background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
|
||||
<extracomment>Description for option in Setting Screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide Clock</source>
|
||||
<translation>Hide Clock</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Max Days Next Up</source>
|
||||
<translation>Max Days Next Up</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.</source>
|
||||
<translation>Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reason</source>
|
||||
<translation>Reason</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Total Bitrate</source>
|
||||
<translation>Total Bitrate</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Audio Channels</source>
|
||||
<translation>Audio Channels</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Stream Information</source>
|
||||
<translation>Stream Information</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Codec</source>
|
||||
<translation>Codec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Codec Tag</source>
|
||||
<translation>Codec Tag</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Container</source>
|
||||
<translation>Container</translation>
|
||||
<extracomment>Video streaming container</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Video range type</source>
|
||||
<translation>Video range type</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pixel format</source>
|
||||
<translation>Pixel format</translation>
|
||||
<extracomment>Video pixel format</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to find any albums or songs belonging to this artist</source>
|
||||
<translation>Unable to find any albums or songs belonging to this artist</translation>
|
||||
<extracomment>Popup message when we find no audio data for an artist</extracomment>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -285,13 +285,18 @@
|
|||
<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>
|
||||
|
@ -489,6 +494,16 @@
|
|||
<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>AV1 Support</source>
|
||||
<translation>AV1 Support</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** Support Direct Play of AV1 content if this Roku device supports it.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enabled</source>
|
||||
<translation>Enabled</translation>
|
||||
|
@ -703,10 +718,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>Direct Play H.264 Unsupported Profile Levels</source>
|
||||
<translation>Direct Play H.264 Unsupported Profile Levels</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>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>Direct Play HEVC Unsupported Profile Levels</source>
|
||||
<translation>Direct Play HEVC Unsupported Profile Levels</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.</source>
|
||||
<translation>Attempt Direct Play for HEVC media with unsupported profile levels before falling back to 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 +856,49 @@
|
|||
<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>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>
|
||||
</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>
|
||||
|
|
|
@ -1884,5 +1884,320 @@
|
|||
<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>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -3605,5 +3605,441 @@
|
|||
<source>RELEASE_DATE</source>
|
||||
<translation>Date de sortie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Return to Top</source>
|
||||
<translation>Retour en haut</translation>
|
||||
<extracomment>UI -> Media Grid -> Item Title in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Here is your Quick Connect code:</source>
|
||||
<translation>Voici votre code Quick Connect :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Getting Playback Information</source>
|
||||
<translation>Erreur lors de l'obtention des informations de lecture</translation>
|
||||
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pick a Jellyfin server from the local network</source>
|
||||
<translation>Sélectionnez un serveur Jellyfin disponible sur votre réseau local :</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel Recording</source>
|
||||
<translation>Annuler l'enregistrement</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record</source>
|
||||
<translation>Enregistrer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Screensaver</source>
|
||||
<translation>Écran de veille</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to episode</source>
|
||||
<translation>Aller à l'épisode</translation>
|
||||
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to series</source>
|
||||
<translation>Aller à la série</translation>
|
||||
<extracomment>Continue Watching Popup Menu - Navigate to the Series Detail Page</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Studios</source>
|
||||
<translation>Studios</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Search now</source>
|
||||
<translation>Rechercher maintenant</translation>
|
||||
<extracomment>Help text in search Box</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>You can search for Titles, People, Live TV Channels and more</source>
|
||||
<translation>Vous pouvez rechercher des titres, des personnes, des chaînes de télévision en direct et plus encore</translation>
|
||||
<extracomment>Help text in search results</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quick Connect</source>
|
||||
<translation>Quick Connect</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 of %2</source>
|
||||
<translation>%1 sur %2</translation>
|
||||
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for Jellyfin's screensaver.</source>
|
||||
<translation>Options de l'écran de veille de Jellyfin.</translation>
|
||||
<extracomment>Description for Screensaver user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cinema Mode</source>
|
||||
<translation>Mode Cinéma</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
|
||||
<translation>Le Mode Cinéma vous fait vivre l'expérience du cinéma directement dans votre salon en vous permettant de diffuser des intros personnalisées avant le film.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>WxH</source>
|
||||
<translation>WxH</translation>
|
||||
<extracomment>Video width x height</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Shows</source>
|
||||
<translation>Séries</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Networks</source>
|
||||
<translation>Réseaux</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>User Interface</source>
|
||||
<translation>Interface Utilisateur</translation>
|
||||
<extracomment>Title for User Interface section in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Close</source>
|
||||
<translation>Fermer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Play Trailer</source>
|
||||
<translation>Lire la bande-annonce</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Item Count</source>
|
||||
<translation>Nombre d'éléments</translation>
|
||||
<extracomment>UI -> Media Grid -> Item Count in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Version</source>
|
||||
<translation>Version</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connecting to Server</source>
|
||||
<translation>Connexion au Serveur</translation>
|
||||
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Guide</source>
|
||||
<translation>Guide Télé</translation>
|
||||
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not found</source>
|
||||
<translation>Introuvable</translation>
|
||||
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>The requested content does not exist on the server</source>
|
||||
<translation>Le contenu demandé n'existe pas sur le serveur</translation>
|
||||
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter the server name or IP address</source>
|
||||
<translation>Entrer le nom du serveur ou son adresse IP</translation>
|
||||
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to season</source>
|
||||
<translation>Aller à la saison</translation>
|
||||
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for TV Shows.</source>
|
||||
<translation>Options pour les Séries Télé.</translation>
|
||||
<extracomment>Description for TV Shows user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Blur Unwatched Episodes</source>
|
||||
<translation>Flouter les épisodes non visionnés</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>If enabled, images of unwatched episodes will be blurred.</source>
|
||||
<translation>Si activé, les images des épisodes non visionnés seront floutées.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Video Codec</source>
|
||||
<translation>Codec Vidéo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Audio Codec</source>
|
||||
<translation>Codec Audio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Audio Channels</source>
|
||||
<translation>Canaux Audio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Codec</source>
|
||||
<translation>Codec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Codec Tag</source>
|
||||
<translation>Balise de Codec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Container</source>
|
||||
<translation>Conteneur</translation>
|
||||
<extracomment>Video streaming container</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Size</source>
|
||||
<translation>Taille</translation>
|
||||
<extracomment>Video size</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unknown</source>
|
||||
<translation>Inconnu</translation>
|
||||
<extracomment>Title for a cast member for which we have no information for</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enabled</source>
|
||||
<translation>Activé</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disabled</source>
|
||||
<translation>Désactivé</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Playback</source>
|
||||
<translation>Lecture</translation>
|
||||
<extracomment>Title for Playback section in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
|
||||
<translation>Une erreur a été rencontrée lors de la lecture de ce fichier. Le serveur n'a pas communiqué les données nécessaires pour le transcodage.</translation>
|
||||
<extracomment>Content of message box when trying to play an item which requires transcoding, and the server did not provide transcode url</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</source>
|
||||
<translation>Toujours afficher les titres sous les images des affiches. (Si désactivé, les titres s'afficherons uniquement sous les éléments en surbrillance).</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>Afficher le nombre d'éléments dans la bibliotèque et l'index de l'élément sélectionné.</translation>
|
||||
<extracomment>Description for option in Setting Screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use voice remote to search</source>
|
||||
<translation>Utiliser la télécomande vocale pour rechercher</translation>
|
||||
<extracomment>Help text in search voice text box</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>(Dialog will close automatically)</source>
|
||||
<translation>(La boîte de dialogue se fermera automatiquement)</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>Utiliser la touche Replay pour lentement animer la sélection du première élément du dossier. (Si désactivé, le premier élément du dossier sera immédiatement sélectionné).</translation>
|
||||
<extracomment>Description for option in Setting Screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for Details pages.</source>
|
||||
<translation>Options des Pages de Détails.</translation>
|
||||
<extracomment>Description for Details page user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for TV Shows.</source>
|
||||
<translation>Options pour les Séries Télévisée.</translation>
|
||||
<extracomment>Description for TV Shows user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>View Channel</source>
|
||||
<translation>Voir la Chaîne</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use Splashscreen as Screensaver Background</source>
|
||||
<translation>Utiliser le Splash Screen comme fond d'écran de veille</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>WxH</source>
|
||||
<translation>LxH</translation>
|
||||
<extracomment>Video width x height</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>Utiliser le Splash Screen généré comme fond d'écran de veille de Jellyfin. Jellyfin devra être fermé et rouvert pour que le changement prenne effet.</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>Utiliser le Splash Screen généré comme arrière plan principal de Jellyfin. Jellyfin devra être fermé et rouvert pour que le changement prenne effet.</translation>
|
||||
<extracomment>Description for option in Setting Screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide Clock</source>
|
||||
<translation>Masquer l'Horloge</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</source>
|
||||
<translation>Masquer toutes les horloges dans Jellyfin. Jellyfin devra être fermé et rouvert pour que le changement prenne effet.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings relating to how the application looks.</source>
|
||||
<translation>Réglages relatifs à l'apparence de l'application.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pixel format</source>
|
||||
<translation>Format des pixels</translation>
|
||||
<extracomment>Video pixel format</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to find any albums or songs belonging to this artist</source>
|
||||
<translation>Aucuns n'albums ni chansons ont été trouvés pour cet artiste</translation>
|
||||
<extracomment>Popup message when we find no audio data for an artist</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Starts</source>
|
||||
<translation>Débute</translation>
|
||||
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record Series</source>
|
||||
<translation>Enregistrer la Série</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide Taglines</source>
|
||||
<translation>Masquer les étiquettes</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Media Grid options.</source>
|
||||
<translation>Options de la Grille Média.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set Favorite</source>
|
||||
<translation>Mettre en Favori</translation>
|
||||
<extracomment>Button Text - When pressed, sets item as Favorite</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Details Page</source>
|
||||
<translation>Page de Détails</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for Home Page.</source>
|
||||
<translation>Options pour la Page d'Accueil.</translation>
|
||||
<extracomment>Description for Home Page user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Max Days Next Up</source>
|
||||
<translation>Nombre Max de jours dans Suivant</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>Définir le nombre maximum de jours une émission doit rester dans la liste "Suivant" sans la regarder.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Playback Information</source>
|
||||
<translation>Informations de Lecture</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Design Elements</source>
|
||||
<translation>Élements de Désign</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Home Page</source>
|
||||
<translation>Page d'Accueil</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Transcoding Information</source>
|
||||
<translation>Informations de Transcodage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options that alter the design of Jellyfin.</source>
|
||||
<translation>Options qui modifient le design de Jellyfin.</translation>
|
||||
<extracomment>Description for Design Elements user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use Splashscreen as Home Background</source>
|
||||
<translation>Utiliser le Splash Screen comme arrière-plan principal</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Media Grid</source>
|
||||
<translation>Grille Média</translation>
|
||||
<extracomment>UI -> Media Grid section in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>MPEG-2 Support</source>
|
||||
<translation>Support MPEG-2</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel Series Recording</source>
|
||||
<translation>Annuler l'enregistrement de la Série</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>...or enter server URL manually:</source>
|
||||
<translation>Si aucun serveur n'est affiché ci-dessus vous pouvez également saisir l'adresse IP du serveur manuellement :</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</source>
|
||||
<translation>Prise en charge de la lecture directe du codec MPEG-2 (ex., Live TV). Cela empêchera le transcodage du contenu MPEG-2 mais utilisera sensiblement plus de bande passante.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Item Titles</source>
|
||||
<translation>Titres des éléments</translation>
|
||||
<extracomment>UI -> Media Grid -> Item Title in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set Watched</source>
|
||||
<translation>Mettre en Visionné</translation>
|
||||
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>There was an error authenticating via Quick Connect.</source>
|
||||
<translation>Une erreur s'est produite lors de l'autentification via Quick Connect.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hides tagline text on details pages.</source>
|
||||
<translation>Masquer les étiquettes sur la page des détails.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings relating to playback and supported codec and media types.</source>
|
||||
<translation>Réglages relatifs à la lecture, aux codecs pris en charges et aux types de médias.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reason</source>
|
||||
<translation>Raison</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>direct</source>
|
||||
<translation>direct</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Total Bitrate</source>
|
||||
<translation>Débit Total</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Stream Information</source>
|
||||
<translation>Informations du Flux</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Level</source>
|
||||
<translation>Niveau</translation>
|
||||
<extracomment>Video profile level</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Bit Rate</source>
|
||||
<translation>Débit</translation>
|
||||
<extracomment>Video streaming bit rate</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Video range type</source>
|
||||
<translation>Différents types de vidéo</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -7642,5 +7642,17 @@ elemeket</translation>
|
|||
<source>Save Credentials?</source>
|
||||
<translation>Mented a hitelesítő adatokat?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete Saved</source>
|
||||
<translation>Mentettek Törlése</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Mented a hitelesítő adatokat?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>On Now</source>
|
||||
<translation>Most</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -1723,5 +1723,203 @@
|
|||
<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>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -2583,5 +2583,65 @@ não contém itens</translation>
|
|||
<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>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -671,5 +671,292 @@
|
|||
<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>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
2
manifest
2
manifest
|
@ -2,7 +2,7 @@
|
|||
title=Jellyfin
|
||||
major_version=1
|
||||
minor_version=6
|
||||
build_version=1
|
||||
build_version=2
|
||||
|
||||
### Main Menu Icons / Channel Poster Artwork
|
||||
mm_icon_focus_fhd=pkg:/images/channel-poster_fhd.png
|
||||
|
|
6804
package-lock.json
generated
6804
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,15 +1,11 @@
|
|||
{
|
||||
"name": "jellyfin-roku",
|
||||
"version": "1.6.1",
|
||||
"version": "1.6.2",
|
||||
"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",
|
||||
"brighterscript": "0.61.1",
|
||||
"ropm": "0.10.10"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
"title": "Playback",
|
||||
"description": "Settings relating to playback and supported codec and media types.",
|
||||
"children": [
|
||||
{
|
||||
"title": "AV1 Support",
|
||||
"description": "** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.",
|
||||
"settingName": "playback.av1",
|
||||
"type": "bool",
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"title": "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.",
|
||||
|
@ -11,12 +18,26 @@
|
|||
"default": "false"
|
||||
},
|
||||
{
|
||||
"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.",
|
||||
"title": "MPEG-4 Support",
|
||||
"description": "Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.",
|
||||
"settingName": "playback.mpeg4",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"title": "Direct Play H.264 Unsupported Profile Levels",
|
||||
"description": "Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.",
|
||||
"settingName": "playback.tryDirect.h264ProfileLevel",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"title": "Direct Play HEVC Unsupported Profile Levels",
|
||||
"description": "Attempt Direct Play for HEVC media with unsupported profile levels before falling back to trancoding if it fails.",
|
||||
"settingName": "playback.tryDirect.hevcProfileLevel",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"title": "Cinema Mode",
|
||||
"description": "Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.",
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
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()
|
||||
|
||||
' The main function that runs when the application is launched.
|
||||
m.screen = CreateObject("roSGScreen")
|
||||
|
||||
|
@ -13,13 +9,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")
|
||||
|
@ -110,8 +99,20 @@ sub Main (args as dynamic) as void
|
|||
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
|
||||
group = CreateItemGrid(selectedItem)
|
||||
end if
|
||||
sceneManager.callFunc("pushScene", group)
|
||||
else if selectedItem.type = "Folder" and selectedItem.json.type = "Genre"
|
||||
group = CreateMovieLibraryView(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 +129,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 +145,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"
|
||||
|
@ -332,7 +340,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)
|
||||
end if
|
||||
|
@ -437,12 +445,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,7 +352,7 @@ 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
|
||||
|
@ -369,7 +369,7 @@ function CreateSeriesDetailsGroup(series)
|
|||
|
||||
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 +453,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 +475,14 @@ 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 CreateSearchPage()
|
||||
' Search + Results Page
|
||||
group = CreateObject("roSGNode", "searchResults")
|
||||
|
@ -476,10 +498,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
|
||||
|
@ -570,17 +592,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,7 +19,7 @@ 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
|
||||
|
@ -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
|
||||
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
|
||||
|
@ -213,16 +220,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
|
||||
|
||||
|
@ -420,7 +431,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)
|
||||
|
|
|
@ -329,26 +329,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)
|
||||
|
|
|
@ -18,6 +18,7 @@ end function
|
|||
|
||||
function getDeviceProfile() as object
|
||||
playMpeg2 = get_user_setting("playback.mpeg2") = "true"
|
||||
playAv1 = get_user_setting("playback.av1") = "true"
|
||||
|
||||
'Check if 5.1 Audio Output connected
|
||||
maxAudioChannels = 2
|
||||
|
@ -26,14 +27,19 @@ function getDeviceProfile() as object
|
|||
maxAudioChannels = 6
|
||||
end if
|
||||
|
||||
if playMpeg2 and di.CanDecodeVideo({ Codec: "mpeg2" }).Result = true
|
||||
tsVideoCodecs = "h264,mpeg2video"
|
||||
else
|
||||
tsVideoCodecs = "h264"
|
||||
addHevcProfile = false
|
||||
MAIN10 = ""
|
||||
tsVideoCodecs = "h264"
|
||||
if di.CanDecodeVideo({ Codec: "hevc" }).Result = true
|
||||
tsVideoCodecs = "h265,hevc," + tsVideoCodecs
|
||||
addHevcProfile = true
|
||||
if di.CanDecodeVideo({ Codec: "hevc", Profile: "main 10" }).Result
|
||||
MAIN10 = "|main 10"
|
||||
end if
|
||||
end if
|
||||
|
||||
if di.CanDecodeVideo({ Codec: "hevc" }).Result = true
|
||||
tsVideoCodecs = tsVideoCodecs + ",h265,hevc"
|
||||
if playMpeg2 and di.CanDecodeVideo({ Codec: "mpeg2" }).Result = true
|
||||
tsVideoCodecs = tsVideoCodecs + ",mpeg2video"
|
||||
end if
|
||||
|
||||
if di.CanDecodeAudio({ Codec: "ac3" }).result
|
||||
|
@ -42,9 +48,42 @@ function getDeviceProfile() as object
|
|||
tsAudioCodecs = "aac"
|
||||
end if
|
||||
|
||||
addAv1Profile = false
|
||||
if playAv1 and di.CanDecodeVideo({ Codec: "av1" }).result
|
||||
tsVideoCodecs = tsVideoCodecs + ",av1"
|
||||
addAv1Profile = true
|
||||
end if
|
||||
|
||||
addVp9Profile = false
|
||||
if di.CanDecodeVideo({ Codec: "vp9" }).result
|
||||
tsVideoCodecs = tsVideoCodecs + ",vp9"
|
||||
addVp9Profile = true
|
||||
end if
|
||||
|
||||
hevcVideoRangeTypes = "SDR"
|
||||
vp9VideoRangeTypes = "SDR"
|
||||
av1VideoRangeTypes = "SDR"
|
||||
|
||||
dp = di.GetDisplayProperties()
|
||||
if dp.Hdr10 ' or dp.Hdr10Plus?
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|HDR10"
|
||||
vp9VideoRangeTypes = vp9VideoRangeTypes + "|HDR10"
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|HDR10"
|
||||
end if
|
||||
if dp.HLG
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|HLG"
|
||||
vp9VideoRangeTypes = vp9VideoRangeTypes + "|HLG"
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|HLG"
|
||||
end if
|
||||
if dp.DolbyVision
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|DOVI"
|
||||
'vp9VideoRangeTypes = vp9VideoRangeTypes + ",DOVI" no evidence that vp9 can hold DOVI
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|DOVI"
|
||||
end if
|
||||
|
||||
DirectPlayProfile = GetDirectPlayProfiles()
|
||||
|
||||
return {
|
||||
deviceProfile = {
|
||||
"MaxStreamingBitrate": 120000000,
|
||||
"MaxStaticBitrate": 100000000,
|
||||
"MusicStreamingTranscodingBitrate": 192000,
|
||||
|
@ -133,24 +172,6 @@ function getDeviceProfile() as object
|
|||
"IsRequired": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "Video",
|
||||
"Codec": "hevc",
|
||||
"Conditions": [
|
||||
{
|
||||
"Condition": "EqualsAny",
|
||||
"Property": "VideoProfile",
|
||||
"Value": "main",
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Condition": "LessThanEqual",
|
||||
"Property": "VideoLevel",
|
||||
"Value": StrI(120 * 5.1),
|
||||
"IsRequired": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SubtitleProfiles": [
|
||||
|
@ -172,12 +193,68 @@ function getDeviceProfile() as object
|
|||
}
|
||||
]
|
||||
}
|
||||
if addAv1Profile
|
||||
deviceProfile.CodecProfiles.push({
|
||||
"Type": "Video",
|
||||
"Codec": "av1",
|
||||
"Conditions": [
|
||||
{
|
||||
"Condition": "EqualsAny",
|
||||
"Property": "VideoRangeType",
|
||||
"Value": av1VideoRangeTypes,
|
||||
"IsRequired": false
|
||||
}
|
||||
]
|
||||
})
|
||||
end if
|
||||
if addHevcProfile
|
||||
deviceProfile.CodecProfiles.push({
|
||||
"Type": "Video",
|
||||
"Codec": "hevc",
|
||||
"Conditions": [
|
||||
{
|
||||
"Condition": "EqualsAny",
|
||||
"Property": "VideoProfile",
|
||||
"Value": "main" + MAIN10,
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Condition": "EqualsAny",
|
||||
"Property": "VideoRangeType",
|
||||
"Value": hevcVideoRangeTypes,
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Condition": "LessThanEqual",
|
||||
"Property": "VideoLevel",
|
||||
"Value": (120 * 5.1).ToStr(),
|
||||
"IsRequired": false
|
||||
}
|
||||
]
|
||||
})
|
||||
end if
|
||||
if addVp9Profile
|
||||
deviceProfile.CodecProfiles.push({
|
||||
"Type": "Video",
|
||||
"Codec": "vp9",
|
||||
"Conditions": [
|
||||
{
|
||||
"Condition": "EqualsAny",
|
||||
"Property": "VideoRangeType",
|
||||
"Value": vp9VideoRangeTypes,
|
||||
"IsRequired": false
|
||||
}
|
||||
]
|
||||
})
|
||||
end if
|
||||
|
||||
return deviceProfile
|
||||
end function
|
||||
|
||||
|
||||
function GetDirectPlayProfiles() as object
|
||||
|
||||
mp4Video = "h264,mpeg4"
|
||||
mp4Video = "h264"
|
||||
mp4Audio = "mp3,pcm,lpcm,wav"
|
||||
mkvVideo = "h264,vp8"
|
||||
mkvAudio = "mp3,pcm,lpcm,wav"
|
||||
|
@ -202,6 +279,10 @@ function GetDirectPlayProfiles() as object
|
|||
mkvVideo = mkvVideo + ",mpeg2video"
|
||||
end if
|
||||
|
||||
if get_user_setting("playback.mpeg4") = "true"
|
||||
mp4Video = mp4Video + ",mpeg4"
|
||||
end if
|
||||
|
||||
' Check for supported Audio
|
||||
if di.CanDecodeAudio({ Codec: "ac3" }).result
|
||||
mkvAudio = mkvAudio + ",ac3"
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"testPath": "out/staging/source/tests/specs",
|
||||
"rootPath": "",
|
||||
"outputPath": "out/staging/source/tests/rooibos",
|
||||
"projectPath": "."
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component
|
||||
name="TestsScene"
|
||||
extends="Scene"
|
||||
xsi:noNamespaceSchemaLocation="https://devtools.web.roku.com/schema/RokuSceneGraph.xsd"
|
||||
>
|
||||
<interface>
|
||||
<function name="Rooibos_CreateTestNode" />
|
||||
</interface>
|
||||
|
||||
<script type="text/brightscript" uri="pkg:/source/tests/rooibos/rooibosDist.brs" />
|
||||
</component>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,20 +0,0 @@
|
|||
'@TestSuite [GLT] Globals Tests
|
||||
|
||||
'@Setup
|
||||
function GLT_setup() as void
|
||||
|
||||
end function
|
||||
|
||||
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
'@It tests the ability to use the globals getter/setter
|
||||
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
|
||||
'@Test Global Getter and Setter
|
||||
'@Params["this", "that"]
|
||||
'@Params["somenumber", 2342342]
|
||||
function GLT__SetGetGlobals(key, value) as void
|
||||
setGlobal(key, value)
|
||||
m.assertEqual(getGlobal(key), value)
|
||||
end function
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"logLevel": 4,
|
||||
"testsDirectory": "pkg:source/tests/specs",
|
||||
"failFast": false,
|
||||
"showOnlyFailures": false,
|
||||
"maxLinesWithoutSuiteDirective": 30,
|
||||
"supportLegacyTests": false
|
||||
}
|
Loading…
Reference in New Issue
Block a user