old_contents:
sub Main()
	app = CreateObject( "roAppManager" )
	app.setTheme( LoadTheme() )
	categories = LoadConfig()

	CreatePosterScreen(categories).showScreen()
end sub

function LoadTheme()
	theme = CreateObject( "roAssociativeArray" )
	theme.OverhangSliceSD = "pkg:/images/Overhang_Background_SD.png"
	theme.OverhangSliceHD = "pkg:/images/Overhang_Background_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 = "20"

	theme.OverhangPrimaryLogoOffsetSD_X = "40"
	theme.OverhangPrimaryLogoOffsetSD_Y = "20"

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

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

	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 )

	return theme
end function

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
		ShowMessageDialog( "Cannot Connect", "Cannot connect to server. Please check your connection and try again." )
	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.png"
		contentType:		"episode"
		streamurls:		["http://videocdn-us.geocdn.scaleengine.net/jblive-iphone/live/jblive.stream/playlist.m3u8"]
		streamformat:		"hls"
		streamqualities:	["SD"]
		streambitrates:	[0]
		categories:		[]
	}

	result.audio = {
		live:			true
		title:			"Audio Stream"
		shortDescriptionLine1:	"Audio Stream"
		shortDescriptionLine2:	"Listen To JB Radio Live!"
		sdPosterURL:		"pkg:/images/mm_icon_focus_sd.jpg"
		hdPosterURL:		"pkg:/images/mm_icon_focus_hd.jpg"
		contentType:		"episode"
		streamurls:		["http://jbradio.out.airtime.pro:8000/jbradio_a"]
		streamformat:		"mp3"
		streambitrates:	[0]
		streamqualities:	["SD"]
		categories:		[]
	}

	return result
end function

function GetQualityIcon()
	result = {
		screenTarget:		"quality"
		title:			"Quality"
		shortDescriptionLine1:	"Quality"
		shortDescriptionLine2:	"Currently set to HQ"
		sdPosterURL:		"pkg:/images/settings.png"
		hdPosterURL:		"pkg:/images/settings.png"
		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

new_contents:
sub Main()
	app = CreateObject( "roAppManager" )
	app.setTheme( LoadTheme() )
	categories = LoadConfig()

	CreatePosterScreen(categories).showScreen()
end sub

function LoadTheme()
	theme = CreateObject( "roAssociativeArray" )
	theme.OverhangSliceSD = "pkg:/images/Overhang_Background_SD.png"
	theme.OverhangSliceHD = "pkg:/images/Overhang_Background_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 = "20"

	theme.OverhangPrimaryLogoOffsetSD_X = "40"
	theme.OverhangPrimaryLogoOffsetSD_Y = "20"

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

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

	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 )

	return theme
end function

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
		ShowMessageDialog( "Cannot Connect", "Cannot connect to server. Please check your connection and try again." )
	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.png"
		contentType:		"episode"
		streamurls:		["http://jblive.videocdn.scaleengine.net/jb-live/play/jblive.stream/playlist.m3u8"]
		streamformat:		"hls"
		streamqualities:	["SD"]
		streambitrates:	[0]
		categories:		[]
	}

	result.audio = {
		live:			true
		title:			"Audio Stream"
		shortDescriptionLine1:	"Audio Stream"
		shortDescriptionLine2:	"Listen To JB Radio Live!"
		sdPosterURL:		"pkg:/images/mm_icon_focus_sd.jpg"
		hdPosterURL:		"pkg:/images/mm_icon_focus_hd.jpg"
		contentType:		"episode"
		streamurls:		["http://jbradio.out.airtime.pro:8000/jbradio_a"]
		streamformat:		"mp3"
		streambitrates:	[0]
		streamqualities:	["SD"]
		categories:		[]
	}

	return result
end function

function GetQualityIcon()
	result = {
		screenTarget:		"quality"
		title:			"Quality"
		shortDescriptionLine1:	"Quality"
		shortDescriptionLine2:	"Currently set to HQ"
		sdPosterURL:		"pkg:/images/settings.png"
		hdPosterURL:		"pkg:/images/settings.png"
		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

subject: Update stream url

message:
Update stream url

Use the new stream url

license: mit

repos: cbojar/jb-roku,cdgeeko/jb-roku


old_contents:
function CreateSpringboardScreen(episodes, selectedEpisodeIndex)
	instance = CreateObject("roAssociativeArray")

	instance.screen = CreateObject("roSpringboardScreen")
	instance.messagePort = CreateObject("roMessagePort")
	instance.screen.SetMessagePort(instance.messagePort)
	instance.lastMessage = invalid
	instance.screen.SetStaticRatingEnabled(false)

	instance.episodes = episodes
	instance.selectedEpisodeIndex = selectedEpisodeIndex

	instance.getSelectedEpisode = function()
		return m.episodes[m.selectedEpisodeIndex]
	end function

	instance.showScreen = function()
		m.showAds()
		m.showEpisode()
		m.waitForInput()

		return m.selectedEpisodeIndex
	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.showEpisode = function()
		episode = m.getSelectedEpisode()
	    m.setButtons()

	    m.swapPoster()
	    m.screen.SetContent(episode)
	    m.screen.Show()
	    m.swapPoster()
	end function

	instance.swapPoster = function()
		episode = m.getSelectedEpisode()
		tmp = episode.sdPosterURL
		if episode.sdPosterURL_hq <> invalid and episode.sdPosterURL_hq <> ""
			episode.sdPosterURL = episode.sdPosterURL_hq
			episode.sdPosterURL_hq = tmp
		end if
		tmp = episode.hdPosterURL
		if episode.hdPosterURL_hq <> invalid and episode.hdPosterURL_hq <> ""
			episode.hdPosterURL = episode.hdPosterURL_hq
			episode.hdPosterURL_hq = tmp
		end if
	end function

	instance.setButtons = function()
		episode = m.getSelectedEpisode()
		m.screen.ClearButtons()
		if m.isPartiallyWatched() then
			m.screen.AddButton(1, "Resume Playing")
			m.screen.AddButton(2, "Play from Beginning")
		else
			m.screen.AddButton(2, "Play")
		end if
	end function

	instance.isPartiallyWatched = function()
		episode = m.getSelectedEpisode()
		return RegRead(episode.title) <> invalid and RegRead(episode.title).ToInt() >= 30
	end function

	instance.waitForInput = function()
		episode = m.getSelectedEpisode()
		while m.hasNonExitMessage()
			m.processMessage()
		end while
	end function

	instance.processMessage = function()
		msg = m.getLastMessage()
		if msg = invalid then
			return invalid
		end if

		if msg.isButtonPressed()
			m.processButtonPress()
		else if msg.isRemoteKeyPressed()
			m.processRemoteKeyPress()
		end if
	end function

	instance.getLastMessage = function()
		return m.lastMessage
	end function

	instance.processButtonPress = function()
		msg = m.getLastMessage()
		episode = m.getSelectedEpisode()
		if msg.GetIndex() = 1
			PlayStart = RegRead(episode.title)
			if PlayStart <> invalid then
				episode.PlayStart = PlayStart.ToInt()
			end if
		else if msg.GetIndex() = 2
			episode.PlayStart = 0
		end if
		ShowVideoScreen(episode)
		m.setButtons()
		m.screen.Show()
	end function

	instance.processRemoteKeyPress = function()
		msg = m.getLastMessage()
		if msg.GetIndex() = 4 ' LEFT
			m.movePreviousEpisode()
		else if msg.GetIndex() = 5 ' RIGHT
			m.moveNextEpisode()
		end if
		m.showEpisode()
	end function

	instance.hasNonExitMessage = function()
		m.lastMessage = wait(0, m.messagePort)
		return m.lastMessage = invalid or not m.lastMessage.isScreenClosed()
	end function

	instance.moveNextEpisode = function()
		if m.selectedEpisodeIndex = m.episodes.Count() - 1
			m.selectedEpisodeIndex = 0
		else
			m.selectedEpisodeIndex = m.selectedEpisodeIndex + 1
		end if

		return m.selectedEpisodeIndex
	end function

	instance.movePreviousEpisode = function()
		if m.selectedEpisodeIndex = 0
			m.selectedEpisodeIndex = m.episodes.Count() - 1
		else
			m.selectedEpisodeIndex = m.selectedEpisodeIndex - 1
		end if

		return m.selectedEpisodeIndex
	end function

	return instance
end function

new_contents:
function CreateSpringboardScreen(episodes, selectedEpisodeIndex)
	instance = CreateObject("roAssociativeArray")

	instance.screen = CreateObject("roSpringboardScreen")
	instance.messagePort = CreateObject("roMessagePort")
	instance.screen.SetMessagePort(instance.messagePort)
	instance.watchedStatusTracker = WatchedStatusTracker()
	instance.lastMessage = invalid
	instance.screen.SetStaticRatingEnabled(false)

	instance.episodes = episodes
	instance.selectedEpisodeIndex = selectedEpisodeIndex

	instance.getSelectedEpisode = function()
		return m.episodes[m.selectedEpisodeIndex]
	end function

	instance.showScreen = function()
		m.showAds()
		m.showEpisode()
		m.waitForInput()

		return m.selectedEpisodeIndex
	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.showEpisode = function()
		episode = m.getSelectedEpisode()
	    m.setButtons()

	    m.swapPoster()
	    m.screen.SetContent(episode)
	    m.screen.Show()
	    m.swapPoster()
	end function

	instance.swapPoster = function()
		episode = m.getSelectedEpisode()
		tmp = episode.sdPosterURL
		if episode.sdPosterURL_hq <> invalid and episode.sdPosterURL_hq <> ""
			episode.sdPosterURL = episode.sdPosterURL_hq
			episode.sdPosterURL_hq = tmp
		end if
		tmp = episode.hdPosterURL
		if episode.hdPosterURL_hq <> invalid and episode.hdPosterURL_hq <> ""
			episode.hdPosterURL = episode.hdPosterURL_hq
			episode.hdPosterURL_hq = tmp
		end if
	end function

	instance.setButtons = function()
		episode = m.getSelectedEpisode()
		m.screen.ClearButtons()
		if m.isPartiallyWatched() then
			m.screen.AddButton(1, "Resume Playing")
			m.screen.AddButton(2, "Play from Beginning")
		else
			m.screen.AddButton(2, "Play")
		end if
	end function

	instance.isPartiallyWatched = function()
		episode = m.getSelectedEpisode()
		return m.watchedStatusTracker.hasProgress(episode.title)
	end function

	instance.waitForInput = function()
		episode = m.getSelectedEpisode()
		while m.hasNonExitMessage()
			m.processMessage()
		end while
	end function

	instance.processMessage = function()
		msg = m.getLastMessage()
		if msg = invalid then
			return invalid
		end if

		if msg.isButtonPressed()
			m.processButtonPress()
		else if msg.isRemoteKeyPressed()
			m.processRemoteKeyPress()
		end if
	end function

	instance.getLastMessage = function()
		return m.lastMessage
	end function

	instance.processButtonPress = function()
		msg = m.getLastMessage()
		episode = m.getSelectedEpisode()
		if msg.GetIndex() = 1
			PlayStart = m.watchedStatusTracker.getProgress(episode.title)
			if PlayStart > 0 then
				episode.PlayStart = PlayStart
			end if
		else if msg.GetIndex() = 2
			m.watchedStatusTracker.removeProgress(episode.title)
			episode.PlayStart = 0
		end if
		ShowVideoScreen(episode)
		m.setButtons()
		m.screen.Show()
	end function

	instance.processRemoteKeyPress = function()
		msg = m.getLastMessage()
		if msg.GetIndex() = 4 ' LEFT
			m.movePreviousEpisode()
		else if msg.GetIndex() = 5 ' RIGHT
			m.moveNextEpisode()
		end if
		m.showEpisode()
	end function

	instance.hasNonExitMessage = function()
		m.lastMessage = wait(0, m.messagePort)
		return m.lastMessage = invalid or not m.lastMessage.isScreenClosed()
	end function

	instance.moveNextEpisode = function()
		if m.selectedEpisodeIndex = m.episodes.Count() - 1
			m.selectedEpisodeIndex = 0
		else
			m.selectedEpisodeIndex = m.selectedEpisodeIndex + 1
		end if

		return m.selectedEpisodeIndex
	end function

	instance.movePreviousEpisode = function()
		if m.selectedEpisodeIndex = 0
			m.selectedEpisodeIndex = m.episodes.Count() - 1
		else
			m.selectedEpisodeIndex = m.selectedEpisodeIndex - 1
		end if

		return m.selectedEpisodeIndex
	end function

	return instance
end function

subject: Convert SpringboardScreen to use WatchedStatusTracker.

repos: cdgeeko/jb-roku,cbojar/jb-roku


old_contents:
'**********************************************************
' Video Player Example Application - Video Playback
' November 2009
' Copyright (c) 2009 Roku Inc. All Rights Reserved.
'**********************************************************

'***********************************************************
' Create and show the video screen.  The video screen is
' a special full screen video playback component.  It
' handles most of the keypresses automatically and our
' job is primarily to make sure it has the correct data
' at startup. We will receive event back on progress and
' error conditions so it's important to monitor these to
' understand what's going on, especially in the case of errors
'***********************************************************
Function showVideoScreen(episode As Object)
	if type(episode) <> "roAssociativeArray" then
		print "invalid data passed to showVideoScreen"
		return -1
	endif

	port = CreateObject("roMessagePort")
	screen = CreateObject("roVideoScreen")
	screen.SetMessagePort(port)

	screen.SetContent( episode )
	screen.SetPositionNotificationPeriod( 3 ) 'Fire isPlaybackPosition every 3 seconds
	screen.Show()

	'Uncomment his line to dump the contents of the episode to be played
	'PrintAA(episode)

	while true
		msg = wait(0, port)
		if type(msg) = "roVideoScreenEvent" then
			print "showHomeScreen | msg = "; msg.getMessage() " | index = "; msg.GetIndex()
			if msg.isScreenClosed()
				print "Screen closed"
				exit while
			else if msg.isRequestFailed()
				print "Video request failure: "; msg.GetIndex(); " " msg.GetData()
			else if msg.isStatusMessage()
				print "Video status: "; msg.GetIndex(); " " msg.GetData()
			else if msg.isButtonPressed()
				print "Button pressed: "; msg.GetIndex(); " " msg.GetData()
			else if msg.isPlaybackPosition() then
		                nowpos = msg.GetIndex()
		                RegWrite( episode.title, nowpos.toStr() )
				print "Marked progress: "; msg.GetIndex()
			else if msg.isFullResult() then
		                RegDelete( episode.title )
			else
				print "Unexpected event type: "; msg.GetType()
			end if
		else
			print "Unexpected message class: "; type(msg)
		end if
	end while
End Function

new_contents:
'**********************************************************
' Video Player Example Application - Video Playback
' November 2009
' Copyright (c) 2009 Roku Inc. All Rights Reserved.
'**********************************************************

'***********************************************************
' Create and show the video screen.  The video screen is
' a special full screen video playback component.  It
' handles most of the keypresses automatically and our
' job is primarily to make sure it has the correct data
' at startup. We will receive event back on progress and
' error conditions so it's important to monitor these to
' understand what's going on, especially in the case of errors
'***********************************************************
Function showVideoScreen(episode As Object)
	if type(episode) <> "roAssociativeArray" then
		print "invalid data passed to showVideoScreen"
		return -1
	endif

	port = CreateObject("roMessagePort")
	screen = CreateObject("roVideoScreen")
	screen.SetMessagePort(port)

	screen.SetContent( episode )
	screen.SetPositionNotificationPeriod( 15 )
	screen.Show()

	while true
		msg = wait(0, port)
		if type(msg) = "roVideoScreenEvent" then
			if msg.isScreenClosed()
				print "Screen closed"
				exit while
			else if msg.isRequestFailed()
				print "Video request failure: "; msg.GetIndex(); " " msg.GetData()
			else if msg.isStatusMessage()
				print "Video status: "; msg.GetIndex(); " " msg.GetData()
			else if msg.isButtonPressed()
				print "Button pressed: "; msg.GetIndex(); " " msg.GetData()
			else if msg.isPlaybackPosition() then
				nowpos = msg.GetIndex()
				RegWrite( episode.title, nowpos.toStr() )
				print "Marked progress: "; msg.GetIndex()
			else if msg.isFullResult() then
				RegDelete( episode.title )
			else
				print "Unexpected event type: "; msg.GetType()
			end if
		else
			print "Unexpected message class: "; type(msg)
		end if
	end while
End Function

subject: Clean up VideoScreen a bit, and increase interval between progress save marker.


old_contents:
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

new_contents:
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

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


old_contents:
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

new_contents:
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

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


old_contents:
''
''	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

new_contents:
''
''	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

message:
Enable gzip compression encoding for HTTP requests.

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


new_contents:
function WatchedStatusTracker()
	instance = CreateObject("roAssociativeArray")

	instance.saveProgress = function(title as String, currentPosition as Integer)
		if(currentPosition < 30)
			return false
		end if

		RegWrite(title, currentPosition.toStr())
		return true
	end function

	instance.getProgress = function(title as String)
		savedProgress = RegRead(title)
		if savedProgress = invalid
			return 0
		end if

		return savedProgress.toInt()
	end function

	instance.hasProgress = function(title as String)
		savedProgress = RegRead(title)
		return savedProgress <> invalid
	end function

	instance.removeProgress = function(title as String)
		RegDelete(title)
	end function

	return instance
end function

subject: Create WatchedStatusTracker to encapsulate tracking all watch progress.