mirror of
https://github.com/jellyfin/jellyfin-roku.git
synced 2024-11-23 06:09:41 +00:00
Merge branch 'master' into expand-homerows
This commit is contained in:
commit
aa5210f758
6
.github/workflows/build-dev.yml
vendored
6
.github/workflows/build-dev.yml
vendored
@ -10,8 +10,8 @@ jobs:
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "npm"
|
||||
@ -21,7 +21,7 @@ jobs:
|
||||
run: npm run ropm
|
||||
- name: Build app
|
||||
run: npm run build
|
||||
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # 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
@ -13,7 +13,7 @@ jobs:
|
||||
# Give the default GITHUB_TOKEN write permission to commit and push the changed files back to the repository.
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
6
.github/workflows/build-prod.yml
vendored
6
.github/workflows/build-prod.yml
vendored
@ -12,8 +12,8 @@ jobs:
|
||||
if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'release-prep') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "npm"
|
||||
@ -23,7 +23,7 @@ jobs:
|
||||
run: npm run ropm
|
||||
- name: Build app for production
|
||||
run: npm run build-prod
|
||||
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
with:
|
||||
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/build/staging
|
||||
|
12
.github/workflows/bump-version.yml
vendored
12
.github/workflows/bump-version.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Install required packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
@ -50,7 +50,7 @@ jobs:
|
||||
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
|
||||
- name: Checkout bugfix branch
|
||||
if: github.event.inputs.targetBranch == 'bugfix'
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
ref: ${{ env.targetBranch }}
|
||||
# Save old version again if needed
|
||||
@ -101,7 +101,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Install jq to update json
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
@ -125,7 +125,7 @@ jobs:
|
||||
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
|
||||
- name: Checkout bugfix branch
|
||||
if: github.event.inputs.targetBranch == 'bugfix'
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
ref: ${{ env.targetBranch }}
|
||||
# Calculate new version
|
||||
@ -169,7 +169,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Install jq to update json
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
@ -193,7 +193,7 @@ jobs:
|
||||
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
|
||||
- name: Checkout bugfix branch
|
||||
if: github.event.inputs.targetBranch == 'bugfix'
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
ref: ${{ env.targetBranch }}
|
||||
# Calculate new version
|
||||
|
2
.github/workflows/deploy-api-docs.yml
vendored
2
.github/workflows/deploy-api-docs.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
|
||||
- name: Upload artifact
|
||||
|
6
.github/workflows/roku-analysis.yml
vendored
6
.github/workflows/roku-analysis.yml
vendored
@ -12,8 +12,8 @@ jobs:
|
||||
if: github.repository == 'jellyfin/jellyfin-roku' && github.event_name != 'pull_request' || github.repository == 'jellyfin/jellyfin-roku' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "npm"
|
||||
@ -28,7 +28,7 @@ jobs:
|
||||
if: env.BRANCH_NAME == 'master'
|
||||
run: npm run build-prod
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 # v4
|
||||
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "21"
|
||||
|
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@ -2,9 +2,9 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Build and Deploy",
|
||||
"type": "brightscript",
|
||||
"request": "launch",
|
||||
"name": "Jellyfin Debug",
|
||||
"rootDir": "${workspaceFolder}/build/staging",
|
||||
"preLaunchTask": "build-dev",
|
||||
"stopOnEntry": false,
|
||||
@ -18,6 +18,22 @@
|
||||
//WARNING: don't edit this value. Instead, set "brightscript.debug.password": "YOUR_PASSWORD_HERE" in your vscode user settings
|
||||
//"password": "${promptForPassword}",
|
||||
},
|
||||
{
|
||||
"name": "Deploy",
|
||||
"type": "brightscript",
|
||||
"request": "launch",
|
||||
"rootDir": "${workspaceFolder}/build/staging",
|
||||
"stopOnEntry": false,
|
||||
// To enable RALE:
|
||||
// set "brightscript.debug.raleTrackerTaskFileLocation": "/absolute/path/to/rale/TrackerTask.xml" in your vscode user settings
|
||||
// set the below field to true
|
||||
"injectRaleTrackerTask": false,
|
||||
"injectRdbOnDeviceComponent": true,
|
||||
//WARNING: don't edit this value. Instead, set "brightscript.debug.host": "YOUR_HOST_HERE" in your vscode user settings
|
||||
//"host": "${promptForHost}",
|
||||
//WARNING: don't edit this value. Instead, set "brightscript.debug.password": "YOUR_PASSWORD_HERE" in your vscode user settings
|
||||
//"password": "${promptForPassword}",
|
||||
},
|
||||
{
|
||||
"name": "Run tests",
|
||||
"type": "brightscript",
|
||||
|
2
Makefile
2
Makefile
@ -3,7 +3,7 @@
|
||||
# If you want to get_images, you'll also need convert from ImageMagick
|
||||
##########################################################################
|
||||
|
||||
VERSION := 2.1.4
|
||||
VERSION := 2.2.2
|
||||
|
||||
## usage
|
||||
|
||||
|
@ -20,9 +20,6 @@ sub init()
|
||||
m.checkmark.width = 90
|
||||
m.checkmark.height = 60
|
||||
|
||||
m.itemText.translation = [0, m.itemPoster.height + 7]
|
||||
m.itemText.visible = m.gridTitles = "showalways"
|
||||
|
||||
' Add some padding space when Item Titles are always showing
|
||||
if m.itemText.visible then m.itemText.maxWidth = 250
|
||||
|
||||
@ -38,14 +35,17 @@ sub init()
|
||||
end if
|
||||
end if
|
||||
|
||||
m.itemText.translation = [0, m.itemPoster.height + 7]
|
||||
m.itemText.visible = m.gridTitles = "showalways"
|
||||
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
m.backdrop.blendColor = "#00a4db" ' set default in case global var is invalid
|
||||
localGlobal = m.global
|
||||
myGlobal = m.global
|
||||
|
||||
if isValid(localGlobal) and isValid(localGlobal.constants) and isValid(localGlobal.constants.poster_bg_pallet)
|
||||
posterBackgrounds = localGlobal.constants.poster_bg_pallet
|
||||
if isValid(myGlobal) and isValid(myGlobal.constants) and isValid(myGlobal.constants.poster_bg_pallet)
|
||||
posterBackgrounds = myGlobal.constants.poster_bg_pallet
|
||||
m.backdrop.blendColor = posterBackgrounds[rnd(posterBackgrounds.count()) - 1]
|
||||
end if
|
||||
|
||||
@ -62,8 +62,8 @@ sub itemContentChanged()
|
||||
m.itemIcon.uri = itemData.iconUrl
|
||||
m.itemText.text = itemData.Title
|
||||
else if itemData.type = "Series"
|
||||
if isValid(localGlobal) and isValid(localGlobal.session) and isValid(localGlobal.session.user) and isValid(localGlobal.session.user.settings)
|
||||
if localGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false
|
||||
if isValid(myGlobal) and isValid(myGlobal.session) and isValid(myGlobal.session.user) and isValid(myGlobal.session.user.settings)
|
||||
if myGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false
|
||||
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
|
||||
if itemData.json.UserData.UnplayedItemCount > 0
|
||||
m.unplayedCount.visible = true
|
||||
@ -116,7 +116,7 @@ sub itemContentChanged()
|
||||
m.itemText.text = itemData.Title
|
||||
end if
|
||||
' Adjust to wide posters for "View All Next Up"
|
||||
if m.topParent.overhangTitle = tr("View All Next Up")
|
||||
if m.itemGrid.overhangTitle = tr("View All Next Up")
|
||||
m.posterMask.maskUri = ""
|
||||
|
||||
m.itemPoster.height = 300
|
||||
|
@ -4,7 +4,7 @@ import "pkg:/source/utils/config.bs"
|
||||
sub init()
|
||||
m.itemPoster = m.top.findNode("itemPoster")
|
||||
m.posterText = m.top.findNode("posterText")
|
||||
m.title = m.top.findNode("title")
|
||||
initTitle()
|
||||
m.posterText.font.size = 30
|
||||
m.title.font.size = 25
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
@ -23,6 +23,10 @@ sub init()
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub initTitle()
|
||||
m.title = m.top.findNode("title")
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
m.backdrop.blendColor = "#101010"
|
||||
|
||||
@ -54,6 +58,8 @@ sub itemContentChanged()
|
||||
end sub
|
||||
|
||||
sub focusChanged()
|
||||
if not isValid(m.title) then initTitle()
|
||||
|
||||
if m.top.itemHasFocus = true
|
||||
m.title.repeatCount = -1
|
||||
else
|
||||
|
@ -6,9 +6,12 @@ import "pkg:/source/roku_modules/log/LogMixin.brs"
|
||||
|
||||
sub init()
|
||||
m.log = log.Logger("ItemGrid")
|
||||
m.log.debug("start init()")
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
m.options = m.top.findNode("options")
|
||||
|
||||
m.showItemCount = m.global.session.user.settings["itemgrid.showItemCount"]
|
||||
m.showItemCount = userSettings["itemgrid.showItemCount"]
|
||||
|
||||
m.tvGuide = invalid
|
||||
m.channelFocused = invalid
|
||||
@ -67,9 +70,10 @@ sub init()
|
||||
m.alphaMenu = m.alpha.findNode("alphaMenu")
|
||||
|
||||
'Get reset folder setting
|
||||
m.resetGrid = m.global.session.user.settings["itemgrid.reset"]
|
||||
m.resetGrid = userSettings["itemgrid.reset"]
|
||||
|
||||
m.top.gridTitles = m.global.session.user.settings["itemgrid.gridTitles"]
|
||||
m.top.gridTitles = userSettings["itemgrid.gridTitles"]
|
||||
m.log.debug("end init()")
|
||||
end sub
|
||||
|
||||
'Genre Item Selected
|
||||
@ -79,8 +83,10 @@ end sub
|
||||
|
||||
'Load initial set of Data
|
||||
sub loadInitialItems()
|
||||
m.log.debug("start loadInitialItems()")
|
||||
m.loadItemsTask.control = "stop"
|
||||
startLoadingSpinner()
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
if m.top.parentItem.json.Type = "CollectionFolder" 'or m.top.parentItem.json.Type = "Folder"
|
||||
m.top.HomeLibraryItem = m.top.parentItem.Id
|
||||
@ -93,25 +99,25 @@ sub loadInitialItems()
|
||||
' Read view/sort/filter settings
|
||||
if m.top.parentItem.collectionType = "livetv"
|
||||
' Translate between app and server nomenclature
|
||||
viewSetting = m.global.session.user.settings["display.livetv.landing"]
|
||||
viewSetting = userSettings["display.livetv.landing"]
|
||||
if viewSetting = "guide"
|
||||
m.view = "tvGuide"
|
||||
else
|
||||
m.view = "livetv"
|
||||
end if
|
||||
m.sortField = m.global.session.user.settings["display.livetv.sortField"]
|
||||
sortAscendingStr = m.global.session.user.settings["display.livetv.sortAscending"]
|
||||
m.filter = m.global.session.user.settings["display.livetv.filter"]
|
||||
m.sortField = userSettings["display.livetv.sortField"]
|
||||
sortAscendingStr = userSettings["display.livetv.sortAscending"]
|
||||
m.filter = userSettings["display.livetv.filter"]
|
||||
else if m.top.parentItem.collectionType = "music"
|
||||
m.view = m.global.session.user.settings["display.music.view"]
|
||||
m.sortField = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortField"]
|
||||
sortAscendingStr = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortAscending"]
|
||||
m.filter = m.global.session.user.settings["display." + m.top.parentItem.Id + ".filter"]
|
||||
m.view = userSettings["display.music.view"]
|
||||
m.sortField = userSettings["display." + m.top.parentItem.Id + ".sortField"]
|
||||
sortAscendingStr = userSettings["display." + m.top.parentItem.Id + ".sortAscending"]
|
||||
m.filter = userSettings["display." + m.top.parentItem.Id + ".filter"]
|
||||
else
|
||||
m.sortField = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortField"]
|
||||
sortAscendingStr = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortAscending"]
|
||||
m.filter = m.global.session.user.settings["display." + m.top.parentItem.Id + ".filter"]
|
||||
m.view = m.global.session.user.settings["display." + m.top.parentItem.Id + ".landing"]
|
||||
m.sortField = userSettings["display." + m.top.parentItem.Id + ".sortField"]
|
||||
sortAscendingStr = userSettings["display." + m.top.parentItem.Id + ".sortAscending"]
|
||||
m.filter = userSettings["display." + m.top.parentItem.Id + ".filter"]
|
||||
m.view = userSettings["display." + m.top.parentItem.Id + ".landing"]
|
||||
end if
|
||||
|
||||
if m.sortField = invalid
|
||||
@ -177,7 +183,7 @@ sub loadInitialItems()
|
||||
m.loadItemsTask.itemType = "MusicArtist"
|
||||
m.loadItemsTask.itemId = m.top.parentItem.Id
|
||||
|
||||
m.view = m.global.session.user.settings["display.music.view"]
|
||||
m.view = userSettings["display.music.view"]
|
||||
|
||||
if m.view = "music-album"
|
||||
m.loadItemsTask.itemType = "MusicAlbum"
|
||||
@ -188,7 +194,7 @@ sub loadInitialItems()
|
||||
' For LiveTV, we want to "Fit" the item images, not zoom
|
||||
m.top.imageDisplayMode = "scaleToFit"
|
||||
|
||||
if m.global.session.user.settings["display.livetv.landing"] = "guide" and m.options.view <> "livetv"
|
||||
if userSettings["display.livetv.landing"] = "guide" and m.options.view <> "livetv"
|
||||
showTvGuide()
|
||||
end if
|
||||
else if m.top.parentItem.collectionType = "CollectionFolder" or m.top.parentItem.type = "CollectionFolder" or m.top.parentItem.collectionType = "boxsets" or m.top.parentItem.Type = "Boxset" or m.top.parentItem.Type = "Boxsets" or m.top.parentItem.Type = "Folder" or m.top.parentItem.Type = "Channel"
|
||||
@ -235,6 +241,7 @@ sub loadInitialItems()
|
||||
startLoadingSpinner(false)
|
||||
m.loadItemsTask.control = "RUN"
|
||||
SetUpOptions()
|
||||
m.log.debug("end loadInitialItems()")
|
||||
end sub
|
||||
|
||||
' Set Movies view, sort, and filter options
|
||||
@ -295,7 +302,7 @@ sub setTvShowsOptions(options)
|
||||
{ "Title": tr("TITLE"), "Name": "SortName" },
|
||||
{ "Title": tr("IMDB_RATING"), "Name": "CommunityRating" },
|
||||
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
|
||||
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
|
||||
{ "Title": tr("DATE_PLAYED"), "Name": "SeriesDatePlayed" },
|
||||
{ "Title": tr("OFFICIAL_RATING"), "Name": "OfficialRating" },
|
||||
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
|
||||
{ "Title": tr("Random"), "Name": "Random" },
|
||||
@ -445,6 +452,7 @@ end sub
|
||||
|
||||
'Handle loaded data, and add to Grid
|
||||
sub ItemDataLoaded(msg)
|
||||
m.log.debug("start ItemDataLoaded()")
|
||||
itemData = msg.GetData()
|
||||
m.loadItemsTask.unobserveField("content")
|
||||
m.loadItemsTask.content = []
|
||||
@ -501,22 +509,28 @@ sub ItemDataLoaded(msg)
|
||||
end if
|
||||
|
||||
stopLoadingSpinner()
|
||||
m.log.debug("end ItemDataLoaded()")
|
||||
end sub
|
||||
|
||||
'Set Background Image
|
||||
sub SetBackground(backgroundUri as string)
|
||||
|
||||
m.log.debug("start SetBackground()", backgroundUri, m.swapAnimation.state, m.newBackdrop.loadStatus)
|
||||
'If a new image is being loaded, or transitioned to, store URL to load next
|
||||
if m.swapAnimation.state <> "stopped" or m.newBackdrop.loadStatus = "loading"
|
||||
m.queuedBGUri = backgroundUri
|
||||
return
|
||||
if not m.top.alphaActive
|
||||
if m.swapAnimation.state <> "stopped" or m.newBackdrop.loadStatus = "loading"
|
||||
m.log.debug("caching new background URI")
|
||||
m.queuedBGUri = backgroundUri
|
||||
return
|
||||
end if
|
||||
end if
|
||||
|
||||
m.newBackdrop.uri = backgroundUri
|
||||
m.log.debug("end SetBackground()")
|
||||
end sub
|
||||
|
||||
'Handle new item being focused
|
||||
sub onItemFocused()
|
||||
m.log.debug("start onItemFocused()", m.itemGrid.currFocusRow, m.itemGrid.itemFocused)
|
||||
|
||||
focusedRow = m.itemGrid.currFocusRow
|
||||
|
||||
@ -525,7 +539,7 @@ sub onItemFocused()
|
||||
updateTitle()
|
||||
|
||||
' If no selected item, set background to parent backdrop
|
||||
if itemInt = -1
|
||||
if itemInt = -1 or focusedRow = -1
|
||||
return
|
||||
end if
|
||||
|
||||
@ -538,6 +552,7 @@ sub onItemFocused()
|
||||
if focusedRow >= m.loadedRows - 5 and m.loadeditems < m.loadItemsTask.totalRecordCount
|
||||
loadMoreData()
|
||||
end if
|
||||
m.log.debug("end onItemFocused()")
|
||||
end sub
|
||||
|
||||
'When Image Loading Status changes
|
||||
@ -558,6 +573,7 @@ sub swapDone()
|
||||
|
||||
'If there is another one to load
|
||||
if m.newBackdrop.uri <> m.queuedBGUri and m.queuedBGUri <> ""
|
||||
m.log.debug("Loading queued backdrop image", m.queuedBGUri)
|
||||
SetBackground(m.queuedBGUri)
|
||||
m.queuedBGUri = ""
|
||||
end if
|
||||
@ -566,6 +582,7 @@ end sub
|
||||
|
||||
'Load next set of items
|
||||
sub loadMoreData()
|
||||
m.log.debug("start loadMoreData()")
|
||||
if m.Loading = true then return
|
||||
|
||||
startLoadingSpinner(false)
|
||||
@ -573,6 +590,7 @@ sub loadMoreData()
|
||||
m.loadItemsTask.startIndex = m.loadedItems
|
||||
m.loadItemsTask.observeField("content", "ItemDataLoaded")
|
||||
m.loadItemsTask.control = "RUN"
|
||||
m.log.debug("end loadMoreData()")
|
||||
end sub
|
||||
|
||||
'Item Selected
|
||||
@ -767,6 +785,20 @@ function getItemFocused()
|
||||
return invalid
|
||||
end function
|
||||
|
||||
sub alphaActiveChanged()
|
||||
m.log.debug("start alphaActiveChanged()", m.top.alphaActive)
|
||||
|
||||
if m.top.alphaActive
|
||||
' fade into an empty backdrop
|
||||
m.swapAnimation.state = "stop"
|
||||
m.queuedBGUri = ""
|
||||
' use a 1px image because we can't use the animation to fade into a blank uri string
|
||||
SetBackground("pkg:/images/1px-262626.png")
|
||||
end if
|
||||
|
||||
m.log.debug("end alphaActiveChanged()")
|
||||
end sub
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
if not press then return false
|
||||
|
||||
@ -833,11 +865,14 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
return true
|
||||
end if
|
||||
else if key = "left" and topGrp.isinFocusChain() and m.alpha.visible
|
||||
m.log.debug("Now entering alpha menu")
|
||||
m.top.alphaActive = true
|
||||
topGrp.setFocus(false)
|
||||
m.alphaMenu.setFocus(true)
|
||||
|
||||
return true
|
||||
else if key = "right" and m.alpha.isinFocusChain()
|
||||
m.log.debug("Now leaving alpha menu")
|
||||
m.top.alphaActive = false
|
||||
m.alphaMenu.setFocus(false)
|
||||
topGrp.setFocus(true)
|
||||
|
@ -33,7 +33,7 @@
|
||||
<field id="quickPlayNode" type="node" alwaysNotify="true" />
|
||||
<field id="imageDisplayMode" type="string" value="scaleToZoom" />
|
||||
<field id="alphaSelected" type="string" alias="alpha.letterSelected" alwaysNotify="true" onChange="alphaSelectedChanged" />
|
||||
<field id="alphaActive" type="boolean" value="false" />
|
||||
<field id="alphaActive" type="boolean" value="false" onChange="alphaActiveChanged" />
|
||||
<field id="jumpToItem" type="integer" value="" />
|
||||
<field id="gridTitles" type="string" />
|
||||
</interface>
|
||||
|
@ -6,6 +6,7 @@ import "pkg:/source/utils/config.bs"
|
||||
import "pkg:/source/api/Image.bs"
|
||||
import "pkg:/source/api/userauth.bs"
|
||||
import "pkg:/source/utils/deviceCapabilities.bs"
|
||||
import "pkg:/source/utils/session.bs"
|
||||
|
||||
enum SubtitleSelection
|
||||
notset = -2
|
||||
@ -17,21 +18,23 @@ sub init()
|
||||
end sub
|
||||
|
||||
sub loadItems()
|
||||
queueManager = m.global.queueManager
|
||||
|
||||
' Reset intro tracker in case task gets reused
|
||||
m.top.isIntro = false
|
||||
|
||||
' Only show preroll once per queue
|
||||
if m.global.queueManager.callFunc("isPrerollActive")
|
||||
if queueManager.callFunc("isPrerollActive")
|
||||
' Prerolls not allowed if we're resuming video
|
||||
if m.global.queueManager.callFunc("getCurrentItem").startingPoint = 0
|
||||
if queueManager.callFunc("getCurrentItem").startingPoint = 0
|
||||
preRoll = GetIntroVideos(m.top.itemId)
|
||||
if isValid(preRoll) and preRoll.TotalRecordCount > 0 and isValid(preRoll.items[0])
|
||||
' If an error is thrown in the Intros plugin, instead of passing the error they pass the entire rick roll music video.
|
||||
' Bypass the music video and treat it as an error message
|
||||
if lcase(preRoll.items[0].name) <> "rick roll'd"
|
||||
m.global.queueManager.callFunc("push", m.global.queueManager.callFunc("getCurrentItem"))
|
||||
queueManager.callFunc("push", queueManager.callFunc("getCurrentItem"))
|
||||
m.top.itemId = preRoll.items[0].id
|
||||
m.global.queueManager.callFunc("setPrerollStatus", false)
|
||||
queueManager.callFunc("setPrerollStatus", false)
|
||||
m.top.isIntro = true
|
||||
end if
|
||||
end if
|
||||
@ -39,7 +42,7 @@ sub loadItems()
|
||||
end if
|
||||
|
||||
if m.top.selectedAudioStreamIndex = 0
|
||||
currentItem = m.global.queueManager.callFunc("getCurrentItem")
|
||||
currentItem = queueManager.callFunc("getCurrentItem")
|
||||
if isValid(currentItem) and isValid(currentItem.json)
|
||||
m.top.selectedAudioStreamIndex = FindPreferredAudioStream(currentItem.json.MediaStreams)
|
||||
end if
|
||||
@ -71,14 +74,30 @@ end function
|
||||
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"
|
||||
video.content = invalid
|
||||
return
|
||||
end if
|
||||
|
||||
queueManager = m.global.queueManager
|
||||
userSession = m.global.session.user
|
||||
userSettings = userSession.settings
|
||||
|
||||
session.video.Update(meta)
|
||||
|
||||
if isValid(meta.json.MediaSources[0].RunTimeTicks)
|
||||
if meta.json.MediaSources[0].RunTimeTicks = 0
|
||||
video.length = 0
|
||||
else
|
||||
video.length = meta.json.MediaSources[0].RunTimeTicks / 10000000
|
||||
end if
|
||||
end if
|
||||
if isValid(meta.json.MediaSources[0]) and isValid(meta.json.MediaSources[0].MediaStreams[0])
|
||||
video.MaxVideoDecodeResolution = [meta.json.MediaSources[0].MediaStreams[0].Width, meta.json.MediaSources[0].MediaStreams[0].Height]
|
||||
end if
|
||||
|
||||
subtitle_idx = m.top.selectedSubtitleIndex
|
||||
videotype = LCase(meta.type)
|
||||
|
||||
' Check for any Live TV streams or Recordings coming from other places other than the TV Guide
|
||||
@ -118,15 +137,15 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
||||
video.logoImage = api.items.GetImageURL(logoLookupID, "logo", 0, { "maxHeight": 65, "maxWidth": 300, "quality": "90" })
|
||||
end if
|
||||
|
||||
if m.global.session.user.Configuration.EnableNextEpisodeAutoPlay
|
||||
if LCase(m.top.itemType) = "episode"
|
||||
if LCase(m.top.itemType) = "episode"
|
||||
if userSettings["playback.playnextepisode"] = "enabled" or userSettings["playback.playnextepisode"] = "webclient" and userSession.Configuration.EnableNextEpisodeAutoPlay
|
||||
addNextEpisodesToQueue(video.showID)
|
||||
end if
|
||||
end if
|
||||
|
||||
playbackPosition = 0!
|
||||
|
||||
currentItem = m.global.queueManager.callFunc("getCurrentItem")
|
||||
currentItem = queueManager.callFunc("getCurrentItem")
|
||||
|
||||
if isValid(currentItem) and isValid(currentItem.startingPoint)
|
||||
playbackPosition = currentItem.startingPoint
|
||||
@ -196,20 +215,18 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
||||
}
|
||||
end if
|
||||
|
||||
|
||||
' 'TODO: allow user selection of subtitle track before playback initiated, for now set to no subtitles
|
||||
video.directPlaySupported = m.playbackInfo.MediaSources[0].SupportsDirectPlay
|
||||
fully_external = false
|
||||
|
||||
|
||||
' For h264/hevc video, Roku spec states that it supports specfic encoding levels
|
||||
' The device can decode content with a Higher Encoding level but may play it back with certain
|
||||
' artifacts. If the user preference is set, and the only reason the server says we need to
|
||||
' transcode is that the Encoding Level is not supported, then try to direct play but silently
|
||||
' fall back to the transcode if that fails.
|
||||
if m.playbackInfo.MediaSources[0].MediaStreams.Count() > 0 and meta.live = false
|
||||
tryDirectPlay = m.global.session.user.settings["playback.tryDirect.h264ProfileLevel"] and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
|
||||
tryDirectPlay = tryDirectPlay or (m.global.session.user.settings["playback.tryDirect.hevcProfileLevel"] and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc")
|
||||
tryDirectPlay = userSettings["playback.tryDirect.h264ProfileLevel"] and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
|
||||
tryDirectPlay = tryDirectPlay or (userSettings["playback.tryDirect.hevcProfileLevel"] and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc")
|
||||
if tryDirectPlay and isValid(m.playbackInfo.MediaSources[0].TranscodingUrl) and forceTranscoding = false
|
||||
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
|
||||
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
|
||||
@ -249,7 +266,8 @@ end sub
|
||||
' @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"
|
||||
userSession = m.global.session.user
|
||||
if userSession.configuration.SubtitleMode = "None"
|
||||
return SubtitleSelection.none ' No subtitles desired: return none
|
||||
end if
|
||||
|
||||
@ -275,7 +293,7 @@ function defaultSubtitleTrackFromVid(videoID) as integer
|
||||
return defaultTextSubs
|
||||
end if
|
||||
|
||||
if not m.global.session.user.settings["playback.subs.onlytext"]
|
||||
if not userSession.settings["playback.subs.onlytext"]
|
||||
return defaultSubtitleTrack(subtitles["all"], selectedAudioLanguage) ' if no appropriate text subs exist, allow non-text
|
||||
end if
|
||||
|
||||
@ -350,11 +368,15 @@ sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
|
||||
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
|
||||
if protocol <> "file"
|
||||
uri = parseUrl(m.playbackInfo.MediaSources[0].Path)
|
||||
if isLocalhost(uri[2])
|
||||
if not isValidAndNotEmpty(uri) then return
|
||||
|
||||
if isValid(uri[2]) and isLocalhost(uri[2])
|
||||
' if the domain of the URI is local to the server,
|
||||
' create a new URI by appending the received path to the server URL
|
||||
' later we will substitute the users provided URL for this case
|
||||
video.content.url = buildURL(uri[4])
|
||||
if isValid(uri[4])
|
||||
video.content.url = buildURL(uri[4])
|
||||
end if
|
||||
else
|
||||
fully_external = true
|
||||
video.content.url = m.playbackInfo.MediaSources[0].Path
|
||||
@ -475,6 +497,8 @@ end function
|
||||
|
||||
' Add next episodes to the playback queue
|
||||
sub addNextEpisodesToQueue(showID)
|
||||
queueManager = m.global.queueManager
|
||||
|
||||
' Don't queue next episodes if we already have a playback queue
|
||||
maxQueueCount = 1
|
||||
|
||||
@ -482,13 +506,13 @@ sub addNextEpisodesToQueue(showID)
|
||||
maxQueueCount = 2
|
||||
end if
|
||||
|
||||
if m.global.queueManager.callFunc("getCount") > maxQueueCount then return
|
||||
if queueManager.callFunc("getCount") > maxQueueCount then return
|
||||
|
||||
videoID = m.top.itemId
|
||||
|
||||
' If first item is an intro video, use the next item in the queue
|
||||
if m.top.isIntro
|
||||
currentVideo = m.global.queueManager.callFunc("getItemByIndex", 1)
|
||||
currentVideo = queueManager.callFunc("getItemByIndex", 1)
|
||||
|
||||
if isValid(currentVideo) and isValid(currentVideo.id)
|
||||
videoID = currentVideo.id
|
||||
@ -510,7 +534,7 @@ sub addNextEpisodesToQueue(showID)
|
||||
|
||||
if isValid(data) and data.Items.Count() > 1
|
||||
for i = 1 to data.Items.Count() - 1
|
||||
m.global.queueManager.callFunc("push", data.Items[i])
|
||||
queueManager.callFunc("push", data.Items[i])
|
||||
end for
|
||||
end if
|
||||
end sub
|
||||
@ -569,8 +593,9 @@ function sortSubtitles(id as string, MediaStreams)
|
||||
end function
|
||||
|
||||
function FindPreferredAudioStream(streams as dynamic) as integer
|
||||
preferredLanguage = m.global.session.user.Configuration.AudioLanguagePreference
|
||||
playDefault = m.global.session.user.Configuration.PlayDefaultAudioTrack
|
||||
userConfig = m.global.session.user.configuration
|
||||
preferredLanguage = userConfig.AudioLanguagePreference
|
||||
playDefault = userConfig.PlayDefaultAudioTrack
|
||||
|
||||
if playDefault <> invalid and playDefault = true
|
||||
return 1
|
||||
|
@ -30,6 +30,7 @@ end sub
|
||||
|
||||
sub init()
|
||||
setupNodes()
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
m.overhang.isVisible = false
|
||||
|
||||
@ -39,7 +40,7 @@ sub init()
|
||||
alphaMicText = m.alpha.findNode("alphaMicText")
|
||||
alphaMicText.visible = false
|
||||
|
||||
m.showItemCount = m.global.session.user.settings["itemgrid.showItemCount"]
|
||||
m.showItemCount = userSettings["itemgrid.showItemCount"]
|
||||
|
||||
m.swapAnimation.observeField("state", "swapDone")
|
||||
|
||||
@ -86,7 +87,7 @@ sub init()
|
||||
m.loadItemsTask.totalRecordCount = 0
|
||||
|
||||
'Get reset folder setting
|
||||
m.resetGrid = m.global.session.user.settings["itemgrid.reset"]
|
||||
m.resetGrid = userSettings["itemgrid.reset"]
|
||||
end sub
|
||||
|
||||
sub OnScreenHidden()
|
||||
@ -112,6 +113,7 @@ end sub
|
||||
sub loadInitialItems()
|
||||
m.loadItemsTask.control = "stop"
|
||||
startLoadingSpinner(false)
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
if m.top.parentItem.json.Type = "CollectionFolder"
|
||||
m.top.HomeLibraryItem = m.top.parentItem.Id
|
||||
@ -123,15 +125,15 @@ sub loadInitialItems()
|
||||
SetBackground("")
|
||||
end if
|
||||
|
||||
m.sortField = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortField"]
|
||||
m.filter = m.global.session.user.settings["display." + m.top.parentItem.Id + ".filter"]
|
||||
m.filterOptions = m.global.session.user.settings["display." + m.top.parentItem.Id + ".filterOptions"]
|
||||
m.view = m.global.session.user.settings["display." + m.top.parentItem.Id + ".landing"]
|
||||
m.sortAscending = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortAscending"]
|
||||
m.sortField = userSettings["display." + m.top.parentItem.Id + ".sortField"]
|
||||
m.filter = userSettings["display." + m.top.parentItem.Id + ".filter"]
|
||||
m.filterOptions = userSettings["display." + m.top.parentItem.Id + ".filterOptions"]
|
||||
m.view = userSettings["display." + m.top.parentItem.Id + ".landing"]
|
||||
m.sortAscending = userSettings["display." + m.top.parentItem.Id + ".sortAscending"]
|
||||
|
||||
' If user has not set a preferred view for this folder, check if they've set a default view
|
||||
if not isValid(m.view)
|
||||
m.view = m.global.session.user.settings["itemgrid.movieDefaultView"]
|
||||
m.view = userSettings["itemgrid.movieDefaultView"]
|
||||
end if
|
||||
|
||||
if not isValid(m.sortField) then m.sortField = "SortName"
|
||||
@ -200,7 +202,7 @@ sub loadInitialItems()
|
||||
m.itemGrid.numRows = "3"
|
||||
m.selectedMovieOverview.visible = false
|
||||
m.infoGroup.visible = false
|
||||
m.top.showItemTitles = m.global.session.user.settings["itemgrid.gridTitles"]
|
||||
m.top.showItemTitles = userSettings["itemgrid.gridTitles"]
|
||||
if LCase(m.top.showItemTitles) = "hidealways"
|
||||
m.itemGrid.itemSize = "[230, 315]"
|
||||
m.itemGrid.rowHeights = "[315]"
|
||||
|
@ -18,10 +18,8 @@ sub init()
|
||||
m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode
|
||||
end if
|
||||
|
||||
m.gridTitles = m.global.session.user.settings["itemgrid.gridTitles"]
|
||||
m.posterText.visible = false
|
||||
m.postTextBackground.visible = false
|
||||
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
|
@ -25,6 +25,7 @@ end sub
|
||||
|
||||
sub init()
|
||||
setupNodes()
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
m.overhang.isVisible = false
|
||||
|
||||
@ -34,7 +35,7 @@ sub init()
|
||||
alphaMicText = m.alpha.findNode("alphaMicText")
|
||||
alphaMicText.visible = false
|
||||
|
||||
m.showItemCount = m.global.session.user.settings["itemgrid.showItemCount"]
|
||||
m.showItemCount = userSettings["itemgrid.showItemCount"]
|
||||
|
||||
m.swapAnimation.observeField("state", "swapDone")
|
||||
|
||||
@ -80,7 +81,7 @@ sub init()
|
||||
m.loadItemsTask.totalRecordCount = 0
|
||||
|
||||
'Get reset folder setting
|
||||
m.resetGrid = m.global.session.user.settings["itemgrid.reset"]
|
||||
m.resetGrid = userSettings["itemgrid.reset"]
|
||||
end sub
|
||||
|
||||
sub OnScreenHidden()
|
||||
@ -106,6 +107,7 @@ end sub
|
||||
sub loadInitialItems()
|
||||
m.loadItemsTask.control = "stop"
|
||||
startLoadingSpinner(false)
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
if LCase(m.top.parentItem.json.Type) = "collectionfolder"
|
||||
m.top.HomeLibraryItem = m.top.parentItem.Id
|
||||
@ -117,17 +119,17 @@ sub loadInitialItems()
|
||||
SetBackground("")
|
||||
end if
|
||||
|
||||
m.sortField = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortField"]
|
||||
m.sortAscending = m.global.session.user.settings["display." + m.top.parentItem.Id + ".sortAscending"]
|
||||
m.filter = m.global.session.user.settings["display." + m.top.parentItem.Id + ".filter"]
|
||||
m.view = m.global.session.user.settings["display." + m.top.parentItem.Id + ".landing"]
|
||||
m.sortField = userSettings["display." + m.top.parentItem.Id + ".sortField"]
|
||||
m.sortAscending = userSettings["display." + m.top.parentItem.Id + ".sortAscending"]
|
||||
m.filter = userSettings["display." + m.top.parentItem.Id + ".filter"]
|
||||
m.view = userSettings["display." + m.top.parentItem.Id + ".landing"]
|
||||
|
||||
if not isValid(m.sortField) then m.sortField = "SortName"
|
||||
if not isValid(m.filter) then m.filter = "All"
|
||||
if not isValid(m.view) then m.view = "ArtistsPresentation"
|
||||
if not isValid(m.sortAscending) then m.sortAscending = true
|
||||
|
||||
m.top.showItemTitles = m.global.session.user.settings["itemgrid.gridTitles"]
|
||||
m.top.showItemTitles = userSettings["itemgrid.gridTitles"]
|
||||
|
||||
if LCase(m.top.parentItem.json.type) = "musicgenre"
|
||||
m.itemGrid.translation = "[96, 60]"
|
||||
|
@ -13,7 +13,8 @@ sub init()
|
||||
m.top.transcodeReasons = []
|
||||
m.bufferCheckTimer.duration = 30
|
||||
|
||||
if m.global.session.user.settings["ui.design.hideclock"] = true
|
||||
userSettings = m.global.session.user.settings
|
||||
if userSettings["ui.design.hideclock"]
|
||||
clockNode = findNodeBySubtype(m.top, "clock")
|
||||
if clockNode[0] <> invalid then clockNode[0].parent.removeChild(clockNode[0].node)
|
||||
end if
|
||||
@ -22,7 +23,7 @@ sub init()
|
||||
m.nextEpisodeButton = m.top.findNode("nextEpisode")
|
||||
m.nextEpisodeButton.text = tr("Next Episode")
|
||||
m.nextEpisodeButton.setFocus(false)
|
||||
m.nextupbuttonseconds = m.global.session.user.settings["playback.nextupbuttonseconds"].ToInt()
|
||||
m.nextupbuttonseconds = userSettings["playback.nextupbuttonseconds"].ToInt()
|
||||
|
||||
m.showNextEpisodeButtonAnimation = m.top.findNode("showNextEpisodeButton")
|
||||
m.hideNextEpisodeButtonAnimation = m.top.findNode("hideNextEpisodeButton")
|
||||
@ -92,12 +93,14 @@ end sub
|
||||
sub showNextEpisodeButton()
|
||||
if m.top.content.contenttype <> 4 then return ' only display when content is type "Episode"
|
||||
if m.nextupbuttonseconds = 0 then return ' is the button disabled?
|
||||
if m.nextEpisodeButton.opacity <> 0 then return
|
||||
userSession = m.global.session.user
|
||||
if userSession.settings["playback.playnextepisode"] = "disabled" then return
|
||||
if userSession.settings["playback.playnextepisode"] = "webclient" and not userSession.Configuration.EnableNextEpisodeAutoPlay then return
|
||||
|
||||
if m.nextEpisodeButton.opacity = 0 and m.global.session.user.configuration.EnableNextEpisodeAutoPlay
|
||||
m.nextEpisodeButton.visible = true
|
||||
m.showNextEpisodeButtonAnimation.control = "start"
|
||||
m.nextEpisodeButton.setFocus(true)
|
||||
end if
|
||||
m.nextEpisodeButton.visible = true
|
||||
m.showNextEpisodeButtonAnimation.control = "start"
|
||||
m.nextEpisodeButton.setFocus(true)
|
||||
end sub
|
||||
|
||||
'
|
||||
|
@ -17,10 +17,10 @@ sub init()
|
||||
|
||||
' Randomize the background colors
|
||||
backdropColor = "#00a4db" ' set default in case global var is invalid
|
||||
localGlobal = m.global
|
||||
myGlobal = m.global
|
||||
|
||||
if isValid(localGlobal) and isValid(localGlobal.constants) and isValid(localGlobal.constants.poster_bg_pallet)
|
||||
posterBackgrounds = localGlobal.constants.poster_bg_pallet
|
||||
if isValid(myGlobal) and isValid(myGlobal.constants) and isValid(myGlobal.constants.poster_bg_pallet)
|
||||
posterBackgrounds = myGlobal.constants.poster_bg_pallet
|
||||
backdropColor = posterBackgrounds[rnd(posterBackgrounds.count()) - 1]
|
||||
end if
|
||||
|
||||
@ -65,8 +65,9 @@ sub itemContentChanged() as void
|
||||
m.poster = m.top.findNode("poster")
|
||||
itemData = m.top.itemContent
|
||||
m.title.text = itemData.title
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
if m.global.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false
|
||||
if not userSettings["ui.tvshows.disableUnwatchedEpisodeCount"]
|
||||
if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
|
||||
if itemData.json.UserData.UnplayedItemCount > 0
|
||||
m.unplayedCount.visible = true
|
||||
@ -94,7 +95,7 @@ sub itemContentChanged() as void
|
||||
|
||||
imageUrl = itemData.posterURL
|
||||
|
||||
if m.global.session.user.settings["ui.tvshows.blurunwatched"] = true
|
||||
if userSettings["ui.tvshows.blurunwatched"]
|
||||
if itemData.json.lookup("Type") = "Episode" and isValid(itemData.json.userdata)
|
||||
if not itemData.json.userdata.played
|
||||
imageUrl = imageUrl + "&blur=15"
|
||||
@ -116,7 +117,7 @@ sub focusChanged()
|
||||
m.staticTitle.visible = false
|
||||
m.title.visible = true
|
||||
' text to speech for accessibility
|
||||
if m.global.device.isAudioGuideEnabled = true
|
||||
if m.global.device.isAudioGuideEnabled
|
||||
txt2Speech = CreateObject("roTextToSpeech")
|
||||
txt2Speech.Flush()
|
||||
txt2Speech.Say(m.title.text)
|
||||
|
@ -8,7 +8,7 @@ end sub
|
||||
sub PlaystateUpdate()
|
||||
if m.top.status = "start"
|
||||
url = "Sessions/Playing"
|
||||
else if m.top.status = "stop"
|
||||
else if m.top.status = "stop" or m.top.status = "finished"
|
||||
url = "Sessions/Playing/Stopped"
|
||||
else if m.top.status = "update"
|
||||
url = "Sessions/Playing/Progress"
|
||||
|
@ -1,11 +1,12 @@
|
||||
sub init()
|
||||
m.content = m.top.findNode("content")
|
||||
appVersion = m.global.app.version
|
||||
|
||||
setPalette()
|
||||
|
||||
m.top.id = "OKDialog"
|
||||
m.top.height = 900
|
||||
m.top.title = tr("Welcome to version") + " " + m.global.app.version
|
||||
m.top.title = tr("Welcome to version") + " " + appVersion
|
||||
m.top.buttons = [tr("OK")]
|
||||
|
||||
dialogStyles = {
|
||||
@ -27,7 +28,7 @@ sub init()
|
||||
|
||||
textLine = m.content.CreateChild("StdDlgMultiStyleTextItem")
|
||||
textLine.drawingStyles = dialogStyles
|
||||
textLine.text = tr("To view a complete list of changes visit") + " <url>https://github.com/jellyfin/jellyfin-roku/releases/tag/v" + m.global.app.version + "</url>"
|
||||
textLine.text = tr("To view a complete list of changes visit") + " <url>https://github.com/jellyfin/jellyfin-roku/releases/tag/v" + appVersion + "</url>"
|
||||
end sub
|
||||
|
||||
sub setPalette()
|
||||
|
@ -1,7 +1,9 @@
|
||||
import "pkg:/source/utils/config.bs"
|
||||
import "pkg:/source/api/baserequest.bs"
|
||||
import "pkg:/source/roku_modules/log/LogMixin.brs"
|
||||
|
||||
sub init()
|
||||
m.log = log.Logger("captionTask")
|
||||
m.top.observeField("url", "fetchCaption")
|
||||
m.top.currentCaption = []
|
||||
m.top.currentPos = 0
|
||||
@ -41,17 +43,26 @@ sub setFont()
|
||||
end sub
|
||||
|
||||
sub fetchCaption()
|
||||
m.log.debug("start fetchCaption()")
|
||||
m.captionTimer.control = "stop"
|
||||
re = CreateObject("roRegex", "(http.*?\.vtt)", "s")
|
||||
url = re.match(m.top.url)[0]
|
||||
|
||||
if url <> invalid
|
||||
port = createObject("roMessagePort")
|
||||
m.reader.setUrl(url)
|
||||
text = m.reader.GetToString()
|
||||
m.captionList = parseVTT(text)
|
||||
m.captionTimer.control = "start"
|
||||
m.reader.setMessagePort(port)
|
||||
if m.reader.AsyncGetToString()
|
||||
msg = port.waitMessage(0)
|
||||
if type(msg) = "roUrlEvent"
|
||||
m.captionList = parseVTT(msg.GetString())
|
||||
m.captionTimer.control = "start"
|
||||
end if
|
||||
end if
|
||||
else
|
||||
m.captionTimer.control = "stop"
|
||||
end if
|
||||
m.log.debug("end fetchCaption()", url)
|
||||
end sub
|
||||
|
||||
function newlabel(txt)
|
||||
|
@ -5,7 +5,11 @@ import "pkg:/source/utils/config.bs"
|
||||
sub setFields()
|
||||
json = m.top.json
|
||||
m.top.id = json.id
|
||||
m.top.title = json.name
|
||||
if isValid(json.number)
|
||||
m.top.title = `${tr("CH")} ${json.number} ${json.name}`
|
||||
else
|
||||
m.top.title = json.name
|
||||
end if
|
||||
m.top.live = true
|
||||
m.top.Type = "TvChannel"
|
||||
setPoster()
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component name="ChannelData" extends="JFContentItem">
|
||||
<interface>
|
||||
<field id="image" type="node" onChange="setPoster" />
|
||||
<field id="channelID" type="string" />
|
||||
<field id="selectedAudioStreamIndex" type="integer" value="0" />
|
||||
</interface>
|
||||
|
@ -60,6 +60,7 @@ sub setData()
|
||||
end if
|
||||
|
||||
else if datum.type = "Series"
|
||||
m.top.isWatched = datum.UserData.Played
|
||||
imgParams = { "maxHeight": 261 }
|
||||
imgParams.Append({ "maxWidth": 464 })
|
||||
|
||||
|
@ -4,9 +4,12 @@ import "pkg:/source/utils/config.bs"
|
||||
|
||||
sub setFields()
|
||||
json = m.top.json
|
||||
m.top.Type = "Person"
|
||||
|
||||
if json = invalid then return
|
||||
|
||||
m.top.id = json.id
|
||||
m.top.favorite = json.UserData.isFavorite
|
||||
m.top.Type = "Person"
|
||||
setPoster()
|
||||
end sub
|
||||
|
||||
|
@ -35,6 +35,7 @@ sub updateSize()
|
||||
end sub
|
||||
|
||||
sub loadParts(data as object)
|
||||
m.extrasGrp = m.top.getParent().findNode("extrasGrp")
|
||||
m.top.parentId = data.id
|
||||
m.people = data.People
|
||||
m.LoadAdditionalPartsTask.itemId = m.top.parentId
|
||||
@ -42,6 +43,7 @@ sub loadParts(data as object)
|
||||
end sub
|
||||
|
||||
sub loadPersonVideos(personId)
|
||||
m.extrasGrp = m.top.getParent().findNode("extrasGrp")
|
||||
m.personId = personId
|
||||
m.LoadMoviesTask.itemId = m.personId
|
||||
m.LoadMoviesTask.observeField("content", "onMoviesLoaded")
|
||||
@ -114,7 +116,7 @@ sub onLikeThisLoaded()
|
||||
m.SpecialFeaturesTask.control = "RUN"
|
||||
end sub
|
||||
|
||||
function onSpecialFeaturesLoaded()
|
||||
sub onSpecialFeaturesLoaded()
|
||||
data = m.SpecialFeaturesTask.content
|
||||
m.SpecialFeaturesTask.unobserveField("content")
|
||||
if data <> invalid and data.count() > 0
|
||||
@ -132,8 +134,8 @@ function onSpecialFeaturesLoaded()
|
||||
addRowSize([462, 372])
|
||||
end if
|
||||
|
||||
return m.top.content
|
||||
end function
|
||||
showOrHideMe()
|
||||
end sub
|
||||
|
||||
sub onMoviesLoaded()
|
||||
data = m.LoadMoviesTask.content
|
||||
@ -179,6 +181,8 @@ sub onSeriesLoaded()
|
||||
m.top.content.appendChild(row)
|
||||
end if
|
||||
m.top.visible = true
|
||||
|
||||
showOrHideMe()
|
||||
end sub
|
||||
|
||||
function buildRow(rowTitle as string, items, imgWdth = 0)
|
||||
@ -218,6 +222,17 @@ sub addRowSize(newRow)
|
||||
m.top.rowItemSize = newSizeArray
|
||||
end sub
|
||||
|
||||
' don't show popup panel if there is nothing to show
|
||||
sub showOrHideMe()
|
||||
if isValid(m.top.content)
|
||||
if m.top.content.getChildCount() = 0
|
||||
m.extrasGrp.visible = false
|
||||
else
|
||||
m.extrasGrp.visible = true
|
||||
end if
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub onRowItemSelected()
|
||||
m.top.selectedItem = m.top.content.getChild(m.top.rowItemSelected[0]).getChild(m.top.rowItemSelected[1])
|
||||
end sub
|
||||
|
@ -10,22 +10,22 @@ sub init()
|
||||
initItemPoster()
|
||||
m.itemProgress = m.top.findNode("progress")
|
||||
m.itemProgressBackground = m.top.findNode("progressBackground")
|
||||
m.itemIcon = m.top.findNode("itemIcon")
|
||||
initItemIcon()
|
||||
initItemTextExtra()
|
||||
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
|
||||
m.unplayedCount = m.top.findNode("unplayedCount")
|
||||
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
|
||||
m.playedIndicator = m.top.findNode("playedIndicator")
|
||||
initPlayedIndicator()
|
||||
|
||||
m.showProgressBarAnimation = m.top.findNode("showProgressBar")
|
||||
m.showProgressBarField = m.top.findNode("showProgressBarField")
|
||||
|
||||
' Randomize the background colors
|
||||
backdropColor = "#00a4db" ' set default in case global var is invalid
|
||||
localGlobal = m.global
|
||||
myGlobal = m.global
|
||||
|
||||
if isValid(localGlobal) and isValid(localGlobal.constants) and isValid(localGlobal.constants.poster_bg_pallet)
|
||||
posterBackgrounds = localGlobal.constants.poster_bg_pallet
|
||||
if isValid(myGlobal) and isValid(myGlobal.constants) and isValid(myGlobal.constants.poster_bg_pallet)
|
||||
posterBackgrounds = myGlobal.constants.poster_bg_pallet
|
||||
backdropColor = posterBackgrounds[rnd(posterBackgrounds.count()) - 1]
|
||||
end if
|
||||
|
||||
@ -50,11 +50,19 @@ sub initBackdrop()
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
end sub
|
||||
|
||||
sub initItemIcon()
|
||||
m.itemIcon = m.top.findNode("itemIcon")
|
||||
end sub
|
||||
|
||||
sub initPlayedIndicator()
|
||||
m.playedIndicator = m.top.findNode("playedIndicator")
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
if isValid(m.unplayedCount) then m.unplayedCount.visible = false
|
||||
itemData = m.top.itemContent
|
||||
if itemData = invalid then return
|
||||
localGlobal = m.global
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
itemData.Title = itemData.name ' Temporarily required while we move from "HomeItem" to "JFContentItem"
|
||||
|
||||
@ -63,15 +71,17 @@ sub itemContentChanged()
|
||||
if not isValid(m.itemText) then initItemText()
|
||||
if not isValid(m.itemTextExtra) then initItemTextExtra()
|
||||
if not isValid(m.backdrop) then initBackdrop()
|
||||
if not isValid(m.itemIcon) then initItemIcon()
|
||||
if not isValid(m.playedIndicator) then initPlayedIndicator()
|
||||
|
||||
m.itemPoster.width = itemData.imageWidth
|
||||
m.itemText.maxWidth = itemData.imageWidth
|
||||
m.itemTextExtra.width = itemData.imageWidth
|
||||
m.itemTextExtra.visible = true
|
||||
m.itemTextExtra.text = ""
|
||||
|
||||
m.backdrop.width = itemData.imageWidth
|
||||
|
||||
|
||||
if isValid(itemData.iconUrl)
|
||||
m.itemIcon.uri = itemData.iconUrl
|
||||
end if
|
||||
@ -82,12 +92,15 @@ sub itemContentChanged()
|
||||
m.playedIndicator.visible = false
|
||||
|
||||
if LCase(itemData.type) = "series"
|
||||
if isValid(localGlobal) and isValid(localGlobal.session) and isValid(localGlobal.session.user) and isValid(localGlobal.session.user.settings)
|
||||
if not localGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"]
|
||||
if isValid(userSettings)
|
||||
unwatchedEpisodeCountSetting = userSettings["ui.tvshows.disableUnwatchedEpisodeCount"]
|
||||
if isValid(unwatchedEpisodeCountSetting) and not unwatchedEpisodeCountSetting
|
||||
if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
|
||||
if itemData.json.UserData.UnplayedItemCount > 0
|
||||
if isValid(m.unplayedCount) then m.unplayedCount.visible = true
|
||||
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
||||
if isValid(m.unplayedEpisodeCount)
|
||||
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
@ -151,16 +164,16 @@ sub itemContentChanged()
|
||||
drawProgressBar(itemData)
|
||||
end if
|
||||
|
||||
if localGlobal.session.user.settings["ui.general.episodeimagesnextup"] = "webclient"
|
||||
tmpSetting = localGlobal.session.user.Configuration.useEpisodeImagesInNextUpAndResume
|
||||
if userSettings["ui.general.episodeimagesnextup"] = "webclient"
|
||||
tmpSetting = m.global.session.user.Configuration.useEpisodeImagesInNextUpAndResume
|
||||
if isValid(tmpSetting) and tmpSetting
|
||||
m.itemPoster.uri = itemData.thumbnailURL
|
||||
else
|
||||
m.itemPoster.uri = itemData.widePosterURL
|
||||
end if
|
||||
else if localGlobal.session.user.settings["ui.general.episodeimagesnextup"] = "show"
|
||||
else if userSettings["ui.general.episodeimagesnextup"] = "show"
|
||||
m.itemPoster.uri = itemData.widePosterURL
|
||||
else if localGlobal.session.user.settings["ui.general.episodeimagesnextup"] = "episode"
|
||||
else if userSettings["ui.general.episodeimagesnextup"] = "episode"
|
||||
m.itemPoster.uri = itemData.thumbnailURL
|
||||
end if
|
||||
|
||||
|
@ -82,10 +82,11 @@ sub processUserSections()
|
||||
m.processedRowCount = 0
|
||||
|
||||
sessionUser = m.global.session.user
|
||||
userSettings = sessionUser.settings
|
||||
|
||||
' calculate expected row count by processing homesections
|
||||
for i = 0 to 6
|
||||
userSection = sessionUser.settings["homesection" + i.toStr()]
|
||||
userSection = userSettings["homesection" + i.toStr()]
|
||||
sectionName = userSection ?? "none"
|
||||
sectionName = LCase(sectionName)
|
||||
|
||||
@ -105,7 +106,7 @@ sub processUserSections()
|
||||
' Add home sections in order based on user settings
|
||||
loadedSections = 0
|
||||
for i = 0 to 6
|
||||
userSection = sessionUser.settings["homesection" + i.toStr()]
|
||||
userSection = userSettings["homesection" + i.toStr()]
|
||||
sectionName = userSection ?? "none"
|
||||
sectionName = LCase(sectionName)
|
||||
|
||||
@ -155,10 +156,10 @@ function getOriginalSectionIndex(sectionName as string) as integer
|
||||
sectionIndex = 0
|
||||
indexLatestMediaSection = 0
|
||||
|
||||
sessionUser = m.global.session.user
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
for i = 0 to 6
|
||||
userSection = sessionUser.settings["homesection" + i.toStr()]
|
||||
userSection = userSettings["homesection" + i.toStr()]
|
||||
settingSectionName = userSection ?? "none"
|
||||
settingSectionName = LCase(settingSectionName)
|
||||
|
||||
@ -692,6 +693,7 @@ end sub
|
||||
sub itemSelected()
|
||||
m.selectedRowItem = m.top.rowItemSelected
|
||||
|
||||
m.global.launchSource = "home"
|
||||
m.top.selectedItem = m.top.content.getChild(m.top.rowItemSelected[0]).getChild(m.top.rowItemSelected[1])
|
||||
|
||||
'Prevent the selected item event from double firing
|
||||
|
@ -60,6 +60,7 @@ sub loadItems()
|
||||
|
||||
' Load Next Up
|
||||
else if m.top.itemsToLoad = "nextUp"
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
url = "Shows/NextUp"
|
||||
params = {}
|
||||
@ -68,12 +69,13 @@ sub loadItems()
|
||||
params["SortOrder"] = "Descending"
|
||||
params["ImageTypeLimit"] = 1
|
||||
params["UserId"] = m.global.session.user.id
|
||||
params["EnableRewatching"] = m.global.session.user.settings["ui.details.enablerewatchingnextup"]
|
||||
params["EnableRewatching"] = userSettings["ui.details.enablerewatchingnextup"]
|
||||
params["DisableFirstEpisode"] = false
|
||||
params["limit"] = 24
|
||||
params["EnableTotalRecordCount"] = false
|
||||
params["EnableResumable"] = false
|
||||
|
||||
maxDaysInNextUp = m.global.session.user.settings["ui.details.maxdaysnextup"].ToInt()
|
||||
maxDaysInNextUp = userSettings["ui.details.maxdaysnextup"].ToInt()
|
||||
if isValid(maxDaysInNextUp)
|
||||
if maxDaysInNextUp > 0
|
||||
dateToday = CreateObject("roDateTime")
|
||||
|
@ -58,9 +58,9 @@ sub onSelectAudioPressed()
|
||||
|
||||
audioData.data.push(audioStreamItem)
|
||||
end for
|
||||
|
||||
m.global.sceneManager.callFunc("radioDialog", tr("Select Audio"), audioData)
|
||||
m.global.sceneManager.observeField("returnData", "onSelectionMade")
|
||||
sceneManager = m.global.sceneManager
|
||||
sceneManager.callFunc("radioDialog", tr("Select Audio"), audioData)
|
||||
sceneManager.observeField("returnData", "onSelectionMade")
|
||||
end sub
|
||||
|
||||
' User requested subtitle selection popup
|
||||
@ -110,23 +110,25 @@ sub onSelectSubtitlePressed()
|
||||
"Type": "subtitleselection"
|
||||
})
|
||||
|
||||
m.global.sceneManager.callFunc("radioDialog", tr("Select Subtitles"), subtitleData)
|
||||
m.global.sceneManager.observeField("returnData", "onSelectionMade")
|
||||
sceneManager = m.global.sceneManager
|
||||
sceneManager.callFunc("radioDialog", tr("Select Subtitles"), subtitleData)
|
||||
sceneManager.observeField("returnData", "onSelectionMade")
|
||||
end sub
|
||||
|
||||
' User has selected something from the radioDialog popup
|
||||
sub onSelectionMade()
|
||||
m.global.sceneManager.unobserveField("returnData")
|
||||
sceneManager = m.global.sceneManager
|
||||
sceneManager.unobserveField("returnData")
|
||||
|
||||
if not isValid(m.global.sceneManager.returnData) then return
|
||||
if not isValid(m.global.sceneManager.returnData.type) then return
|
||||
if not isValid(sceneManager.returnData) then return
|
||||
if not isValid(sceneManager.returnData.type) then return
|
||||
|
||||
if LCase(m.global.sceneManager.returnData.type) = "subtitleselection"
|
||||
if LCase(sceneManager.returnData.type) = "subtitleselection"
|
||||
processSubtitleSelection()
|
||||
return
|
||||
end if
|
||||
|
||||
if LCase(m.global.sceneManager.returnData.type) = "audioselection"
|
||||
if LCase(sceneManager.returnData.type) = "audioselection"
|
||||
processAudioSelection()
|
||||
return
|
||||
end if
|
||||
@ -216,21 +218,24 @@ end sub
|
||||
' Playback state change event handlers
|
||||
sub onStateChange()
|
||||
if LCase(m.view.state) = "finished"
|
||||
sceneManager = m.global.sceneManager
|
||||
queueManager = m.global.queueManager
|
||||
|
||||
' Close any open dialogs
|
||||
if m.global.sceneManager.callFunc("isDialogOpen")
|
||||
m.global.sceneManager.callFunc("dismissDialog")
|
||||
if sceneManager.callFunc("isDialogOpen")
|
||||
sceneManager.callFunc("dismissDialog")
|
||||
end if
|
||||
|
||||
' If there is something next in the queue, play it
|
||||
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
|
||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||
m.global.queueManager.callFunc("moveForward")
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
if queueManager.callFunc("getPosition") < queueManager.callFunc("getCount") - 1
|
||||
sceneManager.callFunc("clearPreviousScene")
|
||||
queueManager.callFunc("moveForward")
|
||||
queueManager.callFunc("playQueue")
|
||||
return
|
||||
end if
|
||||
|
||||
' Playback completed, return user to previous screen
|
||||
m.global.sceneManager.callFunc("popScene")
|
||||
sceneManager.callFunc("popScene")
|
||||
m.global.audioPlayer.loopMode = ""
|
||||
end if
|
||||
end sub
|
||||
|
@ -97,6 +97,7 @@ sub itemContentChanged()
|
||||
' Updates video metadata
|
||||
item = m.top.itemContent
|
||||
if isValid(item) and isValid(item.json)
|
||||
userSettings = m.global.session.user.settings
|
||||
itemData = item.json
|
||||
m.top.id = itemData.id
|
||||
m.top.findNode("moviePoster").uri = m.top.itemContent.posterURL
|
||||
@ -120,7 +121,7 @@ sub itemContentChanged()
|
||||
m.infoGroup.removeChild(m.top.findNode("officialRating"))
|
||||
end if
|
||||
|
||||
if m.global.session.user.settings["ui.movies.showRatings"]
|
||||
if userSettings["ui.movies.showRatings"]
|
||||
if isValid(itemData.communityRating)
|
||||
setFieldText("communityRating", int(itemData.communityRating * 10) / 10)
|
||||
else
|
||||
@ -145,7 +146,7 @@ sub itemContentChanged()
|
||||
|
||||
if type(itemData.RunTimeTicks) = "LongInteger"
|
||||
setFieldText("runtime", stri(getRuntime()) + " mins")
|
||||
if m.global.session.user.settings["ui.design.hideclock"] <> true
|
||||
if userSettings["ui.design.hideclock"] <> true
|
||||
setFieldText("ends-at", tr("Ends at %1").Replace("%1", getEndTime()))
|
||||
end if
|
||||
end if
|
||||
@ -173,7 +174,7 @@ sub itemContentChanged()
|
||||
m.top.findNode("details").removeChild(m.top.findNode("director"))
|
||||
end if
|
||||
|
||||
if m.global.session.user.settings["ui.details.hidetagline"] = false
|
||||
if userSettings["ui.details.hidetagline"] = false
|
||||
if itemData.taglines.count() > 0
|
||||
setFieldText("tagline", itemData.taglines[0])
|
||||
end if
|
||||
@ -402,7 +403,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
m.options.setFocus(true)
|
||||
end if
|
||||
|
||||
if key = "down" and m.buttonGrp.isInFocusChain()
|
||||
if key = "down" and m.buttonGrp.isInFocusChain() and m.extrasGrp.visible = true
|
||||
m.top.lastFocus = m.extrasGrid
|
||||
m.extrasGrid.setFocus(true)
|
||||
m.top.findNode("VertSlider").reverse = false
|
||||
|
@ -136,7 +136,7 @@ sub createFullDscrDlg()
|
||||
dlg.Title = tr("Press 'Back' to Close")
|
||||
dlg.width = 1290
|
||||
dlg.palette = m.dlgPalette
|
||||
dlg.overview = [m.dscr.text]
|
||||
dlg.overview = m.dscr.text
|
||||
m.fullDscrDlg = dlg
|
||||
m.top.getScene().dialog = dlg
|
||||
border = createObject("roSGNode", "Poster")
|
||||
|
@ -223,7 +223,7 @@ sub createFullDscrDlg()
|
||||
dlg.Title = tr("Press 'Back' to Close")
|
||||
dlg.width = 1290
|
||||
dlg.palette = m.dlgPalette
|
||||
dlg.overview = [m.dscr.text]
|
||||
dlg.overview = m.dscr.text
|
||||
m.fullDscrDlg = dlg
|
||||
m.top.getScene().dialog = dlg
|
||||
border = createObject("roSGNode", "Poster")
|
||||
|
@ -9,9 +9,14 @@ sub init()
|
||||
m.lastRecordedPositionTimestamp = 0
|
||||
m.scrubTimestamp = -1
|
||||
|
||||
m.playlistTypeCount = m.global.queueManager.callFunc("getQueueUniqueTypes").count()
|
||||
m.queueManager = m.global.queueManager
|
||||
m.playlistTypeCount = m.queueManager.callFunc("getQueueUniqueTypes").count()
|
||||
|
||||
m.audioPlayer = m.global.audioPlayer
|
||||
m.audioPlayer.observeField("state", "audioStateChanged")
|
||||
m.audioPlayer.observeField("position", "audioPositionChanged")
|
||||
m.audioPlayer.observeField("bufferingStatus", "bufferPositionChanged")
|
||||
|
||||
setupAudioNode()
|
||||
setupAnimationTasks()
|
||||
setupButtons()
|
||||
setupInfoNodes()
|
||||
@ -91,13 +96,6 @@ sub setupDataTasks()
|
||||
m.LoadScreenSaverTimeoutTask = CreateObject("roSGNode", "LoadScreenSaverTimeoutTask")
|
||||
end sub
|
||||
|
||||
' Creates audio node used to play song(s)
|
||||
sub setupAudioNode()
|
||||
m.global.audioPlayer.observeField("state", "audioStateChanged")
|
||||
m.global.audioPlayer.observeField("position", "audioPositionChanged")
|
||||
m.global.audioPlayer.observeField("bufferingStatus", "bufferPositionChanged")
|
||||
end sub
|
||||
|
||||
' Setup playback buttons, default to Play button selected
|
||||
sub setupButtons()
|
||||
m.buttons = m.top.findNode("buttons")
|
||||
@ -149,10 +147,10 @@ end sub
|
||||
sub bufferPositionChanged()
|
||||
if m.inScrubMode then return
|
||||
|
||||
if not isValid(m.global.audioPlayer.bufferingStatus)
|
||||
if not isValid(m.audioPlayer.bufferingStatus)
|
||||
bufferPositionBarWidth = m.seekBar.width
|
||||
else
|
||||
bufferPositionBarWidth = m.seekBar.width * m.global.audioPlayer.bufferingStatus.percentage
|
||||
bufferPositionBarWidth = m.seekBar.width * m.audioPlayer.bufferingStatus.percentage
|
||||
end if
|
||||
|
||||
' Ensure position bar is never wider than the seek bar
|
||||
@ -168,16 +166,16 @@ end sub
|
||||
sub audioPositionChanged()
|
||||
stopLoadingSpinner()
|
||||
|
||||
if m.global.audioPlayer.position = 0
|
||||
if m.audioPlayer.position = 0
|
||||
m.playPosition.width = 0
|
||||
end if
|
||||
|
||||
if not isValid(m.global.audioPlayer.position)
|
||||
if not isValid(m.audioPlayer.position)
|
||||
playPositionBarWidth = 0
|
||||
else if not isValid(m.songDuration)
|
||||
playPositionBarWidth = 0
|
||||
else
|
||||
songPercentComplete = m.global.audioPlayer.position / m.songDuration
|
||||
songPercentComplete = m.audioPlayer.position / m.songDuration
|
||||
playPositionBarWidth = m.seekBar.width * songPercentComplete
|
||||
end if
|
||||
|
||||
@ -189,7 +187,7 @@ sub audioPositionChanged()
|
||||
if not m.inScrubMode
|
||||
moveSeekbarThumb(playPositionBarWidth)
|
||||
' Change the seek position timestamp
|
||||
m.seekTimestamp.text = secondsToHuman(m.global.audioPlayer.position, false)
|
||||
m.seekTimestamp.text = secondsToHuman(m.audioPlayer.position, false)
|
||||
end if
|
||||
|
||||
' Use animation to make the display smooth
|
||||
@ -197,9 +195,9 @@ sub audioPositionChanged()
|
||||
m.playPositionAnimation.control = "start"
|
||||
|
||||
' Update displayed position timestamp
|
||||
if isValid(m.global.audioPlayer.position)
|
||||
m.lastRecordedPositionTimestamp = m.global.audioPlayer.position
|
||||
m.positionTimestamp.text = secondsToHuman(m.global.audioPlayer.position, false)
|
||||
if isValid(m.audioPlayer.position)
|
||||
m.lastRecordedPositionTimestamp = m.audioPlayer.position
|
||||
m.positionTimestamp.text = secondsToHuman(m.audioPlayer.position, false)
|
||||
else
|
||||
m.lastRecordedPositionTimestamp = 0
|
||||
m.positionTimestamp.text = "0:00"
|
||||
@ -247,25 +245,24 @@ sub endScreenSaver()
|
||||
end sub
|
||||
|
||||
sub audioStateChanged()
|
||||
|
||||
' Song Finished, attempt to move to next song
|
||||
if m.global.audioPlayer.state = "finished"
|
||||
if m.audioPlayer.state = "finished"
|
||||
' User has enabled single song loop, play current song again
|
||||
if m.global.audioPlayer.loopMode = "one"
|
||||
if m.audioPlayer.loopMode = "one"
|
||||
m.scrubTimestamp = -1
|
||||
playAction()
|
||||
exitScrubMode()
|
||||
return
|
||||
end if
|
||||
|
||||
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
|
||||
if m.queueManager.callFunc("getPosition") < m.queueManager.callFunc("getCount") - 1
|
||||
m.top.state = "finished"
|
||||
else
|
||||
' We are at the end of the song queue
|
||||
|
||||
' User has enabled loop for entire song queue, move back to first song
|
||||
if m.global.audioPlayer.loopMode = "all"
|
||||
m.global.queueManager.callFunc("setPosition", -1)
|
||||
if m.audioPlayer.loopMode = "all"
|
||||
m.queueManager.callFunc("setPosition", -1)
|
||||
LoadNextSong()
|
||||
return
|
||||
end if
|
||||
@ -277,18 +274,19 @@ sub audioStateChanged()
|
||||
end sub
|
||||
|
||||
function playAction() as boolean
|
||||
if m.global.audioPlayer.state = "playing"
|
||||
m.global.audioPlayer.control = "pause"
|
||||
|
||||
if m.audioPlayer.state = "playing"
|
||||
m.audioPlayer.control = "pause"
|
||||
' Allow screen to go to real screensaver
|
||||
WriteAsciiFile("tmp:/scene.temp", "nowplaying-paused")
|
||||
MoveFile("tmp:/scene.temp", "tmp:/scene")
|
||||
else if m.global.audioPlayer.state = "paused"
|
||||
m.global.audioPlayer.control = "resume"
|
||||
else if m.audioPlayer.state = "paused"
|
||||
m.audioPlayer.control = "resume"
|
||||
' Write screen tracker for screensaver
|
||||
WriteAsciiFile("tmp:/scene.temp", "nowplaying")
|
||||
MoveFile("tmp:/scene.temp", "tmp:/scene")
|
||||
else if m.global.audioPlayer.state = "finished"
|
||||
m.global.audioPlayer.control = "play"
|
||||
else if m.audioPlayer.state = "finished"
|
||||
m.audioPlayer.control = "play"
|
||||
' Write screen tracker for screensaver
|
||||
WriteAsciiFile("tmp:/scene.temp", "nowplaying")
|
||||
MoveFile("tmp:/scene.temp", "tmp:/scene")
|
||||
@ -298,20 +296,19 @@ function playAction() as boolean
|
||||
end function
|
||||
|
||||
function previousClicked() as boolean
|
||||
currentQueuePosition = m.global.queueManager.callFunc("getPosition")
|
||||
|
||||
currentQueuePosition = m.queueManager.callFunc("getPosition")
|
||||
if currentQueuePosition = 0 then return false
|
||||
|
||||
if m.playlistTypeCount > 1
|
||||
previousItem = m.global.queueManager.callFunc("getItemByIndex", currentQueuePosition - 1)
|
||||
previousItemType = m.global.queueManager.callFunc("getItemType", previousItem)
|
||||
previousItem = m.queueManager.callFunc("getItemByIndex", currentQueuePosition - 1)
|
||||
previousItemType = m.queueManager.callFunc("getItemType", previousItem)
|
||||
|
||||
if previousItemType <> "audio"
|
||||
m.global.audioPlayer.control = "stop"
|
||||
m.audioPlayer.control = "stop"
|
||||
|
||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||
m.global.queueManager.callFunc("moveBack")
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
m.queueManager.callFunc("moveBack")
|
||||
m.queueManager.callFunc("playQueue")
|
||||
return true
|
||||
end if
|
||||
end if
|
||||
@ -321,34 +318,33 @@ function previousClicked() as boolean
|
||||
m.lastRecordedPositionTimestamp = 0
|
||||
m.positionTimestamp.text = "0:00"
|
||||
|
||||
if m.global.audioPlayer.state = "playing"
|
||||
m.global.audioPlayer.control = "stop"
|
||||
if m.audioPlayer.state = "playing"
|
||||
m.audioPlayer.control = "stop"
|
||||
end if
|
||||
|
||||
' Reset loop mode due to manual user interaction
|
||||
if m.global.audioPlayer.loopMode = "one"
|
||||
if m.audioPlayer.loopMode = "one"
|
||||
resetLoopModeToDefault()
|
||||
end if
|
||||
|
||||
m.global.queueManager.callFunc("moveBack")
|
||||
m.queueManager.callFunc("moveBack")
|
||||
pageContentChanged()
|
||||
|
||||
return true
|
||||
end function
|
||||
|
||||
sub resetLoopModeToDefault()
|
||||
m.global.audioPlayer.loopMode = ""
|
||||
m.audioPlayer.loopMode = ""
|
||||
setLoopButtonImage()
|
||||
end sub
|
||||
|
||||
function loopClicked() as boolean
|
||||
|
||||
if m.global.audioPlayer.loopMode = ""
|
||||
m.global.audioPlayer.loopMode = "all"
|
||||
else if m.global.audioPlayer.loopMode = "all"
|
||||
m.global.audioPlayer.loopMode = "one"
|
||||
if m.audioPlayer.loopMode = ""
|
||||
m.audioPlayer.loopMode = "all"
|
||||
else if m.audioPlayer.loopMode = "all"
|
||||
m.audioPlayer.loopMode = "one"
|
||||
else
|
||||
m.global.audioPlayer.loopMode = ""
|
||||
m.audioPlayer.loopMode = ""
|
||||
end if
|
||||
|
||||
setLoopButtonImage()
|
||||
@ -357,10 +353,10 @@ function loopClicked() as boolean
|
||||
end function
|
||||
|
||||
sub setLoopButtonImage()
|
||||
if m.global.audioPlayer.loopMode = "all"
|
||||
if m.audioPlayer.loopMode = "all"
|
||||
m.loopIndicator.opacity = "1"
|
||||
m.loopIndicator.uri = m.loopIndicator.uri.Replace("-off", "-on")
|
||||
else if m.global.audioPlayer.loopMode = "one"
|
||||
else if m.audioPlayer.loopMode = "one"
|
||||
m.loopIndicator.uri = m.loopIndicator.uri.Replace("-on", "1-on")
|
||||
else
|
||||
m.loopIndicator.uri = m.loopIndicator.uri.Replace("1-on", "-off")
|
||||
@ -368,19 +364,20 @@ sub setLoopButtonImage()
|
||||
end sub
|
||||
|
||||
function nextClicked() as boolean
|
||||
if m.playlistTypeCount > 1
|
||||
currentQueuePosition = m.global.queueManager.callFunc("getPosition")
|
||||
if currentQueuePosition < m.global.queueManager.callFunc("getCount") - 1
|
||||
|
||||
nextItem = m.global.queueManager.callFunc("getItemByIndex", currentQueuePosition + 1)
|
||||
nextItemType = m.global.queueManager.callFunc("getItemType", nextItem)
|
||||
if m.playlistTypeCount > 1
|
||||
currentQueuePosition = m.queueManager.callFunc("getPosition")
|
||||
if currentQueuePosition < m.queueManager.callFunc("getCount") - 1
|
||||
|
||||
nextItem = m.queueManager.callFunc("getItemByIndex", currentQueuePosition + 1)
|
||||
nextItemType = m.queueManager.callFunc("getItemType", nextItem)
|
||||
|
||||
if nextItemType <> "audio"
|
||||
m.global.audioPlayer.control = "stop"
|
||||
m.audioPlayer.control = "stop"
|
||||
|
||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||
m.global.queueManager.callFunc("moveForward")
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
m.queueManager.callFunc("moveForward")
|
||||
m.queueManager.callFunc("playQueue")
|
||||
return true
|
||||
end if
|
||||
end if
|
||||
@ -392,11 +389,11 @@ function nextClicked() as boolean
|
||||
m.positionTimestamp.text = "0:00"
|
||||
|
||||
' Reset loop mode due to manual user interaction
|
||||
if m.global.audioPlayer.loopMode = "one"
|
||||
if m.audioPlayer.loopMode = "one"
|
||||
resetLoopModeToDefault()
|
||||
end if
|
||||
|
||||
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
|
||||
if m.queueManager.callFunc("getPosition") < m.queueManager.callFunc("getCount") - 1
|
||||
LoadNextSong()
|
||||
end if
|
||||
|
||||
@ -404,14 +401,14 @@ function nextClicked() as boolean
|
||||
end function
|
||||
|
||||
sub toggleShuffleEnabled()
|
||||
m.global.queueManager.callFunc("toggleShuffle")
|
||||
m.queueManager.callFunc("toggleShuffle")
|
||||
end sub
|
||||
|
||||
function findCurrentSongIndex(songList) as integer
|
||||
if not isValidAndNotEmpty(songList) then return 0
|
||||
|
||||
for i = 0 to songList.count() - 1
|
||||
if songList[i].id = m.global.queueManager.callFunc("getCurrentItem").id
|
||||
if songList[i].id = m.queueManager.callFunc("getCurrentItem").id
|
||||
return i
|
||||
end if
|
||||
end for
|
||||
@ -420,15 +417,14 @@ function findCurrentSongIndex(songList) as integer
|
||||
end function
|
||||
|
||||
function shuffleClicked() as boolean
|
||||
|
||||
currentSongIndex = findCurrentSongIndex(m.global.queueManager.callFunc("getUnshuffledQueue"))
|
||||
currentSongIndex = findCurrentSongIndex(m.queueManager.callFunc("getUnshuffledQueue"))
|
||||
|
||||
toggleShuffleEnabled()
|
||||
|
||||
if not m.global.queueManager.callFunc("getIsShuffled")
|
||||
if not m.queueManager.callFunc("getIsShuffled")
|
||||
m.shuffleIndicator.opacity = ".4"
|
||||
m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-on", "-off")
|
||||
m.global.queueManager.callFunc("setPosition", currentSongIndex)
|
||||
m.queueManager.callFunc("setPosition", currentSongIndex)
|
||||
setTrackNumberDisplay()
|
||||
return true
|
||||
end if
|
||||
@ -441,26 +437,26 @@ function shuffleClicked() as boolean
|
||||
end function
|
||||
|
||||
sub setShuffleIconState()
|
||||
if m.global.queueManager.callFunc("getIsShuffled")
|
||||
if m.queueManager.callFunc("getIsShuffled")
|
||||
m.shuffleIndicator.opacity = "1"
|
||||
m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-off", "-on")
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub setTrackNumberDisplay()
|
||||
setFieldTextValue("numberofsongs", "Track " + stri(m.global.queueManager.callFunc("getPosition") + 1) + "/" + stri(m.global.queueManager.callFunc("getCount")))
|
||||
setFieldTextValue("numberofsongs", "Track " + stri(m.queueManager.callFunc("getPosition") + 1) + "/" + stri(m.queueManager.callFunc("getCount")))
|
||||
end sub
|
||||
|
||||
sub LoadNextSong()
|
||||
if m.global.audioPlayer.state = "playing"
|
||||
m.global.audioPlayer.control = "stop"
|
||||
if m.audioPlayer.state = "playing"
|
||||
m.audioPlayer.control = "stop"
|
||||
end if
|
||||
|
||||
exitScrubMode()
|
||||
|
||||
' Reset playPosition bar without animation
|
||||
m.playPosition.width = 0
|
||||
m.global.queueManager.callFunc("moveForward")
|
||||
m.queueManager.callFunc("moveForward")
|
||||
pageContentChanged()
|
||||
end sub
|
||||
|
||||
@ -469,7 +465,7 @@ sub pageContentChanged()
|
||||
|
||||
m.LoadAudioStreamTask.control = "STOP"
|
||||
|
||||
currentItem = m.global.queueManager.callFunc("getCurrentItem")
|
||||
currentItem = m.queueManager.callFunc("getCurrentItem")
|
||||
|
||||
m.LoadAudioStreamTask.itemId = currentItem.id
|
||||
m.LoadAudioStreamTask.observeField("content", "onAudioStreamLoaded")
|
||||
@ -478,7 +474,7 @@ end sub
|
||||
|
||||
' If we have more and 1 song to play, fade in the next and previous controls
|
||||
sub loadButtons()
|
||||
if m.global.queueManager.callFunc("getCount") > 1
|
||||
if m.queueManager.callFunc("getCount") > 1
|
||||
m.shuffleIndicator.opacity = ".4"
|
||||
m.loopIndicator.opacity = ".4"
|
||||
m.displayButtonsAnimation.control = "start"
|
||||
@ -495,7 +491,7 @@ sub onAudioStreamLoaded()
|
||||
m.bufferPosition.width = 0
|
||||
|
||||
useMetaTask = false
|
||||
currentItem = m.global.queueManager.callFunc("getCurrentItem")
|
||||
currentItem = m.queueManager.callFunc("getCurrentItem")
|
||||
|
||||
if not isValid(currentItem.RunTimeTicks)
|
||||
useMetaTask = true
|
||||
@ -531,9 +527,9 @@ sub onAudioStreamLoaded()
|
||||
m.totalLengthTimestamp.text = ticksToHuman(currentItem.RunTimeTicks)
|
||||
end if
|
||||
|
||||
m.global.audioPlayer.content = data
|
||||
m.global.audioPlayer.control = "none"
|
||||
m.global.audioPlayer.control = "play"
|
||||
m.audioPlayer.content = data
|
||||
m.audioPlayer.control = "none"
|
||||
m.audioPlayer.control = "play"
|
||||
end if
|
||||
end sub
|
||||
|
||||
@ -739,7 +735,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
if m.inScrubMode
|
||||
startLoadingSpinner()
|
||||
m.inScrubMode = false
|
||||
m.global.audioPlayer.seek = m.scrubTimestamp
|
||||
m.audioPlayer.seek = m.scrubTimestamp
|
||||
return true
|
||||
end if
|
||||
|
||||
@ -773,15 +769,15 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
end if
|
||||
|
||||
if key = "back"
|
||||
m.global.audioPlayer.control = "stop"
|
||||
m.global.audioPlayer.loopMode = ""
|
||||
m.audioPlayer.control = "stop"
|
||||
m.audioPlayer.loopMode = ""
|
||||
else if key = "rewind"
|
||||
return previousClicked()
|
||||
else if key = "fastforward"
|
||||
return nextClicked()
|
||||
else if key = "left"
|
||||
if m.buttons.hasFocus()
|
||||
if m.global.queueManager.callFunc("getCount") = 1 then return false
|
||||
if m.queueManager.callFunc("getCount") = 1 then return false
|
||||
|
||||
if m.top.selectedButtonIndex > 0
|
||||
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
|
||||
@ -791,7 +787,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
end if
|
||||
else if key = "right"
|
||||
if m.buttons.hasFocus()
|
||||
if m.global.queueManager.callFunc("getCount") = 1 then return false
|
||||
if m.queueManager.callFunc("getCount") = 1 then return false
|
||||
|
||||
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
|
||||
if m.top.selectedButtonIndex < m.buttonCount - 1 then m.top.selectedButtonIndex = m.top.selectedButtonIndex + 1
|
||||
|
@ -127,7 +127,7 @@ sub createFullDscrDlg()
|
||||
dlg.Title = tr("Press 'Back' to Close")
|
||||
dlg.width = 1290
|
||||
dlg.palette = m.dlgPalette
|
||||
dlg.overview = [m.dscr.text]
|
||||
dlg.overview = m.dscr.text
|
||||
m.fullDscrDlg = dlg
|
||||
m.top.getScene().dialog = dlg
|
||||
border = createObject("roSGNode", "Poster")
|
||||
|
@ -10,8 +10,9 @@ sub init()
|
||||
m.textBackground = m.top.findNode("background")
|
||||
m.statusTimer = m.top.findNode("statusTimer")
|
||||
m.statusTimer.observeField("fire", "statusUpdate")
|
||||
m.slideshow = m.global.session.user.settings["photos.slideshow"]
|
||||
m.random = m.global.session.user.settings["photos.random"]
|
||||
userSettings = m.global.session.user.settings
|
||||
m.slideshow = userSettings["photos.slideshow"]
|
||||
m.random = userSettings["photos.random"]
|
||||
|
||||
m.showStatusAnimation = m.top.findNode("showStatusAnimation")
|
||||
m.hideStatusAnimation = m.top.findNode("hideStatusAnimation")
|
||||
|
@ -91,26 +91,28 @@ sub settingFocused()
|
||||
m.integerSetting.visible = false
|
||||
m.radioSetting.visible = false
|
||||
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
if selectedSetting.type = invalid
|
||||
return
|
||||
else if selectedSetting.type = "bool"
|
||||
|
||||
m.boolSetting.visible = true
|
||||
|
||||
if m.global.session.user.settings[selectedSetting.settingName] = true
|
||||
if userSettings[selectedSetting.settingName] = true
|
||||
m.boolSetting.checkedItem = 1
|
||||
else
|
||||
m.boolSetting.checkedItem = 0
|
||||
end if
|
||||
else if selectedSetting.type = "integer"
|
||||
integerValue = m.global.session.user.settings[selectedSetting.settingName].ToStr()
|
||||
integerValue = userSettings[selectedSetting.settingName].ToStr()
|
||||
if isValid(integerValue)
|
||||
m.integerSetting.text = integerValue
|
||||
end if
|
||||
m.integerSetting.visible = true
|
||||
else if LCase(selectedSetting.type) = "radio"
|
||||
|
||||
selectedValue = m.global.session.user.settings[selectedSetting.settingName]
|
||||
selectedValue = userSettings[selectedSetting.settingName]
|
||||
|
||||
radioContent = CreateObject("roSGNode", "ContentNode")
|
||||
|
||||
@ -173,7 +175,6 @@ sub boolSettingChanged()
|
||||
set_setting(selectedSetting.settingName, "true")
|
||||
' setting specific triggers
|
||||
if selectedSetting.settingName = "global.rememberme"
|
||||
print "m.global.session.user.id=", m.global.session.user.id
|
||||
set_setting("active_user", m.global.session.user.id)
|
||||
end if
|
||||
else
|
||||
|
@ -6,11 +6,28 @@ sub init()
|
||||
|
||||
m.top.showRowLabel = [false]
|
||||
|
||||
m.top.observeField("selectItemId", "onItemSelected")
|
||||
|
||||
updateSize()
|
||||
|
||||
m.top.setFocus(true)
|
||||
end sub
|
||||
|
||||
sub onItemSelected()
|
||||
Id = m.top.selectItemId
|
||||
|
||||
if Id <> invalid and Id <> "" and m.top.objects <> invalid
|
||||
for i = 0 to m.top.objects.items.count() - 1
|
||||
item = m.top.objects.items[i]
|
||||
if item.id = Id
|
||||
m.top.jumpToItem = i
|
||||
return
|
||||
end if
|
||||
end for
|
||||
end if
|
||||
end sub
|
||||
|
||||
|
||||
sub updateSize()
|
||||
m.top.translation = [450, 180]
|
||||
|
||||
|
@ -4,5 +4,8 @@
|
||||
<field id="objects" type="assocarray" onChange="setupRows" />
|
||||
<field id="escapeButton" type="string" alwaysNotify="true" />
|
||||
<field id="doneLoading" type="boolean" />
|
||||
<field id="selectItemId" type="string" />
|
||||
|
||||
<function name="onItemSelected" />
|
||||
</interface>
|
||||
</component>
|
@ -4,9 +4,15 @@ sub init()
|
||||
m.rows = m.top.findNode("tvEpisodeRow")
|
||||
m.tvListOptions = m.top.findNode("tvListOptions")
|
||||
|
||||
m.top.observeField("selectItemId", "onItemSelected")
|
||||
m.rows.observeField("doneLoading", "rowsDoneLoading")
|
||||
end sub
|
||||
|
||||
sub onItemSelected()
|
||||
Id = m.top.selectItemId
|
||||
m.rows.selectItemId = Id
|
||||
end sub
|
||||
|
||||
sub setupRows()
|
||||
objects = m.top.objects
|
||||
m.rows.objects = objects
|
||||
|
@ -8,5 +8,8 @@
|
||||
<field id="objects" type="assocarray" onChange="setupRows" />
|
||||
<field id="itemSelected" type="integer" />
|
||||
<field id="doneLoading" type="boolean" />
|
||||
<field id="selectItemId" type="string" />
|
||||
|
||||
<function name="onItemSelected" />
|
||||
</interface>
|
||||
</component>
|
@ -6,6 +6,7 @@ import "pkg:/source/api/sdk.bs"
|
||||
|
||||
sub init()
|
||||
m.top.optionsAvailable = false
|
||||
m.top.observeField("selectItemId", "onItemSelected")
|
||||
|
||||
m.rows = m.top.findNode("picker")
|
||||
m.poster = m.top.findNode("seasonPoster")
|
||||
@ -25,6 +26,11 @@ sub setSeasonLoading()
|
||||
m.top.overhangTitle = tr("Loading...")
|
||||
end sub
|
||||
|
||||
sub onItemSelected()
|
||||
Id = m.top.selectItemId
|
||||
m.rows.selectItemId = Id
|
||||
end sub
|
||||
|
||||
' Updates the visibility of the Extras button based on if this season has any extra features
|
||||
sub setExtraButtonVisibility()
|
||||
if isValid(m.top.extrasObjects) and isValidAndNotEmpty(m.top.extrasObjects.items)
|
||||
@ -73,6 +79,7 @@ end function
|
||||
' OnScreenShown: Callback function when view is presented on screen
|
||||
'
|
||||
sub OnScreenShown()
|
||||
|
||||
if m.isFirstRun
|
||||
m.isFirstRun = false
|
||||
return
|
||||
@ -80,6 +87,7 @@ sub OnScreenShown()
|
||||
|
||||
m.tvEpisodeRow.setFocus(true)
|
||||
m.top.refreshSeasonDetailsData = not m.top.refreshSeasonDetailsData
|
||||
|
||||
end sub
|
||||
|
||||
' Handle navigation input from the remote and act on it
|
||||
|
@ -19,6 +19,9 @@
|
||||
<field id="objects" alias="picker.objects" />
|
||||
<field id="episodeObjects" type="assocarray" />
|
||||
<field id="extrasObjects" type="assocarray" onChange="setExtraButtonVisibility" />
|
||||
<field id="selectItemId" type="string" />
|
||||
|
||||
<function name="updateSeason" />
|
||||
<function name="onItemSelected" />
|
||||
</interface>
|
||||
</component>
|
@ -21,13 +21,16 @@ end sub
|
||||
sub itemContentChanged()
|
||||
item = m.top.itemContent
|
||||
itemData = item.json
|
||||
userSettings = m.global.session.user.settings
|
||||
|
||||
' Set default video source if user hasn't selected one yet
|
||||
if item.selectedVideoStreamId = "" and isValid(itemData.MediaSources)
|
||||
item.selectedVideoStreamId = itemData.MediaSources[0].id
|
||||
end if
|
||||
|
||||
if isValid(itemData.indexNumber)
|
||||
if isValid(itemData.parentIndexNumber) and itemData.parentIndexNumber = 0
|
||||
indexNumber = `${tr("Special")} - `
|
||||
else if isValid(itemData.indexNumber)
|
||||
indexNumber = `${itemData.indexNumber}. `
|
||||
if isValid(itemData.indexNumberEnd)
|
||||
indexNumber = `${itemData.indexNumber}-${itemData.indexNumberEnd}. `
|
||||
@ -46,7 +49,7 @@ sub itemContentChanged()
|
||||
|
||||
imageUrl = item.posterURL
|
||||
|
||||
if m.global.session.user.settings["ui.tvshows.blurunwatched"] = true
|
||||
if userSettings["ui.tvshows.blurunwatched"] = true
|
||||
if itemData.lookup("Type") = "Episode"
|
||||
if not itemData.userdata.played
|
||||
imageUrl = imageUrl + "&blur=15"
|
||||
@ -64,12 +67,12 @@ sub itemContentChanged()
|
||||
m.top.findNode("runtime").text = stri(runTime).trim() + " mins"
|
||||
end if
|
||||
|
||||
if m.global.session.user.settings["ui.design.hideclock"] <> true
|
||||
if userSettings["ui.design.hideclock"] <> true
|
||||
m.top.findNode("endtime").text = tr("Ends at %1").Replace("%1", getEndTime())
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.global.session.user.settings["ui.tvshows.disableCommunityRating"] = false
|
||||
if userSettings["ui.tvshows.disableCommunityRating"] = false
|
||||
if isValid(itemData.communityRating)
|
||||
m.top.findNode("star").visible = true
|
||||
m.top.findNode("communityRating").text = str(int(itemData.communityRating * 10) / 10)
|
||||
|
@ -181,8 +181,12 @@ end function
|
||||
|
||||
sub onShuffleEpisodeDataLoaded()
|
||||
m.getShuffleEpisodesTask.unobserveField("data")
|
||||
m.global.queueManager.callFunc("set", m.getShuffleEpisodesTask.data.items)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
|
||||
if isValid(m.getShuffleEpisodesTask.data)
|
||||
queueManager = m.global.queueManager
|
||||
queueManager.callFunc("set", m.getShuffleEpisodesTask.data.items)
|
||||
queueManager.callFunc("playQueue")
|
||||
end if
|
||||
end sub
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
|
@ -35,14 +35,27 @@ sub init()
|
||||
m.optionControls.buttonFocused = m.optionControls.getChildCount() - 1
|
||||
|
||||
m.videoControls.getChild(m.defaultButtonIndex).focus = true
|
||||
m.deviceInfo = CreateObject("roDeviceInfo")
|
||||
end sub
|
||||
|
||||
' onProgressPercentageChanged: Handler for changes to m.top.progressPercentage param
|
||||
'
|
||||
sub onProgressPercentageChanged()
|
||||
m.videoPositionTime.text = secondsToHuman(m.top.positionTime, true)
|
||||
m.videoRemainingTime.text = secondsToHuman(m.top.remainingPositionTime, true)
|
||||
|
||||
osdmode = m.global.session.user.settings["ui.general.osdremainingtime"]
|
||||
if m.global.session.user.settings["ui.design.hideclock"]
|
||||
' in order to honor the hide clocks setting
|
||||
osdmode = "remaining"
|
||||
end if
|
||||
|
||||
if osdmode = "remaining"
|
||||
m.videoRemainingTime.text = secondsToHuman(m.top.remainingPositionTime, true)
|
||||
else if osdmode = "timeofday"
|
||||
m.videoRemainingTime.text = secondsToEndTime(m.top.remainingPositionTime)
|
||||
else if osdmode = "both"
|
||||
m.videoRemainingTime.text = secondsToHuman(m.top.remainingPositionTime, true) + " | " + secondsToEndTime(m.top.remainingPositionTime)
|
||||
end if
|
||||
|
||||
m.progressBar.width = m.progressBarBackground.width * m.top.progressPercentage
|
||||
end sub
|
||||
|
||||
@ -211,7 +224,8 @@ sub inactiveCheck()
|
||||
return
|
||||
end if
|
||||
|
||||
if m.deviceInfo.timeSinceLastKeypress() >= m.top.inactiveTimeout
|
||||
deviceInfo = CreateObject("roDeviceInfo")
|
||||
if deviceInfo.timeSinceLastKeypress() >= m.top.inactiveTimeout
|
||||
m.top.action = "hide"
|
||||
end if
|
||||
end sub
|
||||
|
@ -34,7 +34,7 @@
|
||||
</Rectangle>
|
||||
|
||||
<Label id="videoPositionTime" font="font:MediumSystemFont" color="0xffffffFF" translation="[103,985]" />
|
||||
<Label id="videoRemainingTime" font="font:MediumSystemFont" color="0xffffffFF" horizAlign="right" width="200" translation="[1617,985]" />
|
||||
<Label id="videoRemainingTime" font="font:MediumSystemFont" color="0xffffffFF" horizAlign="right" width="400" translation="[1450,985]" />
|
||||
|
||||
<Timer id="inactivityTimer" duration="1" repeat="true" />
|
||||
</children>
|
||||
|
@ -1,10 +1,15 @@
|
||||
import "pkg:/source/utils/misc.bs"
|
||||
import "pkg:/source/utils/config.bs"
|
||||
import "pkg:/source/utils/session.bs"
|
||||
import "pkg:/source/roku_modules/log/LogMixin.brs"
|
||||
|
||||
sub init()
|
||||
m.log = log.Logger("VideoPlayerView")
|
||||
' Hide the overhang on init to prevent showing 2 clocks
|
||||
m.top.getScene().findNode("overhang").visible = false
|
||||
userSettings = m.global.session.user.settings
|
||||
m.currentItem = m.global.queueManager.callFunc("getCurrentItem")
|
||||
m.originalClosedCaptionState = invalid
|
||||
|
||||
m.top.id = m.currentItem.id
|
||||
m.top.seekMode = "accurate"
|
||||
@ -43,7 +48,7 @@ sub init()
|
||||
m.top.transcodeReasons = []
|
||||
m.bufferCheckTimer.duration = 30
|
||||
|
||||
if m.global.session.user.settings["ui.design.hideclock"] = true
|
||||
if userSettings["ui.design.hideclock"] = true
|
||||
clockNode = findNodeBySubtype(m.top, "clock")
|
||||
if clockNode[0] <> invalid then clockNode[0].parent.removeChild(clockNode[0].node)
|
||||
end if
|
||||
@ -52,7 +57,7 @@ sub init()
|
||||
m.nextEpisodeButton = m.top.findNode("nextEpisode")
|
||||
m.nextEpisodeButton.text = tr("Next Episode")
|
||||
m.nextEpisodeButton.setFocus(false)
|
||||
m.nextupbuttonseconds = m.global.session.user.settings["playback.nextupbuttonseconds"].ToInt()
|
||||
m.nextupbuttonseconds = userSettings["playback.nextupbuttonseconds"].ToInt()
|
||||
|
||||
m.showNextEpisodeButtonAnimation = m.top.findNode("showNextEpisodeButton")
|
||||
m.hideNextEpisodeButtonAnimation = m.top.findNode("hideNextEpisodeButton")
|
||||
@ -97,24 +102,30 @@ end sub
|
||||
' @param {string} action - skip action to take
|
||||
sub handleItemSkipAction(action as string)
|
||||
if action = "itemnext"
|
||||
queueManager = m.global.queueManager
|
||||
|
||||
' If there is something next in the queue, play it
|
||||
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
|
||||
if queueManager.callFunc("getPosition") < queueManager.callFunc("getCount") - 1
|
||||
m.top.control = "stop"
|
||||
session.video.Delete()
|
||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||
m.global.queueManager.callFunc("moveForward")
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
queueManager.callFunc("moveForward")
|
||||
queueManager.callFunc("playQueue")
|
||||
end if
|
||||
|
||||
return
|
||||
end if
|
||||
|
||||
if action = "itemback"
|
||||
queueManager = m.global.queueManager
|
||||
|
||||
' If there is something previous in the queue, play it
|
||||
if m.global.queueManager.callFunc("getPosition") > 0
|
||||
if queueManager.callFunc("getPosition") > 0
|
||||
m.top.control = "stop"
|
||||
session.video.Delete()
|
||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||
m.global.queueManager.callFunc("moveBack")
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
queueManager.callFunc("moveBack")
|
||||
queueManager.callFunc("playQueue")
|
||||
end if
|
||||
|
||||
return
|
||||
@ -451,7 +462,12 @@ sub onVideoContentLoaded()
|
||||
availableSubtitleTrackIndex = availSubtitleTrackIdx(selectedSubtitle.Track.TrackName)
|
||||
if availableSubtitleTrackIndex <> -1
|
||||
if not selectedSubtitle.IsEncoded
|
||||
m.top.globalCaptionMode = "On"
|
||||
if selectedSubtitle.IsForced
|
||||
' If IsForced, make sure to remember the Roku global setting so we
|
||||
' can set it back when the video is done playing.
|
||||
m.originalClosedCaptionState = m.top.globalCaptionMode
|
||||
m.top.globalCaptionMode = "On"
|
||||
end if
|
||||
m.top.subtitleTrack = m.top.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName
|
||||
end if
|
||||
end if
|
||||
@ -517,12 +533,14 @@ sub showNextEpisodeButton()
|
||||
if m.osd.visible then return
|
||||
if m.top.content.contenttype <> 4 then return ' only display when content is type "Episode"
|
||||
if m.nextupbuttonseconds = 0 then return ' is the button disabled?
|
||||
if m.nextEpisodeButton.opacity <> 0 then return
|
||||
userSettings = m.global.session.user.settings
|
||||
if userSettings["playback.playnextepisode"] = "disabled" then return
|
||||
if userSettings["playback.playnextepisode"] = "webclient" and not m.global.session.user.Configuration.EnableNextEpisodeAutoPlay then return
|
||||
|
||||
if m.nextEpisodeButton.opacity = 0 and m.global.session.user.configuration.EnableNextEpisodeAutoPlay
|
||||
m.nextEpisodeButton.visible = true
|
||||
m.showNextEpisodeButtonAnimation.control = "start"
|
||||
m.nextEpisodeButton.setFocus(true)
|
||||
end if
|
||||
m.nextEpisodeButton.visible = true
|
||||
m.showNextEpisodeButtonAnimation.control = "start"
|
||||
m.nextEpisodeButton.setFocus(true)
|
||||
end sub
|
||||
|
||||
'
|
||||
@ -600,6 +618,7 @@ end sub
|
||||
'
|
||||
' When Video Player state changes
|
||||
sub onState(msg)
|
||||
m.log.debug("start onState()", m.top.state)
|
||||
if isValid(m.captionTask)
|
||||
m.captionTask.playerState = m.top.state + m.top.globalCaptionMode
|
||||
end if
|
||||
@ -607,23 +626,26 @@ sub onState(msg)
|
||||
' Pass video state into OSD
|
||||
m.osd.playbackState = m.top.state
|
||||
|
||||
' When buffering, start timer to monitor buffering process
|
||||
if m.top.state = "buffering" and m.bufferCheckTimer <> invalid
|
||||
|
||||
' start timer
|
||||
m.bufferCheckTimer.control = "start"
|
||||
m.bufferCheckTimer.ObserveField("fire", "bufferCheck")
|
||||
if m.top.state = "buffering"
|
||||
' When buffering, start timer to monitor buffering process
|
||||
if isValid(m.bufferCheckTimer)
|
||||
m.bufferCheckTimer.control = "start"
|
||||
m.bufferCheckTimer.ObserveField("fire", "bufferCheck")
|
||||
end if
|
||||
else if m.top.state = "error"
|
||||
m.log.error(m.top.errorCode, m.top.errorMsg, m.top.errorStr, m.top.errorCode)
|
||||
|
||||
print m.top.errorInfo
|
||||
|
||||
if not m.playReported and m.top.transcodeAvailable
|
||||
m.top.retryWithTranscoding = true ' If playback was not reported, retry with transcoding
|
||||
else
|
||||
' If an error was encountered, Display dialog
|
||||
showPlaybackErrorDialog(tr("Error During Playback"))
|
||||
session.video.Delete()
|
||||
end if
|
||||
|
||||
' Stop playback and exit player
|
||||
m.top.control = "stop"
|
||||
m.top.backPressed = true
|
||||
|
||||
else if m.top.state = "playing"
|
||||
|
||||
' Check if next episode is available
|
||||
@ -649,16 +671,24 @@ sub onState(msg)
|
||||
m.playbackTimer.control = "stop"
|
||||
ReportPlayback("stop")
|
||||
m.playReported = false
|
||||
session.video.Delete()
|
||||
else if m.top.state = "finished"
|
||||
m.playbackTimer.control = "stop"
|
||||
ReportPlayback("finished")
|
||||
session.video.Delete()
|
||||
else
|
||||
m.log.warning("Unhandled state", m.top.state, m.playReported, m.playFinished)
|
||||
end if
|
||||
|
||||
m.log.debug("end onState()", m.top.state)
|
||||
end sub
|
||||
|
||||
'
|
||||
' Report playback to server
|
||||
sub ReportPlayback(state = "update" as string)
|
||||
|
||||
if m.top.position = invalid then return
|
||||
|
||||
m.log.debug("start ReportPlayback()", state, int(m.top.position))
|
||||
|
||||
params = {
|
||||
"ItemId": m.top.id,
|
||||
"PlaySessionId": m.top.PlaySessionId,
|
||||
@ -673,10 +703,18 @@ sub ReportPlayback(state = "update" as string)
|
||||
m.bufferCheckTimer.duration = 30
|
||||
end if
|
||||
|
||||
if (state = "stop" or state = "finished") and m.originalClosedCaptionState <> invalid
|
||||
m.log.debug("ReportPlayback() setting", m.top.globalCaptionMode, "back to", m.originalClosedCaptionState)
|
||||
m.top.globalCaptionMode = m.originalClosedCaptionState
|
||||
m.originalClosedCaptionState = invalid
|
||||
end if
|
||||
|
||||
' Report playstate via worker task
|
||||
playstateTask = m.global.playstateTask
|
||||
playstateTask.setFields({ status: state, params: params })
|
||||
playstateTask.control = "RUN"
|
||||
|
||||
m.log.debug("end ReportPlayback()", state, int(m.top.position))
|
||||
end sub
|
||||
|
||||
'
|
||||
@ -702,7 +740,7 @@ sub bufferCheck(msg)
|
||||
|
||||
' Stop playback and exit player
|
||||
m.top.control = "stop"
|
||||
m.top.backPressed = true
|
||||
session.video.Delete()
|
||||
end if
|
||||
end if
|
||||
|
||||
@ -772,6 +810,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
if key = "OK" and m.nextEpisodeButton.hasfocus() and not m.top.trickPlayBar.visible
|
||||
m.top.control = "stop"
|
||||
m.top.state = "finished"
|
||||
session.video.Delete()
|
||||
hideNextEpisodeButton()
|
||||
return true
|
||||
else
|
||||
@ -846,6 +885,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
|
||||
if key = "back"
|
||||
m.top.control = "stop"
|
||||
session.video.Delete()
|
||||
end if
|
||||
|
||||
return 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
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