Set live stream quality to SD for compatibility with SD users. This also appears to work the same for HD users as before.

sub Main()
	LoadTheme()
	categories = LoadConfig()

	if categories.Count() > 1
		ShowPosterScreen( categories )
	else
		ShowEpisodeScreen( categories[0] )
	end if
end sub

sub LoadTheme()
	app = CreateObject( "roAppManager" )
	theme = CreateObject( "roAssociativeArray" )
	theme.OverhangSliceSD = "pkg:/images/Overhang_Slice_SD.png"
	theme.OverhangSliceHD = "pkg:/images/Overhang_Slice_HD.png"
	theme.OverhanglogoHD = "pkg:/images/Logo_Overhang_JB_HD.png"
	theme.OverhanglogoSD = "pkg:/images/Logo_Overhang_JB_SD.png"

	theme.OverhangPrimaryLogoOffsetHD_X = "60"
	theme.OverhangPrimaryLogoOffsetHD_Y = "40"

	theme.OverhangPrimaryLogoOffsetSD_X = "60"
	theme.OverhangPrimaryLogoOffsetSD_Y = "40"

	backgroundColor = "#E0E0E0"
	primaryTextColor = "#333333"
	secondaryTextColor = "#666666"
	tertiaryTextColor = "#999999"

	theme.backgroundColor = ValidStr( backgroundColor )
	theme.breadcrumbTextLeft = ValidStr( primaryTextColor )
	theme.breadcrumbDelimiter = ValidStr( primaryTextColor )
	theme.breadcrumbTextRight = ValidStr( primaryTextColor )

	theme.posterScreenLine1Text = ValidStr( primaryTextColor )
	theme.posterScreenLine2Text = ValidStr( secondaryTextColor )
	theme.episodeSynopsisText = ValidStr( secondaryTextColor )

	theme.springboardTitleText = ValidStr( primaryTextColor )
	theme.springboardSynopsisColor = ValidStr( secondaryTextColor )
	theme.springboardRuntimeColor = ValidStr( tertiaryTextColor )
	theme.springboardDirectorColor = ValidStr( tertiaryTextColor )
	theme.springboardDirectorLabelColor = ValidStr( tertiaryTextColor )
	theme.springboardActorColor = ValidStr( tertiaryTextColor )

	app.SetTheme( theme )
end sub

function LoadConfig()
	result = []

	result.push( GetLiveStream() )

	opml = CreateObject( "roXMLElement" )
	raw = NWM_UT_GetStringFromURL( "http://roku.jupitercolony.com/opml.xml" ) ' Pull configuration from a remote config file
	if opml.Parse( raw )
		for each category in opml.body.outline
			result.Push( BuildCategory( category ) )
		next
	else ' Fallback if cannot contact server, find file, or parse
		raw = ReadASCIIFile("pkg:/config.opml") ' Pull configuration from a local config file
		if opml.Parse( raw )
			for each category in opml.body.outline
				result.Push( BuildCategory( category ) )
			next
		end if
	end if

	'result.push( GetQualityIcon() )

	return result
end function

function BuildCategory( category )
	result = {
		title:			ValidStr( category@title )
		shortDescriptionLine1:	ValidStr( category@title )
		shortDescriptionLine2:	ValidStr( category@subtitle )
		sdPosterURL:		ValidStr( category@img )
		hdPosterURL:		ValidStr( category@img )
		url:			ValidStr( category@url )
		categories:		[]
	}

	if category@hqfeed <> invalid
		result.url = ValidStr( category@hqfeed )
	end if

	quality = RegRead( "quality" )
	if quality <> invalid AND strtoi( ValidStr( quality ) ) = 1
		if category@lqfeed <> invalid
			result.url = ValidStr( category@lqfeed )
		end if
	end if

	if category.outline.Count() > 0
		for each subCategory in category.outline
			result.categories.Push( BuildCategory( subCategory ) )
		next
	end if

	return result
end function

function GetLiveStream()
	result = {
		screenTarget:		"livestream"
		calendar:		"http://roku.jupitercolony.com/live-calendar.xml"
		live:			true
		title:			"Live Stream"
		shortDescriptionLine1:	"Live Stream"
		shortDescriptionLine2:	"Watch Jupiter Broadcasting Live!"
		sdPosterURL:		"pkg:/images/mm_icon_focus_sd.jpg"
		hdPosterURL:		"pkg:/images/mm_icon_focus_hd.jpg"
		contentType:		"episode"
		streamurls:		["http://videocdn-us.geocdn.scaleengine.net/jblive-iphone/live/jblive.stream/playlist.m3u8"]
		streamformat:		"hls"
		streamqualities:	["SD"]
		streambitrates:		[0]
		categories:		[]
	}

	return result
end function

function GetQualityIcon()
	result = {
		screenTarget:		"quality"
		title:			"Quality"
		shortDescriptionLine1:	"Quality"
		shortDescriptionLine2:	"Currently set to HQ"
		sdPosterURL:		"pkg:/images/mm_icon_focus_sd.jpg"
		hdPosterURL:		"pkg:/images/mm_icon_focus_hd.jpg"
		contentType:		"episode"
		categories:		[]
	}

	quality = RegRead( "quality" )
	if( quality <> invalid AND strtoi( ValidStr( quality ) ) = 1 )
		result.shortDescriptionLine2 = "Currently set to LQ (mobile quality)"
	end if

	return result
end function

sub Main()
	LoadTheme()
	categories = LoadConfig()

	if categories.Count() > 1
		ShowPosterScreen( categories )
	else
		ShowEpisodeScreen( categories[0] )
	end if
end sub

sub LoadTheme()
	app = CreateObject( "roAppManager" )
	theme = CreateObject( "roAssociativeArray" )
	theme.OverhangSliceSD = "pkg:/images/Overhang_Slice_SD.png"
	theme.OverhangSliceHD = "pkg:/images/Overhang_Slice_HD.png"
	theme.OverhanglogoHD = "pkg:/images/Logo_Overhang_JB_HD.png"
	theme.OverhanglogoSD = "pkg:/images/Logo_Overhang_JB_SD.png"

	theme.OverhangPrimaryLogoOffsetHD_X = "60"
	theme.OverhangPrimaryLogoOffsetHD_Y = "40"

	theme.OverhangPrimaryLogoOffsetSD_X = "60"
	theme.OverhangPrimaryLogoOffsetSD_Y = "40"

	backgroundColor = "#E0E0E0"
	primaryTextColor = "#333333"
	secondaryTextColor = "#666666"
	tertiaryTextColor = "#999999"

	theme.backgroundColor = ValidStr( backgroundColor )
	theme.breadcrumbTextLeft = ValidStr( primaryTextColor )
	theme.breadcrumbDelimiter = ValidStr( primaryTextColor )
	theme.breadcrumbTextRight = ValidStr( primaryTextColor )

	theme.posterScreenLine1Text = ValidStr( primaryTextColor )
	theme.posterScreenLine2Text = ValidStr( secondaryTextColor )
	theme.episodeSynopsisText = ValidStr( secondaryTextColor )

	theme.springboardTitleText = ValidStr( primaryTextColor )
	theme.springboardSynopsisColor = ValidStr( secondaryTextColor )
	theme.springboardRuntimeColor = ValidStr( tertiaryTextColor )
	theme.springboardDirectorColor = ValidStr( tertiaryTextColor )
	theme.springboardDirectorLabelColor = ValidStr( tertiaryTextColor )
	theme.springboardActorColor = ValidStr( tertiaryTextColor )

	app.SetTheme( theme )
end sub

function LoadConfig()
	result = []

	result.push( GetLiveStream() )

	opml = CreateObject( "roXMLElement" )
	raw = NWM_UT_GetStringFromURL( "http://roku.jupitercolony.com/opml.xml" ) ' Pull configuration from a remote config file
	if opml.Parse( raw )
		for each category in opml.body.outline
			result.Push( BuildCategory( category ) )
		next
	else ' Fallback if cannot contact server, find file, or parse
		raw = ReadASCIIFile("pkg:/config.opml") ' Pull configuration from a local config file
		if opml.Parse( raw )
			for each category in opml.body.outline
				result.Push( BuildCategory( category ) )
			next
		end if
	end if

	'result.push( GetQualityIcon() )

	return result
end function

function BuildCategory( category )
	result = {
		title:			ValidStr( category@title )
		shortDescriptionLine1:	ValidStr( category@title )
		shortDescriptionLine2:	ValidStr( category@subtitle )
		sdPosterURL:		ValidStr( category@img )
		hdPosterURL:		ValidStr( category@img )
		url:			ValidStr( category@url )
		categories:		[]
	}

	if category@hqfeed <> invalid
		result.url = ValidStr( category@hqfeed )
	end if

	quality = RegRead( "quality" )
	if quality <> invalid AND strtoi( ValidStr( quality ) ) = 1
		if category@lqfeed <> invalid
			result.url = ValidStr( category@lqfeed )
		end if
	end if

	if category.outline.Count() > 0
		for each subCategory in category.outline
			result.categories.Push( BuildCategory( subCategory ) )
		next
	end if

	return result
end function

function GetLiveStream()
	result = {
		screenTarget:		"livestream"
		calendar:		"http://roku.jupitercolony.com/live-calendar.xml"
		live:			true
		title:			"Live Stream"
		shortDescriptionLine1:	"Live Stream"
		shortDescriptionLine2:	"Watch Jupiter Broadcasting Live!"
		sdPosterURL:		"pkg:/images/mm_icon_focus_sd.jpg"
		hdPosterURL:		"pkg:/images/mm_icon_focus_hd.jpg"
		contentType:		"episode"
		streamurls:		["http://videocdn-us.geocdn.scaleengine.net/jblive-iphone/live/jblive.stream/playlist.m3u8"]
		streamformat:		"hls"
		streamqualities:	["HD"]
		streambitrates:		[0]
		categories:		[]
	}

	return result
end function

function GetQualityIcon()
	result = {
		screenTarget:		"quality"
		title:			"Quality"
		shortDescriptionLine1:	"Quality"
		shortDescriptionLine2:	"Currently set to HQ"
		sdPosterURL:		"pkg:/images/mm_icon_focus_sd.jpg"
		hdPosterURL:		"pkg:/images/mm_icon_focus_hd.jpg"
		contentType:		"episode"
		categories:		[]
	}

	quality = RegRead( "quality" )
	if( quality <> invalid AND strtoi( ValidStr( quality ) ) = 1 )
		result.shortDescriptionLine2 = "Currently set to LQ (mobile quality)"
	end if

	return result
end function


Check for upcoming events to prevent crash. Fixes #5.

function CreateLivestreamScreen(livestream)
	instance = CreateObject("roAssociativeArray")

	instance.screen = CreateObject("roSpringboardScreen")
	instance.messagePort = CreateObject("roMessagePort")
	instance.screen.SetMessagePort(instance.messagePort)
	instance.audioStream = CreateAudioPlayer(instance.messagePort)
	instance.audioStream.SetStream(livestream.audio)

	instance.livestream = livestream

	instance.showScreen = function()
		m.showStars()
		m.showAds()
		m.setButtons(false)
		m.showEpisode() ' Show data so far
		m.fetchCalendar()
		m.showEpisode()
		m.waitForInput()
	end function

	instance.showStars = function()
		' Disable star ratings
		m.screen.SetStaticRatingEnabled(false)
	end function

	instance.showAds = function()
		'Don't show ads, really
		'm.screen.setAdURL(sdAd, hdAd)
		'm.screen.setAdSelectable(false)
		x = 0 'Don't break the editor
	end function

	instance.setButtons = function(isAudioPlaying)
		m.screen.ClearButtons()
		if isAudioPlaying
			m.screen.AddButton(3, "Stop Listening")
		else
			m.screen.AddButton(1, "Watch")
			m.screen.AddButton(2, "Listen")
		end if
	end function

	instance.showEpisode = function()
		' Set content to live stream (automatically parses array)
		m.screen.SetContent(m.livestream)
		m.screen.Show()
	end function

	instance.fetchCalendar = function()
		calendarRaw = m.getRawCalendar()
		calendar = m.parseRawCalendar(calendarRaw)
		desc = m.getDescriptionWithCalendar(calendar)
		m.updateDescriptionWithCalendar(desc)
	end function

	instance.getRawCalendar = function()
		return NWM_UT_GetStringFromURL(m.livestream.calendar)
	end function

	instance.parseRawCalendar = function(calendarRaw)
		calendar = CreateObject("roXMLElement")
		if not calendar.Parse(calendarRaw)
			return invalid
		end if
		return calendar
	end function

	instance.getDescriptionWithCalendar = function(calendar)
		if calendar = invalid
			return "Calendar could not be loaded."
		end if

		if calendar.getChildElements() = invalid
			return "No upcoming events found."
		end if

		nextEvent = m.getNextEvent(calendar)
		isLive = m.isEventLive(nextEvent)
		desc = m.getEventDescription(nextEvent, isLive)

		return desc
	end function

	instance.getNowInSeconds = function()
		now = CreateObject("roDateTime")
		now.mark()
		return now.asSeconds()
	end function

	instance.getNextEvent = function(calendar)
		now = m.getNowInSeconds()
		nextEvent = invalid

		for each event in calendar.Event
			eventStart = m.parseXMLTime(event.start.GetText())
			eventFinish = m.parseXMLTime(event.finish.GetText())
			' If nextevent is not set, set to current event (first run)
			if nextEvent = invalid
				nextEvent = event
			else
				nextEventStart = m.parseXMLTime(nextEvent.start.GetText())
				' If event starts before nextevent and finishes after current time
				if eventStart < nextEventStart AND eventFinish > now
					nextEvent = event
				end if
			end if
		next

		return {
			start: m.parseXMLTime(nextEvent.start.getText())
			finish: m.parseXMLTime(nextEvent.finish.getText())
			summary: strReplace(nextevent.summary.GetText(), "LIVE: ", "") ' Remove "Live:" portions from description
		}
	end function

	instance.parseXMLTime = function(xmlTime)
		return strtoi(ValidStr(xmlTime))
	end function

	instance.isEventLive = function(event)
		now = m.getNowInSeconds()

		' If event starts before now and ends after now, it is live
		return (event.start < now AND event.finish > now)
	end function

	instance.getEventDescription = function(event, isLive)
		desc = event.summary

		if islive
			desc = "LIVE NOW: " + desc
		else
			whenLive = CreateObject("roDateTime")
			whenLive.fromSeconds(event.start)
			whenLive.toLocalTime()

			' Show next live show name and date
			desc = "Next Live Show: " + desc + nl()
			desc = desc + whenLive.asDateString("long-date") + " "

			' Show next live show time (24hr)
			desc = desc + m.get24HourTimeString(whenLive.getHours(), whenLive.getMinutes())

			' Show next live show time (12hr)
			desc = desc + " (" + m.get12HourTimeString(whenLive.getHours(), whenLive.getMinutes()) + ")"
		end if

		desc = desc + nl() + nl() + "Live pre-shows generally start approximately 15 minutes before shows."

		return desc
	end function

	instance.get24HourTimeString = function(hours, minutes)
		timestr = itostr(hours) + ":"
		if minutes < 10 ' If less than 10 minutes, add leading zero
			timestr = timestr + "0"
		end if
		timestr = timestr + itostr(minutes)

		return timestr
	end function

	instance.get12HourTimeString = function(hours, minutes)
		timestr = ""

		ispm = hours > 11 ' If hours is more than 11, is PM
		if ispm ' If is PM, subtract 12 and show hours
			if hours = 12 ' If is PM and is 12, show 12
				timestr = timestr + "12:"
			else
				timestr = timestr + itostr(hours - 12) + ":"
			end if
		else ' If is AM show hours
			if hours = 0 ' If is AM and is 0, show 12
				timestr = timestr + "12:"
			else
				timestr = timestr + itostr(hours) + ":"
			end if
		end if

		if minutes < 10 ' If less than 10 minutes, add leading zero
			timestr = timestr + "0"
		end if
		timestr = timestr + itostr(minutes) + " "

		if ispm
			timestr = timestr + "PM"
		else
			timestr = timestr + "AM"
		end if

		return timestr
	end function

	instance.updateDescriptionWithCalendar = function(desc)
		m.livestream.shortDescriptionLine2 = desc
		m.livestream.description = desc
	end function

	instance.waitForInput = function()
		while true
			msg = wait(0, m.messagePort)
			if msg <> invalid
				if msg.isScreenClosed()
					m.audioStream.Stop()
					exit while
				else if msg.isButtonPressed()
					if msg.GetIndex() = 1
						m.audioStream.Stop()
						CreateVideoScreen(m.livestream).showScreen()
					else if msg.GetIndex() = 2
						m.audioStream.Play()
					else if msg.GetIndex() = 3
						m.audioStream.Stop()
					endif
					m.setButtons(m.audioStream.IsPlaying())
				endif
			end if
		end while
	end function

	return instance
end function

function CreateLivestreamScreen(livestream)
	instance = CreateObject("roAssociativeArray")

	instance.screen = CreateObject("roSpringboardScreen")
	instance.messagePort = CreateObject("roMessagePort")
	instance.screen.SetMessagePort(instance.messagePort)
	instance.audioStream = CreateAudioPlayer(instance.messagePort)
	instance.audioStream.SetStream(livestream.audio)

	instance.livestream = livestream

	instance.showScreen = function()
		m.showStars()
		m.showAds()
		m.setButtons(false)
		m.showEpisode() ' Show data so far
		m.fetchCalendar()
		m.showEpisode()
		m.waitForInput()
	end function

	instance.showStars = function()
		' Disable star ratings
		m.screen.SetStaticRatingEnabled(false)
	end function

	instance.showAds = function()
		'Don't show ads, really
		'm.screen.setAdURL(sdAd, hdAd)
		'm.screen.setAdSelectable(false)
		x = 0 'Don't break the editor
	end function

	instance.setButtons = function(isAudioPlaying)
		m.screen.ClearButtons()
		if isAudioPlaying
			m.screen.AddButton(3, "Stop Listening")
		else
			m.screen.AddButton(1, "Watch")
			m.screen.AddButton(2, "Listen")
		end if
	end function

	instance.showEpisode = function()
		' Set content to live stream (automatically parses array)
		m.screen.SetContent(m.livestream)
		m.screen.Show()
	end function

	instance.fetchCalendar = function()
		calendarRaw = m.getRawCalendar()
		calendar = m.parseRawCalendar(calendarRaw)
		desc = m.getDescriptionWithCalendar(calendar)
		m.updateDescriptionWithCalendar(desc)
	end function

	instance.getRawCalendar = function()
		return NWM_UT_GetStringFromURL(m.livestream.calendar)
	end function

	instance.parseRawCalendar = function(calendarRaw)
		calendar = CreateObject("roXMLElement")
		if not calendar.Parse(calendarRaw)
			return invalid
		end if
		return calendar
	end function

	instance.getDescriptionWithCalendar = function(calendar)
		if calendar = invalid
			return "Calendar could not be loaded."
		end if

		nextEvent = m.getNextEvent(calendar)
		isLive = m.isEventLive(nextEvent)
		desc = m.getEventDescription(nextEvent, isLive)

		return desc
	end function

	instance.getNowInSeconds = function()
		now = CreateObject("roDateTime")
		now.mark()
		return now.asSeconds()
	end function

	instance.getNextEvent = function(calendar)
		now = m.getNowInSeconds()
		nextEvent = invalid

		for each event in calendar.Event
			eventStart = m.parseXMLTime(event.start.GetText())
			eventFinish = m.parseXMLTime(event.finish.GetText())
			' If nextevent is not set, set to current event (first run)
			if nextEvent = invalid
				nextEvent = event
			else
				nextEventStart = m.parseXMLTime(nextEvent.start.GetText())
				' If event starts before nextevent and finishes after current time
				if eventStart < nextEventStart AND eventFinish > now
					nextEvent = event
				end if
			end if
		next

		return {
			start: m.parseXMLTime(nextEvent.start.getText())
			finish: m.parseXMLTime(nextEvent.finish.getText())
			summary: strReplace(nextevent.summary.GetText(), "LIVE: ", "") ' Remove "Live:" portions from description
		}
	end function

	instance.parseXMLTime = function(xmlTime)
		return strtoi(ValidStr(xmlTime))
	end function

	instance.isEventLive = function(event)
		now = m.getNowInSeconds()

		' If event starts before now and ends after now, it is live
		return (event.start < now AND event.finish > now)
	end function

	instance.getEventDescription = function(event, isLive)
		desc = event.summary

		if islive
			desc = "LIVE NOW: " + desc
		else
			whenLive = CreateObject("roDateTime")
			whenLive.fromSeconds(event.start)
			whenLive.toLocalTime()

			' Show next live show name and date
			desc = "Next Live Show: " + desc + nl()
			desc = desc + whenLive.asDateString("long-date") + " "

			' Show next live show time (24hr)
			desc = desc + m.get24HourTimeString(whenLive.getHours(), whenLive.getMinutes())

			' Show next live show time (12hr)
			desc = desc + " (" + m.get12HourTimeString(whenLive.getHours(), whenLive.getMinutes()) + ")"
		end if

		desc = desc + nl() + nl() + "Live pre-shows generally start approximately 15 minutes before shows."

		return desc
	end function

	instance.get24HourTimeString = function(hours, minutes)
		timestr = itostr(hours) + ":"
		if minutes < 10 ' If less than 10 minutes, add leading zero
			timestr = timestr + "0"
		end if
		timestr = timestr + itostr(minutes)

		return timestr
	end function

	instance.get12HourTimeString = function(hours, minutes)
		timestr = ""

		ispm = hours > 11 ' If hours is more than 11, is PM
		if ispm ' If is PM, subtract 12 and show hours
			if hours = 12 ' If is PM and is 12, show 12
				timestr = timestr + "12:"
			else
				timestr = timestr + itostr(hours - 12) + ":"
			end if
		else ' If is AM show hours
			if hours = 0 ' If is AM and is 0, show 12
				timestr = timestr + "12:"
			else
				timestr = timestr + itostr(hours) + ":"
			end if
		end if

		if minutes < 10 ' If less than 10 minutes, add leading zero
			timestr = timestr + "0"
		end if
		timestr = timestr + itostr(minutes) + " "

		if ispm
			timestr = timestr + "PM"
		else
			timestr = timestr + "AM"
		end if

		return timestr
	end function

	instance.updateDescriptionWithCalendar = function(desc)
		m.livestream.shortDescriptionLine2 = desc
		m.livestream.description = desc
	end function

	instance.waitForInput = function()
		while true
			msg = wait(0, m.messagePort)
			if msg <> invalid
				if msg.isScreenClosed()
					m.audioStream.Stop()
					exit while
				else if msg.isButtonPressed()
					if msg.GetIndex() = 1
						m.audioStream.Stop()
						CreateVideoScreen(m.livestream).showScreen()
					else if msg.GetIndex() = 2
						m.audioStream.Play()
					else if msg.GetIndex() = 3
						m.audioStream.Stop()
					endif
					m.setButtons(m.audioStream.IsPlaying())
				endif
			end if
		end while
	end function

	return instance
end function


bug 1109918 - use generic description style on detail screen

' This Source Code Form is subject to the terms of the Mozilla Public
' License, v. 2.0. If a copy of the MPL was not distributed with this file,
' You can obtain one at http://mozilla.org/MPL/2.0/.

Function preShowDetailScreen(breadA=invalid, breadB=invalid) As Object
    port=CreateObject("roMessagePort")
    screen = CreateObject("roSpringboardScreen")
    ' screen.SetDescriptionStyle("video")
    screen.SetDescriptionStyle("generic")
    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"
                print "ButtonPressed"
                if msg.GetIndex() = 1
                    PlayStart = RegRead(showList[showIndex].ContentId)
                    if PlayStart <> invalid then
                        showList[showIndex].PlayStart = PlayStart.ToInt()
                    endif
                    showVideoScreen(showList[showIndex])
                    refreshShowDetail(screen,showList,showIndex)
                endif
                if msg.GetIndex() = 2
                    showList[showIndex].PlayStart = 0
                    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.ClearButtons()
    if regread(show.contentid) <> invalid and regread(show.contentid).toint() >=30 then
    screen.AddButton(1, "Resume playing")
    screen.AddButton(2, "Play from beginning")
    else
    screen.addbutton(2,"Play")
    end if
    screen.SetStaticRatingEnabled(false)
    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

' This Source Code Form is subject to the terms of the Mozilla Public
' License, v. 2.0. If a copy of the MPL was not distributed with this file,
' You can obtain one at http://mozilla.org/MPL/2.0/.

Function preShowDetailScreen(breadA=invalid, breadB=invalid) As Object
    port=CreateObject("roMessagePort")
    screen = CreateObject("roSpringboardScreen")
    screen.SetDescriptionStyle("video")
    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"
                print "ButtonPressed"
                if msg.GetIndex() = 1
                    PlayStart = RegRead(showList[showIndex].ContentId)
                    if PlayStart <> invalid then
                        showList[showIndex].PlayStart = PlayStart.ToInt()
                    endif
                    showVideoScreen(showList[showIndex])
                    refreshShowDetail(screen,showList,showIndex)
                endif
                if msg.GetIndex() = 2
                    showList[showIndex].PlayStart = 0
                    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.ClearButtons()
    if regread(show.contentid) <> invalid and regread(show.contentid).toint() >=30 then
    screen.AddButton(1, "Resume playing")
    screen.AddButton(2, "Play from beginning")
    else
    screen.addbutton(2,"Play")
    end if
    screen.SetStaticRatingEnabled(false)
    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


Added/copy-pasted additional library functionality.

--HG--
extra : convert_revision : svn%3A1a989651-ce6e-ed51-1896-c0ee68ac2431/trunk%4013

REM This file has no dependencies on other common files.
REM
REM Functions in this file:
REM     GetDeviceVersion
REM     GetDeviceESN
REM     IsHD
REM

'******************************************************
'Get our device version
'******************************************************
Function GetDeviceVersion()
    if m.softwareVersion = invalid OR m.softwareVersion = "" then
        m.softwareVersion = CreateObject("roDeviceInfo").GetVersion()
    end if
    return m.softwareVersion
End Function

'******************************************************
'Get our serial number
'******************************************************
Function GetDeviceESN()
    if m.serialNumber = invalid OR m.serialNumber = "" then
        m.serialNumber = CreateObject("roDeviceInfo").GetDeviceUniqueId()
    end if
    return m.serialNumber
End Function

'******************************************************
'Determine if the UI is displayed in SD or HD mode
'******************************************************
Function IsHD()
    di = CreateObject("roDeviceInfo")
    if di.GetDisplayMode() = "720p" then return true
    return false
End Function

'******************************************************
'Return a newline character
'******************************************************
Function nl()
    return chr(10)
End Function

'******************************************************
'Registry Helper Functions
'******************************************************
Function RegRead(key, section=invalid)
    if section = invalid then section = "Default"
    sec = CreateObject("roRegistrySection", section)
    if sec.Exists(key) then return sec.Read(key)
    return invalid
End Function

Function RegWrite(key, val, section=invalid)
    if section = invalid then section = "Default"
    sec = CreateObject("roRegistrySection", section)
    sec.Write(key, val)
    sec.Flush() 'commit it
End Function

Function RegDelete(key, section=invalid)
    if section = invalid then section = "Default"
    sec = CreateObject("roRegistrySection", section)
    sec.Delete(key)
    sec.Flush()
End Function


updated ip

updated ip

'******************************************************
'**  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://10.0.20.150/roku"

    conn.UrlCategoryFeed = conn.UrlPrefix + "/categories"

    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

'******************************************************
'**  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://10.0.200.114/roku"
    conn.UrlCategoryFeed = conn.UrlPrefix + "/categories"

    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


EE-559 Changed category feed to production url

'******************************************************
'**  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   = "https://newspring.cc/roku"

    conn.UrlCategoryFeed = conn.UrlPrefix + "/categories"

    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

'******************************************************
'**  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://10.0.20.150/roku"

    conn.UrlCategoryFeed = conn.UrlPrefix + "/categories"

    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


Disabled beta quality feature.

Disabled beta quality feature.

--HG--
extra : convert_revision : svn%3A1a989651-ce6e-ed51-1896-c0ee68ac2431/trunk%4027

sub Main()
	LoadTheme()
	categories = LoadConfig()

	if categories.Count() > 1
		ShowPosterScreen( categories )
	else
		ShowEpisodeScreen( categories[0] )
	end if
end sub

sub LoadTheme()
	app = CreateObject( "roAppManager" )
	theme = CreateObject( "roAssociativeArray" )
	theme.OverhangSliceSD = "pkg:/images/Overhang_Slice_SD.png"
	theme.OverhangSliceHD = "pkg:/images/Overhang_Slice_HD.png"
	theme.OverhanglogoHD = "pkg:/images/Logo_Overhang_JB_HD.png"
	theme.OverhanglogoSD = "pkg:/images/Logo_Overhang_JB_SD.png"

	theme.OverhangPrimaryLogoOffsetHD_X = "60"
	theme.OverhangPrimaryLogoOffsetHD_Y = "40"

	theme.OverhangPrimaryLogoOffsetSD_X = "60"
	theme.OverhangPrimaryLogoOffsetSD_Y = "40"

	backgroundColor = "#E0E0E0"
	primaryTextColor = "#333333"
	secondaryTextColor = "#666666"
	tertiaryTextColor = "#999999"

	theme.backgroundColor = ValidStr( backgroundColor )
	theme.breadcrumbTextLeft = ValidStr( primaryTextColor )
	theme.breadcrumbDelimiter = ValidStr( primaryTextColor )
	theme.breadcrumbTextRight = ValidStr( primaryTextColor )

	theme.posterScreenLine1Text = ValidStr( primaryTextColor )
	theme.posterScreenLine2Text = ValidStr( secondaryTextColor )
	theme.episodeSynopsisText = ValidStr( secondaryTextColor )

	theme.springboardTitleText = ValidStr( primaryTextColor )
	theme.springboardSynopsisColor = ValidStr( secondaryTextColor )
	theme.springboardRuntimeColor = ValidStr( tertiaryTextColor )
	theme.springboardDirectorColor = ValidStr( tertiaryTextColor )
	theme.springboardDirectorLabelColor = ValidStr( tertiaryTextColor )
	theme.springboardActorColor = ValidStr( tertiaryTextColor )

	app.SetTheme( theme )
end sub

function LoadConfig()
	result = []

	result.push( GetLiveStream() )

	opml = CreateObject( "roXMLElement" )
	raw = NWM_UT_GetStringFromURL( "http://roku.jupitercolony.com/opml.xml" ) ' Pull configuration from a remote config file
	if opml.Parse( raw )
		for each category in opml.body.outline
			result.Push( BuildCategory( category ) )
		next
	else ' Fallback if cannot contact server, find file, or parse
		raw = ReadASCIIFile("pkg:/config.opml") ' Pull configuration from a local config file
		if opml.Parse( raw )
			for each category in opml.body.outline
				result.Push( BuildCategory( category ) )
			next
		end if
	end if

	'result.push( GetQualityIcon() )

	return result
end function

function BuildCategory( category )
	result = {
		title:			ValidStr( category@title )
		shortDescriptionLine1:	ValidStr( category@title )
		shortDescriptionLine2:	ValidStr( category@subtitle )
		sdPosterURL:		ValidStr( category@img )
		hdPosterURL:		ValidStr( category@img )
		url:			ValidStr( category@url )
		categories:		[]
	}

	if category@hqfeed <> invalid
		result.url = ValidStr( category@hqfeed )
	end if

	quality = RegRead( "quality" )
	if quality <> invalid AND strtoi( ValidStr( quality ) ) = 1
		if category@lqfeed <> invalid
			result.url = ValidStr( category@lqfeed )
		end if
	end if

	if category.outline.Count() > 0
		for each subCategory in category.outline
			result.categories.Push( BuildCategory( subCategory ) )
		next
	end if

	return result
end function

function GetLiveStream()
	result = {
		screenTarget:		"livestream"
		calendar:		"http://roku.jupitercolony.com/live-calendar.xml"
		live:			true
		title:			"Live Stream"
		shortDescriptionLine1:	"Live Stream"
		shortDescriptionLine2:	"Watch Jupiter Broadcasting Live!"
		sdPosterURL:		"pkg:/images/mm_icon_focus_sd.png"
		hdPosterURL:		"pkg:/images/mm_icon_focus_hd.jpg"
		contentType:		"episode"
		streamurls:		["http://videocdn-us.geocdn.scaleengine.net/jblive-iphone/live/jblive.stream/playlist.m3u8"]
		streamformat:		"hls"
		streamqualities:	["HD"]
		streambitrates:		[0]
		categories:		[]
	}

	return result
end function

function GetQualityIcon()
	result = {
		screenTarget:		"quality"
		title:			"Quality"
		shortDescriptionLine1:	"Quality"
		shortDescriptionLine2:	"Currently set to HQ"
		sdPosterURL:		"pkg:/images/mm_icon_focus_sd.png"
		hdPosterURL:		"pkg:/images/mm_icon_focus_hd.jpg"
		contentType:		"episode"
		categories:		[]
	}

	quality = RegRead( "quality" )
	if( quality <> invalid AND strtoi( ValidStr( quality ) ) = 1 )
		result.shortDescriptionLine2 = "Currently set to LQ (mobile quality)"
	end if

	return result
end function

sub Main()
	LoadTheme()
	categories = LoadConfig()

	if categories.Count() > 1
		ShowPosterScreen( categories )
	else
		ShowEpisodeScreen( categories[0] )
	end if
end sub

sub LoadTheme()
	app = CreateObject( "roAppManager" )
	theme = CreateObject( "roAssociativeArray" )
	theme.OverhangSliceSD = "pkg:/images/Overhang_Slice_SD.png"
	theme.OverhangSliceHD = "pkg:/images/Overhang_Slice_HD.png"
	theme.OverhanglogoHD = "pkg:/images/Logo_Overhang_JB_HD.png"
	theme.OverhanglogoSD = "pkg:/images/Logo_Overhang_JB_SD.png"

	theme.OverhangPrimaryLogoOffsetHD_X = "60"
	theme.OverhangPrimaryLogoOffsetHD_Y = "40"

	theme.OverhangPrimaryLogoOffsetSD_X = "60"
	theme.OverhangPrimaryLogoOffsetSD_Y = "40"

	backgroundColor = "#E0E0E0"
	primaryTextColor = "#333333"
	secondaryTextColor = "#666666"
	tertiaryTextColor = "#999999"

	theme.backgroundColor = ValidStr( backgroundColor )
	theme.breadcrumbTextLeft = ValidStr( primaryTextColor )
	theme.breadcrumbDelimiter = ValidStr( primaryTextColor )
	theme.breadcrumbTextRight = ValidStr( primaryTextColor )

	theme.posterScreenLine1Text = ValidStr( primaryTextColor )
	theme.posterScreenLine2Text = ValidStr( secondaryTextColor )
	theme.episodeSynopsisText = ValidStr( secondaryTextColor )

	theme.springboardTitleText = ValidStr( primaryTextColor )
	theme.springboardSynopsisColor = ValidStr( secondaryTextColor )
	theme.springboardRuntimeColor = ValidStr( tertiaryTextColor )
	theme.springboardDirectorColor = ValidStr( tertiaryTextColor )
	theme.springboardDirectorLabelColor = ValidStr( tertiaryTextColor )
	theme.springboardActorColor = ValidStr( tertiaryTextColor )

	app.SetTheme( theme )
end sub

function LoadConfig()
	result = []

	result.push( GetLiveStream() )

	opml = CreateObject( "roXMLElement" )
	raw = NWM_UT_GetStringFromURL( "http://roku.jupitercolony.com/opml.xml" ) ' Pull configuration from a remote config file
	if opml.Parse( raw )
		for each category in opml.body.outline
			result.Push( BuildCategory( category ) )
		next
	else ' Fallback if cannot contact server, find file, or parse
		raw = ReadASCIIFile("pkg:/config.opml") ' Pull configuration from a local config file
		if opml.Parse( raw )
			for each category in opml.body.outline
				result.Push( BuildCategory( category ) )
			next
		end if
	end if

	result.push( GetQualityIcon() )

	return result
end function

function BuildCategory( category )
	result = {
		title:			ValidStr( category@title )
		shortDescriptionLine1:	ValidStr( category@title )
		shortDescriptionLine2:	ValidStr( category@subtitle )
		sdPosterURL:		ValidStr( category@img )
		hdPosterURL:		ValidStr( category@img )
		url:			ValidStr( category@url )
		categories:		[]
	}

	if category@hqfeed <> invalid
		result.url = ValidStr( category@hqfeed )
	end if

	quality = RegRead( "quality" )
	if quality <> invalid AND strtoi( ValidStr( quality ) ) = 1
		if category@lqfeed <> invalid
			result.url = ValidStr( category@lqfeed )
		end if
	end if

	if category.outline.Count() > 0
		for each subCategory in category.outline
			result.categories.Push( BuildCategory( subCategory ) )
		next
	end if

	return result
end function

function GetLiveStream()
	result = {
		screenTarget:		"livestream"
		calendar:		"http://roku.jupitercolony.com/live-calendar.xml"
		live:			true
		title:			"Live Stream"
		shortDescriptionLine1:	"Live Stream"
		shortDescriptionLine2:	"Watch Jupiter Broadcasting Live!"
		sdPosterURL:		"pkg:/images/mm_icon_focus_sd.png"
		hdPosterURL:		"pkg:/images/mm_icon_focus_hd.jpg"
		contentType:		"episode"
		streamurls:		["http://videocdn-us.geocdn.scaleengine.net/jblive-iphone/live/jblive.stream/playlist.m3u8"]
		streamformat:		"hls"
		streamqualities:	["HD"]
		streambitrates:		[0]
		categories:		[]
	}

	return result
end function

function GetQualityIcon()
	result = {
		screenTarget:		"quality"
		title:			"Quality"
		shortDescriptionLine1:	"Quality"
		shortDescriptionLine2:	"Currently set to HQ"
		sdPosterURL:		"pkg:/images/mm_icon_focus_sd.png"
		hdPosterURL:		"pkg:/images/mm_icon_focus_hd.jpg"
		contentType:		"episode"
		categories:		[]
	}

	quality = RegRead( "quality" )
	if( quality <> invalid AND strtoi( ValidStr( quality ) ) = 1 )
		result.shortDescriptionLine2 = "Currently set to LQ (mobile quality)"
	end if

	return result
end function


Replaced Play with Watch on livestream screen to remove ambiguity.

function ShowLivestreamScreen( livestream )
	screen = CreateObject( "roSpringboardScreen" )
	screen.SetMessagePort( CreateObject( "roMessagePort" ) )

	'screen.setAdURL( sdAd, hdAd )
	'screen.setAdSelectable( false )

	' Disable star ratings
	screen.SetStaticRatingEnabled( false )
	' Add "Play" button
	Livestream_SetButtons( screen, false )
	' Set content to live stream (automatically parses array)
	screen.SetContent( livestream )
	' Show screen
	screen.Show()

	' Fetch calendar feed
	raw = NWM_UT_GetStringFromURL( livestream.calendar )
	' Parse calendar feed
	calendar = CreateObject( "roXMLElement" )
	if calendar.Parse( raw )
		islive = false
		nextevent = invalid
		now = CreateObject( "roDateTime" )
		now.mark()
		now_s = now.asSeconds()

		for each event in calendar.Event
			' Parse start and end times into ints
			estart = strtoi( ValidStr( event.start.GetText() ) )
			efinish = strtoi( ValidStr( event.finish.GetText() ) )
			' If nextevent is not set, set to current event (first run)
			if nextevent = invalid
				nextevent = event
			else
				' Parse event's start time
				nestart = strtoi( ValidStr( nextevent.start.GetText() ) )
				' If event starts before nextevent and finishes after current time
				if estart < nestart AND efinish > now_s
					nextevent = event
				end if
			end if
		next
		nestart = strtoi( ValidStr( nextevent.start.GetText() ) )
		nefinish = strtoi( ValidStr( nextevent.finish.GetText() ) )

		islive = ( nestart < now_s AND nefinish > now_s ) ' If event starts before now and ends after now, it is live
		desc = strReplace( nextevent.summary.GetText(), "LIVE: ", "" ) ' Remove "Live:" portions from description
		if islive
			desc = "LIVE NOW: " + desc
		else
			nextlive = CreateObject( "roDateTime" )
			nextlive.fromSeconds( nestart )
			nextlive.toLocalTime()

			' Show next live show name and date
			desc = "Next Live Show: " + desc + nl()
			desc = desc + nextlive.asDateString( "long-date" ) + " "

			' Show next live show time (24hr)
			desc = desc + itostr( nextlive.getHours() ) + ":"
			if nextlive.getMinutes() < 10 ' If less than 10 minutes, add leading zero
				desc = desc + "0"
			end if
			desc = desc + itostr( nextlive.getMinutes() )

			' Show next live show time (12hr)
			ispm = nextlive.getHours() > 11 ' If hours is more than 11, is PM
			desc = desc + " ("
			if ispm ' If is PM, subtract 12 and show hours
				if nextlive.getHours() = 12 ' If is PM and is 12, show 12
					desc = desc + "12:"
				else
					desc = desc + itostr( nextlive.getHours() - 12 ) + ":"
				end if
			else ' If is AM show hours
				if nextlive.getHours() = 0 ' If is AM and is 0, show 12
					desc = desc + "12:"
				else
					desc = desc + itostr( nextlive.getHours() ) + ":"
				end if
			end if
			if nextlive.getMinutes() < 10 ' If less than 10 minutes, add leading zero
				desc = desc + "0"
			end if
			desc = desc + itostr( nextlive.getMinutes() ) + " "
			if ispm ' If is PM, show PM
				desc = desc + "PM"
			else ' If is AM, show AM
				desc = desc + "AM"
			end if
			desc = desc + ")"
		end if

		desc = desc + nl() + nl() + "Live pre-shows generally start approximately 15 minutes before shows."

		livestream.shortDescriptionLine2 = desc
		livestream.description = desc
		screen.SetContent( livestream )
		screen.Show()
	end if

	AudioStream = CreateAudioPlayer( screen.GetMessagePort() )
	AudioStream.SetStream( livestream.audio )

	while true
		msg = wait( 0, screen.GetMessagePort() )
		if msg <> invalid
			if msg.isScreenClosed()
				AudioStream.Stop()
				exit while
			else if msg.isButtonPressed()
				if msg.GetIndex() = 1
					AudioStream.Stop()
					ShowVideoScreen( livestream )
				else if msg.GetIndex() = 2
					AudioStream.Play()
				else if msg.GetIndex() = 3
					AudioStream.Stop()
				endif
				Livestream_SetButtons( screen, AudioStream.IsPlaying() )
			endif
		end if
	end while

	return selectedEpisode
end function

sub Livestream_SetButtons( screen, isPlaying )
	screen.ClearButtons()
	if isPlaying
		screen.AddButton( 3, "Stop Listening" )
	else
		screen.AddButton( 1, "Watch" )
		screen.AddButton( 2, "Listen" )
	end if
end sub

function ShowLivestreamScreen( livestream )
	screen = CreateObject( "roSpringboardScreen" )
	screen.SetMessagePort( CreateObject( "roMessagePort" ) )

	'screen.setAdURL( sdAd, hdAd )
	'screen.setAdSelectable( false )

	' Disable star ratings
	screen.SetStaticRatingEnabled( false )
	' Add "Play" button
	Livestream_SetButtons( screen, false )
	' Set content to live stream (automatically parses array)
	screen.SetContent( livestream )
	' Show screen
	screen.Show()

	' Fetch calendar feed
	raw = NWM_UT_GetStringFromURL( livestream.calendar )
	' Parse calendar feed
	calendar = CreateObject( "roXMLElement" )
	if calendar.Parse( raw )
		islive = false
		nextevent = invalid
		now = CreateObject( "roDateTime" )
		now.mark()
		now_s = now.asSeconds()

		for each event in calendar.Event
			' Parse start and end times into ints
			estart = strtoi( ValidStr( event.start.GetText() ) )
			efinish = strtoi( ValidStr( event.finish.GetText() ) )
			' If nextevent is not set, set to current event (first run)
			if nextevent = invalid
				nextevent = event
			else
				' Parse event's start time
				nestart = strtoi( ValidStr( nextevent.start.GetText() ) )
				' If event starts before nextevent and finishes after current time
				if estart < nestart AND efinish > now_s
					nextevent = event
				end if
			end if
		next
		nestart = strtoi( ValidStr( nextevent.start.GetText() ) )
		nefinish = strtoi( ValidStr( nextevent.finish.GetText() ) )

		islive = ( nestart < now_s AND nefinish > now_s ) ' If event starts before now and ends after now, it is live
		desc = strReplace( nextevent.summary.GetText(), "LIVE: ", "" ) ' Remove "Live:" portions from description
		if islive
			desc = "LIVE NOW: " + desc
		else
			nextlive = CreateObject( "roDateTime" )
			nextlive.fromSeconds( nestart )
			nextlive.toLocalTime()

			' Show next live show name and date
			desc = "Next Live Show: " + desc + nl()
			desc = desc + nextlive.asDateString( "long-date" ) + " "

			' Show next live show time (24hr)
			desc = desc + itostr( nextlive.getHours() ) + ":"
			if nextlive.getMinutes() < 10 ' If less than 10 minutes, add leading zero
				desc = desc + "0"
			end if
			desc = desc + itostr( nextlive.getMinutes() )

			' Show next live show time (12hr)
			ispm = nextlive.getHours() > 11 ' If hours is more than 11, is PM
			desc = desc + " ("
			if ispm ' If is PM, subtract 12 and show hours
				if nextlive.getHours() = 12 ' If is PM and is 12, show 12
					desc = desc + "12:"
				else
					desc = desc + itostr( nextlive.getHours() - 12 ) + ":"
				end if
			else ' If is AM show hours
				if nextlive.getHours() = 0 ' If is AM and is 0, show 12
					desc = desc + "12:"
				else
					desc = desc + itostr( nextlive.getHours() ) + ":"
				end if
			end if
			if nextlive.getMinutes() < 10 ' If less than 10 minutes, add leading zero
				desc = desc + "0"
			end if
			desc = desc + itostr( nextlive.getMinutes() ) + " "
			if ispm ' If is PM, show PM
				desc = desc + "PM"
			else ' If is AM, show AM
				desc = desc + "AM"
			end if
			desc = desc + ")"
		end if

		desc = desc + nl() + nl() + "Live pre-shows generally start approximately 15 minutes before shows."

		livestream.shortDescriptionLine2 = desc
		livestream.description = desc
		screen.SetContent( livestream )
		screen.Show()
	end if

	AudioStream = CreateAudioPlayer( screen.GetMessagePort() )
	AudioStream.SetStream( livestream.audio )

	while true
		msg = wait( 0, screen.GetMessagePort() )
		if msg <> invalid
			if msg.isScreenClosed()
				AudioStream.Stop()
				exit while
			else if msg.isButtonPressed()
				if msg.GetIndex() = 1
					AudioStream.Stop()
					ShowVideoScreen( livestream )
				else if msg.GetIndex() = 2
					AudioStream.Play()
				else if msg.GetIndex() = 3
					AudioStream.Stop()
				endif
				Livestream_SetButtons( screen, AudioStream.IsPlaying() )
			endif
		end if
	end while

	return selectedEpisode
end function

sub Livestream_SetButtons( screen, isPlaying )
	screen.ClearButtons()
	if isPlaying
		screen.AddButton( 3, "Stop Listening" )
	else
		screen.AddButton( 1, "Play" )
		screen.AddButton( 2, "Listen" )
	end if
end sub


Some comments added in roku

Some comments added in roku

'To get the authentication token for Junction TV API calls

'For detailed description read: http://api.junctiontv.com/jtv/jtapi/getting-access-tokens-2
'Refer Github: https://github.com/JunctionTV/Getting-Access-Tokens

'-----------------------------------------------------------------------------------------
' Author: Subhankar Ganguly <subhankar1981@gmail.com>
'
' License: The MIT License (MIT)
' Copyright (c) <2016> <Junction TV Inc.>
'------------------------------------------------------------------------------------------

Sub Main(args as Dynamic)
print "Main called "
respauth = ApiAuthentication()
APIAuthToken = respauth.gettoken(respauth)
print "APIAuthToken : "+APIAuthToken
End Sub


Enable gzip compression encoding for HTTP requests.

--HG--
extra : convert_revision : svn%3A1a989651-ce6e-ed51-1896-c0ee68ac2431/trunk%4011

''
''	NWM_Utilities.brs
''	chagedorn@roku.com
''
''

function NWM_Utilities()
	this = {
		GetStringFromURL:	NWM_UT_GetStringFromURL
		HTMLEntityDecode:	NWM_UT_HTMLEntityDecode
		HTMLStripTags:		NWM_UT_HTMLStripTags
	}

	return this
end function

function NWM_UT_GetStringFromURL(url, userAgent = "")
	result = ""
	timeout = 10000

  ut = CreateObject("roURLTransfer")
  ut.SetPort(CreateObject("roMessagePort"))
  ut.EnableEncodings(true) '' Enable gzip encoding
  if userAgent <> ""
	  ut.AddHeader("user-agent", userAgent)
	end if
  ut.SetURL(url)
	if ut.AsyncGetToString()
		event = wait(timeout, ut.GetPort())
		if type(event) = "roUrlEvent"
				print ValidStr(event.GetResponseCode())
				result = event.GetString()
				'exit while
		elseif event = invalid
				ut.AsyncCancel()
				REM reset the connection on timeouts
				'ut = CreateURLTransferObject(url)
				'timeout = 2 * timeout
		else
				print "roUrlTransfer::AsyncGetToString(): unknown event"
		endif
	end if

	return result
end function

function NWM_UT_HTMLEntityDecode(inStr)
	result = inStr

	rx = CreateObject("roRegEx", "'", "")
	result = rx.ReplaceAll(result, "'")

	rx = CreateObject("roRegEx", "&", "")
	result = rx.ReplaceAll(result, "&")

	rx = CreateObject("roRegEx", "&(quot|rsquo|lsquo);", "")
	result = rx.ReplaceAll(result, Chr(34))

	rx = CreateObject("roRegEx", "&\w+;", "")
	result = rx.ReplaceAll(result, Chr(34))

	return result
end function

function NWM_UT_HTMLStripTags(inStr)
	result = inStr

	rx = CreateObject("roRegEx", "<.*?>", "")
	result = rx.ReplaceAll(result, "")

	return result
end function

''
''	NWM_Utilities.brs
''	chagedorn@roku.com
''
''

function NWM_Utilities()
	this = {
		GetStringFromURL:	NWM_UT_GetStringFromURL
		HTMLEntityDecode:	NWM_UT_HTMLEntityDecode
		HTMLStripTags:		NWM_UT_HTMLStripTags
	}

	return this
end function

function NWM_UT_GetStringFromURL(url, userAgent = "")
	result = ""
	timeout = 10000

  ut = CreateObject("roURLTransfer")
  ut.SetPort(CreateObject("roMessagePort"))
  if userAgent <> ""
	  ut.AddHeader("user-agent", userAgent)
	end if
  ut.SetURL(url)
	if ut.AsyncGetToString()
		event = wait(timeout, ut.GetPort())
		if type(event) = "roUrlEvent"
				print ValidStr(event.GetResponseCode())
				result = event.GetString()
				'exit while
		elseif event = invalid
				ut.AsyncCancel()
				REM reset the connection on timeouts
				'ut = CreateURLTransferObject(url)
				'timeout = 2 * timeout
		else
				print "roUrlTransfer::AsyncGetToString(): unknown event"
		endif
	end if

	return result
end function

function NWM_UT_HTMLEntityDecode(inStr)
	result = inStr

	rx = CreateObject("roRegEx", "'", "")
	result = rx.ReplaceAll(result, "'")

	rx = CreateObject("roRegEx", "&", "")
	result = rx.ReplaceAll(result, "&")

	rx = CreateObject("roRegEx", "&(quot|rsquo|lsquo);", "")
	result = rx.ReplaceAll(result, Chr(34))

	rx = CreateObject("roRegEx", "&\w+;", "")
	result = rx.ReplaceAll(result, Chr(34))

	return result
end function

function NWM_UT_HTMLStripTags(inStr)
	result = inStr

	rx = CreateObject("roRegEx", "<.*?>", "")
	result = rx.ReplaceAll(result, "")

	return result
end function


Don't need to be so showy.

Don't need to be so showy.

function ShowSpringboardScreen( episodes, selectedEpisode )
	screen = CreateObject( "roSpringboardScreen" )
	screen.SetMessagePort( CreateObject( "roMessagePort" ) )

	'screen.setAdURL( sdAd, hdAd )
	'screen.setAdSelectable( false )

	screen.SetStaticRatingEnabled( false )
	SpringBoardScreen_showContent( screen, episodes[selectedEpisode] )

	while true
		msg = wait( 0, screen.GetMessagePort() )
		if msg <> invalid
			if msg.isScreenClosed()
				exit while
			else if msg.isButtonPressed()
				if msg.GetIndex() = 1
					PlayStart = RegRead( episodes[selectedEpisode].title )
					if PlayStart <> invalid then
						episodes[selectedEpisode].PlayStart = PlayStart.ToInt()
					end if
					ShowVideoScreen( episodes[selectedEpisode] )
					SpringBoardScreen_setButtons( screen, episodes[selectedEpisode] )
					screen.Show()
				end if
				if msg.GetIndex() = 2
					episodes[selectedEpisode].PlayStart = 0
					ShowVideoScreen( episodes[selectedEpisode] )
					SpringBoardScreen_setButtons( screen, episodes[selectedEpisode] )
					screen.Show()
				end if
			else if msg.isRemoteKeyPressed()
				if msg.GetIndex() = 4 ' LEFT
					if selectedEpisode = 0
						selectedEpisode = episodes.Count() - 1
					else
						selectedEpisode = selectedEpisode - 1
					end if
					SpringBoardScreen_showContent( screen, episodes[selectedEpisode] )
				else if msg.GetIndex() = 5 ' RIGHT
					if selectedEpisode = episodes.Count() - 1
						selectedEpisode = 0
					else
						selectedEpisode = selectedEpisode + 1
					end if
                    SpringBoardScreen_showContent( screen, episodes[selectedEpisode] )
				end if
			end if
		end if
	end while
	return selectedEpisode
end function

sub SpringBoardScreen_showContent( screen, episode )
    SpringBoardScreen_setButtons( screen, episode )

    SpringBoardScreen_swapPoster( episode )
    screen.SetContent( episode )
    screen.Show()
    SpringBoardScreen_swapPoster( episode )
end sub

sub SpringBoardScreen_swapPoster( show )
	tmp = show.sdPosterURL
	if show.sdPosterURL_hq <> invalid and show.sdPosterURL_hq <> ""
		show.sdPosterURL = show.sdPosterURL_hq
		show.sdPosterURL_hq = tmp
	end if
	tmp = show.hdPosterURL
	if show.hdPosterURL_hq <> invalid and show.hdPosterURL_hq <> ""
		show.hdPosterURL = show.hdPosterURL_hq
		show.hdPosterURL_hq = tmp
	end if
end sub

sub SpringBoardScreen_setButtons( screen, episode )
	screen.ClearButtons()
	if RegRead( episode.title ) <> invalid and RegRead( episode.title ).toint() >=30 then
		screen.AddButton( 1, "Resume Playing" )
		screen.AddButton( 2, "Play from Beginning" )
	else
		screen.addbutton( 2,"Play" )
	end if
end sub

function ShowSpringboardScreen( episodes, selectedEpisode )
	screen = CreateObject( "roSpringboardScreen" )
	screen.SetMessagePort( CreateObject( "roMessagePort" ) )

	'screen.setAdURL( sdAd, hdAd )
	'screen.setAdSelectable( false )

	screen.SetStaticRatingEnabled( false )
	SpringBoardScreen_showContent( screen, episodes[selectedEpisode] )

	while true
		msg = wait( 0, screen.GetMessagePort() )
		if msg <> invalid
			if msg.isScreenClosed()
				exit while
			else if msg.isButtonPressed()
				if msg.GetIndex() = 1
					PlayStart = RegRead( episodes[selectedEpisode].title )
					if PlayStart <> invalid then
						episodes[selectedEpisode].PlayStart = PlayStart.ToInt()
					end if
					ShowVideoScreen( episodes[selectedEpisode] )
					SpringBoardScreen_setButtons( screen, episodes[selectedEpisode] )
					screen.Show()
				end if
				if msg.GetIndex() = 2
					episodes[selectedEpisode].PlayStart = 0
					ShowVideoScreen( episodes[selectedEpisode] )
					SpringBoardScreen_setButtons( screen, episodes[selectedEpisode] )
					screen.Show()
				end if
			else if msg.isRemoteKeyPressed()
				if msg.GetIndex() = 4 ' LEFT
					if selectedEpisode = 0
						selectedEpisode = episodes.Count() - 1
					else
						selectedEpisode = selectedEpisode - 1
					end if
					SpringBoardScreen_showContent( screen, episodes[selectedEpisode] )
				else if msg.GetIndex() = 5 ' RIGHT
					if selectedEpisode = episodes.Count() - 1
						selectedEpisode = 0
					else
						selectedEpisode = selectedEpisode + 1
					end if
                    SpringBoardScreen_showContent( screen, episodes[selectedEpisode] )
				end if
			end if
		end if
	end while
	return selectedEpisode
end function

sub SpringBoardScreen_showContent( screen, episode )
    SpringBoardScreen_setButtons( screen, episode )
    screen.Show()

    SpringBoardScreen_swapPoster( episode )
    screen.SetContent( episode )
    screen.Show()
    SpringBoardScreen_swapPoster( episode )
end sub

sub SpringBoardScreen_swapPoster( show )
	tmp = show.sdPosterURL
	if show.sdPosterURL_hq <> invalid and show.sdPosterURL_hq <> ""
		show.sdPosterURL = show.sdPosterURL_hq
		show.sdPosterURL_hq = tmp
	end if
	tmp = show.hdPosterURL
	if show.hdPosterURL_hq <> invalid and show.hdPosterURL_hq <> ""
		show.hdPosterURL = show.hdPosterURL_hq
		show.hdPosterURL_hq = tmp
	end if
end sub

sub SpringBoardScreen_setButtons( screen, episode )
	screen.ClearButtons()
	if RegRead( episode.title ) <> invalid and RegRead( episode.title ).toint() >=30 then
		screen.AddButton( 1, "Resume Playing" )
		screen.AddButton( 2, "Play from Beginning" )
	else
		screen.addbutton( 2,"Play" )
	end if
end sub