Make TV series work again, or for the first time\? (#94)

This commit is contained in:
Nick Bisby 2019-12-06 20:49:37 -06:00 committed by GitHub
parent 6859d7aa3f
commit edab57c45b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 290 additions and 243 deletions

View File

@ -3,7 +3,7 @@
<interface> <interface>
<field id="id" type="string" /> <field id="id" type="string" />
<field id="title" type="string" /> <field id="title" type="string" />
<field id="description" type="string" /> <field id="overview" type="string" />
<field id="seasons" type="associativearray" /> <field id="seasons" type="associativearray" />
<field id="nextup" type="associativearray" /> <field id="nextup" type="associativearray" />
<field id="image" type="node" onChange="setPoster" /> <field id="image" type="node" onChange="setPoster" />

View File

@ -7,7 +7,7 @@
<field id="posterURL" type="string" /> <field id="posterURL" type="string" />
<field id="showID" type="string" /> <field id="showID" type="string" />
<field id="seasonID" type="string" /> <field id="seasonID" type="string" />
<field id="description" type="string" /> <field id="overview" type="string" />
<field id="json" type="associativearray" onChange="setFields" /> <field id="json" type="associativearray" onChange="setFields" />
<function name="loadSeasons" /> <function name="loadSeasons" />
</interface> </interface>

View File

@ -5,7 +5,7 @@
<field id="title" type="string" /> <field id="title" type="string" />
<field id="image" type="node" onChange="setPoster" /> <field id="image" type="node" onChange="setPoster" />
<field id="posterURL" type="string" /> <field id="posterURL" type="string" />
<field id="description" type="string" /> <field id="overview" type="string" />
<field id="json" type="associativearray" onChange="setFields" /> <field id="json" type="associativearray" onChange="setFields" />
<function name="getPoster" /> <function name="getPoster" />
</interface> </interface>

View File

@ -1,32 +1,139 @@
sub init() sub init()
set = m.top.findNode("panelset") m.top.overhangTitle = "TV Show"
set.height = 1080 main = m.top.findNode("toplevel")
main.translation = [50, 175]
panel = set.findNode("panel-desc")
panel.panelSize = "full"
panel.hasNextPanel = true
panel.isFullScreen = true
panel.leftPosition = 150
panel2 = set.findNode("panel-seasons")
panel2.panelSize = "full"
panel2.hasNextPanel = false
panel2.isFullScreen = true
panel2.leftPosition = 150
' TODO - set the bounds so seasons dont go off the edge of the screen
end sub end sub
sub panelFocusChanged() sub itemContentChanged()
set = m.top.findNode("panelset") ' Updates video metadata
index = m.top.panelFocused ' TODO - make things use item rather than itemData
item = m.top.itemContent
itemData = item.json
if index = 0 m.top.findNode("tvshowPoster").uri = m.top.itemContent.posterURL
' Description page
' TODO - get the buttons to actually take focus back ' Handle all "As Is" fields
set.findNode("description").findNode("buttons").setFocus(true) m.top.overhangTitle = itemData.name
else if index = 1 setFieldText("releaseYear", itemData.productionYear)
' Seasons page setFieldText("officialRating", itemData.officialRating)
set.findNode("seasons").setFocus(true) setFieldText("communityRating", str(itemData.communityRating))
setFieldText("overview", itemData.overview)
if type(itemData.RunTimeTicks) = "LongInteger"
setFieldText("runtime", stri(getRuntime()) + " mins")
end if
setFieldText("history", getHistory())
if itemData.genres.count() > 0
setFieldText("genres", itemData.genres.join(", "))
end if
director = invalid
for each person in itemData.people
if person.type = "Director"
director = person.name
exit for
end if end if
end for
if itemData.taglines.count() > 0
setFieldText("tagline", itemData.taglines[0])
end if
' m.top.findNode("TVSeasonSelect").TVSeasonData = m.top.itemContent.seasons
end sub
end sub sub setFieldText(field, value)
node = m.top.findNode(field)
if node = invalid or value = invalid then return
' Handle non strings... Which _shouldn't_ happen, but hey
if type(value) = "roInt" or type(value) = "Integer" then
value = str(value)
else if type(value) <> "roString" and type(value) <> "String" then
value = ""
end if
node.text = value
end sub
function getRuntime() as integer
itemData = m.top.itemContent.json
' A tick is .1ms, so 1/10,000,000 for ticks to seconds,
' then 1/60 for seconds to minutess... 1/600,000,000
return round(itemData.RunTimeTicks / 600000000.0)
end function
function getEndTime() as string
itemData = m.top.itemContent.json
date = CreateObject("roDateTime")
duration_s = int(itemData.RunTimeTicks / 10000000.0)
date.fromSeconds(date.asSeconds() + duration_s)
date.toLocalTime()
hours = date.getHours()
meridian = "AM"
if hours = 0
hours = 12
meridian = "AM"
else if hours = 12
hours = 12
meridian = "PM"
else if hours > 12
hours = hours - 12
meridian = "PM"
end if
return Substitute("{0}:{1} {2}", stri(hours).trim(), stri(date.getMinutes()).trim(), meridian)
end function
function getHistory() as string
itemData = m.top.itemContent.json
' Aired Fridays at 9:30 PM on ABC (US)
airwords = invalid
studio = invalid
if itemData.status = "Ended"
verb = "Aired"
else
verb = "Airs"
end if
airdays = itemData.airdays
airtime = itemData.airtime
if airtime <> invalid and airdays.count() = 1
airwords = airdays[0] + " at " + airtime
end if
if itemData.studios.count() > 0
studio = itemData.studios[0].name
end if
if studio = invalid and airwords = invalid
return ""
end if
words = verb
if airwords <> invalid
words = words + " " + airwords
end if
if studio <> invalid
words = words + " on " + studio
end if
return words
end function
function round(f as float) as integer
' BrightScript only has a "floor" round
' This compares floor to floor + 1 to find which is closer
m = int(f)
n = m + 1
x = abs(f - m)
y = abs(f - n)
if y > x
return m
else
return n
end if
end function

View File

@ -1,19 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<component name="TVShowItemDetailScene" extends="Scene"> <component name="TVShowDetails" extends="JFGroup">
<children> <children>
<PanelSet id="panelset"> <LayoutGroup id="toplevel" layoutDirection="vert" itemSpacings="[-10]" >
<Panel id="panel-desc"> <LayoutGroup id="main_group" layoutDirection="horiz" itemSpacings="[15]" >
<TVShowDescription id="description" /> <Poster id="tvshowPoster" width="300" height="450" />
</Panel> <LayoutGroup layoutDirection="vert" itemSpacings="[15]">
<Panel id="panel-seasons"> <LayoutGroup layoutDirection="horiz" itemSpacings="[150]">
<TVSeasonRow id="seasons" /> <Label id="releaseYear" />
</Panel> <Label id="officialRating" />
</PanelSet> <Label id="communityRating" />
</LayoutGroup>
<Label id="genres" />
<Label id="tagline" />
<Label id="overview" wrap="true" width="1420" maxLines="4" />
<Label id="history" />
</LayoutGroup>
</LayoutGroup>
<TVSeasonRow id="seasons" />
</LayoutGroup>
</children> </children>
<interface> <interface>
<field id="itemData" type="node" alias="description.itemContent" /> <field id="itemContent" type="node" onChange="itemContentChanged" />
<field id="seasonData" type="associativearray" alias="seasons.TVSeasonData" /> <field id="seasonData" type="associativearray" alias="seasons.TVSeasonData" />
<field id="panelFocused" alias="panelset.leftPanelIndex" type="integer" onChange="panelFocusChanged" /> <field id="seasonSelected" alias="seasons.rowItemSelected" />
</interface> </interface>
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
<script type="text/brightscript" uri="details.brs" /> <script type="text/brightscript" uri="details.brs" />
</component> </component>

View File

@ -1,2 +1,7 @@
sub init() sub init()
m.top.overhangTitle = "Season"
end sub
sub setSeason()
m.top.overhangTitle = m.top.seasonData.name
end sub end sub

View File

@ -1,21 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<component name="TVEpisodes" extends="Scene"> <component name="TVEpisodes" extends="JFGroup">
<children> <children>
<TVEpisodeRow <ItemGrid id="picker" visible="true" itemsPerRow="10" />
id="TVEpisodeSelect"
visible="true"
/>
<Overhang
id="overhang"
title="TV Shows"
/>
<Rectangle id="footerBackdrop" />
<Pager id="pager" />
</children> </children>
<interface> <interface>
<field id="showData" type="node" /> <field id="episodeSelected" alias="picker.itemSelected" />
<field id="seasonData" type="node" /> <field id="seasonData" type="node" onChange="setSeason" />
<field id="episodeData" type="node" alias="TVEpisodeSelect.TVEpisodeData" /> <field id="objects" alias="picker.objects" />
</interface> </interface>
<script type="text/brightscript" uri="episode.brs" /> <script type="text/brightscript" uri="episode.brs" />
</component> </component>

View File

@ -2,11 +2,12 @@ sub init()
m.top.itemComponentName = "ListPoster" m.top.itemComponentName = "ListPoster"
m.top.content = getData() m.top.content = getData()
'm.top.rowFocusAnimationStyle = "floatingFocus" m.top.rowFocusAnimationStyle = "floatingFocus"
'm.top.vertFocusAnimationStyle = "floatingFocus" 'm.top.vertFocusAnimationStyle = "floatingFocus"
m.top.showRowLabel = [true] m.top.showRowLabel = [false]
m.top.rowLabelOffset = [0, 20] m.top.showRowCounter = [true]
m.top.rowLabelOffset = [0, 5]
updateSize() updateSize()
@ -14,25 +15,16 @@ sub init()
end sub end sub
sub updateSize() sub updateSize()
' Infinite scroll, rowsize is just how many show on screen at once
m.top.rowSize = 5
dimensions = m.top.getScene().currentDesignResolution
border = 50
m.top.translation = [border, border]
textHeight = 80 textHeight = 80
' Do we decide width by rowSize, or rowSize by width... itemWidth = 200
itemWidth = (dimensions["width"] - border*2) / m.top.rowSize itemHeight = 380 ' width * 1.5 + text
itemHeight = itemWidth * 1.5 + textHeight
m.top.visible = true m.top.visible = true
' size of the whole row ' size of the whole row
m.top.itemSize = [dimensions["width"] - border*2, itemHeight] m.top.itemSize = [1720, itemHeight]
' spacing between rows ' spacing between rows
m.top.itemSpacing = [ 0, 10 ] m.top.itemSpacing = [ 0, 0 ]
' size of the item in the row ' size of the item in the row
m.top.rowItemSize = [ itemWidth, itemHeight ] m.top.rowItemSize = [ itemWidth, itemHeight ]

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<component name="TVSeasonRow" extends="RowList"> <component name="TVSeasonRow" extends="RowList">
<interface> <interface>
<field id="rowSize" type="int" />
<field id="TVSeasonData" type="associativearray" onChange="getData" /> <field id="TVSeasonData" type="associativearray" onChange="getData" />
</interface> </interface>
<script type="text/brightscript" uri="rowlist-season.brs" /> <script type="text/brightscript" uri="rowlist-season.brs" />

View File

@ -1,2 +1,14 @@
sub init() sub init()
end sub m.top.overhangTitle = "TV Shows"
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "down"
m.top.lastFocus = m.top.focusedChild
m.top.findNode("paginator").setFocus(true)
end if
return false
end function

View File

@ -1,14 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<component name="TVShows" extends="Scene"> <component name="TVShows" extends="JFGroup">
<children> <children>
<ItemGrid id="picker" visible="true" itemsPerRow="10" /> <ItemGrid id="picker" visible="true" itemsPerRow="10" />
<Overhang
id="overhang"
title="TV Shows"
/>
<Rectangle id="footerBackdrop" />
<Pager id="pager" />
<OptionsSlider id="options" /> <OptionsSlider id="options" />
<Rectangle translation="[0,981]" width="1920" height="100" color="#101010" />
</children> </children>
<interface>
<field id="seriesSelected" alias="picker.itemSelected" />
<field id="objects" alias="picker.objects" />
<field id="pageNumber" type="integer" />
<field id="library" type="node" />
</interface>
<script type="text/brightscript" uri="scene.brs" /> <script type="text/brightscript" uri="scene.brs" />
</component> </component>

View File

@ -77,6 +77,14 @@ sub Main()
group = CreateMovieListGroup(node) group = CreateMovieListGroup(node)
m.overhang.title = group.overhangTitle m.overhang.title = group.overhangTitle
m.scene.appendChild(group) m.scene.appendChild(group)
else if node.type = "tvshows"
group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
group = CreateSeriesListGroup(node)
m.overhang.title = group.overhangTitle
m.scene.appendChild(group)
else if node.type = "boxsets" else if node.type = "boxsets"
group.lastFocus = group.focusedChild group.lastFocus = group.focusedChild
group.setFocus(false) group.setFocus(false)
@ -110,6 +118,45 @@ sub Main()
group = CreateMovieDetailsGroup(node) group = CreateMovieDetailsGroup(node)
m.scene.appendChild(group) m.scene.appendChild(group)
m.overhang.title = group.overhangTitle m.overhang.title = group.overhangTitle
else if isNodeEvent(msg, "seriesSelected")
' If you select a TV Series from ANYWHERE, follow this flow
node = getMsgPicker(msg, "picker")
group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
group = CreateSeriesDetailsGroup(node)
m.scene.appendChild(group)
m.overhang.title = group.overhangTitle
else if isNodeEvent(msg, "seasonSelected")
' If you select a TV Season from ANYWHERE, follow this flow
ptr = msg.getData()
' ptr is for [row, col] of selected item... but we only have 1 row
series = msg.getRoSGNode()
node = series.seasonData.items[ptr[1]]
group.lastFocus = group.focusedChild.focusedChild
group.setFocus(false)
group.visible = false
group = CreateSeasonDetailsGroup(series.itemContent, node)
m.scene.appendChild(group)
m.overhang.title = group.overhangTitle
else if isNodeEvent(msg, "episodeSelected")
' If you select a TV Episode from ANYWHERE, follow this flow
node = getMsgPicker(msg, "picker")
group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
video_id = node.id
group = CreateVideoPlayerGroup(video_id)
m.scene.appendChild(group)
group.setFocus(true)
group.control = "play"
m.overhang.visible = false
else if isNodeEvent(msg, "search_value") else if isNodeEvent(msg, "search_value")
query = msg.getRoSGNode().search_value query = msg.getRoSGNode().search_value
group.findNode("SearchBox").visible = false group.findNode("SearchBox").visible = false

View File

@ -218,182 +218,45 @@ function CreateMovieDetailsGroup(movie)
return group return group
end function end function
sub ShowTVShowOptions(library) function CreateSeriesListGroup(library)
' TV Show List Page group = CreateObject("roSGNode", "TVShows")
port = m.port group.id = library.id
screen = m.screen group.library = library
scene = screen.CreateScene("TVShows")
overhang = scene.findNode("overhang") group.observeField("seriesSelected", m.port)
overhang.title = library.name
themeScene(scene) sidepanel = group.findNode("options")
item_grid = scene.findNode("picker") p = CreatePaginator()
group.appendChild(p)
page_num = 1 group.pageNumber = 1
page_size = 50 SeriesLister(group, m.page_size)
sort_order = get_user_setting("series_sort_order", "Ascending") return group
sort_field = get_user_setting("series_sort_field", "SortName") end function
item_list = ItemList(library.id, {"limit": page_size, function CreateSeriesDetailsGroup(series)
"page": page_num, group = CreateObject("roSGNode", "TVShowDetails")
"SortBy": sort_field,
"SortOrder": sort_order })
item_grid.objects = item_list
item_grid.observeField("escapeButton", port) group.itemContent = ItemMetaData(series.id)
item_grid.observeField("itemSelected", port) group.seasonData = TVSeasons(series.id)
pager = scene.findNode("pager") group.observeField("seasonSelected", m.port)
pager.currentPage = page_num
pager.maxPages = item_list.TotalRecordCount / page_size
if item_list.TotalRecordCount mod page_size > 0 then pager.maxPages += 1
pager.observeField("escape", port) return group
pager.observeField("pageSelected", port) end function
sidepanel = scene.findNode("options") function CreateSeasonDetailsGroup(series, season)
panel_options = [ group = CreateObject("roSGNode", "TVEpisodes")
{"title": "Sort Field",
"base_title": "Sort Field",
"key": "series_sort_field",
"default": "SortName",
"values": [
{display: "Date Added", value: "DateCreated"},
{display: "Release Date", value: "PremiereDate"},
{display: "Name", value: "SortName"}
]},
{"title": "Sort Order",
"base_title": "Sort Order",
"key": "series_sort_order",
"default": "Ascending",
"values": [
{display: "Descending", value: "Descending"},
{display: "Ascending", value: "Ascending"}
]}
]
new_options = []
for each opt in panel_options
o = CreateObject("roSGNode", "OptionsData")
o.title = opt.title
o.choices = opt.values
o.base_title = opt.base_title
o.config_key = opt.key
o.value = get_user_setting(opt.key, opt.default)
new_options.append([o])
end for
sidepanel.options = new_options group.seasonData = TVSeasons(series.id)
sidepanel.observeField("escape", port) group.objects = TVEpisodes(series.id, season.id)
while true group.observeField("episodeSelected", m.port)
msg = wait(0, port)
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed() then
return
else if nodeEventQ(msg, "escapeButton")
node = msg.getRoSGNode()
if node.escapeButton = "down"
pager.setFocus(true)
pager.getChild(0).setFocus(true)
else if node.escapeButton = "options"
sidepanel.visible = true
sidepanel.findNode("panelList").setFocus(true)
end if
else if nodeEventQ(msg, "escape") and msg.getNode() = "pager"
item_grid.setFocus(true)
else if nodeEventQ(msg, "escape") and msg.getNode() = "options"
item_grid.setFocus(true)
else if nodeEventQ(msg, "pageSelected") and pager.pageSelected <> invalid
pager.pageSelected = invalid
page_num = int(val(msg.getData().id))
pager.currentPage = page_num
item_list = ItemList(library.id, {"limit": page_size,
"StartIndex": page_size * (page_num - 1),
"SortBy": sort_field,
"SortOrder": sort_order })
item_grid.objects = item_list
item_grid.setFocus(true)
else if type(msg) = "roSGScreenEvent" and msg.isScreenClosed() then
return
else if nodeEventQ(msg, "itemSelected")
target = getMsgPicker(msg)
ShowTVShowDetails(target)
end if
end while
end sub
sub ShowTVShowDetails(series) return group
' TV Show Detail Page end function
port = m.port
screen = m.screen
scene = screen.CreateScene("TVShowItemDetailScene")
themeScene(scene)
series = ItemMetaData(series.id)
scene.itemData = series
scene.findNode("description").findNode("buttons").setFocus(true)
scene.seasonData = TVSeasons(series.id)
scene.findNode("description").findNode("buttons").setFocus(true)
'buttons = scene.findNode("buttons")
'buttons.observeField("buttonSelected", port)
scene.findNode("seasons").observeField("rowItemSelected", port)
while true
msg = wait(0, port)
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed() then
return
else if nodeEventQ(msg, "buttonSelected")
' What button could we even be watching yet
else if nodeEventQ(msg, "rowItemSelected")
' Assume for now it's a season being selected
season_list = msg.getRoSGNode()
item = msg.getData()
season = season_list.content.getChild(item[0]).getChild(item[1])
ShowTVSeasonEpisodes(series, season)
else
print msg
print type(msg)
end if
end while
end sub
sub ShowTVSeasonEpisodes(series, season)
' TV Show Season Episdoe List
port = m.port
screen = m.screen
scene = screen.CreateScene("TVEpisodes")
themeScene(scene)
scene.showData = ItemMetaData(series.id)
scene.seasonData = TVSeasons(series.id)
scene.episodeData = TVEpisodes(series.id, season.id)
scene.findNode("TVEpisodeSelect").observeField("rowItemSelected", port)
while true
msg = wait(0, port)
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed() then
return
else if nodeEventQ(msg, "rowItemSelected")
episode_list = msg.getRoSGNode()
item = msg.getData()
episode = episode_list.content.getChild(item[0]).getChild(item[1])
ShowVideoPlayer(episode.id)
else
print msg
print type(msg)
end if
end while
end sub
function CreateCollectionsList(library) function CreateCollectionsList(library)
' Load Movie Collection Items ' Load Movie Collection Items
@ -504,6 +367,22 @@ function MovieLister(group, page_size)
p.maxPages = div_ceiling(group.objects.TotalRecordCount, page_size) p.maxPages = div_ceiling(group.objects.TotalRecordCount, page_size)
end function end function
function SeriesLister(group, page_size)
sort_order = get_user_setting("series_sort_order", "Ascending")
sort_field = get_user_setting("series_sort_field", "SortName")
item_list = ItemList(group.id, {"limit": page_size,
"StartIndex": page_size * (group.pageNumber - 1),
"SortBy": sort_field,
"SortOrder": sort_order,
"IncludeItemTypes": "Series"
})
group.objects = item_list
p = group.findNode("paginator")
p.maxPages = div_ceiling(group.objects.TotalRecordCount, page_size)
end function
function CollectionLister(group, page_size) function CollectionLister(group, page_size)
sort_order = get_user_setting("boxsets_sort_order", "Ascending") sort_order = get_user_setting("boxsets_sort_order", "Ascending")
sort_field = get_user_setting("boxsets_sort_field", "SortName") sort_field = get_user_setting("boxsets_sort_field", "SortName")
@ -518,3 +397,5 @@ function CollectionLister(group, page_size)
p = group.findNode("paginator") p = group.findNode("paginator")
p.maxPages = div_ceiling(group.objects.TotalRecordCount, page_size) p.maxPages = div_ceiling(group.objects.TotalRecordCount, page_size)
end function end function

View File

@ -189,7 +189,9 @@ function TVEpisodes(show_id as string, season_id as string)
for each item in data.Items for each item in data.Items
tmp = CreateObject("roSGNode", "TVEpisodeData") tmp = CreateObject("roSGNode", "TVEpisodeData")
tmp.image = PosterImage(item.id) tmp.image = PosterImage(item.id)
tmp.image.posterDisplayMode = "scaleToFit" if tmp.image <> invalid
tmp.image.posterDisplayMode = "scaleToFit"
end if
tmp.json = item tmp.json = item
results.push(tmp) results.push(tmp)
end for end for