HMS

Home Media Server for Roku Players
git clone https://www.brianlane.com/git/HMS
Log | Files | Refs | README | LICENSE

commit 91d06cc07ea8b950554c43d6efc3a2cf12717abc
parent 0df0df5d875d192f940ef96b0a58274201a941d7
Author: Brian C. Lane <bcl@brianlane.com>
Date:   Wed,  3 Nov 2010 20:12:38 -0700

Removed old files

Diffstat:
DHMS/source/appDetailScreen.brs | 156-------------------------------------------------------------------------------
DHMS/source/appHomeScreen.brs | 119-------------------------------------------------------------------------------
DHMS/source/appVideoScreen.brs | 74--------------------------------------------------------------------------
DHMS/source/categoryFeed.brs | 249-------------------------------------------------------------------------------
DHMS/source/showFeed.brs | 207-------------------------------------------------------------------------------
5 files changed, 0 insertions(+), 805 deletions(-)

diff --git a/HMS/source/appDetailScreen.brs b/HMS/source/appDetailScreen.brs @@ -1,156 +0,0 @@ -'********************************************************** -'** Video Player Example Application - Detail Screen -'** November 2009 -'** Copyright (c) 2009 Roku Inc. All Rights Reserved. -'********************************************************** - -Function preShowDetailScreen(breadA=invalid, breadB=invalid) As Object - port=CreateObject("roMessagePort") - screen = CreateObject("roSpringboardScreen") - screen.SetMessagePort(port) - if breadA<>invalid and breadB<>invalid then - screen.SetBreadcrumbText(breadA, breadB) - end if - - return screen -End Function - -'*************************************************************** -'** The show detail screen (springboard) is where the user sees -'** the details for a show and is allowed to select a show to -'** begin playback. This is the main event loop for that screen -'** and where we spend our time waiting until the user presses a -'** button and then we decide how best to handle the event. -'*************************************************************** -Function showDetailScreen(screen As Object, showList As Object, showIndex as Integer) As Integer - - if validateParam(screen, "roSpringboardScreen", "showDetailScreen") = false return -1 - if validateParam(showList, "roArray", "showDetailScreen") = false return -1 - - refreshShowDetail(screen, showList, showIndex) - - 'remote key id's for left/right navigation - remoteKeyLeft = 4 - remoteKeyRight = 5 - - while true - msg = wait(0, screen.GetMessagePort()) - - if type(msg) = "roSpringboardScreenEvent" then - if msg.isScreenClosed() - print "Screen closed" - exit while - else if msg.isRemoteKeyPressed() - print "Remote key pressed" - if msg.GetIndex() = remoteKeyLeft then - showIndex = getPrevShow(showList, showIndex) - if showIndex <> -1 - refreshShowDetail(screen, showList, showIndex) - end if - else if msg.GetIndex() = remoteKeyRight - showIndex = getNextShow(showList, showIndex) - if showIndex <> -1 - refreshShowDetail(screen, showList, showIndex) - end if - endif - else if msg.isButtonPressed() - print "ButtonPressed" - if msg.GetIndex() = 1 - showVideoScreen(showList[showIndex]) - refreshShowDetail(screen, showList, showIndex) - endif - if msg.GetIndex() = 2 - ' Set the starting position - showList[showIndex].PlayStart = showList[showIndex].LastPos - showVideoScreen(showList[showIndex]) - refreshShowDetail(screen, showList, showIndex) - endif - if msg.GetIndex() = 3 - endif - print "Button pressed: "; msg.GetIndex(); " " msg.GetData() - end if - else - print "Unexpected message class: "; type(msg) - end if - end while - - return showIndex - -End Function - -'************************************************************** -'** Refresh the contents of the show detail screen. This may be -'** required on initial entry to the screen or as the user moves -'** left/right on the springboard. When the user is on the -'** springboard, we generally let them press left/right arrow keys -'** to navigate to the previous/next show in a circular manner. -'** When leaving the screen, the should be positioned on the -'** corresponding item in the poster screen matching the current show -'************************************************************** -Function refreshShowDetail(screen As Object, showList As Object, showIndex as Integer) As Integer - - if validateParam(screen, "roSpringboardScreen", "refreshShowDetail") = false return -1 - if validateParam(showList, "roArray", "refreshShowDetail") = false return -1 - - show = showList[showIndex] - - 'Uncomment this statement to dump the details for each show -' PrintAA(show) - - screen.SetDescriptionStyle(show.ContentType) - - screen.ClearButtons() - if show.LastPos = 0 then - screen.AddButton(1, "play") - else - screen.AddButton(2, "resume playing") - screen.AddButton(1, "play from beginning") - end if - - screen.SetContent(show) - screen.Show() - -End Function - -'******************************************************** -'** Get the next item in the list and handle the wrap -'** around case to implement a circular list for left/right -'** navigation on the springboard screen -'******************************************************** -Function getNextShow(showList As Object, showIndex As Integer) As Integer - if validateParam(showList, "roArray", "getNextShow") = false return -1 - - nextIndex = showIndex + 1 - if nextIndex >= showList.Count() or nextIndex < 0 then - nextIndex = 0 - end if - - show = showList[nextIndex] - if validateParam(show, "roAssociativeArray", "getNextShow") = false return -1 - - return nextIndex -End Function - - -'******************************************************** -'** Get the previous item in the list and handle the wrap -'** around case to implement a circular list for left/right -'** navigation on the springboard screen -'******************************************************** -Function getPrevShow(showList As Object, showIndex As Integer) As Integer - if validateParam(showList, "roArray", "getPrevShow") = false return -1 - - prevIndex = showIndex - 1 - if prevIndex < 0 or prevIndex >= showList.Count() then - if showList.Count() > 0 then - prevIndex = showList.Count() - 1 - else - return -1 - end if - end if - - show = showList[prevIndex] - if validateParam(show, "roAssociativeArray", "getPrevShow") = false return -1 - - return prevIndex -End Function diff --git a/HMS/source/appHomeScreen.brs b/HMS/source/appHomeScreen.brs @@ -1,119 +0,0 @@ -'***************************************************************** -'** Home Media Server Application -- Home Screen -'** Copyright (c) 2010 Brian C. Lane All Rights Reserved. -'***************************************************************** - -'****************************************************** -'** Perform any startup/initialization stuff prior to -'** initially showing the screen. -'****************************************************** -Function preShowHomeScreen(breadA=invalid, breadB=invalid) As Object - - if validateParam(breadA, "roString", "preShowHomeScreen", true) = false return -1 - if validateParam(breadA, "roString", "preShowHomeScreen", true) = false return -1 - - port=CreateObject("roMessagePort") - screen = CreateObject("roPosterScreen") - screen.SetMessagePort(port) - if breadA<>invalid and breadB<>invalid then - screen.SetBreadcrumbText(breadA, breadB) - end if - - screen.SetListStyle("flat-category") - screen.setAdDisplayMode("scale-to-fit") - return screen - -End Function - - -'****************************************************** -'** Display the home screen and wait for events from -'** the screen. The screen will show retreiving while -'** we fetch and parse the feeds for the game posters -'****************************************************** -Function showHomeScreen(screen) As Integer - - if validateParam(screen, "roPosterScreen", "showHomeScreen") = false return -1 - - checkServerUrl(false) ' Check Registry for server URL - - ' @TODO This needs to show something while it is loading the category list - - initCategoryList() - screen.SetContentList(m.Categories.Kids) - screen.SetFocusedListItem(0) - screen.Show() - - while true - msg = wait(0, screen.GetMessagePort()) - if type(msg) = "roPosterScreenEvent" then - print "showHomeScreen | msg = "; msg.GetMessage() " | index = "; msg.GetIndex() - if msg.isListFocused() then - print "list focused | index = "; msg.GetIndex(); " | category = "; m.curCategory - else if msg.isListItemSelected() then - print "list item selected | index = "; msg.GetIndex() - kid = m.Categories.Kids[msg.GetIndex()] - if kid.type = "special_category" then - displaySpecialCategoryScreen() - else - displayCategoryPosterScreen(kid) - end if - else if msg.isScreenClosed() then - return -1 - end if - end If - end while - - return 0 - -End Function - - -'********************************************************** -'** When a poster on the home screen is selected, we call -'** this function passing an associative array with the -'** data for the selected show. This data should be -'** sufficient for the show detail (springboard) to display -'********************************************************** -Function displayCategoryPosterScreen(category As Object) As Dynamic - - if validateParam(category, "roAssociativeArray", "displayCategoryPosterScreen") = false return -1 - screen = preShowPosterScreen(category.Title, "") - showPosterScreen(screen, category) - - return 0 -End Function - -'********************************************************** -'** Special categories can be used to have categories that -'** don't correspond to the content hierarchy, but are -'** managed from the server by data from the feed. In these -'** cases we might show a different type of screen other -'** than a poster screen of content. For example, a special -'** category could be search, music, options or similar. -'********************************************************** -Function displaySpecialCategoryScreen() As Dynamic - - ' do nothing, this is intended to just show how - ' you might add a special category ionto the feed - print "Special Category" - - return 0 -End Function - -'************************************************************ -'** initialize the category tree. We fetch a category list -'** from the server, parse it into a hierarchy of nodes and -'** then use this to build the home screen and pass to child -'** screen in the heirarchy. Each node terminates at a list -'** of content for the sub-category describing individual videos -'************************************************************ -Function initCategoryList() As Void - - conn = InitCategoryFeedConnection() - - m.Categories = conn.LoadCategoryFeed(conn) - m.CategoryNames = conn.GetCategoryNames(m.Categories) - -End Function - diff --git a/HMS/source/appVideoScreen.brs b/HMS/source/appVideoScreen.brs @@ -1,74 +0,0 @@ -'********************************************************** -'** Video Player Example Application - Video Playback -'** November 2009 -'** Copyright (c) 2009 Roku Inc. All Rights Reserved. -'********************************************************** - -'*********************************************************** -'** Create and show the video screen. The video screen is -'** a special full screen video playback component. It -'** handles most of the keypresses automatically and our -'** job is primarily to make sure it has the correct data -'** at startup. We will receive event back on progress and -'** error conditions so it's important to monitor these to -'** understand what's going on, especially in the case of errors -'*********************************************************** -Function showVideoScreen(episode As Object) - - if type(episode) <> "roAssociativeArray" then - print "invalid data passed to showVideoScreen" - return -1 - endif - - port = CreateObject("roMessagePort") - screen = CreateObject("roVideoScreen") - screen.SetMessagePort(port) - - screen.Show() - screen.SetContent(episode) - screen.Show() - - 'Uncomment his line to dump the contents of the episode to be played - PrintAA(episode) - - lastpos = 0 - screen.SetPositionNotificationPeriod(1) - while true - msg = wait(0, port) - if type(msg) = "roVideoScreenEvent" then - print "showHomeScreen | msg = "; msg.getMessage() " | index = "; msg.GetIndex() - if msg.isScreenClosed() - print "Screen closed" - - ' Save the last position to the server - print "lastpos = "; lastpos - - ' server URL + User ID and Media ID - url = "http://" + RegRead("ServerURL") - url = url +"/user/last/"+ episode.UserId +"/"+ episode.MediaId - - print "url: " + url - http = NewHttp(url) - rsp = http.PostFromStringWithTimeout(itostr(lastpos), 30) - - episode.LastPos = lastpos - exit while - elseif msg.isPlaybackPosition() - print "playback position: "; msg.GetIndex() - lastpos = msg.GetIndex() - elseif msg.isRequestFailed() - print "Video request failure: "; msg.GetIndex(); " " msg.GetData() - elseif msg.isStatusMessage() - print "Video status: "; msg.GetIndex(); " " msg.GetData() - elseif msg.isButtonPressed() - print "Button pressed: "; msg.GetIndex(); " " msg.GetData() - else - print "Unexpected event type: "; msg.GetType() - end if - else - print "Unexpected message class: "; type(msg) - end if - end while - -End Function - diff --git a/HMS/source/categoryFeed.brs b/HMS/source/categoryFeed.brs @@ -1,249 +0,0 @@ -'****************************************************** -'** Video Player Example Application -- Category Feed -'** November 2009 -'** Copyright (c) 2009 Roku Inc. All Rights Reserved. -'****************************************************** - -'****************************************************** -' Set up the category feed connection object -' This feed provides details about top level categories -'****************************************************** -Function InitCategoryFeedConnection() As Object - - conn = CreateObject("roAssociativeArray") - - conn.UrlPrefix = "http://" + RegRead("ServerURL") - conn.UrlCategoryFeed = conn.UrlPrefix + "/xml/users" - print "Retrieving categories from: "; conn.UrlCategoryFeed - - conn.Timer = CreateObject("roTimespan") - - conn.LoadCategoryFeed = load_category_feed - conn.GetCategoryNames = get_category_names - - print "created feed connection for " + conn.UrlCategoryFeed - return conn - -End Function - -'********************************************************* -'** Create an array of names representing the children -'** for the current list of categories. This is useful -'** for filling in the filter banner with the names of -'** all the categories at the next level in the hierarchy -'********************************************************* -Function get_category_names(categories As Object) As Dynamic - - categoryNames = CreateObject("roArray", 100, true) - - for each category in categories.kids - 'print category.Title - categoryNames.Push(category.Title) - next - - return categoryNames - -End Function - - -'****************************************************************** -'** Given a connection object for a category feed, fetch, -'** parse and build the tree for the feed. the results are -'** stored hierarchically with parent/child relationships -'** with a single default node named Root at the root of the tree -'****************************************************************** -Function load_category_feed(conn As Object) As Dynamic - - http = NewHttp(conn.UrlCategoryFeed) - - Dbg("url: ", http.Http.GetUrl()) - - m.Timer.Mark() - rsp = http.GetToStringWithRetry() - Dbg("Took: ", m.Timer) - - m.Timer.Mark() - xml=CreateObject("roXMLElement") - if not xml.Parse(rsp) then - print "Can't parse feed" - return invalid - endif - Dbg("Parse Took: ", m.Timer) - - m.Timer.Mark() - if xml.category = invalid then - print "no categories tag" - return invalid - endif - - if islist(xml.category) = false then - print "invalid feed body" - return invalid - endif - - if xml.category[0].GetName() <> "category" then - print "no initial category tag" - return invalid - endif - - topNode = MakeEmptyCatNode() - topNode.Title = "root" - topNode.isapphome = true - - print "begin category node parsing" - - categories = xml.GetChildElements() - print "number of categories: " + itostr(categories.Count()) - for each e in categories - o = ParseCategoryNode(e) - if o <> invalid then - topNode.AddKid(o) - print "added new child node" - else - print "parse returned no child node" - endif - next - Dbg("Traversing: ", m.Timer) - - return topNode - -End Function - -'****************************************************** -'MakeEmptyCatNode - use to create top node in the tree -'****************************************************** -Function MakeEmptyCatNode() As Object - return init_category_item() -End Function - - -'*********************************************************** -'Given the xml element to an <Category> tag in the category -'feed, walk it and return the top level node to its tree -'*********************************************************** -Function ParseCategoryNode(xml As Object) As dynamic - o = init_category_item() - - print "ParseCategoryNode: " + xml.GetName() - 'PrintXML(xml, 5) - - 'parse the curent node to determine the type. everything except - 'special categories are considered normal, others have unique types - if xml.GetName() = "category" then - print "category: " + xml@title + " | " + xml@description - o.Type = "normal" - o.Title = xml@title - o.Description = xml@Description - o.ShortDescriptionLine1 = xml@Title - o.ShortDescriptionLine2 = xml@Description - o.SDPosterURL = xml@sd_img - o.HDPosterURL = xml@hd_img - elseif xml.GetName() = "categoryLeaf" then - o.Type = "normal" - elseif xml.GetName() = "specialCategory" then - if invalid <> xml.GetAttributes() then - for each a in xml.GetAttributes() - if a = "type" then - o.Type = xml.GetAttributes()[a] - print "specialCategory: " + xml@type + "|" + xml@title + " | " + xml@description - o.Title = xml@title - o.Description = xml@Description - o.ShortDescriptionLine1 = xml@Title - o.ShortDescriptionLine2 = xml@Description - o.SDPosterURL = xml@sd_img - o.HDPosterURL = xml@hd_img - endif - next - endif - else - print "ParseCategoryNode skip: " + xml.GetName() - return invalid - endif - - 'only continue processing if we are dealing with a known type - 'if new types are supported, make sure to add them to the list - 'and parse them correctly further downstream in the parser - while true - if o.Type = "normal" exit while - if o.Type = "special_category" exit while - print "ParseCategoryNode unrecognized feed type" - return invalid - end while - - 'get the list of child nodes and recursed - 'through everything under the current node - for each e in xml.GetBody() - name = e.GetName() - if name = "category" then - print "category: " + e@title + " [" + e@description + "]" - kid = ParseCategoryNode(e) - kid.Title = e@title - kid.Description = e@Description - kid.ShortDescriptionLine1 = xml@Description - kid.SDPosterURL = xml@sd_img - kid.HDPosterURL = xml@hd_img - o.AddKid(kid) - elseif name = "categoryLeaf" then - print "categoryLeaf: " + e@title + " [" + e@description + "]" - kid = ParseCategoryNode(e) - kid.Title = e@title - kid.Description = e@Description - kid.Feed = e@feed - o.AddKid(kid) - elseif name = "specialCategory" then - print "specialCategory: " + e@title + " [" + e@description + "]" - kid = ParseCategoryNode(e) - kid.Title = e@title - kid.Description = e@Description - kid.sd_img = e@sd_img - kid.hd_img = e@hd_img - kid.Feed = e@feed - o.AddKid(kid) - endif - next - - return o -End Function - - -'****************************************************** -'Initialize a Category Item -'****************************************************** -Function init_category_item() As Object - o = CreateObject("roAssociativeArray") - o.Title = "" - o.Type = "normal" - o.Description = "" - o.Kids = CreateObject("roArray", 100, true) - o.Parent = invalid - o.Feed = "" - o.IsLeaf = cn_is_leaf - o.AddKid = cn_add_kid - return o -End Function - - -'******************************************************** -'** Helper function for each node, returns true/false -'** indicating that this node is a leaf node in the tree -'******************************************************** -Function cn_is_leaf() As Boolean - if m.Kids.Count() > 0 return true - if m.Feed <> "" return false - return true -End Function - - -'********************************************************* -'** Helper function for each node in the tree to add a -'** new node as a child to this node. -'********************************************************* -Sub cn_add_kid(kid As Object) - if kid = invalid then - print "skipping: attempt to add invalid kid failed" - return - endif - - kid.Parent = m - m.Kids.Push(kid) -End Sub diff --git a/HMS/source/showFeed.brs b/HMS/source/showFeed.brs @@ -1,207 +0,0 @@ -'********************************************************** -'** Video Player Example Application - Show Feed -'** November 2009 -'** Copyright (c) 2009 Roku Inc. All Rights Reserved. -'********************************************************** - -'****************************************************** -'** Set up the show feed connection object -'** This feed provides the detailed list of shows for -'** each subcategory (categoryLeaf) in the category -'** category feed. Given a category leaf node for the -'** desired show list, we'll hit the url and get the -'** results. -'****************************************************** - -Function InitShowFeedConnection(category As Object) As Object - - if validateParam(category, "roAssociativeArray", "initShowFeedConnection") = false return invalid - - conn = CreateObject("roAssociativeArray") - conn.UrlShowFeed = category.feed - - conn.Timer = CreateObject("roTimespan") - - conn.LoadShowFeed = load_show_feed - conn.ParseShowFeed = parse_show_feed - conn.InitFeedItem = init_show_feed_item - - print "created feed connection for " + conn.UrlShowFeed - return conn - -End Function - - -'****************************************************** -'Initialize a new feed object -'****************************************************** -Function newShowFeed() As Object - - o = CreateObject("roArray", 100, true) - return o - -End Function - - -'*********************************************************** -' Initialize a ShowFeedItem. This sets the default values -' for everything. The data in the actual feed is sometimes -' sparse, so these will be the default values unless they -' are overridden while parsing the actual game data -'*********************************************************** -Function init_show_feed_item() As Object - o = CreateObject("roAssociativeArray") - - o.UserId = "" - o.MediaId = "" - o.ContentId = "" - o.Title = "" - o.ContentType = "" - o.Description = "" - o.Length = "" - o.LastPos = "" - o.Actors = CreateObject("roArray", 3, true) - o.Director = CreateObject("roArray", 1, true) - o.Categories = CreateObject("roArray", 3, true) - o.StreamFormats = CreateObject("roArray", 5, true) - o.StreamQualities = CreateObject("roArray", 5, true) - o.StreamBitrates = CreateObject("roArray", 5, true) - o.StreamUrls = CreateObject("roArray", 5, true) - - return o -End Function - - -'************************************************************* -'** Grab and load a show detail feed. The url we are fetching -'** is specified as part of the category provided during -'** initialization. This feed provides a list of all shows -'** with details for the given category feed. -'********************************************************* -Function load_show_feed(conn As Object) As Dynamic - - if validateParam(conn, "roAssociativeArray", "load_show_feed") = false return invalid - - print "url: " + conn.UrlShowFeed - http = NewHttp(conn.UrlShowFeed) - - m.Timer.Mark() - rsp = http.GetToStringWithRetry() - print "Request Time: " + itostr(m.Timer.TotalMilliseconds()) - - feed = newShowFeed() - xml=CreateObject("roXMLElement") - if not xml.Parse(rsp) then - print "Can't parse feed" - print rsp - return feed - endif - - if xml.GetName() <> "feed" then - print "no feed tag found" - return feed - endif - - if islist(xml.GetBody()) = false then - print "no feed body found" - return feed - endif - - m.Timer.Mark() - m.ParseShowFeed(xml, feed) - print "Show Feed Parse Took : " + itostr(m.Timer.TotalMilliseconds()) - - return feed - -End Function - - -'************************************************************************** -'************************************************************************** -Function parse_show_feed(xml As Object, feed As Object) As Void - - showCount = 0 - showList = xml.GetChildElements() - - for each curShow in showList - - 'for now, don't process meta info about the feed size - if curShow.GetName() = "resultLength" or curShow.GetName() = "endIndex" then - goto skipitem - endif - - item = init_show_feed_item() - - 'fetch all values from the xml for the current show - item.HDPosterUrl = validstr(curShow@hdPosterUrl) - item.SDPosterUrl = validstr(curShow@sdPosterUrl) - item.ContentId = validstr(curShow.contentId.GetText()) - item.UserId = validstr(curShow.userId.GetText()) - item.MediaId = validstr(curShow.mediaId.GetText()) - item.Title = validstr(curShow.title.GetText()) - item.Description = validstr(curShow.description.GetText()) - item.ContentType = validstr(curShow.contentType.GetText()) - item.ContentQuality = validstr(curShow.contentQuality.GetText()) - item.Length = strtoi(validstr(curShow.length.GetText())) - item.LastPos = strtoi(validstr(curShow.lastPos.GetText())) - item.HDBifUrl = validstr(curShow.hdBifUrl.GetText()) - item.SDBifUrl = validstr(curShow.sdBifUrl.GetText()) - item.StreamFormat = validstr(curShow.streamFormat.GetText()) - item.ReleaseDate = validstr(curshow.releaseDate.GetText()) - item.Rating = validstr(curshow.rating.GetText()) - item.StarRating = validstr(curshow.starRating.GetText()) - item.UserStarRating = validstr(curshow.userStarRating.GetText()) - item.ShortDescriptionLine1 = validstr(curshow.ShortDescriptionLine1.GetText()) - item.ShortDescriptionLine2 = validstr(curshow.ShortDescriptionLine2.GetText()) - item.EpisodeNumber = validstr(curshow.episodeNumber.GetText()) - item.HDBranded = strtobool(validstr(curshow.hdBranded.GetText())) - item.isHD = strtobool(validstr(curshow.isHD.GetText())) - item.TextOverlayUL = validstr(curshow.textOverlayUL.GetText()) - item.TextOverlayUR = validstr(curshow.textOverlayUR.GetText()) - item.TextOverlayBody = validstr(curshow.textOverlayBody.GetText()) - item.Album = validstr(curshow.album.GetText()) - item.Artist = validstr(curshow.artist.GetText()) - - 'media may be at multiple bitrates, so parse an build arrays - for idx = 0 to 4 - e = curShow.media[idx] - if e <> invalid then - item.StreamFormats.Push(validstr(e.streamFormat.GetText())) - item.StreamBitrates.Push(strtoi(validstr(e.streamBitrate.GetText()))) - item.StreamQualities.Push(validstr(e.streamQuality.GetText())) - item.StreamUrls.Push(validstr(e.streamUrl.GetText())) - endif - next idx - - ' May be multiple actors - for idx = 0 to 2 - e = curShow.actor[idx] - if e <> invalid then - item.Actors.Push(validstr(e.GetText())) - endif - next idx - - ' May be multiple directors - for idx = 0 to 2 - e = curShow.director[idx] - if e <> invalid then - item.Director.Push(validstr(e.GetText())) - endif - next idx - - ' May be multiple categories - for idx = 0 to 2 - e = curShow.category[idx] - if e <> invalid then - item.Categories.Push(validstr(e.GetText())) - endif - next idx - - showCount = showCount + 1 - feed.Push(item) - - skipitem: - - next - -End Function