Merge branch 'unstable' into jf-video-stats

This commit is contained in:
Jimi 2022-09-05 22:42:35 -06:00
commit e02afad161
42 changed files with 893 additions and 255 deletions

View File

@ -19,6 +19,13 @@ Open up the new folder:
cd jellyfin-roku cd jellyfin-roku
``` ```
Install Dependencies:
```bash
npm install
```
## Method 1: Visual Studio Code ## Method 1: Visual Studio Code
We recommend using Visual Studio Code when working on this project. The [BrightScript Language extension](https://marketplace.visualstudio.com/items?itemName=RokuCommunity.brightscript) provides a rich debugging experience, including in-editor syntax checking, debugging/breakpoint support, variable inspection at runtime, auto-formatting, an integrated remote control mode, and [much more](https://rokucommunity.github.io/vscode-brightscript-language/features.html). We recommend using Visual Studio Code when working on this project. The [BrightScript Language extension](https://marketplace.visualstudio.com/items?itemName=RokuCommunity.brightscript) provides a rich debugging experience, including in-editor syntax checking, debugging/breakpoint support, variable inspection at runtime, auto-formatting, an integrated remote control mode, and [much more](https://rokucommunity.github.io/vscode-brightscript-language/features.html).

View File

@ -25,7 +25,17 @@ sub init()
m.itemGrid.observeField("itemFocused", "onItemFocused") m.itemGrid.observeField("itemFocused", "onItemFocused")
m.itemGrid.observeField("itemSelected", "onItemSelected") m.itemGrid.observeField("itemSelected", "onItemSelected")
m.itemGrid.observeField("AlphaSelected", "onItemAlphaSelected") m.itemGrid.observeField("alphaSelected", "onItemalphaSelected")
'Voice filter setup
m.voiceBox = m.top.findNode("voiceBox")
m.voiceBox.voiceEnabled = true
m.voiceBox.active = true
m.voiceBox.observeField("text", "onvoiceFilter")
'set voice help text
m.voiceBox.hintText = tr("Use voice remote to search")
'backdrop
m.newBackdrop.observeField("loadStatus", "newBGLoaded") m.newBackdrop.observeField("loadStatus", "newBGLoaded")
'Background Image Queued for loading 'Background Image Queued for loading
@ -45,19 +55,35 @@ sub init()
m.spinner = m.top.findNode("spinner") m.spinner = m.top.findNode("spinner")
m.spinner.visible = true m.spinner.visible = true
m.Alpha = m.top.findNode("AlphaMenu") m.Alpha = m.top.findNode("AlphaMenu")
m.AlphaSelected = m.top.findNode("AlphaSelected") m.AlphaSelected = m.top.findNode("AlphaSelected")
'Get reset folder setting 'Get reset folder setting
m.resetGrid = get_user_setting("itemgrid.reset") = "true" m.resetGrid = get_user_setting("itemgrid.reset") = "true"
'Check if device has voice remote
devinfo = CreateObject("roDeviceInfo")
m.deviFeature = devinfo.HasFeature("voice_remote")
m.micButton = m.top.findNode("micButton")
m.micButtonText = m.top.findNode("micButtonText")
'Hide voice search if device does not have voice remote
if m.deviFeature = false
m.micButton.visible = false
m.micButtonText.visible = false
end if
end sub end sub
' '
'Load initial set of Data 'Load initial set of Data
sub loadInitialItems() sub loadInitialItems()
m.loadItemsTask.control = "stop"
m.spinner.visible = true
if m.top.parentItem.json.Type = "CollectionFolder" 'or m.top.parentItem.json.Type = "Folder" if m.top.parentItem.json.Type = "CollectionFolder" 'or m.top.parentItem.json.Type = "Folder"
m.top.HomeLibraryItem = m.top.parentItem.Id m.top.HomeLibraryItem = m.top.parentItem.Id
end if end if
if m.top.parentItem.backdropUrl <> invalid if m.top.parentItem.backdropUrl <> invalid
SetBackground(m.top.parentItem.backdropUrl) SetBackground(m.top.parentItem.backdropUrl)
end if end if
@ -66,6 +92,14 @@ sub loadInitialItems()
if m.top.parentItem.collectionType = "livetv" if m.top.parentItem.collectionType = "livetv"
' Translate between app and server nomenclature ' Translate between app and server nomenclature
viewSetting = get_user_setting("display.livetv.landing") viewSetting = get_user_setting("display.livetv.landing")
'Move mic to be visiable on TV Guide screen
if m.deviFeature = true
m.micButton.translation = "[1540, 92]"
m.micButtonText.visible = true
m.micButtonText.translation = "[1600,130]"
m.micButtonText.font.size = 22
m.micButtonText.text = tr("Search")
end if
if viewSetting = "guide" if viewSetting = "guide"
m.view = "tvGuide" m.view = "tvGuide"
else else
@ -112,24 +146,31 @@ sub loadInitialItems()
end if end if
updateTitle() updateTitle()
m.loadItemsTask.nameStartsWith = m.top.AlphaSelected m.loadItemsTask.nameStartsWith = m.top.alphaSelected
m.loadItemsTask.searchTerm = m.voiceBox.text
m.emptyText.visible = false m.emptyText.visible = false
m.loadItemsTask.sortField = m.sortField m.loadItemsTask.sortField = m.sortField
m.loadItemsTask.sortAscending = m.sortAscending m.loadItemsTask.sortAscending = m.sortAscending
m.loadItemsTask.filter = m.filter m.loadItemsTask.filter = m.filter
m.loadItemsTask.startIndex = 0 m.loadItemsTask.startIndex = 0
' Load Item Types ' Load Item Types
if m.top.parentItem.collectionType = "movies" if getCollectionType() = "movies"
m.loadItemsTask.itemType = "Movie" m.loadItemsTask.itemType = "Movie"
m.loadItemsTask.itemId = m.top.parentItem.Id m.loadItemsTask.itemId = m.top.parentItem.Id
else if m.top.parentItem.collectionType = "tvshows" else if getCollectionType() = "tvshows"
m.loadItemsTask.itemType = "Series" m.loadItemsTask.itemType = "Series"
m.loadItemsTask.itemId = m.top.parentItem.Id m.loadItemsTask.itemId = m.top.parentItem.Id
else if m.top.parentItem.collectionType = "music" else if getCollectionType() = "music"
' Default Settings ' Default Settings
m.loadItemsTask.recursive = false
m.itemGrid.itemSize = "[290, 290]" if m.voiceBox.text <> ""
m.itemGrid.itemSpacing = "[ 0, 20]" m.loadItemsTask.recursive = true
else
m.loadItemsTask.recursive = false
m.itemGrid.itemSize = "[290, 290]"
end if
m.loadItemsTask.itemType = "MusicArtist,MusicAlbum" m.loadItemsTask.itemType = "MusicArtist,MusicAlbum"
m.loadItemsTask.itemId = m.top.parentItem.Id m.loadItemsTask.itemId = m.top.parentItem.Id
@ -143,19 +184,21 @@ sub loadInitialItems()
m.loadItemsTask.recursive = true m.loadItemsTask.recursive = true
end if end if
else if m.top.parentItem.collectionType = "livetv" else if m.top.parentItem.collectionType = "livetv"
m.loadItemsTask.itemType = "LiveTV" m.loadItemsTask.itemType = "TvChannel"
m.loadItemsTask.itemId = " "
' For LiveTV, we want to "Fit" the item images, not zoom ' For LiveTV, we want to "Fit" the item images, not zoom
m.top.imageDisplayMode = "scaleToFit" m.top.imageDisplayMode = "scaleToFit"
if get_user_setting("display.livetv.landing") = "guide" and m.options.view <> "livetv" if get_user_setting("display.livetv.landing") = "guide" and m.options.view <> "livetv"
showTvGuide() showTvGuide()
end if 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 = "Folder" or m.top.parentItem.Type = "Channel" 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"
' Non-recursive, to not show subfolder contents if m.voiceBox.text <> ""
m.loadItemsTask.recursive = false m.loadItemsTask.recursive = true
else if m.top.parentItem.Type = "Channel" else
m.top.imageDisplayMode = "scaleToFit" ' non recursive for collections (folders, boxsets, photo albums, etc)
m.loadItemsTask.recursive = false
end if
else if m.top.parentItem.json.type = "Studio" else if m.top.parentItem.json.type = "Studio"
m.loadItemsTask.itemId = m.top.parentItem.parentFolder m.loadItemsTask.itemId = m.top.parentItem.parentFolder
m.loadItemsTask.itemType = "Series,Movie" m.loadItemsTask.itemType = "Series,Movie"
@ -491,13 +534,33 @@ sub onItemSelected()
m.top.selectedItem = m.itemGrid.content.getChild(m.itemGrid.itemSelected) m.top.selectedItem = m.itemGrid.content.getChild(m.itemGrid.itemSelected)
end sub end sub
sub onItemAlphaSelected() sub onItemalphaSelected()
m.loadedRows = 0 if m.top.alphaSelected <> ""
m.loadedItems = 0 m.loadedRows = 0
m.data = CreateObject("roSGNode", "ContentNode") m.loadedItems = 0
m.itemGrid.content = m.data m.data = CreateObject("roSGNode", "ContentNode")
m.spinner.visible = true m.itemGrid.content = m.data
loadInitialItems() m.loadItemsTask.searchTerm = ""
m.VoiceBox.text = ""
m.loadItemsTask.nameStartsWith = m.alpha.itemAlphaSelected
m.spinner.visible = true
loadInitialItems()
end if
end sub
sub onvoiceFilter()
if m.VoiceBox.text <> ""
m.loadedRows = 0
m.loadedItems = 0
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
m.top.alphaSelected = ""
m.loadItemsTask.NameStartsWith = " "
m.loadItemsTask.searchTerm = m.voiceBox.text
m.loadItemsTask.recursive = true
m.spinner.visible = true
loadInitialItems()
end if
end sub end sub
@ -598,6 +661,7 @@ sub optionsClosed()
if m.tvGuide <> invalid if m.tvGuide <> invalid
m.tvGuide.lastFocus.setFocus(true) m.tvGuide.lastFocus.setFocus(true)
end if end if
end sub end sub
sub showTVGuide() sub showTVGuide()
@ -608,6 +672,7 @@ sub showTVGuide()
m.tvGuide.observeField("focusedChannel", "onChannelFocused") m.tvGuide.observeField("focusedChannel", "onChannelFocused")
end if end if
m.tvGuide.filter = m.filter m.tvGuide.filter = m.filter
m.tvGuide.searchTerm = m.voiceBox.text
m.top.appendChild(m.tvGuide) m.top.appendChild(m.tvGuide)
m.tvGuide.lastFocus.setFocus(true) m.tvGuide.lastFocus.setFocus(true)
end sub end sub
@ -629,6 +694,12 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false if not press then return false
topGrp = m.top.findNode("itemGrid") topGrp = m.top.findNode("itemGrid")
searchGrp = m.top.findNode("voiceBox")
if key = "left" and searchGrp.isinFocusChain()
topGrp.setFocus(true)
searchGrp.setFocus(false)
end if
if key = "options" if key = "options"
if m.options.visible = true if m.options.visible = true
m.options.visible = false m.options.visible = false
@ -655,9 +726,13 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.options.visible = false m.options.visible = false
optionsClosed() optionsClosed()
return true return true
else
m.global.sceneManager.callfunc("popScene")
m.loadItemsTask.control = "stop"
return true
end if end if
else if key = "play" or key = "OK" else if key = "play" or key = "OK"
markupGrid = m.top.getChild(2) markupGrid = m.top.findNode("itemGrid")
itemToPlay = markupGrid.content.getChild(markupGrid.itemFocused) itemToPlay = markupGrid.content.getChild(markupGrid.itemFocused)
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode") if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
@ -673,9 +748,10 @@ function onKeyEvent(key as string, press as boolean) as boolean
else if key = "left" and topGrp.isinFocusChain() else if key = "left" and topGrp.isinFocusChain()
m.top.alphaActive = true m.top.alphaActive = true
topGrp.setFocus(false) topGrp.setFocus(false)
alpha = m.Alpha.getChild(0).findNode("Alphamenu") alpha = m.alpha.getChild(0).findNode("Alphamenu")
alpha.setFocus(true) alpha.setFocus(true)
return true return true
else if key = "right" and m.Alpha.isinFocusChain() else if key = "right" and m.Alpha.isinFocusChain()
m.top.alphaActive = false m.top.alphaActive = false
m.Alpha.setFocus(false) m.Alpha.setFocus(false)
@ -690,6 +766,20 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if end if
end if end if
if key = "replay"
m.spinner.visible = true
m.loadItemsTask.searchTerm = ""
m.loadItemsTask.nameStartsWith = ""
m.voiceBox.text = ""
m.top.alphaSelected = ""
m.loadItemsTask.filter = "All"
m.filter = "All"
m.data = CreateObject("roSGNode", "ContentNode")
m.itemGrid.content = m.data
loadInitialItems()
return true
end if
return false return false
end function end function
@ -699,9 +789,11 @@ sub updateTitle()
else if m.filter = "Favorites" else if m.filter = "Favorites"
m.top.overhangTitle = m.top.parentItem.title + " " + tr("(Favorites)") m.top.overhangTitle = m.top.parentItem.title + " " + tr("(Favorites)")
end if end if
if m.voiceBox.text <> ""
if m.top.AlphaSelected <> "" m.top.overhangTitle = m.top.parentItem.title + tr(" (Filtered by ") + m.loadItemsTask.searchTerm + ")"
m.top.overhangTitle = m.top.parentItem.title + " " + tr("(Filtered)") end if
if m.top.alphaSelected <> ""
m.top.overhangTitle = m.top.parentItem.title + tr(" (Filtered by ") + m.loadItemsTask.nameStartsWith + ")"
end if end if
if m.options.view = "Networks" or m.view = "Networks" if m.options.view = "Networks" or m.view = "Networks"

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<component name="ItemGrid" extends="JFGroup"> <component name="ItemGrid" extends="JFGroup">
<children> <children>
<VoiceTextEditBox id="VoiceBox" visible="true" width = "40" translation = "[52, 120]" />
<Rectangle id="VoiceBoxCover" height="240" width="100" color="0x262626ff" translation = "[25, 75]" />
<poster id="backdrop" <poster id="backdrop"
loadDisplayMode="scaleToFill" loadDisplayMode="scaleToFill"
width="1920" width="1920"
@ -23,10 +24,12 @@
vertFocusAnimationStyle = "fixed" vertFocusAnimationStyle = "fixed"
itemSize = "[ 290, 425 ]" itemSize = "[ 290, 425 ]"
itemSpacing = "[ 0, 45 ]" itemSpacing = "[ 0, 45 ]"
drawFocusFeedback = "false" /> drawFocusFeedback = "false" />
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1920" horizAlign="center" vertAlign="center" height="64" visible="false" /> <Label id="micButtonText" font="font:SmallSystemFont" visible="false" />
<Button id = "micButton" maxWidth = "20" translation = "[20, 120]" iconUri = "pkg:/images/icons/mic_icon.png"/>
<Label translation="[0,540]" id="emptyText" font="font:LargeSystemFont" width="1910" horizAlign="center" vertAlign="center" height="64" visible="false" />
<ItemGridOptions id="options" visible="false" /> <ItemGridOptions id="options" visible="false" />
<Spinner id="spinner" translation="[920, 540]" /> <Spinner id="spinner" translation="[900, 450]" />
<Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear" > <Animation id="backroundSwapAnimation" duration="1" repeat="false" easeFunction="linear" >
<FloatFieldInterpolator id = "fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 0.25 ]" fieldToInterp="backdropTransition.opacity" /> <FloatFieldInterpolator id = "fadeinLoading" key="[0.0, 1.0]" keyValue="[ 0.00, 0.25 ]" fieldToInterp="backdropTransition.opacity" />
<FloatFieldInterpolator id = "fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 0.25, 0.00 ]" fieldToInterp="backdrop.opacity" /> <FloatFieldInterpolator id = "fadeoutLoaded" key="[0.0, 1.0]" keyValue="[ 0.25, 0.00 ]" fieldToInterp="backdrop.opacity" />

View File

@ -1,7 +1,7 @@
sub init() sub init()
m.buttons = m.top.findNode("buttons") m.buttons = m.top.findNode("buttons")
m.buttons.buttons = [tr("TAB_VIEW"), tr("TAB_SORT"), tr("TAB_FILTER")] m.buttons.buttons = [tr("View"), tr("Sort"), tr("Filter")]
m.buttons.selectedIndex = 1 m.buttons.selectedIndex = 1
m.buttons.setFocus(true) m.buttons.setFocus(true)

View File

@ -1,9 +1,15 @@
sub init() sub init()
m.top.functionName = "loadItems" m.top.functionName = "loadItems"
m.top.limit = 60
usersettingLimit = get_user_setting("itemgrid.Limit")
if usersettingLimit <> invalid
m.top.limit = usersettingLimit
end if
end sub end sub
sub loadItems() sub loadItems()
results = [] results = []
sort_field = m.top.sortField sort_field = m.top.sortField
@ -26,15 +32,30 @@ sub loadItems()
StudioIds: m.top.studioIds, StudioIds: m.top.studioIds,
genreIds: m.top.genreIds genreIds: m.top.genreIds
} }
' Handle special case when getting names starting with numeral ' Handle special case when getting names starting with numeral
if m.top.NameStartsWith <> "" if m.top.NameStartsWith <> ""
if m.top.NameStartsWith = "#" if m.top.NameStartsWith = "#"
params.NameLessThan = "A" if m.top.ItemType = "LiveTV" or m.top.ItemType = "TvChannel"
params.searchterm = "A"
params.append({ parentid: " " })
else
params.NameLessThan = "A"
end if
else else
params.NameStartsWith = m.top.nameStartsWith if m.top.ItemType = "LiveTV" or m.top.ItemType = "TvChannel"
params.searchterm = m.top.nameStartsWith
params.append({ parentid: " " })
else
params.NameStartsWith = m.top.nameStartsWith
end if
end if end if
end if end if
if m.top.searchTerm <> ""
params.searchTerm = m.top.searchTerm
end if
filter = m.top.filter filter = m.top.filter
if filter = "All" or filter = "all" if filter = "All" or filter = "all"
' do nothing ' do nothing
@ -71,7 +92,7 @@ sub loadItems()
tmp = CreateObject("roSGNode", "MovieData") tmp = CreateObject("roSGNode", "MovieData")
else if item.Type = "Series" else if item.Type = "Series"
tmp = CreateObject("roSGNode", "SeriesData") tmp = CreateObject("roSGNode", "SeriesData")
else if item.Type = "BoxSet" else if item.Type = "BoxSet" or item.Type = "ManualPlaylistsFolder"
tmp = CreateObject("roSGNode", "CollectionData") tmp = CreateObject("roSGNode", "CollectionData")
else if item.Type = "TvChannel" else if item.Type = "TvChannel"
tmp = CreateObject("roSGNode", "ChannelData") tmp = CreateObject("roSGNode", "ChannelData")

View File

@ -12,6 +12,7 @@
<field id="nameStartsWith" type="string" value="" /> <field id="nameStartsWith" type="string" value="" />
<field id="recursive" type="boolean" value="true" /> <field id="recursive" type="boolean" value="true" />
<field id="filter" type="string" value="All" /> <field id="filter" type="string" value="All" />
<field id="searchTerm" type="string" value="" />
<field id="studioIds" type="string" value="" /> <field id="studioIds" type="string" value="" />
<field id="genreIds" type="string" value="" /> <field id="genreIds" type="string" value="" />
<field id="view" type="string" value="" /> <field id="view" type="string" value="" />

View File

@ -1,6 +1,7 @@
sub init() sub init()
m.title = m.top.findNode("title") m.title = m.top.findNode("title")
m.staticTitle = m.top.findNode("staticTitle") m.staticTitle = m.top.findNode("staticTitle")
m.series = m.top.findNode("Series")
m.poster = m.top.findNode("poster") m.poster = m.top.findNode("poster")
m.backdrop = m.top.findNode("backdrop") m.backdrop = m.top.findNode("backdrop")
@ -39,6 +40,8 @@ sub updateSize()
m.staticTitle.height = m.title.height m.staticTitle.height = m.title.height
m.staticTitle.translation = m.title.translation m.staticTitle.translation = m.title.translation
m.series.maxWidth = maxSize[0]
m.poster.width = int(maxSize[0]) - 4 m.poster.width = int(maxSize[0]) - 4
m.poster.height = int(maxSize[1]) - m.title.height 'Set poster height to available space m.poster.height = int(maxSize[1]) - m.title.height 'Set poster height to available space
@ -54,9 +57,14 @@ sub itemContentChanged() as void
if itemData.json.lookup("Type") = "Episode" and itemData.json.IndexNumber <> invalid if itemData.json.lookup("Type") = "Episode" and itemData.json.IndexNumber <> invalid
m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text
m.series.text = itemData.json.Series
m.series.visible = true
else if itemData.json.lookup("Type") = "MusicAlbum" else if itemData.json.lookup("Type") = "MusicAlbum"
m.title.font = "font:SmallestSystemFont" m.title.font = "font:SmallestSystemFont"
m.staticTitle.font = "font:SmallestSystemFont" m.staticTitle.font = "font:SmallestSystemFont"
end if end if
m.staticTitle.text = m.title.text m.staticTitle.text = m.title.text
@ -64,7 +72,7 @@ sub itemContentChanged() as void
if get_user_setting("ui.tvshows.blurunwatched") = "true" if get_user_setting("ui.tvshows.blurunwatched") = "true"
if itemData.json.lookup("Type") = "Episode" if itemData.json.lookup("Type") = "Episode" and itemData.json.userdata <> invalid
if not itemData.json.userdata.played if not itemData.json.userdata.played
imageUrl = imageUrl + "&blur=15" imageUrl = imageUrl + "&blur=15"
end if end if
@ -82,6 +90,7 @@ sub focusChanged()
if m.top.itemHasFocus = true if m.top.itemHasFocus = true
m.title.repeatCount = -1 m.title.repeatCount = -1
m.series.repeatCount = -1
m.staticTitle.visible = false m.staticTitle.visible = false
m.title.visible = true m.title.visible = true
@ -94,6 +103,7 @@ sub focusChanged()
else else
m.title.repeatCount = 0 m.title.repeatCount = 0
m.series.repeatCount = 0
m.staticTitle.visible = true m.staticTitle.visible = true
m.title.visible = false m.title.visible = false
end if end if

View File

@ -2,6 +2,12 @@
<component name="ListPoster" extends="Group"> <component name="ListPoster" extends="Group">
<children> <children>
<Rectangle id="backdrop" /> <Rectangle id="backdrop" />
<ScrollingLabel id="Series"
horizAlign="center"
font="font:SmallSystemFont"
repeatCount="0"
visible="false"
/>
<Poster id="poster" translation="[2,0]" loadDisplayMode="scaleToFit" /> <Poster id="poster" translation="[2,0]" loadDisplayMode="scaleToFit" />
<ScrollingLabel id="title" <ScrollingLabel id="title"
horizAlign="center" horizAlign="center"

View File

@ -154,7 +154,7 @@ sub createDialogPallete()
DialogSecondaryTextColor: "0xf8f8f8ff", DialogSecondaryTextColor: "0xf8f8f8ff",
DialogSecondaryItemColor: "0xcc7ecc4D", DialogSecondaryItemColor: "0xcc7ecc4D",
DialogInputFieldColor: "0x80FF8080", DialogInputFieldColor: "0x80FF8080",
DialogKeyboardColor: "0x80FF804D", KeyboardDialogColor: "0x80FF804D",
DialogFootprintColor: "0x80FF804D" DialogFootprintColor: "0x80FF804D"
} }
end sub end sub

View File

@ -3,48 +3,25 @@ sub init()
m.top.horizAlignment = "center" m.top.horizAlignment = "center"
m.top.vertAlignment = "top" m.top.vertAlignment = "top"
m.top.visible = false m.top.visible = false
m.searchText = m.top.findNode("search_Key")
m.searchText.textEditBox.hintText = tr("Search")
m.searchText.keyGrid.keyDefinitionUri = "pkg:/components/data/CustomAddressKDF.json"
m.searchText.textEditBox.voiceEnabled = true
m.searchText.textEditBox.active = true
m.searchText.ObserveField("text", "searchMedias")
m.searchSelect = m.top.findNode("searchSelect")
'set lable text
m.label = m.top.findNode("text")
m.label.text = tr("Search now")
show_dialog()
end sub end sub
function onKeyEvent(key as string, press as boolean) as boolean sub searchMedias()
if not press then return false m.top.search_values = m.searchText.text
if m.top.search_values.len() > 1
if key = "OK" m.searchText.textEditBox.leadingEllipsis = true
' Make a Keyboard Dialog here else
show_dialog() m.searchText.textEditBox.leadingEllipsis = false
return true
end if end if
return false
end function
function onDialogButton()
d = m.top.getScene().dialog
button_text = d.buttons[d.buttonSelected]
if button_text = tr("Search")
m.top.search_value = d.text
dismiss_dialog()
return true
else if button_text = tr("Cancel")
dismiss_dialog()
return true
end if
return false
end function
sub show_dialog()
dialog = CreateObject("roSGNode", "KeyboardDialog")
dialog.title = tr("Search")
dialog.buttons = [tr("Search"), tr("Cancel")]
m.top.getScene().dialog = dialog
dialog.observeField("buttonselected", "onDialogButton")
end sub
sub dismiss_dialog()
m.top.getScene().dialog.close = true
end sub end sub

View File

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

View File

@ -45,13 +45,18 @@ end function
sub show_dialog(configField) sub show_dialog(configField)
dialog = createObject("roSGNode", "KeyboardDialog") dialog = createObject("roSGNode", "StandardKeyboardDialog")
m.configField = configField m.configField = configField
dialog.title = "Enter the " + configField.label dialog.title = "Enter the " + configField.label
dialog.buttons = [tr("OK"), tr("Cancel")] dialog.buttons = [tr("OK"), tr("Cancel")]
m.greenPalette = createObject("roSGNode", "RSGPalette")
m.greenPalette.colors = {
DialogBackgroundColor: "#2A2B2A"
}
dialog.palette = m.greenPalette
if configField.type = "password" if configField.type = "password"
dialog.keyboard.textEditBox.secureMode = true dialog.textEditBox.secureMode = true
end if end if
if configField.value <> "" if configField.value <> ""

View File

@ -107,10 +107,13 @@ sub ScanForServersComplete(event)
end sub end sub
sub ShowKeyboard() sub ShowKeyboard()
dialog = createObject("roSGNode", "KeyboardDialog") dialog = createObject("roSGNode", "StandardKeyboardDialog")
dialog.title = tr("Enter the server name or ip address") dialog.title = tr("Enter the server name or ip address")
dialog.buttons = [tr("OK"), tr("Cancel")] dialog.buttons = [tr("OK"), tr("Cancel")]
dialog.text = m.serverUrlTextbox.text dialog.text = m.serverUrlTextbox.text
greenPalette = createObject("roSGNode", "RSGPalette")
greenPalette.colors = { DialogBackgroundColor: "#2A2B2A" }
dialog.palette = greenPalette
m.top.getscene().dialog = dialog m.top.getscene().dialog = dialog
m.dialog = dialog m.dialog = dialog

View File

@ -0,0 +1,96 @@
{
"keyboardWidthFHD": 374,
"keyboardHeightFHD": 409,
"keyboardWidthHD": 250,
"keyboardHeightHD": 273,
"sections": [
{
"grids": [
{
"rows": [
{
"keys": [
{ "label": "a" },
{ "label": "b" },
{ "label": "c" },
{ "label": "d" },
{ "label": "e" },
{ "label": "f" }
]
},
{
"keys": [
{ "label": "g" },
{ "label": "h" },
{ "label": "i" },
{ "label": "j" },
{ "label": "k" },
{ "label": "l" }
]
},
{
"keys": [
{ "label": "m" },
{ "label": "n" },
{ "label": "o" },
{ "label": "p" },
{ "label": "q" },
{ "label": "r" }
]
},
{
"keys": [
{ "label": "s" },
{ "label": "t" },
{ "label": "u" },
{ "label": "v" },
{ "label": "w" },
{ "label": "x" }
]
},
{
"keys": [
{ "label": "y" },
{ "label": "z" },
{ "label": "1" },
{ "label": "2" },
{ "label": "3" },
{ "label": "4" }
]
},
{
"keys": [
{ "label": "5" },
{ "label": "6" },
{ "label": "7" },
{ "label": "8" },
{ "label": "9" },
{ "label": "0" }
]
},
{
"keys": [
{
"icon": "theme:DKB_ClearKeyBitmap",
"focusIcon": "theme:DKB_ClearKeyFocusBitmap",
"strOut": "clear"
},
{
"icon": "theme:DKB_SpaceKeyBitmap",
"focusIcon": "theme:DKB_SpaceKeyFocusBitmap",
"strOut": "space"
},
{
"icon": "theme:DKB_DeleteKeyBitmap",
"focusIcon": "theme:DKB_DeleteKeyFocusBitmap",
"autoRepeat": 1,
"strOut": "backspace"
}
]
}
]
}
]
}
]
}

View File

@ -11,6 +11,7 @@
<field id="type" type="string" value="Episode" /> <field id="type" type="string" value="Episode" />
<field id="json" type="assocarray" onChange="setFields" /> <field id="json" type="assocarray" onChange="setFields" />
<field id="selectedAudioStreamIndex" type="integer" /> <field id="selectedAudioStreamIndex" type="integer" />
<field id="favorite" type="boolean" />
</interface> </interface>
<script type="text/brightscript" uri="TVEpisodeData.brs" /> <script type="text/brightscript" uri="TVEpisodeData.brs" />
</component> </component>

View File

@ -54,6 +54,22 @@ sub loadItems()
params["ImageTypeLimit"] = 1 params["ImageTypeLimit"] = 1
params["UserId"] = get_setting("active_user") params["UserId"] = get_setting("active_user")
maxDaysInNextUp = get_user_setting("ui.details.maxdaysnextup", "365")
if isValid(maxDaysInNextUp)
maxDaysInNextUp = Val(maxDaysInNextUp)
if maxDaysInNextUp > 0
dateToday = CreateObject("roDateTime")
dateCutoff = CreateObject("roDateTime")
dateCutoff.FromSeconds(dateToday.AsSeconds() - (maxDaysInNextUp * 86400))
params["NextUpDateCutoff"] = dateCutoff.ToISOString()
params["EnableRewatching"] = false
params["DisableFirstEpisode"] = false
params["limit"] = 24
end if
end if
resp = APIRequest(url, params) resp = APIRequest(url, params)
data = getJson(resp) data = getJson(resp)
for each item in data.Items for each item in data.Items

View File

@ -12,6 +12,7 @@
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" /> <script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" /> <script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" /> <script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" /> <script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" /> <script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
</component> </component>

View File

@ -6,15 +6,41 @@ sub loadChannels()
results = [] results = []
sort_field = m.top.sortField
if m.top.sortAscending = true
sort_order = "Ascending"
else
sort_order = "Descending"
end if
params = { params = {
includeItemTypes: "LiveTvChannel",
SortBy: sort_field,
SortOrder: sort_order,
recursive: m.top.recursive,
UserId: get_setting("active_user") UserId: get_setting("active_user")
} }
' Handle special case when getting names starting with numeral
if m.top.NameStartsWith <> ""
if m.top.NameStartsWith = "#"
params.searchterm = "A"
else
params.searchterm = m.top.nameStartsWith
end if
end if
'Append voice search when there is text
if m.top.searchTerm <> ""
params.searchTerm = m.top.searchTerm
end if
if m.top.filter = "Favorites" if m.top.filter = "Favorites"
params.append({ isFavorite: true }) params.append({ isFavorite: true })
end if end if
url = "LiveTv/Channels" url = Substitute("Users/{0}/Items/", get_setting("active_user"))
resp = APIRequest(url, params) resp = APIRequest(url, params)
data = getJson(resp) data = getJson(resp)
@ -39,7 +65,5 @@ sub loadChannels()
results.push(channel) results.push(channel)
end if end if
end for end for
m.top.channels = results m.top.channels = results
end sub end sub

View File

@ -5,7 +5,11 @@
<field id="limit" type="integer" value="" /> <field id="limit" type="integer" value="" />
<field id="startIndex" type="integer" value="0" /> <field id="startIndex" type="integer" value="0" />
<field id="filter" type="string" value="All" /> <field id="filter" type="string" value="All" />
<field id="searchTerm" type="string" value="" />
<field id="sortField" type="string" value="SortName" />
<field id="sortAscending" type="boolean" value="true" />
<field id="nameStartsWith" type="string" value="" />
<field id="recursive" type="boolean" value="true" />
<!-- Total records available from server--> <!-- Total records available from server-->
<field id="channels" type="array" /> <field id="channels" type="array" />
</interface> </interface>

View File

@ -1,5 +1,4 @@
sub init() sub init()
m.EPGLaunchCompleteSignaled = false m.EPGLaunchCompleteSignaled = false
m.scheduleGrid = m.top.findNode("scheduleGrid") m.scheduleGrid = m.top.findNode("scheduleGrid")
m.detailsPane = m.top.findNode("detailsPane") m.detailsPane = m.top.findNode("detailsPane")
@ -7,7 +6,6 @@ sub init()
m.detailsPane.observeField("watchSelectedChannel", "onWatchChannelSelected") m.detailsPane.observeField("watchSelectedChannel", "onWatchChannelSelected")
m.detailsPane.observeField("recordSelectedChannel", "onRecordChannelSelected") m.detailsPane.observeField("recordSelectedChannel", "onRecordChannelSelected")
m.detailsPane.observeField("recordSeriesSelectedChannel", "onRecordSeriesChannelSelected") m.detailsPane.observeField("recordSeriesSelectedChannel", "onRecordSeriesChannelSelected")
m.gridStartDate = CreateObject("roDateTime") m.gridStartDate = CreateObject("roDateTime")
m.scheduleGrid.contentStartTime = m.gridStartDate.AsSeconds() - 1800 m.scheduleGrid.contentStartTime = m.gridStartDate.AsSeconds() - 1800
m.gridEndDate = createObject("roDateTime") m.gridEndDate = createObject("roDateTime")
@ -28,10 +26,11 @@ sub init()
m.top.lastFocus = m.scheduleGrid m.top.lastFocus = m.scheduleGrid
m.channelIndex = {} m.channelIndex = {}
m.spinner = m.top.findNode("spinner")
end sub end sub
sub channelFilterSet() sub channelFilterSet()
print "Channel Filter set"
m.scheduleGrid.jumpToChannel = 0 m.scheduleGrid.jumpToChannel = 0
if m.top.filter <> invalid and m.LoadChannelsTask.filter <> m.top.filter if m.top.filter <> invalid and m.LoadChannelsTask.filter <> m.top.filter
if m.LoadChannelsTask.state = "run" then m.LoadChannelsTask.control = "stop" if m.LoadChannelsTask.state = "run" then m.LoadChannelsTask.control = "stop"
@ -42,6 +41,19 @@ sub channelFilterSet()
end sub end sub
'Voice Search set
sub channelsearchTermSet()
m.scheduleGrid.jumpToChannel = 0
if m.top.searchTerm <> invalid and m.LoadChannelsTask.searchTerm <> m.top.searchTerm
if m.LoadChannelsTask.state = "run" then m.LoadChannelsTask.control = "stop"
m.LoadChannelsTask.searchTerm = m.top.searchTerm
m.spinner.visible = true
m.LoadChannelsTask.control = "RUN"
end if
end sub
' Initial list of channels loaded ' Initial list of channels loaded
sub onChannelsLoaded() sub onChannelsLoaded()
gridData = createObject("roSGNode", "ContentNode") gridData = createObject("roSGNode", "ContentNode")
@ -49,32 +61,36 @@ sub onChannelsLoaded()
counter = 0 counter = 0
channelIdList = "" channelIdList = ""
for each item in m.LoadChannelsTask.channels 'if search returns channels
gridData.appendChild(item) if m.LoadChannelsTask.channels.count() > 0
m.channelIndex[item.Id] = counter for each item in m.LoadChannelsTask.channels
counter = counter + 1 gridData.appendChild(item)
channelIdList = channelIdList + item.Id + "," m.channelIndex[item.Id] = counter
end for counter = counter + 1
channelIdList = channelIdList + item.Id + ","
end for
m.scheduleGrid.content = gridData
m.scheduleGrid.content = gridData m.LoadScheduleTask = createObject("roSGNode", "LoadScheduleTask")
m.LoadScheduleTask.observeField("schedule", "onScheduleLoaded")
m.LoadScheduleTask = createObject("roSGNode", "LoadScheduleTask") m.LoadScheduleTask.startTime = m.gridStartDate.ToISOString()
m.LoadScheduleTask.observeField("schedule", "onScheduleLoaded") m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
m.LoadScheduleTask.channelIds = channelIdList
m.LoadScheduleTask.control = "RUN"
m.LoadScheduleTask.startTime = m.gridStartDate.ToISOString() m.LoadProgramDetailsTask = createObject("roSGNode", "LoadProgramDetailsTask")
m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString() m.LoadProgramDetailsTask.observeField("programDetails", "onProgramDetailsLoaded")
m.LoadScheduleTask.channelIds = channelIdList
m.LoadScheduleTask.control = "RUN"
m.LoadProgramDetailsTask = createObject("roSGNode", "LoadProgramDetailsTask") m.scheduleGrid.setFocus(true)
m.LoadProgramDetailsTask.observeField("programDetails", "onProgramDetailsLoaded") if m.EPGLaunchCompleteSignaled = false
m.top.signalBeacon("EPGLaunchComplete") ' Required Roku Performance monitoring
m.EPGLaunchCompleteSignaled = true
end if
m.LoadChannelsTask.channels = []
m.scheduleGrid.setFocus(true)
if m.EPGLaunchCompleteSignaled = false
m.top.signalBeacon("EPGLaunchComplete") ' Required Roku Performance monitoring
m.EPGLaunchCompleteSignaled = true
end if end if
m.LoadChannelsTask.channels = []
end sub end sub
' When LoadScheduleTask completes (initial or more data) and we have a schedule to display ' When LoadScheduleTask completes (initial or more data) and we have a schedule to display
@ -102,6 +118,7 @@ sub onScheduleLoaded()
m.scheduleGrid.showLoadingDataFeedback = false m.scheduleGrid.showLoadingDataFeedback = false
m.scheduleGrid.setFocus(true) m.scheduleGrid.setFocus(true)
m.LoadScheduleTask.schedule = [] m.LoadScheduleTask.schedule = []
m.spinner.visible = false
end sub end sub
sub onProgramFocused() sub onProgramFocused()
@ -118,7 +135,9 @@ sub onProgramFocused()
m.top.focusedChannel = channel m.top.focusedChannel = channel
' Exit if Channels not yet loaded ' Exit if Channels not yet loaded
if channel = invalid or channel.getChildCount() = 0 if channel = invalid or channel.getChildCount() = 0
m.detailsPane.programDetails = invalid m.detailsPane.programDetails = invalid
return return
end if end if
@ -195,6 +214,22 @@ sub onWatchChannelSelected()
m.top.watchChannel = m.detailsPane.channel m.top.watchChannel = m.detailsPane.channel
end sub end sub
' As user scrolls grid, check if more data requries to be loaded
sub onGridScrolled()
' If we're within 12 hours of end of grid, load next 24hrs of data
if m.scheduleGrid.leftEdgeTargetTime + (12 * 60 * 60) > m.gridEndDate.AsSeconds()
' Ensure the task is not already (still) running,
if m.LoadScheduleTask.state <> "run"
m.LoadScheduleTask.startTime = m.gridEndDate.ToISOString()
m.gridEndDate.FromSeconds(m.gridEndDate.AsSeconds() + (24 * 60 * 60))
m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
m.LoadScheduleTask.control = "RUN"
end if
end if
end sub
' Handle user selecting "Record Channel" from Program Details ' Handle user selecting "Record Channel" from Program Details
sub onRecordChannelSelected() sub onRecordChannelSelected()
if m.detailsPane.recordSelectedChannel = false then return if m.detailsPane.recordSelectedChannel = false then return
@ -242,27 +277,18 @@ sub onRecordOperationDone()
end if end if
end sub end sub
' As user scrolls grid, check if more data requries to be loaded
sub onGridScrolled()
' If we're within 12 hours of end of grid, load next 24hrs of data
if m.scheduleGrid.leftEdgeTargetTime + (12 * 60 * 60) > m.gridEndDate.AsSeconds()
' Ensure the task is not already (still) running,
if m.LoadScheduleTask.state <> "run"
m.LoadScheduleTask.startTime = m.gridEndDate.ToISOString()
m.gridEndDate.FromSeconds(m.gridEndDate.AsSeconds() + (24 * 60 * 60))
m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
m.LoadScheduleTask.control = "RUN"
end if
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false if not press then return false
detailsGrp = m.top.findNode("detailsPane")
gridGrp = m.top.findNode("scheduleGrid")
if key = "back" and m.detailsPane.isInFocusChain() if key = "back" and detailsGrp.isInFocusChain()
focusProgramDetails(false) focusProgramDetails(false)
detailsGrp.setFocus(false)
gridGrp.setFocus(true)
return true
else if key = "back"
m.global.sceneManager.callFunc("popScene")
return true return true
end if end if

View File

@ -12,6 +12,7 @@
programTitleFocusedColor="#ffffff" iconFocusedColor="#ff0000" programTitleFocusedColor="#ffffff" iconFocusedColor="#ff0000"
showPastTimeScreen="true" pastTimeScreenBlendColor="#555555" showPastTimeScreen="true" pastTimeScreenBlendColor="#555555"
/> />
<Spinner id="spinner" translation="[900, 450]" />
<Animation id="gridMoveAnimation" duration="1" repeat="false" easeFunction="outQuad" > <Animation id="gridMoveAnimation" duration="1" repeat="false" easeFunction="outQuad" >
<Vector2DFieldInterpolator id="gridMoveAnimationPosition" key="[0.0, 0.5]" fieldToInterp="scheduleGrid.translation" /> <Vector2DFieldInterpolator id="gridMoveAnimationPosition" key="[0.0, 0.5]" fieldToInterp="scheduleGrid.translation" />
</Animation> </Animation>
@ -20,6 +21,7 @@
<field id="watchChannel" type="node" alwaysNotify="false" /> <field id="watchChannel" type="node" alwaysNotify="false" />
<field id="focusedChannel" type="node" alwaysNotify="false" /> <field id="focusedChannel" type="node" alwaysNotify="false" />
<field id="filter" type="string" value="All" onChange="channelFilterSet" /> <field id="filter" type="string" value="All" onChange="channelFilterSet" />
<field id="searchTerm" type="string" value="" onChange="channelsearchTermSet" />
</interface> </interface>
<script type="text/brightscript" uri="schedule.brs" /> <script type="text/brightscript" uri="schedule.brs" />
</component> </component>

View File

@ -46,8 +46,9 @@ sub itemContentChanged()
m.top.findNode("moviePoster").uri = m.top.itemContent.posterURL m.top.findNode("moviePoster").uri = m.top.itemContent.posterURL
' Set default video source ' Set default video source
m.top.selectedVideoStreamId = itemData.MediaSources[0].id if itemData.MediaSources <> invalid
m.top.selectedVideoStreamId = itemData.MediaSources[0].id
end if
' Find first Audio Stream and set that as default ' Find first Audio Stream and set that as default
SetDefaultAudioTrack(itemData) SetDefaultAudioTrack(itemData)

View File

@ -14,6 +14,7 @@ sub init()
m.loopMode = "" m.loopMode = ""
m.shuffleEvent = "" m.shuffleEvent = ""
m.buttonCount = m.buttons.getChildCount() m.buttonCount = m.buttons.getChildCount()
m.playReported = false
m.screenSaverTimeout = 300 m.screenSaverTimeout = 300
@ -198,6 +199,24 @@ sub endScreenSaver()
end sub end sub
sub audioStateChanged() sub audioStateChanged()
if m.top.audio.state = "playing"
if m.playReported
ReportPlayback()
else
ReportPlayback("start")
m.playReported = true
end if
else if m.top.audio.state = "paused"
ReportPlayback()
else if m.top.audio.state = "stopped"
ReportPlayback("stop")
m.playReported = false
else if m.top.audio.state = "finished"
ReportPlayback("stop")
m.playReported = false
end if
' Song Finished, attempt to move to next song ' Song Finished, attempt to move to next song
if m.top.audio.state = "finished" if m.top.audio.state = "finished"
if m.loopMode = "one" if m.loopMode = "one"
@ -240,6 +259,10 @@ function playAction() as boolean
end function end function
function previousClicked() as boolean function previousClicked() as boolean
if m.top.audio.state = "playing"
m.top.audio.control = "stop"
end if
if m.currentSongIndex > 0 if m.currentSongIndex > 0
m.currentSongIndex-- m.currentSongIndex--
pageContentChanged() pageContentChanged()
@ -331,6 +354,10 @@ function shuffleClicked() as boolean
end function end function
sub LoadNextSong() sub LoadNextSong()
if m.top.audio.state = "playing"
m.top.audio.control = "stop"
end if
' Reset playPosition bar without animation ' Reset playPosition bar without animation
m.playPosition.width = 0 m.playPosition.width = 0
m.currentSongIndex++ m.currentSongIndex++
@ -362,7 +389,6 @@ sub onAudioStreamLoaded()
m.LoadAudioStreamTask.unobserveField("content") m.LoadAudioStreamTask.unobserveField("content")
if data <> invalid and data.count() > 0 if data <> invalid and data.count() > 0
m.top.audio.content = data m.top.audio.content = data
m.top.audio.control = "stop"
m.top.audio.control = "none" m.top.audio.control = "none"
m.top.audio.control = "play" m.top.audio.control = "play"
end if end if
@ -382,7 +408,7 @@ sub onMetaDataLoaded()
if data <> invalid and data.count() > 0 if data <> invalid and data.count() > 0
' Use metadata to load backdrop image ' Use metadata to load backdrop image
if isvalid(data?.json?.ArtistItems?[0].id) if isvalid(data?.json?.ArtistItems?[0]?.id)
m.LoadBackdropImageTask.itemId = data.json.ArtistItems[0].id m.LoadBackdropImageTask.itemId = data.json.ArtistItems[0].id
m.LoadBackdropImageTask.observeField("content", "onBackdropImageLoaded") m.LoadBackdropImageTask.observeField("content", "onBackdropImageLoaded")
m.LoadBackdropImageTask.control = "RUN" m.LoadBackdropImageTask.control = "RUN"
@ -515,3 +541,21 @@ sub OnScreenHidden()
WriteAsciiFile("tmp:/scene.temp", "") WriteAsciiFile("tmp:/scene.temp", "")
MoveFile("tmp:/scene.temp", "tmp:/scene") MoveFile("tmp:/scene.temp", "tmp:/scene")
end sub end sub
' Report playback to server
sub ReportPlayback(state = "update" as string)
if m.top.audio.position = invalid then return
params = {
"ItemId": m.top.pageContent[m.currentSongIndex],
"PlaySessionId": m.top.audio.content.id,
"PositionTicks": int(m.top.audio.position) * 10000000&, 'Ensure a LongInteger is used
"IsPaused": (m.top.audio.state = "paused")
}
' Report playstate via worker task
playstateTask = m.global.playstateTask
playstateTask.setFields({ status: state, params: params })
playstateTask.control = "RUN"
end sub

View File

@ -1,3 +1,60 @@
sub init() sub init()
m.top.optionsAvailable = false m.top.optionsAvailable = false
m.searchSpinner = m.top.findnode("searchSpinner")
m.searchSelect = m.top.findnode("searchSelect")
m.searchTask = CreateObject("roSGNode", "SearchTask")
'set label text
m.searchHelpText = m.top.findNode("SearchHelpText")
m.searchHelpText.text = tr("You can search for Titles, People, Live TV Channels and more")
end sub end sub
sub searchMedias()
query = m.top.searchAlpha
'if user deletes the search string hide the spinner
if query.len() = 0
m.searchSpinner.visible = false
end if
'if search task is running and user selectes another letter stop the search and load the next letter
m.searchTask.control = "stop"
if query <> invalid and query <> ""
m.searchSpinner.visible = true
end if
m.searchTask.observeField("results", "loadResults")
m.searchTask.query = query
m.top.overhangTitle = tr("Search") + ": " + query
m.searchTask.control = "RUN"
end sub
sub loadResults()
m.searchTask.unobserveField("results")
m.searchSpinner.visible = false
m.searchSelect.itemdata = m.searchTask.results
m.searchSelect.query = m.top.SearchAlpha
m.searchHelpText.visible = false
m.searchAlphabox = m.top.findnode("searchResults")
m.searchAlphabox.translation = "[470, 85]"
end sub
function onKeyEvent(key as string, press as boolean) as boolean
m.searchAlphabox = m.top.findNode("search_Key")
if m.searchAlphabox.textEditBox.hasFocus()
m.searchAlphabox.textEditBox.translation = "[0, -150]"
else
m.searchAlphabox.textEditBox.translation = "[0, 0]"
end if
if key = "left" and m.searchSelect.isinFocusChain() and (m.searchSelect.currFocusColumn = -1 or m.searchSelect.currFocusColumn = 0)
m.searchAlphabox.setFocus(true)
return true
else if key = "right"
m.searchSelect.setFocus(true)
return true
end if
return false
end function

View File

@ -1,13 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<component name="SearchResults" extends="JFGroup"> <component name="searchResults" extends="JFGroup">
<children> <children>
<SearchBox id="SearchBox" visible="true" translation="[960, 145]" /> <Rectangle width="1920" height="1080" color="#000000" opacity="0.75" />
<SearchRow id="SearchSelect" visible="false" /> <LayoutGroup layoutDirection="horiz" id="SearchAlphabox" translation="[70, 120]">
<SearchBox id="SearchBox" visible="true" focusable="true"/>
</LayoutGroup>
<LayoutGroup layoutDirection="vert" id="searchResults" translation="[470, 150]" >
<Label id = "SearchHelpText" text=""/>
<SearchRow id="searchSelect" visible="true" focusable="true"/>
</LayoutGroup>
<OptionsSlider id="options" /> <OptionsSlider id="options" />
<Spinner id = "searchSpinner" visible="false" translation="[1050, 500]"/>
</children> </children>
<interface> <interface>
<field id="query" type="string" alwaysNotify="true" /> <field id="query" type="string" alwaysNotify="true" />
<field id="selectedItem" type="node" alwaysNotify="true" />
<field id="quickPlayNode" type="node" alwaysNotify="true" />
<field id="imageDisplayMode" type="string" value="scaleToZoom" />
<field id="searchAlpha" type="string" alias="SearchBox.search_values" alwaysNotify="true" onChange="searchMedias" />
</interface> </interface>
<script type="text/brightscript" uri="SearchResults.brs" /> <script type="text/brightscript" uri="SearchResults.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
</component> </component>

View File

@ -11,27 +11,28 @@ sub init()
' TODO - Define a failed to load image background ' TODO - Define a failed to load image background
' m.top.failedBitmapURI ' m.top.failedBitmapURI
m.top.setFocus(true)
end sub end sub
sub updateSize() sub updateSize()
' In search results, rowSize only dictates how many are on screen at once ' In search results, rowSize only dictates how many are on screen at once
m.top.rowSize = 5 m.top.rowSize = 3
dimensions = m.top.getScene().currentDesignResolution dimensions = m.top.getScene().currentDesignResolution
border = 75 border = 50
m.top.translation = [border, border + 115] m.top.translation = [border, border + 115]
textHeight = 80 textHeight = 80
itemWidth = (dimensions["width"] - border * 2) / m.top.rowSize itemWidth = (dimensions["width"] - border) / 6
itemHeight = itemWidth * 1.5 + textHeight itemHeight = itemWidth + (textHeight / 2.3)
m.top.itemSize = [dimensions["width"] - border * 2, itemHeight] m.top.itemSize = [1350, itemHeight] ' this is used for setting the row size
m.top.itemSpacing = [0, 50] m.top.itemSpacing = [0, 105]
m.top.rowItemSize = [itemWidth, itemHeight] m.top.rowItemSize = [itemWidth, itemHeight]
m.top.rowItemSpacing = [0, 0] m.top.rowItemSpacing = [0, 0]
m.top.numRows = 2
m.top.translation = "[12,18]"
end sub end sub
function getData() function getData()
@ -45,16 +46,17 @@ function getData()
' todo - Or get the old data? I can't remember... ' todo - Or get the old data? I can't remember...
data = CreateObject("roSGNode", "ContentNode") data = CreateObject("roSGNode", "ContentNode")
' Do this to keep the ordering, AssociateArrays have no order ' Do this to keep the ordering, AssociateArrays have no order
type_array = ["Movie", "Series", "TvChannel", "Episode", "AlbumArtist", "Album", "Audio", "Person"] type_array = ["Movie", "Series", "TvChannel", "Episode", "MusicArtist", "MusicAlbum", "Audio", "Person", "PlaylistsFolder"]
content_types = { content_types = {
"TvChannel": { "label": "Channels", "count": 0 }, "TvChannel": { "label": "Channels", "count": 0 },
"Movie": { "label": "Movies", "count": 0 }, "Movie": { "label": "Movies", "count": 0 },
"Series": { "label": "Shows", "count": 0 }, "Series": { "label": "Shows", "count": 0 },
"Episode": { "label": "Episodes", "count": 0 }, "Episode": { "label": "Episodes", "count": 0 },
"AlbumArtist": { "label": "Artists", "count": 0 }, "MusicArtist": { "label": "Artists", "count": 0 },
"Album": { "label": "Albums", "count": 0 }, "MusicAlbum": { "label": "Albums", "count": 0 },
"Audio": { "label": "Songs", "count": 0 }, "Audio": { "label": "Songs", "count": 0 },
"Person": { "label": "People", "count": 0 } "Person": { "label": "People", "count": 0 },
"PlaylistsFolder": { "label": "Playlist", "count": 0 }
} }
for each item in itemData.searchHints for each item in itemData.searchHints
@ -84,3 +86,4 @@ sub addRow(data, title, type_filter)
end if end if
end for end for
end sub end sub

View File

@ -4,7 +4,12 @@
<field id="rowSize" type="int" /> <field id="rowSize" type="int" />
<field id="itemData" type="assocarray" onChange="getData" /> <field id="itemData" type="assocarray" onChange="getData" />
<field id="query" type="string" /> <field id="query" type="string" />
<field id="itemSelected" type="int" /> <field id="itemSelected" type="node" alwaysNotify="true" />
</interface> </interface>
<script type="text/brightscript" uri="SearchRow.brs" /> <script type="text/brightscript" uri="SearchRow.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
</component> </component>

View File

@ -0,0 +1,9 @@
sub init()
m.top.functionName = "search"
end sub
sub search()
if m.top.query <> invalid and m.top.query <> ""
m.top.results = searchMedia(m.top.query)
end if
end sub

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="SearchTask" extends="Task">
<interface>
<field id="query" type="string" />
<field id="results" type="assocarray" />
</interface>
<script type="text/brightscript" uri="SearchTask.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Items.brs" />
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/deviceCapabilities.brs" />
</component>

View File

@ -12,6 +12,9 @@ sub init()
m.path = m.top.findNode("path") m.path = m.top.findNode("path")
m.boolSetting = m.top.findNode("boolSetting") m.boolSetting = m.top.findNode("boolSetting")
m.integerSetting = m.top.findNode("integerSetting")
m.integerSetting.observeField("submit", "onKeyGridSubmit")
m.integerSetting.observeField("escape", "onKeyGridEscape")
m.settingsMenu.setFocus(true) m.settingsMenu.setFocus(true)
m.settingsMenu.observeField("itemFocused", "settingFocused") m.settingsMenu.observeField("itemFocused", "settingFocused")
@ -24,6 +27,17 @@ sub init()
LoadMenu({ children: m.configTree }) LoadMenu({ children: m.configTree })
end sub end sub
sub onKeyGridSubmit()
selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused]
set_user_setting(selectedSetting.settingName, m.integerSetting.text)
m.settingsMenu.setFocus(true)
end sub
sub onKeyGridEscape()
if m.integerSetting.escape = "left" or m.integerSetting.escape = "back"
m.settingsMenu.setFocus(true)
end if
end sub
sub LoadMenu(configSection) sub LoadMenu(configSection)
@ -58,8 +72,6 @@ sub LoadMenu(configSection)
end for end for
end sub end sub
sub settingFocused() sub settingFocused()
selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused] selectedSetting = m.userLocation.peek().children[m.settingsMenu.itemFocused]
@ -68,6 +80,7 @@ sub settingFocused()
' Hide Settings ' Hide Settings
m.boolSetting.visible = false m.boolSetting.visible = false
m.integerSetting.visible = false
if selectedSetting.type = invalid if selectedSetting.type = invalid
return return
@ -80,6 +93,12 @@ sub settingFocused()
else else
m.boolSetting.checkedItem = 0 m.boolSetting.checkedItem = 0
end if end if
else if selectedSetting.type = "integer"
integerValue = get_user_setting(selectedSetting.settingName, selectedSetting.default)
if isValid(integerValue)
m.integerSetting.text = integerValue
end if
m.integerSetting.visible = true
else else
print "Unknown setting type " + selectedSetting.type print "Unknown setting type " + selectedSetting.type
end if end if
@ -91,11 +110,13 @@ sub settingSelected()
selectedItem = m.userLocation.peek().children[m.settingsMenu.itemFocused] selectedItem = m.userLocation.peek().children[m.settingsMenu.itemFocused]
if selectedItem.type <> invalid ' Show setting if selectedItem.type <> invalid ' Show setting
if selectedItem.type = "bool" if selectedItem.type = "bool"
m.boolSetting.setFocus(true) m.boolSetting.setFocus(true)
end if end if
if selectedItem.type = "integer"
m.integerSetting.setFocus(true)
end if
else if selectedItem.children <> invalid and selectedItem.children.Count() > 0 ' Show sub menu else if selectedItem.children <> invalid and selectedItem.children.Count() > 0 ' Show sub menu
LoadMenu(selectedItem) LoadMenu(selectedItem)
m.settingsMenu.setFocus(true) m.settingsMenu.setFocus(true)

View File

@ -2,50 +2,39 @@
<component name="Settings" extends="JFGroup"> <component name="Settings" extends="JFGroup">
<children> <children>
<Label id="path" translation = "[95,175]" font="font:SmallestBoldSystemFont" /> <Label id="path" translation = "[95,175]" font="font:SmallestBoldSystemFont" />
<LabelList <LabelList
translation = "[120,250]" translation = "[120,250]"
id = "settingsMenu" id = "settingsMenu"
itemSize = "[440,48]" itemSize = "[440,48]"
vertFocusAnimationStyle = "floatingFocus" vertFocusAnimationStyle = "floatingFocus"
focusBitmapBlendColor = "#006fab" focusBitmapBlendColor = "#006fab"
focusedColor = "#ffffff" focusedColor = "#ffffff"
itemSpacing = "[0,5]" itemSpacing = "[0,5]" />
/>
<Poster <Poster
translation = "[710,250]" translation = "[710,250]" id="testRectangle" width="880" height="700" uri="pkg:/images/white.9.png"
id="testRectangle" blendColor = "#3f3f3f" />
width="880"
height="700"
uri="pkg:/images/white.9.png"
blendColor = "#3f3f3f"
/>
<LayoutGroup <LayoutGroup translation = "[1150,275]" id="settingDetail" vertAlignment="top" horizAlignment="center" itemSpacings="[50]">
translation = "[1150,275]"
id="settingDetail"
vertAlignment="top"
horizAlignment="center"
itemSpacings="[50]"
>
<ScrollingLabel font="font:LargeSystemFont" id="settingTitle" maxWidth="750" /> <ScrollingLabel font="font:LargeSystemFont" id="settingTitle" maxWidth="750" />
<Label id="settingDesc" width="750" wrap = "true" /> <Label id="settingDesc" width="750" wrap = "true" />
<RadioButtonList id="boolSetting" vertFocusAnimationStyle="floatingFocus"> <RadioButtonList id="boolSetting" vertFocusAnimationStyle="floatingFocus">
<ContentNode role="content"> <ContentNode role="content">
<ContentNode title="Disabled" /> <ContentNode title="Disabled" />
<ContentNode title="Enabled" /> <ContentNode title="Enabled" />
</ContentNode> </ContentNode>
</RadioButtonList> </RadioButtonList>
</LayoutGroup>
</LayoutGroup>
<intkeyboard_integerKeyboard translation="[900, 520]" id="integerSetting" maxLength="3" domain="numeric" visible="false" />
</children> </children>
<script type="text/brightscript" uri="settings.brs" /> <script type="text/brightscript" uri="settings.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" /> <script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
</component> </component>

BIN
images/icons/mic_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -3302,7 +3302,7 @@
<translation>Speciální funkce</translation> <translation>Speciální funkce</translation>
<message> <message>
<source>Press &apos;OK&apos; to Close</source> <source>Press &apos;OK&apos; to Close</source>
<translation>Press &apos;OK&apos; to Close</translation> <translation>Pro zavřestiskni &apos;OK&apos;</translation>
</message> </message>
</message> </message>
<message> <message>
@ -3402,5 +3402,74 @@
<source>Save Credentials?</source> <source>Save Credentials?</source>
<translation>Uložit přihlašovací údaje?</translation> <translation>Uložit přihlašovací údaje?</translation>
</message> </message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtr</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Třídit</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Pohled</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Délka</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Datum vydání</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Počet přehrání</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Rodičovské hodnocení</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Datum přehrání</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Datum přidání</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Honocení kritiků</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Hodnocení IMDb</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Název</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Tato %1 neobsahuje žádné položky</translation>
</message>
<message>
<source>On Now</source>
<translation>Nyní</translation>
</message>
<message>
<source>Delete Saved</source>
<translation>Smazat uložené</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Uložit přihlašovací údaje?</translation>
</message>
</context> </context>
</TS> </TS>

View File

@ -476,13 +476,13 @@
<extracomment>Title for Playback section in user setting screen.</extracomment> <extracomment>Title for Playback section in user setting screen.</extracomment>
</message> </message>
<message> <message>
<source>MPEG 2 Support</source> <source>MPEG-2 Support</source>
<translation>MPEG 2 Support</translation> <translation>MPEG-2 Support</translation>
<extracomment>Settings Menu - Title for option</extracomment> <extracomment>Settings Menu - Title for option</extracomment>
</message> </message>
<message> <message>
<source>Support direct play of MPEG 2 content (e.g. Live TV). This will prevent transcoding of MPEG 2 content, but uses significantly more bandwidth</source> <source>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</source>
<translation>Support direct play of MPEG 2 content (e.g. Live TV). This will prevent transcoding of MPEG 2 content, but uses significantly more bandwidth</translation> <translation>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</translation>
<extracomment>Settings Menu - Description for option</extracomment> <extracomment>Settings Menu - Description for option</extracomment>
</message> </message>
<message> <message>
@ -504,8 +504,8 @@
<extracomment>UI -&gt; Media Grid section in user setting screen.</extracomment> <extracomment>UI -&gt; Media Grid section in user setting screen.</extracomment>
</message> </message>
<message> <message>
<source>Media Grid Options</source> <source>Media Grid options.</source>
<translation>Media Grid Options</translation> <translation>Media Grid options.</translation>
</message> </message>
<message> <message>
<source>Item Titles</source> <source>Item Titles</source>
@ -513,8 +513,8 @@
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment> <extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message> </message>
<message> <message>
<source>Always show the titles below the poster images. (If disabled, title will be shown under hilighted item only)</source> <source>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</source>
<translation>Always show the titles below the poster images. (If disabled, title will be shown under hilighted item only)</translation> <translation>Always show the titles below the poster images. (If disabled, the title will be shown under the highlighted item only).</translation>
<extracomment>Description for option in Setting Screen</extracomment> <extracomment>Description for option in Setting Screen</extracomment>
</message> </message>
<message> <message>
@ -523,8 +523,8 @@
<extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment> <extracomment>UI -&gt; Media Grid -&gt; Item Count in user setting screen.</extracomment>
</message> </message>
<message> <message>
<source>Show item count in the library, and index of selected item.</source> <source>Show item count in the library and index of selected item.</source>
<translation>Show item count in the library, and index of selected item.</translation> <translation>Show item count in the library and index of selected item.</translation>
<extracomment>Description for option in Setting Screen</extracomment> <extracomment>Description for option in Setting Screen</extracomment>
</message> </message>
<message> <message>
@ -552,6 +552,21 @@
<translation>Go to episode</translation> <translation>Go to episode</translation>
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment> <extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
</message> </message>
<message>
<source>Use voice remote to search</source>
<translation>Use voice remote to search</translation>
<extracomment>Help text in search voice text box</extracomment>
</message>
<message>
<source>Search now</source>
<translation>Search now</translation>
<extracomment>Help text in search Box</extracomment>
</message>
<message>
<source>You can search for Titles, People, Live TV Channels and more</source>
<translation>You can search for Titles, People, Live TV Channels and more</translation>
<extracomment>Help text in search results</extracomment>
</message>
<message> <message>
<source>%1 of %2</source> <source>%1 of %2</source>
<translation>%1 of %2</translation> <translation>%1 of %2</translation>
@ -591,18 +606,18 @@
<extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment> <extracomment>UI -&gt; Media Grid -&gt; Item Title in user setting screen.</extracomment>
</message> </message>
<message> <message>
<source>Use the replay button to slowly animate to the first item in the folder. (If disabled, The folder will reset to the first item immediately)</source> <source>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</source>
<translation>Use the replay button to slowly animate to the first item in the folder. (If disabled, The folder will reset to the first item immediately)</translation> <translation>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</translation>
<extracomment>Description for option in Setting Screen</extracomment> <extracomment>Description for option in Setting Screen</extracomment>
</message> </message>
<message> <message>
<source>Detail Page</source> <source>Details Page</source>
<translation>Detail Page</translation> <translation>Details Page</translation>
</message> </message>
<message> <message>
<source>Options for details pages.</source> <source>Options for Details pages.</source>
<translation>Options for details pages.</translation> <translation>Options for Details pages.</translation>
<extracomment>Description for Detail Page user settings.</extracomment> <extracomment>Description for Details page user settings.</extracomment>
</message> </message>
<message> <message>
<source>Hide Taglines</source> <source>Hide Taglines</source>
@ -624,8 +639,8 @@
<extracomment>Option Title in user setting screen</extracomment> <extracomment>Option Title in user setting screen</extracomment>
</message> </message>
<message> <message>
<source>If enabled, images for unwatched episodes will be blurred.</source> <source>If enabled, images of unwatched episodes will be blurred.</source>
<translation>If enabled, images for unwatched episodes will be blurred.</translation> <translation>If enabled, images of unwatched episodes will be blurred.</translation>
</message> </message>
<message> <message>
<source>Screensaver</source> <source>Screensaver</source>
@ -642,8 +657,8 @@
<extracomment>Option Title in user setting screen</extracomment> <extracomment>Option Title in user setting screen</extracomment>
</message> </message>
<message> <message>
<source>Use generated splashscreen image as Jellyfin&apos;s screensaver background.</source> <source>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Use generated splashscreen image as Jellyfin&apos;s screensaver background.</translation> <translation>Use generated splashscreen image as Jellyfin&apos;s screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
</message> </message>
<message> <message>
<source>Design Elements</source> <source>Design Elements</source>
@ -660,18 +675,18 @@
<extracomment>Option Title in user setting screen</extracomment> <extracomment>Option Title in user setting screen</extracomment>
</message> </message>
<message> <message>
<source>Use generated splashscreen image as Jellyfin home background. Jellyfin will need to be closed and reopened for change to take effect.</source> <source>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</source>
<translation>Use generated splashscreen image as Jellyfin home background. Jellyfin will need to be closed and reopened for change to take effect.</translation> <translation>Use generated splashscreen image as Jellyfin&apos;s home background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
<extracomment>Description for option in Setting Screen</extracomment> <extracomment>Description for option in Setting Screen</extracomment>
</message> </message>
<message> <message>
<source>Cinema mode</source> <source>Cinema Mode</source>
<translation>Cinema mode</translation> <translation>Cinema Mode</translation>
<extracomment>Settings Menu - Title for option</extracomment> <extracomment>Settings Menu - Title for option</extracomment>
</message> </message>
<message> <message>
<source>Cinema mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source> <source>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
<translation>Cinema mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</translation> <translation>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</translation>
<extracomment>Settings Menu - Description for option</extracomment> <extracomment>Settings Menu - Description for option</extracomment>
</message> </message>
<message> <message>
@ -692,6 +707,33 @@
<source>Play Trailer</source> <source>Play Trailer</source>
<translation>Play Trailer</translation> <translation>Play Trailer</translation>
</message> </message>
<message>
<source>Settings relating to playback and supported codec and media types.</source>
<translation>Settings relating to playback and supported codec and media types.</translation>
</message>
<message>
<source>Settings relating to how the application looks.</source>
<translation>Settings relating to how the application looks.</translation>
</message>
<message>
<source>Home Page</source>
<translation>Home Page</translation>
</message>
<message>
<source>Options for Home Page.</source>
<translation>Options for Home Page.</translation>
<extracomment>Description for Home Page user settings.</extracomment>
</message>
<message>
<source>Max Days Next Up</source>
<translation>Max Days Next Up</translation>
<extracomment>Option Title in user setting screen</extracomment>
</message>
<message>
<source>Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.</source>
<translation>Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.</translation>
<extracomment>Settings Menu - Description for option</extracomment>
</message>
<message> <message>
<source>Playback Information</source> <source>Playback Information</source>
<translation>Playback Information</translation> <translation>Playback Information</translation>

View File

@ -2692,5 +2692,13 @@
<source>IMDB_RATING</source> <source>IMDB_RATING</source>
<translation>Classement IMDb</translation> <translation>Classement IMDb</translation>
</message> </message>
<message>
<source>Delete Saved</source>
<translation>Supprimer les informations enregistrées</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Enregistrer les identifiants&#xa0;?</translation>
</message>
</context> </context>
</TS> </TS>

View File

@ -6325,5 +6325,13 @@ elemeket</translation>
<source>Save Credentials?</source> <source>Save Credentials?</source>
<translation>Mented a hitelesítő adatokat?</translation> <translation>Mented a hitelesítő adatokat?</translation>
</message> </message>
<message>
<source>Delete Saved</source>
<translation>Mentettek Törlése</translation>
</message>
<message>
<source>Save Credentials?</source>
<translation>Mented a hitelesítő adatokat?</translation>
</message>
</context> </context>
</TS> </TS>

14
package-lock.json generated
View File

@ -12,7 +12,8 @@
"api": "npm:jellyfin-api-bs-client@^1.0.5", "api": "npm:jellyfin-api-bs-client@^1.0.5",
"bgv": "npm:button-group-vert@^1.0.1", "bgv": "npm:button-group-vert@^1.0.1",
"brighterscript-formatter": "^1.6.8", "brighterscript-formatter": "^1.6.8",
"sob": "npm:slide-out-button@^1.0.1" "sob": "npm:slide-out-button@^1.0.1",
"intKeyboard": "npm:integer-keyboard@^1.0.12"
}, },
"devDependencies": { "devDependencies": {
"@rokucommunity/bslint": "0.7.5", "@rokucommunity/bslint": "0.7.5",
@ -3767,6 +3768,12 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}, },
"node_modules/intKeyboard": {
"name": "integer-keyboard",
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/integer-keyboard/-/integer-keyboard-1.0.12.tgz",
"integrity": "sha512-DSLyd/PvtEBfc4grICTxSLu94Yo/Vm6rNerRZRbbzRrP0HQ9pYaquoY2RD9x6gAmica43gsFimScNpuRYVe54w=="
},
"node_modules/invariant": { "node_modules/invariant": {
"version": "2.2.4", "version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@ -9535,6 +9542,11 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}, },
"intKeyboard": {
"version": "npm:integer-keyboard@1.0.12",
"resolved": "https://registry.npmjs.org/integer-keyboard/-/integer-keyboard-1.0.12.tgz",
"integrity": "sha512-DSLyd/PvtEBfc4grICTxSLu94Yo/Vm6rNerRZRbbzRrP0HQ9pYaquoY2RD9x6gAmica43gsFimScNpuRYVe54w=="
},
"invariant": { "invariant": {
"version": "2.2.4", "version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",

View File

@ -13,6 +13,7 @@
"ropm": "0.10.9" "ropm": "0.10.9"
}, },
"scripts": { "scripts": {
"postinstall": "npx ropm copy",
"validate": "npx bsc --copy-to-staging=false --create-package=false", "validate": "npx bsc --copy-to-staging=false --create-package=false",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"lint": "bslint", "lint": "bslint",
@ -37,6 +38,7 @@
"api": "npm:jellyfin-api-bs-client@^1.0.5", "api": "npm:jellyfin-api-bs-client@^1.0.5",
"bgv": "npm:button-group-vert@^1.0.1", "bgv": "npm:button-group-vert@^1.0.1",
"brighterscript-formatter": "^1.6.8", "brighterscript-formatter": "^1.6.8",
"sob": "npm:slide-out-button@^1.0.1" "sob": "npm:slide-out-button@^1.0.1",
"intKeyboard": "npm:integer-keyboard@^1.0.12"
} }
} }

View File

@ -18,8 +18,8 @@
"default": "true" "default": "true"
}, },
{ {
"title": "Cinema mode", "title": "Cinema Mode",
"description": "Cinema mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.", "description": "Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.",
"settingName": "playback.cinemamode", "settingName": "playback.cinemamode",
"type": "bool", "type": "bool",
"default": "false" "default": "false"
@ -30,9 +30,22 @@
"title": "User Interface", "title": "User Interface",
"description": "Settings relating to how the application looks.", "description": "Settings relating to how the application looks.",
"children": [ "children": [
{
"title": "Home Page",
"description": "Options for Home page.",
"children": [
{
"title": "Max Days Next Up",
"description": "Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.",
"settingName": "ui.details.maxdaysnextup",
"type": "integer",
"default": "365"
}
]
},
{ {
"title": "Details Page", "title": "Details Page",
"description": "Options for details pages.", "description": "Options for Details pages.",
"children": [ "children": [
{ {
"title": "Hide Taglines", "title": "Hide Taglines",
@ -42,8 +55,8 @@
"default": "false" "default": "false"
} }
] ]
}, },
{ {
"title": "TV Shows", "title": "TV Shows",
"description": "Options for TV Shows.", "description": "Options for TV Shows.",
"children": [ "children": [
@ -55,8 +68,8 @@
"default": "false" "default": "false"
} }
] ]
}, },
{ {
"title": "Screensaver", "title": "Screensaver",
"description": "Options for Jellyfin's screensaver.", "description": "Options for Jellyfin's screensaver.",
"children": [ "children": [
@ -68,7 +81,7 @@
"default": "false" "default": "false"
} }
] ]
}, },
{ {
"title": "Design Elements", "title": "Design Elements",
"description": "Options that alter the design of Jellyfin.", "description": "Options that alter the design of Jellyfin.",
@ -91,7 +104,7 @@
}, },
{ {
"title": "Media Grid", "title": "Media Grid",
"description": "Media Grid Options.", "description": "Media Grid options.",
"children": [ "children": [
{ {
"title": "Item Count", "title": "Item Count",
@ -118,4 +131,4 @@
} }
] ]
} }
] ]

View File

@ -250,8 +250,29 @@ sub Main (args as dynamic) as void
' types: [ Series (Show), Episode, Movie, Audio, Person, Studio, MusicArtist ] ' types: [ Series (Show), Episode, Movie, Audio, Person, Studio, MusicArtist ]
if node.type = "Series" if node.type = "Series"
group = CreateSeriesDetailsGroup(node) group = CreateSeriesDetailsGroup(node)
else else if node.type = "Movie"
group = CreateMovieDetailsGroup(node) group = CreateMovieDetailsGroup(node)
else if node.type = "MusicArtist"
group = CreateArtistView(node.json)
else if node.type = "MusicAlbum"
group = CreateAlbumView(node.json)
else if node.type = "Audio"
group = CreateAudioPlayerGroup([node.json])
else if node.type = "Person"
group = CreatePersonView(node)
else if node.type = "TvChannel"
group = CreateVideoPlayerGroup(node.id)
sceneManager.callFunc("pushScene", group)
else if node.type = "Episode"
group = CreateVideoPlayerGroup(node.id)
sceneManager.callFunc("pushScene", group)
else if node.type = "Audio"
selectedIndex = msg.getData()
screenContent = msg.getRoSGNode()
group = CreateAudioPlayerGroup([screenContent.albumData.items[node.id]])
else
' TODO - switch on more node types
message_dialog("This type is not yet supported: " + node.type + ".")
end if end if
else if isNodeEvent(msg, "buttonSelected") else if isNodeEvent(msg, "buttonSelected")
' If a button is selected, we have some determining to do ' If a button is selected, we have some determining to do
@ -334,8 +355,8 @@ sub Main (args as dynamic) as void
end if end if
group = CreateSearchPage() group = CreateSearchPage()
sceneManager.callFunc("pushScene", group) sceneManager.callFunc("pushScene", group)
group.findNode("SearchBox").findNode("search-input").setFocus(true) group.findNode("SearchBox").findNode("search_Key").setFocus(true)
group.findNode("SearchBox").findNode("search-input").active = true group.findNode("SearchBox").findNode("search_Key").active = true
else if button.id = "change_server" else if button.id = "change_server"
unset_setting("server") unset_setting("server")
unset_setting("port") unset_setting("port")

View File

@ -446,12 +446,8 @@ end function
function CreateSearchPage() function CreateSearchPage()
' Search + Results Page ' Search + Results Page
group = CreateObject("roSGNode", "SearchResults") group = CreateObject("roSGNode", "searchResults")
options = group.findNode("searchSelect")
search = group.findNode("SearchBox")
search.observeField("search_value", m.port)
options = group.findNode("SearchSelect")
options.observeField("itemSelected", m.port) options.observeField("itemSelected", m.port)
return group return group

View File

@ -34,34 +34,40 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT
end function end function
' Search across all libraries ' Search across all libraries
function SearchMedia(query as string) function searchMedia(query as string)
' This appears to be done differently on the web now ' This appears to be done differently on the web now
' For each potential type, a separate query is done: ' For each potential type, a separate query is done:
' varying item types, and artists, and people ' varying item types, and artists, and people
resp = APIRequest(Substitute("Users/{0}/Items", get_setting("active_user")), {
"searchTerm": query,
"IncludePeople": true,
"IncludeMedia": true,
"IncludeShows": true,
"IncludeGenres": false,
"IncludeStudios": false,
"IncludeArtists": false,
"IncludeItemTypes": "TvChannel,Movie,BoxSet,Series,Episode,Video",
"EnableTotalRecordCount": false,
"ImageTypeLimit": 1,
"Recursive": true
})
data = getJson(resp) if query <> ""
results = [] resp = APIRequest(Substitute("Search/Hints", get_setting("active_user")), {
for each item in data.Items "searchTerm": query,
tmp = CreateObject("roSGNode", "SearchData") "IncludePeople": true,
tmp.image = PosterImage(item.id) "IncludeMedia": true,
tmp.json = item "IncludeShows": true,
results.push(tmp) "IncludeGenres": true,
end for "IncludeStudios": true,
data.SearchHints = results "IncludeArtists": true,
return data "IncludeItemTypes": "LiveTvChannel,Movie,BoxSet,Series,Episode,Video,Person,Audio,MusicAlbum,MusicArtist,Playlist",
"EnableTotalRecordCount": false,
"ImageTypeLimit": 1,
"Recursive": true,
"limit": 100
})
data = getJson(resp)
results = []
for each item in data.SearchHints
tmp = CreateObject("roSGNode", "SearchData")
tmp.image = PosterImage(item.id)
tmp.json = item
results.push(tmp)
end for
data.SearchHints = results
return data
end if
return []
end function end function
' MetaData about an item ' MetaData about an item
@ -94,7 +100,7 @@ function ItemMetaData(id as string)
tmp.image = PosterImage(data.id, imgParams) tmp.image = PosterImage(data.id, imgParams)
tmp.json = data tmp.json = data
return tmp return tmp
else if data.type = "BoxSet" else if data.type = "BoxSet" or data.type = "Playlist"
tmp = CreateObject("roSGNode", "CollectionData") tmp = CreateObject("roSGNode", "CollectionData")
tmp.image = PosterImage(data.id, imgParams) tmp.image = PosterImage(data.id, imgParams)
tmp.json = data tmp.json = data
@ -268,6 +274,9 @@ function AudioStream(id as string)
content.title = songData.title content.title = songData.title
content.streamformat = songData.mediaSources[0].container content.streamformat = songData.mediaSources[0].container
playbackInfo = ItemPostPlaybackInfo(songData.id, params.MediaSourceId)
content.id = playbackInfo.PlaySessionId
return content return content
end function end function