Merge remote-tracking branch 'upstream/unstable' into fix-crash-logs

This commit is contained in:
Charles Ewert 2023-05-11 12:37:29 -04:00
commit ccf60236d8
246 changed files with 9141 additions and 12859 deletions

View File

@ -18,4 +18,4 @@ jobs:
days-before-pr-stale: 21
days-before-pr-close: 7
exempt-draft-pr: true
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token: ${{ secrets.JF_BOT_TOKEN }}

View File

@ -30,4 +30,5 @@ jobs:
uses: eps1lon/actions-label-merge-conflict@releases/2.x
with:
dirtyLabel: "merge conflict"
repoToken: ${{ secrets.GITHUB_TOKEN }}
commentOnDirty: "This pull request has merge conflicts. Please resolve the conflicts so the PR can be reviewed. Thanks!"
repoToken: ${{ secrets.JF_BOT_TOKEN }}

View File

@ -1,4 +1,4 @@
name: build
name: build-dev
on:
pull_request:
@ -17,11 +17,14 @@ jobs:
with:
node-version: "lts/*"
cache: "npm"
- run: npm ci
- run: npx ropm install
- run: make dev
- name: NPM install
run: npm ci
- name: Install roku module dependencies
run: npx ropm install
- name: Build app
run: npm run build
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3
with:
name: Jellyfin-Roku-dev-${{ github.sha }}
path: ${{ github.workspace }}/out/staging
path: ${{ github.workspace }}/build/staging
if-no-files-found: error

View File

@ -1,4 +1,4 @@
name: build
name: build-prod
on:
pull_request:
@ -66,11 +66,14 @@ jobs:
with:
node-version: "lts/*"
cache: "npm"
- run: npm ci
- run: npx ropm install
- run: make release
- name: NPM install
run: npm ci
- name: Install roku module dependencies
run: npx ropm install
- name: Build app for production
run: npm run build-prod
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3
with:
name: Jellyfin-Roku-release-${{ github.sha }}
path: ${{ github.workspace }}/out/staging
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
path: ${{ github.workspace }}/build/staging
if-no-files-found: error

17
.gitignore vendored
View File

@ -1,18 +1,17 @@
*.svg
jellyfin-roku.zip
source/globals.brs
.env
###BrightScript specific
# BrightScript
dist/apps
out/
build/
roku_modules
#NPM modules
source/globals.brs
jellyfin-roku.zip
# Rooibos
bsconfig-tdd.json
# NPM
node_modules/
#Eclipse
# Eclipse
.buildpath
.project
.settings

418
.vscode/brighterscript.code-snippets vendored Normal file
View File

@ -0,0 +1,418 @@
{
"rooibos beforeEach": {
"prefix": "beforeEach",
"body": [
"@beforeEach",
"function ${2:namespace}_${3:itGroup}_beforeEach()",
"\t$0",
"end function"
]
},
"rooibos afterEach": {
"prefix": "afterEach",
"body": [
"@afterEach",
"function ${2:namespace}_${3:itGroup}_afterEach()",
"\t$0",
"end function"
]
},
"rooibos setup": {
"prefix": "setup",
"body": [
"@setup",
"function ${2:namespace}_setup()",
"\t$0",
"end function"
]
},
"rooibos tearDown": {
"prefix": "tearDown",
"body": [
"@tearDown",
"function ${2:namespace}_tearDown()",
"\t$0",
"end function"
]
},
"rooibos ignore": {
"prefix": "ignore",
"body": [
"@ignore ${1:reason}",
"$0"
]
},
"rooibos only": {
"prefix": "only",
"body": [
"@only",
"$0"
]
},
"rooibos testSuite": {
"prefix": "suite",
"body": [
"@suite(\"$1\")",
"$0"
]
},
"rooibos testcase": {
"prefix": "it",
"body": [
"@it(\"$1\")",
"function _()",
"\t$0",
"end function"
]
},
"rooibos params": {
"prefix": "params",
"body": [
"@params(${1:values})$0"
]
},
"rooibos it": {
"prefix": "describe",
"body": [
"'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++",
"@describe(\"${1:groupName}\")",
"'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++",
"",
"$0"
]
},
"rooibos stub": {
"prefix": "stub",
"body": [
"m.stub(${1:target}, \"${2:methodName}\", [${3:methodArgs}], ${4:result})",
"$0"
]
},
"rooibos mock": {
"prefix": "expect",
"body": [
"${1:mockName} = m.mock(${2:target}, \"${3:methodName}\", ${4:expectedNumberOfcalls}, [${5:methodArgs}], ${6:result})",
"$0"
]
},
"rooibos expect": {
"prefix": "expect",
"body": [
"m.expectOnce(${1:target}, \"${2:methodName}\", ${3:expectedNumberOfcalls}, [${4:methodArgs}], ${5:result})",
"$0"
]
},
"rooibos expectOnce": {
"prefix": "expectOnce",
"body": [
"m.expectOnce(${1:target}, \"${2:methodName}\", [${3:methodArgs}], ${4:result})",
"$0"
]
},
"rooibos expectCallfunc": {
"prefix": "expectCallfunc",
"body": [
"m.expectOnce(${1:target}, \"callFunc\", [\"${2:methodName}\", ${3:methodArgs}], ${4:result})",
"$0"
]
},
"rooibos expectObserveNodeField": {
"prefix": "eonf",
"body": [
"m.expectOnce(${1:target}, \"observeNodeField\", [${2:node},\"${3:fieldName}\", m.${4:callback}])",
"$0"
]
},
"rooibos expectUnObserveNodeField": {
"prefix": "eunf",
"body": [
"m.expectOnce(${1:target}, \"unobserveNodeField\", [${2:node},\"${:fieldName}\", m.${4:callback}])",
"$0"
]
},
"rooibos expectObjectOnce": {
"prefix": "expectObjectOnce",
"body": [
"${1:name} = { \"id\" : \"${1:name}\" }",
"m.expectOnce(${2:target}, \"${3:methodName}\", [${4:methodArgs}], ${1:name})",
"$0"
]
},
"rooibos expectGetInstance": {
"prefix": "expectGetInstance",
"body": [
"${1:name} = { \"id\" : \"${1:name}\" }",
"m.expectOnce(${2:target}, \"getInstance\", [\"${3:instanceName}\"], ${1:name})",
"$0"
]
},
"rooibos expectCreateSGNode": {
"prefix": "expectCreateSGNode",
"body": [
"${1:name} = { \"id\" : \"${1:name}\" }",
"m.expectOnce(${2:target}, \"createSGNode\", [\"${3:nodeType}\"$0], ${1:name})"
]
},
"rooibos expectGetClassInstance": {
"prefix": "expectGetClassInstance",
"body": [
"${1:name} = { \"id\" : \"${1:name}\" }",
"m.expectOnce(${2:target}, \"getClassInstance\", [\"${3:instanceName}\"], ${1:name})",
"$0"
]
},
"rooibos expectExpectOnce": {
"prefix": "expectExpect",
"body": [
"${1:name} = { \"id\" : \"${1:name}\" }",
"m.expectOnce(${2:target}, \"${3:methodName}\", [${4:methodArgs}], ${1:name})",
"m.expectOnce(${1:name}, \"${5:methodName}\", [${6:methodArgs}], ${7:name})",
"$0"
]
},
"rooibos expectNone": {
"prefix": "expectNone",
"body": [
"m.expectNone(${1:target}, \"${2:methodName}\")",
"$0"
]
},
"rooibos assertFalse": {
"prefix": "assertFalse",
"body": [
"m.assertFalse(${1:value})",
"$0"
]
},
"rooibos assertAsync": {
"prefix": "assertAsync",
"body": [
"m.AssertAsyncField(${1:value}, $2{:fieldName})",
"$0"
]
},
"rooibos assertTrue": {
"prefix": "assertTrue",
"body": [
"m.assertTrue(${1:value})",
"$0"
]
},
"rooibos assertEqual": {
"prefix": "assertEqual",
"body": [
"m.assertEqual(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertLike": {
"prefix": "assertLike",
"body": [
"m.assertLike(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNotEqual": {
"prefix": "assertNotEqual",
"body": [
"m.assertNotEqual(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertInvalid": {
"prefix": "assertInvalid",
"body": [
"m.assertInvalid(${1:value})",
"$0"
]
},
"rooibos assertNotInvalid": {
"prefix": "assertNotInvalid",
"body": [
"m.assertNotInvalid(${1:value})",
"$0"
]
},
"rooibos assertAAHasKey": {
"prefix": "assertAAHasKey",
"body": [
"m.assertAAHasKey(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertAANotHasKey": {
"prefix": "assertAANotHasKey",
"body": [
"m.assertAANotHasKey(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertAAHasKeys": {
"prefix": "assertAAHasKeys",
"body": [
"m.assertAAHasKeys(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertAANotHasKeys": {
"prefix": "assertAANotHasKeys",
"body": [
"m.assertAANotHasKeys(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayContains": {
"prefix": "assertArrayContains",
"body": [
"m.assertArrayContains(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayNotContains": {
"prefix": "assertArrayNotContains",
"body": [
"m.assertArrayNotContains(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayContainsSubset": {
"prefix": "assertArrayContainsSubset",
"body": [
"m.assertArrayContainsSubset(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayContainsAAs": {
"prefix": "assertArrayContainsAAs",
"body": [
"m.assertArrayContainsAAs(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayNotContainsSubset": {
"prefix": "assertArrayNotContainsSubset",
"body": [
"m.assertArrayNotContainsSubset(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayCount": {
"prefix": "assertArrayCount",
"body": [
"m.assertArrayCount(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayNotCount": {
"prefix": "assertArrayNotCount",
"body": [
"m.assertArrayNotCount(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertEmpty": {
"prefix": "assertEmpty",
"body": [
"m.assertEmpty(${1:value})",
"$0"
]
},
"rooibos assertNotEmpty": {
"prefix": "assertNotEmpty",
"body": [
"m.assertNotEmpty(${1:value})",
"$0"
]
},
"rooibos assertArrayContainsOnlyValuesOfType": {
"prefix": "assertArrayContainsOnlyValuesOfType",
"body": [
"m.assertArrayContainsOnlyValuesOfType(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertType": {
"prefix": "assertType",
"body": [
"m.assertType(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertSubType": {
"prefix": "assertSubType",
"body": [
"m.assertSubType(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeCount": {
"prefix": "assertNodeCount",
"body": [
"m.assertNodeCount(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeNotCount": {
"prefix": "assertNodeNotCount",
"body": [
"m.assertNodeNotCount(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeEmpty": {
"prefix": "assertNodeEmpty",
"body": [
"m.assertNodeEmpty(${1:value})",
"$0"
]
},
"rooibos assertNodeNotEmpty": {
"prefix": "assertNodeNotEmpty",
"body": [
"m.assertNodeNotEmpty(${1:value})",
"$0"
]
},
"rooibos assertNodeContains": {
"prefix": "assertNodeContains",
"body": [
"m.assertNodeContains(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeNotContains": {
"prefix": "assertNodeNotContains",
"body": [
"m.assertNodeNotContains(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeContainsFields": {
"prefix": "assertNodeContainsFields",
"body": [
"m.assertNodeContainsFields(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeNotContainsFields": {
"prefix": "assertNodeNotContainsFields",
"body": [
"m.assertNodeNotContainsFields(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertAAContainsSubset": {
"prefix": "assertAAContainsSubset",
"body": [
"m.assertAAContainsSubset(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertMocks": {
"prefix": "assertMocks",
"body": [
"m.assertMocks(${1:value}, ${2:expected})",
"$0"
]
}
}

70
.vscode/launch.json vendored
View File

@ -4,7 +4,9 @@
{
"type": "brightscript",
"request": "launch",
"name": "Jellyfin Debug: Launch",
"name": "Jellyfin Debug",
"rootDir": "${workspaceFolder}/build/staging",
"preLaunchTask": "build-dev",
"stopOnEntry": false,
// To enable RALE:
// set "brightscript.debug.raleTrackerTaskFileLocation": "/absolute/path/to/rale/TrackerTask.xml" in your vscode user settings
@ -14,14 +16,66 @@
//"host": "${promptForHost}",
//WARNING: don't edit this value. Instead, set "brightscript.debug.password": "YOUR_PASSWORD_HERE" in your vscode user settings
//"password": "${promptForPassword}",
},
{
"name": "Run tests",
"type": "brightscript",
"request": "launch",
"consoleOutput": "full",
"internalConsoleOptions": "neverOpen",
"preLaunchTask": "build-tests",
"retainStagingFolder": true,
"stopOnEntry": false,
"files": [
"components/**/*",
"images/**/*",
"locale/**/*",
"settings/**/*",
"source/**/*",
"manifest"
]
"!**/images/*.*",
"!**/fonts/*.*",
"!*.jpg",
"!*.png",
"*",
"*.*",
"**/*.*",
"!*.zip",
"!**/*.zip"
],
"rootDir": "${workspaceFolder}/build",
"sourceDirs": [
"${workspaceFolder}/test-app"
],
"enableDebuggerAutoRecovery": true,
"stopDebuggerOnAppExit": true,
"enableVariablesPanel": false,
"injectRaleTrackerTask": false,
"enableDebugProtocol": false
},
{
"name": "Run test-tdd",
"type": "brightscript",
"request": "launch",
"consoleOutput": "full",
"internalConsoleOptions": "neverOpen",
"preLaunchTask": "build-tdd",
"retainStagingFolder": true,
"stopOnEntry": false,
"files": [
"!**/images/*.*",
"!**/fonts/*.*",
"!*.jpg",
"!*.png",
"*",
"*.*",
"**/*.*",
"!*.zip",
"!**/*.zip"
],
"rootDir": "${workspaceFolder}/build",
"sourceDirs": [
"${workspaceFolder}/test-app"
],
"enableDebuggerAutoRecovery": true,
"stopDebuggerOnAppExit": true,
"enableVariablesPanel": false,
"injectRaleTrackerTask": false,
"enableDebugProtocol": false
}
]
}

10
.vscode/settings.json vendored
View File

@ -2,6 +2,14 @@
"files.associations": {
"*.ts": "xml"
},
"[xml]": {
"editor.defaultFormatter": "redhat.vscode-xml"
},
"[markdown]": {
"editor.defaultFormatter": "DavidAnson.vscode-markdownlint"
},
"xml.format.maxLineWidth": 0,
"editor.formatOnSave": true
"editor.formatOnSave": true,
"brightscript.output.hyperlinkFormat": "FilenameAndFunction",
"brightscript.bsdk": "node_modules/brighterscript"
}

77
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,77 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build-dev",
"type": "shell",
"command": "npm run build",
"problemMatcher": [],
"presentation": {
"echo": true,
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "build-prod",
"type": "shell",
"command": "npm run build-prod",
"problemMatcher": [],
"presentation": {
"echo": true,
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "build-tests",
"type": "shell",
"command": "npm run build-tests",
"problemMatcher": [],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "build-tdd",
"type": "shell",
"command": "npm run build-tdd",
"problemMatcher": [],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -10,7 +10,7 @@
##########################################################################
APPNAME = Jellyfin_Roku
VERSION = 1.6.5
VERSION = 1.6.6
ZIP_EXCLUDE= -x xml/* -x artwork/* -x \*.pkg -x storeassets\* -x keys\* -x \*/.\* -x *.git* -x *.DS* -x *.pkg* -x dist/**\* -x out/**\*

37
bsconfig-prod.json Normal file
View File

@ -0,0 +1,37 @@
{
"files": [
"manifest",
"source/**/*.*",
"components/**/*.*",
"images/**/*.*",
"locale/en_US/translations.ts",
"locale/en_GB/translations.ts",
"locale/fr_CA/translations.ts",
"locale/es_ES/translations.ts",
"locale/de_DE/translations.ts",
"locale/it_IT/translations.ts",
"locale/pt_BR/translations.ts",
"settings/**/*.*",
{
"src": "resources/branding/release/*.*",
"dest": "images"
}
],
"plugins": [
"@rokucommunity/bslint",
"roku-log-bsc-plugin"
],
"rokuLog": {
"strip": true,
"insertPkgPath": false,
"removeComments": true
},
"diagnosticFilters": [
"node_modules/**/*",
"**/roku_modules/**/*"
],
"allowBrighterScriptInBrightScript": true,
"autoImportComponentScript": true,
"stagingDir": "build/staging",
"retainStagingDir": true
}

49
bsconfig-tests.json Normal file
View File

@ -0,0 +1,49 @@
{
"files": [
{
"src": "test-app/**/*"
},
{
"src": "source/**/!(Main.brs)",
"dest": "source"
},
{
"src": "components/**/*",
"dest": "components"
},
{
"src": "locale/**/*",
"dest": "locale"
},
{
"src": "settings/**/*",
"dest": "settings"
}
],
"diagnosticFilters": [
"node_modules/**/*",
"**/roku_modules/**/*"
],
"autoImportComponentScript": true,
"allowBrighterScriptInBrightScript": true,
"createPackage": false,
"stagingFolderPath": "build",
"plugins": [
"rooibos-roku"
],
"rooibos": {
"isRecordingCodeCoverage": false,
"testsFilePattern": null,
"tags": [
"!integration",
"!deprecated",
"!fixme"
],
"showOnlyFailures": true,
"catchCrashes": true,
"lineWidth": 70,
"failFast": false,
"sendHomeOnFinish": false
},
"sourceMap": true
}

View File

@ -9,11 +9,19 @@
"settings/*.*"
],
"plugins": [
"@rokucommunity/bslint"
"@rokucommunity/bslint",
"roku-log-bsc-plugin"
],
"rokuLog": {
"insertPkgPath": true
},
"diagnosticFilters": [
"**/roku_modules/**/*",
"**/testFramework/*",
"**/tests/*"
]
"node_modules/**/*",
"**/roku_modules/**/*"
],
"sourceMap": true,
"autoImportComponentScript": true,
"allowBrighterScriptInBrightScript": true,
"stagingDir": "build/staging",
"retainStagingDir": true
}

View File

@ -1,6 +1,10 @@
{
"files": [
"source/**/*.brs",
"components/**/*.brs"
]
}
"files": [
"source/**/*.brs",
"source/**/*.bs",
"components/**/*.brs",
"components/**/*.bs",
"test-app/**/*.bs",
"!**/roku_modules/**/*.*"
]
}

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="ButtonGroupHoriz" extends="ButtonGroup">
<interface>
<field id="escape" type="string" alwaysNotify="true" />
</interface>
<script type="text/brightscript" uri="ButtonGroupHoriz.brs" />
</component>
</component>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="JFButtons" extends="Group">
<children>
<Group>
@ -8,13 +8,13 @@
<rectangle id="focus" />
<LayoutGroup id="buttonGroup" layoutDirection="horiz" itemSpacings = "[75]" translation="[50,20]">
<LayoutGroup id="buttonGroup" layoutDirection="horiz" itemSpacings="[75]" translation="[50,20]">
</LayoutGroup>
<Animation id="moveFocusAnimation" duration="0.25" repeat="false" easeFunction="outQuad">
<FloatFieldInterpolator id = "focusWidth" key="[0.0, 1.0]" keyValue="[ 0.00, 0.25 ]" fieldToInterp="focus.width" />
<FloatFieldInterpolator id = "focusHeight" key="[0.0, 1.0]" keyValue="[ 0.25, 0.00 ]" fieldToInterp="focus.height" />
<Vector2DFieldInterpolator id = "focusLocation" key="[0.0, 1.0]" keyValue="[]" fieldToInterp="focus.translation" />
<FloatFieldInterpolator id="focusWidth" key="[0.0, 1.0]" keyValue="[ 0.00, 0.25 ]" fieldToInterp="focus.width" />
<FloatFieldInterpolator id="focusHeight" key="[0.0, 1.0]" keyValue="[ 0.25, 0.00 ]" fieldToInterp="focus.height" />
<Vector2DFieldInterpolator id="focusLocation" key="[0.0, 1.0]" keyValue="[]" fieldToInterp="focus.translation" />
</Animation>
</Group>
@ -25,5 +25,4 @@
<field id="focusedIndex" type="integer" alwaysNotify="true" />
<field id="selectedIndex" type="integer" onChange="selectedIndexChanged" />
</interface>
<script type="text/brightscript" uri="JFButtons.brs" />
</component>
</component>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="TextSizeTask" extends="Task">
<interface>
<field id="fontName" type="string" />
<field id="fontSize" type="int" />
<field id="text" type="array" />
<field id="text" type="array" />
<field id="maxWidth" type="int" value="1920" />
<field id="name" type="string" />
@ -12,5 +12,4 @@
<field id="width" type="array" />
<field id="height" type="int" />
</interface>
<script type="text/brightscript" uri="TextSizeTask.brs" />
</component>

View File

@ -1,3 +1,6 @@
import "pkg:/source/utils/config.brs"
import "pkg:/source/roku_modules/api/api.brs"
sub init()
m.top.functionName = "getNextEpisodeTask"
end sub

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="GetNextEpisodeTask" extends="Task">
<interface>
@ -6,7 +6,4 @@
<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>

View File

@ -1,26 +1,32 @@
import "pkg:/source/utils/config.brs"
import "pkg:/source/utils/misc.brs"
import "pkg:/source/utils/deviceCapabilities.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/roku_modules/api/api.brs"
sub init()
m.top.functionName = "getPlaybackInfoTask"
end sub
function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioTrackIndex = -1 as integer, subtitleTrackIndex = -1 as integer, startTimeTicks = 0 as longinteger)
function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioTrackIndex = -1 as integer, startTimeTicks = 0 as longinteger)
currentView = m.global.sceneManager.callFunc("getActiveScene")
currentItem = m.global.queueManager.callFunc("getCurrentItem")
body = {
"DeviceProfile": getDeviceProfile()
}
params = {
"UserId": get_setting("active_user"),
"StartTimeTicks": startTimeTicks,
"StartTimeTicks": currentItem.startingPoint,
"IsPlayback": true,
"AutoOpenLiveStream": true,
"MaxStreamingBitrate": "140000000",
"MaxStaticBitrate": "140000000",
"SubtitleStreamIndex": subtitleTrackIndex
"SubtitleStreamIndex": currentView.selectedSubtitle,
"MediaSourceId": currentItem.id,
"AudioStreamIndex": currentItem.selectedAudioStreamIndex
}
mediaSourceId = id
if mediaSourceId <> "" then params.MediaSourceId = mediaSourceId
if audioTrackIndex > -1 then params.AudioStreamIndex = audioTrackIndex
req = APIRequest(Substitute("Items/{0}/PlaybackInfo", id), params)
req.SetRequest("POST")
return postJson(req, FormatJson(body))

View File

@ -1,14 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="GetPlaybackInfoTask" extends="Task">
<interface>
<field id="videoID" type="string" />
<field id="data" type="assocarray" />
</interface>
<script type="text/brightscript" uri="GetPlaybackInfoTask.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<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/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
</component>

View File

@ -1,3 +1,6 @@
import "pkg:/source/utils/config.brs"
import "pkg:/source/roku_modules/api/api.brs"
sub init()
m.top.functionName = "getShuffleEpisodesTask"
end sub

View File

@ -1,11 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="GetShuffleEpisodesTask" extends="Task">
<interface>
<field id="showID" type="string" />
<field id="data" type="assocarray" />
</interface>
<script type="text/brightscript" uri="GetShuffleEpisodesTask.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
</component>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="IconButton" extends="Group">
<children>
<Poster id="buttonBackground" uri="pkg:/images/white.9.png" />
@ -17,5 +17,4 @@
<field id="focus" type="boolean" />
<field id="escape" type="string" value="" />
</interface>
<script type="text/brightscript" uri="IconButton.brs" />
</component>
</component>

View File

@ -24,5 +24,20 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
return true
end if
if key = "up"
if m.Alphamenu.itemFocused = 0
m.Alphamenu.jumpToItem = m.Alphamenu.numRows - 1
return true
end if
end if
if key = "down"
if m.Alphamenu.itemFocused = m.Alphamenu.numRows - 1
m.Alphamenu.jumpToItem = 0
return true
end if
end if
return false
end function

View File

@ -1,55 +1,54 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="Alpha" extends = "Group">
<?xml version="1.0" encoding="utf-8"?>
<component name="Alpha" extends="Group">
<children>
<LabelList
translation="[50, 185]"
vertFocusAnimationStyle="floatingFocus"
drawFocusFeedback="true"
id = "Alphamenu"
font="font:SmallestSystemFont"
itemSpacing="[0,2]"
itemSize="[28,28]"
numRows="27"
textHorizAlign="center"
focusBitmapUri = "pkg:/images/white.png"
focusBitmapBlendColor = "#FFFFFF"
focusFootprintBlendColor = "#666666"
<LabelList
translation="[50, 185]"
vertFocusAnimationStyle="floatingFocus"
drawFocusFeedback="true"
id="Alphamenu"
font="font:SmallestSystemFont"
itemSpacing="[0,2]"
itemSize="[28,28]"
numRows="27"
textHorizAlign="center"
focusBitmapUri="pkg:/images/white.png"
focusBitmapBlendColor="#FFFFFF"
focusFootprintBlendColor="#666666"
>
<ContentNode id="alphatext" role = "content" >
<ContentNode title = "#" />
<ContentNode title = "A" />
<ContentNode title = "B" />
<ContentNode title = "C" />
<ContentNode title = "D" />
<ContentNode title = "E" />
<ContentNode title = "F" />
<ContentNode title = "G" />
<ContentNode title = "H" />
<ContentNode title = "I" />
<ContentNode title = "J" />
<ContentNode title = "K" />
<ContentNode title = "L" />
<ContentNode title = "M" />
<ContentNode title = "N" />
<ContentNode title = "O" />
<ContentNode title = "P" />
<ContentNode title = "Q" />
<ContentNode title = "R" />
<ContentNode title = "S" />
<ContentNode title = "T" />
<ContentNode title = "U" />
<ContentNode title = "V" />
<ContentNode title = "W" />
<ContentNode title = "X" />
<ContentNode title = "Y" />
<ContentNode title = "Z" />
</ContentNode>
</LabelList>
<ContentNode id="alphatext" role="content">
<ContentNode title="#" />
<ContentNode title="A" />
<ContentNode title="B" />
<ContentNode title="C" />
<ContentNode title="D" />
<ContentNode title="E" />
<ContentNode title="F" />
<ContentNode title="G" />
<ContentNode title="H" />
<ContentNode title="I" />
<ContentNode title="J" />
<ContentNode title="K" />
<ContentNode title="L" />
<ContentNode title="M" />
<ContentNode title="N" />
<ContentNode title="O" />
<ContentNode title="P" />
<ContentNode title="Q" />
<ContentNode title="R" />
<ContentNode title="S" />
<ContentNode title="T" />
<ContentNode title="U" />
<ContentNode title="V" />
<ContentNode title="W" />
<ContentNode title="X" />
<ContentNode title="Y" />
<ContentNode title="Z" />
</ContentNode>
</LabelList>
</children>
<interface>
<field id="selectedItem" type="node" alwaysNotify="true" />
<field id="focusedChild" type="node" onChange="focusChanged" />
<field id="itemAlphaSelected" type="string" />
</interface>
<script type="text/brightscript" uri="Alpha.brs" />
</component>

View File

@ -1,3 +1,7 @@
import "pkg:/source/api/UserLibrary.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/config.brs"
sub init()
m.top.functionName = "setFavoriteStatus"
end sub

View File

@ -1,12 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="FavoriteItemsTask" extends="Task">
<interface>
<field id="itemId" type="string" />
<field id="favTask" type="string" value="" />
</interface>
<script type="text/brightscript" uri="FavoriteItemsTask.brs" />
<script type="text/brightscript" uri="pkg:/source/api/UserLibrary.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>

View File

@ -1,4 +1,9 @@
import "pkg:/source/utils/config.brs"
import "pkg:/source/utils/misc.brs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
sub init()
m.log = log.Logger("GridItem")
m.posterMask = m.top.findNode("posterMask")
m.itemPoster = m.top.findNode("itemPoster")
m.itemIcon = m.top.findNode("itemIcon")
@ -117,7 +122,7 @@ sub itemContentChanged()
m.posterText.height = 200
m.posterText.width = 280
else
print "Unhandled Grid Item Type: " + itemData.type
m.log.warn("Unhandled Grid Item Type", itemData.type)
end if
'If Poster not loaded, ensure "blue box" is shown until loaded

View File

@ -18,7 +18,4 @@
<field id="itemHasFocus" type="boolean" onChange="focusChanged" />
<field id="focusPercent" type="float" onChange="focusChanging" />
</interface>
<script type="text/brightscript" uri="GridItem.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
</component>

View File

@ -1,3 +1,6 @@
import "pkg:/source/utils/misc.brs"
import "pkg:/source/utils/config.brs"
sub init()
m.itemPoster = m.top.findNode("itemPoster")
m.posterText = m.top.findNode("posterText")

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?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" />
@ -11,7 +11,4 @@
<field id="itemContent" type="node" onChange="itemContentChanged" />
<field id="itemHasFocus" type="boolean" onChange="focusChanged" alwaysNotify="true" />
</interface>
<script type="text/brightscript" uri="GridItemSmall.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>
</component>

View File

@ -1,5 +1,11 @@
sub init()
import "pkg:/source/utils/misc.brs"
import "pkg:/source/utils/config.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/deviceCapabilities.brs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
sub init()
m.log = log.Logger("ItemGrid")
m.options = m.top.findNode("options")
m.showItemCount = get_user_setting("itemgrid.showItemCount") = "true"
@ -67,13 +73,10 @@ sub init()
'Get reset folder setting
m.resetGrid = get_user_setting("itemgrid.reset") = "true"
'Check if device has voice remote
devinfo = CreateObject("roDeviceInfo")
m.deviFeature = devinfo.HasFeature("voice_remote")
m.micButton = m.top.findNode("micButton")
m.micButtonText = m.top.findNode("micButtonText")
'Hide voice search if device does not have voice remote
if m.deviFeature = false
if m.global.device.hasVoiceRemote = false
m.micButton.visible = false
m.micButtonText.visible = false
end if
@ -104,7 +107,7 @@ sub loadInitialItems()
' Translate between app and server nomenclature
viewSetting = get_user_setting("display.livetv.landing")
'Move mic to be visiable on TV Guide screen
if m.deviFeature = true
if m.global.device.hasVoiceRemote = true
m.micButton.translation = "[1540, 92]"
m.micButtonText.visible = true
m.micButtonText.translation = "[1600,130]"
@ -209,7 +212,7 @@ sub loadInitialItems()
m.loadItemsTask.itemType = "Series,Movie"
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
else
print "[ItemGrid] Unknown Type: " m.top.parentItem
m.log.warn("Unknown Item Type", m.top.parentItem)
end if
if m.top.parentItem.type <> "Folder" and (m.options.view = "Networks" or m.view = "Networks" or m.options.view = "Studios" or m.view = "Studios")

View File

@ -1,36 +1,36 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="ItemGrid" extends="JFGroup">
<children>
<VoiceTextEditBox id="VoiceBox" visible="true" width = "40" translation = "[52, 120]" />
<Rectangle id="VoiceBoxCover" height="240" width="100" color="0x262626ff" translation = "[25, 75]" />
<VoiceTextEditBox id="VoiceBox" visible="true" width="40" translation="[52, 120]" />
<Rectangle id="VoiceBoxCover" height="240" width="100" color="0x262626ff" translation="[25, 75]" />
<poster id="backdrop" loadDisplayMode="scaleToFill" width="1920" height="1080" opacity="0.25" />
<poster id="backdropTransition" loadDisplayMode="scaleToFill" width="1920" height="1080" opacity="0.25" />
<MarkupGrid
id = "itemGrid"
translation = "[ 96, 160 ]"
itemComponentName = "GridItem"
numColumns = "6"
numRows = "5"
vertFocusAnimationStyle = "fixed"
itemSize = "[ 290, 425 ]"
itemSpacing = "[ 0, 45 ]"
drawFocusFeedback = "false" />
<MarkupGrid
id="itemGrid"
translation="[ 96, 160 ]"
itemComponentName="GridItem"
numColumns="6"
numRows="5"
vertFocusAnimationStyle="fixed"
itemSize="[ 290, 425 ]"
itemSpacing="[ 0, 45 ]"
drawFocusFeedback="false" />
<RowList opacity="0" id="genrelist" translation="[120, 160]" showRowLabel="true" itemComponentName="GridItemSmall" numColumns="1" numRows="3" vertFocusAnimationStyle="fixed" itemSize = "[1900, 360]" rowItemSize="[ [230, 320] ]" rowItemSpacing="[ [20, 0] ]" itemSpacing="[0, 60]" />
<RowList opacity="0" id="genrelist" translation="[120, 160]" showRowLabel="true" itemComponentName="GridItemSmall" numColumns="1" numRows="3" vertFocusAnimationStyle="fixed" itemSize="[1900, 360]" rowItemSize="[ [230, 320] ]" rowItemSpacing="[ [20, 0] ]" itemSpacing="[0, 60]" />
<Label id="micButtonText" font="font:SmallSystemFont" visible="false" />
<Button id = "micButton" maxWidth = "20" translation = "[20, 120]" iconUri = "pkg:/images/icons/mic_icon.png"/>
<Button id="micButton" maxWidth="20" translation="[20, 120]" iconUri="pkg:/images/icons/mic_icon.png" />
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1910" horizAlign="center" vertAlign="center" height="64" visible="false" />
<ItemGridOptions id="options" visible="false" />
<Spinner id="spinner" translation="[900, 450]" />
<Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear">
<FloatFieldInterpolator id = "fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 0.25 ]" fieldToInterp="backdropTransition.opacity" />
<FloatFieldInterpolator id = "fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 0.25, 0.00 ]" fieldToInterp="backdrop.opacity" />
<FloatFieldInterpolator id="fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 0.25 ]" fieldToInterp="backdropTransition.opacity" />
<FloatFieldInterpolator id="fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 0.25, 0.00 ]" fieldToInterp="backdrop.opacity" />
</Animation>
<Alpha id="AlphaMenu" />
</children>
<interface>
<field id="HomeLibraryItem" type="string"/>
<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" />
@ -39,9 +39,4 @@
<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/utils/deviceCapabilities.brs" />
<script type="text/brightscript" uri="ItemGrid.brs" />
</component>
</component>

View File

@ -1,5 +1,8 @@
sub init()
import "pkg:/source/utils/misc.brs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
sub init()
m.log = log.Logger("ItemGridOptions")
m.buttons = m.top.findNode("buttons")
m.buttons.buttons = [tr("View"), tr("Sort"), tr("Filter")]
m.buttons.selectedIndex = 1
@ -203,7 +206,7 @@ sub setHeartColor(color as string)
end if
end for
catch e
print e.number, e.message
m.log.error("setHeartColor()", e.number, e.message)
end try
end sub

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="ItemGridOptions" extends="Group">
<children>
<Rectangle width="1920" height="1080" color="#000000" opacity="0.75" />
@ -9,11 +9,11 @@
</LayoutGroup>
<LayoutGroup id="menuOptions" horizAlignment="center" translation="[860,200]" itemSpacings="[50]">
<Group>
<RadiobuttonList id="viewMenu" itemspacing="[0,10]" vertFocusAnimationStyle="floatingFocus" opacity="0" drawFocusFeedback="false">
<RadiobuttonList id="viewMenu" itemSize="[600, 75]" itemspacing="[0,10]" vertFocusAnimationStyle="floatingFocus" opacity="0" drawFocusFeedback="false">
</RadiobuttonList>
<RadiobuttonList id="sortMenu" itemspacing="[0,10]" vertFocusAnimationStyle="floatingFocus" opacity="1" numRows="8" drawFocusFeedback="false">
<RadiobuttonList id="sortMenu" itemSize="[600, 75]" itemspacing="[0,10]" vertFocusAnimationStyle="floatingFocus" opacity="1" numRows="8" drawFocusFeedback="false">
</RadiobuttonList>
<RadiobuttonList id="filterMenu" checkOnSelect="false" itemspacing="[0,10]" vertFocusAnimationStyle="floatingFocus" opacity="0" drawFocusFeedback="false">
<RadiobuttonList id="filterMenu" itemSize="[600, 75]" checkOnSelect="false" itemspacing="[0,10]" vertFocusAnimationStyle="floatingFocus" opacity="0" drawFocusFeedback="false">
</RadiobuttonList>
</Group>
</LayoutGroup>
@ -52,6 +52,4 @@
<field id="favorite" type="string" value="Favorite" />
</interface>
<script type="text/brightscript" uri="ItemGridOptions.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
</component>
</component>

View File

@ -1,4 +1,14 @@
import "pkg:/source/api/Items.brs"
import "pkg:/source/roku_modules/api/api.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/config.brs"
import "pkg:/source/utils/misc.brs"
import "pkg:/source/api/Image.brs"
import "pkg:/source/utils/deviceCapabilities.brs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
sub init()
m.log = log.Logger("LoadItemsTask2")
m.top.functionName = "loadItems"
m.top.limit = 60
@ -109,6 +119,13 @@ sub loadItems()
Fields: "Genres"
})
params.IncludeItemTypes = "MusicAlbum,Audio"
else if m.top.ItemType = "AlbumArtists"
url = "Artists/AlbumArtists"
params.append({
UserId: get_setting("active_user"),
Fields: "Genres"
})
params.IncludeItemTypes = "MusicAlbum,Audio"
else if m.top.ItemType = "MusicAlbum"
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
params.append({ ImageTypeLimit: 1 })
@ -228,7 +245,7 @@ sub loadItems()
tmp.posterUrl = api_API().items.getimageurl(item.id, "primary", 0, { "maxHeight": 280, "maxWidth": 280, "quality": "90" })
else
print "[LoadItems] Unknown Type: " item.Type
m.log.warn("Unknown Type", item.Type)
end if
if tmp <> invalid

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="LoadItemsTask2" extends="Task">
<interface>
@ -21,12 +21,4 @@
<field id="totalRecordCount" type="int" value="-1" />
<field id="content" type="array" />
</interface>
<script type="text/brightscript" uri="LoadItemsTask2.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
</component>

View File

@ -1,29 +1,55 @@
import "pkg:/source/utils/misc.brs"
import "pkg:/source/api/Items.brs"
import "pkg:/source/api/UserLibrary.brs"
import "pkg:/source/roku_modules/api/api.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/config.brs"
import "pkg:/source/api/Image.brs"
import "pkg:/source/api/userauth.brs"
import "pkg:/source/utils/deviceCapabilities.brs"
sub init()
m.top.functionName = "loadItems"
m.top.limit = 60
usersettingLimit = get_user_setting("itemgrid.Limit")
if isValid(usersettingLimit)
m.top.limit = usersettingLimit
end if
end sub
sub loadItems()
m.top.content = [LoadItems_VideoPlayer(m.top.itemId)]
' Reset intro tracker in case task gets reused
m.top.isIntro = false
' Only show preroll once per queue
if m.global.queueManager.callFunc("isPrerollActive")
' Prerolls not allowed if we're resuming video
if m.global.queueManager.callFunc("getCurrentItem").startingPoint = 0
preRoll = GetIntroVideos(m.top.itemId)
if isValid(preRoll) and preRoll.TotalRecordCount > 0 and isValid(preRoll.items[0])
' If an error is thrown in the Intros plugin, instead of passing the error they pass the entire rick roll music video.
' Bypass the music video and treat it as an error message
if lcase(preRoll.items[0].name) <> "rick roll'd"
m.global.queueManager.callFunc("push", m.global.queueManager.callFunc("getCurrentItem"))
m.top.itemId = preRoll.items[0].id
m.global.queueManager.callFunc("setPrerollStatus", false)
m.top.isIntro = true
end if
end if
end if
end if
id = m.top.itemId
mediaSourceId = invalid
audio_stream_idx = m.top.selectedAudioStreamIndex
subtitle_idx = m.top.selectedSubtitleIndex
forceTranscoding = false
m.top.content = [LoadItems_VideoPlayer(id, mediaSourceId, audio_stream_idx, subtitle_idx, forceTranscoding)]
end sub
function LoadItems_VideoPlayer(id, mediaSourceId = invalid, audio_stream_idx = 1, subtitle_idx = -1, forceTranscoding = false, showIntro = true, allowResumeDialog = true)
function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean) as dynamic
video = {}
video.id = id
video.content = createObject("RoSGNode", "ContentNode")
LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, subtitle_idx, -1, forceTranscoding, showIntro, allowResumeDialog)
if video.errorMsg = "introaborted"
return video
end if
LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, subtitle_idx, forceTranscoding)
if video.content = invalid
return invalid
@ -32,11 +58,12 @@ function LoadItems_VideoPlayer(id, mediaSourceId = invalid, audio_stream_idx = 1
return video
end function
sub LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -1, playbackPosition = -1, forceTranscoding = false, showIntro = true, allowResumeDialog = true)
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean)
meta = ItemMetaData(video.id)
if not isValid(meta)
video.errorMsg = "Error loading metadata"
video.content = invalid
return
end if
@ -50,42 +77,24 @@ sub LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtit
video.content.title = meta.title
video.showID = meta.showID
if playbackPosition = -1
playbackPosition = meta.json.UserData.PlaybackPositionTicks
if allowResumeDialog
if playbackPosition > 0
dialogResult = startPlayBackOver(playbackPosition)
'Dialog returns -1 when back pressed, 0 for resume, and 1 for start over
if dialogResult.indexselected = -1
'User pressed back, return invalid and don't load video
video.content = invalid
return
else if dialogResult.indexselected = 1
'Start Over selected, change position to 0
playbackPosition = 0
end if
end if
user = AboutMe()
if user.Configuration.EnableNextEpisodeAutoPlay
if LCase(m.top.itemType) = "episode"
addNextEpisodesToQueue(video.showID)
end if
end if
' For phase 1 of playlist support, we don't support intros yet
showIntro = false
playbackPosition = 0!
' Don't attempt to play an intro for an intro video
if showIntro
' Do not play intros when resuming playback
if playbackPosition = 0
if not PlayIntroVideo(video.id, audio_stream_idx)
video.errorMsg = "introaborted"
return
end if
end if
currentItem = m.global.queueManager.callFunc("getCurrentItem")
if isValid(currentItem) and isValid(currentItem.startingPoint)
playbackPosition = currentItem.startingPoint
end if
' PlayStart requires the time to be in seconds
video.content.PlayStart = int(playbackPosition / 10000000)
if not isValid(mediaSourceId) then mediaSourceId = video.id
if meta.live then mediaSourceId = ""
@ -95,6 +104,7 @@ sub LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtit
video.audioIndex = audio_stream_idx
if not isValid(m.playbackInfo)
video.errorMsg = "Error loading playback info"
video.content = invalid
return
end if
@ -153,6 +163,7 @@ sub LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtit
if m.playbackInfo.MediaSources[0].TranscodingUrl = invalid
' If server does not provide a transcode URL, display a message to the user
m.global.sceneManager.callFunc("userMessage", tr("Error Getting Playback Information"), tr("An error was encountered while playing this item. Server did not provide required transcoding data."))
video.errorMsg = "Error getting playback information"
video.content = invalid
return
end if
@ -165,9 +176,7 @@ sub LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtit
video.content.setCertificatesFile("common:/certs/ca-bundle.crt")
video.audioTrack = (audio_stream_idx + 1).ToStr() ' Roku's track indexes count from 1. Our index is zero based
' Perform relevant setup work for selected subtitle, and return the index of the subtitle
' is enabled/will be enabled, indexed on the provided list of subtitles
video.SelectedSubtitle = setupSubtitle(video, video.Subtitles, subtitle_idx)
video.SelectedSubtitle = subtitle_idx
if not fully_external
video.content = authorize_request(video.content)
@ -212,18 +221,19 @@ end sub
sub addSubtitlesToVideo(video, meta)
subtitles = sortSubtitles(meta.id, m.playbackInfo.MediaSources[0].MediaStreams)
safesubs = subtitles["all"]
subtitleTracks = []
if get_user_setting("playback.subs.onlytext") = "true"
safesubs = []
for each subtitle in subtitles["all"]
if subtitle["IsTextSubtitleStream"]
safesubs.push(subtitle)
end if
end for
video.Subtitles = safesubs
else
video.Subtitles = subtitles["all"]
safesubs = subtitles["text"]
end if
video.content.SubtitleTracks = subtitles["text"]
for each subtitle in safesubs
subtitleTracks.push(subtitle.track)
end for
video.content.SubtitleTracks = subtitleTracks
video.fullSubtitleData = safesubs
end sub
@ -242,26 +252,6 @@ function getTranscodeReasons(url as string) as object
return []
end function
'Opens dialog asking user if they want to resume video or start playback over only on the home screen
function startPlayBackOver(time as longinteger)
' If we're inside a play queue, start the episode from the beginning
if m.global.queueManager.callFunc("getCount") > 1 then return { indexselected: 1 }
resumeData = [
"Resume playing at " + ticksToHuman(time) + ".",
"Start over from the beginning."
]
m.global.sceneManager.callFunc("optionDialog", tr("Playback Options"), ["Choose an option"], resumeData)
while not isValid(m.global.sceneManager.returnData)
end while
return m.global.sceneManager.returnData
end function
function directPlaySupported(meta as object) as boolean
devinfo = CreateObject("roDeviceInfo")
if isValid(meta.json.MediaSources[0]) and meta.json.MediaSources[0].SupportsDirectPlay = false
@ -304,306 +294,52 @@ function getContainerType(meta as object) as string
return container
end function
function getAudioFormat(meta as object) as string
' Determine the codec of the audio file source
if meta.json.mediaSources = invalid then return ""
' Add next episodes to the playback queue
sub addNextEpisodesToQueue(showID)
' Don't queue next episodes if we already have a playback queue
maxQueueCount = 1
audioInfo = getAudioInfo(meta)
if audioInfo.count() = 0 or audioInfo[0].codec = invalid then return ""
return audioInfo[0].codec
end function
if m.top.isIntro
maxQueueCount = 2
end if
function getAudioInfo(meta as object) as object
' Return audio metadata for a given stream
results = []
for each source in meta.json.mediaSources[0].mediaStreams
if source["type"] = "Audio"
results.push(source)
end if
end for
return results
end function
if m.global.queueManager.callFunc("getCount") > maxQueueCount then return
sub autoPlayNextEpisode(videoID as string, showID as string)
' use web client setting
if m.user.Configuration.EnableNextEpisodeAutoPlay
' query API for next episode ID
url = Substitute("Shows/{0}/Episodes", showID)
urlParams = { "UserId": get_setting("active_user") }
urlParams.Append({ "StartItemId": videoID })
urlParams.Append({ "Limit": 2 })
resp = APIRequest(url, urlParams)
data = getJson(resp)
videoID = m.top.itemId
if isValid(data) and data.Items.Count() = 2
' setup new video node
nextVideo = invalid
' remove last videoplayer scene
m.global.sceneManager.callFunc("clearPreviousScene")
if isValid(nextVideo)
m.global.sceneManager.callFunc("pushScene", nextVideo)
else
m.global.sceneManager.callFunc("popScene")
' If first item is an intro video, use the next item in the queue
if m.top.isIntro
currentVideo = m.global.queueManager.callFunc("getItemByIndex", 1)
if isValid(currentVideo) and isValid(currentVideo.id)
videoID = currentVideo.id
' Override showID value since it's for the intro video
meta = ItemMetaData(videoID)
if isValid(meta)
showID = meta.showID
end if
else
' can't play next episode
m.global.sceneManager.callFunc("popScene")
end if
else
m.global.sceneManager.callFunc("popScene")
end if
url = Substitute("Shows/{0}/Episodes", showID)
urlParams = { "UserId": get_setting("active_user") }
urlParams.Append({ "StartItemId": videoID })
urlParams.Append({ "Limit": 50 })
resp = APIRequest(url, urlParams)
data = getJson(resp)
if isValid(data) and data.Items.Count() > 1
for i = 1 to data.Items.Count() - 1
m.global.queueManager.callFunc("push", data.Items[i])
end for
end if
end sub
' Returns an array of playback info to be displayed during playback.
' In the future, with a custom playback info view, we can return an associated array.
function GetPlaybackInfo()
sessions = api_API().sessions.get()
if isValid(sessions) and sessions.Count() > 0
return GetTranscodingStats(sessions[0])
end if
errMsg = tr("Unable to get playback information")
return [errMsg]
end function
function GetTranscodingStats(session)
sessionStats = []
if isValid(session.TranscodingInfo) and session.TranscodingInfo.Count() > 0
transcodingReasons = session.TranscodingInfo.TranscodeReasons
videoCodec = session.TranscodingInfo.VideoCodec
audioCodec = session.TranscodingInfo.AudioCodec
totalBitrate = session.TranscodingInfo.Bitrate
audioChannels = session.TranscodingInfo.AudioChannels
if isValid(transcodingReasons) and transcodingReasons.Count() > 0
sessionStats.push("** " + tr("Transcoding Information") + " **")
for each item in transcodingReasons
sessionStats.push(tr("Reason") + ": " + item)
end for
end if
if isValid(videoCodec)
data = tr("Video Codec") + ": " + videoCodec
if session.TranscodingInfo.IsVideoDirect
data = data + " (" + tr("direct") + ")"
end if
sessionStats.push(data)
end if
if isValid(audioCodec)
data = tr("Audio Codec") + ": " + audioCodec
if session.TranscodingInfo.IsAudioDirect
data = data + " (" + tr("direct") + ")"
end if
sessionStats.push(data)
end if
if isValid(totalBitrate)
data = tr("Total Bitrate") + ": " + getDisplayBitrate(totalBitrate)
sessionStats.push(data)
end if
if isValid(audioChannels)
data = tr("Audio Channels") + ": " + Str(audioChannels)
sessionStats.push(data)
end if
end if
if havePlaybackInfo()
stream = m.playbackInfo.mediaSources[0].MediaStreams[0]
sessionStats.push("** " + tr("Stream Information") + " **")
if isValid(stream.Container)
data = tr("Container") + ": " + stream.Container
sessionStats.push(data)
end if
if isValid(stream.Size)
data = tr("Size") + ": " + stream.Size
sessionStats.push(data)
end if
if isValid(stream.BitRate)
data = tr("Bit Rate") + ": " + getDisplayBitrate(stream.BitRate)
sessionStats.push(data)
end if
if isValid(stream.Codec)
data = tr("Codec") + ": " + stream.Codec
sessionStats.push(data)
end if
if isValid(stream.CodecTag)
data = tr("Codec Tag") + ": " + stream.CodecTag
sessionStats.push(data)
end if
if isValid(stream.VideoRangeType)
data = tr("Video range type") + ": " + stream.VideoRangeType
sessionStats.push(data)
end if
if isValid(stream.PixelFormat)
data = tr("Pixel format") + ": " + stream.PixelFormat
sessionStats.push(data)
end if
if isValid(stream.Width) and isValid(stream.Height)
data = tr("WxH") + ": " + Str(stream.Width) + " x " + Str(stream.Height)
sessionStats.push(data)
end if
if isValid(stream.Level)
data = tr("Level") + ": " + Str(stream.Level)
sessionStats.push(data)
end if
end if
return sessionStats
end function
function havePlaybackInfo()
if not isValid(m.playbackInfo)
return false
end if
if not isValid(m.playbackInfo.mediaSources)
return false
end if
if m.playbackInfo.mediaSources.Count() <= 0
return false
end if
if not isValid(m.playbackInfo.mediaSources[0].MediaStreams)
return false
end if
if m.playbackInfo.mediaSources[0].MediaStreams.Count() <= 0
return false
end if
return true
end function
function getDisplayBitrate(bitrate)
if bitrate > 1000000
return Str(Fix(bitrate / 1000000)) + " Mbps"
else
return Str(Fix(bitrate / 1000)) + " Kbps"
end if
end function
' Roku translates the info provided in subtitleTracks into availableSubtitleTracks
' Including ignoring tracks, if they are not understood, thus making indexing unpredictable.
' This function translates between our internel selected subtitle index
' and the corresponding index in availableSubtitleTracks.
function availSubtitleTrackIdx(video, sub_idx) as integer
url = video.Subtitles[sub_idx].Track.TrackName
idx = 0
for each availTrack in video.availableSubtitleTracks
' The TrackName must contain the URL we supplied originally, though
' Roku mangles the name a bit, so we check if the URL is a substring, rather
' than strict equality
if Instr(1, availTrack.TrackName, url)
return idx
end if
idx = idx + 1
end for
return -1
end function
' Identify the default subtitle track for a given video id
' returns the server-side track index for the appriate subtitle
function defaultSubtitleTrackFromVid(video_id) as integer
meta = ItemMetaData(video_id)
if isValid(meta) and isValid(meta.json) and isValid(meta.json.mediaSources)
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
default_text_subs = defaultSubtitleTrack(subtitles["all"], true) ' Find correct subtitle track (forced text)
if default_text_subs <> -1
return default_text_subs
else
if get_user_setting("playback.subs.onlytext") = "false"
return defaultSubtitleTrack(subtitles["all"]) ' if no appropriate text subs exist, allow non-text
else
return -1
end if
end if
end if
' No valid mediaSources (i.e. LiveTV)
return -1
end function
' Identify the default subtitle track
' if "requires_text" is true, only return a track if it is textual
' This allows forcing text subs, since roku requires transcoding of non-text subs
' returns the server-side track index for the appriate subtitle
function defaultSubtitleTrack(sorted_subtitles, require_text = false) as integer
if m.user.Configuration.SubtitleMode = "None"
return -1 ' No subtitles desired: select none
end if
for each item in sorted_subtitles
' Only auto-select subtitle if language matches preference
languageMatch = (m.user.Configuration.SubtitleLanguagePreference = item.Track.Language)
' Ensure textuality of subtitle matches preferenced passed as arg
matchTextReq = ((require_text and item.IsTextSubtitleStream) or not require_text)
if languageMatch and matchTextReq
if m.user.Configuration.SubtitleMode = "Default" and (item.isForced or item.IsDefault or item.IsExternal)
return item.Index ' Finds first forced, or default, or external subs in sorted list
else if m.user.Configuration.SubtitleMode = "Always" and not item.IsForced
return item.Index ' Select the first non-forced subtitle option in the sorted list
else if m.user.Configuration.SubtitleMode = "OnlyForced" and item.IsForced
return item.Index ' Select the first forced subtitle option in the sorted list
else if m.user.Configuration.SubtitlePlaybackMode = "Smart" and (item.isForced or item.IsDefault or item.IsExternal)
' Simplified "Smart" logic here mimics Default (as that is fallback behavior normally)
' Avoids detecting preferred audio language (as is utilized in main client)
return item.Index
end if
end if
end for
return -1 ' Keep current default behavior of "None", if no correct subtitle is identified
end function
' Given a set of subtitles, and a subtitle index (the index on the server, not in the list provided)
' this will set all relevant settings for roku (mainly closed captions) and return the index of the
' subtitle track specified, but indexed based on the provided list of subtitles
function setupSubtitle(video, subtitles, subtitle_idx = -1) as integer
if subtitle_idx = -1
' If we are not using text-based subtitles, turn them off
video.globalCaptionMode = "Off"
return -1
end if
' Translate the raw index to one relative to the provided list
subtitleSelIdx = getSubtitleSelIdxFromSubIdx(subtitles, subtitle_idx)
selectedSubtitle = subtitles[subtitleSelIdx]
if selectedSubtitle.IsEncoded
' With encoded subtitles, turn off captions
video.globalCaptionMode = "Off"
else
' If this is a text-based subtitle, set relevant settings for roku captions
video.globalCaptionMode = "On"
video.subtitleTrack = video.availableSubtitleTracks[availSubtitleTrackIdx(video, subtitleSelIdx)].TrackName
end if
return subtitleSelIdx
end function
' The subtitle index on the server differs from the index we track locally
' This function converts the former into the latter
function getSubtitleSelIdxFromSubIdx(subtitles, sub_idx) as integer
selIdx = 0
if sub_idx = -1 then return -1
for each item in subtitles
if item.Index = sub_idx
return selIdx
end if
selIdx = selIdx + 1
end for
return -1
end function
'Checks available subtitle tracks and puts subtitles in forced, default, and non-default/forced but preferred language at the top
function sortSubtitles(id as string, MediaStreams)
m.user = AboutMe()
tracks = { "forced": [], "default": [], "normal": [] }
tracks = { "forced": [], "default": [], "normal": [], "text": [] }
'Too many args for using substitute
prefered_lang = m.user.Configuration.SubtitleLanguagePreference
for each stream in MediaStreams
@ -627,6 +363,8 @@ function sortSubtitles(id as string, MediaStreams)
trackType = "forced"
else if stream.IsDefault
trackType = "default"
else if stream.IsTextSubtitleStream
trackType = "text"
else
trackType = "normal"
end if
@ -640,14 +378,9 @@ function sortSubtitles(id as string, MediaStreams)
tracks["default"].append(tracks["normal"])
tracks["forced"].append(tracks["default"])
tracks["forced"].append(tracks["text"])
textTracks = []
for i = 0 to tracks["forced"].count() - 1
if tracks["forced"][i].IsTextSubtitleStream
textTracks.push(tracks["forced"][i].Track)
end if
end for
return { "all": tracks["forced"], "text": textTracks }
return { "all": tracks["forced"], "text": tracks["text"] }
end function
function getSubtitleLanguages()
@ -1143,122 +876,3 @@ function getSubtitleLanguages()
"zza": "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki"
}
end function
function CreateSeasonDetailsGroup(series, season)
group = CreateObject("roSGNode", "TVEpisodes")
group.optionsAvailable = false
m.global.sceneManager.callFunc("pushScene", group)
group.seasonData = ItemMetaData(season.id).json
group.objects = TVEpisodes(series.id, season.id)
group.observeField("episodeSelected", m.port)
group.observeField("quickPlayNode", m.port)
return group
end function
function PlayIntroVideo(video_id, audio_stream_idx) as boolean
' Intro videos only play if user has cinema mode setting enabled
if get_user_setting("playback.cinemamode") = "true"
' Check if server has intro videos setup and available
introVideos = GetIntroVideos(video_id)
if introVideos = invalid then return true
if introVideos.TotalRecordCount > 0
' Bypass joke pre-roll
if lcase(introVideos.items[0].name) = "rick roll'd" then return true
introVideo = LoadItems_VideoPlayer(introVideos.items[0].id, introVideos.items[0].id, audio_stream_idx, defaultSubtitleTrackFromVid(video_id), false, false)
port = CreateObject("roMessagePort")
introVideo.observeField("state", port)
m.global.sceneManager.callFunc("pushScene", introVideo)
introPlaying = true
while introPlaying
msg = wait(0, port)
if type(msg) = "roSGNodeEvent"
if msg.GetData() = "finished"
m.global.sceneManager.callFunc("clearPreviousScene")
introPlaying = false
else if msg.GetData() = "stopped"
introPlaying = false
return false
end if
end if
end while
end if
end if
return true
end function
function CreateMovieDetailsGroup(movie)
group = CreateObject("roSGNode", "MovieDetails")
group.overhangTitle = movie.title
group.optionsAvailable = false
m.global.sceneManager.callFunc("pushScene", group)
movieMetaData = ItemMetaData(movie.id)
group.itemContent = movieMetaData
group.trailerAvailable = false
activeUser = get_setting("active_user")
trailerData = invalid
if isValid(activeUser) and isValid(movie.id)
trailerData = api_API().users.getlocaltrailers(activeUser, movie.id)
end if
if isValid(trailerData)
group.trailerAvailable = trailerData.Count() > 0
end if
buttons = group.findNode("buttons")
for each b in buttons.getChildren(-1, 0)
b.observeField("buttonSelected", m.port)
end for
extras = group.findNode("extrasGrid")
extras.observeField("selectedItem", m.port)
extras.callFunc("loadParts", movieMetaData.json)
return group
end function
function CreateSeriesDetailsGroup(series)
' Get season data early in the function so we can check number of seasons.
seasonData = TVSeasons(series.id)
' Divert to season details if user setting goStraightToEpisodeListing is enabled and only one season exists.
if get_user_setting("ui.tvshows.goStraightToEpisodeListing") = "true" and seasonData.Items.Count() = 1
return CreateSeasonDetailsGroupByID(series.id, seasonData.Items[0].id)
end if
group = CreateObject("roSGNode", "TVShowDetails")
group.optionsAvailable = false
m.global.sceneManager.callFunc("pushScene", group)
group.itemContent = ItemMetaData(series.id)
group.seasonData = seasonData ' Re-use variable from beginning of function
group.observeField("seasonSelected", m.port)
extras = group.findNode("extrasGrid")
extras.observeField("selectedItem", m.port)
extras.callFunc("loadParts", group.itemcontent.json)
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

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="LoadVideoContentTask" extends="Task">
<interface>
<field id="itemId" type="string" />
<field id="selectedAudioStreamIndex" type="integer" value="0" />
<field id="selectedSubtitleIndex" type="integer" value="-1" />
<field id="isIntro" type="boolean" />
<field id="startIndex" type="integer" value="0" />
<field id="itemType" type="string" value="" />
<field id="limit" type="integer" value="60" />
<field id="metadata" type="assocarray" />
<field id="sortField" type="string" value="SortName" />
<field id="sortAscending" type="boolean" value="true" />
@ -20,14 +22,4 @@
<field id="totalRecordCount" type="int" value="-1" />
<field id="content" type="array" />
</interface>
<script type="text/brightscript" uri="LoadVideoContentTask.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/api/UserLibrary.brs" />
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/api/userauth.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
</component>

View File

@ -1,3 +1,10 @@
import "pkg:/source/utils/misc.brs"
import "pkg:/source/utils/config.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/api/Image.brs"
import "pkg:/source/utils/deviceCapabilities.brs"
import "pkg:/source/roku_modules/api/api.brs"
sub setupNodes()
m.options = m.top.findNode("options")
m.itemGrid = m.top.findNode("itemGrid")
@ -82,11 +89,8 @@ sub init()
'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
if m.global.device.hasVoiceRemote = false
m.micButton.visible = false
m.micButtonText.visible = false
end if

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<?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]" />
<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" />
@ -33,21 +33,21 @@
<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]" />
<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"/>
<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" />
<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="HomeLibraryItem" type="string" />
<field id="parentItem" type="node" onChange="loadInitialItems" />
<field id="selectedItem" type="node" alwaysNotify="true" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
@ -57,11 +57,4 @@
<field id="showItemTitles" type="string" value="showonhover" />
<field id="jumpToItem" type="integer" value="" />
</interface>
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
<script type="text/brightscript" uri="MovieLibraryView.brs" />
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
</component>
</component>

View File

@ -1,3 +1,6 @@
import "pkg:/source/utils/misc.brs"
import "pkg:/source/utils/config.brs"
sub init()
m.itemPoster = m.top.findNode("itemPoster")
m.postTextBackground = m.top.findNode("postTextBackground")

View File

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

View File

@ -1,3 +1,9 @@
import "pkg:/source/utils/misc.brs"
import "pkg:/source/utils/config.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/api/Image.brs"
import "pkg:/source/utils/deviceCapabilities.brs"
sub setupNodes()
m.options = m.top.findNode("options")
m.itemGrid = m.top.findNode("itemGrid")
@ -76,11 +82,8 @@ sub init()
'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
if m.global.device.hasVoiceRemote = false
m.micButton.visible = false
m.micButtonText.visible = false
end if
@ -148,6 +151,11 @@ sub loadInitialItems()
m.top.showItemTitles = "hidealways"
else if LCase(m.view) = "artistsgrid" or LCase(m.options.view) = "artistsgrid"
m.loadItemsTask.genreIds = ""
else if LCase(m.view) = "albumartistsgrid" or LCase(m.options.view) = "albumartistsgrid"
m.loadItemsTask.genreIds = ""
else if LCase(m.view) = "albumartistspresentation" or LCase(m.options.view) = "albumartistspresentation"
m.loadItemsTask.genreIds = ""
m.top.showItemTitles = "hidealways"
else
m.loadItemsTask.itemId = m.top.parentItem.Id
end if
@ -179,6 +187,12 @@ sub loadInitialItems()
else if LCase(m.options.view) = "artistsgrid" or LCase(m.view) = "artistsgrid"
m.itemGrid.translation = "[96, 60]"
m.itemGrid.numRows = "4"
else if LCase(m.options.view) = "albumartistsgrid" or LCase(m.view) = "albumartistsgrid"
m.loadItemsTask.itemType = "AlbumArtists"
m.itemGrid.translation = "[96, 60]"
m.itemGrid.numRows = "4"
else if LCase(m.options.view) = "albumartistspresentation" or LCase(m.view) = "albumartistspresentation"
m.loadItemsTask.itemType = "AlbumArtists"
else if LCase(m.options.view) = "genres" or LCase(m.view) = "genres"
m.loadItemsTask.itemType = ""
m.loadItemsTask.recursive = true
@ -206,6 +220,8 @@ sub setMusicOptions(options)
options.views = [
{ "Title": tr("Artists (Presentation)"), "Name": "ArtistsPresentation" },
{ "Title": tr("Artists (Grid)"), "Name": "ArtistsGrid" },
{ "Title": tr("Album Artists (Presentation)"), "Name": "AlbumArtistsPresentation" },
{ "Title": tr("Album Artists (Grid)"), "Name": "AlbumArtistsGrid" },
{ "Title": tr("Albums"), "Name": "Albums" },
{ "Title": tr("Genres"), "Name": "Genres" }
]
@ -449,6 +465,10 @@ sub onItemFocused()
return
end if
if LCase(m.options.view) = "albumartistsgrid" or LCase(m.view) = "albumartistsgrid"
return
end if
if not m.selectedArtistGenres.visible
m.selectedArtistGenres.visible = true
end if

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="MusicLibraryView" extends="JFScreen">
<children>
<Rectangle id="screenSaverBackground" width="1920" height="1080" color="#000000" />
<VoiceTextEditBox id="VoiceBox" visible="true" width = "40" translation = "[52, 120]" />
<Rectangle id="VoiceBoxCover" height="240" width="100" color="0x000000ff" translation = "[25, 75]" />
<VoiceTextEditBox id="VoiceBox" visible="true" width="40" translation="[52, 120]" />
<Rectangle id="VoiceBoxCover" height="240" width="100" color="0x000000ff" translation="[25, 75]" />
<maskGroup translation="[820, 0]" id="backgroundMask" maskUri="pkg:/images/backgroundmask.png" maskSize="[1220,445]">
<poster id="backdrop" loadDisplayMode="scaleToFill" width="1100" height="450" opacity="1" />
@ -21,18 +21,18 @@
<MarkupGrid id="genrelist" itemComponentName="MusicArtistGridItem" numColumns="6" numRows="4" vertFocusAnimationStyle="fixed" translation="[96, 60]" itemSize="[280, 280]" itemSpacing="[20, 20]" opacity="0" />
<Label id="micButtonText" font="font:SmallSystemFont" visible="false" />
<Button id = "micButton" maxWidth = "20" translation = "[20, 120]" iconUri = "pkg:/images/icons/mic_icon.png"/>
<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" />
<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="HomeLibraryItem" type="string" />
<field id="parentItem" type="node" onChange="loadInitialItems" />
<field id="selectedItem" type="node" alwaysNotify="true" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
@ -42,10 +42,4 @@
<field id="showItemTitles" type="string" value="showonhover" />
<field id="jumpToItem" type="integer" value="" />
</interface>
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
<script type="text/brightscript" uri="MusicLibraryView.brs" />
</component>
</component>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="JFButton" extends="Button">
<interface>
<field id="minChars" type="int" />
</interface>
<script type="text/brightscript" uri="JFButton.brs" />
</component>
</component>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="JFGroup" extends="Group">
<interface>
<field id="backPressed" type="boolean" alwaysNotify="true" />
@ -7,5 +7,4 @@
<field id="optionsAvailable" value="true" type="boolean" />
<field id="overhangVisible" value="true" type="boolean" />
</interface>
<script type="text/brightscript" uri="JFGroup.brs" />
</component>
</component>

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="JFMessageDialog" extends="JFGroup">
<children>
<Poster
id="dialogBackground"
uri="pkg:/images/dialog.9.png"
blendColor="#000000"
translation="[0, 0]"
/>
id="dialogBackground"
uri="pkg:/images/dialog.9.png"
blendColor="#000000"
translation="[0, 0]"
/>
<Label id="messageText" horizAlign="center" wrap="true" />
<LabelList id="optionList" vertFocusAnimationStyle="floatingFocus" textHorizAlign="center">
<ContentNode id = "content" role = "content"></ContentNode>
<ContentNode id="content" role="content"></ContentNode>
</LabelList>
</children>
<interface>
<field id="id" type="string" />
@ -20,5 +20,4 @@
<field id="fontHeight" type="integer" />
<field id="fontWidth" type="integer" />
</interface>
<script type="text/brightscript" uri="JFMessageDialog.brs" />
</component>
</component>

View File

@ -1,3 +1,5 @@
import "pkg:/source/utils/config.brs"
sub init()
m.top.id = "overhang"
' hide seperators till they're needed
@ -18,9 +20,6 @@ sub init()
' show clock based on user setting
m.hideClock = get_user_setting("ui.design.hideclock") = "true"
if not m.hideClock
' get system preference clock format (12/24hr)
di = CreateObject("roDeviceInfo")
m.clockFormat = di.GetClockFormat()
' save node references
m.overlayHours = m.top.findNode("overlayHours")
m.overlayMinutes = m.top.findNode("overlayMinutes")
@ -105,7 +104,7 @@ sub resetTime()
end sub
sub updateTimeDisplay()
if m.clockFormat = "24h"
if m.global.device.clockFormat = "24h"
m.overlayMeridian.text = ""
if m.currentHours < 10
m.overlayHours.text = "0" + StrI(m.currentHours).trim()

View File

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="JFOverhang" extends="Group">
<children>
<Poster id="overlayLogo" uri="pkg:/images/logo.png" translation="[70, 53]" width="270" height="72" />
<LayoutGroup id="overlayLeftGroup" layoutDirection="horiz" translation="[375, 53]" itemSpacings="30">
<Rectangle id="overlayLeftSeperator" color="#666666" width="2" height="64"/>
<Rectangle id="overlayLeftSeperator" color="#666666" width="2" height="64" />
<ScrollingLabel id="overlayTitle" font="font:LargeSystemFont" vertAlign="center" height="64" maxWidth="1100" repeatCount="0" />
</LayoutGroup>
<LayoutGroup id="overlayRightGroup" layoutDirection="horiz" itemSpacings="30" translation="[1820, 53]" horizAlignment="right">
<Label id="overlayCurrentUser" font="font:MediumSystemFont" width="300" horizAlign="right" vertAlign="center" height="64" />
<Rectangle id="overlayRightSeperator" color="#666666" width="2" height="64" visible="false"/>
<Rectangle id="overlayRightSeperator" color="#666666" width="2" height="64" visible="false" />
<LayoutGroup id="overlayTimeGroup" layoutDirection="horiz" horizAlignment="right" itemSpacings="0">
<Label id="overlayHours" font="font:MediumSystemFont" vertAlign="center" height="64" />
<Label font="font:MediumSystemFont" text=":" vertAlign="center" height="64" />
@ -40,6 +40,4 @@
<field id="disableMoveAnimation" value="false" type="boolean" />
<function name="resetTime" />
</interface>
<script type="text/brightscript" uri="JFOverhang.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>
</component>

View File

@ -1,3 +1,5 @@
import "pkg:/source/utils/misc.brs"
sub init()
m.top.backgroundColor = "#262626" '"#101010"
m.top.backgroundURI = ""

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="JFScene" extends="Scene">
<children>
<Group id="content" />
@ -7,6 +7,4 @@
<interface>
<field id="exit" type="boolean" alwaysNotify="true" />
</interface>
<script type="text/brightscript" uri="JFScene.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
</component>
</component>

View File

@ -1,3 +1,10 @@
import "pkg:/source/roku_modules/log/LogMixin.brs"
sub init()
' initialize the log manager. second param sets log output:
' 1 error, 2 warn, 3 info, 4 verbose, 5 debug
_rLog = log_initializeLogManager(["log_PrintTransport"], 5) 'bs:disable-line
end sub
' Function called when the screen is displayed by the screen manager
' It is expected that screens override this function to handle focus
' managmenet and any other actions required on screen shown
@ -14,3 +21,4 @@ end sub
' to handle focus any actions required on the screen being hidden
sub OnScreenHidden()
end sub

View File

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="JFScreen" extends="JFGroup">
<interface>
<function name="OnScreenShown" />
<function name="OnScreenHidden" />
</interface>
<script type="text/brightscript" uri="JFScreen.brs" />
</component>
</component>

View File

@ -1,3 +1,7 @@
import "pkg:/source/utils/misc.brs"
import "pkg:/source/utils/config.brs"
import "pkg:/source/roku_modules/api/api.brs"
sub init()
m.playbackTimer = m.top.findNode("playbackTimer")
m.bufferCheckTimer = m.top.findNode("bufferCheckTimer")

View File

@ -23,11 +23,6 @@
<field id="mediaSourceId" type="string" />
<field id="audioIndex" 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>
<Group id="captionGroup" translation="[960,1020]"></Group>

View File

@ -1,3 +1,6 @@
import "pkg:/source/utils/config.brs"
import "pkg:/source/utils/misc.brs"
sub init()
m.title = m.top.findNode("title")
m.staticTitle = m.top.findNode("staticTitle")
@ -8,8 +11,6 @@ sub init()
m.backdrop = m.top.findNode("backdrop")
m.deviceInfo = CreateObject("roDeviceInfo")
' Randmomise the background colors
posterBackgrounds = m.global.constants.poster_bg_pallet
m.backdrop.color = posterBackgrounds[rnd(posterBackgrounds.count()) - 1]
@ -101,7 +102,7 @@ sub focusChanged()
m.staticTitle.visible = false
m.title.visible = true
' text to speech for accessibility
if m.deviceInfo.IsAudioGuideEnabled() = true
if m.global.device.isAudioGuideEnabled = true
txt2Speech = CreateObject("roTextToSpeech")
txt2Speech.Flush()
txt2Speech.Say(m.title.text)

View File

@ -16,7 +16,4 @@
<field id="itemWidth" type="integer" />
<field id="itemHasFocus" type="boolean" onChange="focusChanged" />
</interface>
<script type="text/brightscript" uri="ListPoster.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
</component>

View File

@ -0,0 +1,17 @@
sub setTitle()
m.top.findNode("titleArea").primaryTitle = m.top.title
end sub
sub setOverview()
m.top.findNode("description").text = m.top.overview
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if press = false then return false
if key = "OK" and m.top.findNode("contentArea").isInFocusChain()
m.top.close = true
end if
return false
end function

View File

@ -1,35 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="OverviewDialog" extends="StandardDialog">
<interface>
<field id="Title" type="string" onchange="setTitle" />
<field id="Overview" type="string" onChange="setOverview" />
</interface>
<script type="text/brightscript">
<![CDATA[
sub setTitle()
m.top.findNode("titleArea").primaryTitle = m.top.title
end sub
sub setOverview()
m.top.findNode("description").text = m.top.overview
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if press = false then return false
if key = "OK" and m.top.findNode("contentArea").isInFocusChain()
m.top.close = true
end if
return false
end function
]]>
</script>
<children>
<StdDlgTitleArea id="titleArea" />
<StdDlgContentArea id="contentArea">
<StdDlgTextItem id="description"
namedTextStyle="normal" />
</StdDlgContentArea>
</children>
<interface>
<field id="Title" type="string" onchange="setTitle" />
<field id="Overview" type="string" onChange="setOverview" />
</interface>
<children>
<StdDlgTitleArea id="titleArea" />
<StdDlgContentArea id="contentArea">
<StdDlgTextItem id="description"
namedTextStyle="normal" />
</StdDlgContentArea>
</children>
</component>

View File

@ -1,3 +1,7 @@
import "pkg:/source/api/Image.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/config.brs"
sub init()
m.dscr = m.top.findNode("description")
m.vidsList = m.top.findNode("extrasGrid")

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="PersonDetails" extends="JFGroup">
<interface>
<field id="itemContent" type="node" onChange="loadPerson" />
@ -7,26 +7,26 @@
</interface>
<children>
<LayoutGroup id="main_group"
layoutdirection="vert" translation="[60, 180]" itemSpacings="[36]" >
<LayoutGroup id="header_group" layoutdirection="horiz" >
<LayoutGroup id="title_group" layoutdirection="vert" itemSpacings="[11]" >
<Rectangle id="title_rectangle" height="100" width="1426" color="#262626">
<Label id="name" font="font:LargeBoldSystemFont" height="100" width="1426" vertAlign="bottom" />
</Rectangle>
</LayoutGroup>
<ButtonGroupHoriz id="buttons" >
<Button id="favorite-button" text="Favorite" iconUri="" focusedIconUri="" />
</ButtonGroupHoriz>
layoutdirection="vert" translation="[60, 180]" itemSpacings="[36]">
<LayoutGroup id="header_group" layoutdirection="horiz">
<LayoutGroup id="title_group" layoutdirection="vert" itemSpacings="[11]">
<Rectangle id="title_rectangle" height="100" width="1426" color="#262626">
<Label id="name" font="font:LargeBoldSystemFont" height="100" width="1426" vertAlign="bottom" />
</Rectangle>
</LayoutGroup>
<ButtonGroupHoriz id="buttons">
<Button id="favorite-button" text="Favorite" iconUri="" focusedIconUri="" />
</ButtonGroupHoriz>
</LayoutGroup>
<LayoutGroup id="personInfoGroup"
layoutDirection="horiz" itemSpacings="[46]">
layoutDirection="horiz" itemSpacings="[46]">
<Poster id="personImage"
width="430" height="645" />
width="430" height="645" />
<LayoutGroup id="vertSpacer" layoutDirection="vert" itemSpacings="[24]">
<LayoutGroup id="dataGroup>" layoutDirection="vert" translation="[450,180]">
<Rectangle id="dscrBorder" height="645" width="1322" color="0x202020ff" visible="true">
<Rectangle id='dscrRect' translation="[3, 3]" height="639" width="1316" color="0x202020ff">
<Label id="description"
<Label id="description"
height="627" width="1280" wrap="true" translation="[18, 15]"
font="font:SmallestSystemFont" color="#e4e4e4ff" ellipsisText=" ... (-OK- for More)" />
</Rectangle>
@ -37,8 +37,4 @@
</LayoutGroup>
<extrasSlider id="personVideos" />
</children>
<script type="text/brightscript" uri="PersonDetails.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>
</component>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<!-- A PlaybackDialog is a regular dialog, except it takes key releases -->
<!-- instead of presses so that key releases don't fall into other listeners -->
<component name="PlaybackDialog" extends="Dialog">
<script type="text/brightscript" uri="PlaybackDialog.brs" />
</component>

View File

@ -0,0 +1,4 @@
sub init()
checkmark = m.top.findNode("checkmark")
checkmark.font.size = 48
end sub

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<component name="PlayedCheckmark" extends="Rectangle">
<children>
<Label id="checkmark" width="60" height="50" font="font:MediumBoldSystemFont" horizAlign="center" vertAlign="bottom" text="✓" />
</children>
</component>

View File

@ -1,3 +1,6 @@
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/config.brs"
sub init()
m.top.functionName = "PlaystateUpdate"
end sub

View File

@ -1,11 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="PlaystateTask" extends="Task">
<interface>
<field id="status" type="string" />
<field id="params" type="assocarray" />
</interface>
<script type="text/brightscript" uri="PlaystateTask.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>

View File

@ -1,33 +1,138 @@
sub init()
m.content = m.top.findNode("content")
m.top.observeField("contentData", "onContentDataChanged")
import "pkg:/source/utils/misc.brs"
sub init()
m.contentArea = m.top.findNode("contentArea")
m.radioOptions = m.top.findNode("radioOptions")
m.scrollBarColumn = []
m.top.observeField("contentData", "onContentDataChanged")
m.top.observeFieldScoped("buttonSelected", "onButtonSelected")
m.radioOptions.observeField("focusedChild", "onItemFocused")
m.top.id = "OKDialog"
m.top.height = 900
m.top.title = "What's New?"
m.top.buttons = [tr("OK")]
end sub
' Event handler for when user selected a button
sub onButtonSelected()
if m.top.buttonSelected = 0
m.global.sceneManager.returnData = m.top.contentData.data[m.content.selectedIndex]
m.global.sceneManager.returnData = m.top.contentData.data[m.radioOptions.selectedIndex]
end if
end sub
' Event handler for when user's cursor highlights an option in the option list
sub onItemFocused()
focusedChild = m.radioOptions.focusedChild
if not isValid(focusedChild) then return
moveScrollBar()
' If the option list is scrollable, move the option list to the user's section
if m.scrollBarColumn.count() <> 0
hightedButtonTranslation = m.radioOptions.focusedChild.translation
m.radioOptions.translation = [m.radioOptions.translation[0], -1 * hightedButtonTranslation[1]]
end if
end sub
' Move the popup's scroll bar
sub moveScrollBar()
' If we haven't found the scrollbar column node yet, try to find it now
if m.scrollBarColumn.count() = 0
scrollBar = findNodeBySubtype(m.contentArea, "StdDlgScrollbar")
if scrollBar.count() = 0 or not isValid(scrollBar[0]) or not isValid(scrollBar[0].node)
return
end if
m.scrollBarColumn = findNodeBySubtype(scrollBar[0].node, "Poster")
if m.scrollBarColumn.count() = 0 or not isValid(m.scrollBarColumn[0]) or not isValid(m.scrollBarColumn[0].node)
return
end if
m.scrollBarThumb = findNodeBySubtype(m.scrollBarColumn[0].node, "Poster")
if m.scrollBarThumb.count() = 0 or not isValid(m.scrollBarThumb[0]) or not isValid(m.scrollBarThumb[0].node)
return
end if
m.scrollBarThumb[0].node.blendColor = "#444444"
' If the user presses left then right, it's possible for us to lose focus. Ensure focus stays on the option list.
scrollBar[0].node.observeField("focusedChild", "onScrollBarFocus")
' Hide the default scrollbar background
m.scrollBarColumn[0].node.uri = ""
' Create a new scrollbar background so we can move the original nodes freely
scrollbarBackground = createObject("roSGNode", "Rectangle")
scrollbarBackground.color = "#101010"
scrollbarBackground.opacity = "0.3"
scrollbarBackground.width = "30"
scrollbarBackground.height = m.contentArea.clippingRect.height
scrollbarBackground.translation = [0, 0]
scrollBar[0].node.insertChild(scrollbarBackground, 0)
' Determine the proper scroll amount for the scrollbar
m.scrollAmount = (m.contentArea.clippingRect.height - int(m.scrollBarThumb[0].node.height)) / m.radioOptions.getChildCount()
m.scrollAmount += m.scrollAmount / m.radioOptions.getChildCount()
end if
if not isvalid(m.radioOptions.focusedChild.id) then return
m.scrollBarColumn[0].node.translation = [0, val(m.radioOptions.focusedChild.id) * m.scrollAmount]
end sub
' If somehow the scrollbar gains focus, set focus back to the option list
sub onScrollBarFocus()
m.radioOptions.setFocus(true)
' Ensure scrollbar styles remain in an unfocused state
m.scrollBarThumb[0].node.blendColor = "#353535"
end sub
' Once user selected an item, move cursor down to OK button
sub onItemSelected()
buttonArea = findNodeBySubtype(m.top, "StdDlgButtonArea")
if buttonArea.count() <> 0 and isValid(buttonArea[0]) and isValid(buttonArea[0].node)
buttonArea[0].node.setFocus(true)
end if
end sub
sub onContentDataChanged()
i = 0
for each item in m.top.contentData.data
cardItem = m.content.CreateChild("StdDlgActionCardItem")
cardItem = m.radioOptions.CreateChild("StdDlgActionCardItem")
cardItem.iconType = "radiobutton"
cardItem.id = i
if isValid(item.selected)
m.content.selectedIndex = i
m.radioOptions.selectedIndex = i
end if
textLine = cardItem.CreateChild("SimpleLabel")
textLine.text = item.description
textLine.text = item.track.description
cardItem.observeField("selected", "onItemSelected")
i++
end for
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if key = "right"
' By default RIGHT from the option list selects the OK button
' Instead, keep the user on the option list
return true
end if
if not press then return false
if key = "up"
' By default UP from the OK button is the scrollbar
' Instead, move the user to the option list
if not m.radioOptions.isinFocusChain()
m.radioOptions.setFocus(true)
return true
end if
end if
return false
end function

View File

@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="RadioDialog" extends="StandardMessageDialog">
<?xml version="1.0" encoding="utf-8"?>
<component name="RadioDialog" extends="StandardMessageDialog" initialFocus="radioOptions">
<children>
<StdDlgContentArea>
<StdDlgItemGroup id="content" />
<StdDlgContentArea id="contentArea">
<StdDlgItemGroup id="radioOptions" />
</StdDlgContentArea>
</children>
<interface>
<field id="contentData" type="assocarray" />
</interface>
<script type="text/brightscript" uri="RadioDialog.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
</component>

View File

@ -1,3 +1,9 @@
import "pkg:/source/api/Items.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/config.brs"
import "pkg:/source/api/Image.brs"
import "pkg:/source/utils/deviceCapabilities.brs"
sub init()
m.top.layoutDirection = "vert"
m.top.horizAlignment = "center"

View File

@ -1,16 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="SearchBox" extends="LayoutGroup">
<children>
<Label id = "text" text="" visible="false" />
<DynamicMiniKeyboard id="search_Key" />
<Label id="text" text="" visible="false" />
<DynamicMiniKeyboard id="search_Key" />
</children>
<interface>
<field id="search_values" type="string" alwaysNotify="true" />
</interface>
<script type="text/brightscript" uri="SearchBox.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
</component>
</component>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="Spinner" extends="BusySpinner">
<script type="text/brightscript" uri="Spinner.brs" />
</component>
</component>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="StandardDialog" extends="StandardMessageDialog">
<children>
<StdDlgContentArea id="content" />
@ -6,5 +6,4 @@
<interface>
<field id="contentData" type="assocarray" />
</interface>
<script type="text/brightscript" uri="StandardDialog.brs" />
</component>

View File

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

View File

@ -1,3 +1,6 @@
import "pkg:/source/utils/config.brs"
import "pkg:/source/api/baserequest.brs"
sub init()
m.top.observeField("url", "fetchCaption")
m.top.currentCaption = []
@ -17,12 +20,12 @@ sub init()
m.textColorDict = { "Default": &HFFFFFFFF, "White": &HFFFFFFFF, "Black": &H000000FF, "Red": &HFF0000FF, "Green": &H008000FF, "Blue": &H0000FFFF, "Yellow": &HFFFF00FF, "Magenta": &HFF00FFFF, "Cyan": &H00FFFFFF }
m.bgColorDict = { "Default": &H000000FF, "White": &HFFFFFFFF, "Black": &H000000FF, "Red": &HFF0000FF, "Green": &H008000FF, "Blue": &H0000FFFF, "Yellow": &HFFFF00FF, "Magenta": &HFF00FFFF, "Cyan": &H00FFFFFF }
m.settings = CreateObject("roDeviceInfo")
m.fontSize = m.fontSizeDict[m.settings.GetCaptionsOption("Text/Size")]
m.textColor = m.textColorDict[m.settings.GetCaptionsOption("Text/Color")]
m.textOpac = m.percentageDict[m.settings.GetCaptionsOption("Text/Opacity")]
m.bgColor = m.bgColorDict[m.settings.GetCaptionsOption("Background/Color")]
m.bgOpac = m.percentageDict[m.settings.GetCaptionsOption("Background/Opacity")]
deviceInfo = CreateObject("roDeviceInfo")
m.fontSize = m.fontSizeDict[deviceInfo.GetCaptionsOption("Text/Size")]
m.textColor = m.textColorDict[deviceInfo.GetCaptionsOption("Text/Color")]
m.textOpac = m.percentageDict[deviceInfo.GetCaptionsOption("Text/Opacity")]
m.bgColor = m.bgColorDict[deviceInfo.GetCaptionsOption("Background/Color")]
m.bgOpac = m.percentageDict[deviceInfo.GetCaptionsOption("Background/Opacity")]
setFont()
end sub

View File

@ -6,9 +6,6 @@
<field id="playerState" type="string" value="stopped" />
<field id="currentPos" type="int" />
</interface>
<script type="text/brightscript" uri="captionTask.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<children>
<timer id="captionTimer" repeat="true" duration="0.1" />
</children>

View File

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="ConfigData" extends="ContentNode">
<interface>
<field id="label" type="string" />
<field id="value" type="string" />
<field id="type" type="string" />
</interface>
<script type="text/brightscript" uri="ConfigData.brs" />
</component>
</component>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="ConfigItem" extends="Group">
<children>
<LayoutGroup layoutDirection="horiz" >
<LayoutGroup layoutDirection="horiz">
<Label id="label" />
<TextEditBox id="value" />
</LayoutGroup>
@ -11,6 +11,4 @@
<field id="itemContent" type="node" onChange="itemContentChanged" />
<field id="itemHasFocus" type="boolean" onChange="setColors" />
</interface>
<script type="text/brightscript" uri="ConfigItem.brs" />
</component>
</component>

View File

@ -1,3 +1,5 @@
import "pkg:/source/utils/config.brs"
sub init()
m.top.itemComponentName = "ConfigItem"

View File

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="ConfigList" extends="MarkupList">
<interface>
<field id="configItems" type="nodearray" onChange="setData" />
</interface>
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="ConfigList.brs" />
</component>
</component>

View File

@ -15,7 +15,6 @@ sub itemContentChanged() as void
end sub
sub onFocusPercentChange(event)
'print "focusPercentChange: " ; event.getData()
setTextColor(event.getData())
end sub

View File

@ -6,8 +6,6 @@
<field id="focusPercent" type="float" onchange="onFocusPercentChange" />
</interface>
<script type="text/brightscript" uri="JFServer.brs" />
<children>
<Poster id="poster" translation="[0,5]" width="90" height="90" />
<Group id="labels" translation="[100,0]">
@ -19,4 +17,4 @@
</Group>
</children>
</component>
</component>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="LoginScene" extends="JFGroup">
<children>
<label text="Enter Configuration"
@ -30,5 +30,4 @@
duration="3"
repeat="true" />
</children>
<script type="text/brightscript" uri="LoginScene.brs"/>
</component>
</component>

View File

@ -1,7 +1,10 @@
import "pkg:/source/roku_modules/log/LogMixin.brs"
'
' Task used to discover jellyfin servers on the local network
'
sub init()
m.log = log.Logger("ServerDiscoveryTask")
m.top.functionName = "execute"
end sub
@ -36,7 +39,7 @@ sub execute()
end while
m.top.content = m.servers
print m.servers[0], m.servers[1], m.servers[2]
m.log.debug("Jellyfin servers found", m.servers[0], m.servers[1], m.servers[2])
end sub
sub AddServer(server)
@ -75,9 +78,9 @@ sub ProcessClientDiscoveryResponse(message)
iconWidth: 120,
iconHeight: 120
})
print "Found Jellyfin server using client discovery at " + server.Address
m.log.info("Found Jellyfin server using client discovery", server.Address)
catch e
print "Error scanning for jellyfin server", message
m.log.error("Error scanning for jellyfin server", message)
end try
end if
end sub
@ -122,7 +125,7 @@ sub ProcessSSDPResponse(message)
if locationUrl = invalid
return
else if m.locationUrlMap[locationUrl] <> invalid
print "Already discovered this location " + locationUrl
m.log.warn("Already discovered this location", locationUrl)
return
end if
@ -162,7 +165,7 @@ sub ProcessSSDPResponse(message)
end if
end for
AddServer(server)
print "Found jellyfin server using SSDP and DLNA at " + server.baseUrl
m.log.info("Found jellyfin server using SSDP and DLNA", server.baseUrl)
end if
end if
end sub

View File

@ -3,5 +3,4 @@
<interface>
<field id="content" type="array" />
</interface>
<script type="text/brightscript" uri="ServerDiscoveryTask.brs" />
</component>
</component>

View File

@ -1,4 +1,8 @@
import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/utils/config.brs"
sub init()
m.log = log.Logger("SetServerScreen")
m.top.setFocus(true)
m.spinner = m.top.findNode("spinner")
@ -14,7 +18,7 @@ sub init()
end sub
function onKeyEvent(key as string, press as boolean) as boolean
print "SetServerScreen onKeyEvent", key, press
m.log.debug("SetServerScreen onKeyEvent", key, press)
if not press then return true
handled = true

View File

@ -4,7 +4,7 @@
<field id="serverUrl" type="string" alias="serverUrlTextbox.text" />
<field id="serverWidth" alias="serverUrlOutline.width,serverUrlTextbox.width,serverUrlContainer.width,submitSizer.width" value="1620" />
<field id="serverHeight" alias="serverUrlOutline.height,serverUrlTextbox.height,serverUrlContainer.height" value="60" />
<field id="errorMessage" type="string" alias="errorMessage.text"/>
<field id="errorMessage" type="string" alias="errorMessage.text" />
</interface>
<children>
@ -25,7 +25,7 @@
<TextEditBox id="serverUrlTextbox" hintText="e.g. 192.168.1.100:8096 or https://example.com/jellyfin"></TextEditBox>
<Poster id="serverUrlOutline" visible="false" uri="pkg:/images/hd_focus.9.png" />
</Rectangle>
<label id="errorMessage" text="" font="font:MediumSystemFont" color="#ff0000FF" />
<label id="errorMessage" text="" font="font:MediumSystemFont" color="#ff0000FF" />
<LayoutGroup horizAlignment="center">
<JFButton id="submit" minChars="30" text="Submit"></JFButton>
<!--add a known width invisibile element to allow the button to be centered-->
@ -35,8 +35,4 @@
<OptionsSlider id="options" />
</children>
<script type="text/brightscript" uri="SetServerScreen.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>
</component>

View File

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="AlbumData" extends="ContentNode">
<interface>
<field id="id" type="string" />
<field id="title" type="string" />
<field id="json" type="assocarray" onChange="setFields" />
</interface>
<script type="text/brightscript" uri="AlbumData.brs" />
</component>
</component>

View File

@ -1,3 +1,7 @@
import "pkg:/source/api/Image.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/config.brs"
sub setFields()
json = m.top.json
m.top.id = json.id

View File

@ -1,10 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="ChannelData" extends="JFContentItem">
<interface>
<field id="channelID" type="string" />
<field id="selectedAudioStreamIndex" type="integer" value="1" />
</interface>
<script type="text/brightscript" uri="ChannelData.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>
</component>

View File

@ -1,3 +1,7 @@
import "pkg:/source/api/Image.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/config.brs"
sub setFields()
json = m.top.json

View File

@ -1,12 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="CollectionData" extends="JFContentItem">
<interface>
<field id="collectionID" type="string" />
<field id="description" type="string" />
<field id="overview" type="string" value="boxsets" />
</interface>
<script type="text/brightscript" uri="CollectionData.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<!-- The "ContentNode" for displaying the actual Extras Item -->
<component name="ExtrasData" extends="ContentNode">
<script type="text/brightscript">
@ -19,8 +19,9 @@
<field id="Type" type="string" />
<field id="subTitle" type="string" />
<field id="labelText" type="string" />
<field id="selectedAudioStreamIndex" type="integer" value="1" />
<field id="posterUrl" type="string" />
<field id="imageWidth" type="integer" value="234" />
<field id="json" type="assocarray" />
</interface>
</component>
</component>

View File

@ -1,3 +1,7 @@
import "pkg:/source/api/Image.brs"
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/config.brs"
sub setFields()
json = m.top.json

View File

@ -1,7 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="FolderData" extends="JFContentItem">
<script type="text/brightscript" uri="FolderData.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
</component>
</component>

View File

@ -1,3 +1,6 @@
import "pkg:/source/utils/config.brs"
import "pkg:/source/roku_modules/api/api.brs"
sub init()
m.top.functionName = "getFiltersTask"
end sub

View File

@ -1,11 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="GetFiltersTask" extends="Task">
<interface>
<field id="params" type="assocarray" />
<field id="filters" type="assocarray" />
</interface>
<script type="text/brightscript" uri="GetFiltersTask.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/roku_modules/api/api.brs" />
</component>

View File

@ -1,3 +1,7 @@
import "pkg:/source/api/baserequest.brs"
import "pkg:/source/utils/config.brs"
import "pkg:/source/api/Image.brs"
sub setData()
' We keep json around just as a reference,
' but ideally everything should be going through one of the interfaces
@ -28,8 +32,9 @@ sub setData()
end if
else if datum.type = "Episode"
imgParams = { "AddPlayedIndicator": datum.UserData.Played }
m.top.isWatched = datum.UserData.Played
imgParams = {}
imgParams.Append({ "maxHeight": 261 })
imgParams.Append({ "maxWidth": 464 })
@ -68,8 +73,9 @@ sub setData()
end if
else if datum.type = "Movie"
imgParams = { AddPlayedIndicator: datum.UserData.Played }
m.top.isWatched = datum.UserData.Played
imgParams = {}
imgParams.Append({ "maxHeight": 261 })
imgParams.Append({ "maxWidth": 175 })
@ -92,8 +98,9 @@ sub setData()
end if
else if datum.type = "Video"
imgParams = { AddPlayedIndicator: datum.UserData.Played }
m.top.isWatched = datum.UserData.Played
imgParams = {}
imgParams.Append({ "maxHeight": 261 })
imgParams.Append({ "maxWidth": 175 })

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<component name="HomeData" extends="ContentNode">
<interface>
<field id="id" type="string" />
@ -13,9 +13,8 @@
<field id="imageWidth" type="integer" value="464" />
<field id="PlayedPercentage" type="float" value="0" />
<field id="usePoster" type="bool" value="false" />
<field id="isWatched" type="bool" value="false" />
<field id="selectedAudioStreamIndex" type="integer" value="1" />
<field id="startingPoint" type="longinteger" value="0" />
</interface>
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="HomeData.brs" />
</component>
</component>

Some files were not shown because too many files have changed in this diff Show More