Merge remote-tracking branch 'origin/master' into selectAudio
This commit is contained in:
commit
301cd5c58f
2
.github/workflows/auto-close-stale-pr.yml
vendored
2
.github/workflows/auto-close-stale-pr.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
|||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8
|
||||
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9
|
||||
with:
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
|
|
2
.github/workflows/automations.yml
vendored
2
.github/workflows/automations.yml
vendored
|
@ -6,8 +6,6 @@ concurrency:
|
|||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- unstable
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
|
|
7
.github/workflows/build-dev.yml
vendored
7
.github/workflows/build-dev.yml
vendored
|
@ -2,11 +2,10 @@ name: build-dev
|
|||
|
||||
on:
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
- unstable
|
||||
- master
|
||||
- "*.*.z"
|
||||
|
||||
jobs:
|
||||
dev:
|
||||
|
@ -23,7 +22,7 @@ jobs:
|
|||
run: npm run ropm
|
||||
- name: Build app
|
||||
run: npm run build
|
||||
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3
|
||||
- uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4
|
||||
with:
|
||||
name: Jellyfin-Roku-dev-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/build/staging
|
||||
|
|
2
.github/workflows/build-docs.yml
vendored
2
.github/workflows/build-docs.yml
vendored
|
@ -3,7 +3,7 @@ name: build-docs
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- unstable
|
||||
- master
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
|
|
57
.github/workflows/build-prod.yml
vendored
57
.github/workflows/build-prod.yml
vendored
|
@ -1,64 +1,15 @@
|
|||
# Builds the production version of the app
|
||||
name: build-prod
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- "*.*.z"
|
||||
|
||||
jobs:
|
||||
version-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout master (the latest release)
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
with:
|
||||
ref: master
|
||||
- name: Install jq to parse json
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: jq
|
||||
- name: Save old package.json version
|
||||
run: echo "oldPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
|
||||
- name: Find and save old major_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Find and save old minor_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Find and save old build_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Save old manifest version
|
||||
run: echo "oldManVersion=${{ env.oldMajor }}.${{ env.oldMinor }}.${{ env.oldBuild }}" >> $GITHUB_ENV
|
||||
- name: Save old Makefile version
|
||||
run: awk 'BEGIN { FS=" = " } /^VERSION/ { print "oldMakeVersion="$2; }' Makefile >> $GITHUB_ENV
|
||||
- name: Checkout PR branch
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
- name: Save new package.json version
|
||||
run: echo "newPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
|
||||
- name: package.json version must be updated
|
||||
if: env.oldPackVersion == env.newPackVersion
|
||||
run: exit 1
|
||||
- name: Find and save new major_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^major_version/ { print "newMajor="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Find and save new minor_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "newMinor="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Find and save new build_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^build_version/ { print "newBuild="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Save new manifest version
|
||||
run: echo "newManVersion=${{ env.newMajor }}.${{ env.newMinor }}.${{ env.newBuild }}" >> $GITHUB_ENV
|
||||
- name: Manifest version must be updated
|
||||
if: env.oldManVersion == env.newManVersion
|
||||
run: exit 1
|
||||
- name: Save new Makefile version
|
||||
run: awk 'BEGIN { FS=" = " } /^VERSION/ { print "newMakeVersion="$2; }' Makefile >> $GITHUB_ENV
|
||||
- name: Makefile version must be updated
|
||||
if: env.oldMakeVersion == env.newMakeVersion
|
||||
run: exit 1
|
||||
- name: All new versions must match
|
||||
if: (env.newManVersion != env.newPackVersion) || (env.newManVersion != env.newMakeVersion)
|
||||
run: exit 1
|
||||
prod:
|
||||
if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'release-prep') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
|
@ -72,7 +23,7 @@ jobs:
|
|||
run: npm run ropm
|
||||
- name: Build app for production
|
||||
run: npm run build-prod
|
||||
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3
|
||||
- uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4
|
||||
with:
|
||||
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/build/staging
|
||||
|
|
8
.github/workflows/deploy-api-docs.yml
vendored
8
.github/workflows/deploy-api-docs.yml
vendored
|
@ -3,7 +3,7 @@ name: deploy-api-docs
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: ["unstable"]
|
||||
branches: ["master"]
|
||||
paths: ["docs/**"] # only run if the docs are updated
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
|
@ -32,12 +32,12 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@f156874f8191504dae5b037505266ed5dda6c382 # v3
|
||||
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@a753861a5debcf57bf8b404356158c8e1e33150c # v2
|
||||
uses: actions/upload-pages-artifact@0252fc4ba7626f0298f0cf00902a25c6afc77fa8 # v3
|
||||
with:
|
||||
# Only upload the api docs folder
|
||||
path: "docs/api"
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@9dbe3824824f8a1377b8e298bafde1a50ede43e5 # v2
|
||||
uses: actions/deploy-pages@7a9bd943aa5e5175aeb8502edcc6c1c02d398e10 # v4
|
||||
|
|
3
.github/workflows/lint.yml
vendored
3
.github/workflows/lint.yml
vendored
|
@ -2,7 +2,6 @@ name: lint
|
|||
on:
|
||||
pull_request:
|
||||
|
||||
|
||||
jobs:
|
||||
brightscript:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -79,4 +78,4 @@ jobs:
|
|||
- name: Install roku package dependencies
|
||||
run: npx ropm install
|
||||
- name: Check markdown files for spelling errors
|
||||
run: npm run lint-spelling
|
||||
run: npm run lint-spelling
|
||||
|
|
80
.github/workflows/release-prep.yml
vendored
Normal file
80
.github/workflows/release-prep.yml
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
# All of the jobs in this workflow will only run if the PR that triggered it has a 'release-prep' label
|
||||
name: release-prep
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
version-check:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'release-prep') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: DEBUG ${{ github.event.pull_request.base.ref }}
|
||||
run: echo ${{ github.event.pull_request.base.ref }}
|
||||
- name: Checkout the branch this PR wants to update
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
- name: Install jq to parse json
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: jq
|
||||
- name: Save old package.json version
|
||||
run: echo "oldPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
|
||||
- name: Find and save old major_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Find and save old minor_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Find and save old build_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Save old manifest version
|
||||
run: echo "oldManVersion=${{ env.oldMajor }}.${{ env.oldMinor }}.${{ env.oldBuild }}" >> $GITHUB_ENV
|
||||
- name: Save old Makefile version
|
||||
run: awk 'BEGIN { FS=" := " } /^VERSION/ { print "oldMakeVersion="$2; }' Makefile >> $GITHUB_ENV
|
||||
- name: Checkout PR branch
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
- name: Save new package.json version
|
||||
run: echo "newPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
|
||||
- name: package.json version must be updated
|
||||
if: env.oldPackVersion == env.newPackVersion
|
||||
run: exit 1
|
||||
- name: Find and save new major_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^major_version/ { print "newMajor="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Find and save new minor_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "newMinor="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Find and save new build_version from manifest
|
||||
run: awk 'BEGIN { FS="=" } /^build_version/ { print "newBuild="$2; }' manifest >> $GITHUB_ENV
|
||||
- name: Save new manifest version
|
||||
run: echo "newManVersion=${{ env.newMajor }}.${{ env.newMinor }}.${{ env.newBuild }}" >> $GITHUB_ENV
|
||||
- name: Manifest version must be updated
|
||||
if: env.oldManVersion == env.newManVersion
|
||||
run: exit 1
|
||||
- name: Save new Makefile version
|
||||
run: awk 'BEGIN { FS=" := " } /^VERSION/ { print "newMakeVersion="$2; }' Makefile >> $GITHUB_ENV
|
||||
- name: Makefile version must be updated
|
||||
if: env.oldMakeVersion == env.newMakeVersion
|
||||
run: exit 1
|
||||
- name: All new versions must match
|
||||
if: (env.newManVersion != env.newPackVersion) || (env.newManVersion != env.newMakeVersion)
|
||||
run: exit 1
|
||||
build-prod:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'release-prep') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "npm"
|
||||
- name: NPM install
|
||||
run: npm ci
|
||||
- name: Install roku module dependencies
|
||||
run: npm run ropm
|
||||
- name: Build app for production
|
||||
run: npm run build-prod
|
||||
- uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4
|
||||
with:
|
||||
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/build/staging
|
||||
if-no-files-found: error
|
2
.github/workflows/roku-analysis.yml
vendored
2
.github/workflows/roku-analysis.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
if: env.BRANCH_NAME == 'master'
|
||||
run: npm run build-prod
|
||||
- name: Use Java 17
|
||||
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
|
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -18,4 +18,4 @@
|
|||
"docs/api/**": true
|
||||
},
|
||||
"brightscriptcomment.addExtraAtStartAndEnd": false
|
||||
}
|
||||
}
|
2
Makefile
2
Makefile
|
@ -3,7 +3,7 @@
|
|||
# If you want to get_images, you'll also need convert from ImageMagick
|
||||
##########################################################################
|
||||
|
||||
VERSION := 1.6.6
|
||||
VERSION := 2.0.1
|
||||
|
||||
## usage
|
||||
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
import "pkg:/source/utils/misc.bs"
|
||||
|
||||
' @fileoverview Clock component to display current time formatted based on user's chosen 12 or 24 hour setting
|
||||
|
||||
' Possible clock formats
|
||||
enum ClockFormat
|
||||
h12 = "12h"
|
||||
h24 = "24h"
|
||||
end enum
|
||||
|
||||
sub init()
|
||||
|
||||
' If hideclick setting is checked, exit without setting any variables
|
||||
' If hideclick setting is enabled, exit without setting any variables
|
||||
if m.global.session.user.settings["ui.design.hideclock"]
|
||||
return
|
||||
end if
|
||||
|
@ -16,11 +24,11 @@ sub init()
|
|||
m.currentTimeTimer.control = "start"
|
||||
|
||||
' Default to 12 hour clock
|
||||
m.format = "short-h12"
|
||||
m.format = ClockFormat.h12
|
||||
|
||||
' If user has selected a 24 hour clock, update date display format
|
||||
if LCase(m.global.device.clockFormat) = "24h"
|
||||
m.format = "short-h24"
|
||||
if LCase(m.global.device.clockFormat) = ClockFormat.h24
|
||||
m.format = ClockFormat.h24
|
||||
end if
|
||||
end sub
|
||||
|
||||
|
@ -34,6 +42,64 @@ sub onCurrentTimeTimerFire()
|
|||
' Convert to local time zone
|
||||
m.dateTimeObject.ToLocalTime()
|
||||
|
||||
' Format time as requested
|
||||
m.clockTime.text = m.dateTimeObject.asTimeStringLoc(m.format)
|
||||
' Format time for display - based on 12h/24h setting
|
||||
formattedTime = formatTimeAsString()
|
||||
|
||||
' Display time
|
||||
m.clockTime.text = formattedTime
|
||||
end sub
|
||||
|
||||
' formatTimeAsString: Returns a string with the current time formatted for either a 12 or 24 hour clock
|
||||
'
|
||||
' @return {string} current time formatted for either a 12 hour or 24 hour clock
|
||||
function formatTimeAsString() as string
|
||||
return m.format = ClockFormat.h12 ? format12HourTime() : format24HourTime()
|
||||
end function
|
||||
|
||||
' format12HourTime: Returns a string with the current time formatted for a 12 hour clock
|
||||
'
|
||||
' @return {string} current time formatted for a 12 hour clock
|
||||
function format12HourTime() as string
|
||||
currentHour = m.dateTimeObject.GetHours()
|
||||
currentMinute = m.dateTimeObject.GetMinutes()
|
||||
|
||||
displayedHour = StrI(currentHour).trim()
|
||||
displayedMinute = StrI(currentMinute).trim()
|
||||
meridian = currentHour < 12 ? "am" : "pm"
|
||||
|
||||
if currentHour = 0
|
||||
displayedHour = "12"
|
||||
end if
|
||||
|
||||
if currentHour > 12
|
||||
correctedHour = currentHour - 12
|
||||
displayedHour = StrI(correctedHour).trim()
|
||||
end if
|
||||
|
||||
if currentMinute < 10
|
||||
displayedMinute = `0${displayedMinute}`
|
||||
end if
|
||||
|
||||
return `${displayedHour}:${displayedMinute} ${meridian}`
|
||||
end function
|
||||
|
||||
' format24HourTime: Returns a string with the current time formatted for a 24 hour clock
|
||||
'
|
||||
' @return {string} current time formatted for a 24 hour clock
|
||||
function format24HourTime() as string
|
||||
currentHour = m.dateTimeObject.GetHours()
|
||||
currentMinute = m.dateTimeObject.GetMinutes()
|
||||
|
||||
displayedHour = StrI(currentHour).trim()
|
||||
displayedMinute = StrI(currentMinute).trim()
|
||||
|
||||
if currentHour < 10
|
||||
displayedHour = `0${displayedHour}`
|
||||
end if
|
||||
|
||||
if currentMinute < 10
|
||||
displayedMinute = `0${displayedMinute}`
|
||||
end if
|
||||
|
||||
return `${displayedHour}:${displayedMinute}`
|
||||
end function
|
||||
|
|
|
@ -15,6 +15,10 @@ sub init()
|
|||
|
||||
m.unplayedCount = m.top.findNode("unplayedCount")
|
||||
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
|
||||
m.playedIndicator = m.top.findNode("playedIndicator")
|
||||
m.checkmark = m.top.findNode("checkmark")
|
||||
m.checkmark.width = 90
|
||||
m.checkmark.height = 60
|
||||
|
||||
m.itemText.translation = [0, m.itemPoster.height + 7]
|
||||
|
||||
|
@ -44,6 +48,10 @@ sub itemContentChanged()
|
|||
if itemData = invalid then return
|
||||
|
||||
if itemData.type = "Movie"
|
||||
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.Played) and itemData.json.UserData.Played
|
||||
m.playedIndicator.visible = true
|
||||
end if
|
||||
|
||||
m.itemPoster.uri = itemData.PosterUrl
|
||||
m.itemIcon.uri = itemData.iconUrl
|
||||
m.itemText.text = itemData.Title
|
||||
|
@ -59,6 +67,9 @@ sub itemContentChanged()
|
|||
end if
|
||||
end if
|
||||
end if
|
||||
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.Played) and itemData.json.UserData.Played = true
|
||||
m.playedIndicator.visible = true
|
||||
end if
|
||||
|
||||
m.itemPoster.uri = itemData.PosterUrl
|
||||
m.itemIcon.uri = itemData.iconUrl
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
<maskGroup id="posterMask" maskUri="pkg:/images/postermask.png" scaleRotateCenter="[145, 212.5]" scale="[0.85,0.85]">
|
||||
<Poster id="backdrop" width="290" height="425" loadDisplayMode="scaleToZoom" uri="pkg:/images/white.9.png" />
|
||||
<Poster id="itemPoster" width="290" height="425" loadDisplayMode="scaleToZoom">
|
||||
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[201, 0]">
|
||||
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" opacity=".99" translation="[201, 0]">
|
||||
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:MediumBoldSystemFont" horizAlign="center" vertAlign="center" />
|
||||
</Rectangle>
|
||||
<PlayedCheckmark id="playedIndicator" color="#00a4dcFF" width="90" height="60" opacity=".99" translation="[201, 0]" visible="false" />
|
||||
</Poster>
|
||||
<Poster id="itemIcon" width="50" height="50" translation="[230,10]" />
|
||||
<Label id="posterText" width="280" height="415" translation="[5,5]" horizAlign="center" vertAlign="center" ellipsizeOnBoundary="true" wrap="true" />
|
||||
|
|
|
@ -8,6 +8,7 @@ sub init()
|
|||
m.posterText.font.size = 30
|
||||
m.title.font.size = 25
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
m.playedIndicator = m.top.findNode("playedIndicator")
|
||||
|
||||
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
|
||||
|
||||
|
@ -37,6 +38,10 @@ sub itemContentChanged()
|
|||
|
||||
if not isValid(itemData) then return
|
||||
|
||||
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.Played) and itemData.json.UserData.Played
|
||||
m.playedIndicator.visible = true
|
||||
end if
|
||||
|
||||
m.itemPoster.uri = itemData.PosterUrl
|
||||
m.posterText.text = itemData.title
|
||||
m.title.text = itemData.title
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<ScrollingLabel translation="[0,340]" id="title" horizAlign="center" font="font:SmallSystemFont" repeatCount="0" maxWidth="230" />
|
||||
<Poster id="itemIcon" width="50" height="50" translation="[230,10]" />
|
||||
<Label id="posterText" width="230" height="320" translation="[5,5]" horizAlign="center" vertAlign="center" ellipsizeOnBoundary="true" wrap="true" />
|
||||
<PlayedCheckmark id="playedIndicator" color="#00a4dcFF" width="60" height="46" translation="[170, 15]" visible="false" />
|
||||
</children>
|
||||
<interface>
|
||||
<field id="itemContent" type="node" onChange="itemContentChanged" />
|
||||
|
|
|
@ -64,9 +64,6 @@ sub init()
|
|||
'set inital counts for overhang before content is loaded.
|
||||
m.loadItemsTask.totalRecordCount = 0
|
||||
|
||||
m.spinner = m.top.findNode("spinner")
|
||||
m.spinner.visible = true
|
||||
|
||||
m.Alpha = m.top.findNode("AlphaMenu")
|
||||
m.AlphaSelected = m.top.findNode("AlphaSelected")
|
||||
|
||||
|
@ -92,7 +89,7 @@ end sub
|
|||
'Load initial set of Data
|
||||
sub loadInitialItems()
|
||||
m.loadItemsTask.control = "stop"
|
||||
m.spinner.visible = true
|
||||
startLoadingSpinner()
|
||||
|
||||
if m.top.parentItem.json.Type = "CollectionFolder" 'or m.top.parentItem.json.Type = "Folder"
|
||||
m.top.HomeLibraryItem = m.top.parentItem.Id
|
||||
|
@ -238,7 +235,7 @@ sub loadInitialItems()
|
|||
end if
|
||||
|
||||
m.loadItemsTask.observeField("content", "ItemDataLoaded")
|
||||
m.spinner.visible = true
|
||||
startLoadingSpinner(false)
|
||||
m.loadItemsTask.control = "RUN"
|
||||
SetUpOptions()
|
||||
end sub
|
||||
|
@ -259,7 +256,8 @@ sub setMoviesOptions(options)
|
|||
{ "Title": tr("OFFICIAL_RATING"), "Name": "OfficialRating" },
|
||||
{ "Title": tr("PLAY_COUNT"), "Name": "PlayCount" },
|
||||
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
|
||||
{ "Title": tr("RUNTIME"), "Name": "Runtime" }
|
||||
{ "Title": tr("RUNTIME"), "Name": "Runtime" },
|
||||
{ "Title": tr("Random"), "Name": "Random" },
|
||||
]
|
||||
options.filter = [
|
||||
{ "Title": tr("All"), "Name": "All" },
|
||||
|
@ -278,6 +276,7 @@ sub setBoxsetsOptions(options)
|
|||
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
|
||||
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
|
||||
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
|
||||
{ "Title": tr("Random"), "Name": "Random" },
|
||||
]
|
||||
options.filter = [
|
||||
{ "Title": tr("All"), "Name": "All" },
|
||||
|
@ -302,6 +301,7 @@ sub setTvShowsOptions(options)
|
|||
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
|
||||
{ "Title": tr("OFFICIAL_RATING"), "Name": "OfficialRating" },
|
||||
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
|
||||
{ "Title": tr("Random"), "Name": "Random" },
|
||||
]
|
||||
options.filter = [
|
||||
{ "Title": tr("All"), "Name": "All" },
|
||||
|
@ -348,6 +348,7 @@ sub setMusicOptions(options)
|
|||
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
|
||||
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
|
||||
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
|
||||
{ "Title": tr("Random"), "Name": "Random" },
|
||||
]
|
||||
options.filter = [
|
||||
{ "Title": tr("All"), "Name": "All" },
|
||||
|
@ -450,6 +451,7 @@ end sub
|
|||
'
|
||||
'Handle loaded data, and add to Grid
|
||||
sub ItemDataLoaded(msg)
|
||||
stopLoadingSpinner()
|
||||
m.top.alphaActive = false
|
||||
itemData = msg.GetData()
|
||||
m.loadItemsTask.unobserveField("content")
|
||||
|
@ -475,7 +477,7 @@ sub ItemDataLoaded(msg)
|
|||
m.genreList.setFocus(true)
|
||||
|
||||
m.loading = false
|
||||
m.spinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
return
|
||||
end if
|
||||
|
||||
|
@ -498,7 +500,7 @@ sub ItemDataLoaded(msg)
|
|||
|
||||
m.itemGrid.setFocus(true)
|
||||
m.genreList.setFocus(false)
|
||||
m.spinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
end sub
|
||||
|
||||
'
|
||||
|
@ -571,8 +573,9 @@ end sub
|
|||
'
|
||||
'Load next set of items
|
||||
sub loadMoreData()
|
||||
m.spinner.visible = true
|
||||
if m.Loading = true then return
|
||||
|
||||
startLoadingSpinner(false)
|
||||
m.Loading = true
|
||||
m.loadItemsTask.startIndex = m.loadedItems
|
||||
m.loadItemsTask.observeField("content", "ItemDataLoaded")
|
||||
|
@ -594,7 +597,7 @@ sub onItemalphaSelected()
|
|||
m.loadItemsTask.searchTerm = ""
|
||||
m.VoiceBox.text = ""
|
||||
m.loadItemsTask.nameStartsWith = m.alpha.itemAlphaSelected
|
||||
m.spinner.visible = true
|
||||
startLoadingSpinner(false)
|
||||
loadInitialItems()
|
||||
end if
|
||||
end sub
|
||||
|
@ -609,7 +612,7 @@ sub onvoiceFilter()
|
|||
m.loadItemsTask.NameStartsWith = " "
|
||||
m.loadItemsTask.searchTerm = m.voiceBox.text
|
||||
m.loadItemsTask.recursive = true
|
||||
m.spinner.visible = true
|
||||
startLoadingSpinner(false)
|
||||
loadInitialItems()
|
||||
end if
|
||||
end sub
|
||||
|
@ -735,6 +738,9 @@ sub onChannelSelected(msg)
|
|||
if node.watchChannel <> invalid
|
||||
' Clone the node when it's reused/update in the TimeGrid it doesn't automatically start playing
|
||||
m.top.selectedItem = node.watchChannel.clone(false)
|
||||
' Make sure to set watchChanel to invalid in case the user hits back and then selects
|
||||
' the same channel on the guide (without moving away from the currently selected channel)
|
||||
m.tvGuide.watchChannel = invalid
|
||||
end if
|
||||
end sub
|
||||
|
||||
|
@ -842,7 +848,6 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
|||
end if
|
||||
|
||||
if key = "replay"
|
||||
m.spinner.visible = true
|
||||
m.loadItemsTask.searchTerm = ""
|
||||
m.loadItemsTask.nameStartsWith = ""
|
||||
m.voiceBox.text = ""
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
<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" />
|
||||
|
|
|
@ -7,6 +7,11 @@ import "pkg:/source/api/Image.bs"
|
|||
import "pkg:/source/api/userauth.bs"
|
||||
import "pkg:/source/utils/deviceCapabilities.bs"
|
||||
|
||||
enum SubtitleSelection
|
||||
notset = -2
|
||||
none = -1
|
||||
end enum
|
||||
|
||||
sub init()
|
||||
m.user = AboutMe()
|
||||
m.top.functionName = "loadItems"
|
||||
|
@ -44,19 +49,18 @@ sub loadItems()
|
|||
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)]
|
||||
m.top.content = [LoadItems_VideoPlayer(id, mediaSourceId, audio_stream_idx, forceTranscoding)]
|
||||
end sub
|
||||
|
||||
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
|
||||
function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_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, forceTranscoding)
|
||||
LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, forceTranscoding)
|
||||
|
||||
if video.content = invalid
|
||||
return invalid
|
||||
|
@ -65,9 +69,10 @@ function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic,
|
|||
return video
|
||||
end function
|
||||
|
||||
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean)
|
||||
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean)
|
||||
|
||||
meta = ItemMetaData(video.id)
|
||||
subtitle_idx = m.top.selectedSubtitleIndex
|
||||
|
||||
if not isValid(meta)
|
||||
video.errorMsg = "Error loading metadata"
|
||||
|
@ -77,6 +82,21 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
|||
|
||||
videotype = LCase(meta.type)
|
||||
|
||||
' Check for any Live TV streams coming from other places other than the TV Guide
|
||||
if isValid(meta.json) and isValid(meta.json.ChannelId)
|
||||
if isValid(meta.json.EpisodeTitle)
|
||||
meta.title = meta.json.EpisodeTitle
|
||||
else if isValid(meta.json.Name)
|
||||
meta.title = meta.json.Name
|
||||
end if
|
||||
meta.live = true
|
||||
if LCase(meta.json.type) = "program"
|
||||
video.id = meta.json.ChannelId
|
||||
else
|
||||
video.id = meta.json.id
|
||||
end if
|
||||
end if
|
||||
|
||||
if videotype = "episode" or videotype = "series"
|
||||
video.content.contenttype = "episode"
|
||||
end if
|
||||
|
@ -107,16 +127,41 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
|||
if meta.live then mediaSourceId = ""
|
||||
|
||||
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
|
||||
video.videoId = video.id
|
||||
video.mediaSourceId = mediaSourceId
|
||||
video.audioIndex = audio_stream_idx
|
||||
|
||||
if not isValid(m.playbackInfo)
|
||||
video.errorMsg = "Error loading playback info"
|
||||
video.content = invalid
|
||||
return
|
||||
end if
|
||||
|
||||
addSubtitlesToVideo(video, meta)
|
||||
|
||||
' Enable default subtitle track
|
||||
if subtitle_idx = SubtitleSelection.notset
|
||||
defaultSubtitleIndex = defaultSubtitleTrackFromVid(video.id)
|
||||
|
||||
if defaultSubtitleIndex <> SubtitleSelection.none
|
||||
video.SelectedSubtitle = defaultSubtitleIndex
|
||||
subtitle_idx = defaultSubtitleIndex
|
||||
|
||||
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
|
||||
if not isValid(m.playbackInfo)
|
||||
video.errorMsg = "Error loading playback info"
|
||||
video.content = invalid
|
||||
return
|
||||
end if
|
||||
|
||||
addSubtitlesToVideo(video, meta)
|
||||
else
|
||||
video.SelectedSubtitle = subtitle_idx
|
||||
end if
|
||||
else
|
||||
video.SelectedSubtitle = subtitle_idx
|
||||
end if
|
||||
|
||||
video.videoId = video.id
|
||||
video.mediaSourceId = mediaSourceId
|
||||
video.audioIndex = audio_stream_idx
|
||||
|
||||
video.PlaySessionId = m.playbackInfo.PlaySessionId
|
||||
|
||||
if meta.live
|
||||
|
@ -130,9 +175,7 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
|||
m.playbackInfo = meta.json
|
||||
end if
|
||||
|
||||
addSubtitlesToVideo(video, meta)
|
||||
addAudioStreamsToVideo(video)
|
||||
|
||||
if meta.live
|
||||
video.transcodeParams = {
|
||||
"MediaSourceId": m.playbackInfo.MediaSources[0].Id,
|
||||
|
@ -184,13 +227,106 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
|||
setCertificateAuthority(video.content)
|
||||
video.audioTrack = (audio_stream_idx + 1).ToStr() ' Roku's track indexes count from 1. Our index is zero based
|
||||
|
||||
video.SelectedSubtitle = subtitle_idx
|
||||
|
||||
if not fully_external
|
||||
video.content = authRequest(video.content)
|
||||
end if
|
||||
end sub
|
||||
|
||||
' defaultSubtitleTrackFromVid: Identifies the default subtitle track given video id
|
||||
'
|
||||
' @param {dynamic} videoID - id of video user is playing
|
||||
' @return {integer} indicating the default track's server-side index. Defaults to {SubtitleSelection.none} is one is not found
|
||||
function defaultSubtitleTrackFromVid(videoID) as integer
|
||||
if m.global.session.user.configuration.SubtitleMode = "None"
|
||||
return SubtitleSelection.none ' No subtitles desired: return none
|
||||
end if
|
||||
|
||||
meta = ItemMetaData(videoID)
|
||||
|
||||
if not isValid(meta) then return SubtitleSelection.none
|
||||
if not isValid(meta.json) then return SubtitleSelection.none
|
||||
if not isValidAndNotEmpty(meta.json.mediaSources) then return SubtitleSelection.none
|
||||
if not isValidAndNotEmpty(meta.json.MediaSources[0].MediaStreams) then return SubtitleSelection.none
|
||||
|
||||
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
|
||||
selectedAudioLanguage = meta.json.MediaSources[0].MediaStreams[m.top.selectedAudioStreamIndex].Language ?? ""
|
||||
|
||||
defaultTextSubs = defaultSubtitleTrack(subtitles["all"], selectedAudioLanguage, true) ' Find correct subtitle track (forced text)
|
||||
if defaultTextSubs <> SubtitleSelection.none
|
||||
return defaultTextSubs
|
||||
end if
|
||||
|
||||
if not m.global.session.user.settings["playback.subs.onlytext"]
|
||||
return defaultSubtitleTrack(subtitles["all"], selectedAudioLanguage) ' if no appropriate text subs exist, allow non-text
|
||||
end if
|
||||
|
||||
return SubtitleSelection.none
|
||||
end function
|
||||
|
||||
' defaultSubtitleTrack:
|
||||
'
|
||||
' @param {dynamic} sortedSubtitles - array of subtitles sorted by type and language
|
||||
' @param {string} selectedAudioLanguage - language for selected audio track
|
||||
' @param {boolean} [requireText=false] - indicates if only text subtitles should be considered
|
||||
' @return {integer} indicating the default track's server-side index. Defaults to {SubtitleSelection.none} is one is not found
|
||||
function defaultSubtitleTrack(sortedSubtitles, selectedAudioLanguage as string, requireText = false as boolean) as integer
|
||||
userConfig = m.global.session.user.configuration
|
||||
|
||||
subtitleMode = isValid(userConfig.SubtitleMode) ? LCase(userConfig.SubtitleMode) : ""
|
||||
|
||||
allowSmartMode = false
|
||||
|
||||
' Only evaluate selected audio language if we have a value
|
||||
if selectedAudioLanguage <> ""
|
||||
allowSmartMode = selectedAudioLanguage <> userConfig.SubtitleLanguagePreference
|
||||
end if
|
||||
|
||||
for each item in sortedSubtitles
|
||||
' Only auto-select subtitle if language matches SubtitleLanguagePreference
|
||||
languageMatch = true
|
||||
if userConfig.SubtitleLanguagePreference <> ""
|
||||
languageMatch = (userConfig.SubtitleLanguagePreference = item.Track.Language)
|
||||
end if
|
||||
|
||||
' Ensure textuality of subtitle matches preferenced passed as arg
|
||||
matchTextReq = ((requireText and item.IsTextSubtitleStream) or not requireText)
|
||||
|
||||
if languageMatch and matchTextReq
|
||||
if subtitleMode = "default" and (item.isForced or item.IsDefault)
|
||||
' Return first forced or default subtitle track
|
||||
return item.Index
|
||||
else if subtitleMode = "always"
|
||||
' Return the first found subtitle track
|
||||
return item.Index
|
||||
else if subtitleMode = "onlyforced" and item.IsForced
|
||||
' Return first forced subtitle track
|
||||
return item.Index
|
||||
else if subtitleMode = "smart" and allowSmartMode
|
||||
' Return the first found subtitle track
|
||||
return item.Index
|
||||
end if
|
||||
end if
|
||||
end for
|
||||
|
||||
' User has chosed smart subtitle mode
|
||||
' We already attempted to load subtitles in preferred language, but none were found.
|
||||
' Fall back to default behaviour while ignoring preferredlanguage
|
||||
if subtitleMode = "smart" and allowSmartMode
|
||||
for each item in sortedSubtitles
|
||||
' Ensure textuality of subtitle matches preferenced passed as arg
|
||||
matchTextReq = ((requireText and item.IsTextSubtitleStream) or not requireText)
|
||||
if matchTextReq
|
||||
if item.isForced or item.IsDefault
|
||||
' Return first forced or default subtitle track
|
||||
return item.Index
|
||||
end if
|
||||
end if
|
||||
end for
|
||||
end if
|
||||
|
||||
return SubtitleSelection.none ' Keep current default behavior of "None", if no correct subtitle is identified
|
||||
end function
|
||||
|
||||
sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
|
||||
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
|
||||
if protocol <> "file"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<interface>
|
||||
<field id="itemId" type="string" />
|
||||
<field id="selectedAudioStreamIndex" type="integer" value="0" />
|
||||
<field id="selectedSubtitleIndex" type="integer" value="-1" />
|
||||
<field id="selectedSubtitleIndex" type="integer" value="-2" />
|
||||
<field id="isIntro" type="boolean" />
|
||||
<field id="startIndex" type="integer" value="0" />
|
||||
<field id="itemType" type="string" value="" />
|
||||
|
|
|
@ -17,7 +17,6 @@ sub setupNodes()
|
|||
m.selectedMovieOfficialRating = m.top.findNode("selectedMovieOfficialRating")
|
||||
m.movieLogo = m.top.findNode("movieLogo")
|
||||
m.swapAnimation = m.top.findNode("backroundSwapAnimation")
|
||||
m.spinner = m.top.findNode("spinner")
|
||||
m.Alpha = m.top.findNode("AlphaMenu")
|
||||
m.AlphaSelected = m.top.findNode("AlphaSelected")
|
||||
m.micButton = m.top.findNode("micButton")
|
||||
|
@ -83,8 +82,6 @@ sub init()
|
|||
'set inital counts for overhang before content is loaded.
|
||||
m.loadItemsTask.totalRecordCount = 0
|
||||
|
||||
m.spinner.visible = true
|
||||
|
||||
'Get reset folder setting
|
||||
m.resetGrid = m.global.session.user.settings["itemgrid.reset"]
|
||||
|
||||
|
@ -117,7 +114,7 @@ end sub
|
|||
'Load initial set of Data
|
||||
sub loadInitialItems()
|
||||
m.loadItemsTask.control = "stop"
|
||||
m.spinner.visible = true
|
||||
startLoadingSpinner(false)
|
||||
|
||||
if m.top.parentItem.json.Type = "CollectionFolder"
|
||||
m.top.HomeLibraryItem = m.top.parentItem.Id
|
||||
|
@ -219,7 +216,6 @@ sub loadInitialItems()
|
|||
end if
|
||||
|
||||
m.loadItemsTask.observeField("content", "ItemDataLoaded")
|
||||
m.spinner.visible = true
|
||||
m.loadItemsTask.control = "RUN"
|
||||
|
||||
m.getFiltersTask.observeField("filters", "FilterDataLoaded")
|
||||
|
@ -257,7 +253,8 @@ sub setMoviesOptions(options)
|
|||
{ "Title": tr("OFFICIAL_RATING"), "Name": "OfficialRating" },
|
||||
{ "Title": tr("PLAY_COUNT"), "Name": "PlayCount" },
|
||||
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
|
||||
{ "Title": tr("RUNTIME"), "Name": "Runtime" }
|
||||
{ "Title": tr("RUNTIME"), "Name": "Runtime" },
|
||||
{ "Title": tr("Random"), "Name": "Random" },
|
||||
]
|
||||
|
||||
options.filter = [
|
||||
|
@ -276,7 +273,7 @@ sub setMoviesOptions(options)
|
|||
if m.options.view = "Studios" or m.view = "Studios"
|
||||
options.sort = [
|
||||
{ "Title": tr("TITLE"), "Name": "SortName" },
|
||||
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
|
||||
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" }
|
||||
]
|
||||
options.filter = [
|
||||
{ "Title": tr("All"), "Name": "All" },
|
||||
|
@ -436,7 +433,7 @@ sub ItemDataLoaded(msg)
|
|||
m.genreList.setFocus(true)
|
||||
|
||||
m.loading = false
|
||||
m.spinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
' Return focus to options menu if it was opened while library was loading
|
||||
if m.options.visible
|
||||
m.options.setFocus(true)
|
||||
|
@ -486,7 +483,7 @@ sub ItemDataLoaded(msg)
|
|||
m.emptyText.visible = true
|
||||
end if
|
||||
|
||||
m.spinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
' Return focus to options menu if it was opened while library was loading
|
||||
if m.options.visible
|
||||
m.options.setFocus(true)
|
||||
|
@ -691,8 +688,9 @@ end sub
|
|||
'
|
||||
'Load next set of items
|
||||
sub loadMoreData()
|
||||
m.spinner.visible = true
|
||||
if m.Loading = true then return
|
||||
|
||||
startLoadingSpinner(false)
|
||||
m.Loading = true
|
||||
m.loadItemsTask.startIndex = m.loadedItems
|
||||
m.loadItemsTask.observeField("content", "ItemDataLoaded")
|
||||
|
@ -736,7 +734,6 @@ sub onItemalphaSelected()
|
|||
m.loadItemsTask.searchTerm = ""
|
||||
m.VoiceBox.text = ""
|
||||
m.loadItemsTask.nameStartsWith = m.alpha.itemAlphaSelected
|
||||
m.spinner.visible = true
|
||||
loadInitialItems()
|
||||
end if
|
||||
end sub
|
||||
|
@ -751,7 +748,6 @@ sub onvoiceFilter()
|
|||
m.loadItemsTask.NameStartsWith = " "
|
||||
m.loadItemsTask.searchTerm = m.voiceBox.text
|
||||
m.loadItemsTask.recursive = true
|
||||
m.spinner.visible = true
|
||||
loadInitialItems()
|
||||
end if
|
||||
end sub
|
||||
|
@ -924,7 +920,6 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
|||
end if
|
||||
|
||||
if key = "replay"
|
||||
m.spinner.visible = true
|
||||
m.loadItemsTask.searchTerm = ""
|
||||
m.loadItemsTask.nameStartsWith = ""
|
||||
m.voiceBox.text = ""
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
<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" />
|
||||
|
|
|
@ -17,7 +17,6 @@ sub setupNodes()
|
|||
m.selectedArtistGenres = m.top.findNode("selectedArtistGenres")
|
||||
m.artistLogo = m.top.findNode("artistLogo")
|
||||
m.swapAnimation = m.top.findNode("backroundSwapAnimation")
|
||||
m.spinner = m.top.findNode("spinner")
|
||||
m.Alpha = m.top.findNode("AlphaMenu")
|
||||
m.AlphaSelected = m.top.findNode("AlphaSelected")
|
||||
m.micButton = m.top.findNode("micButton")
|
||||
|
@ -77,8 +76,6 @@ sub init()
|
|||
'set inital counts for overhang before content is loaded.
|
||||
m.loadItemsTask.totalRecordCount = 0
|
||||
|
||||
m.spinner.visible = true
|
||||
|
||||
'Get reset folder setting
|
||||
m.resetGrid = m.global.session.user.settings["itemgrid.reset"]
|
||||
|
||||
|
@ -111,7 +108,7 @@ end sub
|
|||
'Load initial set of Data
|
||||
sub loadInitialItems()
|
||||
m.loadItemsTask.control = "stop"
|
||||
m.spinner.visible = true
|
||||
startLoadingSpinner(false)
|
||||
|
||||
if LCase(m.top.parentItem.json.Type) = "collectionfolder"
|
||||
m.top.HomeLibraryItem = m.top.parentItem.Id
|
||||
|
@ -143,14 +140,12 @@ sub loadInitialItems()
|
|||
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
|
||||
else if LCase(m.view) = "artistspresentation" or LCase(m.options.view) = "artistspresentation"
|
||||
m.loadItemsTask.genreIds = ""
|
||||
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
|
||||
|
@ -204,7 +199,6 @@ sub loadInitialItems()
|
|||
end if
|
||||
|
||||
m.loadItemsTask.observeField("content", "ItemDataLoaded")
|
||||
m.spinner.visible = true
|
||||
m.loadItemsTask.control = "RUN"
|
||||
SetUpOptions()
|
||||
end sub
|
||||
|
@ -232,6 +226,7 @@ sub setMusicOptions(options)
|
|||
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
|
||||
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
|
||||
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
|
||||
{ "Title": tr("Random"), "Name": "Random" },
|
||||
]
|
||||
|
||||
options.filter = [
|
||||
|
@ -242,6 +237,7 @@ sub setMusicOptions(options)
|
|||
if LCase(m.options.view) = "genres" or LCase(m.view) = "genres"
|
||||
options.sort = [
|
||||
{ "Title": tr("TITLE"), "Name": "SortName" },
|
||||
{ "Title": tr("Random"), "Name": "Random" },
|
||||
]
|
||||
options.filter = []
|
||||
end if
|
||||
|
@ -250,6 +246,7 @@ sub setMusicOptions(options)
|
|||
options.sort = [
|
||||
{ "Title": tr("TITLE"), "Name": "SortName" },
|
||||
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
|
||||
{ "Title": tr("Random"), "Name": "Random" },
|
||||
]
|
||||
end if
|
||||
end sub
|
||||
|
@ -326,6 +323,7 @@ end sub
|
|||
'
|
||||
'Handle loaded data, and add to Grid
|
||||
sub ItemDataLoaded(msg)
|
||||
stopLoadingSpinner()
|
||||
m.top.alphaActive = false
|
||||
itemData = msg.GetData()
|
||||
m.loadItemsTask.unobserveField("content")
|
||||
|
@ -351,7 +349,6 @@ sub ItemDataLoaded(msg)
|
|||
m.loadedRows = m.loadedItems / m.genreList.numColumns
|
||||
|
||||
m.loading = false
|
||||
m.spinner.visible = false
|
||||
return
|
||||
end if
|
||||
|
||||
|
@ -374,8 +371,6 @@ sub ItemDataLoaded(msg)
|
|||
m.emptyText.text = tr("NO_ITEMS").Replace("%1", m.top.parentItem.Type)
|
||||
m.emptyText.visible = true
|
||||
end if
|
||||
|
||||
m.spinner.visible = false
|
||||
end sub
|
||||
|
||||
'
|
||||
|
@ -556,8 +551,9 @@ end sub
|
|||
'
|
||||
'Load next set of items
|
||||
sub loadMoreData()
|
||||
m.spinner.visible = true
|
||||
if m.Loading = true then return
|
||||
|
||||
startLoadingSpinner(false)
|
||||
m.Loading = true
|
||||
m.loadItemsTask.startIndex = m.loadedItems
|
||||
m.loadItemsTask.observeField("content", "ItemDataLoaded")
|
||||
|
@ -612,7 +608,6 @@ sub onItemalphaSelected()
|
|||
m.loadItemsTask.searchTerm = ""
|
||||
m.VoiceBox.text = ""
|
||||
m.loadItemsTask.nameStartsWith = m.alpha.itemAlphaSelected
|
||||
m.spinner.visible = true
|
||||
loadInitialItems()
|
||||
end if
|
||||
end sub
|
||||
|
@ -627,7 +622,6 @@ sub onvoiceFilter()
|
|||
m.loadItemsTask.NameStartsWith = " "
|
||||
m.loadItemsTask.searchTerm = m.voiceBox.text
|
||||
m.loadItemsTask.recursive = true
|
||||
m.spinner.visible = true
|
||||
loadInitialItems()
|
||||
end if
|
||||
end sub
|
||||
|
@ -787,7 +781,6 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
|||
end if
|
||||
|
||||
if key = "replay"
|
||||
m.spinner.visible = true
|
||||
m.loadItemsTask.searchTerm = ""
|
||||
m.loadItemsTask.nameStartsWith = ""
|
||||
m.voiceBox.text = ""
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
<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" />
|
||||
|
|
|
@ -3,6 +3,27 @@ import "pkg:/source/utils/misc.bs"
|
|||
sub init()
|
||||
m.top.backgroundColor = "#262626" '"#101010"
|
||||
m.top.backgroundURI = ""
|
||||
m.spinner = m.top.findNode("spinner")
|
||||
end sub
|
||||
|
||||
' Triggered when the isLoading boolean component field is changed
|
||||
sub isLoadingChanged()
|
||||
m.spinner.visible = m.top.isLoading
|
||||
end sub
|
||||
|
||||
' Triggered when the disableRemote boolean component field is changed
|
||||
sub disableRemoteChanged()
|
||||
if m.top.disableRemote
|
||||
dialog = createObject("roSGNode", "ProgressDialog")
|
||||
dialog.id = "invisibiledialog"
|
||||
dialog.visible = false
|
||||
dialog.opacity = 0
|
||||
m.top.dialog = dialog
|
||||
else
|
||||
if isValid(m.top.dialog)
|
||||
m.top.dialog.close = true
|
||||
end if
|
||||
end if
|
||||
end sub
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
<children>
|
||||
<Group id="content" />
|
||||
<JFOverhang id="overhang" />
|
||||
<Spinner id="spinner" translation="[897, 477]" visible="false" />
|
||||
</children>
|
||||
<interface>
|
||||
<field id="disableRemote" type="boolean" value="false" onchange="disableRemoteChanged" />
|
||||
<field id="isLoading" type="boolean" value="false" onchange="isLoadingChanged" />
|
||||
<field id="exit" type="boolean" alwaysNotify="true" />
|
||||
</interface>
|
||||
</component>
|
|
@ -8,6 +8,10 @@ sub init()
|
|||
m.poster = m.top.findNode("poster")
|
||||
m.unplayedCount = m.top.findNode("unplayedCount")
|
||||
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
|
||||
m.playedIndicator = m.top.findNode("playedIndicator")
|
||||
m.checkmark = m.top.findNode("checkmark")
|
||||
m.checkmark.width = 90
|
||||
m.checkmark.height = 60
|
||||
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
|
||||
|
@ -65,6 +69,10 @@ sub itemContentChanged() as void
|
|||
end if
|
||||
end if
|
||||
|
||||
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.Played) and itemData.json.UserData.Played
|
||||
m.playedIndicator.visible = true
|
||||
end if
|
||||
|
||||
if itemData.json.lookup("Type") = "Episode" and isValid(itemData.json.IndexNumber)
|
||||
m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text
|
||||
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
<Rectangle id="backdrop" />
|
||||
<ScrollingLabel id="Series" horizAlign="center" font="font:SmallSystemFont" repeatCount="0" visible="false" />
|
||||
<Poster id="poster" translation="[2,0]" loadDisplayMode="scaleToFit">
|
||||
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[104, 0]">
|
||||
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[102, 0]">
|
||||
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:MediumBoldSystemFont" horizAlign="center" vertAlign="center" />
|
||||
</Rectangle>
|
||||
<PlayedCheckmark id="playedIndicator" color="#00a4dcFF" width="90" height="60" translation="[102, 0]" visible="false" />
|
||||
</Poster>
|
||||
<ScrollingLabel id="title" horizAlign="center" font="font:SmallSystemFont" repeatCount="0" visible="false" />
|
||||
<Label id="staticTitle" horizAlign="center" font="font:SmallSystemFont" wrap="false" />
|
||||
|
|
|
@ -2,5 +2,5 @@ sub init()
|
|||
m.top.poster.uri = "pkg:/images/spinner.png"
|
||||
m.top.control = "start"
|
||||
m.top.clockwise = true
|
||||
m.top.spinInterval = 3
|
||||
m.top.spinInterval = 1
|
||||
end sub
|
||||
|
|
|
@ -5,7 +5,7 @@ sub init()
|
|||
|
||||
m.top.id = "OKDialog"
|
||||
m.top.height = 900
|
||||
m.top.title = "What's New?"
|
||||
m.top.title = m.global.app.version + " - " + tr("What's New?")
|
||||
m.top.buttons = [tr("OK")]
|
||||
|
||||
dialogStyles = {
|
||||
|
@ -21,7 +21,7 @@ sub init()
|
|||
}
|
||||
}
|
||||
|
||||
whatsNewList = ParseJSON(ReadAsciiFile("pkg:/source/static/whatsNew.json"))
|
||||
whatsNewList = ParseJSON(ReadAsciiFile("pkg:/source/static/whatsNew/" + m.global.app.version.ToStr().trim() + ".json"))
|
||||
|
||||
for each item in whatsNewList
|
||||
textLine = m.content.CreateChild("StdDlgMultiStyleTextItem")
|
||||
|
|
|
@ -5,7 +5,6 @@ sub init()
|
|||
m.log = log.Logger("SetServerScreen")
|
||||
m.top.setFocus(true)
|
||||
|
||||
m.spinner = m.top.findNode("spinner")
|
||||
m.serverPicker = m.top.findNode("serverPicker")
|
||||
m.serverUrlTextbox = m.top.findNode("serverUrlTextbox")
|
||||
m.serverUrlContainer = m.top.findNode("serverUrlContainer")
|
||||
|
@ -76,7 +75,7 @@ sub ScanForServers()
|
|||
'run the task
|
||||
m.ssdpScanner.observeField("content", "ScanForServersComplete")
|
||||
m.ssdpScanner.control = "RUN"
|
||||
m.spinner.visible = true
|
||||
startLoadingSpinner(false)
|
||||
end sub
|
||||
|
||||
sub ScanForServersComplete(event)
|
||||
|
@ -109,7 +108,7 @@ sub ScanForServersComplete(event)
|
|||
end if
|
||||
|
||||
m.serverPicker.content = items
|
||||
m.spinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
|
||||
'if we have at least one server, focus on the server picker
|
||||
if m.servers.Count() > 0
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
</LayoutGroup>
|
||||
<!--background for server picker-->
|
||||
<Rectangle color="0x00000020" width="1620" height="400">
|
||||
<Spinner id="spinner" translation="[717, 136]" />
|
||||
<MarkupList id="serverPicker" translation="[50, 20]" itemComponentName="JFServer" itemSpacing="[0, 10]" itemSize="[1520, 100]" numRows="3" vertFocusAnimationStyle="floatingFocus" />
|
||||
</Rectangle>
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import "pkg:/source/roku_modules/log/LogMixin.brs"
|
||||
import "pkg:/source/utils/misc.bs"
|
||||
|
||||
sub init()
|
||||
m.log = log.Logger("SceneManager")
|
||||
|
@ -120,7 +121,7 @@ sub popScene()
|
|||
' Exit app if the stack is empty after removing group
|
||||
m.scene.exit = true
|
||||
end if
|
||||
|
||||
stopLoadingSpinner()
|
||||
end sub
|
||||
|
||||
|
||||
|
|
|
@ -53,12 +53,6 @@ function setPreference(key as string, value as string)
|
|||
return set_user_setting("pref-" + key, value)
|
||||
end function
|
||||
|
||||
sub setActive()
|
||||
if m.global.session.user.settings["global.rememberme"]
|
||||
set_setting("active_user", m.top.id)
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub setServer(hostname as string)
|
||||
m.top.server = hostname
|
||||
end sub
|
||||
|
|
|
@ -11,6 +11,5 @@
|
|||
<function name="setPreference" />
|
||||
<function name="loadFromRegistry" />
|
||||
<function name="saveToRegistry" />
|
||||
<function name="setActive" />
|
||||
</interface>
|
||||
</component>
|
|
@ -9,6 +9,10 @@ sub init()
|
|||
m.top.optionsAvailable = true
|
||||
m.postTask = createObject("roSGNode", "PostTask")
|
||||
|
||||
m.homeRows = m.top.findNode("homeRows")
|
||||
|
||||
m.fadeInFocusBitmap = m.top.findNode("fadeInFocusBitmap")
|
||||
|
||||
if m.global.session.user.settings["ui.home.splashBackground"] = true
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
m.backdrop.uri = buildURL("/Branding/Splashscreen?format=jpg&foregroundLayer=0.15&fillWidth=1280&width=1280&fillHeight=720&height=720&tag=splash")
|
||||
|
@ -16,11 +20,14 @@ sub init()
|
|||
end sub
|
||||
|
||||
sub refresh()
|
||||
m.top.findNode("homeRows").callFunc("updateHomeRows")
|
||||
m.homeRows.focusBitmapBlendColor = "0xFFFFFFFF"
|
||||
m.homeRows.callFunc("updateHomeRows")
|
||||
end sub
|
||||
|
||||
sub loadLibraries()
|
||||
m.top.findNode("homeRows").callFunc("loadLibraries")
|
||||
m.homeRows.focusBitmapBlendColor = "0xFFFFFF00"
|
||||
m.homeRows.callFunc("loadLibraries")
|
||||
m.fadeInFocusBitmap.control = "start"
|
||||
end sub
|
||||
|
||||
' JFScreen hook that gets ran as needed.
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
<component name="Home" extends="JFScreen">
|
||||
<children>
|
||||
<Poster id="backdrop" loadDisplayMode="scaleToZoom" width="1920" height="1200" />
|
||||
<HomeRows id="homeRows" />
|
||||
<HomeRows id="homeRows" focusBitmapBlendColor="0xFFFFFF00" />
|
||||
<OptionsSlider id="options" />
|
||||
|
||||
<Animation id="fadeInFocusBitmap" delay="1" duration=".2" repeat="false" easeFunction="inQuad">
|
||||
<ColorFieldInterpolator id="fadeInFocusBitmapInterpolator" key="[0.0, 1.0]" keyValue="[0xFFFFFF00, 0xFFFFFFFF]" fieldToInterp="homeRows.focusBitmapBlendColor" />
|
||||
</Animation>
|
||||
</children>
|
||||
<interface>
|
||||
<field id="selectedItem" alias="homeRows.selectedItem" />
|
||||
|
|
|
@ -28,6 +28,7 @@ end sub
|
|||
|
||||
|
||||
sub itemContentChanged()
|
||||
m.unplayedCount.visible = false
|
||||
itemData = m.top.itemContent
|
||||
if itemData = invalid then return
|
||||
|
||||
|
@ -65,6 +66,10 @@ sub itemContentChanged()
|
|||
|
||||
' Format the Data based on the type of Home Data
|
||||
if itemData.type = "CollectionFolder" or itemData.type = "UserView" or itemData.type = "Channel"
|
||||
m.itemText.font.size = 35
|
||||
m.itemText.height = 64
|
||||
m.itemText.horizAlign = "center"
|
||||
m.itemText.vertAlign = "bottom"
|
||||
m.itemText.text = itemData.name
|
||||
m.itemPoster.uri = itemData.widePosterURL
|
||||
return
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<component name="HomeRow" extends="ContentNode">
|
||||
<interface>
|
||||
<field id="imageWidth" type="integer" value="464" />
|
||||
<field id="cursorSize" type="array" value="[464, 331]" />
|
||||
<field id="usePoster" type="bool" value="false" />
|
||||
</interface>
|
||||
</component>
|
|
@ -1,4 +1,7 @@
|
|||
import "pkg:/source/utils/misc.bs"
|
||||
import "pkg:/source/constants/HomeRowItemSizes.bs"
|
||||
|
||||
const LOADING_WAIT_TIME = 2
|
||||
|
||||
sub init()
|
||||
m.top.itemComponentName = "HomeItem"
|
||||
|
@ -10,11 +13,14 @@ sub init()
|
|||
|
||||
m.top.showRowLabel = [true]
|
||||
m.top.rowLabelOffset = [0, 20]
|
||||
m.top.showRowCounter = [true]
|
||||
' Hide the row counter to prevent flicker. We'll show it once loading timer fires
|
||||
m.top.showRowCounter = [false]
|
||||
|
||||
m.homeSectionIndexes = {
|
||||
count: 0
|
||||
}
|
||||
m.top.content = CreateObject("roSGNode", "ContentNode")
|
||||
|
||||
m.loadingTimer = createObject("roSGNode", "Timer")
|
||||
m.loadingTimer.duration = LOADING_WAIT_TIME
|
||||
m.loadingTimer.observeField("fire", "loadingTimerComplete")
|
||||
|
||||
updateSize()
|
||||
|
||||
|
@ -26,7 +32,7 @@ sub init()
|
|||
m.LoadLibrariesTask = createObject("roSGNode", "LoadItemsTask")
|
||||
m.LoadLibrariesTask.observeField("content", "onLibrariesLoaded")
|
||||
|
||||
' set up tesk nodes for other rows
|
||||
' set up task nodes for other rows
|
||||
m.LoadContinueWatchingTask = createObject("roSGNode", "LoadItemsTask")
|
||||
m.LoadContinueWatchingTask.itemsToLoad = "continue"
|
||||
|
||||
|
@ -57,139 +63,239 @@ sub updateSize()
|
|||
' spacing between items in a row
|
||||
m.top.rowItemSpacing = [20, 0]
|
||||
|
||||
' Default size to wide poster, the most used size
|
||||
m.top.rowItemSize = homeRowItemSizes.WIDE_POSTER
|
||||
|
||||
m.top.visible = true
|
||||
end sub
|
||||
|
||||
sub onLibrariesLoaded()
|
||||
' save data for other functions
|
||||
m.libraryData = m.LoadLibrariesTask.content
|
||||
m.LoadLibrariesTask.unobserveField("content")
|
||||
m.LoadLibrariesTask.content = []
|
||||
' processUserSections: Loop through user's chosen home section settings and generate the content for each row
|
||||
'
|
||||
sub processUserSections()
|
||||
m.expectedRowCount = 1 ' the favorites row is hardcoded to always show atm
|
||||
m.processedRowCount = 0
|
||||
|
||||
content = CreateObject("roSGNode", "ContentNode")
|
||||
sizeArray = []
|
||||
loadedSections = 0
|
||||
|
||||
' Add sections in order based on user settings
|
||||
' calculate expected row count by processing homesections
|
||||
for i = 0 to 6
|
||||
sectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()])
|
||||
sectionLoaded = addHomeSection(content, sizeArray, sectionName)
|
||||
if sectionName = "latestmedia"
|
||||
' expect 1 row per filtered media library
|
||||
m.filteredLatest = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.LatestItemsExcludes)
|
||||
for each latestLibrary in m.filteredLatest
|
||||
if latestLibrary.collectionType <> "boxsets" and latestLibrary.collectionType <> "livetv" and latestLibrary.json.CollectionType <> "Program"
|
||||
m.expectedRowCount++
|
||||
end if
|
||||
end for
|
||||
else if sectionName <> "none"
|
||||
m.expectedRowCount++
|
||||
end if
|
||||
end for
|
||||
|
||||
' Add home sections in order based on user settings
|
||||
loadedSections = 0
|
||||
for i = 0 to 6
|
||||
sectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()])
|
||||
sectionLoaded = false
|
||||
if sectionName <> "none"
|
||||
sectionLoaded = addHomeSection(sectionName)
|
||||
end if
|
||||
|
||||
' Count how many sections with data are loaded
|
||||
if sectionLoaded then loadedSections++
|
||||
|
||||
' If 2 sections with data are loaded or we're at the end of the web client section data, consider the home view loaded
|
||||
if loadedSections = 2 or i = 6
|
||||
if not m.global.app_loaded
|
||||
if not m.global.app_loaded
|
||||
if loadedSections = 2 or i = 6
|
||||
m.top.signalBeacon("AppLaunchComplete") ' Roku Performance monitoring
|
||||
m.global.app_loaded = true
|
||||
end if
|
||||
end if
|
||||
end for
|
||||
|
||||
' Favorites isn't an option on Web settings, so we must manually add it for now
|
||||
addHomeSection(content, sizeArray, "favorites")
|
||||
' Favorites isn't an option in Web settings, so we manually add it to the end for now
|
||||
addHomeSection("favorites")
|
||||
|
||||
m.top.rowItemSize = sizeArray
|
||||
m.top.content = content
|
||||
' Start the timer for creating the content rows before we set the cursor size
|
||||
m.loadingTimer.control = "start"
|
||||
end sub
|
||||
|
||||
' Removes a home section from the home rows
|
||||
sub removeHomeSection(sectionType as string)
|
||||
sectionName = LCase(sectionType)
|
||||
' onLibrariesLoaded: Handler when LoadLibrariesTask returns data
|
||||
'
|
||||
sub onLibrariesLoaded()
|
||||
' save data for other functions
|
||||
m.libraryData = m.LoadLibrariesTask.content
|
||||
m.LoadLibrariesTask.unobserveField("content")
|
||||
m.LoadLibrariesTask.content = []
|
||||
|
||||
removedSectionIndex = m.homeSectionIndexes[sectionName]
|
||||
processUserSections()
|
||||
end sub
|
||||
|
||||
if not isValid(removedSectionIndex) then return
|
||||
' getOriginalSectionIndex: Gets the index of a section from user settings and adds count of currently known latest media sections
|
||||
'
|
||||
' @param {string} sectionName - Name of section we're looking up
|
||||
'
|
||||
' @return {integer} indicating index of section taking latest media sections into account
|
||||
function getOriginalSectionIndex(sectionName as string) as integer
|
||||
searchSectionName = LCase(sectionName).Replace(" ", "")
|
||||
|
||||
for each section in m.homeSectionIndexes
|
||||
if m.homeSectionIndexes[section] > removedSectionIndex
|
||||
m.homeSectionIndexes[section]--
|
||||
sectionIndex = 0
|
||||
indexLatestMediaSection = 0
|
||||
|
||||
for i = 0 to 6
|
||||
settingSectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()])
|
||||
if settingSectionName = "latestmedia"
|
||||
indexLatestMediaSection = i
|
||||
end if
|
||||
|
||||
if settingSectionName = searchSectionName
|
||||
sectionIndex = i
|
||||
end if
|
||||
end for
|
||||
|
||||
m.homeSectionIndexes.Delete(sectionName)
|
||||
' If the latest media section is before the section we're searching for, then we need to account for how many latest media rows there are
|
||||
addLatestMediaSectionCount = (indexLatestMediaSection < sectionIndex)
|
||||
|
||||
m.top.content.removeChildIndex(removedSectionIndex)
|
||||
if addLatestMediaSectionCount
|
||||
for i = sectionIndex to m.top.content.getChildCount() - 1
|
||||
sectionToTest = m.top.content.getChild(i)
|
||||
if LCase(Left(sectionToTest.title, 6)) = "latest"
|
||||
sectionIndex++
|
||||
end if
|
||||
end for
|
||||
end if
|
||||
|
||||
return sectionIndex
|
||||
end function
|
||||
|
||||
' removeHomeSection: Removes a home section from the home rows
|
||||
'
|
||||
' @param {string} sectionToRemove - Title property of section we're removing
|
||||
sub removeHomeSection(sectionTitleToRemove as string)
|
||||
if not isValid(sectionTitleToRemove) then return
|
||||
|
||||
sectionTitle = LCase(sectionTitleToRemove).Replace(" ", "")
|
||||
if not sectionExists(sectionTitle) then return
|
||||
|
||||
sectionIndexToRemove = getSectionIndex(sectionTitle)
|
||||
|
||||
m.top.content.removeChildIndex(sectionIndexToRemove)
|
||||
setRowItemSize()
|
||||
end sub
|
||||
|
||||
' Adds a new home section to the home rows.
|
||||
' Returns a boolean indicating whether the section was handled.
|
||||
function addHomeSection(content as dynamic, sizeArray as dynamic, sectionName as string) as boolean
|
||||
' setRowItemSize: Loops through all home sections and sets the correct item sizes per row
|
||||
'
|
||||
sub setRowItemSize()
|
||||
if not isValid(m.top.content) then return
|
||||
|
||||
homeSections = m.top.content.getChildren(-1, 0)
|
||||
newSizeArray = CreateObject("roArray", homeSections.count(), false)
|
||||
|
||||
for i = 0 to homeSections.count() - 1
|
||||
newSizeArray[i] = isValid(homeSections[i].cursorSize) ? homeSections[i].cursorSize : homeRowItemSizes.WIDE_POSTER
|
||||
end for
|
||||
m.top.rowItemSize = newSizeArray
|
||||
|
||||
' If we have processed the expected number of content rows, stop the loading timer and run the complete function
|
||||
if m.expectedRowCount = m.processedRowCount
|
||||
m.loadingTimer.control = "stop"
|
||||
loadingTimerComplete()
|
||||
end if
|
||||
end sub
|
||||
|
||||
' loadingTimerComplete: Event handler for when loading wait time has expired
|
||||
'
|
||||
sub loadingTimerComplete()
|
||||
if not m.top.showRowCounter[0]
|
||||
' Show the row counter to prevent flicker
|
||||
m.top.showRowCounter = [true]
|
||||
end if
|
||||
end sub
|
||||
|
||||
' addHomeSection: Adds a new home section to the home rows.
|
||||
'
|
||||
' @param {string} sectionType - Type of section to add
|
||||
' @return {boolean} indicating if the section was handled
|
||||
function addHomeSection(sectionType as string) as boolean
|
||||
' Poster size library items
|
||||
if sectionName = "livetv"
|
||||
createLiveTVRow(content, sizeArray)
|
||||
if sectionType = "livetv"
|
||||
createLiveTVRow()
|
||||
return true
|
||||
end if
|
||||
|
||||
' Poster size library items
|
||||
if sectionName = "smalllibrarytiles"
|
||||
createLibraryRow(content, sizeArray)
|
||||
if sectionType = "smalllibrarytiles"
|
||||
createLibraryRow()
|
||||
return true
|
||||
end if
|
||||
|
||||
' Continue Watching items
|
||||
if sectionName = "resume"
|
||||
createContinueWatchingRow(content, sizeArray)
|
||||
if sectionType = "resume"
|
||||
createContinueWatchingRow()
|
||||
return true
|
||||
end if
|
||||
|
||||
' Next Up items
|
||||
if sectionName = "nextup"
|
||||
createNextUpRow(content, sizeArray)
|
||||
if sectionType = "nextup"
|
||||
createNextUpRow()
|
||||
return true
|
||||
end if
|
||||
|
||||
' Latest items in each library
|
||||
if sectionName = "latestmedia"
|
||||
createLatestInRows(content, sizeArray)
|
||||
if sectionType = "latestmedia"
|
||||
createLatestInRows()
|
||||
return true
|
||||
end if
|
||||
|
||||
' Favorite Items
|
||||
if sectionName = "favorites"
|
||||
createFavoritesRow(content, sizeArray)
|
||||
if sectionType = "favorites"
|
||||
createFavoritesRow()
|
||||
return true
|
||||
end if
|
||||
|
||||
' This section type isn't supported.
|
||||
' Count it as processed since we aren't going to do anything else with it
|
||||
m.processedRowCount++
|
||||
return false
|
||||
end function
|
||||
|
||||
' Create a row displaying the user's libraries
|
||||
sub createLibraryRow(content as dynamic, sizeArray as dynamic)
|
||||
' createLibraryRow: Creates a row displaying the user's libraries
|
||||
'
|
||||
sub createLibraryRow()
|
||||
m.processedRowCount++
|
||||
' Ensure we have data
|
||||
if not isValidAndNotEmpty(m.libraryData) then return
|
||||
|
||||
mediaRow = content.CreateChild("HomeRow")
|
||||
mediaRow.title = tr("My Media")
|
||||
sectionName = tr("My Media")
|
||||
|
||||
m.homeSectionIndexes.AddReplace("library", m.homeSectionIndexes.count)
|
||||
m.homeSectionIndexes.count++
|
||||
' We don't refresh library data, so if section already exists, exit
|
||||
if sectionExists(sectionName)
|
||||
return
|
||||
end if
|
||||
|
||||
sizeArray.push([464, 331])
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = sectionName
|
||||
row.imageWidth = homeRowItemSizes.WIDE_POSTER[0]
|
||||
row.cursorSize = homeRowItemSizes.WIDE_POSTER
|
||||
|
||||
filteredMedia = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.MyMediaExcludes)
|
||||
for each item in filteredMedia
|
||||
mediaRow.appendChild(item)
|
||||
row.appendChild(item)
|
||||
end for
|
||||
|
||||
' Row does not exist, insert it into the home view
|
||||
m.top.content.insertChild(row, getOriginalSectionIndex("smalllibrarytiles"))
|
||||
setRowItemSize()
|
||||
end sub
|
||||
|
||||
' Create a row displaying latest items in each of the user's libraries
|
||||
sub createLatestInRows(content as dynamic, sizeArray as dynamic)
|
||||
' createLatestInRows: Creates a row displaying latest items in each of the user's libraries
|
||||
'
|
||||
sub createLatestInRows()
|
||||
' Ensure we have data
|
||||
if not isValidAndNotEmpty(m.libraryData) then return
|
||||
|
||||
' create a "Latest In" row for each library
|
||||
filteredLatest = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.LatestItemsExcludes)
|
||||
for each lib in filteredLatest
|
||||
for each lib in m.filteredLatest
|
||||
if lib.collectionType <> "boxsets" and lib.collectionType <> "livetv" and lib.json.CollectionType <> "Program"
|
||||
latestInRow = content.CreateChild("HomeRow")
|
||||
latestInRow.title = tr("Latest in") + " " + lib.name + " >"
|
||||
|
||||
m.homeSectionIndexes.AddReplace("latestin" + LCase(lib.name).Replace(" ", ""), m.homeSectionIndexes.count)
|
||||
m.homeSectionIndexes.count++
|
||||
sizeArray.Push([464, 331])
|
||||
|
||||
loadLatest = createObject("roSGNode", "LoadItemsTask")
|
||||
loadLatest.itemsToLoad = "latest"
|
||||
loadLatest.itemId = lib.id
|
||||
|
@ -204,149 +310,168 @@ sub createLatestInRows(content as dynamic, sizeArray as dynamic)
|
|||
end for
|
||||
end sub
|
||||
|
||||
' Create a row displaying the live tv now on section
|
||||
sub createLiveTVRow(content as dynamic, sizeArray as dynamic)
|
||||
contentRow = content.CreateChild("HomeRow")
|
||||
contentRow.title = tr("On Now")
|
||||
m.homeSectionIndexes.AddReplace("livetv", m.homeSectionIndexes.count)
|
||||
m.homeSectionIndexes.count++
|
||||
sizeArray.push([464, 331])
|
||||
' sectionExists: Checks if passed section exists in home row content
|
||||
'
|
||||
' @param {string} sectionTitle - Title of section we're checking for
|
||||
'
|
||||
' @return {boolean} indicating if the section currently exists in the home row content
|
||||
function sectionExists(sectionTitle as string) as boolean
|
||||
if not isValid(sectionTitle) then return false
|
||||
if not isValid(m.top.content) then return false
|
||||
|
||||
searchSectionTitle = LCase(sectionTitle).Replace(" ", "")
|
||||
|
||||
homeSections = m.top.content.getChildren(-1, 0)
|
||||
|
||||
for each section in homeSections
|
||||
if LCase(section.title).Replace(" ", "") = searchSectionTitle
|
||||
return true
|
||||
end if
|
||||
end for
|
||||
|
||||
return false
|
||||
end function
|
||||
|
||||
' getSectionIndex: Returns index of requested section in home row content
|
||||
'
|
||||
' @param {string} sectionTitle - Title of section we're checking for
|
||||
'
|
||||
' @return {integer} indicating index of request section
|
||||
function getSectionIndex(sectionTitle as string) as integer
|
||||
if not isValid(sectionTitle) then return false
|
||||
if not isValid(m.top.content) then return false
|
||||
|
||||
searchSectionTitle = LCase(sectionTitle).Replace(" ", "")
|
||||
|
||||
homeSections = m.top.content.getChildren(-1, 0)
|
||||
|
||||
sectionIndex = homeSections.count()
|
||||
i = 0
|
||||
|
||||
for each section in homeSections
|
||||
if LCase(section.title).Replace(" ", "") = searchSectionTitle
|
||||
sectionIndex = i
|
||||
exit for
|
||||
end if
|
||||
i++
|
||||
end for
|
||||
|
||||
return sectionIndex
|
||||
end function
|
||||
|
||||
' createLiveTVRow: Creates a row displaying the live tv now on section
|
||||
'
|
||||
sub createLiveTVRow()
|
||||
m.LoadOnNowTask.observeField("content", "updateOnNowItems")
|
||||
m.LoadOnNowTask.control = "RUN"
|
||||
end sub
|
||||
|
||||
' Create a row displaying items the user can continue watching
|
||||
sub createContinueWatchingRow(content as dynamic, sizeArray as dynamic)
|
||||
continueWatchingRow = content.CreateChild("HomeRow")
|
||||
continueWatchingRow.title = tr("Continue Watching")
|
||||
m.homeSectionIndexes.AddReplace("resume", m.homeSectionIndexes.count)
|
||||
m.homeSectionIndexes.count++
|
||||
sizeArray.push([464, 331])
|
||||
|
||||
' createContinueWatchingRow: Creates a row displaying items the user can continue watching
|
||||
'
|
||||
sub createContinueWatchingRow()
|
||||
' Load the Continue Watching Data
|
||||
m.LoadContinueWatchingTask.observeField("content", "updateContinueWatchingItems")
|
||||
m.LoadContinueWatchingTask.control = "RUN"
|
||||
end sub
|
||||
|
||||
' Create a row displaying next episodes up to watch
|
||||
sub createNextUpRow(content as dynamic, sizeArray as dynamic)
|
||||
nextUpRow = content.CreateChild("HomeRow")
|
||||
nextUpRow.title = tr("Next Up >")
|
||||
m.homeSectionIndexes.AddReplace("nextup", m.homeSectionIndexes.count)
|
||||
m.homeSectionIndexes.count++
|
||||
sizeArray.push([464, 331])
|
||||
' createNextUpRow: Creates a row displaying next episodes up to watch
|
||||
'
|
||||
sub createNextUpRow()
|
||||
sectionName = tr("Next Up") + ">"
|
||||
|
||||
if not sectionExists(sectionName)
|
||||
nextUpRow = m.top.content.CreateChild("HomeRow")
|
||||
nextUpRow.title = sectionName
|
||||
nextUpRow.imageWidth = homeRowItemSizes.WIDE_POSTER[0]
|
||||
nextUpRow.cursorSize = homeRowItemSizes.WIDE_POSTER
|
||||
end if
|
||||
|
||||
' Load the Next Up Data
|
||||
m.LoadNextUpTask.observeField("content", "updateNextUpItems")
|
||||
m.LoadNextUpTask.control = "RUN"
|
||||
end sub
|
||||
|
||||
' Create a row displaying items from the user's favorites list
|
||||
sub createFavoritesRow(content as dynamic, sizeArray as dynamic)
|
||||
favoritesRow = content.CreateChild("HomeRow")
|
||||
favoritesRow.title = tr("Favorites")
|
||||
sizeArray.Push([464, 331])
|
||||
|
||||
m.homeSectionIndexes.AddReplace("favorites", m.homeSectionIndexes.count)
|
||||
m.homeSectionIndexes.count++
|
||||
|
||||
' createFavoritesRow: Creates a row displaying items from the user's favorites list
|
||||
'
|
||||
sub createFavoritesRow()
|
||||
' Load the Favorites Data
|
||||
m.LoadFavoritesTask.observeField("content", "updateFavoritesItems")
|
||||
m.LoadFavoritesTask.control = "RUN"
|
||||
end sub
|
||||
|
||||
' Update home row data
|
||||
' updateHomeRows: Update function exposed to outside components
|
||||
'
|
||||
sub updateHomeRows()
|
||||
' If resume section exists, reload row's data
|
||||
if m.homeSectionIndexes.doesExist("resume")
|
||||
m.LoadContinueWatchingTask.observeField("content", "updateContinueWatchingItems")
|
||||
m.LoadContinueWatchingTask.control = "RUN"
|
||||
end if
|
||||
|
||||
' If next up section exists, reload row's data
|
||||
if m.homeSectionIndexes.doesExist("nextup")
|
||||
m.LoadNextUpTask.observeField("content", "updateNextUpItems")
|
||||
m.LoadNextUpTask.control = "RUN"
|
||||
end if
|
||||
|
||||
' If favorites section exists, reload row's data
|
||||
if m.homeSectionIndexes.doesExist("favorites")
|
||||
m.LoadFavoritesTask.observeField("content", "updateFavoritesItems")
|
||||
m.LoadFavoritesTask.control = "RUN"
|
||||
end if
|
||||
|
||||
' If live tv's on now section exists, reload row's data
|
||||
if m.homeSectionIndexes.doesExist("livetv")
|
||||
m.LoadOnNowTask.observeField("content", "updateOnNowItems")
|
||||
m.LoadOnNowTask.control = "RUN"
|
||||
end if
|
||||
|
||||
' If latest in library section exists, reload row's data
|
||||
hasLatestHomeSection = false
|
||||
|
||||
for each section in m.homeSectionIndexes
|
||||
if LCase(Left(section, 6)) = "latest"
|
||||
hasLatestHomeSection = true
|
||||
exit for
|
||||
end if
|
||||
end for
|
||||
|
||||
if hasLatestHomeSection
|
||||
updateLatestInRows()
|
||||
end if
|
||||
' Hide the row counter to prevent flicker. We'll show it once loading timer fires
|
||||
m.top.showRowCounter = [false]
|
||||
processUserSections()
|
||||
end sub
|
||||
|
||||
' updateFavoritesItems: Processes LoadFavoritesTask content. Removes, Creates, or Updates favorites row as needed
|
||||
'
|
||||
sub updateFavoritesItems()
|
||||
m.processedRowCount++
|
||||
itemData = m.LoadFavoritesTask.content
|
||||
m.LoadFavoritesTask.unobserveField("content")
|
||||
m.LoadFavoritesTask.content = []
|
||||
|
||||
if itemData = invalid then return
|
||||
sectionName = tr("Favorites")
|
||||
|
||||
rowIndex = m.homeSectionIndexes.favorites
|
||||
|
||||
if itemData.count() < 1
|
||||
removeHomeSection("favorites")
|
||||
return
|
||||
else
|
||||
' remake row using the new data
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = tr("Favorites")
|
||||
|
||||
for each item in itemData
|
||||
usePoster = true
|
||||
|
||||
if lcase(item.type) = "episode" or lcase(item.type) = "audio" or lcase(item.type) = "musicartist"
|
||||
usePoster = false
|
||||
end if
|
||||
|
||||
item.usePoster = usePoster
|
||||
item.imageWidth = row.imageWidth
|
||||
row.appendChild(item)
|
||||
end for
|
||||
|
||||
' replace the old row
|
||||
m.top.content.replaceChild(row, rowIndex)
|
||||
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub updateContinueWatchingItems()
|
||||
itemData = m.LoadContinueWatchingTask.content
|
||||
m.LoadContinueWatchingTask.unobserveField("content")
|
||||
m.LoadContinueWatchingTask.content = []
|
||||
|
||||
if itemData = invalid then return
|
||||
|
||||
if itemData.count() < 1
|
||||
removeHomeSection("resume")
|
||||
if not isValidAndNotEmpty(itemData)
|
||||
removeHomeSection(sectionName)
|
||||
return
|
||||
end if
|
||||
|
||||
' remake row using the new data
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = tr("Continue Watching")
|
||||
row.title = sectionName
|
||||
row.imageWidth = homeRowItemSizes.WIDE_POSTER[0]
|
||||
row.cursorSize = homeRowItemSizes.WIDE_POSTER
|
||||
|
||||
for each item in itemData
|
||||
usePoster = true
|
||||
|
||||
if lcase(item.type) = "episode" or lcase(item.type) = "audio" or lcase(item.type) = "musicartist"
|
||||
usePoster = false
|
||||
end if
|
||||
|
||||
item.usePoster = usePoster
|
||||
item.imageWidth = row.imageWidth
|
||||
row.appendChild(item)
|
||||
end for
|
||||
|
||||
if sectionExists(sectionName)
|
||||
m.top.content.replaceChild(row, getSectionIndex(sectionName))
|
||||
setRowItemSize()
|
||||
return
|
||||
end if
|
||||
|
||||
m.top.content.insertChild(row, getSectionIndex(sectionName))
|
||||
setRowItemSize()
|
||||
end sub
|
||||
|
||||
' updateContinueWatchingItems: Processes LoadContinueWatchingTask content. Removes, Creates, or Updates continue watching row as needed
|
||||
'
|
||||
sub updateContinueWatchingItems()
|
||||
m.processedRowCount++
|
||||
itemData = m.LoadContinueWatchingTask.content
|
||||
m.LoadContinueWatchingTask.unobserveField("content")
|
||||
m.LoadContinueWatchingTask.content = []
|
||||
|
||||
sectionName = tr("Continue Watching")
|
||||
|
||||
if not isValidAndNotEmpty(itemData)
|
||||
removeHomeSection(sectionName)
|
||||
return
|
||||
end if
|
||||
|
||||
sectionName = tr("Continue Watching")
|
||||
|
||||
' remake row using the new data
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = sectionName
|
||||
row.imageWidth = homeRowItemSizes.WIDE_POSTER[0]
|
||||
row.cursorSize = homeRowItemSizes.WIDE_POSTER
|
||||
|
||||
for each item in itemData
|
||||
if isValid(item.json) and isValid(item.json.UserData) and isValid(item.json.UserData.PlayedPercentage)
|
||||
|
@ -358,198 +483,161 @@ sub updateContinueWatchingItems()
|
|||
row.appendChild(item)
|
||||
end for
|
||||
|
||||
' replace the old row
|
||||
m.top.content.replaceChild(row, m.homeSectionIndexes.resume)
|
||||
' Row already exists, replace it with new content
|
||||
if sectionExists(sectionName)
|
||||
m.top.content.replaceChild(row, getSectionIndex(sectionName))
|
||||
setRowItemSize()
|
||||
return
|
||||
end if
|
||||
|
||||
' Row does not exist, insert it into the home view
|
||||
m.top.content.insertChild(row, getOriginalSectionIndex("resume"))
|
||||
setRowItemSize()
|
||||
end sub
|
||||
|
||||
' updateNextUpItems: Processes LoadNextUpTask content. Removes, Creates, or Updates next up row as needed
|
||||
'
|
||||
sub updateNextUpItems()
|
||||
m.processedRowCount++
|
||||
itemData = m.LoadNextUpTask.content
|
||||
m.LoadNextUpTask.unobserveField("content")
|
||||
m.LoadNextUpTask.content = []
|
||||
m.LoadNextUpTask.control = "STOP"
|
||||
|
||||
if itemData = invalid then return
|
||||
sectionName = tr("Next Up") + " >"
|
||||
|
||||
if itemData.count() < 1
|
||||
removeHomeSection("nextup")
|
||||
if not isValidAndNotEmpty(itemData)
|
||||
removeHomeSection(sectionName)
|
||||
return
|
||||
else
|
||||
' remake row using the new data
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = tr("Next Up") + " >"
|
||||
for each item in itemData
|
||||
item.usePoster = row.usePoster
|
||||
item.imageWidth = row.imageWidth
|
||||
row.appendChild(item)
|
||||
end for
|
||||
|
||||
' replace the old row
|
||||
m.top.content.replaceChild(row, m.homeSectionIndexes.nextup)
|
||||
end if
|
||||
end sub
|
||||
|
||||
' Iterate over user's libraries and update data for each Latest In section
|
||||
sub updateLatestInRows()
|
||||
' Ensure we have data
|
||||
if not isValidAndNotEmpty(m.libraryData) then return
|
||||
' remake row using the new data
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = tr("Next Up") + " >"
|
||||
row.imageWidth = homeRowItemSizes.WIDE_POSTER[0]
|
||||
row.cursorSize = homeRowItemSizes.WIDE_POSTER
|
||||
|
||||
' Load new data for each library
|
||||
filteredLatest = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.LatestItemsExcludes)
|
||||
for each lib in filteredLatest
|
||||
if lib.collectionType <> "boxsets" and lib.collectionType <> "livetv" and lib.json.CollectionType <> "Program"
|
||||
loadLatest = createObject("roSGNode", "LoadItemsTask")
|
||||
loadLatest.itemsToLoad = "latest"
|
||||
loadLatest.itemId = lib.id
|
||||
|
||||
metadata = {
|
||||
"title": lib.name,
|
||||
"contentType": lib.json.CollectionType
|
||||
}
|
||||
|
||||
loadLatest.metadata = metadata
|
||||
loadLatest.observeField("content", "updateLatestItems")
|
||||
loadLatest.control = "RUN"
|
||||
end if
|
||||
for each item in itemData
|
||||
item.usePoster = row.usePoster
|
||||
item.imageWidth = row.imageWidth
|
||||
row.appendChild(item)
|
||||
end for
|
||||
|
||||
' Row already exists, replace it with new content
|
||||
if sectionExists(sectionName)
|
||||
m.top.content.replaceChild(row, getSectionIndex(sectionName))
|
||||
setRowItemSize()
|
||||
return
|
||||
end if
|
||||
|
||||
' Row does not exist, insert it into the home view
|
||||
m.top.content.insertChild(row, getSectionIndex(sectionName))
|
||||
setRowItemSize()
|
||||
end sub
|
||||
|
||||
' updateLatestItems: Processes LoadItemsTask content. Removes, Creates, or Updates latest in {library} row as needed
|
||||
'
|
||||
' @param {dynamic} msg - LoadItemsTask
|
||||
sub updateLatestItems(msg)
|
||||
m.processedRowCount++
|
||||
itemData = msg.GetData()
|
||||
|
||||
node = msg.getRoSGNode()
|
||||
node.unobserveField("content")
|
||||
node.content = []
|
||||
|
||||
if itemData = invalid then return
|
||||
sectionName = tr("Latest in") + " " + node.metadata.title + " >"
|
||||
|
||||
sectionName = "latestin" + LCase(node.metadata.title).Replace(" ", "")
|
||||
|
||||
if itemData.count() < 1
|
||||
if not isValidAndNotEmpty(itemData)
|
||||
removeHomeSection(sectionName)
|
||||
return
|
||||
else
|
||||
' remake row using new data
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = tr("Latest in") + " " + node.metadata.title + " >"
|
||||
row.usePoster = true
|
||||
' Handle specific types with different item widths
|
||||
if node.metadata.contentType = "movies"
|
||||
row.imageWidth = 180
|
||||
itemSize = [188, 331]
|
||||
else if node.metadata.contentType = "music"
|
||||
row.imageWidth = 261
|
||||
itemSize = [261, 331]
|
||||
else
|
||||
row.imageWidth = 464
|
||||
itemSize = [464, 331]
|
||||
end if
|
||||
|
||||
imagesize = homeRowItemSizes.WIDE_POSTER
|
||||
|
||||
if isValid(node.metadata.contentType)
|
||||
if LCase(node.metadata.contentType) = "movies"
|
||||
imagesize = homeRowItemSizes.MOVIE_POSTER
|
||||
else if LCase(node.metadata.contentType) = "music"
|
||||
imagesize = homeRowItemSizes.MUSIC_ALBUM
|
||||
end if
|
||||
end if
|
||||
|
||||
for each item in itemData
|
||||
item.usePoster = row.usePoster
|
||||
item.imageWidth = row.imageWidth
|
||||
row.appendChild(item)
|
||||
end for
|
||||
' remake row using new data
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = sectionName
|
||||
row.imageWidth = imagesize[0]
|
||||
row.cursorSize = imagesize
|
||||
row.usePoster = true
|
||||
|
||||
rowIndex = m.homeSectionIndexes[sectionName]
|
||||
|
||||
' Replace the old row
|
||||
if isValid(rowIndex)
|
||||
updateSizeArray(itemSize, rowIndex, "replace")
|
||||
m.top.content.replaceChild(row, rowIndex)
|
||||
return
|
||||
end if
|
||||
|
||||
' Determine highest index of a Lastest In section so we can append the new section after it
|
||||
highestLatestHomeSectionIndex = 0
|
||||
|
||||
for each section in m.homeSectionIndexes
|
||||
if LCase(Left(section, 6)) = "latest"
|
||||
if m.homeSectionIndexes[section] > highestLatestHomeSectionIndex
|
||||
highestLatestHomeSectionIndex = m.homeSectionIndexes[section]
|
||||
end if
|
||||
end if
|
||||
end for
|
||||
|
||||
' We have data for a section that doesn't currently exist
|
||||
rowIndex = highestLatestHomeSectionIndex + 1
|
||||
|
||||
' Advance all the indexes greater than or equal than our new row
|
||||
for each section in m.homeSectionIndexes
|
||||
if m.homeSectionIndexes[section] >= rowIndex
|
||||
m.homeSectionIndexes[section]++
|
||||
end if
|
||||
end for
|
||||
|
||||
m.homeSectionIndexes.AddReplace(sectionName, rowIndex)
|
||||
|
||||
m.top.content.insertChild(row, rowIndex)
|
||||
updateSizeArray(itemSize, rowIndex)
|
||||
for each item in itemData
|
||||
item.usePoster = row.usePoster
|
||||
item.imageWidth = row.imageWidth
|
||||
row.appendChild(item)
|
||||
end for
|
||||
|
||||
if sectionExists(sectionName)
|
||||
' Row already exists, replace it with new content
|
||||
m.top.content.replaceChild(row, getSectionIndex(sectionName))
|
||||
setRowItemSize()
|
||||
return
|
||||
end if
|
||||
|
||||
m.top.content.insertChild(row, getOriginalSectionIndex("latestmedia"))
|
||||
setRowItemSize()
|
||||
end sub
|
||||
|
||||
' updateOnNowItems: Processes LoadOnNowTask content. Removes, Creates, or Updates latest in on now row as needed
|
||||
'
|
||||
sub updateOnNowItems()
|
||||
m.processedRowCount++
|
||||
itemData = m.LoadOnNowTask.content
|
||||
m.LoadOnNowTask.unobserveField("content")
|
||||
m.LoadOnNowTask.content = []
|
||||
|
||||
if itemData = invalid then return
|
||||
sectionName = tr("On Now")
|
||||
|
||||
if itemData.count() < 1
|
||||
removeHomeSection("livetv")
|
||||
if not isValidAndNotEmpty(itemData)
|
||||
removeHomeSection(sectionName)
|
||||
return
|
||||
else
|
||||
' remake row using the new data
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = tr("On Now")
|
||||
itemSize = [464, 331]
|
||||
row.imageWidth = 464
|
||||
for each item in itemData
|
||||
row.usePoster = false
|
||||
if (not isValid(item.thumbnailURL) or item.thumbnailURL = "") and isValid(item.json) and isValid(item.json.imageURL)
|
||||
item.thumbnailURL = item.json.imageURL
|
||||
row.usePoster = true
|
||||
row.imageWidth = 180
|
||||
itemSize = [188, 331]
|
||||
end if
|
||||
item.usePoster = row.usePoster
|
||||
item.imageWidth = row.imageWidth
|
||||
row.appendChild(item)
|
||||
end for
|
||||
|
||||
' replace the old row
|
||||
updateSizeArray(itemSize, m.homeSectionIndexes.livetv, "replace")
|
||||
m.top.content.replaceChild(row, m.homeSectionIndexes.livetv)
|
||||
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub updateSizeArray(rowItemSize, rowIndex = invalid, action = "insert")
|
||||
sizeArray = m.top.rowItemSize
|
||||
' append by default
|
||||
if rowIndex = invalid
|
||||
rowIndex = sizeArray.count()
|
||||
end if
|
||||
|
||||
newSizeArray = []
|
||||
for i = 0 to sizeArray.count()
|
||||
if rowIndex = i
|
||||
if action = "replace"
|
||||
newSizeArray.Push(rowItemSize)
|
||||
else if action = "insert"
|
||||
newSizeArray.Push(rowItemSize)
|
||||
if isValid(sizeArray[i])
|
||||
newSizeArray.Push(sizeArray[i])
|
||||
end if
|
||||
end if
|
||||
else if isValid(sizeArray[i])
|
||||
newSizeArray.Push(sizeArray[i])
|
||||
' remake row using the new data
|
||||
row = CreateObject("roSGNode", "HomeRow")
|
||||
row.title = tr("On Now")
|
||||
row.imageWidth = homeRowItemSizes.WIDE_POSTER[0]
|
||||
row.cursorSize = homeRowItemSizes.WIDE_POSTER
|
||||
|
||||
for each item in itemData
|
||||
row.usePoster = false
|
||||
|
||||
if (not isValid(item.thumbnailURL) or item.thumbnailURL = "") and isValid(item.json) and isValid(item.json.imageURL)
|
||||
item.thumbnailURL = item.json.imageURL
|
||||
row.usePoster = true
|
||||
row.imageWidth = homeRowItemSizes.MOVIE_POSTER[0]
|
||||
row.cursorSize = homeRowItemSizes.MOVIE_POSTER
|
||||
end if
|
||||
|
||||
item.usePoster = row.usePoster
|
||||
item.imageWidth = row.imageWidth
|
||||
row.appendChild(item)
|
||||
end for
|
||||
m.top.rowItemSize = newSizeArray
|
||||
|
||||
' Row already exists, replace it with new content
|
||||
if sectionExists(sectionName)
|
||||
m.top.content.replaceChild(row, getSectionIndex(sectionName))
|
||||
setRowItemSize()
|
||||
return
|
||||
end if
|
||||
|
||||
' Row does not exist, insert it into the home view
|
||||
m.top.content.insertChild(row, getOriginalSectionIndex("livetv"))
|
||||
setRowItemSize()
|
||||
end sub
|
||||
|
||||
sub itemSelected()
|
||||
m.selectedRowItem = m.top.rowItemSelected
|
||||
|
||||
m.top.selectedItem = m.top.content.getChild(m.top.rowItemSelected[0]).getChild(m.top.rowItemSelected[1])
|
||||
|
||||
'Prevent the selected item event from double firing
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import "pkg:/source/utils/misc.bs"
|
||||
|
||||
sub init()
|
||||
m.EPGLaunchCompleteSignaled = false
|
||||
m.scheduleGrid = m.top.findNode("scheduleGrid")
|
||||
|
@ -26,8 +28,6 @@ sub init()
|
|||
m.top.lastFocus = m.scheduleGrid
|
||||
|
||||
m.channelIndex = {}
|
||||
|
||||
m.spinner = m.top.findNode("spinner")
|
||||
end sub
|
||||
|
||||
sub channelFilterSet()
|
||||
|
@ -48,14 +48,14 @@ sub channelsearchTermSet()
|
|||
if LCase(m.top.searchTerm) = LCase(tr("all")) or m.LoadChannelsTask.searchTerm = LCase(tr("all"))
|
||||
m.top.searchTerm = " "
|
||||
m.LoadChannelsTask.searchTerm = " "
|
||||
m.spinner.visible = true
|
||||
startLoadingSpinner()
|
||||
m.LoadChannelsTask.control = "RUN"
|
||||
'filter if the searterm is not invalid
|
||||
else if m.top.searchTerm <> invalid and LCase(m.LoadChannelsTask.searchTerm) <> LCase(m.top.searchTerm)
|
||||
if m.LoadChannelsTask.state = "run" then m.LoadChannelsTask.control = "stop"
|
||||
|
||||
m.LoadChannelsTask.searchTerm = m.top.searchTerm
|
||||
m.spinner.visible = true
|
||||
startLoadingSpinner()
|
||||
m.LoadChannelsTask.control = "RUN"
|
||||
end if
|
||||
|
||||
|
@ -125,7 +125,7 @@ sub onScheduleLoaded()
|
|||
m.scheduleGrid.showLoadingDataFeedback = false
|
||||
m.scheduleGrid.setFocus(true)
|
||||
m.LoadScheduleTask.schedule = []
|
||||
m.spinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
end sub
|
||||
|
||||
sub onProgramFocused()
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
programTitleFocusedColor="#ffffff" iconFocusedColor="#ff0000"
|
||||
showPastTimeScreen="true" pastTimeScreenBlendColor="#555555"
|
||||
/>
|
||||
<Spinner id="spinner" translation="[900, 450]" />
|
||||
<Animation id="gridMoveAnimation" duration="1" repeat="false" easeFunction="outQuad">
|
||||
<Vector2DFieldInterpolator id="gridMoveAnimationPosition" key="[0.0, 0.5]" fieldToInterp="scheduleGrid.translation" />
|
||||
</Animation>
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import "pkg:/source/utils/misc.bs"
|
||||
|
||||
sub init()
|
||||
m.top.optionsAvailable = false
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
stopLoadingSpinner()
|
||||
m.top.findNode("UserRow").ItemContent = m.top.itemContent
|
||||
redraw()
|
||||
end sub
|
||||
|
|
|
@ -161,19 +161,20 @@ sub processSubtitleSelection()
|
|||
end if
|
||||
|
||||
if m.selectedSubtitle.IsEncoded
|
||||
' Roku can not natively display these subtitles, so turn off the caption mode on the device
|
||||
m.view.globalCaptionMode = "Off"
|
||||
else
|
||||
' Roku can natively display these subtitles, ensure the caption mode on the device is on
|
||||
m.view.globalCaptionMode = "On"
|
||||
end if
|
||||
|
||||
if m.selectedSubtitle.IsExternal
|
||||
' Roku may rearrange subtitle tracks. Look up track based on name to ensure we get the correct index
|
||||
availableSubtitleTrackIndex = availSubtitleTrackIdx(m.selectedSubtitle.Track.TrackName)
|
||||
if availableSubtitleTrackIndex = -1 then return
|
||||
|
||||
m.view.subtitleTrack = m.view.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName
|
||||
else
|
||||
m.view.selectedSubtitle = m.selectedSubtitle.Index
|
||||
end if
|
||||
|
||||
m.view.selectedSubtitle = m.selectedSubtitle.Index
|
||||
end sub
|
||||
|
||||
' User requested playback info
|
||||
|
|
|
@ -21,8 +21,6 @@ sub init()
|
|||
m.buttonGrp.setFocus(true)
|
||||
m.top.lastFocus = m.buttonGrp
|
||||
|
||||
m.spinner = m.top.findNode("spinner")
|
||||
|
||||
m.top.observeField("itemContent", "itemContentChanged")
|
||||
end sub
|
||||
|
||||
|
@ -150,7 +148,7 @@ sub itemContentChanged()
|
|||
SetUpVideoOptions(itemData.mediaSources)
|
||||
SetUpAudioOptions(itemData.mediaStreams)
|
||||
m.buttonGrp.visible = true
|
||||
m.spinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
end sub
|
||||
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
</LayoutGroup>
|
||||
</LayoutGroup>
|
||||
<MovieOptions id="movieOptions" visible="false" />
|
||||
<Spinner id="spinner" translation="[900, 450]" visible="true" />
|
||||
|
||||
<!-- "Cast and Crew" row -->
|
||||
<extrasSlider id="movieExtras" />
|
||||
|
|
|
@ -12,8 +12,6 @@ sub init()
|
|||
m.songListRect = m.top.FindNode("songListRect")
|
||||
|
||||
m.songList.observeField("doneLoading", "onDoneLoading")
|
||||
m.spinner = m.top.findNode("spinner")
|
||||
m.spinner.visible = true
|
||||
|
||||
m.dscr = m.top.findNode("overview")
|
||||
createDialogPallete()
|
||||
|
@ -99,8 +97,6 @@ end sub
|
|||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
if not press then return false
|
||||
|
||||
if m.spinner.visible then return false
|
||||
|
||||
if key = "options"
|
||||
if m.dscr.isTextEllipsized
|
||||
createFullDscrDlg()
|
||||
|
@ -169,9 +165,5 @@ end sub
|
|||
|
||||
sub onDoneLoading()
|
||||
m.songList.unobservefield("doneLoading")
|
||||
m.spinner.visible = false
|
||||
end sub
|
||||
|
||||
sub OnScreenHidden()
|
||||
m.spinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
end sub
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
</LayoutGroup>
|
||||
</LayoutGroup>
|
||||
</LayoutGroup>
|
||||
<Spinner id="spinner" translation="[920, 540]" visible="false" />
|
||||
</children>
|
||||
<interface>
|
||||
<field id="pageContent" type="node" onChange="pageContentChanged" />
|
||||
|
|
|
@ -76,6 +76,7 @@ sub OnScreenShown()
|
|||
m.overhang.isVisible = false
|
||||
m.overhang.opacity = "1"
|
||||
end if
|
||||
stopLoadingSpinner()
|
||||
end sub
|
||||
|
||||
sub OnScreenHidden()
|
||||
|
|
|
@ -447,6 +447,7 @@ sub loadButtons()
|
|||
end sub
|
||||
|
||||
sub onAudioStreamLoaded()
|
||||
stopLoadingSpinner()
|
||||
data = m.LoadAudioStreamTask.content[0]
|
||||
m.LoadAudioStreamTask.unobserveField("content")
|
||||
if data <> invalid and data.count() > 0
|
||||
|
|
|
@ -11,8 +11,6 @@ sub init()
|
|||
m.songListRect = m.top.FindNode("songListRect")
|
||||
|
||||
m.songList.observeField("doneLoading", "onDoneLoading")
|
||||
m.spinner = m.top.findNode("spinner")
|
||||
m.spinner.visible = true
|
||||
|
||||
m.dscr = m.top.findNode("overview")
|
||||
createDialogPallete()
|
||||
|
@ -98,8 +96,6 @@ end sub
|
|||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
if not press then return false
|
||||
|
||||
if m.spinner.visible then return false
|
||||
|
||||
if key = "options"
|
||||
if m.dscr.isTextEllipsized
|
||||
createFullDscrDlg()
|
||||
|
@ -160,9 +156,5 @@ end sub
|
|||
|
||||
sub onDoneLoading()
|
||||
m.songList.unobservefield("doneLoading")
|
||||
m.spinner.visible = false
|
||||
end sub
|
||||
|
||||
sub OnScreenHidden()
|
||||
m.spinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
end sub
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
</LayoutGroup>
|
||||
</LayoutGroup>
|
||||
</LayoutGroup>
|
||||
<Spinner id="spinner" translation="[920, 540]" visible="false" />
|
||||
</children>
|
||||
<interface>
|
||||
<field id="pageContent" type="node" onChange="pageContentChanged" />
|
||||
|
|
|
@ -41,6 +41,7 @@ sub itemContentChanged()
|
|||
end sub
|
||||
|
||||
sub onPhotoLoaded()
|
||||
stopLoadingSpinner()
|
||||
if m.LoadLibrariesTask.results <> invalid
|
||||
photo = m.top.findNode("photo")
|
||||
photo.uri = m.LoadLibrariesTask.results
|
||||
|
|
|
@ -6,7 +6,6 @@ import "pkg:/source/utils/deviceCapabilities.bs"
|
|||
|
||||
sub init()
|
||||
m.top.optionsAvailable = false
|
||||
m.searchSpinner = m.top.findnode("searchSpinner")
|
||||
m.searchSelect = m.top.findnode("searchSelect")
|
||||
m.searchTask = CreateObject("roSGNode", "SearchTask")
|
||||
|
||||
|
@ -20,12 +19,12 @@ sub searchMedias()
|
|||
query = m.top.searchAlpha
|
||||
'if user deletes the search string hide the spinner
|
||||
if query.len() = 0
|
||||
m.searchSpinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
end if
|
||||
'if search task is running and user selectes another letter stop the search and load the next letter
|
||||
m.searchTask.control = "stop"
|
||||
if query <> invalid and query <> ""
|
||||
m.searchSpinner.visible = true
|
||||
startLoadingSpinner(false)
|
||||
end if
|
||||
m.searchTask.observeField("results", "loadResults")
|
||||
m.searchTask.query = query
|
||||
|
@ -37,7 +36,7 @@ end sub
|
|||
sub loadResults()
|
||||
m.searchTask.unobserveField("results")
|
||||
|
||||
m.searchSpinner.visible = false
|
||||
stopLoadingSpinner()
|
||||
m.searchSelect.itemdata = m.searchTask.results
|
||||
m.searchSelect.query = m.top.SearchAlpha
|
||||
m.searchHelpText.visible = false
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<SearchRow id="searchSelect" visible="true" focusable="true" />
|
||||
</LayoutGroup>
|
||||
<OptionsSlider id="options" />
|
||||
<Spinner id="searchSpinner" visible="false" translation="[1050, 500]" />
|
||||
</children>
|
||||
<interface>
|
||||
<field id="query" type="string" alwaysNotify="true" />
|
||||
|
|
|
@ -93,6 +93,8 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
|||
focusedItem = getFocusedItem()
|
||||
if isValid(focusedItem)
|
||||
m.top.selectedItem = focusedItem
|
||||
'Prevent the selected item event from double firing
|
||||
m.top.selectedItem = invalid
|
||||
end if
|
||||
return true
|
||||
end if
|
||||
|
|
|
@ -268,6 +268,9 @@ end sub
|
|||
|
||||
' Event handler for when selectedSubtitle changes
|
||||
sub onSubtitleChange()
|
||||
' If the global caption mode is on, that means Roku can display the subtitles natively and doesn't need a video stop/start
|
||||
if LCase(m.top.globalCaptionMode) = "on" then return
|
||||
|
||||
' Save the current video position
|
||||
m.global.queueManager.callFunc("setTopStartingPoint", int(m.top.position) * 10000000&)
|
||||
|
||||
|
@ -327,6 +330,8 @@ sub onVideoContentLoaded()
|
|||
videoContent = m.LoadMetaDataTask.content
|
||||
m.LoadMetaDataTask.content = []
|
||||
|
||||
stopLoadingSpinner()
|
||||
|
||||
' If we have nothing to play, return to previous screen
|
||||
if not isValid(videoContent)
|
||||
showPlaybackErrorDialog(tr("There was an error retrieving the data for this item from the server."))
|
||||
|
@ -361,6 +366,29 @@ sub onVideoContentLoaded()
|
|||
m.top.allowCaptions = true
|
||||
end if
|
||||
|
||||
' Allow default subtitles
|
||||
m.top.unobserveField("selectedSubtitle")
|
||||
|
||||
' Set subtitleTrack property is subs are natively supported by Roku
|
||||
selectedSubtitle = invalid
|
||||
for each subtitle in m.top.fullSubtitleData
|
||||
if subtitle.Index = videoContent[0].selectedSubtitle
|
||||
selectedSubtitle = subtitle
|
||||
exit for
|
||||
end if
|
||||
end for
|
||||
|
||||
if isValid(selectedSubtitle)
|
||||
availableSubtitleTrackIndex = availSubtitleTrackIdx(selectedSubtitle.Track.TrackName)
|
||||
if availableSubtitleTrackIndex <> -1
|
||||
m.top.subtitleTrack = m.top.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName
|
||||
end if
|
||||
end if
|
||||
|
||||
m.top.selectedSubtitle = videoContent[0].selectedSubtitle
|
||||
|
||||
m.top.observeField("selectedSubtitle", "onSubtitleChange")
|
||||
|
||||
if isValid(m.top.audioIndex)
|
||||
m.top.audioTrack = (m.top.audioIndex + 1).toStr()
|
||||
else
|
||||
|
@ -608,6 +636,25 @@ function stateAllowsOSD() as boolean
|
|||
return inArray(validStates, m.top.state)
|
||||
end function
|
||||
|
||||
|
||||
' availSubtitleTrackIdx: Returns Roku's index for requested subtitle track
|
||||
'
|
||||
' @param {string} tracknameToFind - TrackName for subtitle we're looking to match
|
||||
' @return {integer} indicating Roku's index for requested subtitle track. Returns -1 if not found
|
||||
function availSubtitleTrackIdx(tracknameToFind as string) as integer
|
||||
idx = 0
|
||||
for each availTrack in m.top.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, tracknameToFind)
|
||||
return idx
|
||||
end if
|
||||
idx = idx + 1
|
||||
end for
|
||||
return -1
|
||||
end function
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
|
||||
' Keypress handler while user is inside the chapter menu
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<field id="selectPlaybackInfoPressed" type="boolean" alwaysNotify="true" />
|
||||
<field id="PlaySessionId" type="string" />
|
||||
<field id="Subtitles" type="array" />
|
||||
<field id="SelectedSubtitle" type="integer" value="-1" alwaysNotify="true" />
|
||||
<field id="SelectedSubtitle" type="integer" value="-2" alwaysNotify="true" />
|
||||
<field id="container" type="string" />
|
||||
<field id="directPlaySupported" type="boolean" />
|
||||
<field id="systemOverlay" type="boolean" value="false" />
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user