diff --git a/components/ItemGrid2/ItemGrid2.brs b/components/ItemGrid2/ItemGrid2.brs
index 2e653e25..b12418f3 100644
--- a/components/ItemGrid2/ItemGrid2.brs
+++ b/components/ItemGrid2/ItemGrid2.brs
@@ -1,6 +1,7 @@
sub init()
m.options = m.top.findNode("options")
+ m.tvGuide = invalid
m.itemGrid = m.top.findNode("itemGrid")
m.backdrop = m.top.findNode("backdrop")
@@ -78,6 +79,7 @@ end sub
sub SetUpOptions()
options = {}
+ options.filter = []
'Movies
if m.top.parentItem.collectionType = "movies" then
@@ -113,13 +115,18 @@ sub SetUpOptions()
options.filter = []
'Live TV
else if m.top.parentItem.collectionType = "livetv" then
- options.views = [{"Title": tr("Live TV"), "Name": "livetv" }]
+ options.views = [
+ {"Title": tr("Channels"), "Name": "livetv" },
+ {"Title": tr("TV Guide"), "Name": "tvGuide" }
+ ]
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" }
]
options.filter = []
else
- options.views = [{ "Title": tr("Default"), "Name": "default" }]
+ options.views = [
+ {"Title": tr("Default"), "Name": "default" }
+ ]
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" }
]
@@ -259,6 +266,20 @@ end sub
'
'Check if options updated and any reloading required
sub optionsClosed()
+
+ if (m.options.view = "tvGuide") then
+ if m.tvGuide = invalid then
+ m.tvGuide = createObject("roSGNode", "Schedule")
+ endif
+ m.tvGuide.observeField("watchChannel", "onChannelSelected")
+ m.top.appendChild(m.tvGuide)
+ m.tvGuide.lastFocus.setFocus(true)
+ return
+ else if m.tvGuide <> invalid then
+ ' Try to hide the TV Guide
+ m.top.removeChild(m.tvGuide)
+ end if
+
reload = false
if m.options.sortField <> m.sortField or m.options.sortAscending <> m.sortAscending then
m.sortField = m.options.sortField
@@ -280,6 +301,15 @@ sub optionsClosed()
end sub
+sub onChannelSelected(msg)
+ node = msg.getRoSGNode()
+ m.top.lastFocus = lastFocusedChild(node)
+ if node.watchChannel <> invalid then
+ ' Clone the node when it's reused/update in the TimeGrid it doesn't automatically start playing
+ m.top.selectedItem = node.watchChannel.clone(false)
+ end if
+end sub
+
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
@@ -287,9 +317,11 @@ function onKeyEvent(key as string, press as boolean) as boolean
if key = "options"
if m.options.visible = true then
m.options.visible = false
+ m.top.removeChild(m.options)
optionsClosed()
else
m.options.visible = true
+ m.top.appendChild(m.options)
m.options.setFocus(true)
end if
return true
diff --git a/components/ItemGrid2/ItemGrid2.xml b/components/ItemGrid2/ItemGrid2.xml
index bde386d4..88d9d45e 100644
--- a/components/ItemGrid2/ItemGrid2.xml
+++ b/components/ItemGrid2/ItemGrid2.xml
@@ -35,5 +35,6 @@
+
diff --git a/components/ItemGrid2/ItemGridOptions.brs b/components/ItemGrid2/ItemGridOptions.brs
index c66a60b2..30582fbf 100644
--- a/components/ItemGrid2/ItemGridOptions.brs
+++ b/components/ItemGrid2/ItemGridOptions.brs
@@ -39,7 +39,7 @@ sub optionsSet()
entry = viewContent.CreateChild("ContentNode")
entry.title = view.Title
m.viewNames.push(view.Name)
- if view.selected <> invalid and view.selected = true then
+ if (view.selected <> invalid and view.selected = true) or viewContent.Name = m.top.view then
selectedViewIndex = index
end if
index = index + 1
@@ -136,6 +136,12 @@ function onKeyEvent(key as string, press as boolean) as boolean
return true
else if key = "OK"
if(m.menus[m.selectedItem].isInFocusChain()) then
+ ' Handle View Screen
+ if(m.selectedItem = 0) then
+ m.selectedViewIndex = m.menus[0].itemSelected
+ m.top.view = m.viewNames[m.selectedViewIndex]
+ end if
+
' Handle Sort screen
if(m.selectedItem = 1) then
if m.menus[1].itemSelected <> m.selectedSortIndex then
diff --git a/components/data/ChannelData.brs b/components/data/ChannelData.brs
index 8ed93ee0..693d84c5 100644
--- a/components/data/ChannelData.brs
+++ b/components/data/ChannelData.brs
@@ -2,7 +2,7 @@ sub setFields()
json = m.top.json
m.top.id = json.id
- m.top.Title = json.name
+ m.top.title = json.name
m.top.live = true
m.top.Type = "TvChannel"
setPoster()
@@ -11,7 +11,7 @@ end sub
sub setPoster()
if m.top.image <> invalid
m.top.posterURL = m.top.image.url
- else if m.top.json.ImageTags.Primary <> invalid then
+ else if m.top.json.ImageTags <> invalid and m.top.json.ImageTags.Primary <> invalid then
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
end if
diff --git a/components/data/ChannelData.xml b/components/data/ChannelData.xml
index 0fdab666..6d7b7f90 100644
--- a/components/data/ChannelData.xml
+++ b/components/data/ChannelData.xml
@@ -2,7 +2,6 @@
-
diff --git a/components/data/ScheduleProgramData.brs b/components/data/ScheduleProgramData.brs
new file mode 100644
index 00000000..c11673e9
--- /dev/null
+++ b/components/data/ScheduleProgramData.brs
@@ -0,0 +1,66 @@
+sub setFields()
+ json = m.top.json
+
+ startDate = createObject("roDateTime")
+ endDate = createObject("roDateTime")
+ startDate.FromISO8601String(json.StartDate)
+ endDate.FromISO8601String(json.EndDate)
+
+ m.top.Title = json.Name
+ m.top.PlayStart = startDate.AsSeconds()
+ m.top.PlayDuration = endDate.AsSeconds() - m.top.PlayStart
+ m.top.Id = json.Id
+ m.top.Description = json.overview
+ m.top.EpisodeTitle = json.EpisodeTitle
+ m.top.isLive = json.isLive
+ m.top.isRepeat = json.isRepeat
+ m.top.startDate = json.startDate
+ m.top.endDate = json.endDate
+ m.top.channelId = json.channelId
+
+ if json.IsSeries <> invalid and json.IsSeries = true then
+ if json.IndexNumber <> invalid
+ m.top.episodeNumber = json.IndexNumber
+ end if
+
+ if json.ParentIndexNumber <> invalid
+ m.top.seasonNumber = json.ParentIndexNumber
+ end if
+ end if
+
+
+ ' m.top.id = json.id
+ ' m.top.Title = json.name
+ ' m.top.Description = json.overview
+ ' m.top.favorite = json.UserData.isFavorite
+ ' m.top.watched = json.UserData.played
+ ' m.top.Type = "Movie"
+
+ ' if json.ProductionYear <> invalid then
+ ' m.top.SubTitle = json.ProductionYear
+ ' end if
+
+ ' if json.OfficialRating <> invalid and json.OfficialRating <> "" then
+ ' m.top.Rating = json.OfficialRating
+ ' if m.top.SubTitle <> "" then
+ ' m.top.SubTitle = m.top.SubTitle + " - " + m.top.Rating
+ ' else
+ ' m.top.SubTitle = m.top.Rating
+ ' end if
+ ' end if
+
+ setPoster()
+ end sub
+
+ sub setPoster()
+ if m.top.image <> invalid
+ m.top.posterURL = m.top.image.url
+ else
+ if m.top.json.ImageTags <> invalid and m.top.json.ImageTags.Thumb <> invalid then
+ imgParams = { "maxHeight": 500, "maxWidth": 500, "Tag" : m.top.json.ImageTags.Thumb }
+ m.top.posterURL = ImageURL(m.top.json.id, "Thumb", imgParams)
+ ' imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag" : m.top.json.ImageTags.Primary }
+ ' m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
+ end if
+ end if
+ end sub
diff --git a/components/data/ScheduleProgramData.xml b/components/data/ScheduleProgramData.xml
new file mode 100644
index 00000000..87c067b6
--- /dev/null
+++ b/components/data/ScheduleProgramData.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/liveTv/LoadChannelsTask.brs b/components/liveTv/LoadChannelsTask.brs
new file mode 100644
index 00000000..24533c52
--- /dev/null
+++ b/components/liveTv/LoadChannelsTask.brs
@@ -0,0 +1,34 @@
+sub init()
+ m.top.functionName = "loadChannels"
+ end sub
+
+ sub loadChannels()
+
+ results = []
+
+ params = {
+ UserId: get_setting("active_user")
+ limit: m.top.limit,
+ StartIndex: m.top.startIndex
+ }
+
+ url = "LiveTv/Channels"
+
+ resp = APIRequest(url, params)
+ data = getJson(resp)
+
+ if data.TotalRecordCount = invalid then
+ m.top.channels = results
+ return
+ end if
+
+
+ for each item in data.Items
+ channel = createObject("roSGNode", "ChannelData")
+ channel.json = item
+ results.push(channel)
+ end for
+
+ m.top.channels = results
+
+ end sub
\ No newline at end of file
diff --git a/components/liveTv/LoadChannelsTask.xml b/components/liveTv/LoadChannelsTask.xml
new file mode 100644
index 00000000..c349b8a9
--- /dev/null
+++ b/components/liveTv/LoadChannelsTask.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/liveTv/LoadProgramDetailsTask.brs b/components/liveTv/LoadProgramDetailsTask.brs
new file mode 100644
index 00000000..63a79a55
--- /dev/null
+++ b/components/liveTv/LoadProgramDetailsTask.brs
@@ -0,0 +1,32 @@
+sub init()
+ m.top.functionName = "loadProgramDetails"
+
+end sub
+
+sub loadProgramDetails()
+
+ channelIndex = m.top.ChannelIndex
+ programIndex = m.top.ProgramIndex
+
+ params = {
+ UserId: get_setting("active_user"),
+ }
+
+ url = Substitute("LiveTv/Programs/{0}", m.top.programId)
+
+ resp = APIRequest(url, params)
+ data = getJson(resp)
+
+ if data = invalid then
+ m.top.programDetails = {}
+ return
+ end if
+
+ program = createObject("roSGNode", "ScheduleProgramData")
+ program.json = data
+ program.channelIndex = ChannelIndex
+ program.programIndex = ProgramIndex
+ program.fullyLoaded = true
+ m.top.programDetails = program
+
+end sub
\ No newline at end of file
diff --git a/components/liveTv/LoadProgramDetailsTask.xml b/components/liveTv/LoadProgramDetailsTask.xml
new file mode 100644
index 00000000..47bc88c4
--- /dev/null
+++ b/components/liveTv/LoadProgramDetailsTask.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/liveTv/LoadSheduleTask.brs b/components/liveTv/LoadSheduleTask.brs
new file mode 100644
index 00000000..351504ea
--- /dev/null
+++ b/components/liveTv/LoadSheduleTask.brs
@@ -0,0 +1,41 @@
+sub init()
+ m.top.functionName = "loadSchedule"
+ end sub
+
+ sub loadSchedule()
+
+ results = []
+
+ params = {
+ UserId: get_setting("active_user"),
+ SortBy: "startDate",
+ EnableImages: false
+ EnableTotalRecordCount: false,
+ EnableUserData: false
+ channelIds: m.top.channelIds
+ MaxStartDate: m.top.endTime,
+ MinEndDate: m.top.startTime
+ }
+
+ url = "LiveTv/Programs"
+
+ resp = APIRequest(url, params)
+ data = getJson(resp)
+
+ if data = invalid then
+ m.top.schedule = results
+ return
+ end if
+
+ results = []
+
+ for each item in data.Items
+ program = createObject("roSGNode", "ScheduleProgramData")
+ program.json = item
+ results.push(program)
+ end for
+
+
+ m.top.schedule = results
+
+ end sub
\ No newline at end of file
diff --git a/components/liveTv/LoadSheduleTask.xml b/components/liveTv/LoadSheduleTask.xml
new file mode 100644
index 00000000..29d287c9
--- /dev/null
+++ b/components/liveTv/LoadSheduleTask.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/liveTv/ProgramDetails.brs b/components/liveTv/ProgramDetails.brs
new file mode 100644
index 00000000..be1aae8f
--- /dev/null
+++ b/components/liveTv/ProgramDetails.brs
@@ -0,0 +1,217 @@
+sub init()
+
+ ' Max "Overview" lines to show in Preview and Detail
+ m.maxPreviewLines = 5
+ m.maxDetailLines = 14
+
+ m.detailsView = m.top.findNode("detailsView")
+
+ m.programName = m.top.findNode("programName")
+ m.episodeTitle = m.top.findNode("episodeTitle")
+ m.episodeNumber = m.top.findNode("episodeNumber")
+ m.overview = m.top.findNode("overview")
+
+ m.episodeDetailsGroup = m.top.findNode("episodeDetailsGroup")
+ m.isLiveGroup = m.top.findNode("isLive")
+ m.isRepeatGroup = m.top.findNode("isRepeat")
+
+ m.broadcastDetails = m.top.findNode("broadcastDetails")
+ m.duration = m.top.findNode("duration")
+ m.channelName = m.top.findNode("channelName")
+ m.image = m.top.findNode("image")
+
+ m.focusAnimationOpacity = m.top.findNode("focusAnimationOpacity")
+ m.focusAnimation = m.top.findNode("focusAnimation")
+
+ m.viewChannelButton = m.top.findNode("viewChannelButton")
+
+ m.focusAnimation.observeField("state", "onAnimationComplete")
+
+ setupLabels()
+end sub
+
+
+' Set up Live and Repeat label sizes
+sub setupLabels()
+
+ boundingRect = m.top.findNode("isLiveText").boundingRect()
+ isLiveBackground = m.top.findNode("isLiveBackground")
+ isLiveBackground.width = boundingRect.width + 16
+ isLiveBackground.height = boundingRect.height + 8
+ m.episodeDetailsGroup.removeChildIndex(0)
+
+ boundingRect = m.top.findNode("isRepeatText").boundingRect()
+ isRepeatBackground = m.top.findNode("isRepeatBackground")
+ isRepeatBackground.width = boundingRect.width + 16
+ isRepeatBackground.height = boundingRect.height + 8
+ m.episodeDetailsGroup.removeChildIndex(0)
+
+ boundingRect = m.viewChannelButton.boundingRect()
+ buttonBackground = m.top.findNode("viewChannelButtonBackground")
+ buttonBackground.width = boundingRect.width + 20
+ buttonBackground.height = boundingRect.height + 20
+end sub
+
+sub programUpdated()
+
+ m.top.watchSelectedChannel = false
+ m.overview.maxLines = m.maxDetailLines
+ prog = m.top.programDetails
+
+ ' If no program selected, hide details view
+ if prog = invalid then
+ m.detailsView.visible = "false"
+ return
+ end if
+
+ m.programName.text = prog.Title
+ m.overview.text = prog.description
+
+ m.episodeDetailsGroup.removeChildrenIndex(m.episodeDetailsGroup.getChildCount(), 0)
+
+ if prog.isLive then
+ m.episodeDetailsGroup.appendChild(m.isLiveGroup)
+ else if prog.isRepeat then
+ m.episodeDetailsGroup.appendChild(m.isRepeatGroup)
+ end if
+
+ ' Episode Number
+ if prog.seasonNumber > 0 and prog.episodeNumber > 0 then
+ m.episodeNumber.text = "S" + StrI(prog.seasonNumber).trim() + ":E" + StrI(prog.episodeNumber).trim()
+ if prog.episodeTitle <> "" then m.episodeNumber.text = m.episodeNumber.text + " -" ' Add a Dash if showing Episode Number and Title
+ m.episodeDetailsGroup.appendChild(m.episodeNumber)
+ end if
+
+ if prog.episodeTitle <> invalid and prog.episodeTitle <> "" then
+ m.episodeTitle.text = prog.episodeTitle
+ m.episodeTitle.visible = true
+ m.episodeDetailsGroup.appendChild(m.episodeTitle)
+ end if
+
+ m.duration.text = getDurationStringFromSeconds(prog.PlayDuration)
+
+ ' Calculate Broadcast Details
+ now = createObject("roDateTime")
+ startDate = createObject("roDateTime")
+ endDate = createObject("roDateTime")
+ startDate.FromISO8601String(prog.StartDate)
+ endDate.FromISO8601String(prog.EndDate)
+
+ day = getRelativeDayName(startDate)
+
+ if startDate.AsSeconds() < now.AsSeconds() and endDate.AsSeconds() > now.AsSeconds() then
+ if day = "today" then
+ m.broadcastDetails.text = tr("Started at") + " " + formatTime(startDate)
+ else
+ m.broadcastDetails.text = tr("Started") + " " + tr(day) + ", " + formatTime(startDate)
+ end if
+ else if startDate.AsSeconds() > now.AsSeconds()
+ if day = "today" then
+ m.broadcastDetails.text = tr("Starts at") + " " + formatTime(startDate)
+ else
+ m.broadcastDetails.text = tr("Starts") + " " + tr(day) + ", " + formatTime(startDate)
+ end if
+ else
+ if day = "today" then
+ m.broadcastDetails.text = tr("Ended at") + " " + formatTime(endDate)
+ else
+ m.broadcastDetails.text = tr("Ended") + " " + tr(day) + ", " + formatTime(endDate)
+ end if
+ end if
+
+ m.image.uri = prog.PosterURL
+
+ if prog.channelName <> invalid and prog.channelName <> "" then
+ m.channelName.text = prog.channelName
+ end if
+
+ m.detailsView.visible = "true"
+
+ m.top.height = m.detailsView.boundingRect().height
+ m.overview.maxLines = m.maxPreviewLines
+end sub
+
+'
+' Get relative date name for a date (yesterday, today, tomorrow, or otherwise weekday name )
+function getRelativeDayName(date) as string
+
+ now = createObject("roDateTime")
+
+ ' Check for Today
+ if now.AsDateString("short-date-dashes") = date.AsDateString("short-date-dashes") then
+ return "today"
+ end if
+
+ ' Check for Yesterday
+ todayMidnight = now.AsSeconds() - (now.AsSeconds() MOD 86400)
+ dateMidnight = date.AsSeconds() - (date.AsSeconds() MOD 86400)
+
+ if todayMidnight - dateMidnight = 86400 then
+ return "yesterday"
+ end if
+
+ if dateMidnight - todayMidnight = 86400 then
+ return "tomorrow"
+ end if
+
+ return date.GetWeekday()
+
+end function
+
+'
+' Get program duratio string (e.g. 1h 20m)
+function getDurationStringFromSeconds(seconds) as string
+
+ hours = 0
+ minutes = seconds / 60.0
+
+ if minutes > 60 then
+ hours = (minutes - (minutes MOD 60)) / 60
+ minutes = minutes MOD 60
+ end if
+
+ if hours > 0 then
+ return "%1h %2m".Replace("%1", StrI(hours).trim()).Replace("%2", StrI(minutes).trim())
+ else
+ return "%1m".Replace("%1", StrI(minutes).trim())
+ end if
+
+end function
+
+'
+' Show view channel button when item has Focus
+sub focusChanged()
+ if m.top.hasFocus = true then
+ m.overview.maxLines = m.maxDetailLines
+ m.focusAnimationOpacity.keyValue = [0, 1]
+ else
+ m.top.watchSelectedChannel = false
+ m.focusAnimationOpacity.keyValue = [1, 0]
+ end if
+
+ m.focusAnimation.control = "start"
+
+end sub
+
+sub onAnimationComplete()
+ if m.focusAnimation.state = "stopped" and m.top.hasFocus = false then
+ m.overview.maxLines = m.maxPreviewLines
+ end if
+end sub
+
+function onKeyEvent(key as string, press as boolean) as boolean
+ if not press then return false
+
+ if key = "OK" then
+ m.top.watchSelectedChannel = true
+ return true
+ end if
+
+ if key = "left" or key = "right" or key = "up" or key = "down" then
+ return true
+ end if
+
+ return false
+end function
+
+
diff --git a/components/liveTv/ProgramDetails.xml b/components/liveTv/ProgramDetails.xml
new file mode 100644
index 00000000..54b198e9
--- /dev/null
+++ b/components/liveTv/ProgramDetails.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/liveTv/schedule.brs b/components/liveTv/schedule.brs
new file mode 100644
index 00000000..10431a02
--- /dev/null
+++ b/components/liveTv/schedule.brs
@@ -0,0 +1,181 @@
+sub init()
+
+ m.scheduleGrid = m.top.findNode("scheduleGrid")
+ m.detailsPane = m.top.findNode("detailsPane")
+
+ m.detailsPane.observeField("watchSelectedChannel", "onWatchChannelSelected")
+
+ m.gridStartDate = CreateObject("roDateTime")
+ m.scheduleGrid.contentStartTime = m.gridStartDate.AsSeconds() - 1800
+ m.gridEndDate = createObject("roDateTime")
+ m.gridEndDate.FromSeconds(m.gridStartDate.AsSeconds() + (24 * 60 * 60))
+
+ m.scheduleGrid.observeField("programFocused", "onProgramFocused")
+ m.scheduleGrid.observeField("programSelected", "onProgramSelected")
+ m.scheduleGrid.observeField("leftEdgeTargetTime", "onGridScrolled")
+ m.scheduleGrid.channelInfoWidth = 350
+
+ m.gridMoveAnimation = m.top.findNode("gridMoveAnimation")
+ m.gridMoveAnimationPosition = m.top.findNode("gridMoveAnimationPosition")
+
+ m.LoadChannelsTask = createObject("roSGNode", "LoadChannelsTask")
+ m.LoadChannelsTask.observeField("channels", "onChannelsLoaded")
+ m.LoadChannelsTask.control = "RUN"
+
+ m.top.lastFocus = m.scheduleGrid
+
+ m.channelIndex = {}
+end sub
+
+' Initial list of channels loaded
+sub onChannelsLoaded()
+ gridData = createObject("roSGNode", "ContentNode")
+
+ counter = 0
+ channelIdList = ""
+
+ for each item in m.LoadChannelsTask.channels
+ gridData.appendChild(item)
+ m.channelIndex[item.Id] = counter
+ counter = counter + 1
+ channelIdList = channelIdList + item.Id + ","
+ end for
+
+ m.scheduleGrid.content = gridData
+
+ m.LoadScheduleTask = createObject("roSGNode", "LoadScheduleTask")
+ m.LoadScheduleTask.observeField("schedule", "onScheduleLoaded")
+
+ m.LoadScheduleTask.startTime = m.gridStartDate.ToISOString()
+ m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
+ m.LoadScheduleTask.channelIds = channelIdList
+ m.LoadScheduleTask.control = "RUN"
+
+
+ m.LoadProgramDetailsTask = createObject("roSGNode", "LoadProgramDetailsTask")
+ m.LoadProgramDetailsTask.observeField("programDetails", "onProgramDetailsLoaded")
+
+ m.scheduleGrid.setFocus(true)
+end sub
+
+' When LoadScheduleTask completes (initial or more data) and we have a schedule to display
+sub onScheduleLoaded()
+
+ for each item in m.LoadScheduleTask.schedule
+
+ channel = m.scheduleGrid.content.GetChild(m.channelIndex[item.ChannelId])
+
+ if channel.PosterUrl <> "" then
+ item.channelLogoUri = channel.PosterUrl
+ end if
+ if channel.Title <> "" then
+ item.channelName = channel.Title
+ end if
+
+ channel.appendChild(item)
+ end for
+end sub
+
+sub onProgramFocused()
+
+ m.top.watchChannel = invalid
+ channel = m.scheduleGrid.content.GetChild(m.scheduleGrid.programFocusedDetails.focusChannelIndex)
+
+ ' Exit if Channels not yet loaded
+ if channel.getChildCount() = 0 then
+ m.detailsPane.programDetails = invalid
+ return
+ end if
+
+ prog = channel.GetChild(m.scheduleGrid.programFocusedDetails.focusIndex)
+
+ if prog.fullyLoaded = false then
+ m.LoadProgramDetailsTask.programId = prog.Id
+ m.LoadProgramDetailsTask.channelIndex = m.scheduleGrid.programFocusedDetails.focusChannelIndex
+ m.LoadProgramDetailsTask.programIndex = m.scheduleGrid.programFocusedDetails.focusIndex
+ m.LoadProgramDetailsTask.control = "RUN"
+ end if
+
+ m.detailsPane.programDetails = prog
+
+end sub
+
+' Update the Program Details with full information
+sub onProgramDetailsLoaded()
+ if m.LoadProgramDetailsTask.programDetails = invalid then return
+ channel = m.scheduleGrid.content.GetChild(m.LoadProgramDetailsTask.programDetails.channelIndex)
+ channel.ReplaceChild(m.LoadProgramDetailsTask.programDetails, m.LoadProgramDetailsTask.programDetails.programIndex)
+end sub
+
+
+sub onProgramSelected()
+ ' If there is no program data - view the channel
+ if m.detailsPane.programDetails = invalid then
+ m.top.watchChannel = m.scheduleGrid.content.GetChild(m.scheduleGrid.programFocusedDetails.focusChannelIndex)
+ return
+ end if
+
+ ' Move Grid Down
+ focusProgramDetails(true)
+end sub
+
+' Move the TV Guide Grid down or up depending whether details are selected
+sub focusProgramDetails(setFocused)
+
+ h = m.detailsPane.height
+ if h < 400 then h = 400
+ h = h + 160 + 80
+
+ if setFocused = true then
+ m.gridMoveAnimationPosition.keyValue = [ [0,600], [0, h] ]
+ m.detailsPane.setFocus(true)
+ m.detailsPane.hasFocus = true
+ m.top.lastFocus = m.detailsPane
+ else
+ m.detailsPane.hasFocus = false
+ m.gridMoveAnimationPosition.keyValue = [ [0, h], [0,600] ]
+ m.scheduleGrid.setFocus(true)
+ m.top.lastFocus = m.scheduleGrid
+ end if
+
+ m.gridMoveAnimation.control = "start"
+end sub
+
+' Handle user selecting "Watch Channel" from Program Details
+sub onWatchChannelSelected()
+
+ if m.detailsPane.watchSelectedChannel = false then return
+
+ ' Set focus back to grid before showing channel, to ensure grid has focus when we return
+ focusProgramDetails(false)
+
+ m.top.watchChannel = m.scheduleGrid.content.GetChild(m.LoadProgramDetailsTask.programDetails.channelIndex)
+
+end sub
+
+' As user scrolls grid, check if more data requries to be loaded
+sub onGridScrolled()
+
+ ' If we're within 12 hours of end of grid, load next 24hrs of data
+ if m.scheduleGrid.leftEdgeTargetTime + (12 * 60 * 60) > m.gridEndDate.AsSeconds() then
+
+ ' Ensure the task is not already (still) running,
+ if m.LoadScheduleTask.state <> "run" then
+ m.LoadScheduleTask.startTime = m.gridEndDate.ToISOString()
+ m.gridEndDate.FromSeconds(m.gridEndDate.AsSeconds() + (24 * 60 * 60))
+ m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
+ m.LoadScheduleTask.control = "RUN"
+ end if
+ end if
+end sub
+
+function onKeyEvent(key as string, press as boolean) as boolean
+ if not press then return false
+
+ if key = "back" and m.detailsPane.isInFocusChain() then
+ focusProgramDetails(false)
+ return true
+ end if
+
+ return false
+end function
diff --git a/components/liveTv/schedule.xml b/components/liveTv/schedule.xml
new file mode 100644
index 00000000..362b23c6
--- /dev/null
+++ b/components/liveTv/schedule.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/backgroundmask.png b/images/backgroundmask.png
new file mode 100644
index 00000000..557b579c
Binary files /dev/null and b/images/backgroundmask.png differ
diff --git a/images/white.9.png b/images/white.9.png
new file mode 100644
index 00000000..31dc0139
Binary files /dev/null and b/images/white.9.png differ
diff --git a/locale/default/translations.ts b/locale/default/translations.ts
index 74dccd1e..b01fa641 100644
--- a/locale/default/translations.ts
+++ b/locale/default/translations.ts
@@ -200,5 +200,110 @@
Unable to load Channel Data from the server
+
+
+ today
+ Current day
+
+
+
+ yesterday
+ Previous day
+
+
+
+ tomorrow
+ Next day
+
+
+
+ tomorrow
+ Next day
+
+
+
+ Sunday
+ Day of Week
+
+
+
+ Monday
+ Day of Week
+
+
+
+ Tuesday
+ Day of Week
+
+
+
+ Wednesday
+ Day of Week
+
+
+
+ Thursday
+ Day of Week
+
+
+
+ Friday
+ Day of Week
+
+
+
+ Saturday
+ Day of Week
+
+
+
+ Started at
+ (Past Tense) For defining time when a program started today (e.g. Started at 08:00)
+
+
+
+ Started
+ (Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00)
+
+
+
+ Starts at
+ (Future Tense) For defining time when a program will start today (e.g. Starts at 08:00)
+
+
+
+ Starts
+ (Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00)
+
+
+
+ Ended at
+ (Past Tense) For defining time when a program will ended (e.g. Ended at 08:00)
+
+
+
+ Ends at
+ (Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00)
+
+
+
+ Live
+ If TV Show is being broadcast live (not pre-recorded)
+
+
+
+ Repeat
+ If TV Shows has previoulsy been broadcast
+
+
+
+ Channels
+ Menu option for showing Live TV Channel List
+
+
+
+ TV Guide
+ Menu option for showing Live TV Guide / Schedule
+
diff --git a/locale/en_GB/translations.ts b/locale/en_GB/translations.ts
index 8bc56243..42137fcf 100644
--- a/locale/en_GB/translations.ts
+++ b/locale/en_GB/translations.ts
@@ -314,6 +314,111 @@
Filter
+
+
+ today
+ Current day
+
+
+
+ yesterday
+ Previous day
+
+
+
+ tomorrow
+ Next day
+
+
+
+ tomorrow
+ Next day
+
+
+
+ Sunday
+ Day of Week
+
+
+
+ Monday
+ Day of Week
+
+
+
+ Tuesday
+ Day of Week
+
+
+
+ Wednesday
+ Day of Week
+
+
+
+ Thursday
+ Day of Week
+
+
+
+ Friday
+ Day of Week
+
+
+
+ Saturday
+ Day of Week
+
+
+
+ Started at
+ (Past Tense) For defining time when a program started today (e.g. Started at 08:00)
+
+
+
+ Started
+ (Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00)
+
+
+
+ Starts at
+ (Future Tense) For defining time when a program will start today (e.g. Starts at 08:00)
+
+
+
+ Starts
+ (Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00)
+
+
+
+ Ended at
+ (Past Tense) For defining time when a program will ended (e.g. Ended at 08:00)
+
+
+
+ Ends at
+ (Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00)
+
+
+
+ Live
+ If TV Show is being broadcast live (not pre-recorded)
+
+
+
+ Repeat
+ If TV Shows has previoulsy been broadcast
+
+
+
+ Channels
+ Menu option for showing Live TV Channel List
+
+
+
+ TV Guide
+ Menu option for showing Live TV Guide / Schedule
+
diff --git a/locale/en_US/translations.ts b/locale/en_US/translations.ts
index 25ddea96..84970acc 100644
--- a/locale/en_US/translations.ts
+++ b/locale/en_US/translations.ts
@@ -225,7 +225,7 @@
Error During PlaybackDialog title when error occurs during playback
-
+
There was an error retrieving the data for this item from the server.
@@ -237,7 +237,7 @@
An error was encountered while playing this item.Dialog detail when error occurs during playback
-
+
Loading Channel Data
@@ -314,6 +314,111 @@
Filter
+
+
+ today
+ Current day
+
+
+
+ yesterday
+ Previous day
+
+
+
+ tomorrow
+ Next day
+
+
+
+ tomorrow
+ Next day
+
+
+
+ Sunday
+ Day of Week
+
+
+
+ Monday
+ Day of Week
+
+
+
+ Tuesday
+ Day of Week
+
+
+
+ Wednesday
+ Day of Week
+
+
+
+ Thursday
+ Day of Week
+
+
+
+ Friday
+ Day of Week
+
+
+
+ Saturday
+ Day of Week
+
+
+
+ Started at
+ (Past Tense) For defining time when a program started today (e.g. Started at 08:00)
+
+
+
+ Started
+ (Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00)
+
+
+
+ Starts at
+ (Future Tense) For defining time when a program will start today (e.g. Starts at 08:00)
+
+
+
+ Starts
+ (Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00)
+
+
+
+ Ended at
+ (Past Tense) For defining time when a program will ended (e.g. Ended at 08:00)
+
+
+
+ Ends at
+ (Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00)
+
+
+
+ Live
+ If TV Show is being broadcast live (not pre-recorded)
+
+
+
+ Repeat
+ If TV Shows has previoulsy been broadcast
+
+
+
+ Channels
+ Menu option for showing Live TV Channel List
+
+
+
+ TV Guide
+ Menu option for showing Live TV Guide / Schedule
+
diff --git a/source/Main.brs b/source/Main.brs
index 66c71b83..6938442c 100644
--- a/source/Main.brs
+++ b/source/Main.brs
@@ -82,7 +82,7 @@ sub Main()
' If you select a library from ANYWHERE, follow this flow
selectedItem = msg.getData()
if (selectedItem.type = "CollectionFolder" OR selectedItem.type = "UserView" OR selectedItem.type = "Folder") AND ( selectedItem.collectionType = "movies" or selectedItem.collectionType = "CollectionFolder")
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
m.overhang.title = selectedItem.title
@@ -90,7 +90,7 @@ sub Main()
group.overhangTitle = selectedItem.title
m.scene.appendChild(group)
else if (selectedItem.type = "CollectionFolder" OR selectedItem.type = "UserView") AND selectedItem.collectionType = "tvshows"
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@@ -99,7 +99,7 @@ sub Main()
group.overhangTitle = selectedItem.title
m.scene.appendChild(group)
else if (selectedItem.type = "CollectionFolder" OR selectedItem.type = "UserView") AND selectedItem.collectionType = "boxsets" OR selectedItem.type = "Boxset"
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@@ -108,7 +108,7 @@ sub Main()
group.overhangTitle = selectedItem.title
m.scene.appendChild(group)
else if ((selectedItem.type = "CollectionFolder" OR selectedItem.type = "UserView") AND selectedItem.collectionType = "livetv") OR selectedItem.type = "Channel"
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@@ -116,9 +116,9 @@ sub Main()
group = CreateChannelList(selectedItem)
group.overhangTitle = selectedItem.title
m.scene.appendChild(group)
- else if selectedItem.type = "Boxset" then
+ else if selectedItem.type = "Boxset" or selectedItem.collectionType = "folders" then
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@@ -141,7 +141,7 @@ sub Main()
video_id = selectedItem.id
video = CreateVideoPlayerGroup(video_id)
if video <> invalid then
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
group = video
@@ -152,7 +152,7 @@ sub Main()
m.overhang.visible = false
end if
else if selectedItem.type = "Series" then
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@@ -164,7 +164,7 @@ sub Main()
m.scene.appendChild(group)
else if selectedItem.type = "Movie" then
' open movie detail page
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@@ -188,7 +188,7 @@ sub Main()
dialog.close = true
if video <> invalid then
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
group = video
@@ -217,7 +217,7 @@ sub Main()
' If you select a movie from ANYWHERE, follow this flow
node = getMsgPicker(msg, "picker")
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@@ -231,7 +231,7 @@ sub Main()
' If you select a TV Series from ANYWHERE, follow this flow
node = getMsgPicker(msg, "picker")
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@@ -248,7 +248,7 @@ sub Main()
series = msg.getRoSGNode()
node = series.seasonData.items[ptr[1]]
- group.lastFocus = group.focusedChild.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild.focusedChild
group.setFocus(false)
group.visible = false
@@ -263,7 +263,7 @@ sub Main()
video_id = node.id
video = CreateVideoPlayerGroup(video_id)
if video <> invalid then
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
group = video
@@ -298,7 +298,7 @@ sub Main()
else if isNodeEvent(msg, "itemSelected")
' Search item selected
node = getMsgPicker(msg)
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@@ -327,7 +327,7 @@ sub Main()
video_id = group.id
video = CreateVideoPlayerGroup(video_id, audio_stream_idx)
if video <> invalid then
- group.lastFocus = group.focusedChild.focusedChild.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild.focusedChild.focusedChild
group.setFocus(false)
group.visible = false
group = video
@@ -364,7 +364,7 @@ sub Main()
else
group.setFocus(true)
end if
- group.lastFocus = group.focusedChild
+ if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
m.overhang.showOptions = false
@@ -437,7 +437,7 @@ sub Main()
print msg.GetInfo()
end if
else
- print type(msg)
+ print "Unhandled " type(msg)
print msg
end if
end while
diff --git a/source/VideoPlayer.brs b/source/VideoPlayer.brs
index f7632830..6a031c4d 100644
--- a/source/VideoPlayer.brs
+++ b/source/VideoPlayer.brs
@@ -261,7 +261,6 @@ end function
function getContainerType(meta as object) as string
' Determine the file type of the video file source
- print type(meta)
if meta.json.mediaSources = invalid then return ""
container = meta.json.mediaSources[0].container
diff --git a/source/utils/misc.brs b/source/utils/misc.brs
index c033d3b4..26b1a9cc 100644
--- a/source/utils/misc.brs
+++ b/source/utils/misc.brs
@@ -94,7 +94,7 @@ function lastFocusedChild(obj as object) as object
child = obj
for i = 0 to obj.getChildCount()
if obj.focusedChild <> invalid then
- child = child.focusedChild
+ child = obj.focusedChild
end if
end for
return child
@@ -102,7 +102,7 @@ end function
function show_dialog(message as string, options = [], defaultSelection = 0) as integer
group = m.scene.focusedChild
- lastFocus = lastFocusedChild(m.scene)
+ if group.lastFocus = invalid then lastFocus = lastFocusedChild(m.scene)
'We want to handle backPressed instead of the main loop
m.scene.unobserveField("backPressed")