commit
6bab4b719e
|
@ -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")
|
||||
|
@ -59,6 +60,11 @@ sub loadInitialItems()
|
|||
|
||||
'For LiveTV, we want to "Fit" the item images, not zoom
|
||||
m.top.imageDisplayMode = "scaleToFit"
|
||||
|
||||
if get_user_setting("display.livetv.landing") = "guide" then
|
||||
showTvGuid()
|
||||
end if
|
||||
|
||||
else if m.top.parentItem.collectionType = "CollectionFolder" then
|
||||
' Non-recursive, to not show subfolder contents
|
||||
m.loadItemsTask.recursive = false
|
||||
|
@ -78,6 +84,7 @@ end sub
|
|||
sub SetUpOptions()
|
||||
|
||||
options = {}
|
||||
options.filter = []
|
||||
|
||||
'Movies
|
||||
if m.top.parentItem.collectionType = "movies" then
|
||||
|
@ -113,13 +120,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", "Selected": get_user_setting("display.livetv.landing") = "guide" }
|
||||
]
|
||||
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 +271,15 @@ end sub
|
|||
'
|
||||
'Check if options updated and any reloading required
|
||||
sub optionsClosed()
|
||||
|
||||
if (m.options.view = "tvGuide") then
|
||||
showTVGuid()
|
||||
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
|
||||
|
@ -279,6 +300,24 @@ sub optionsClosed()
|
|||
m.itemGrid.setFocus(true)
|
||||
end sub
|
||||
|
||||
sub showTVGuid()
|
||||
m.top.signalBeacon("EPGLaunchInitiate") ' Required Roku Performance monitoring
|
||||
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)
|
||||
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
|
||||
|
||||
|
@ -287,9 +326,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
|
||||
|
|
|
@ -35,5 +35,7 @@
|
|||
<field id="selectedItem" type="node" alwaysNotify="true" />
|
||||
<field id="imageDisplayMode" type="string" value="scaleToZoom" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||
<script type="text/brightscript" uri="ItemGrid2.brs" />
|
||||
</component>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
<component name="ChannelData" extends="JFContentItem">
|
||||
<interface>
|
||||
<field id="channelID" type="string" />
|
||||
<field id="Title" type="string" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="ChannelData.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
|
||||
|
|
|
@ -22,7 +22,7 @@ sub setData()
|
|||
|
||||
' Add Icon URLs for display if there is no Poster
|
||||
if datum.CollectionType = "livetv" then
|
||||
m.top.iconUrl = "pkg:/images/baseline_live_tv_white_48dp.png"
|
||||
m.top.iconUrl = "pkg:/images/media_type_icons/live_tv_white.png"
|
||||
end if
|
||||
|
||||
else if datum.type = "Episode" then
|
||||
|
@ -142,7 +142,7 @@ sub setData()
|
|||
params = { "Tag" : datum.ImageTags.Primary, "maxHeight" : 261, "maxWidth" : 464 }
|
||||
m.top.thumbnailURL = ImageURL(datum.id, "Primary", params)
|
||||
m.top.widePosterUrl = m.top.thumbnailURL
|
||||
m.top.iconUrl = "pkg:/images/baseline_live_tv_white_48dp.png"
|
||||
m.top.iconUrl = "pkg:/images/media_type_icons/live_tv_white.png"
|
||||
end if
|
||||
|
||||
end sub
|
43
components/data/ScheduleProgramData.brs
Normal file
43
components/data/ScheduleProgramData.brs
Normal file
|
@ -0,0 +1,43 @@
|
|||
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
|
||||
|
||||
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)
|
||||
end if
|
||||
end if
|
||||
end sub
|
22
components/data/ScheduleProgramData.xml
Normal file
22
components/data/ScheduleProgramData.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="ScheduleProgramData" extends="JFContentItem">
|
||||
<interface>
|
||||
<field id="fullyLoaded" type="boolean" value="false" />
|
||||
<field id="channelIndex" type="integer" />
|
||||
<field id="programIndex" type="integer" />
|
||||
<field id="episodeTitle" type="string" />
|
||||
<field id="isLive" type="boolean" value="false" />
|
||||
<field id="isRepeat" type="boolean" value="false" />
|
||||
<field id="startDate" type="string" />
|
||||
<field id="endDate" type="string" />
|
||||
<field id="seasonNumber" type="integer" value="-1" />
|
||||
<field id="episodeNumber" type="integer" value="-1" />
|
||||
<field id="channelId" type="string" />
|
||||
<field id="channelLogoUri" type="string" />
|
||||
<field id="channelName" type="string" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="ScheduleProgramData.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||
</component>
|
34
components/liveTv/LoadChannelsTask.brs
Normal file
34
components/liveTv/LoadChannelsTask.brs
Normal file
|
@ -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
|
15
components/liveTv/LoadChannelsTask.xml
Normal file
15
components/liveTv/LoadChannelsTask.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
|
||||
<component name="LoadChannelsTask" extends="Task">
|
||||
<interface>
|
||||
<field id="limit" type="integer" value="500" />
|
||||
<field id="startIndex" type="integer" value="0" />
|
||||
|
||||
<!-- Total records available from server-->
|
||||
<field id="channels" type="array" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="LoadChannelsTask.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||
<!-- <script type="text/brightscript" uri="pkg:/source/api/Image.brs" /> -->
|
||||
</component>
|
32
components/liveTv/LoadProgramDetailsTask.brs
Normal file
32
components/liveTv/LoadProgramDetailsTask.brs
Normal file
|
@ -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
|
13
components/liveTv/LoadProgramDetailsTask.xml
Normal file
13
components/liveTv/LoadProgramDetailsTask.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
|
||||
<component name="LoadProgramDetailsTask" extends="Task">
|
||||
<interface>
|
||||
<field id="programId" type="string" />
|
||||
<field id="ChannelIndex" type="integer" />
|
||||
<field id="ProgramIndex" type="integer" />
|
||||
<field id="programDetails" type="node" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="LoadProgramDetailsTask.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||
</component>
|
41
components/liveTv/LoadSheduleTask.brs
Normal file
41
components/liveTv/LoadSheduleTask.brs
Normal file
|
@ -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)
|
||||
data = postJson(resp, FormatJson(params))
|
||||
|
||||
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
|
13
components/liveTv/LoadSheduleTask.xml
Normal file
13
components/liveTv/LoadSheduleTask.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
|
||||
<component name="LoadScheduleTask" extends="Task">
|
||||
<interface>
|
||||
<field id="startTime" type="string" />
|
||||
<field id="endTime" type="string" />
|
||||
<field id="channelIds" type="string" />
|
||||
<field id="schedule" type="array" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="LoadSheduleTask.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||
</component>
|
230
components/liveTv/ProgramDetails.brs
Normal file
230
components/liveTv/ProgramDetails.brs
Normal file
|
@ -0,0 +1,230 @@
|
|||
sub init()
|
||||
|
||||
' Max "Overview" lines to show in Preview and Detail
|
||||
m.maxPreviewLines = 5
|
||||
m.maxDetailLines = 14
|
||||
|
||||
m.detailsView = m.top.findNode("detailsView")
|
||||
m.noInfoView = m.top.findNode("noInformation")
|
||||
|
||||
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 channelUpdated()
|
||||
if m.top.channel = invalid
|
||||
m.top.findNode("noInfoChannelName").text = ""
|
||||
m.channelName.text = ""
|
||||
else
|
||||
m.top.findNode("noInfoChannelName").text = m.top.channel.Title
|
||||
m.channelName.text= m.top.channel.Title
|
||||
if m.top.programDetails = invalid then
|
||||
m.image.uri = m.top.channel.posterURL
|
||||
end if
|
||||
end if
|
||||
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
|
||||
channelUpdated()
|
||||
m.detailsView.visible = "false"
|
||||
m.noInfoView.visible = "true"
|
||||
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
|
||||
|
||||
|
||||
m.detailsView.visible = "true"
|
||||
m.noInfoView.visible = "false"
|
||||
|
||||
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 duration 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
|
||||
|
65
components/liveTv/ProgramDetails.xml
Normal file
65
components/liveTv/ProgramDetails.xml
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="ProgramDetails" extends="JFGroup">
|
||||
<children>
|
||||
|
||||
<!-- Selected Item Details -->
|
||||
<maskGroup id="backgroundMask" maskUri="pkg:/images/backgroundmask.png" translation="[1208, 127]" maskSize="[712,400]">
|
||||
<Poster id="image" height="400" width="712" loadDisplayMode="scaleToFit" />
|
||||
</maskGroup>
|
||||
|
||||
<Group id="detailsView" visible="false">
|
||||
|
||||
<Group translation = "[ 96, 160 ]">
|
||||
<LayoutGroup itemSpacings="[4,20, 20, 20, 40]">
|
||||
<Label id="programName" font="font:LargeBoldSystemFont" />
|
||||
|
||||
<LayoutGroup id="episodeDetailsGroup" layoutDirection="horiz" itemSpacings="[10]">
|
||||
<Group id="isLive">
|
||||
<Poster id="isLiveBackground" uri="pkg:/images/white.9.png" blendColor="#FF0000" />
|
||||
<Label id="isLiveText" text="Live" font="font:SmallestBoldSystemFont" translation="[8,4]" />
|
||||
</Group>
|
||||
<Group id="isRepeat">
|
||||
<Poster id="isRepeatBackground" uri="pkg:/images/white.9.png" blendColor="#009688" />
|
||||
<Label id="isRepeatText" text="Repeat" font="font:SmallestBoldSystemFont" translation="[8,4]" />
|
||||
</Group>
|
||||
<Label id="episodeNumber" font="font:SmallSystemFont" />
|
||||
<Label id="episodeTitle" font="font:SmallSystemFont" />
|
||||
</LayoutGroup>
|
||||
|
||||
<LayoutGroup layoutDirection="horiz" itemSpacings="[30]">
|
||||
<Label id="duration" />
|
||||
<Label id="broadcastDetails" />
|
||||
<Label id="channelName" />
|
||||
</LayoutGroup>
|
||||
|
||||
<label id="overview" wrap="true" width="1250" font="font:SmallestSystemFont" />
|
||||
|
||||
<!-- View Channel button -->
|
||||
<Group id="viewChannelButton" opacity="0">
|
||||
<Poster id="viewChannelButtonBackground" uri="pkg:/images/white.9.png" blendColor="#006fab" />
|
||||
<Label text="View Channel" translation="[20,20]" />
|
||||
</Group>
|
||||
|
||||
</LayoutGroup>
|
||||
|
||||
</Group>
|
||||
</Group>
|
||||
<!-- When no schedule information to display -->
|
||||
<LayoutGroup id="noInformation" translation="[96, 300]">
|
||||
<Label id="noInfoChannelName" font="font:LargeBoldSystemFont" />
|
||||
<Label font="font:SmallSystemFont" text="No schedule information" />
|
||||
</LayoutGroup>
|
||||
<Animation id="focusAnimation" duration="0.66" repeat="false" easeFunction="linear" >
|
||||
<FloatFieldInterpolator id="focusAnimationOpacity" key="[0.0, 1]" fieldToInterp="viewChannelButton.opacity" />
|
||||
</Animation>
|
||||
</children>
|
||||
<interface>
|
||||
<field id="WatchSelectedChannel" type="boolean" value="false" />
|
||||
<field id="channel" type="node" onchange="channelUpdated" />
|
||||
<field id="programDetails" type="node" onchange="programUpdated" />
|
||||
<field id="height" type="integer" />
|
||||
<field id="hasFocus" type="boolean" onChange="focusChanged" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="ProgramDetails.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||
</component>
|
189
components/liveTv/schedule.brs
Normal file
189
components/liveTv/schedule.brs
Normal file
|
@ -0,0 +1,189 @@
|
|||
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)
|
||||
m.top.signalBeacon("EPGLaunchComplete") ' Required Roku Performance monitoring
|
||||
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
|
||||
|
||||
m.scheduleGrid.showLoadingDataFeedback = false
|
||||
end sub
|
||||
|
||||
sub onProgramFocused()
|
||||
|
||||
m.top.watchChannel = invalid
|
||||
channel = m.scheduleGrid.content.GetChild(m.scheduleGrid.programFocusedDetails.focusChannelIndex)
|
||||
m.detailsPane.channel = channel
|
||||
|
||||
' 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 <> invalid and 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)
|
||||
|
||||
' If TV Show does not have its own image, use the channel logo
|
||||
if m.LoadProgramDetailsTask.programDetails.PosterUrl = invalid or m.LoadProgramDetailsTask.programDetails.PosterUrl = "" then
|
||||
m.LoadProgramDetailsTask.programDetails.PosterUrl = channel.PosterUrl
|
||||
end if
|
||||
|
||||
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
|
23
components/liveTv/schedule.xml
Normal file
23
components/liveTv/schedule.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="Schedule" extends="JFGroup">
|
||||
<children>
|
||||
<rectangle translation="[0,125]" width="1920" height="955" color="#262626" />
|
||||
|
||||
<!-- Selected Item Details -->
|
||||
<ProgramDetails id="detailsPane" focusable="true" />
|
||||
|
||||
<TimeGrid id="scheduleGrid" translation="[0,600]"
|
||||
automaticLoadingDataFeedback="false" showLoadingDataFeedback="true"
|
||||
focusBitmapUri="pkg:/images/white.9.png" focusBitmapBlendColor="#006fab"
|
||||
programTitleFocusedColor="#ffffff"
|
||||
showPastTimeScreen="true" pastTimeScreenBlendColor="#555555"
|
||||
/>
|
||||
<Animation id="gridMoveAnimation" duration="1" repeat="false" easeFunction="outQuad" >
|
||||
<Vector2DFieldInterpolator id="gridMoveAnimationPosition" key="[0.0, 0.5]" fieldToInterp="scheduleGrid.translation" />
|
||||
</Animation>
|
||||
</children>
|
||||
<interface>
|
||||
<field id="watchChannel" type="node" alwaysNotify="false" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="schedule.brs" />
|
||||
</component>
|
BIN
images/backgroundmask.png
Normal file
BIN
images/backgroundmask.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 810 KiB |
|
@ -200,5 +200,105 @@
|
|||
<source>Unable to load Channel Data from the server</source>
|
||||
<translation>Unable to load Channel Data from the server</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>today</source>
|
||||
<translation>today</translation>
|
||||
<extracomment>Current day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>yesterday</source>
|
||||
<translation>yesterday</translation>
|
||||
<extracomment>Previous day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>tomorrow</source>
|
||||
<translation>tomorrow</translation>
|
||||
<extracomment>Next day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sunday</source>
|
||||
<translation>Sunday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Monday</source>
|
||||
<translation>Monday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tuesday</source>
|
||||
<translation>Tuesday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wednesday</source>
|
||||
<translation>Wednesday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Thursday</source>
|
||||
<translation>Thursday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Friday</source>
|
||||
<translation>Friday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Saturday</source>
|
||||
<translation>Saturday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started at</source>
|
||||
<translation>Started at</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started</source>
|
||||
<translation>Started</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Starts at</source>
|
||||
<translation>Starts at</translation>
|
||||
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Starts</source>
|
||||
<translation>Starts</translation>
|
||||
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ended at</source>
|
||||
<translation>Ended at</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ends at</source>
|
||||
<translation>Ends at</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Live</source>
|
||||
<translation>Live</translation>
|
||||
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Repeat</source>
|
||||
<translation>Repeat</translation>
|
||||
<extracomment>If TV Shows has previously been broadcasted</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Channels</source>
|
||||
<translation>Channels</translation>
|
||||
<extracomment>Menu option for showing Live TV Channel List</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Guide</source>
|
||||
<translation>TV Guide</translation>
|
||||
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -314,6 +314,106 @@
|
|||
<source>TAB_FILTER</source>
|
||||
<translation>Filter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>today</source>
|
||||
<translation>today</translation>
|
||||
<extracomment>Current day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>yesterday</source>
|
||||
<translation>yesterday</translation>
|
||||
<extracomment>Previous day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>tomorrow</source>
|
||||
<translation>tomorrow</translation>
|
||||
<extracomment>Next day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sunday</source>
|
||||
<translation>Sunday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Monday</source>
|
||||
<translation>Monday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tuesday</source>
|
||||
<translation>Tuesday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wednesday</source>
|
||||
<translation>Wednesday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Thursday</source>
|
||||
<translation>Thursday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Friday</source>
|
||||
<translation>Friday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Saturday</source>
|
||||
<translation>Saturday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started at</source>
|
||||
<translation>Started at</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started</source>
|
||||
<translation>Started</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Starts at</source>
|
||||
<translation>Starts at</translation>
|
||||
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Starts</source>
|
||||
<translation>Starts</translation>
|
||||
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ended at</source>
|
||||
<translation>Ended at</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ends at</source>
|
||||
<translation>Ends at</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Live</source>
|
||||
<translation>Live</translation>
|
||||
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Repeat</source>
|
||||
<translation>Repeat</translation>
|
||||
<extracomment>If TV Shows has previously been broadcasted</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Channels</source>
|
||||
<translation>Channels</translation>
|
||||
<extracomment>Menu option for showing Live TV Channel List</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Guide</source>
|
||||
<translation>TV Guide</translation>
|
||||
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
|
||||
</message>
|
||||
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -225,7 +225,7 @@
|
|||
<translation>Error During Playback</translation>
|
||||
<extracomment>Dialog title when error occurs during playback</extracomment>
|
||||
</message>
|
||||
|
||||
|
||||
<message>
|
||||
<source>There was an error retrieving the data for this item from the server.</source>
|
||||
<translation>There was an error retrieving the data for this item from the server.</translation>
|
||||
|
@ -237,7 +237,7 @@
|
|||
<translation>An error was encountered while playing this item.</translation>
|
||||
<extracomment>Dialog detail when error occurs during playback</extracomment>
|
||||
</message>
|
||||
|
||||
|
||||
<message>
|
||||
<source>Loading Channel Data</source>
|
||||
<translation>Loading Channel Data</translation>
|
||||
|
@ -314,6 +314,106 @@
|
|||
<source>TAB_FILTER</source>
|
||||
<translation>Filter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>today</source>
|
||||
<translation>today</translation>
|
||||
<extracomment>Current day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>yesterday</source>
|
||||
<translation>yesterday</translation>
|
||||
<extracomment>Previous day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>tomorrow</source>
|
||||
<translation>tomorrow</translation>
|
||||
<extracomment>Next day</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sunday</source>
|
||||
<translation>Sunday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Monday</source>
|
||||
<translation>Monday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tuesday</source>
|
||||
<translation>Tuesday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wednesday</source>
|
||||
<translation>Wednesday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Thursday</source>
|
||||
<translation>Thursday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Friday</source>
|
||||
<translation>Friday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Saturday</source>
|
||||
<translation>Saturday</translation>
|
||||
<extracomment>Day of Week</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started at</source>
|
||||
<translation>Started at</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Started</source>
|
||||
<translation>Started</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Starts at</source>
|
||||
<translation>Starts at</translation>
|
||||
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Starts</source>
|
||||
<translation>Starts</translation>
|
||||
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ended at</source>
|
||||
<translation>Ended at</translation>
|
||||
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ends at</source>
|
||||
<translation>Ends at</translation>
|
||||
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Live</source>
|
||||
<translation>Live</translation>
|
||||
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Repeat</source>
|
||||
<translation>Repeat</translation>
|
||||
<extracomment>If TV Shows has previously been broadcasted</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Channels</source>
|
||||
<translation>Channels</translation>
|
||||
<extracomment>Menu option for showing Live TV Channel List</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Guide</source>
|
||||
<translation>TV Guide</translation>
|
||||
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
|
||||
</message>
|
||||
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -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
|
||||
|
@ -486,6 +486,7 @@ function LoginFlow(startOver = false as boolean)
|
|||
get_token(userSelected, "")
|
||||
if get_setting("active_user") <> invalid then
|
||||
m.user = AboutMe()
|
||||
LoadUserPreferences()
|
||||
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
|
||||
return true
|
||||
end if
|
||||
|
@ -508,6 +509,7 @@ function LoginFlow(startOver = false as boolean)
|
|||
goto start_login
|
||||
end if
|
||||
|
||||
LoadUserPreferences()
|
||||
wipe_groups()
|
||||
|
||||
'Send Device Profile information to server
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -67,3 +67,19 @@ function GetPublicUsers()
|
|||
resp = APIRequest(url)
|
||||
return getJson(resp)
|
||||
end function
|
||||
|
||||
' Load and parse Display Settings from server
|
||||
sub LoadUserPreferences()
|
||||
id = get_setting("active_user")
|
||||
' Currently using client "emby", which is what website uses so we get same Display prefs as web.
|
||||
' May want to change to specific Roku display settings
|
||||
url = Substitute("DisplayPreferences/usersettings?userId={0}&client=emby", id)
|
||||
resp = APIRequest(url)
|
||||
jsonResponse = getJson(resp)
|
||||
|
||||
if jsonResponse <> invalid and jsonResponse.CustomPrefs <> invalid and jsonResponse.CustomPrefs["landing-livetv"] <> invalid then
|
||||
set_user_setting("display.livetv.landing", jsonResponse.CustomPrefs["landing-livetv"])
|
||||
else
|
||||
unset_user_setting("display.livetv.landing")
|
||||
end if
|
||||
end sub
|
|
@ -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")
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user