Merge branch 'master' into expand-homerows

This commit is contained in:
Charles Ewert 2024-11-08 11:29:49 -05:00 committed by GitHub
commit aa5210f758
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
372 changed files with 5551 additions and 1630 deletions

View File

@ -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

View File

@ -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 }}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -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]"

View File

@ -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()

View File

@ -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]"

View File

@ -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
'

View File

@ -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)

View File

@ -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"

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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>

View File

@ -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 })

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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

View File

@ -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]

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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