Merge branch 'master' of https://github.com/jellyfin/jellyfin-roku into ssdp-scan

This commit is contained in:
Bronley 2021-07-09 06:53:16 -04:00
commit fa2452c037
86 changed files with 3777 additions and 1884 deletions

5
.gitignore vendored
View File

@ -15,3 +15,8 @@ rooibosFunctionMap.brs
*/buildinfo.brs
.vscode
logs
#Eclipse
.buildpath
.project
.settings

View File

@ -12,7 +12,7 @@
##########################################################################
APPNAME = Jellyfin_Roku
VERSION = 0.0.1
VERSION = 1.4.9
ROKU_TEST_ID = 1
ROKU_TEST_WAIT_DURATION = 5

View File

@ -6,5 +6,6 @@
"images/**/*.*",
"resources/**/*.*",
"locale/**/*.*"
]
],
"plugins": [ "@rokucommunity/bslint" ]
}

View File

@ -34,7 +34,7 @@ end sub
'
' When options are fully displayed, set focus and selected option
sub renderChanged()
if m.top.renderTracking = "full" then
if m.top.renderTracking = "full"
highlightSelected(m.selectedFocusedIndex, false)
m.top.setfocus(true)
end if
@ -77,7 +77,7 @@ sub highlightSelected(index as integer, animate = true)
val = m.buttonGroup.getChild(index)
rect = val.ancestorBoundingRect(m.top)
if animate = true then
if animate = true
m.focusAnimTranslation.keyValue = [m.focusRing.translation, [rect.x - 25, rect.y - 30]]
m.focusAnimWidth.keyValue = [m.focusRing.width, val.width + 50]
m.focusAnimHeight.keyValue = [m.focusRing.height, val.height + 60]
@ -92,7 +92,7 @@ end sub
' Change opacity of the highlighted menu item based on focus
sub focusChanged()
if m.top.isInFocusChain() then
if m.top.isInFocusChain()
m.focusRing.opacity = 1
else
m.focusRing.opacity = 0.6
@ -105,12 +105,12 @@ function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "left"
if(m.selectedFocusedIndex > 0) m.selectedFocusedIndex = m.selectedFocusedIndex - 1
if m.selectedFocusedIndex > 0 then m.selectedFocusedIndex = m.selectedFocusedIndex - 1
highlightSelected(m.selectedFocusedIndex)
m.top.focusedIndex = m.selectedFocusedIndex
return true
else if key = "right"
if(m.selectedFocusedIndex < m.buttonCount - 1) m.selectedFocusedIndex = m.selectedFocusedIndex + 1
if m.selectedFocusedIndex < m.buttonCount - 1 then m.selectedFocusedIndex = m.selectedFocusedIndex + 1
highlightSelected(m.selectedFocusedIndex)
m.top.focusedIndex = m.selectedFocusedIndex
return true

View File

@ -1,33 +0,0 @@
Sub Init()
m.top.functionName = "listenInput"
End Sub
function ListenInput()
port=createobject("romessageport")
InputObject=createobject("roInput")
InputObject.setmessageport(port)
while true
msg=port.waitmessage(500)
if type(msg)="roInputEvent" then
print "INPUT EVENT!"
if msg.isInput()
inputData = msg.getInfo()
'print inputData'
for each item in inputData
print item +": " inputData[item]
end for
' pass the deeplink to UI
if inputData.DoesExist("mediaType") and inputData.DoesExist("contentID")
deeplink = {
id: inputData.contentID
type: inputData.mediaType
}
print "got input deeplink= "; deeplink
m.top.inputData = deeplink
end if
end if
end if
end while
end function

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component name="InputTask" extends="Task">
<interface>
<field id="inputData" type="assocarray" />
</interface>
<script type="text/brightscript" uri="InputTask.brs" />
</component>

View File

@ -29,22 +29,23 @@ sub itemContentChanged()
if itemData = invalid then return
if itemData.type = "Movie" then
if itemData.type = "Movie"
m.itemPoster.uri = itemData.PosterUrl
m.itemText.text = itemData.Title
else if itemData.type = "Series" then
else if itemData.type = "Series"
m.itemPoster.uri = itemData.PosterUrl
m.itemText.text = itemData.Title
else if itemData.type = "Boxset" then
else if itemData.type = "Boxset"
m.itemPoster.uri = itemData.PosterUrl
m.itemText.text = itemData.Title
else if itemData.type = "TvChannel" then
else if itemData.type = "TvChannel"
m.itemPoster.uri = itemData.PosterUrl
m.itemText.text = itemData.Title
else if itemData.type = "Folder" then
else if itemData.type = "Folder"
m.itemPoster.uri = itemData.PosterUrl
m.itemIcon.uri = itemData.iconUrl
m.itemText.text = itemData.Title
else if itemData.type = "Video" then
else if itemData.type = "Video"
m.itemPoster.uri = itemData.PosterUrl
m.itemText.text = itemData.Title
else
@ -52,7 +53,7 @@ sub itemContentChanged()
end if
'If Poster not loaded, ensure "blue box" is shown until loaded
if m.itemPoster.loadStatus <> "ready" then
if m.itemPoster.loadStatus <> "ready"
m.backdrop.visible = true
m.posterText.visible = true
end if
@ -72,7 +73,7 @@ end sub
'Display or hide title Visibility on focus change
sub focusChanged()
if m.top.itemHasFocus = true then
if m.top.itemHasFocus = true
m.itemText.visible = true
m.itemText.repeatCount = -1
else
@ -84,7 +85,7 @@ end sub
'Hide backdrop and text when poster loaded
sub onPosterLoadStatusChanged()
if m.itemPoster.loadStatus = "ready" then
if m.itemPoster.loadStatus = "ready"
m.backdrop.visible = false
m.posterText.visible = false
end if

View File

@ -33,7 +33,6 @@ sub init()
m.filter = "All"
m.loadItemsTask = createObject("roSGNode", "LoadItemsTask2")
m.loadItemsTask.observeField("content", "ItemDataLoaded")
end sub
@ -41,7 +40,7 @@ end sub
'Load initial set of Data
sub loadInitialItems()
if m.top.parentItem.backdropUrl <> invalid then
if m.top.parentItem.backdropUrl <> invalid
SetBackground(m.top.parentItem.backdropUrl)
end if
@ -51,29 +50,30 @@ sub loadInitialItems()
m.loadItemsTask.filter = m.filter
m.loadItemsTask.startIndex = 0
if m.top.parentItem.collectionType = "movies" then
if m.top.parentItem.collectionType = "movies"
m.loadItemsTask.itemType = "Movie"
else if m.top.parentItem.collectionType = "tvshows" then
else if m.top.parentItem.collectionType = "tvshows"
m.loadItemsTask.itemType = "Series"
else if m.top.parentItem.collectionType = "livetv" then
else if m.top.parentItem.collectionType = "livetv"
m.loadItemsTask.itemType = "LiveTV"
'For LiveTV, we want to "Fit" the item images, not zoom
m.top.imageDisplayMode = "scaleToFit"
if get_user_setting("display.livetv.landing") = "guide" then
if get_user_setting("display.livetv.landing") = "guide"
showTvGuid()
end if
else if m.top.parentItem.collectionType = "CollectionFolder" OR m.top.parentItem.collectionType = "boxsets" then
else if m.top.parentItem.collectionType = "CollectionFolder" OR m.top.parentItem.collectionType = "boxsets" or m.top.parentItem.Type = "Folder" or m.top.parentItem.Type = "Channel"
' Non-recursive, to not show subfolder contents
m.loadItemsTask.recursive = false
else if m.top.parentItem.collectionType = "Channel" then
else if m.top.parentItem.collectionType = "Channel"
m.top.imageDisplayMode = "scaleToFit"
else
print "Unknown Type: " m.top.parentItem
print "[ItemGrid] Unknown Type: " m.top.parentItem
end if
m.loadItemsTask.observeField("content", "ItemDataLoaded")
m.loadItemsTask.control = "RUN"
SetUpOptions()
@ -87,7 +87,7 @@ sub SetUpOptions()
options.filter = []
'Movies
if m.top.parentItem.collectionType = "movies" then
if m.top.parentItem.collectionType = "movies"
options.views = [
{ "Title": tr("Movies"), "Name": "movies" },
]
@ -106,8 +106,21 @@ sub SetUpOptions()
{ "Title": tr("All"), "Name": "All" },
{ "Title": tr("Favorites"), "Name": "Favorites" }
]
'Boxsets
else if m.top.parentItem.collectionType = "boxsets"
options.views = [{ "Title": tr("Shows"), "Name": "shows" }]
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" },
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },
{ "Title": tr("Favorites"), "Name": "Favorites" }
]
'TV Shows
else if m.top.parentItem.collectionType = "tvshows" then
else if m.top.parentItem.collectionType = "tvshows"
options.views = [{ "Title": tr("Shows"), "Name": "shows" }]
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" },
@ -119,7 +132,7 @@ sub SetUpOptions()
]
options.filter = []
'Live TV
else if m.top.parentItem.collectionType = "livetv" then
else if m.top.parentItem.collectionType = "livetv"
options.views = [
{"Title": tr("Channels"), "Name": "livetv" },
{"Title": tr("TV Guide"), "Name": "tvGuide", "Selected": get_user_setting("display.livetv.landing") = "guide" }
@ -139,14 +152,14 @@ sub SetUpOptions()
end if
for each o in options.sort
if o.Name = m.sortField then
if o.Name = m.sortField
o.Selected = true
o.Ascending = m.sortAscending
end if
end for
for each o in options.filter
if o.Name = m.filter then
if o.Name = m.filter
o.Selected = true
end if
end for
@ -161,11 +174,10 @@ end sub
sub ItemDataLoaded(msg)
itemData = msg.GetData()
data = msg.getField()
m.loadItemsTask.unobserveField("content")
m.loadItemsTask.content = []
if itemData = invalid then
if itemData = invalid
m.Loading = false
return
end if
@ -180,7 +192,7 @@ sub ItemDataLoaded(msg)
m.Loading = false
'If there are no items to display, show message
if m.loadedItems = 0 then
if m.loadedItems = 0
m.emptyText.text = tr("NO_ITEMS").Replace("%1", m.top.parentItem.Type)
m.emptyText.visible = true
end if
@ -194,7 +206,7 @@ end sub
sub SetBackground(backgroundUri as string)
'If a new image is being loaded, or transitioned to, store URL to load next
if m.swapAnimation.state <> "stopped" or m.newBackdrop.loadStatus = "loading" then
if m.swapAnimation.state <> "stopped" or m.newBackdrop.loadStatus = "loading"
m.queuedBGUri = backgroundUri
return
end if
@ -211,7 +223,7 @@ sub onItemFocused()
itemInt = m.itemGrid.itemFocused
' If no selected item, set background to parent backdrop
if itemInt = -1 then
if itemInt = -1
return
end if
@ -219,7 +231,7 @@ sub onItemFocused()
SetBackground(m.itemGrid.content.getChild(m.itemGrid.itemFocused).backdropUrl)
' Load more data if focus is within last 3 rows, and there are more items to load
if focusedRow >= m.loadedRows - 3 and m.loadeditems < m.loadItemsTask.totalRecordCount then
if focusedRow >= m.loadedRows - 3 and m.loadeditems < m.loadItemsTask.totalRecordCount
loadMoreData()
end if
end sub
@ -237,7 +249,7 @@ end sub
'Swap Complete
sub swapDone()
if m.swapAnimation.state = "stopped" then
if m.swapAnimation.state = "stopped"
'Set main BG node image and hide transitioning node
m.backdrop.uri = m.newBackdrop.uri
@ -245,7 +257,7 @@ sub swapDone()
m.newBackdrop.opacity = 0
'If there is another one to load
if m.newBackdrop.uri <> m.queuedBGUri and m.queuedBGUri <> "" then
if m.newBackdrop.uri <> m.queuedBGUri and m.queuedBGUri <> ""
SetBackground(m.queuedBGUri)
m.queuedBGUri = ""
end if
@ -275,21 +287,21 @@ end sub
'Check if options updated and any reloading required
sub optionsClosed()
if (m.options.view = "tvGuide") then
if m.options.view = "tvGuide"
showTVGuid()
return
else if m.tvGuide <> invalid then
else if m.tvGuide <> invalid
' 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
if m.options.sortField <> m.sortField or m.options.sortAscending <> m.sortAscending
m.sortField = m.options.sortField
m.sortAscending = m.options.sortAscending
reload = true
end if
if m.options.filter <> m.filter then
if m.options.filter <> m.filter
m.filter = m.options.filter
reload = true
end if
@ -305,7 +317,7 @@ end sub
sub showTVGuid()
m.top.signalBeacon("EPGLaunchInitiate") ' Required Roku Performance monitoring
if m.tvGuide = invalid then
if m.tvGuide = invalid
m.tvGuide = createObject("roSGNode", "Schedule")
endif
m.tvGuide.observeField("watchChannel", "onChannelSelected")
@ -316,7 +328,7 @@ end sub
sub onChannelSelected(msg)
node = msg.getRoSGNode()
m.top.lastFocus = lastFocusedChild(node)
if node.watchChannel <> invalid then
if node.watchChannel <> invalid
' 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
@ -327,7 +339,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "options"
if m.options.visible = true then
if m.options.visible = true
m.options.visible = false
m.top.removeChild(m.options)
optionsClosed()
@ -337,16 +349,16 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.options.setFocus(true)
end if
return true
else if key = "back" then
if m.options.visible = true then
else if key = "back"
if m.options.visible = true
m.options.visible = false
optionsClosed()
return true
end if
else if key = "play" then
else if key = "play"
markupGrid = m.top.getChild(2)
itemToPlay = markupGrid.content.getChild(markupGrid.itemFocused)
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode") then
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
m.top.quickPlayNode = itemToPlay
end if
return true

View File

@ -30,7 +30,7 @@ end sub
sub optionsSet()
' Views Tab
if m.top.options.views <> invalid then
if m.top.options.views <> invalid
viewContent = CreateObject("roSGNode", "ContentNode")
index = 0
selectedViewIndex = 0
@ -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) or viewContent.Name = m.top.view then
if (view.selected <> invalid and view.selected = true) or viewContent.Name = m.top.view
selectedViewIndex = index
end if
index = index + 1
@ -49,7 +49,7 @@ sub optionsSet()
end if
' Sort Tab
if m.top.options.sort <> invalid then
if m.top.options.sort <> invalid
sortContent = CreateObject("roSGNode", "ContentNode")
index = 0
m.selectedSortIndex = 0
@ -58,9 +58,9 @@ sub optionsSet()
entry = sortContent.CreateChild("ContentNode")
entry.title = sortItem.Title
m.sortNames.push(sortItem.Name)
if sortItem.Selected <> invalid and sortItem.Selected = true then
if sortItem.Selected <> invalid and sortItem.Selected = true
m.selectedSortIndex = index
if sortItem.Ascending <> invalid and sortItem.Ascending = false then
if sortItem.Ascending <> invalid and sortItem.Ascending = false
m.top.sortAscending = 0
else
m.top.sortAscending = 1
@ -71,7 +71,7 @@ sub optionsSet()
m.menus[1].content = sortContent
m.menus[1].checkedItem = m.selectedSortIndex
if m.top.sortAscending = 1 then
if m.top.sortAscending = 1
m.menus[1].focusedCheckedIconUri = m.global.constants.icons.ascending_black
m.menus[1].checkedIconUri = m.global.constants.icons.ascending_white
else
@ -81,7 +81,7 @@ sub optionsSet()
end if
' Filter Tab
if m.top.options.filter <> invalid then
if m.top.options.filter <> invalid
filterContent = CreateObject("roSGNode", "ContentNode")
index = 0
m.selectedFilterIndex = 0
@ -90,7 +90,7 @@ sub optionsSet()
entry = filterContent.CreateChild("ContentNode")
entry.title = filterItem.Title
m.filterNames.push(filterItem.Name)
if filterItem.selected <> invalid and filterItem.selected = true then
if filterItem.selected <> invalid and filterItem.selected = true
m.selectedFilterIndex = index
end if
index = index + 1
@ -121,13 +121,13 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if key = "down" or (key = "OK" and m.top.findNode("buttons").hasFocus()) then
if key = "down" or (key = "OK" and m.top.findNode("buttons").hasFocus())
m.top.findNode("buttons").setFocus(false)
m.menus[m.selectedItem].setFocus(true)
m.menus[m.selectedItem].drawFocusFeedback = true
'If user presses down from button menu, focus first item. If OK, focus checked item
if key = "down" then
if key = "down"
m.menus[m.selectedItem].jumpToItem = 0
else
m.menus[m.selectedItem].jumpToItem = m.menus[m.selectedItem].itemSelected
@ -135,16 +135,16 @@ function onKeyEvent(key as string, press as boolean) as boolean
return true
else if key = "OK"
if(m.menus[m.selectedItem].isInFocusChain()) then
if m.menus[m.selectedItem].isInFocusChain()
' Handle View Screen
if(m.selectedItem = 0) then
if m.selectedItem = 0
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
if m.selectedItem = 1
if m.menus[1].itemSelected <> m.selectedSortIndex
m.menus[1].focusedCheckedIconUri = m.global.constants.icons.ascending_black
m.menus[1].checkedIconUri = m.global.constants.icons.ascending_white
@ -153,7 +153,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.top.sortField = m.sortNames[m.selectedSortIndex]
else
if m.top.sortAscending = true then
if m.top.sortAscending = true
m.top.sortAscending = false
m.menus[1].focusedCheckedIconUri = m.global.constants.icons.descending_black
m.menus[1].checkedIconUri = m.global.constants.icons.descending_white
@ -165,14 +165,14 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
end if
' Handle Filter screen
if(m.selectedItem = 2) then
if m.selectedItem = 2
m.selectedFilterIndex = m.menus[2].itemSelected
m.top.filter = m.filterNames[m.selectedFilterIndex]
end if
end if
return true
else if key = "back" or key = "up"
if m.menus[m.selectedItem].isInFocusChain() then
if m.menus[m.selectedItem].isInFocusChain()
m.buttons.setFocus(true)
m.menus[m.selectedItem].drawFocusFeedback = false
return true

View File

@ -8,7 +8,7 @@ sub loadItems()
sort_field = m.top.sortField
if m.top.sortAscending = true then
if m.top.sortAscending = true
sort_order = "Ascending"
else
sort_order = "Descending"
@ -26,17 +26,17 @@ sub loadItems()
}
filter = m.top.filter
if filter = "All" or filter = "all" then
if filter = "All" or filter = "all"
' do nothing
else if filter = "Favorites" then
else if filter = "Favorites"
params.append({ Filters: "IsFavorite"})
end if
if m.top.ItemType <> "" then
if m.top.ItemType <> ""
params.append({ IncludeItemTypes: m.top.ItemType})
end if
if m.top.ItemType = "LiveTV" then
if m.top.ItemType = "LiveTV"
url = "LiveTv/Channels"
else
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
@ -44,30 +44,30 @@ sub loadItems()
resp = APIRequest(url, params)
data = getJson(resp)
if data.TotalRecordCount <> invalid then
if data.TotalRecordCount <> invalid
m.top.totalRecordCount = data.TotalRecordCount
end if
for each item in data.Items
tmp = invalid
if item.Type = "Movie" then
if item.Type = "Movie"
tmp = CreateObject("roSGNode", "MovieData")
else if item.Type = "Series" then
else if item.Type = "Series"
tmp = CreateObject("roSGNode", "SeriesData")
else if item.Type = "BoxSet" then
else if item.Type = "BoxSet"
tmp = CreateObject("roSGNode", "CollectionData")
else if item.Type = "TvChannel" then
else if item.Type = "TvChannel"
tmp = CreateObject("roSGNode", "ChannelData")
else if item.Type = "Folder" then
else if item.Type = "Folder" or item.Type = "ChannelFolderItem" or item.Type = "CollectionFolder"
tmp = CreateObject("roSGNode", "FolderData")
else if item.Type = "Video" then
else if item.Type = "Video"
tmp = CreateObject("roSGNode", "VideoData")
else
print "Unknown Type: " item.Type
print "[LoadItems] Unknown Type: " item.Type
end if
if tmp <> invalid then
if tmp <> invalid
tmp.json = item
results.push(tmp)

View File

@ -42,7 +42,7 @@ sub redraw()
fontHeight = m.top.fontHeight
fontWidth = m.top.fontWidth
if text.text.len() > 0 then
if text.text.len() > 0
textWidth = boxWidth - ( border * 2 )
text.width = textWidth
text.numLines = int(fontWidth / textWidth) + 1
@ -58,7 +58,7 @@ sub redraw()
options.itemSpacing = "[0,20]"
options.numRows = m.top.options.count()
if options.numRows > maxRows then
if options.numRows > maxRows
options.numRows = maxRows
options.wrapDividerHeight = 0
options.vertFocusAnimationStyle= "fixedFocusWrap"

View File

@ -1,8 +1,5 @@
sub init()
m.top.id = "overhang"
' set opacity
bgg = m.top.findNode("overlayBackgroundGroup")
bgg.opacity = 0.333
' hide seperators till they're needed
leftSeperator = m.top.findNode("overlayLeftSeperator")
leftSeperator.visible = "false"
@ -32,9 +29,9 @@ sub init()
end sub
function updateTitle()
sub updateTitle()
leftSeperator = m.top.findNode("overlayLeftSeperator")
if m.top.title <> "" then
if m.top.title <> ""
leftSeperator.visible = "true"
else
leftSeperator.visible = "false"
@ -42,21 +39,21 @@ function updateTitle()
title = m.top.findNode("overlayTitle")
title.text = m.top.title
resetTime()
end function
end sub
function updateUser()
sub updateUser()
rightSeperator = m.top.findNode("overlayRightSeperator")
if m.top.currentUser <> "" then
if m.top.currentUser <> ""
rightSeperator.visible = "true"
else
rightSeperator.visible = "false"
end if
user = m.top.findNode("overlayCurrentUser")
user.text = m.top.currentUser
end function
end sub
function updateTime()
if (m.currentMinutes + 1) > 59 then
sub updateTime()
if (m.currentMinutes + 1) > 59
m.currentHours = m.currentHours + 1
m.currentMinutes = 0
else
@ -64,9 +61,9 @@ function updateTime()
end if
updateTimeDisplay()
end function
end sub
function resetTime()
sub resetTime()
m.currentTimeTimer.control = "stop"
currentTime = CreateObject("roDateTime")
@ -78,31 +75,31 @@ function resetTime()
m.currentMinutes = currentTime.GetMinutes()
updateTimeDisplay()
end function
end sub
function updateTimeDisplay()
sub updateTimeDisplay()
overlayHours = m.top.findNode("overlayHours")
overlayMinutes = m.top.findNode("overlayMinutes")
overlayMeridian = m.top.findNode("overlayMeridian")
if m.clockFormat = "24h" then
if m.clockFormat = "24h"
overlayMeridian.text = ""
if m.currentHours < 10 then
if m.currentHours < 10
overlayHours.text = "0" + StrI(m.currentHours).trim()
else
overlayHours.text = m.currentHours
end if
else
if m.currentHours < 12 then
if m.currentHours < 12
overlayMeridian.text = "AM"
if m.currentHours = 0 then
if m.currentHours = 0
overlayHours.text = "12"
else
overlayHours.text = m.currentHours
end if
else
overlayMeridian.text = "PM"
if m.currentHours = 12 then
if m.currentHours = 12
overlayHours.text = "12"
else
overlayHours.text = m.currentHours - 12
@ -110,21 +107,21 @@ function updateTimeDisplay()
end if
end if
if m.currentMinutes < 10 then
if m.currentMinutes < 10
overlayMinutes.text = "0" + StrI(m.currentMinutes).trim()
else
overlayMinutes.text = m.currentMinutes
end if
end function
end sub
function updateOptions()
sub updateOptions()
optionText = m.top.findNode("overlayOptionsText")
optionStar = m.top.findNode("overlayOptionsStar")
if m.top.showOptions = true then
if m.top.showOptions = true
optionText.visible = true
optionStar.visible = true
else
optionText.visible = false
optionStar.visible = false
end if
end function
end sub

View File

@ -1,14 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="JFOverhang" extends="Group">
<children>
<LayoutGroup id="overlayBackgroundGroup" layoutDirection="horiz" translation="[0, 0]" >
<Rectangle
id="overlayBackground"
color="#000000"
width="1920"
height="125"
translation="[0,0]" />
</LayoutGroup>
<Poster id="overlayLogo"
uri="pkg:/images/logo.png"
translation="[70, 53]"

View File

@ -1,6 +1,8 @@
sub init()
m.top.observeField("state", "onState")
m.bufferPercentage = 0 ' Track whether content is being loaded
m.top.transcodeReasons = []
end sub
@ -9,13 +11,13 @@ end sub
sub onState(msg)
' When buffering, start timer to monitor buffering process
if m.top.state = "buffering" and m.bufferCheckTimer <> invalid then
if m.top.state = "buffering" and m.bufferCheckTimer <> invalid
' start timer
m.bufferCheckTimer = m.top.findNode("bufferCheckTimer")
m.bufferCheckTimer.control = "start"
m.bufferCheckTimer.ObserveField("fire", "bufferCheck")
else if m.top.state = "error" then
else if m.top.state = "error"
' If an error was encountered, Display dialog
dialog = createObject("roSGNode", "Dialog")
@ -43,10 +45,10 @@ sub bufferCheck(msg)
return
end if
if m.top.bufferingStatus <> invalid then
if m.top.bufferingStatus <> invalid
' Check that the buffering percentage is increasing
if m.top.bufferingStatus["percentage"] > m.bufferPercentage then
if m.top.bufferingStatus["percentage"] > m.bufferPercentage
m.bufferPercentage = m.top.bufferingStatus["percentage"]
else
' If buffering has stopped Display dialog
@ -78,7 +80,7 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if m.top.Subtitles.count() and key = "down" then
if m.top.Subtitles.count() and key = "down"
m.top.selectSubtitlePressed = true
return true
end if

View File

@ -7,13 +7,19 @@
<field id="Subtitles" type="array" />
<field id="SelectedSubtitle" type="integer" />
<field id="captionMode" type="string" />
<field id="transcodeParams" type="assocarray" />
<field id="container" type="string" />
<field id="directPlaySupported" type="boolean" />
<field id="decodeAudioSupported" type="boolean" />
<field id="isTranscoded" type="boolean" />
<field id="systemOverlay" type="boolean" value="false" />
<field id="showID" type="string" />
<field id="transcodeParams" type="assocarray" />
<field id="isTranscoded" type="boolean" />
<field id="transcodeReasons" type="array" />
<field id="videoId" type="string" />
<field id="mediaSourceId" type="string" />
<field id="audioIndex" type="integer" />
</interface>
<script type="text/brightscript" uri="JFVideo.brs" />
<children>

View File

@ -59,25 +59,25 @@ sub updateSize()
end sub
function itemContentChanged() as void
m.poster = m.top.findNode("poster")
itemData = m.top.itemContent
m.title.text = itemData.title
if itemData.json.lookup("Type") = "Episode" and itemData.json.IndexNumber <> invalid
m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text
end if
m.staticTitle.text = m.title.text
sub itemContentChanged() as void
m.poster = m.top.findNode("poster")
itemData = m.top.itemContent
m.title.text = itemData.title
if itemData.json.lookup("Type") = "Episode" and itemData.json.IndexNumber <> invalid
m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text
end if
m.staticTitle.text = m.title.text
m.poster.uri = itemData.posterUrl
m.poster.uri = itemData.posterUrl
updateSize()
end function
updateSize()
end sub
'
' Enable title scrolling based on item Focus
sub focusChanged()
if m.top.itemHasFocus = true then
if m.top.itemHasFocus = true
m.title.repeatCount = -1
m.staticTitle.visible = false
m.title.visible = true

View File

@ -1,127 +0,0 @@
sub init()
m.top.currentPage = 0
m.top.maxPages = 0
m.top.layoutDirection = "horiz"
m.top.horizAlignment = "center"
m.top.vertAlignment = "center"
end sub
sub recountPages()
if m.top.currentPage = 0 or m.top.maxPages = 0
return
end if
while m.top.getChildCount() > 0
m.top.removeChildIndex(0)
end while
currentPage = m.top.currentPage
maxPages = m.top.maxPages
minShown = 1
maxShown = maxPages
if currentPage > 1
addPage("<")
minShown = currentPage - 3
end if
if minShown <= 0 then minShown = 1
if currentPage < maxPages then maxShown = currentPage + 3
if maxShown >= maxPages then maxShown = maxPages
for i=minShown to maxShown step 1
addPage(i)
end for
if currentPage <> maxPages
addPage(">")
end if
m.top.pageFocused = m.top.findNode(stri(currentPage).trim())
m.top.pageFocused.color = "#00A4DCff"
updateLayout()
end sub
sub updateLayout()
dimensions = m.top.getScene().currentDesignResolution
height = 115
m.top.translation = [dimensions.width / 2, dimensions.height - (height / 2)]
end sub
sub addPage(i)
p = CreateObject("roSGNode", "Label")
p.height = 50
p.width = 50
p.color = "#a1a1a1FF"
if type(i) = "roInt"
i = stri(i).trim()
end if
p.id = i
p.text = i
m.top.appendChild(p)
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "OK"
p = m.top.focusedChild.id
if p = ">"
m.top.pageSelected = m.top.currentPage + 1
else if p = "<"
m.top.pageSelected = m.top.currentPage - 1
else
m.top.pageSelected = m.top.focusedChild.id
end if
m.top.currentPage = m.top.pageSelected
recountPages()
return true
else if key = "left"
focusPrev()
return true
else if key = "right"
focusNext()
return true
else if key = "up"
if m.top.getParent().findNode("picker") <> invalid
m.top.getParent().findNode("picker").setFocus(true)
else if m.top.getParent().lastFocus <> invalid
m.top.getParent().lastFocus.setFocus(true)
else
m.top.getParent().setFocus(true)
end if
return true
end if
return false
end function
sub focusNext()
i = getFocusIndex()
if (i + 1) = m.top.getChildCount() then return
m.top.pageFocused.color = "#a1a1a1FF"
m.top.pageFocused = m.top.getChild(i + 1)
m.top.pageFocused.color = "#00A4DCff"
m.top.pageFocused.setFocus(true)
end sub
sub focusPrev()
i = getFocusIndex()
if i = 0 then return
m.top.pageFocused.color = "#a1a1a1FF"
m.top.pageFocused = m.top.getChild(i - 1)
m.top.pageFocused.color = "#00A4DCff"
m.top.pageFocused.setFocus(true)
end sub
function getFocusIndex()
for i=0 to m.top.getChildCount() step 1
if m.top.getChild(i).id = m.top.pageFocused.id then return i
end for
return invalid
end function

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="Pager" extends="LayoutGroup" >
<interface>
<field id="currentPage" type="integer" onChange="recountPages" />
<field id="maxPages" type="integer" onChange="recountPages" />
<field id="pageFocused" type="node" />
<field id="pageSelected" type="string" />
<field id="escape" type="boolean" alwaysNotify="true" />
</interface>
<script type="text/brightscript" uri="Pager.brs" />
</component>

View File

@ -1,116 +0,0 @@
sub init()
m.rowList = m.top.findNode("RowList") ' createObject("roSGNode", "RowList")
' m.top.appendChild(m.rowList)
m.rowList.itemComponentName = "HomeItem"
formatRowList()
m.rowList.setfocus(true)
m.rowList.observeField("rowItemSelected", "itemSelected")
end sub
sub formatRowList()
' how many rows are visible on the screen
m.rowList.numRows = 2
m.rowList.rowFocusAnimationStyle = "fixedFocusWrap"
m.rowList.vertFocusAnimationStyle = "fixedFocus"
m.rowList.showRowLabel = [true]
m.rowList.rowLabelOffset = [0, 20]
m.rowList.showRowCounter = [true]
sideborder = 100
m.rowList.translation = [111, 155]
m.rowItemSizes = []
itemWidth = 480
itemHeight = 330
m.rowList.itemSize = [1920 - 111 - 27, itemHeight]
' spacing between rows
m.rowList.itemSpacing = [0, 105]
' spacing between items in a row
m.rowList.rowItemSpacing = [20, 0]
m.rowList.visible = true
end sub
sub setupRows()
for each item in m.top.objects.Items
homeItem = CreateObject("roSGNode", "HomeData")
homeItem.json = item.json
if homeItem.Type = "Video" or homeItem.Type = "Movie" or homeItem.Type = "Episode" then
if m.videoRow = invalid then
m.videoRow = CreateObject("roSGNode", "HomeRow")
m.videoRow.title = tr("Videos")
m.videoRow.usePoster = true
m.videoRow.imageWidth = 180
end if
m.videoRow.appendChild(homeItem)
else if homeItem.Type = "MusicAlbum"
if m.albumRow = invalid then
m.albumRow = CreateObject("roSGNode", "HomeRow")
m.albumRow.imageWidth = 261
m.albumRow.title = tr("Albums")
m.albumRow.usePoster = true
end if
m.albumRow.appendChild(homeItem)
else if homeItem.Type = "Series"
if m.seriesRow = invalid then
m.seriesRow = CreateObject("roSGNode", "HomeRow")
m.seriesRow.title = tr("Series")
m.seriesRow.usePoster = true
m.seriesRow.imageWidth = 180
end if
m.seriesRow.appendChild(homeItem)
else
print "Collection - Unknown Type ", homeItem.Type
end if
end for
data = CreateObject("roSGNode", "ContentNode")
if m.videoRow <> invalid then
data.appendChild(m.videoRow)
m.rowItemSizes.push([188, 331])
end if
if m.seriesRow <> invalid then
data.appendChild(m.seriesRow)
m.rowItemSizes.push([188, 331])
end if
if m.albumRow <> invalid then
data.appendChild(m.albumRow)
m.rowItemSizes.push([261, 331])
end if
m.rowList.rowItemSize = m.rowItemSizes
m.rowList.content = data
end sub
function itemSelected()
m.top.selectedItem = m.rowList.content.getChild(m.rowList.rowItemSelected[0]).getChild(m.rowList.rowItemSelected[1])
end function

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="CollectionDetail" extends="JFGroup">
<children>
<RowList id="rowList" />
</children>
<interface>
<field id="collectionId" type="string" onChange="collectionIdChanged" />
<field id="selectedItem" type="node" alwaysNotify="true" />
<field id="objects" type="assocarray" onChange="setupRows" />
</interface>
<script type="text/brightscript" uri="CollectionDetail.brs" />
</component>

View File

@ -13,22 +13,20 @@ sub init()
end sub
function setData()
sub setData()
items = m.top.configItems
data = CreateObject("roSGNode", "ContentNode")
data.appendChildren(items)
m.top.content = data
end function
end sub
function onItemSelected()
print "HI"
sub onItemSelected()
i = m.top.itemSelected
itemField = m.top.content.getchild(i)
show_dialog(itemField)
end function
end sub
function onDialogButton()
d = m.dialog
@ -42,6 +40,7 @@ function onDialogButton()
dismiss_dialog()
return true
end if
return false
end function

View File

@ -9,7 +9,7 @@ function onKeyEvent(key as String, press as Boolean) as Boolean
list = m.top.findNode("configOptions")
button = m.top.findNode("submit")
if key = "back" then
if key = "back"
m.top.backPressed = true
else if key = "down" and button.focusedChild = invalid
limit = list.content.getChildren(-1, 0).count() - 1

View File

@ -19,8 +19,10 @@
translation="[150, 450]" />
<label text=""
id="alert"
wrap="true"
width="1620"
font="font:MediumSystemFont"
translation="[150, 555]" />
translation="[150, 580]" />
</children>
<script type="text/brightscript" uri="ConfigScene.brs"/>
</component>

View File

@ -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 <> invalid and m.top.json.ImageTags.Primary <> invalid then
else if m.top.json.ImageTags <> invalid and m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
end if

View File

@ -17,16 +17,16 @@ sub setPoster()
m.top.posterURL = m.top.image.url
else
if m.top.json.ImageTags.Primary <> invalid then
if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag" : m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
else if m.top.json.BackdropImageTags <> invalid then
else if m.top.json.BackdropImageTags <> invalid
imgParams = { "maxHeight": 440, "Tag" : m.top.json.BackdropImageTags[0] }
m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if
' Add Backdrop Image
if m.top.json.BackdropImageTags <> invalid then
if m.top.json.BackdropImageTags <> invalid
imgParams = { "maxHeight": 720, "maxWidth": 1280, "Tag" : m.top.json.BackdropImageTags[0] }
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if

View File

@ -6,4 +6,19 @@ sub setFields()
m.top.Type = "Folder"
m.top.iconUrl = "pkg:/images/media_type_icons/folder_white.png"
' This is a temporary measure to avoid displaying landscape photos
' in GridItem components that only support portrait. It will be fixed
' after the ItemGrid is reworked.
if m.top.json.Type <> "CollectionFolder"
setPoster()
end if
end sub
sub setPoster()
if m.top.image <> invalid
m.top.posterURL = m.top.image.url
else if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
end if
end sub

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<component name="FolderData" extends="JFContentItem">
<script type="text/brightscript" uri="FolderData.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>

View File

@ -7,7 +7,7 @@ sub setData()
m.top.name = datum.name
m.top.type = datum.type
if datum.CollectionType = invalid then
if datum.CollectionType = invalid
m.top.CollectionType = datum.type
else
m.top.CollectionType = datum.CollectionType
@ -15,27 +15,29 @@ sub setData()
' Set appropriate Images for Wide and Tall based on type
if datum.type = "CollectionFolder" OR datum.type = "UserView" then
if datum.type = "CollectionFolder" OR datum.type = "UserView"
params = { "Tag" : datum.ImageTags.Primary, "maxHeight" : 261, "maxWidth" : 464 }
m.top.thumbnailURL = ImageURL(datum.id, "Primary", params)
m.top.widePosterUrl = m.top.thumbnailURL
' Add Icon URLs for display if there is no Poster
if datum.CollectionType = "livetv" then
if datum.CollectionType = "livetv"
m.top.iconUrl = "pkg:/images/media_type_icons/live_tv_white.png"
else if datum.CollectionType = "folders"
m.top.iconUrl = "pkg:/images/media_type_icons/folder_white.png"
end if
else if datum.type = "Episode" then
else if datum.type = "Episode"
imgParams = { "AddPlayedIndicator": datum.UserData.Played }
if datum.UserData.PlayedPercentage <> invalid then
if datum.UserData.PlayedPercentage <> invalid
imgParams.Append({ "PercentPlayed": datum.UserData.PlayedPercentage })
end if
imgParams.Append({ "maxHeight": 261 })
imgParams.Append({ "maxWidth": 464 })
if datum.ImageTags.Primary <> invalid then
if datum.ImageTags.Primary <> invalid
param = { "Tag" : datum.ImageTags.Primary }
imgParams.Append(param)
end if
@ -43,51 +45,51 @@ sub setData()
m.top.thumbnailURL = ImageURL(datum.id, "Primary", imgParams)
' Add Wide Poster (Series Backdrop)
if datum.ParentThumbImageTag <> invalid then
if datum.ParentThumbImageTag <> invalid
imgParams["Tag"] = datum.ParentThumbImageTag
m.top.widePosterUrl = ImageURL(datum.ParentThumbItemId, "Thumb", imgParams)
else if datum.ParentBackdropImageTags <> invalid then
else if datum.ParentBackdropImageTags <> invalid
imgParams["Tag"] = datum.ParentBackdropImageTags[0]
m.top.widePosterUrl = ImageURL(datum.ParentBackdropItemId, "Backdrop", imgParams)
else if datum.ImageTags.Primary <> invalid then
else if datum.ImageTags.Primary <> invalid
imgParams["Tag"] = datum.SeriesPrimaryImageTag
m.top.widePosterUrl = ImageURL(datum.id, "Primary", imgParams)
end if
else if datum.type = "Series" then
else if datum.type = "Series"
imgParams = { "maxHeight": 261 }
imgParams.Append({ "maxWidth": 464 })
if datum.UserData.UnplayedItemCount > 0 then
if datum.UserData.UnplayedItemCount > 0
imgParams["UnplayedCount"] = datum.UserData.UnplayedItemCount
end if
if datum.ImageTags.Primary <> invalid then
if datum.ImageTags.Primary <> invalid
imgParams["Tag"] = datum.ImageTags.Primary
end if
m.top.posterURL = ImageURL(datum.id, "Primary", imgParams)
' Add Wide Poster (Series Backdrop)
if datum.ImageTags <> invalid and datum.imageTags.Thumb <> invalid then
if datum.ImageTags <> invalid and datum.imageTags.Thumb <> invalid
imgParams["Tag"] = datum.imageTags.Thumb
m.top.widePosterUrl = ImageURL(datum.Id, "Thumb", imgParams)
else if datum.BackdropImageTags <> invalid then
else if datum.BackdropImageTags <> invalid
imgParams["Tag"] = datum.BackdropImageTags[0]
m.top.widePosterUrl = ImageURL(datum.Id, "Backdrop", imgParams)
end if
else if datum.type = "Movie" then
else if datum.type = "Movie"
imgParams = { AddPlayedIndicator: datum.UserData.Played }
if datum.UserData.PlayedPercentage <> invalid then
if datum.UserData.PlayedPercentage <> invalid
imgParams.Append({ "PercentPlayed": datum.UserData.PlayedPercentage })
end if
imgParams.Append({ "maxHeight": 261 })
imgParams.Append({ "maxWidth": 175 })
if datum.ImageTags.Primary <> invalid then
if datum.ImageTags.Primary <> invalid
param = { "Tag" : datum.ImageTags.Primary }
imgParams.Append(param)
end if
@ -97,25 +99,25 @@ sub setData()
' For wide image, use backdrop
imgParams["maxWidth"] = 464
if datum.ImageTags <> invalid and datum.imageTags.Thumb <> invalid then
if datum.ImageTags <> invalid and datum.imageTags.Thumb <> invalid
imgParams["Tag"] = datum.imageTags.Thumb
m.top.thumbnailUrl = ImageURL(datum.Id, "Thumb", imgParams)
else if datum.BackdropImageTags[0] <> invalid then
else if datum.BackdropImageTags[0] <> invalid
imgParams["Tag"] = datum.BackdropImageTags[0]
m.top.thumbnailUrl = ImageURL(datum.id, "Backdrop", imgParams)
end if
else if datum.type = "Video" then
else if datum.type = "Video"
imgParams = { AddPlayedIndicator: datum.UserData.Played }
if datum.UserData.PlayedPercentage <> invalid then
if datum.UserData.PlayedPercentage <> invalid
imgParams.Append({ "PercentPlayed": datum.UserData.PlayedPercentage })
end if
imgParams.Append({ "maxHeight": 261 })
imgParams.Append({ "maxWidth": 175 })
if datum.ImageTags.Primary <> invalid then
if datum.ImageTags.Primary <> invalid
param = { "Tag" : datum.ImageTags.Primary }
imgParams.Append(param)
end if
@ -125,20 +127,20 @@ sub setData()
' For wide image, use backdrop
imgParams["maxWidth"] = 464
if datum.ImageTags <> invalid and datum.imageTags.Thumb <> invalid then
if datum.ImageTags <> invalid and datum.imageTags.Thumb <> invalid
imgParams["Tag"] = datum.imageTags.Thumb
m.top.thumbnailUrl = ImageURL(datum.Id, "Thumb", imgParams)
else if datum.BackdropImageTags[0] <> invalid then
else if datum.BackdropImageTags[0] <> invalid
imgParams["Tag"] = datum.BackdropImageTags[0]
m.top.thumbnailUrl = ImageURL(datum.id, "Backdrop", imgParams)
end if
else if datum.type = "MusicAlbum" then
else if datum.type = "MusicAlbum"
params = { "Tag" : datum.ImageTags.Primary, "maxHeight" : 261, "maxWidth" : 261 }
m.top.thumbnailURL = ImageURL(datum.id, "Primary", params)
m.top.widePosterUrl = m.top.thumbnailURL
m.top.posterUrl = m.top.thumbnailURL
else if datum.type = "TvChannel" OR datum.type = "Channel" then
else if datum.type = "TvChannel" OR datum.type = "Channel"
params = { "Tag" : datum.ImageTags.Primary, "maxHeight" : 261, "maxWidth" : 464 }
m.top.thumbnailURL = ImageURL(datum.id, "Primary", params)
m.top.widePosterUrl = m.top.thumbnailURL

View File

@ -8,13 +8,13 @@ sub setFields()
m.top.watched = json.UserData.played
m.top.Type = "Movie"
if json.ProductionYear <> invalid then
if json.ProductionYear <> invalid
m.top.SubTitle = json.ProductionYear
end if
if json.OfficialRating <> invalid and json.OfficialRating <> "" then
if json.OfficialRating <> invalid and json.OfficialRating <> ""
m.top.Rating = json.OfficialRating
if m.top.SubTitle <> "" then
if m.top.SubTitle <> ""
m.top.SubTitle = m.top.SubTitle + " - " + m.top.Rating
else
m.top.SubTitle = m.top.Rating
@ -30,17 +30,19 @@ sub setPoster()
m.top.posterURL = m.top.image.url
else
if m.top.json.ImageTags.Primary <> invalid then
if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag" : m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
else if m.top.json.BackdropImageTags <> invalid then
imgParams = { "maxHeight": 440, "Tag" : m.top.json.BackdropImageTags[0] }
else if m.top.json.BackdropImageTags[0] <> invalid
imgParams = { "maxHeight": 440, "Tag" : m.top.json.BackdropImageTags[0] }
m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
else if m.top.json.ParentThumbImageTag <> invalid and m.top.json.ParentThumbItemId <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag" : m.top.json.ParentThumbImageTag }
m.top.posterURL = ImageURL(m.top.json.ParentThumbItemId, "Thumb", imgParams)
end if
' Add Backdrop Image
if m.top.json.BackdropImageTags <> invalid then
if m.top.json.BackdropImageTags[0] <> invalid
imgParams = { "maxHeight": 720, "maxWidth": 1280, "Tag" : m.top.json.BackdropImageTags[0] }
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if

View File

@ -18,7 +18,7 @@ sub setFields()
m.top.endDate = json.endDate
m.top.channelId = json.channelId
if json.IsSeries <> invalid and json.IsSeries = true then
if json.IsSeries <> invalid and json.IsSeries = true
if json.IndexNumber <> invalid
m.top.episodeNumber = json.IndexNumber
end if
@ -35,7 +35,7 @@ sub setFields()
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
if m.top.json.ImageTags <> invalid and m.top.json.ImageTags.Thumb <> invalid
imgParams = { "maxHeight": 500, "maxWidth": 500, "Tag" : m.top.json.ImageTags.Thumb }
m.top.posterURL = ImageURL(m.top.json.id, "Thumb", imgParams)
end if

View File

@ -9,13 +9,13 @@ sub setFields()
m.top.Type = "Series"
m.top.overview = json.overview
if json.ProductionYear <> invalid then
if json.ProductionYear <> invalid
m.top.SubTitle = json.ProductionYear
end if
if json.OfficialRating <> invalid and json.OfficialRating <> "" then
if json.OfficialRating <> invalid and json.OfficialRating <> ""
m.top.Rating = json.OfficialRating
if m.top.SubTitle <> "" then
if m.top.SubTitle <> ""
m.top.SubTitle = m.top.SubTitle + " - " + m.top.Rating
else
m.top.SubTitle = m.top.Rating
@ -30,17 +30,17 @@ sub setPoster()
m.top.posterURL = m.top.image.url
else
if m.top.json.ImageTags.Primary <> invalid then
if m.top.json.ImageTags.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag" : m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
else if m.top.json.BackdropImageTags <> invalid then
else if m.top.json.BackdropImageTags <> invalid
imgParams = { "maxHeight": 440, "Tag" : m.top.json.BackdropImageTags[0] }
m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if
' Add Backdrop Image
if m.top.json.BackdropImageTags <> invalid then
if m.top.json.BackdropImageTags <> invalid
imgParams = { "maxHeight": 720, "maxWidth": 1280, "Tag" : m.top.json.BackdropImageTags[0] }
m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
end if

View File

@ -3,21 +3,21 @@ sub setDataFromJSON()
loadFromJSON(json)
end sub
function loadFromJSON(json)
sub loadFromJSON(json)
m.top.id = json.User.id
m.top.username = json.User.name
m.top.token = json.AccessToken
end function
end sub
function loadFromRegistry(id as string)
sub loadFromRegistry(id as string)
m.top.id = id
m.top.username = get_user_setting("username")
m.top.token = get_user_setting("token")
end function
end sub
function saveToRegistry()
sub saveToRegistry()
set_user_setting("username", m.top.username)
set_user_setting("token", m.top.token)
@ -34,9 +34,9 @@ function saveToRegistry()
})
set_setting("available_users", formatJson(users))
end if
end function
end sub
function removeFromRegistry()
sub removeFromRegistry()
new_users = []
users = parseJson(get_setting("available_users", "[]"))
for each user in users
@ -44,7 +44,7 @@ function removeFromRegistry()
end for
set_setting("available_users", formatJson(new_users))
end function
end sub
function getPreference(key as string, default as string)
return get_user_setting("pref-" + key, default)
@ -54,10 +54,10 @@ function setPreference(key as string, value as string)
return set_user_setting("pref-" + key, value)
end function
function setActive()
sub setActive()
set_setting("active_user", m.top.id)
end function
end sub
function setServer(hostname as string)
sub setServer(hostname as string)
m.top.server = hostname
end function
end sub

View File

@ -14,7 +14,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.Primary <> invalid
imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
end if

View File

@ -3,10 +3,10 @@ sub init()
m.top.optionsAvailable = true
end sub
function refresh()
sub refresh()
m.top.findNode("homeRows").callFunc("updateHomeRows")
end function
end sub
function loadLibraries()
sub loadLibraries()
m.top.findNode("homeRows").callFunc("loadLibraries")
end function
end sub

View File

@ -33,13 +33,13 @@ sub itemContentChanged()
end if
' Format the Data based on the type of Home Data
if itemData.type = "CollectionFolder" OR itemData.type = "UserView" OR itemData.type = "Channel" then
if itemData.type = "CollectionFolder" OR itemData.type = "UserView" OR itemData.type = "Channel"
m.itemText.text = itemData.name
m.itemPoster.uri = itemData.widePosterURL
return
end if
if itemData.type = "UserView" then
if itemData.type = "UserView"
m.itemPoster.width = "96"
m.itemPoster.height = "96"
m.itemPoster.translation = "[192, 88]"
@ -57,10 +57,10 @@ sub itemContentChanged()
m.itemTextExtra.font.size = 22
if itemData.type = "Episode" then
if itemData.type = "Episode"
m.itemText.text = itemData.json.SeriesName
if itemData.usePoster = true then
if itemData.usePoster = true
m.itemPoster.uri = itemData.widePosterURL
else
m.itemPoster.uri = itemData.thumbnailURL
@ -68,13 +68,13 @@ sub itemContentChanged()
' Set Series and Episode Number for Extra Text
extraPrefix = ""
if itemData.json.ParentIndexNumber <> invalid then
if itemData.json.ParentIndexNumber <> invalid
extraPrefix = "S" + StrI(itemData.json.ParentIndexNumber).trim()
end if
if itemData.json.IndexNumber <> invalid then
if itemData.json.IndexNumber <> invalid
extraPrefix = extraPrefix + "E" + StrI(itemData.json.IndexNumber).trim()
end if
if extraPrefix.len() > 0 then
if extraPrefix.len() > 0
extraPrefix = extraPrefix + " - "
end if
@ -82,7 +82,7 @@ sub itemContentChanged()
return
end if
if itemData.type = "Movie" then
if itemData.type = "Movie"
m.itemText.text = itemData.name
' Use best image, but fallback to secondary if it's empty
@ -94,11 +94,11 @@ sub itemContentChanged()
' Set Release Year and Age Rating for Extra Text
textExtra = ""
if itemData.json.ProductionYear <> invalid then
if itemData.json.ProductionYear <> invalid
textExtra = StrI(itemData.json.ProductionYear).trim()
end if
if itemData.json.OfficialRating <> invalid then
if textExtra <> "" then
if itemData.json.OfficialRating <> invalid
if textExtra <> ""
textExtra = textExtra + " - " + itemData.json.OfficialRating
else
textExtra = itemData.json.OfficialRating
@ -109,7 +109,7 @@ sub itemContentChanged()
return
end if
if itemData.type = "Video" then
if itemData.type = "Video"
m.itemText.text = itemData.name
if itemData.imageWidth = 180
@ -119,12 +119,12 @@ sub itemContentChanged()
end if
return
end if
if itemData.type = "Series" then
if itemData.type = "Series"
m.itemText.text = itemData.name
if itemData.usePoster = true then
if itemData.imageWidth = 180 then
if itemData.usePoster = true
if itemData.imageWidth = 180
m.itemPoster.uri = itemData.posterURL
else
m.itemPoster.uri = itemData.widePosterURL
@ -134,12 +134,12 @@ sub itemContentChanged()
end if
textExtra = ""
if itemData.json.ProductionYear <> invalid then
if itemData.json.ProductionYear <> invalid
textExtra = StrI(itemData.json.ProductionYear).trim()
end if
' Set Years Run for Extra Text
if itemData.json.Status = "Continuing" then
if itemData.json.Status = "Continuing"
textExtra = textExtra + " - Present"
else if itemData.json.Status = "Ended" and itemData.json.EndDate <> invalid
textExtra = textExtra + " - " + LEFT(itemData.json.EndDate, 4)
@ -149,7 +149,7 @@ sub itemContentChanged()
return
end if
if itemData.type = "MusicAlbum" then
if itemData.type = "MusicAlbum"
m.itemText.text = itemData.name
m.itemTextExtra.text = itemData.json.AlbumArtist
m.itemPoster.uri = itemData.posterURL
@ -164,7 +164,7 @@ end sub
' Enable title scrolling based on item Focus
sub focusChanged()
if m.top.itemHasFocus = true then
if m.top.itemHasFocus = true
m.itemText.repeatCount = -1
else
m.itemText.repeatCount = 0
@ -174,7 +174,7 @@ end sub
'Hide backdrop and icon when poster loaded
sub onPosterLoadStatusChanged()
if m.itemPoster.loadStatus = "ready" and m.itemPoster.uri <> "" then
if m.itemPoster.loadStatus = "ready" and m.itemPoster.uri <> ""
m.backdrop.visible = false
m.itemIcon.visible = false
else

View File

@ -26,15 +26,12 @@ sub init()
m.LoadNextUpTask.itemsToLoad = "nextUp"
end sub
function loadLibraries()
sub loadLibraries()
m.LoadLibrariesTask.control = "RUN"
end function
end sub
sub updateSize()
sideborder = 100
m.top.translation = [111, 180]
itemWidth = 480
itemHeight = 330
'Set width of Rows to cut off at edge of Safe Zone
@ -68,7 +65,7 @@ sub onLibrariesLoaded()
[464, 331] ' Next Up
]
' validate library data
if (m.libraryData <> invalid and m.libraryData.count() > 0) then
if m.libraryData <> invalid and m.libraryData.count() > 0
userConfig = m.top.userConfig
' populate My Media row
filteredMedia = filterNodeArray(m.libraryData, "id", userConfig.MyMediaExcludes)
@ -78,7 +75,7 @@ sub onLibrariesLoaded()
' create a "Latest In" row for each library
filteredLatest = filterNodeArray(m.libraryData, "id", userConfig.LatestItemsExcludes)
for each lib in filteredLatest
if lib.collectionType <> "boxsets" and lib.collectionType <> "livetv" then
if lib.collectionType <> "boxsets" and lib.collectionType <> "livetv"
latestInRow = content.CreateChild("HomeRow")
latestInRow.title = tr("Latest in") + " " + lib.name + " >"
sizeArray.Push([464, 331])
@ -94,23 +91,23 @@ sub onLibrariesLoaded()
m.LoadContinueTask.control = "RUN"
end sub
function updateHomeRows()
sub updateHomeRows()
m.LoadContinueTask.observeField("content", "updateContinueItems")
m.LoadContinueTask.control = "RUN"
end function
end sub
function updateContinueItems()
sub updateContinueItems()
itemData = m.LoadContinueTask.content
m.LoadContinueTask.unobserveField("content")
m.LoadContinueTask.content = []
if itemData = invalid then return false
if itemData = invalid then return
homeRows = m.top.content
continueRowIndex = getRowIndex("Continue Watching")
if itemData.count() < 1 then
if continueRowIndex <> invalid then
if itemData.count() < 1
if continueRowIndex <> invalid
' remove the row
deleteFromSizeArray(continueRowIndex)
homeRows.removeChildIndex(continueRowIndex)
@ -126,7 +123,7 @@ function updateContinueItems()
row.appendChild(item)
end for
if continueRowIndex = invalid then
if continueRowIndex = invalid
' insert new row under "My Media"
updateSizeArray(itemSize, 1)
homeRows.insertChild(row, 1)
@ -138,20 +135,20 @@ function updateContinueItems()
m.LoadNextUpTask.observeField("content", "updateNextUpItems")
m.LoadNextUpTask.control = "RUN"
end function
end sub
function updateNextUpItems()
sub updateNextUpItems()
itemData = m.LoadNextUpTask.content
m.LoadNextUpTask.unobserveField("content")
m.LoadNextUpTask.content = []
if itemData = invalid then return false
if itemData = invalid then return
homeRows = m.top.content
nextUpRowIndex = getRowIndex("Next Up >")
if itemData.count() < 1 then
if nextUpRowIndex <> invalid then
if itemData.count() < 1
if nextUpRowIndex <> invalid
' remove the row
deleteFromSizeArray(nextUpRowIndex)
homeRows.removeChildIndex(nextUpRowIndex)
@ -167,10 +164,10 @@ function updateNextUpItems()
row.appendChild(item)
end for
if nextUpRowIndex = invalid then
if nextUpRowIndex = invalid
' insert new row under "Continue Watching"
continueRowIndex = getRowIndex("Continue Watching")
if continueRowIndex <> invalid then
if continueRowIndex <> invalid
updateSizeArray(itemSize, continueRowIndex + 1)
homeRows.insertChild(row, continueRowIndex + 1)
else
@ -185,7 +182,7 @@ function updateNextUpItems()
end if
' consider home screen loaded when above rows are loaded
if m.global.app_loaded = false then
if m.global.app_loaded = false
m.top.signalBeacon("AppLaunchComplete") ' Roku Performance monitoring
m.global.app_loaded = true
end if
@ -195,7 +192,7 @@ function updateNextUpItems()
userConfig = m.top.userConfig
filteredLatest = filterNodeArray(m.libraryData, "id", userConfig.LatestItemsExcludes)
for each lib in filteredLatest
if lib.collectionType <> "livetv" and lib.collectionType <> "boxsets" then
if lib.collectionType <> "livetv" and lib.collectionType <> "boxsets"
loadLatest = createObject("roSGNode", "LoadItemsTask")
loadLatest.itemsToLoad = "latest"
loadLatest.itemId = lib.id
@ -208,24 +205,23 @@ function updateNextUpItems()
loadLatest.control = "RUN"
end if
end for
end function
end sub
function updateLatestItems(msg)
sub updateLatestItems(msg)
itemData = msg.GetData()
data = msg.getField()
node = msg.getRoSGNode()
node.unobserveField("content")
node.content = []
if itemData = invalid then return false
if itemData = invalid then return
homeRows = m.top.content
rowIndex = getRowIndex(tr("Latest in") + " " + node.metadata.title + " >")
if itemData.count() < 1 then
if itemData.count() < 1
' remove row
if rowIndex <> invalid then
if rowIndex <> invalid
deleteFromSizeArray(rowIndex)
homeRows.removeChildIndex(rowIndex)
end if
@ -235,10 +231,10 @@ function updateLatestItems(msg)
row.title = tr("Latest in") + " " + node.metadata.title + " >"
row.usePoster = true
' Handle specific types with different item widths
if node.metadata.contentType = "movies" then
if node.metadata.contentType = "movies"
row.imageWidth = 180
itemSize = [188, 331]
else if node.metadata.contentType = "music" then
else if node.metadata.contentType = "music"
row.imageWidth = 261
itemSize = [261, 331]
else
@ -252,7 +248,7 @@ function updateLatestItems(msg)
row.appendChild(item)
end for
if rowIndex = invalid then
if rowIndex = invalid
' append new row
updateSizeArray(itemSize)
homeRows.appendChild(row)
@ -262,14 +258,14 @@ function updateLatestItems(msg)
homeRows.replaceChild(row, rowIndex)
end if
end if
end function
end sub
function getRowIndex(rowTitle as string)
rowIndex = invalid
for i = 1 to m.top.content.getChildCount() - 1
' skip row 0 since it's always "My Media"
tmpRow = m.top.content.getChild(i)
if tmpRow.title = rowTitle then
if tmpRow.title = rowTitle
rowIndex = i
exit for
end if
@ -280,22 +276,22 @@ end function
sub updateSizeArray(rowItemSize, rowIndex = invalid, action = "insert")
sizeArray = m.top.rowItemSize
' append by default
if rowIndex = invalid then
if rowIndex = invalid
rowIndex = sizeArray.count()
end if
newSizeArray = []
for i = 0 to sizeArray.count()
if rowIndex = i then
if action = "replace" then
if rowIndex = i
if action = "replace"
newSizeArray.Push(rowItemSize)
else if action = "insert" then
else if action = "insert"
newSizeArray.Push(rowItemSize)
if sizeArray[i] <> invalid then
if sizeArray[i] <> invalid
newSizeArray.Push(sizeArray[i])
end if
end if
else if sizeArray[i] <> invalid then
else if sizeArray[i] <> invalid
newSizeArray.Push(sizeArray[i])
end if
end for
@ -306,16 +302,16 @@ sub deleteFromSizeArray(rowIndex)
updateSizeArray([0, 0], rowIndex, "delete")
end sub
function itemSelected()
sub itemSelected()
m.top.selectedItem = m.top.content.getChild(m.top.rowItemSelected[0]).getChild(m.top.rowItemSelected[1])
end function
end sub
function onKeyEvent(key as string, press as boolean) as boolean
handled = false
if press then
if key = "play" then
if press
if key = "play"
itemToPlay = m.top.content.getChild(m.top.rowItemFocused[0]).getChild(m.top.rowItemFocused[1])
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode") then
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
m.top.quickPlayNode = itemToPlay
end if
handled = true
@ -331,11 +327,11 @@ function filterNodeArray(nodeArray as object, nodeKey as string, excludeArray as
for each node in nodeArray
excludeThisNode = false
for each exclude in excludeArray
if node[nodeKey] = exclude then
if node[nodeKey] = exclude
excludeThisNode = true
end if
end for
if excludeThisNode = false then
if excludeThisNode = false
newNodeArray.Push(node)
end if
end for

View File

@ -8,8 +8,8 @@ sub init()
params = {
UserId: get_setting("active_user")
limit: m.top.limit,
StartIndex: m.top.startIndex
'limit: m.top.limit,
'StartIndex: m.top.startIndex
}
url = "LiveTv/Channels"
@ -17,7 +17,7 @@ sub init()
resp = APIRequest(url, params)
data = getJson(resp)
if data.TotalRecordCount = invalid then
if data.TotalRecordCount = invalid
m.top.channels = results
return
end if
@ -31,4 +31,4 @@ sub init()
m.top.channels = results
end sub
end sub

View File

@ -2,7 +2,7 @@
<component name="LoadChannelsTask" extends="Task">
<interface>
<field id="limit" type="integer" value="500" />
<field id="limit" type="integer" value="" />
<field id="startIndex" type="integer" value="0" />
<!-- Total records available from server-->
@ -12,4 +12,4 @@
<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>
</component>

View File

@ -17,15 +17,15 @@ sub loadProgramDetails()
resp = APIRequest(url, params)
data = getJson(resp)
if data = invalid then
if data = invalid
m.top.programDetails = {}
return
end if
program = createObject("roSGNode", "ScheduleProgramData")
program.json = data
program.channelIndex = ChannelIndex
program.programIndex = ProgramIndex
program.channelIndex = channelIndex
program.programIndex = programIndex
program.fullyLoaded = true
m.top.programDetails = program

View File

@ -22,7 +22,7 @@ sub init()
resp = APIRequest(url)
data = postJson(resp, FormatJson(params))
if data = invalid then
if data = invalid
m.top.schedule = results
return
end if

View File

@ -60,7 +60,7 @@ sub channelUpdated()
else
m.top.findNode("noInfoChannelName").text = m.top.channel.Title
m.channelName.text= m.top.channel.Title
if m.top.programDetails = invalid then
if m.top.programDetails = invalid
m.image.uri = m.top.channel.posterURL
end if
end if
@ -73,7 +73,7 @@ sub programUpdated()
prog = m.top.programDetails
' If no program selected, hide details view
if prog = invalid then
if prog = invalid
channelUpdated()
m.detailsView.visible = "false"
m.noInfoView.visible = "true"
@ -85,20 +85,20 @@ sub programUpdated()
m.episodeDetailsGroup.removeChildrenIndex(m.episodeDetailsGroup.getChildCount(), 0)
if prog.isLive then
if prog.isLive
m.episodeDetailsGroup.appendChild(m.isLiveGroup)
else if prog.isRepeat then
else if prog.isRepeat
m.episodeDetailsGroup.appendChild(m.isRepeatGroup)
end if
' Episode Number
if prog.seasonNumber > 0 and prog.episodeNumber > 0 then
if prog.seasonNumber > 0 and prog.episodeNumber > 0
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
if prog.episodeTitle <> invalid and prog.episodeTitle <> ""
m.episodeTitle.text = prog.episodeTitle
m.episodeTitle.visible = true
m.episodeDetailsGroup.appendChild(m.episodeTitle)
@ -115,23 +115,28 @@ sub programUpdated()
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)
' Get Start Date in local timezone for display to user
localStartDate = createObject("roDateTime")
localStartDate.FromISO8601String(prog.StartDate)
localStartDate.ToLocalTime()
if startDate.AsSeconds() < now.AsSeconds() and endDate.AsSeconds() > now.AsSeconds()
if day = "today"
m.broadcastDetails.text = tr("Started at") + " " + formatTime(localStartDate)
else
m.broadcastDetails.text = tr("Started") + " " + tr(day) + ", " + formatTime(startDate)
m.broadcastDetails.text = tr("Started") + " " + tr(day) + ", " + formatTime(localStartDate)
end if
else if startDate.AsSeconds() > now.AsSeconds()
if day = "today" then
m.broadcastDetails.text = tr("Starts at") + " " + formatTime(startDate)
if day = "today"
m.broadcastDetails.text = tr("Starts at") + " " + formatTime(localStartDate)
else
m.broadcastDetails.text = tr("Starts") + " " + tr(day) + ", " + formatTime(startDate)
m.broadcastDetails.text = tr("Starts") + " " + tr(day) + ", " + formatTime(localStartDate)
end if
else
if day = "today" then
m.broadcastDetails.text = tr("Ended at") + " " + formatTime(endDate)
if day = "today"
m.broadcastDetails.text = tr("Ended at") + " " + formatTime(localStartDate)
else
m.broadcastDetails.text = tr("Ended") + " " + tr(day) + ", " + formatTime(endDate)
m.broadcastDetails.text = tr("Ended") + " " + tr(day) + ", " + formatTime(localStartDate)
end if
end if
@ -152,7 +157,7 @@ function getRelativeDayName(date) as string
now = createObject("roDateTime")
' Check for Today
if now.AsDateString("short-date-dashes") = date.AsDateString("short-date-dashes") then
if now.AsDateString("short-date-dashes") = date.AsDateString("short-date-dashes")
return "today"
end if
@ -160,11 +165,11 @@ function getRelativeDayName(date) as string
todayMidnight = now.AsSeconds() - (now.AsSeconds() MOD 86400)
dateMidnight = date.AsSeconds() - (date.AsSeconds() MOD 86400)
if todayMidnight - dateMidnight = 86400 then
if todayMidnight - dateMidnight = 86400
return "yesterday"
end if
if dateMidnight - todayMidnight = 86400 then
if dateMidnight - todayMidnight = 86400
return "tomorrow"
end if
@ -179,12 +184,12 @@ function getDurationStringFromSeconds(seconds) as string
hours = 0
minutes = seconds / 60.0
if minutes > 60 then
if minutes > 60
hours = (minutes - (minutes MOD 60)) / 60
minutes = minutes MOD 60
end if
if hours > 0 then
if hours > 0
return "%1h %2m".Replace("%1", StrI(hours).trim()).Replace("%2", StrI(minutes).trim())
else
return "%1m".Replace("%1", StrI(minutes).trim())
@ -195,7 +200,7 @@ end function
'
' Show view channel button when item has Focus
sub focusChanged()
if m.top.hasFocus = true then
if m.top.hasFocus = true
m.overview.maxLines = m.maxDetailLines
m.focusAnimationOpacity.keyValue = [0, 1]
else
@ -208,7 +213,7 @@ sub focusChanged()
end sub
sub onAnimationComplete()
if m.focusAnimation.state = "stopped" and m.top.hasFocus = false then
if m.focusAnimation.state = "stopped" and m.top.hasFocus = false
m.overview.maxLines = m.maxPreviewLines
end if
end sub
@ -216,12 +221,12 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "OK" then
if key = "OK"
m.top.watchSelectedChannel = true
return true
end if
if key = "left" or key = "right" or key = "up" or key = "down" then
if key = "left" or key = "right" or key = "up" or key = "down"
return true
end if

View File

@ -66,10 +66,10 @@ sub onScheduleLoaded()
channel = m.scheduleGrid.content.GetChild(m.channelIndex[item.ChannelId])
if channel.PosterUrl <> "" then
if channel.PosterUrl <> ""
item.channelLogoUri = channel.PosterUrl
end if
if channel.Title <> "" then
if channel.Title <> ""
item.channelName = channel.Title
end if
@ -88,14 +88,14 @@ sub onProgramFocused()
m.detailsPane.channel = channel
' Exit if Channels not yet loaded
if channel.getChildCount() = 0 then
if channel.getChildCount() = 0
m.detailsPane.programDetails = invalid
return
end if
prog = channel.GetChild(m.scheduleGrid.programFocusedDetails.focusIndex)
if prog <> invalid and prog.fullyLoaded = false then
if prog <> invalid and prog.fullyLoaded = false
m.LoadProgramDetailsTask.programId = prog.Id
m.LoadProgramDetailsTask.channelIndex = m.scheduleGrid.programFocusedDetails.focusChannelIndex
m.LoadProgramDetailsTask.programIndex = m.scheduleGrid.programFocusedDetails.focusIndex
@ -111,7 +111,7 @@ sub onProgramDetailsLoaded()
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
if m.LoadProgramDetailsTask.programDetails.PosterUrl = invalid or m.LoadProgramDetailsTask.programDetails.PosterUrl = ""
m.LoadProgramDetailsTask.programDetails.PosterUrl = channel.PosterUrl
end if
@ -122,7 +122,7 @@ end sub
sub onProgramSelected()
' If there is no program data - view the channel
if m.detailsPane.programDetails = invalid then
if m.detailsPane.programDetails = invalid
m.top.watchChannel = m.scheduleGrid.content.GetChild(m.scheduleGrid.programFocusedDetails.focusChannelIndex)
return
end if
@ -138,7 +138,7 @@ sub focusProgramDetails(setFocused)
if h < 400 then h = 400
h = h + 160 + 80
if setFocused = true then
if setFocused = true
m.gridMoveAnimationPosition.keyValue = [ [0,600], [0, h] ]
m.detailsPane.setFocus(true)
m.detailsPane.hasFocus = true
@ -168,10 +168,10 @@ end sub
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
if m.scheduleGrid.leftEdgeTargetTime + (12 * 60 * 60) > m.gridEndDate.AsSeconds()
' Ensure the task is not already (still) running,
if m.LoadScheduleTask.state <> "run" then
if m.LoadScheduleTask.state <> "run"
m.LoadScheduleTask.startTime = m.gridEndDate.ToISOString()
m.gridEndDate.FromSeconds(m.gridEndDate.AsSeconds() + (24 * 60 * 60))
m.LoadScheduleTask.endTime = m.gridEndDate.ToISOString()
@ -183,7 +183,7 @@ 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
if key = "back" and m.detailsPane.isInFocusChain()
focusProgramDetails(false)
return true
end if

View File

@ -8,7 +8,7 @@ sub itemContentChanged()
profileImage = m.top.findNode("profileImage")
profileName = m.top.findNode("profileName")
if itemData.imageURL = "" then
if itemData.imageURL = ""
profileImage.uri = "pkg://images/baseline_person_white_48dp.png"
else
profileImage.uri = itemData.imageURL

View File

@ -8,12 +8,6 @@ sub init()
end sub
sub updateSize()
dimensions = m.top.getScene().currentDesignResolution
border = 200
'm.top.translation = [border, border + 115]
textHeight = 80
itemWidth = 300
itemHeight = 364
@ -32,7 +26,7 @@ end sub
function setData()
if m.top.itemContent = invalid then
if m.top.itemContent = invalid
data = CreateObject("roSGNode", "ContentNode")
return data
end if

View File

@ -14,7 +14,7 @@ sub redraw()
itemWidth = 300
itemSpacing = 40
if userCount < 5 then
if userCount < 5
leftBorder = (1920 - ((userCount * itemWidth) + ((userCount - 1) * itemSpacing))) / 2
end if
' break()
@ -24,15 +24,15 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "back" then
if key = "back"
m.top.backPressed = true
else if key = "up" then
if m.top.focusedChild.isSubType("LabelList") then
else if key = "up"
if m.top.focusedChild.isSubType("LabelList")
m.top.findNode("UserRow").setFocus(true)
return true
end if
else if key = "down" then
if m.top.focusedChild.isSubType("UserRow") then
else if key = "down"
if m.top.focusedChild.isSubType("UserRow")
m.top.findNode("alternateOptions").setFocus(true)
return true
end if

View File

@ -1,30 +1,30 @@
function init()
sub init()
m.title = m.top.findNode("title")
m.description = m.top.findNode("description")
m.selectedIcon = m.top.findNode("selectedIcon")
end function
end sub
function itemContentChanged()
sub itemContentChanged()
m.title.text = m.top.itemContent.title
m.description.text = m.top.itemContent.description
if m.top.itemContent.description = "" then
if m.top.itemContent.description = ""
m.title.translation = [50, 20]
end if
if m.top.itemContent.selected then
if m.top.itemContent.selected
m.selectedIcon.uri = m.global.constants.icons.check_white
else
m.selectedIcon.uri = ""
end if
end function
end sub
'
'Scroll description if focused
sub focusChanged()
if m.top.itemHasFocus = true then
if m.top.itemHasFocus = true
m.description.repeatCount = -1
else
m.description.repeatCount = 0

View File

@ -16,12 +16,11 @@ sub itemContentChanged()
item = m.top.itemContent
itemData = item.json
m.top.id = itemData.id
m.top.findNode("moviePoster").uri = m.top.itemContent.posterURL
' Find first Audio Stream and set that as default
For i=0 To itemData.mediaStreams.Count() - 1
if itemData.mediaStreams[i].Type = "Audio" then
if itemData.mediaStreams[i].Type = "Audio"
m.top.selectedAudioStreamIndex = i
exit for
end if
@ -33,16 +32,16 @@ sub itemContentChanged()
setFieldText("officialRating", itemData.officialRating)
setFieldText("overview", itemData.overview)
if itemData.communityRating <> invalid then
if itemData.communityRating <> invalid
setFieldText("communityRating", itemData.communityRating)
else
' hide the star icon
m.top.findNode("communityRatingGroup").visible = false
end if
if itemData.CriticRating <> invalid then
if itemData.CriticRating <> invalid
setFieldText("criticRatingLabel" , itemData.criticRating)
if itemData.CriticRating > 60 then
if itemData.CriticRating > 60
tomato = "pkg:/images/fresh.png"
else
tomato = "pkg:/images/rotten.png"
@ -60,18 +59,28 @@ sub itemContentChanged()
if itemData.genres.count() > 0
setFieldText("genres", tr("Genres") + ": " + itemData.genres.join(", "))
end if
director = invalid
' show tags if there are no genres to display
if itemData.genres.count() = 0 and itemData.tags.count() > 0
setFieldText("genres", tr("Tags") + ": " + itemData.tags.join(", "))
end if
directors = []
for each person in itemData.people
if person.type = "Director"
director = person.name
exit for
directors.push(person.name)
end if
end for
if director <> invalid
setFieldText("director", tr("Director") + ": " + director)
if directors.count() > 0
setFieldText("director", tr("Director") + ": " + directors.join(", "))
end if
if itemData.mediaStreams[0] <> invalid
setFieldText("video_codec", tr("Video") + ": " + itemData.mediaStreams[0].displayTitle)
end if
if itemData.mediaStreams[m.top.selectedAudioStreamIndex] <> invalid
setFieldText("audio_codec", tr("Audio") + ": " + itemData.mediaStreams[m.top.selectedAudioStreamIndex].displayTitle)
end if
setFieldText("video_codec", tr("Video") + ": " + itemData.mediaStreams[0].displayTitle)
setFieldText("audio_codec", tr("Audio") + ": " + itemData.mediaStreams[m.top.selectedAudioStreamIndex].displayTitle)
' TODO - cmon now. these are buttons, not words
if itemData.taglines.count() > 0
setFieldText("tagline", itemData.taglines[0])
@ -87,7 +96,7 @@ sub SetUpOptions(streams)
tracks = []
for i=0 To streams.Count() - 1
if streams[i].Type = "Audio" then
if streams[i].Type = "Audio"
tracks.push({"Title": streams[i].displayTitle, "Description" : streams[i].Title, "Selected" : m.top.selectedAudioStreamIndex = i, "StreamIndex" : i})
end if
end for
@ -104,11 +113,11 @@ sub setFieldText(field, value)
if node = invalid or value = invalid then return
' Handle non strings... Which _shouldn't_ happen, but hey
if type(value) = "roInt" or type(value) = "Integer" then
if type(value) = "roInt" or type(value) = "Integer"
value = str(value)
else if type(value) = "roFloat" or type(value) = "Float" then
else if type(value) = "roFloat" or type(value) = "Float"
value = str(value)
else if type(value) <> "roString" and type(value) <> "String" then
else if type(value) <> "roString" and type(value) <> "String"
value = ""
end if
@ -175,7 +184,7 @@ end function
'
'Check if options updated and any reloading required
sub optionsClosed()
if m.options.audioSteamIndex <> m.top.selectedAudioStreamIndex then
if m.options.audioSteamIndex <> m.top.selectedAudioStreamIndex
m.top.selectedAudioStreamIndex = m.options.audioSteamIndex
setFieldText("audio_codec", tr("Audio") + ": " + m.top.itemContent.json.mediaStreams[m.top.selectedAudioStreamIndex].displayTitle)
end if
@ -187,7 +196,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
' Due to the way the button pressed event works, need to catch the release for the button as the press is being sent
' directly to the main loop. Will get this sorted in the layout update for Movie Details
if (key = "OK" and m.top.findNode("audio-button").isInFocusChain())
if key = "OK" and m.top.findNode("audio-button").isInFocusChain()
m.options.visible = true
m.options.setFocus(true)
end if
@ -195,7 +204,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "options"
if m.options.visible = true then
if m.options.visible = true
m.options.visible = false
optionsClosed()
else
@ -203,8 +212,8 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.options.setFocus(true)
end if
return true
else if key = "back" then
if m.options.visible = true then
else if key = "back"
if m.options.visible = true
m.options.visible = false
optionsClosed()
return true

View File

@ -27,7 +27,7 @@ end sub
sub optionsSet()
' Views Tab
if m.top.options.views <> invalid then
if m.top.options.views <> invalid
viewContent = CreateObject("roSGNode", "ContentNode")
index = 0
selectedViewIndex = 0
@ -38,7 +38,7 @@ sub optionsSet()
entry.description = view.Description
entry.streamIndex = view.StreamIndex
m.viewNames.push(view.Name)
if view.Selected <> invalid and view.Selected = true then
if view.Selected <> invalid and view.Selected = true
selectedViewIndex = index
entry.selected = true
m.top.audioSteamIndex = view.streamIndex
@ -65,13 +65,13 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
if key = "down" or (key = "OK" and m.top.findNode("buttons").hasFocus()) then
if key = "down" or (key = "OK" and m.top.findNode("buttons").hasFocus())
m.top.findNode("buttons").setFocus(false)
m.menus[m.selectedItem].setFocus(true)
m.menus[m.selectedItem].drawFocusFeedback = true
'If user presses down from button menu, focus first item. If OK, focus checked item
if key = "down" then
if key = "down"
m.menus[m.selectedItem].jumpToItem = 0
else
m.menus[m.selectedItem].jumpToItem = m.menus[m.selectedItem].itemSelected
@ -79,13 +79,12 @@ function onKeyEvent(key as string, press as boolean) as boolean
return true
else if key = "OK"
if(m.menus[m.selectedItem].isInFocusChain()) then
if m.menus[m.selectedItem].isInFocusChain()
selMenu = m.menus[m.selectedItem]
selIndex = selMenu.itemSelected
child = selMenu.content.GetChild(selIndex)
if m.selectedAudioIndex = selIndex then
if m.selectedAudioIndex = selIndex
else
selMenu.content.GetChild(m.selectedAudioIndex).selected = false
newSelection = selMenu.content.GetChild(selIndex)
@ -96,7 +95,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
return true
else if key = "back" or key = "up"
if m.menus[m.selectedItem].isInFocusChain() then
if m.menus[m.selectedItem].isInFocusChain()
m.buttons.setFocus(true)
m.menus[m.selectedItem].drawFocusFeedback = false
return true

View File

@ -1,4 +1,4 @@
function init()
sub init()
' backgroundUri must be set to an empty string before backgroundColor can be set
m.top.backgroundUri = ""
m.top.backgroundColor = "#000000"
@ -8,4 +8,4 @@ function init()
m.BounceAnimation = m.top.findNode("BounceAnimation")
m.BounceAnimation.control = "start" 'Start BounceAnimation
end function
end sub

View File

@ -35,19 +35,19 @@ sub updateSize()
end sub
function getData()
if m.top.itemData = invalid then
if m.top.itemData = invalid
data = CreateObject("roSGNode", "ContentNode")
return data
end if
itemData = m.top.itemData
rowSize = m.top.rowSize
' todo - Or get the old data? I can't remember...
data = CreateObject("roSGNode", "ContentNode")
' Do this to keep the ordering, AssociateArrays have no order
type_array = ["Movie", "Series", "Episode", "AlbumArtist", "Album", "Audio", "Person"]
type_array = ["Movie", "Series", "TvChannel", "Episode", "AlbumArtist", "Album", "Audio", "Person"]
content_types = {
"TvChannel": {"label": "Channels", "count": 0},
"Movie": {"label": "Movies", "count": 0},
"Series": {"label": "Shows", "count": 0},
"Episode": {"label": "Episodes", "count": 0},
@ -74,7 +74,7 @@ function getData()
return data
end function
function addRow(data, title, type_filter)
sub addRow(data, title, type_filter)
itemData = m.top.itemData
row = data.CreateChild("ContentNode")
row.title = title
@ -83,4 +83,4 @@ function addRow(data, title, type_filter)
row.appendChild(item)
end if
end for
end function
end sub

View File

@ -17,7 +17,6 @@ sub updateSize()
border = 96
m.top.translation = [border, 75 + 115]
textHeight = 80
itemWidth = (dimensions["width"] - border*2)
itemHeight = 300
@ -34,16 +33,16 @@ sub updateSize()
m.top.rowItemSpacing = [ 20, 0 ]
end sub
function setupRows()
sub setupRows()
updateSize()
objects = m.top.objects
m.top.numRows = objects.items.count()
m.top.content = setData()
end function
end sub
function setData()
data = CreateObject("roSGNode", "ContentNode")
if m.top.objects = invalid then
if m.top.objects = invalid
' Return an empty node just to return something; we'll update once we have data
return data
end if

View File

@ -8,10 +8,10 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
handled = false
if press then
if key = "play" then
if press
if key = "play"
itemToPlay = m.top.focusedChild.content.getChild(m.top.focusedChild.rowItemFocused[0]).getChild(0)
if itemToPlay <> invalid and itemToPlay.id <> "" then
if itemToPlay <> invalid and itemToPlay.id <> ""
m.top.quickPlayNode = itemToPlay
end if
handled = true

View File

@ -3,10 +3,10 @@ sub init()
m.title.text = tr("Loading...")
end sub
function itemContentChanged() as void
sub itemContentChanged()
item = m.top.itemContent
itemData = item.json
if itemData.indexNumber <> invalid then
if itemData.indexNumber <> invalid
indexNumber = itemData.indexNumber.toStr() + ". "
else
indexNumber = ""
@ -19,13 +19,13 @@ function itemContentChanged() as void
m.top.findNode("runtime").text = stri(getRuntime()).trim() + " mins"
m.top.findNode("endtime").text = tr("Ends at %1").Replace("%1", getEndTime())
end if
if itemData.communityRating <> invalid then
if itemData.communityRating <> invalid
m.top.findNode("star").visible = true
m.top.findNode("communityRating").text = str(int(itemData.communityRating*10)/10)
else
m.top.findNode("star").visible = false
end if
end function
end sub
function getRuntime() as integer
itemData = m.top.itemContent.json

View File

@ -14,7 +14,6 @@ sub init()
end sub
sub updateSize()
textHeight = 80
itemWidth = 200
itemHeight = 380 ' width * 1.5 + text
@ -32,13 +31,12 @@ sub updateSize()
end sub
function getData()
if m.top.TVSeasonData = invalid then
if m.top.TVSeasonData = invalid
data = CreateObject("roSGNode", "ContentNode")
return data
end if
seasonData = m.top.TVSeasonData
rowsize = m.top.rowSize
data = CreateObject("roSGNode", "ContentNode")
row = data.CreateChild("ContentNode")
row.title = "Seasons"

View File

@ -34,17 +34,14 @@ sub itemContentChanged()
if itemData.genres.count() > 0
setFieldText("genres", itemData.genres.join(", "))
end if
director = invalid
for each person in itemData.people
if person.type = "Director"
director = person.name
exit for
end if
end for
if itemData.taglines.count() > 0
setFieldText("tagline", itemData.taglines[0])
end if
' m.top.findNode("TVSeasonSelect").TVSeasonData = m.top.itemContent.seasons
end sub
sub setFieldText(field, value)
@ -52,9 +49,9 @@ sub setFieldText(field, value)
if node = invalid or value = invalid then return
' Handle non strings... Which _shouldn't_ happen, but hey
if type(value) = "roInt" or type(value) = "Integer" then
if type(value) = "roInt" or type(value) = "Integer"
value = str(value)
else if type(value) <> "roString" and type(value) <> "String" then
else if type(value) <> "roString" and type(value) <> "String"
value = ""
end if
@ -77,7 +74,7 @@ function getEndTime() as string
date.fromSeconds(date.asSeconds() + duration_s)
date.toLocalTime()
formatTime(date)
return formatTime(date)
end function
function getHistory() as string

View File

@ -16,7 +16,7 @@ sub itemContentChanged()
m.top.overhangTitle = itemData.name
setFieldText("releaseYear", itemData.productionYear)
setFieldText("officialRating", itemData.officialRating)
if itemData.communityRating <> invalid then
if itemData.communityRating <> invalid
m.top.findNode("star").visible = true
setFieldText("communityRating", itemData.communityRating)
' m.top.findNode("communityRating").text = str(int(itemData.communityRating*10)/10)
@ -35,17 +35,14 @@ sub itemContentChanged()
if itemData.genres.count() > 0
setFieldText("genres", itemData.genres.join(", "))
end if
director = invalid
for each person in itemData.people
if person.type = "Director"
director = person.name
exit for
end if
end for
if itemData.taglines.count() > 0
setFieldText("tagline", itemData.taglines[0])
end if
' m.top.findNode("TVSeasonSelect").TVSeasonData = m.top.itemContent.seasons
end sub
sub setFieldText(field, value)
@ -53,11 +50,11 @@ sub setFieldText(field, value)
if node = invalid or value = invalid then return
' Handle non strings... Which _shouldn't_ happen, but hey
if type(value) = "roInt" or type(value) = "Integer" then
if type(value) = "roInt" or type(value) = "Integer"
value = str(value).trim()
else if type(value) = "roFloat" or type(value) = "Float" then
else if type(value) = "roFloat" or type(value) = "Float"
value = str(value).trim()
else if type(value) <> "roString" and type(value) <> "String" then
else if type(value) <> "roString" and type(value) <> "String"
value = ""
end if
@ -80,7 +77,7 @@ function getEndTime() as string
date.fromSeconds(date.asSeconds() + duration_s)
date.toLocalTime()
formatTime(date)
return formatTime(date)
end function
function getHistory() as string

View File

@ -587,5 +587,419 @@
<source>TAB_VIEW</source>
<translation>Ansicht</translation>
</message>
<message>
<source>Connecting to Server</source>
<translation>Verbindung zum Server wird hergestellt</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>TV-Programm</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Kanäle</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Wiederholung</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>Live</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Endete</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Endete um</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Beginnt</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>Starts at</source>
<translation>Beginnt um</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Seit</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Läuft seit</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Samstag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Freitag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Donnerstag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Mittwoch</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Dienstag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Montag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Sonntag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>morgen</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>gestern</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>heute</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filter</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Sortierung</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Ansicht</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Laufzeit</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Veröffentlichungsdatum</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Wiedergaben</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Altersbeschränkung</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Wiedergegeben am</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Hinzugefügt am</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Kritikerwertung</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>IMDb-Wertung</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Name</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>%1 enthält keine Elemente</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Laden der Kanaldaten vom Server war nicht möglich</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Fehler beim Laden der Kanaldaten</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Lade Kanaldaten</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Bei der Wiedergabe dieses Elements ist ein Fehler aufgetreten.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Beim Laden der Daten dieses Elements ist ein Fehler aufgetreten.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Wiedergabefehler</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Fehler beim Laden des Inhalts</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Sign Out</source>
<translation>Ausloggen</translation>
</message>
<message>
<source>Change Server</source>
<translation>Server wechseln</translation>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>Der aufgerufene Inhalt existiert nicht auf dem Server</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Not found</source>
<translation>Nicht gefunden</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Kanäle</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Verbindung zum Server wird hergestellt</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>TV-Programm</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Wiederholung</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>Live</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Endete</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Endete um</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Beginnt</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>Starts at</source>
<translation>Beginnt um</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Seit</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Läuft seit</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Samstag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Freitag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Donnerstag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Mittwoch</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Dienstag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Montag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Sonntag</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>morgen</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>gestern</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>heute</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filter</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Sortierung</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Ansicht</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Laufzeit</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Veröffentlichungsdatum</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Wiedergaben</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Altersfreigabe</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Wiedergegeben am</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Hinzugefügt am</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Kritikerwertung</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>IMDb Bewertung</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Name</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Diese %1 enthält keine Elemente</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Laden der Kanaldaten vom Server war nicht möglich</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Fehler beim Laden der Kanaldaten</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Lade Kanaldaten</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Bei der Wiedergabe dieses Elements ist ein Fehler aufgetreten.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Beim Laden der Daten dieses Elements ist ein Fehler aufgetreten.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Wiedergabefehler</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Fehler beim Laden des Inhalts</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Sign Out</source>
<translation>Abmelden</translation>
</message>
<message>
<source>Change Server</source>
<translation>Server wechseln</translation>
</message>
</context>
</TS>

View File

@ -1,316 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.0" language="en_US" sourcelanguage="en_US">
<defaultcodec>UTF-8</defaultcodec>
<context>
<name>default</name>
<message>
<source>192.168.1.100:8096 or https://example.com/jellyfin</source>
<translation>192.168.1.100:8096 or https://example.com/jellyfin</translation>
</message>
<message>
<source>Cancel</source>
<translation>Cancel</translation>
</message>
<message>
<source>Connect to Server</source>
<translation>Connect to Server</translation>
</message>
<message>
<source>Ends at %1</source>
<translation>Ends at %1</translation>
</message>
<message>
<source>Enter Configuration</source>
<translation>Enter Configuration</translation>
</message>
<message>
<source>Favorite</source>
<translation>Favorite</translation>
</message>
<message>
<source>Loading...</source>
<translation>Loading...</translation>
</message>
<message>
<source>Login attempt failed.</source>
<translation>Login attempt failed.</translation>
</message>
<message>
<source>OK</source>
<translation>OK</translation>
</message>
<message>
<source>Options</source>
<translation>Options</translation>
</message>
<message>
<source>Play</source>
<translation>Play</translation>
</message>
<message>
<source>Please sign in</source>
<translation>Please sign in</translation>
</message>
<message>
<source>Search</source>
<translation>Search</translation>
</message>
<message>
<source>Server not found, is it online?</source>
<translation>Server not found, is it online?</translation>
</message>
<message>
<source>Shuffle</source>
<translation>Shuffle</translation>
</message>
<message>
<source>Sign In</source>
<translation>Sign In</translation>
</message>
<message>
<source>Submit</source>
<translation>Submit</translation>
</message>
<message>
<source>Watched</source>
<translation>Watched</translation>
</message>
<message>
<source>Change Server</source>
<translation>Change Server</translation>
</message>
<message>
<source>Sign Out</source>
<translation>Sign Out</translation>
</message>
<message>
<source>Profile</source>
<translation>Profile</translation>
</message>
<message>
<source>My Media</source>
<translation>My Media</translation>
</message>
<message>
<source>Continue Watching</source>
<translation>Continue Watching</translation>
</message>
<message>
<source>Next Up</source>
<translation>Next Up</translation>
</message>
<message>
<source>Latest in</source>
<translation>Latest in</translation>
</message>
<message>
<source>Home</source>
<translation>Home</translation>
</message>
<message>
<source>Enter a value...</source>
<translation>Enter a value...</translation>
</message>
<message>
<source>Sort Field</source>
<translation>Sort By</translation>
</message>
<message>
<source>Date Added</source>
<translation>Date Added</translation>
</message>
<message>
<source>Release Date</source>
<translation>Release Date</translation>
</message>
<message>
<source>Name</source>
<translation>Name</translation>
</message>
<message>
<source>Sort Order</source>
<translation>Sort Order</translation>
</message>
<message>
<source>Descending</source>
<translation>Descending</translation>
</message>
<message>
<source>Ascending</source>
<translation>Ascending</translation>
</message>
<message>
<source>Password</source>
<translation>Password</translation>
</message>
<message>
<source>Username</source>
<translation>Username</translation>
</message>
<message>
<source>Genres</source>
<translation>Genres</translation>
</message>
<message>
<source>Director</source>
<translation>Director</translation>
</message>
<message>
<source>Video</source>
<translation>Video</translation>
</message>
<message>
<source>Audio</source>
<translation>Audio</translation>
</message>
<message>
<source>Server</source>
<translation>Server</translation>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Error Retrieving Content</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<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 data for this item from the server.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<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>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Error loading Channel Data</translation>
</message>
<message>
<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>
<context>
<name></name>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>This %1 contains no items</translation>
</message>
<message>
<source>Add User</source>
<translation>Add User</translation>
</message>
</context>
</TS>

View File

@ -144,21 +144,6 @@
<translation>Enter a value...</translation>
</message>
<message>
<source>Sort Field</source>
<translation>Sort By</translation>
</message>
<message>
<source>Date Added</source>
<translation>Date Added</translation>
</message>
<message>
<source>Release Date</source>
<translation>Release Date</translation>
</message>
<message>
<source>Name</source>
<translation>Name</translation>
@ -414,6 +399,20 @@
<translation>TV Guide</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Connecting to Server</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>Not found</source>
<translation>Not found</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>The requested content does not exist on the server</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
</context>
</TS>

View File

@ -144,21 +144,6 @@
<translation>Enter a value...</translation>
</message>
<message>
<source>Sort Field</source>
<translation>Sort By</translation>
</message>
<message>
<source>Date Added</source>
<translation>Date Added</translation>
</message>
<message>
<source>Release Date</source>
<translation>Release Date</translation>
</message>
<message>
<source>Name</source>
<translation>Name</translation>
@ -414,6 +399,20 @@
<translation>TV Guide</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Connecting to Server</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>Not found</source>
<translation>Not found</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>The requested content does not exist on the server</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
</context>
</TS>

View File

@ -507,5 +507,403 @@
<translation>Se produjo un error al recuperar los datos de este elemento del servidor.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Conectando al Servidor</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guía de Televisión</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Canales</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Repetir</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>En Vivo</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Terminó en</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Terminó a las</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Empieza</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>Started</source>
<translation>Empezado</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>Empieza a las</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Empezó a las</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Sabado</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Viernes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jueves</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Miércoles</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Martes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Lunes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Domingo</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>Mañana</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>Ayer</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>Hoy</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtro</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Orden</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Vista</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Duración</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Fecha de Estreno</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Contador de Reproducciones</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Calificación de los padres</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Fecha de Reproducción</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Fecha de adición</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Puntuación de la Critica</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Puntuación IMDB</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nombre</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1 no contiene elementos</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Incapaz de cargar datos del Canal desde el Servidor</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Error al cargar datos del Canal</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Cargando datos del Canal</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Se ha encontrado un error mientras se reproducía este elemento.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Hubo un error al recuperar los datos de este elemento desde el servidor.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Error durante la Reproducción</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Error Recuperando Contenido</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>El contenido solicitado no existe en el server</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Not found</source>
<translation>No Encontrado</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Conectando al Servidor</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guía de Televisión</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Canales</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Repetir</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>En Vivo</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Finaliza a las</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Finalizó a las</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Empieza</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>Starts at</source>
<translation>Empieza a las</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Empezado</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Empezó a las</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Sábado</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Viernes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jueves</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Miércoles</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Martes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Lunes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Domingo</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>mañana</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>Ayer</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>Hoy</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtro</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Orden</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Vista</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Duración</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Fecha de Estreno</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Contador de Reproducciones</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Control de Padres</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Fecha de Reproducción</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Fecha de Inclusión</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Calificación de los Críticos</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Calificación IMDb</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nombre</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1 no contiene items</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Incapaz de cargar los datos del canal desde el server</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Error cargando datos del Canal</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Cargando datos del Canal</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Ha ocurrido un error mientras se reproducía este ítem.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Ha ocurrido un error recuperando los datos para este ítem desde el server.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Error durante la reproducción</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Error recuperando contenido</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
</context>
</TS>

View File

@ -510,5 +510,403 @@
<translation>Error recuperando Contenido</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Conectando al Servidor</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guía de Televisión</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Canales</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Repetir</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>En Vivo</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Finaliza a las</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Terminó a las</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Empieza</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>Starts at</source>
<translation>Empieza a las</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Empezado</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Empezó a las</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Sabado</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Viernes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jueves</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Miércoles</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Martes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Lunes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Domingo</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>Mañana</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>Ayer</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>Hoy</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtro</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Orden</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Vista</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Duración</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Fecha de Estreno</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Contador de Reproducciones</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Calificación de los padres</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Fecha de Reproducción</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Fecha de adición</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Puntuación de la Critica</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Puntuación IMDB</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nombre</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1 no contiene elementos</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Incapaz de cargar datos del Canal desde el Servidor</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Error al cargar datos del Canal</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Cargando datos del Canal</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Se ha encontrado un error mientras se reproducía este elemento.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Hubo un error al recuperar los datos de este elemento desde el servidor.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Error durante la Reproducción</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Error Recuperando Contenido</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>El contenido solicitado no existe en el server</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Not found</source>
<translation>No Encontrado</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Conectando al Servidor</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guía de Televisión</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Canales</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Repetir</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>En Vivo</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Finaliza a las</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Finalizó a las</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Empieza</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>Starts at</source>
<translation>Empieza a las</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Empezado</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Empezó a las</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Sábado</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Viernes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jueves</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Miércoles</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Martes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Lunes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Domingo</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>Mañana</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>Ayer</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>Hoy</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtro</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Orden</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Vista</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Duración</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Fecha de Estreno</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Contador de Reproducciones</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Control de Padres</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Fecha de Reproducción</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Fecha de Inclusión</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Calificación de los Críticos</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Calificación IMDb</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nombre</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1 no contiene items</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Incapaz de cargar los datos del canal desde el server</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Error cargando datos del Canal</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Cargando datos del Canal</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Ha ocurrido un error mientras se reproducía este ítem.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Ha ocurrido un error recuperando los datos para este ítem desde el server.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Error durante la reproducción</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Error recuperando contenido</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
</context>
</TS>

View File

@ -602,5 +602,217 @@
<source>NO_ITEMS</source>
<translation>Este %1 no contiene elementos</translation>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>El contenido no existe en el servidor</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Not found</source>
<translation>No encontrado</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Conectando al servidor</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guía TV</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Canales</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Repetido</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>Directo</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Finaliza a las</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Terminó a las</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Inicia el</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>Starts at</source>
<translation>Inicia a las</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Iniciado</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Iniciado a las</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Sábado</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Viernes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jueves</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Miércoles</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Martes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Lunes</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Domingo</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>mañana</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>ayer</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>hoy</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtro</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Ordenar</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Vista</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Tiempo de reproducción</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nombre</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Fecha de lanzamiento</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Número de reproducciones</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Clasificación parental</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Fecha de reproducción</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Fecha añadido</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Calificación de la crítica</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Calificación de IMDb</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1 no contiene elementos</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>No es posible cargar los datos del canal desde el servidor</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Error al cargar la información del canal</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Cargando información del canal</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Se encontró un error al reproducir este elemento.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Hubo un error extrayendo los datos de este objeto del servidor.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Error Durante la Reproducción</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Error recuperando contenido</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Sign Out</source>
<translation>Cerrar sesión</translation>
</message>
<message>
<source>Change Server</source>
<translation>Cambiar servidor</translation>
</message>
</context>
</TS>

View File

@ -534,5 +534,264 @@
<translation>Relancer</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Chaînes</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Samedi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Vendredi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jeudi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Mercredi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Mardi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Une erreur s&apos;est produite lors de la lecture de cet élément.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Une erreur s&apos;est produite lors de la récupération des informations pour cet élément sur le serveur.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Erreur pendant la lecture</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Erreur lors de la récupération du contenu</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Une erreur s&apos;est produite lors de la récupération des informations pour cet élément sur le serveur.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Erreur lors de la récupération du contenu</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>Le contenu demandé n&apos;existe pas sur le serveur</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Not found</source>
<translation>Non trouvé</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Connexion au serveur</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guide des chaînes</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Chaînes</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Rediffusion</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>En direct</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Terminé à</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Terminé à</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Débutera</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>Starts at</source>
<translation>Débute à</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Débuté</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Débuté à</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Samedi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Vendredi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Jeudi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Mercredi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Mardi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Lundi</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Dimanche</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>demain</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>hier</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Nombre de lectures</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Classification parentale</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Date de lecture</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Notations critiques</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>%1 ne contient aucun élément</translation>
</message>
<message>
<source>Error During Playback</source>
<translation>Erreur pendant la lecture</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>today</source>
<translation>aujourd&apos;hui</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtrer</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Trier</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Vue</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Durée</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Date de sortie</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Date d&apos;ajout</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Notation IMDb</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nom</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Impossible de charger les données de la chaîne depuis le serveur</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Erreur lors du chargement des données de la chaîne</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Chargement des données de la chaîne</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Une erreur s&apos;est produite durant la lecture de cet élément</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Une erreur s&apos;est produite durant la lecture de cet élément.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
</context>
</TS>

View File

@ -118,7 +118,7 @@
</message>
<message>
<source>Sort Field</source>
<translation>Rendezés a következő szerint:</translation>
<translation>Rendezés a következő szerint</translation>
</message>
<message>
<source>Date Added</source>
@ -453,5 +453,408 @@
<source>OFFICIAL_RATING</source>
<translation>Szülői értékelés</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Csatornaadatok betöltése</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Hiba történt az elem lejátszása közben</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Hiba történt az elem(ek) adatainak lekérése során a szerverről.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Hiba történt a lejátszás közben</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Hiba a tartalom beolvasása közben</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Csatlakozás a szerverhez</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Műsorújság</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Csatornák</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Ismétlés</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>É</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Vége volt</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Vége lett</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Kezdődik</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>Starts at</source>
<translation>-kor kezdődik majd</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Kezdődött</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Kezdés</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Szombat</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Péntek</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Csütörtök</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Szerda</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Kedd</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Hétfő</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Vasárnap</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>holnap</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>tegnap</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>ma</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Szűrő</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Rendezés</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Nézet</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Futásidő</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Megjelenés dátuma</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Lejátszások száma</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Korhatár</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Lejátszás dátuma</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Hozzáadás dátuma</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Kritikusok értékelése</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>IMDB Értékelés</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Név</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Ez a(z) %1 nem tartalmaz elemeket</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Nem lehet betölteni a csatornaadatokat a szerverről</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Hiba történt a csatorna adatainak betöltésekor</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Hiba történt az elem lejátszása közben.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>The requested content does not exist on the server</source>
<translation>A kért tartalom nem létezik a szerveren</translation>
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Not found</source>
<translation>Nem található</translation>
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
</message>
<message>
<source>Connecting to Server</source>
<translation>Csatlakozás a szerverhez</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Műsorújság</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Csatornák</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Ismétlés</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>É</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Vége volt</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Vége lett</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Kezdődni fog</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>Starts at</source>
<translation>Kezdődik majd</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Kezdődött</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Ekkor kezdődött</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Szombat</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Péntek</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Csütörtök</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Szerda</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Kedd</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Hétfő</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Vasárnap</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>holnap</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>tegnap</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>ma</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Szűrő</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Rendezés</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Nézet</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Futásidő</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Megjelenés dátuma</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Lejátszások száma</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Korhatár</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Lejátszás dátuma</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Hozzáadva</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Kritikusok értékelése</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>IMDb Értékelés</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Név</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Ez a(z) %1 nem tartalmaz elemeket</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Nem lehet betölteni a csatornaadatokat a szerverről</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Hiba történt a csatornaadatok betöltésekor</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Csatornaadatok betöltése</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Hiba történt az elem lejátszása közben.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Hiba történt az elem(ek) betöltése során a szerverről.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Hiba történt a lejátszás közben</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Hiba a tartalom beolvasása közben</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
</context>
</TS>

View File

@ -582,5 +582,208 @@
<source>Change Server</source>
<translation>Mudar Servidor</translation>
</message>
<message>
<source>Connecting to Server</source>
<translation>Conectando no servidor</translation>
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
</message>
<message>
<source>TV Guide</source>
<translation>Guia de TV</translation>
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
</message>
<message>
<source>Channels</source>
<translation>Canais</translation>
<extracomment>Menu option for showing Live TV Channel List</extracomment>
</message>
<message>
<source>Repeat</source>
<translation>Repetir</translation>
<extracomment>If TV Shows has previously been broadcasted</extracomment>
</message>
<message>
<source>Live</source>
<translation>Ao Vivo</translation>
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
</message>
<message>
<source>Ends at</source>
<translation>Termina</translation>
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Ended at</source>
<translation>Termina às</translation>
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
</message>
<message>
<source>Starts</source>
<translation>Começa</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>Starts at</source>
<translation>Começa às</translation>
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
</message>
<message>
<source>Started</source>
<translation>Começou</translation>
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
</message>
<message>
<source>Started at</source>
<translation>Começou às</translation>
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
</message>
<message>
<source>Saturday</source>
<translation>Sábado</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Friday</source>
<translation>Sexta feira</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Thursday</source>
<translation>Quinta feira</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Wednesday</source>
<translation>Quarta feira</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Tuesday</source>
<translation>Terça feira</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Monday</source>
<translation>Segunda feira</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>Sunday</source>
<translation>Domingo</translation>
<extracomment>Day of Week</extracomment>
</message>
<message>
<source>tomorrow</source>
<translation>amanhã</translation>
<extracomment>Next day</extracomment>
</message>
<message>
<source>yesterday</source>
<translation>ontem</translation>
<extracomment>Previous day</extracomment>
</message>
<message>
<source>today</source>
<translation>hoje</translation>
<extracomment>Current day</extracomment>
</message>
<message>
<comment>Title of Tab for options to filter library content</comment>
<source>TAB_FILTER</source>
<translation>Filtro</translation>
</message>
<message>
<comment>Title of Tab for options to sort library content</comment>
<source>TAB_SORT</source>
<translation>Ordenar</translation>
</message>
<message>
<comment>Title of Tab for switching &quot;views&quot; when looking at a library</comment>
<source>TAB_VIEW</source>
<translation>Visualização</translation>
</message>
<message>
<source>RUNTIME</source>
<translation>Tempo de Execução</translation>
</message>
<message>
<source>RELEASE_DATE</source>
<translation>Data de Lançamento</translation>
</message>
<message>
<source>PLAY_COUNT</source>
<translation>Número de Execução</translation>
</message>
<message>
<source>OFFICIAL_RATING</source>
<translation>Avaliação Parental</translation>
</message>
<message>
<source>DATE_PLAYED</source>
<translation>Data da Execução</translation>
</message>
<message>
<source>DATE_ADDED</source>
<translation>Data de Adição</translation>
</message>
<message>
<source>CRITIC_RATING</source>
<translation>Avaliação da crítica</translation>
</message>
<message>
<source>IMDB_RATING</source>
<translation>Classificação IMDb</translation>
</message>
<message>
<comment>Name or Title field of media item</comment>
<source>TITLE</source>
<translation>Nome</translation>
</message>
<message>
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
<source>NO_ITEMS</source>
<translation>Este %1
não contém itens</translation>
</message>
<message>
<source>Unable to load Channel Data from the server</source>
<translation>Incapaz de carregar os dados do canal do servidor</translation>
</message>
<message>
<source>Error loading Channel Data</source>
<translation>Erro ao carregar dados do canal</translation>
</message>
<message>
<source>Loading Channel Data</source>
<translation>Carregando dados do canal</translation>
</message>
<message>
<source>An error was encountered while playing this item.</source>
<translation>Foi encontrado um erro ao reproduzir este item.</translation>
<extracomment>Dialog detail when error occurs during playback</extracomment>
</message>
<message>
<source>There was an error retrieving the data for this item from the server.</source>
<translation>Ocorreu um erro ao recuperar os dados para este item do servidor.</translation>
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
</message>
<message>
<source>Error During Playback</source>
<translation>Erro durante a reprodução</translation>
<extracomment>Dialog title when error occurs during playback</extracomment>
</message>
<message>
<source>Error Retrieving Content</source>
<translation>Erro ao recuperar conteúdo</translation>
<extracomment>Dialog title when unable to load Content from Server</extracomment>
</message>
<message>
<source>Sign Out</source>
<translation>Sair</translation>
</message>
<message>
<source>Change Server</source>
<translation>Trocar Servidor</translation>
</message>
</context>
</TS>

View File

@ -1,8 +1,8 @@
## Channel Details
title=Jellyfin
major_version=1
minor_version=3
build_version=5
minor_version=4
build_version=9
### Main Menu Icons / Channel Poster Artwork
mm_icon_focus_fhd=pkg:/images/channel-poster_fhd.png

468
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "jellyfin-roku",
"version": "1.4.0",
"version": "1.4.9",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -10,6 +10,377 @@
"integrity": "sha512-2ox6EUL+UTtccTbD4dbVjZK3QHa0PHCqpoKMF8lZz9ayzzEP3iVPF8KZR6hOi6bxsIcbGXVjqmtCVkpC4P9SrA==",
"dev": true
},
"@rokucommunity/bslint": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@rokucommunity/bslint/-/bslint-0.4.0.tgz",
"integrity": "sha512-txIWxeSyoMlBHCV8ZFnyndCtZa7xFdPkCE+/5ZHrt+hw7jeQj8Y3XhCm4o6x1JApQRp/tT/olSwkwIOe29gsKQ==",
"dev": true,
"requires": {
"brighterscript": "^0.39.1",
"fs-extra": "^10.0.0",
"jsonc-parser": "^2.3.0",
"minimatch": "^3.0.4",
"yargs": "^15.4.1"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
}
},
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"brighterscript": {
"version": "0.39.3",
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.39.3.tgz",
"integrity": "sha512-wKF9Nyv78puQf8j0dFe3RMv2+PiHf4CLkxwhM6QrozcXJYtOe5vxx6EuFSjY6bz3dri15yiykxCmHKsJ7K63Tg==",
"dev": true,
"requires": {
"@rokucommunity/bslib": "^0.1.1",
"@xml-tools/parser": "^1.0.7",
"array-flat-polyfill": "^1.0.1",
"chalk": "^2.4.2",
"chevrotain": "^7.0.1",
"chokidar": "^3.5.1",
"clear": "^0.1.0",
"cross-platform-clear-console": "^2.3.0",
"debounce-promise": "^3.1.0",
"eventemitter3": "^4.0.0",
"file-url": "^3.0.0",
"fs-extra": "^7.0.1",
"glob": "^7.1.6",
"jsonc-parser": "^2.3.0",
"long": "^3.2.0",
"luxon": "^1.8.3",
"minimatch": "^3.0.4",
"moment": "^2.23.0",
"p-settle": "^2.1.0",
"parse-ms": "^2.1.0",
"roku-deploy": "^3.4.1",
"serialize-error": "^7.0.1",
"source-map": "^0.7.3",
"vscode-languageserver": "7.0.0",
"vscode-languageserver-protocol": "3.16.0",
"vscode-languageserver-textdocument": "^1.0.1",
"vscode-uri": "^2.1.1",
"xml2js": "^0.4.19",
"yargs": "^16.2.0"
},
"dependencies": {
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
"dev": true,
"requires": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^20.2.2"
}
}
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"chokidar": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
"dev": true,
"requires": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"fsevents": "~2.3.1",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.5.0"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"fs-extra": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"dependencies": {
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true
}
}
},
"glob": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
"integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"readdirp": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
}
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
}
}
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"dev": true,
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"dependencies": {
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"dev": true,
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
}
}
},
"@xml-tools/parser": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz",
@ -753,6 +1124,12 @@
}
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@ -1857,6 +2234,12 @@
}
}
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
@ -2088,6 +2471,16 @@
"repeat-string": "^1.5.2"
}
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -2663,10 +3056,19 @@
"immediate": "~3.0.5"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": {
"p-locate": "^4.1.0"
}
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lodash.toarray": {
@ -3004,6 +3406,32 @@
"p-try": "^1.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": {
"p-limit": "^2.2.0"
},
"dependencies": {
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
}
}
},
"p-reflect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-reflect/-/p-reflect-1.0.0.tgz",
@ -3067,6 +3495,12 @@
"util": "^0.10.3"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -3310,6 +3744,12 @@
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@ -3412,7 +3852,7 @@
"es2015": "0.0.0",
"fs-extra": "^5.0.0",
"glob-all": "^3.1.0",
"lodash": "^4.17.19",
"lodash": "^4.17.21",
"replace-ext": "^1.0.0",
"request": "^2.88.0",
"request-promise": "^4.2.4",
@ -3456,6 +3896,12 @@
"type-fest": "^0.13.1"
}
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
@ -4054,6 +4500,12 @@
"integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==",
"dev": true
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@ -4152,9 +4604,9 @@
}
},
"yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"version": "20.2.7",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz",
"integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==",
"dev": true
}
}

View File

@ -1,18 +1,20 @@
{
"name": "jellyfin-roku",
"version": "1.4.0",
"version": "1.4.9",
"description": "Roku app for Jellyfin media server",
"main": "index.js",
"directories": {
"test": "tests"
},
"devDependencies": {
"@rokucommunity/bslint": "^0.4.0",
"brighterscript": "^0.39.4",
"rooibos-cli": "^1.0.1"
},
"scripts": {
"validate": "npx bsc --copy-to-staging=false --create-package=false",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "bslint"
},
"repository": {
"type": "git",

View File

@ -1,8 +1,8 @@
sub Main()
sub Main (args as Dynamic) as Void
' If the Rooibos files are included in deployment, run tests
'bs:disable-next-line
if (type(Rooibos__Init) = "Function") then Rooibos__Init()
if type(Rooibos__Init) = "Function" then Rooibos__Init()
' The main function that runs when the application is launched.
m.screen = CreateObject("roSGScreen")
@ -22,8 +22,6 @@ sub Main()
m.overhang = CreateObject("roSGNode", "JFOverhang")
m.scene.insertChild(m.overhang, 0)
m.page_size = 48
app_start:
m.overhang.title = ""
' First thing to do is validate the ability to use the API
@ -52,20 +50,41 @@ sub Main()
m.device.setMessagePort(m.port)
m.device.EnableScreensaverExitedEvent(true)
' Check if we were sent content to play with the startup command (Deep Link)
if (args.mediaType <> invalid) and (args.contentId <> invalid)
video = CreateVideoPlayerGroup(args.contentId)
if video <> invalid
if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
group = video
m.scene.appendChild(group)
group.setFocus(true)
group.control = "play"
ReportPlayback(group, "start")
m.overhang.visible = false
else
dialog = createObject("roSGNode", "Dialog")
dialog.id = "OKDialog"
dialog.title = tr("Not found")
dialog.message = tr("The requested content does not exist on the server")
dialog.buttons = [tr("OK")]
m.scene.dialog = dialog
m.scene.dialog.observeField("buttonSelected", m.port)
end if
end if
' This is the core logic loop. Mostly for transitioning between scenes
' This now only references m. fields so could be placed anywhere, in theory
' "group" is always "whats on the screen"
' m.scene's children is the "previous view" stack
while(true)
while true
msg = wait(0, m.port)
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed() then
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed()
print "CLOSING SCREEN"
return
else if isNodeEvent(msg, "buttonSelected")
' Dialog Button Selected - If not handled more locally, just close the dialog
dialog = msg.getRoSGNode()
dialog.unobserveField("buttonSelected")
dialog.close = true
else if isNodeEvent(msg, "backPressed")
n = m.scene.getChildCount() - 1
if msg.getRoSGNode().focusedChild <> invalid and msg.getRoSGNode().focusedChild.isSubtype("JFVideo")
@ -91,9 +110,9 @@ sub Main()
reportingNode = msg.getRoSGNode()
itemNode = reportingNode.quickPlayNode
if itemNode = invalid or itemNode.id = "" then return
if itemNode.type = "Episode" or itemNode.type = "Movie" or itemNode.type = "Video" then
if itemNode.type = "Episode" or itemNode.type = "Movie" or itemNode.type = "Video"
video = CreateVideoPlayerGroup(itemNode.id)
if video <> invalid then
if video <> invalid
group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@ -108,7 +127,7 @@ sub Main()
else if isNodeEvent(msg, "selectedItem")
' 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"
if selectedItem.type = "CollectionFolder" OR selectedItem.type = "UserView" OR selectedItem.type = "Folder" OR selectedItem.type = "Channel" OR selectedItem.type = "Boxset"
group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@ -116,21 +135,12 @@ sub Main()
group = CreateItemGrid(selectedItem)
group.overhangTitle = selectedItem.title
m.scene.appendChild(group)
else if selectedItem.type = "Folder"
group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
m.overhang.title = selectedItem.title
group = CreateItemGrid(selectedItem)
group.overhangTitle = selectedItem.title
m.scene.appendChild(group)
else if selectedItem.type = "Episode" then
else if selectedItem.type = "Episode"
' play episode
' todo: create an episode page to link here
video_id = selectedItem.id
video = CreateVideoPlayerGroup(video_id)
if video <> invalid then
if video <> invalid
group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@ -141,7 +151,7 @@ sub Main()
ReportPlayback(group, "start")
m.overhang.visible = false
end if
else if selectedItem.type = "Series" then
else if selectedItem.type = "Series"
group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@ -152,7 +162,7 @@ sub Main()
group = CreateSeriesDetailsGroup(selectedItem.json)
group.overhangTitle = selectedItem.title
m.scene.appendChild(group)
else if selectedItem.type = "Movie" then
else if selectedItem.type = "Movie"
' open movie detail page
group.lastFocus = group.focusedChild
group.setFocus(false)
@ -165,7 +175,7 @@ sub Main()
group.overhangTitle = selectedItem.title
m.scene.appendChild(group)
else if selectedItem.type = "TvChannel" or selectedItem.type = "Video" then
else if selectedItem.type = "TvChannel" or selectedItem.type = "Video"
' play channel feed
video_id = selectedItem.id
@ -177,7 +187,7 @@ sub Main()
video = CreateVideoPlayerGroup(video_id)
dialog.close = true
if video <> invalid then
if video <> invalid
if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@ -189,6 +199,7 @@ sub Main()
m.overhang.visible = false
else
dialog = createObject("roSGNode", "Dialog")
dialog.id = "OKDialog"
dialog.title = tr("Error loading Channel Data")
dialog.message = tr("Unable to load Channel Data from the server")
dialog.buttons = [tr("OK")]
@ -198,7 +209,6 @@ sub Main()
else
' TODO - switch on more node types
message_dialog("This type is not yet supported: " + selectedItem.type + ".")
selectedItem = invalid
end if
else if isNodeEvent(msg, "movieSelected")
' If you select a movie from ANYWHERE, follow this flow
@ -249,7 +259,7 @@ sub Main()
node = getMsgPicker(msg, "picker")
video_id = node.id
video = CreateVideoPlayerGroup(video_id)
if video <> invalid then
if video <> invalid
group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
@ -267,21 +277,13 @@ sub Main()
options.visible = true
options.setFocus(true)
dialog = createObject("roSGNode", "ProgressDialog")
dialog.title = tr("Loading Search Data")
m.scene.dialog = dialog
results = SearchMedia(query)
dialog.close = true
options.itemData = results
options.query = query
else if isNodeEvent(msg, "pageSelected")
group.pageNumber = msg.getRoSGNode().pageSelected
collectionType = group.subType()
if collectionType = "Collections"
CollectionLister(group, m.page_size)
else if collectionType = "TVShows"
SeriesLister(group, m.page_size)
else if collectionType = "Channels"
ChannelLister(group, m.page_size)
end if
' TODO - abstract away the "picker" node
group.findNode("picker").setFocus(true)
else if isNodeEvent(msg, "itemSelected")
' Search item selected
node = getMsgPicker(msg)
@ -291,7 +293,7 @@ sub Main()
' TODO - swap this based on target.mediatype
' types: [ Series (Show), Episode, Movie, Audio, Person, Studio, MusicArtist ]
if node.type = "Series" then
if node.type = "Series"
group = CreateSeriesDetailsGroup(node)
else
group = CreateMovieDetailsGroup(node)
@ -302,7 +304,7 @@ sub Main()
else if isNodeEvent(msg, "buttonSelected")
' If a button is selected, we have some determining to do
btn = getButton(msg)
if btn.id = "play-button"
if btn <> invalid and btn.id = "play-button"
' Check is a specific Audio Stream was selected
audio_stream_idx = 1
if group.selectedAudioStreamIndex <> invalid
@ -313,7 +315,7 @@ sub Main()
' This is currently page layout Group, button Group, then button
video_id = group.id
video = CreateVideoPlayerGroup(video_id, audio_stream_idx)
if video <> invalid then
if video <> invalid
group.lastFocus = group.focusedChild.focusedChild.focusedChild
group.setFocus(false)
group.visible = false
@ -324,7 +326,7 @@ sub Main()
ReportPlayback(group, "start")
m.overhang.visible = false
end if
else if btn.id = "watched-button"
else if btn <> invalid and btn.id = "watched-button"
movie = group.itemContent
if movie.watched
UnmarkItemWatched(movie.id)
@ -332,7 +334,7 @@ sub Main()
MarkItemWatched(movie.id)
end if
movie.watched = not movie.watched
else if btn.id = "favorite-button"
else if btn <> invalid and btn.id = "favorite-button"
movie = group.itemContent
if movie.favorite
UnmarkItemFavorite(movie.id)
@ -340,6 +342,13 @@ sub Main()
MarkItemFavorite(movie.id)
end if
movie.favorite = not movie.favorite
else
' If there are no other button matches, check if this is a simple "OK" Dialog & Close if so
dialog = msg.getRoSGNode()
if dialog.id = "OKDialog"
dialog.unobserveField("buttonSelected")
dialog.close = true
end if
end if
else if isNodeEvent(msg, "optionSelected")
button = msg.getRoSGNode()
@ -374,9 +383,9 @@ sub Main()
end if
else if isNodeEvent(msg, "selectSubtitlePressed")
node = m.scene.focusedChild
if node.isSubType("JFVideo") then
if node.isSubType("JFVideo")
trackSelected = selectSubtitleTrack(node.Subtitles, node.SelectedSubtitle)
if trackSelected <> invalid and trackSelected <> node.SelectedSubtitle then
if trackSelected <> invalid and trackSelected <> -2
changeSubtitleDuringPlayback(trackSelected)
end if
end if
@ -384,37 +393,22 @@ sub Main()
ReportPlayback(group, "update")
else if isNodeEvent(msg, "state")
node = msg.getRoSGNode()
if node.state = "finished" then
if node.state = "finished"
stopPlayback()
if node.showID = invalid then
if node.showID = invalid
RemoveCurrentGroup()
else
nextEpisode =autoPlayNextEpisode(node.id, node.showID)
if nextEpisode <> invalid then group = nextEpisode
end if
else if node.state = "playing" or node.state = "paused" then
else if node.state = "playing" or node.state = "paused"
ReportPlayback(group, "update")
end if
else if type(msg) = "roDeviceInfoEvent" then
else if type(msg) = "roDeviceInfoEvent"
event = msg.GetInfo()
if event.appFocused <> invalid then
child = m.scene.focusedChild
if child <> invalid and child.isSubType("JFVideo") then
child.systemOverlay = not event.appFocused
if event.AppFocused = true then
systemOverlayClosed()
end if
end if
else if event.Mute <> invalid then
m.mute = event.Mute
child = m.scene.focusedChild
if child <> invalid and child.isSubType("JFVideo") and areSubtitlesDisplayed() and child.systemOverlay = false then
'Event will be called on caption change which includes the current mute status, but we do not want to call until the overlay is closed
reviewSubtitleDisplay()
end if
else if event.exitedScreensaver = true then
if event.exitedScreensaver = true
m.overhang.callFunc("resetTime")
if group.subtype() = "Home" then
if group.subtype() = "Home"
currentTime = CreateObject("roDateTime").AsSeconds()
group.timeLastRefresh = currentTime
group.callFunc("refresh")
@ -424,6 +418,32 @@ sub Main()
print "Unhandled roDeviceInfoEvent:"
print msg.GetInfo()
end if
else if type(msg) = "roInputEvent"
if msg.IsInput()
info = msg.GetInfo()
if info.DoesExist("mediatype") and info.DoesExist("contentid")
video = CreateVideoPlayerGroup(info.contentId)
if video <> invalid
if group.lastFocus = invalid then group.lastFocus = group.focusedChild
group.setFocus(false)
group.visible = false
group = video
m.scene.appendChild(group)
group.setFocus(true)
group.control = "play"
ReportPlayback(group, "start")
m.overhang.visible = false
else
dialog = createObject("roSGNode", "Dialog")
dialog.id = "OKDialog"
dialog.title = tr("Not found")
dialog.message = tr("The requested content does not exist on the server")
dialog.buttons = [tr("OK")]
m.scene.dialog = dialog
m.scene.dialog.observeField("buttonSelected", m.port)
end if
end if
end if
else
print "Unhandled " type(msg)
print msg
@ -433,46 +453,59 @@ sub Main()
end sub
function LoginFlow(startOver = false as boolean)
if m.scene <> invalid then
if m.scene <> invalid
m.scene.unobserveField("backPressed")
end if
'Collect Jellyfin server and user information
start_login:
if get_setting("server") = invalid or ServerInfo() = invalid or startOver = true then
if get_setting("server") = invalid then startOver = true
invalidServer = true
if not startOver
' Show Connecting to Server spinner
dialog = createObject("roSGNode", "ProgressDialog")
dialog.title = tr("Connecting to Server")
m.scene.dialog = dialog
invalidServer = ServerInfo().Error
dialog.close = true
end if
if startOver or invalidServer
print "Get server details"
SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting
serverSelection = CreateServerGroup()
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if serverSelection = "backPressed" then
if serverSelection = "backPressed"
print "backPressed"
wipe_groups()
return false
end if
end if
if get_setting("active_user") = invalid then
if get_setting("active_user") = invalid
SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting
publicUsers = GetPublicUsers()
if publicUsers.count() then
if publicUsers.count()
publicUsersNodes = []
for each item in publicUsers
user = CreateObject("roSGNode", "PublicUserData")
user.id = item.Id
user.name = item.Name
if item.PrimaryImageTag <> invalid then
if item.PrimaryImageTag <> invalid
user.ImageURL = UserImageURL(user.id, { "tag": item.PrimaryImageTag })
end if
publicUsersNodes.push(user)
end for
userSelected = CreateUserSelectGroup(publicUsersNodes)
m.scene.focusedChild.visible = false
if userSelected = "backPressed" then
if userSelected = "backPressed"
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
return LoginFlow(true)
else
'Try to login without password. If the token is valid, we're done
get_token(userSelected, "")
if get_setting("active_user") <> invalid then
if get_setting("active_user") <> invalid
m.user = AboutMe()
LoadUserPreferences()
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
@ -484,7 +517,7 @@ function LoginFlow(startOver = false as boolean)
end if
passwordEntry = CreateSigninGroup(userSelected)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if passwordEntry = "backPressed" then
if passwordEntry = "backPressed"
m.scene.focusedChild.visible = false
return LoginFlow(true)
end if
@ -514,12 +547,12 @@ sub RunScreenSaver()
m.port = createObject("roMessagePort")
screen.setMessagePort(m.port)
scene = screen.createScene("Screensaver")
screen.createScene("Screensaver")
screen.Show()
while(true)
while true
msg = wait(8000, m.port)
if (msg <> invalid)
if msg <> invalid
msgType = type(msg)
if msgType = "roSGScreenEvent"
if msg.isScreenClosed() then return
@ -531,7 +564,7 @@ end sub
sub wipe_groups()
' The 1 remaining child should be the overhang
while(m.scene.getChildCount() > 1)
while m.scene.getChildCount() > 1
m.scene.removeChildIndex(1)
end while
end sub
@ -545,8 +578,8 @@ sub RemoveCurrentGroup()
group = m.scene.getChild(n - 1)
m.overhang.title = group.overhangTitle
m.overhang.showOptions = group.optionsAvailable
if group.optionsAvailable <> prevOptionsAvailable then
if group.optionsAvailable = false then
if group.optionsAvailable <> prevOptionsAvailable
if group.optionsAvailable = false
m.scene.unobserveField("optionsPressed")
else
m.scene.observeField("optionsPressed", m.port)
@ -558,9 +591,9 @@ sub RemoveCurrentGroup()
else
group.setFocus(true)
end if
if group.subtype() = "Home" then
if group.subtype() = "Home"
currentTime = CreateObject("roDateTime").AsSeconds()
if group.timeLastRefresh = invalid or (currentTime - group.timeLastRefresh) > 20 then
if group.timeLastRefresh = invalid or (currentTime - group.timeLastRefresh) > 20
group.timeLastRefresh = currentTime
group.callFunc("refresh")
end if
@ -570,7 +603,7 @@ end sub
' Roku Performance monitoring
sub SendPerformanceBeacon(signalName as string)
if m.global.app_loaded = false then
if m.global.app_loaded = false
m.scene.signalBeacon(signalName)
end if
end sub

View File

@ -12,7 +12,7 @@ function CreateServerGroup()
button.observeField("buttonSelected", port)
screen.observeField("backPressed", port)
while(true)
while true
msg = wait(0, port)
print type(msg), msg
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed()
@ -29,12 +29,33 @@ function CreateServerGroup()
set_setting("password", "")
end if
set_setting("server", serverUrl)
if ServerInfo() = invalid then
' Show Connecting to Server spinner
dialog = createObject("roSGNode", "ProgressDialog")
dialog.title = tr("Connecting to Server")
m.scene.dialog = dialog
serverInfoResult = ServerInfo()
dialog.close = true
if serverInfoResult = invalid
' Maybe don't unset setting, but offer as a prompt
' Server not found, is it online? New values / Retry
print "Server not found, is it online? New values / Retry"
screen.findNode("alert").text = tr("Server not found, is it online?")
SignOut()
else if serverInfoResult.Error <> invalid and serverInfoResult.Error
' If server redirected received, update the URL
if serverInfoResult.UpdatedUrl <> invalid
server_hostname.value = serverInfoResult.UpdatedUrl
end if
' Display Error Message to user
message = tr("Error: ")
if serverInfoResult.ErrorCode <> invalid
message = message + "[" + serverInfoResult.ErrorCode.toStr() + "] "
end if
screen.findNode("alert").text = message + tr(serverInfoResult.ErrorMessage)
SignOut()
else
screen.visible = false
return "true"
@ -48,7 +69,7 @@ function CreateServerGroup()
end function
function CreateUserSelectGroup(users = [])
if users.count() = 0 then
if users.count() = 0
return ""
end if
group = CreateObject("roSGNode", "UserSelect")
@ -59,7 +80,7 @@ function CreateUserSelectGroup(users = [])
group.findNode("userRow").observeField("userSelected", port)
group.findNode("alternateOptions").observeField("itemSelected", port)
group.observeField("backPressed", port)
while(true)
while true
msg = wait(0, port)
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed()
group.visible = false
@ -69,7 +90,7 @@ function CreateUserSelectGroup(users = [])
else if type(msg) = "roSGNodeEvent" and msg.getField() = "userSelected"
return msg.GetData()
else if type(msg) = "roSGNodeEvent" and msg.getField() = "itemSelected"
if msg.getData() = 0 then
if msg.getData() = 0
return ""
end if
end if
@ -77,6 +98,7 @@ function CreateUserSelectGroup(users = [])
' Just hide it when done, in case we need to come back
group.visible = false
return ""
end function
function CreateSigninGroup(user = "")
@ -117,7 +139,7 @@ function CreateSigninGroup(user = "")
group.observeField("backPressed", port)
while(true)
while true
msg = wait(0, port)
if type(msg) = "roSGScreenEvent" and msg.isScreenClosed()
group.visible = false
@ -144,6 +166,7 @@ function CreateSigninGroup(user = "")
' Just hide it when done, in case we need to come back
group.visible = false
return ""
end function
function CreateHomeGroup()
@ -244,26 +267,16 @@ function CreateSearchPage()
return group
end function
function CreateSidePanel(buttons, options)
sub CreateSidePanel(buttons, options)
group = CreateObject("roSGNode", "OptionsSlider")
group.buttons = buttons
group.options = options
end function
function CreatePaginator()
group = CreateObject("roSGNode", "Pager")
group.id = "paginator"
group.observeField("pageSelected", m.port)
return group
end function
end sub
function CreateVideoPlayerGroup(video_id, audio_stream_idx = 1)
' Video is Playing
video = VideoPlayer(video_id, audio_stream_idx)
if video = invalid return invalid
if video = invalid then return invalid
timer = video.findNode("playbackTimer")
video.observeField("backPressed", m.port)
@ -274,46 +287,3 @@ function CreateVideoPlayerGroup(video_id, audio_stream_idx = 1)
return video
end function
function SeriesLister(group, page_size)
sort_order = get_user_setting("series_sort_order", "Ascending")
sort_field = get_user_setting("series_sort_field", "SortName")
item_list = ItemList(group.id, { "limit": page_size,
"StartIndex": page_size * (group.pageNumber - 1),
"SortBy": sort_field,
"SortOrder": sort_order,
"IncludeItemTypes": "Series"
})
group.objects = item_list
p = group.findNode("paginator")
p.maxPages = div_ceiling(group.objects.TotalRecordCount, page_size)
end function
function CollectionLister(group, page_size)
sort_order = get_user_setting("boxsets_sort_order", "Ascending")
sort_field = get_user_setting("boxsets_sort_field", "SortName")
item_list = ItemList(group.id, { "limit": page_size,
"StartIndex": page_size * (group.pageNumber - 1),
"SortBy": sort_field,
"SortOrder": sort_order,
})
group.objects = item_list
p = group.findNode("paginator")
p.maxPages = div_ceiling(group.objects.TotalRecordCount, page_size)
end function
function ChannelLister(group, page_size)
sort_order = get_user_setting("channel_sort_order", "Ascending")
sort_field = get_user_setting("channel_sort_field", "SortName")
group.objects = Channels({ "limit": page_size,
"StartIndex": page_size * (group.pageNumber - 1),
"SortBy": sort_field,
"SortOrder": sort_order,
})
p = group.findNode("paginator")
p.maxPages = div_ceiling(group.objects.TotalRecordCount, page_size)
end function

View File

@ -1,9 +1,11 @@
function VideoPlayer(id, audio_stream_idx = 1)
function VideoPlayer(id, audio_stream_idx = 1, subtitle_idx = -1)
' Get video controls and UI
video = CreateObject("roSGNode", "JFVideo")
video.id = id
video = VideoContent(video, audio_stream_idx)
if video = invalid
AddVideoContent(video, audio_stream_idx, subtitle_idx)
if video.content = invalid
return invalid
end if
jellyfin_blue = "#00a4dcFF"
@ -14,226 +16,130 @@ function VideoPlayer(id, audio_stream_idx = 1)
return video
end function
function VideoContent(video, audio_stream_idx = 1) as object
' Get video stream
sub AddVideoContent(video, audio_stream_idx = 1, subtitle_idx = -1, playbackPosition = -1)
video.content = createObject("RoSGNode", "ContentNode")
params = {}
meta = ItemMetaData(video.id)
if meta = invalid
video.content = invalid
return
end if
video.content.title = meta.title
video.showID = meta.showID
' If there is a last playback positon, ask user if they want to resume
position = meta.json.UserData.PlaybackPositionTicks
if position > 0 then
dialogResult = startPlayBackOver(position)
'Dialog returns -1 when back pressed, 0 for resume, and 1 for start over
if dialogResult = -1 then
'User pressed back, return invalid and don't load video
return invalid
else if dialogResult = 1 then
'Start Over selected, change position to 0
position = 0
else if dialogResult = 2 then
'Mark this item as watched, refresh the page, and return invalid so we don't load the video
MarkItemWatched(video.id)
video.content.watched = not video.content.watched
group = m.scene.focusedChild
group.timeLastRefresh = CreateObject("roDateTime").AsSeconds()
group.callFunc("refresh")
return invalid
end if
end if
video.content.PlayStart = int(position/10000000)
playbackInfo = ItemPostPlaybackInfo(video.id, position)
if playbackInfo = invalid then
return invalid
end if
video.PlaySessionId = playbackInfo.PlaySessionId
if meta.live then
video.content.live = true
video.content.StreamFormat = "hls"
'Original MediaSource seems to be a placeholder and real stream data is available
'after POSTing to PlaybackInfo
json = meta.json
json.AddReplace("MediaSources", playbackInfo.MediaSources)
json.AddReplace("MediaStreams", playbackInfo.MediaSources[0].MediaStreams)
meta.json = json
end if
container = getContainerType(meta)
video.container = container
transcodeParams = getTranscodeParameters(meta, audio_stream_idx)
transcodeParams.append({"PlaySessionId": video.PlaySessionId})
if meta.live then
_livestream_params = {
"MediaSourceId": playbackInfo.MediaSources[0].Id,
"LiveStreamId": playbackInfo.MediaSources[0].LiveStreamId,
"MinSegments": 2 'This is a guess about initial buffer size, segments are 3s each
}
params.append(_livestream_params)
transcodeParams.append(_livestream_params)
end if
subtitles = sortSubtitles(meta.id,meta.json.MediaStreams)
video.Subtitles = subtitles["all"]
video.content.SubtitleTracks = subtitles["text"]
'TODO: allow user selection of subtitle track before playback initiated, for now set to first track
if video.Subtitles.count() then
video.SelectedSubtitle = 0
else
video.SelectedSubtitle = -1
end if
if video.SelectedSubtitle <> -1 and displaySubtitlesByUserConfig(video.Subtitles[video.SelectedSubtitle], meta.json.MediaStreams[audio_stream_idx]) then
if video.Subtitles[0].IsTextSubtitleStream then
video.subtitleTrack = video.availableSubtitleTracks[video.Subtitles[0].TextIndex].TrackName
video.suppressCaptions = false
else
video.suppressCaptions = true
'Watch to see if system overlay opened/closed to change transcoding if caption mode changed
m.device.EnableAppFocusEvent(True)
video.captionMode = video.globalCaptionMode
if video.globalCaptionMode = "On" or (video.globalCaptionMode = "When mute" and m.mute = true) then
'Only transcode if subtitles are turned on
transcodeParams.append({"SubtitleStreamIndex" : video.Subtitles[0].index })
if playbackPosition = -1
playbackPosition = meta.json.UserData.PlaybackPositionTicks
if playbackPosition > 0
dialogResult = startPlayBackOver(playbackPosition)
'Dialog returns -1 when back pressed, 0 for resume, and 1 for start over
if dialogResult = -1
'User pressed back, return invalid and don't load video
video.content = invalid
return
else if dialogResult = 1
'Start Over selected, change position to 0
playbackPosition = 0
else if dialogResult = 2
'Mark this item as watched, refresh the page, and return invalid so we don't load the video
MarkItemWatched(video.id)
video.content.watched = not video.content.watched
group = m.scene.focusedChild
group.timeLastRefresh = CreateObject("roDateTime").AsSeconds()
group.callFunc("refresh")
video.content = invalid
return
end if
end if
else
video.suppressCaptions = true
video.SelectedSubtitle = -1
end if
video.content.PlayStart = int(playbackPosition / 10000000)
' Call PlayInfo from server
mediaSourceId = video.id
if meta.live then mediaSourceId = "" ' Don't send mediaSourceId for Live media
playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
video.videoId = video.id
video.mediaSourceId = video.id
video.audioIndex = audio_stream_idx
if playbackInfo = invalid
video.content = invalid
return
end if
video.directPlaySupported = directPlaySupported(meta)
video.decodeAudioSupported = decodeAudioSupported(meta, audio_stream_idx)
video.transcodeParams = transcodeParams
params = {}
video.PlaySessionId = playbackInfo.PlaySessionId
if video.directPlaySupported and video.decodeAudioSupported and transcodeParams.SubtitleStreamIndex = invalid then
if meta.live
video.content.live = true
video.content.StreamFormat = "hls"
end if
video.container = getContainerType(meta)
subtitles = sortSubtitles(meta.id, playbackInfo.MediaSources[0].MediaStreams)
video.Subtitles = subtitles["all"]
if meta.live
video.transcodeParams = {
"MediaSourceId": playbackInfo.MediaSources[0].Id,
"LiveStreamId": playbackInfo.MediaSources[0].LiveStreamId,
"PlaySessionId": video.PlaySessionId
}
end if
video.content.SubtitleTracks = subtitles["text"]
' 'TODO: allow user selection of subtitle track before playback initiated, for now set to no subtitles
video.SelectedSubtitle = -1
video.directPlaySupported = playbackInfo.MediaSources[0].SupportsDirectPlay
if video.directPlaySupported
params.append({
"Static": "true",
"Container": container,
"Container": video.container,
"PlaySessionId": video.PlaySessionId,
"AudioStreamIndex": audio_stream_idx
})
video.content.url = buildURL(Substitute("Videos/{0}/stream", video.id), params)
video.content.streamformat = container
video.content.switchingStrategy = ""
video.isTranscode = False
video.audioTrack = audio_stream_idx + 1 ' Tell Roku what Audio Track to play (convert from 0 based index for roku)
video.isTranscoded = false
else
video.content.url = buildURL(Substitute("Videos/{0}/master.m3u8", video.id), transcodeParams)
' Get transcoding reason
video.transcodeReasons = getTranscodeReasons(playbackInfo.MediaSources[0].TranscodingUrl)
video.content.url = buildURL(playbackInfo.MediaSources[0].TranscodingUrl)
video.isTranscoded = true
end if
video.content = authorize_request(video.content)
' todo - audioFormat is read only
video.content.audioFormat = getAudioFormat(meta)
video.content.setCertificatesFile("common:/certs/ca-bundle.crt")
return video
end sub
'
' Extract array of Transcode Reasons from the content URL
' @returns Array of Strings
function getTranscodeReasons(url as string) as object
regex = CreateObject("roRegex", "&TranscodeReasons=([^&]*)", "")
match = regex.Match(url)
if match.count() > 1
return match[1].Split(",")
end if
return []
end function
function getTranscodeParameters(meta as object, audio_stream_idx = 1)
params = {"AudioStreamIndex": audio_stream_idx}
if decodeAudioSupported(meta, audio_stream_idx) and meta.json.MediaStreams[audio_stream_idx] <> invalid and meta.json.MediaStreams[audio_stream_idx].Type = "Audio" then
audioCodec = meta.json.MediaStreams[audio_stream_idx].codec
audioChannels = meta.json.MediaStreams[audio_stream_idx].channels
else
params.Append({"AudioCodec": "aac"})
' If 5.1 Audio Output is connected then allow transcoding to 5.1
di = CreateObject("roDeviceInfo")
if di.GetAudioOutputChannel() = "5.1 surround" and di.CanDecodeAudio({ Codec: "aac", ChCnt: 6 }).result then
params.Append({"MaxAudioChannels": "6"})
else
params.Append({"MaxAudioChannels": "2"})
end if
end if
streamInfo = {}
if meta.json.MediaStreams[0] <> invalid and meta.json.MediaStreams[0].codec <> invalid then
streamInfo.Codec = meta.json.MediaStreams[0].codec
end if
if meta.json.MediaStreams[0] <> invalid and meta.json.MediaStreams[0].Profile <> invalid and meta.json.MediaStreams[0].Profile.len() > 0 then
streamInfo.Profile = LCase(meta.json.MediaStreams[0].Profile)
end if
if meta.json.MediaSources[0] <> invalid and meta.json.MediaSources[0].container <> invalid and meta.json.MediaSources[0].container.len() > 0 then
streamInfo.Container = meta.json.MediaSources[0].container
end if
devinfo = CreateObject("roDeviceInfo")
res = devinfo.CanDecodeVideo(streamInfo)
if res.result = false then
params.Append({"VideoCodec": "h264"})
streamInfo.Profile = "h264"
streamInfo.Container = "ts"
end if
params.Append({"MediaSourceId": meta.id})
params.Append({"DeviceId": devinfo.getChannelClientID()})
return params
end function
'Checks available subtitle tracks and puts subtitles in forced, default, and non-default/forced but preferred language at the top
function sortSubtitles(id as string, MediaStreams)
tracks = { "forced": [], "default": [], "normal": [] }
'Too many args for using substitute
dashedid = id.left(8) + "-" + id.mid(8,4) + "-" + id.mid(12,4) + "-" + id.mid(16,4) + "-" + id.right(12)
prefered_lang = m.user.Configuration.SubtitleLanguagePreference
for each stream in MediaStreams
if stream.type = "Subtitle" then
'Documentation lists that srt, ttml, and dfxp can be sideloaded but only srt was working in my testing,
'forcing srt for all text subtitles
url = Substitute("{0}/Videos/{1}/{2}/Subtitles/{3}/0/", get_url(), dashedid, id, stream.index.tostr())
url = url + Substitute("Stream.srt?api_key={0}", get_setting("active_user"))
stream = {
"Track": { "Language" : stream.language, "Description": stream.displaytitle , "TrackName": url },
"IsTextSubtitleStream": stream.IsTextSubtitleStream,
"Index": stream.index,
"TextIndex": -1,
"IsDefault": stream.IsDefault,
"IsForced": stream.IsForced
}
if stream.isForced then
trackType = "forced"
else if stream.IsDefault then
trackType = "default"
else
trackType = "normal"
end if
if prefered_lang <> "" and prefered_lang = stream.Track.Language then
tracks[trackType].unshift(stream)
else
tracks[trackType].push(stream)
end if
end if
end for
tracks["default"].append(tracks["normal"])
tracks["forced"].append(tracks["default"])
textTracks = []
for i = 0 to tracks["forced"].count() - 1
if tracks["forced"][i].IsTextSubtitleStream then tracks["forced"][i].TextIndex = textTracks.count()
textTracks.push(tracks["forced"][i].Track)
end for
return { "all" : tracks["forced"], "text": textTracks }
end function
'Opens dialog asking user if they want to resume video or start playback over
function startPlayBackOver(time as LongInteger) as integer
if m.scene.focusedChild.overhangTitle = "Home" then
if m.scene.focusedChild.overhangTitle = "Home"
return option_dialog([ "Resume playing at " + ticksToHuman(time) + ".", "Start over from the beginning.", "Watched"])
else
return option_dialog([ "Resume playing at " + ticksToHuman(time) + ".", "Start over from the beginning."])
@ -242,41 +148,30 @@ end function
function directPlaySupported(meta as object) as boolean
devinfo = CreateObject("roDeviceInfo")
if meta.json.MediaSources[0] <> invalid and meta.json.MediaSources[0].SupportsDirectPlay = false then
if meta.json.MediaSources[0] <> invalid and meta.json.MediaSources[0].SupportsDirectPlay = false
return false
end if
if meta.json.MediaStreams[0] = invalid then
if meta.json.MediaStreams[0] = invalid
return false
end if
streamInfo = { Codec: meta.json.MediaStreams[0].codec }
if meta.json.MediaStreams[0].Profile <> invalid and meta.json.MediaStreams[0].Profile.len() > 0 then
if meta.json.MediaStreams[0].Profile <> invalid and meta.json.MediaStreams[0].Profile.len() > 0
streamInfo.Profile = LCase(meta.json.MediaStreams[0].Profile)
end if
if meta.json.MediaSources[0].container <> invalid and meta.json.MediaSources[0].container.len() > 0 then
if meta.json.MediaSources[0].container <> invalid and meta.json.MediaSources[0].container.len() > 0
'CanDecodeVideo() requires the .container to be format: “mp4”, “hls”, “mkv”, “ism”, “dash”, “ts” if its to direct stream
if meta.json.MediaSources[0].container = "mov" then
if meta.json.MediaSources[0].container = "mov"
streamInfo.Container = "mp4"
else
streamInfo.Container = meta.json.MediaSources[0].container
end if
end if
return devinfo.CanDecodeVideo(streamInfo).result
end function
function decodeAudioSupported(meta as object, audio_stream_idx = 1) as boolean
decodeResult = devinfo.CanDecodeVideo(streamInfo)
return decodeResult <> invalid and decodeResult.result
'Check for missing audio and allow playing
if meta.json.MediaStreams[audio_stream_idx] = invalid or meta.json.MediaStreams[audio_stream_idx].Type <> "Audio" then return true
devinfo = CreateObject("roDeviceInfo")
codec = meta.json.MediaStreams[audio_stream_idx].codec
streamInfo = { Codec: codec, ChCnt: meta.json.MediaStreams[audio_stream_idx].channels }
'Otherwise check Roku can decode stream and channels
canDecode = devinfo.CanDecodeAudio(streamInfo)
return canDecode.result
end function
function getContainerType(meta as object) as string
@ -313,52 +208,35 @@ function getAudioInfo(meta as object) as object
return results
end function
function ReportPlayback(video, state = "update" as string)
sub ReportPlayback(video, state = "update" as string)
if video = invalid or video.position = invalid then return
params = {
"PlaySessionId": video.PlaySessionId,
"PositionTicks": int(video.position) * 10000000&, 'Ensure a LongInteger is used
"IsPaused": (video.state = "paused"),
}
if video.content.live then
if video.content.live
params.append({
"MediaSourceId": video.transcodeParams.MediaSourceId,
"LiveStreamId": video.transcodeParams.LiveStreamId
})
end if
PlaystateUpdate(video.id, state, params)
end function
end sub
function StopPlayback()
sub StopPlayback()
video = m.scene.focusedchild
if video.state = "finished" then MarkItemWatched(video.id)
video.control = "stop"
m.device.EnableAppFocusEvent(False)
video.findNode("playbackTimer").control = "stop"
ReportPlayback(video, "stop")
end function
function displaySubtitlesByUserConfig(subtitleTrack, audioTrack)
subtitleMode = m.user.Configuration.SubtitleMode
audioLanguagePreference = m.user.Configuration.AudioLanguagePreference
subtitleLanguagePreference = m.user.Configuration.SubtitleLanguagePreference
if subtitleMode = "Default"
return (subtitleTrack.isForced or subtitleTrack.isDefault)
else if subtitleMode = "Smart"
return (audioLanguagePreference <> "" and audioTrack.Language <> invalid and subtitleLanguagePreference <> "" and subtitleTrack.Track.Language <> invalid and subtitleLanguagePreference = subtitleTrack.Track.Language and audioLanguagePreference <> audioTrack.Language)
else if subtitleMode = "OnlyForced"
return subtitleTrack.IsForced
else if subtitleMode = "Always"
return true
else if subtitleMode = "None"
return false
else
return false
end if
end function
end sub
function autoPlayNextEpisode(videoID as string, showID as string)
' use web client setting
if m.user.Configuration.EnableNextEpisodeAutoPlay then
if m.user.Configuration.EnableNextEpisodeAutoPlay
' query API for next episode ID
url = Substitute("Shows/{0}/Episodes", showID)
urlParams = { "UserId": get_setting("active_user")}
@ -367,7 +245,7 @@ function autoPlayNextEpisode(videoID as string, showID as string)
resp = APIRequest(url, urlParams)
data = getJson(resp)
if data <> invalid and data.Items.Count() = 2 then
if data <> invalid and data.Items.Count() = 2
' remove finished video node
n = m.scene.getChildCount() - 1
m.scene.removeChildIndex(n)

View File

@ -1,9 +0,0 @@
function api_BrandingConfig()
' Gets branding configuration
' {
' LoginDisclaimer: string
' CustomCss: string
' }
resp = APIRequest("Branding/Configuration")
return getJson(resp)
end function

View File

@ -37,15 +37,15 @@ end function
function ImageURL(id, version = "Primary", params = {})
' set defaults
if params.maxHeight = invalid then
if params.maxHeight = invalid
param = { "maxHeight" : "384" }
params.append(param)
end if
if params.maxWidth = invalid then
if params.maxWidth = invalid
param = { "maxWidth" : "196" }
params.append(param)
end if
if params.quality = invalid then
if params.quality = invalid
param = { "quality" : "90" }
params.append(param)
end if
@ -56,13 +56,13 @@ end function
function UserImageURL(id, params = {})
' set defaults
if params.maxHeight = invalid then
if params.maxHeight = invalid
params.append({ "maxHeight" : "300" })
end if
if params.maxWidth = invalid then
if params.maxWidth = invalid
params.append({ "maxWidth" : "300" })
end if
if params.quality = invalid then
if params.quality = invalid
params.append({ "quality" : "90" })
end if

View File

@ -1,31 +1,7 @@
function ItemsList(params = {} as object)
' Gets items based on a query.
resp = APIRequest("Items", params)
data = getJson(resp)
' TODO - parse items
return data
end function
function UserItems(params = {} as object)
' Gets items based on a query
resp = APIRequest(Substitute("Items/{0}/Items", get_setting("active_user")), params)
data = getJson(resp)
' TODO - parse items
return data
end function
function UserItemsResume(params = {} as object)
' Gets items based on a query
resp = APIRequest(Substitute("Items/{0}/Items/Resume", get_setting("active_user")), params)
data = getJson(resp)
' TODO - parse items
return data
end function
function ItemGetPlaybackInfo(id as string, StartTimeTicks = 0 as longinteger)
function ItemGetPlaybackInfo(id as string, startTimeTicks = 0 as longinteger)
params = {
"UserId": get_setting("active_user"),
"StartTimeTicks": StartTimeTicks,
"StartTimeTicks": startTimeTicks,
"IsPlayback": true,
"AutoOpenLiveStream": true,
"MaxStreamingBitrate": "140000000"
@ -34,17 +10,24 @@ function ItemGetPlaybackInfo(id as string, StartTimeTicks = 0 as longinteger)
return getJson(resp)
end function
function ItemPostPlaybackInfo(id as string, StartTimeTicks = 0 as longinteger)
function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string , audioTrackIndex = -1 as integer, subtitleTrackIndex = -1 as integer, startTimeTicks = 0 as longinteger)
body = {
"DeviceProfile": getDeviceProfile()
}
params = {
"UserId": get_setting("active_user"),
"StartTimeTicks": StartTimeTicks,
"StartTimeTicks": startTimeTicks,
"IsPlayback": true,
"AutoOpenLiveStream": true,
"MaxStreamingBitrate": "140000000"
"MaxStreamingBitrate": "140000000",
"MaxStaticBitrate": "140000000",
"SubtitleStreamIndex": subtitleTrackIndex
}
if mediaSourceId <> "" then params.MediaSourceId = mediaSourceId
if audioTrackIndex > -1 then params.AudioStreamIndex = audioTrackIndex
req = APIRequest(Substitute("Items/{0}/PlaybackInfo", id), params)
req.SetRequest("POST")
return postJson(req, FormatJson(body))
@ -63,11 +46,12 @@ function SearchMedia(query as string)
"IncludeGenres": false,
"IncludeStudios": false,
"IncludeArtists": false,
' "IncludeItemTypes: "Movie",
"IncludeItemTypes": "TvChannel,Movie,BoxSet,Series,Episode,Video"
"EnableTotalRecordCount": false,
"ImageTypeLimit": 1,
"Recursive": true
})
data = getJson(resp)
results = []
for each item in data.Items
@ -80,94 +64,14 @@ function SearchMedia(query as string)
return data
end function
' List items from within a library
function ItemList(library_id = invalid as string, params = {})
if params["limit"] = invalid
params["limit"] = 30
end if
if params["page"] = invalid
params["page"] = 1
end if
params["parentid"] = library_id
params["recursive"] = true
url = Substitute("Users/{0}/Items/", get_setting("active_user"))
resp = APIRequest(url, params)
data = getJson(resp)
results = []
for each item in data.Items
imgParams = {}
if item.ImageTags.Primary <> invalid then
' If Primary image exists use it
param = { "Tag" : item.ImageTags.Primary }
imgParams.Append(param)
end if
param = { "AddPlayedIndicator": item.UserData.Played }
imgParams.Append(param)
if item.UserData.PlayedPercentage <> invalid then
param = { "PercentPlayed": item.UserData.PlayedPercentage }
imgParams.Append(param)
end if
if item.type = "Movie"
tmp = CreateObject("roSGNode", "MovieData")
tmp.image = PosterImage(item.id, imgParams)
tmp.json = item
results.push(tmp)
else if item.type = "Series"
if item.UserData.UnplayedItemCount > 0 then
param = { "UnplayedCount" : item.UserData.UnplayedItemCount }
imgParams.Append(param)
end if
tmp = CreateObject("roSGNode", "SeriesData")
tmp.image = PosterImage(item.id, imgParams)
tmp.json = item
results.push(tmp)
else if item.type = "BoxSet"
if item.UserData.UnplayedItemCount > 0 then
param = { "UnplayedCount" : item.UserData.UnplayedItemCount }
imgParams.Append(param)
end if
tmp = CreateObject("roSGNode", "CollectionData")
tmp.image = PosterImage(item.id, imgParams)
tmp.json = item
results.push(tmp)
else if item.type = "Episode"
imgParams.Append({ "AddPlayedIndicator": item.UserData.Played })
tmp = CreateObject("roSGNode", "TVEpisodeData")
tmp.image = PosterImage(item.id, imgParams)
tmp.json = item
results.push(tmp)
else if item.type = "MusicAlbum"
tmp = CreateObject("roSGNode", "AlbumData")
tmp.image = PosterImage(item.id, imgParams)
tmp.json = item
results.push(tmp)
else if item.type = "Video"
tmp = CreateObject("roSGNode", "VideoData")
tmp.image = PosterImage(item.id, imgParams)
tmp.json = item
results.push(tmp)
else if item.type = "Folder"
tmp = CreateObject("roSGNode", "FolderData")
tmp.json = item
results.push(tmp)
else
print "Items.brs::ItemList received unhandled type: " item.type
' Otherwise we just stick with the JSON
results.push(item)
end if
end for
data.items = results
return data
end function
' MetaData about an item
function ItemMetaData(id as string)
url = Substitute("Users/{0}/Items/{1}", get_setting("active_user"), id)
resp = APIRequest(url)
data = getJson(resp)
if data = invalid then return invalid
imgParams = {}
if data.UserData.PlayedPercentage <> invalid then
if data.UserData.PlayedPercentage <> invalid
param = { "PercentPlayed": data.UserData.PlayedPercentage }
imgParams.Append(param)
end if
@ -191,7 +95,7 @@ function ItemMetaData(id as string)
else if data.type = "BoxSet"
tmp = CreateObject("roSGNode", "CollectionData")
tmp.image = PosterImage(data.id, imgParams)
tmp.json = item
tmp.json = data
return tmp
else if data.type = "Season"
tmp = CreateObject("roSGNode", "TVSeasonData")
@ -213,7 +117,6 @@ function ItemMetaData(id as string)
' Return json if we don't know what it is
return data
end if
return data
end function
' Seasons for a TV Show
@ -225,7 +128,7 @@ function TVSeasons(id as string)
results = []
for each item in data.Items
imgParams = { "AddPlayedIndicator": item.UserData.Played }
if item.UserData.UnplayedItemCount > 0 then
if item.UserData.UnplayedItemCount > 0
param = { "UnplayedCount" : item.UserData.UnplayedItemCount }
imgParams.Append(param)
end if
@ -246,7 +149,7 @@ function TVEpisodes(show_id as string, season_id as string)
results = []
for each item in data.Items
imgParams = { "AddPlayedIndicator": item.UserData.Played, "maxWidth": 712, "maxheight": 400 }
if item.UserData.PlayedPercentage <> invalid then
if item.UserData.PlayedPercentage <> invalid
param = { "PercentPlayed": item.UserData.PlayedPercentage }
imgParams.Append(param)
end if
@ -262,42 +165,3 @@ function TVEpisodes(show_id as string, season_id as string)
data.Items = results
return data
end function
' The next up episode for a TV show
function TVNext(id as string)
url = Substitute("Shows/NextUp", id)
resp = APIRequest(url, { "UserId": get_setting("active_user"), "SeriesId": id })
data = getJson(resp)
for each item in data.Items
item.image = PosterImage(item.id)
end for
return data
end function
function Channels(params = {})
if params["limit"] = invalid
params["limit"] = 30
end if
if params["page"] = invalid
params["page"] = 1
end if
params["recursive"] = true
resp = APIRequest("LiveTv/Channels", params)
data = getJson(resp)
results = []
for each item in data.Items
imgParams = { "maxWidth": 712, "maxheight": 400 }
tmp = CreateObject("roSGNode", "ChannelData")
tmp.image = PosterImage(item.id, imgParams)
if tmp.image <> invalid
tmp.image.posterDisplayMode = "scaleToFit"
end if
tmp.json = item
results.push(tmp)
end for
data.Items = results
return data
end function

View File

@ -1,19 +0,0 @@
function ItemCounts()
' Gets counts of a library
' Query:
' UserId: Get counts from specific user's library
' IsFavorite: Get counts of favorite items
resp = APIRequest("Items/Counts", {})
data = getJson(resp)
return data
end function
function LibraryMediaFolders()
' Gets all user media folders
' Query:
' IsHidden: Filter by folders that are marked hidden, or not
resp = APIRequest("Library/MediaFolders")
data = getJson(resp)
return data
end function

View File

@ -1,10 +1,13 @@
function PlaystateUpdate(id, state as string, params = {})
if state = "start" then
if state = "start"
url = "Sessions/Playing"
else if state = "stop" then
else if state = "stop"
url = "Sessions/Playing/Stopped"
else if state = "update"
url = "Sessions/Playing/Progress"
else
' Unknow State
return {}
end if
params = PlaystateDefaults(id, params)
resp = APIRequest(url)
@ -40,10 +43,9 @@ function PlaystateDefaults(id="" as string, params={} as object)
return FormatJson(new_params)
end function
function deleteTranscode(id)
sub deleteTranscode(id)
devinfo = CreateObject("roDeviceInfo")
device_id = devinfo.getChannelClientID()
req = APIRequest("/Videos/ActiveEncodings", { "deviceID" : devinfo.getChannelClientID(), "PlaySessionId": id })
req.setRequest("DELETE")
postVoid(req)
end function
end sub

View File

@ -11,9 +11,8 @@ function UnmarkItemFavorite(id as String)
return getJson(resp)
end function
function MarkItemWatched(id as String)
sub MarkItemWatched(id as String)
date = CreateObject("roDateTime")
date.toLocalTime()
dateStr = stri(date.getYear()).trim()
dateStr += leftPad(stri(date.getMonth()).trim(), "0", 2)
dateStr += leftPad(stri(date.getDayOfMonth()).trim(), "0", 2)
@ -22,9 +21,8 @@ function MarkItemWatched(id as String)
dateStr += leftPad(stri(date.getSeconds()).trim(), "0", 2)
url = Substitute("Users/{0}/PlayedItems/{1}", get_setting("active_user"), id)
resp = APIRequest(url, {"DatePlayed": dateStr})
data = postJson(resp)
end function
APIRequest(url, {"DatePlayed": dateStr})
end sub
function UnmarkItemWatched(id as String)
url = Substitute("Users/{0}/PlayedItems/{1}", get_setting("active_user"), id)

View File

@ -5,6 +5,7 @@ function buildParams(params={} as Object) as string
param_array = []
for each field in params.items()
item = ""
if type(field.value) = "String" or type(field.value) = "roString"
item = field.key + "=" + req.escape(field.value.trim())
'item = field.key + "=" + field.value.trim()
@ -30,15 +31,22 @@ function buildParams(params={} as Object) as string
item = field.key + "=" + req.escape(field.value)
'item = field.key + "=" + field.value
end if
param_array.push(item)
if item <> "" then param_array.push(item)
end for
return param_array.join("&")
end function
function buildURL(path as String, params={} as Object) as string
full_url = get_url() + "/" + path
' Add intial '/' if path does not start with one
if path.Left(1) = "/"
full_url = get_url() + path
else
full_url = get_url() + "/" + path
end if
if params.count() > 0
full_url = full_url + "?" + buildParams(params)
end if
@ -136,7 +144,7 @@ function authorize_request(request)
auth = auth + ", Version=" + Chr(34) + version + Chr(34)
user = get_setting("active_user")
if user <> invalid and user <> "" then
if user <> invalid and user <> ""
auth = auth + ", UserId=" + Chr(34) + user + Chr(34)
end if

View File

@ -2,10 +2,8 @@ function get_token(user as String, password as String)
url = "Users/AuthenticateByName?format=json"
req = APIRequest(url)
encPass = CreateObject("roUrlTransfer")
json = postJson(req, FormatJson({ "Username": user, "Pw": password }))
if json = invalid then return invalid
userdata = CreateObject("roSGNode", "UserData")
@ -23,43 +21,91 @@ function AboutMe()
return getJson(resp)
end function
function SignOut()
sub SignOut()
if get_setting("active_user") <> invalid
unset_user_setting("token")
unset_setting("username")
unset_setting("password")
end if
unset_setting("active_user")
m.overhang.currentUser = ""
m.overhang.showOptions = false
m.scene.unobserveField("optionsPressed")
end function
end sub
function AvailableUsers()
users = parseJson(get_setting("available_users", "[]"))
return users
end function
function PickUser(id as string)
sub PickUser(id as string)
this_user = invalid
for each user in AvailableUsers()
if user.id = id then this_user = user
end for
if this_user = invalid then return invalid
if this_user = invalid then return
set_setting("active_user", this_user.id)
set_setting("server", this_user.server)
end function
end sub
function RemoveUser(id as string)
sub RemoveUser(id as string)
user = CreateObject("roSGNode", "UserData")
user.id = id
user.callFunc("removeFromRegistry")
if get_setting("active_user") = id then SignOut()
end function
end sub
function ServerInfo()
url = "System/Info/Public"
resp = APIRequest(url)
return getJson(resp)
req = APIRequest(url)
req.setMessagePort(CreateObject("roMessagePort"))
req.AsyncGetToString()
' wait 15 seconds for a server response
resp = wait(35000, req.GetMessagePort())
' handle unknown errors
if type(resp) <> "roUrlEvent"
return { "Error": true, "ErrorMessage": "Unknown" }
end if
' check for a location redirect header in the response
headers = resp.GetResponseHeaders()
if headers <> invalid and headers.location <> invalid
' only follow redirect if it the API Endpoint path is the same (/System/Info/Public)
' set the server to new location and try again
if right(headers.location, 19) = "/System/Info/Public"
set_setting("server", left(headers.location, len(headers.location) - 19))
info = ServerInfo()
if info.Error
info.UpdatedUrl = left(headers.location, len(headers.location) - 19)
info.ErrorMessage = info.ErrorMessage + " (Note: Server redirected us to " + info.UpdatedUrl + ")"
end if
return info
end if
end if
' handle any non 200 responses, returning the error code and message
if resp.GetResponseCode() <> 200
return { "Error": true, "ErrorCode": resp.GetResponseCode(), "ErrorMessage": resp.GetFailureReason() }
end if
' return the parsed response string
responseString = resp.GetString()
if responseString <> invalid and responseString <> ""
result = ParseJson(responseString)
if result <> invalid
result.Error = false
return result
end if
end if
' otherwise return error message
return { "Error": true, "ErrorMessage": "Does not appear to be a Jellyfin Server" }
end function
function GetPublicUsers()
@ -77,7 +123,7 @@ sub LoadUserPreferences()
resp = APIRequest(url)
jsonResponse = getJson(resp)
if jsonResponse <> invalid and jsonResponse.CustomPrefs <> invalid and jsonResponse.CustomPrefs["landing-livetv"] <> invalid then
if jsonResponse <> invalid and jsonResponse.CustomPrefs <> invalid and jsonResponse.CustomPrefs["landing-livetv"] <> invalid
set_user_setting("display.livetv.landing", jsonResponse.CustomPrefs["landing-livetv"])
else
unset_user_setting("display.livetv.landing")

View File

@ -1,3 +1,142 @@
function selectSubtitleTrack(tracks, current = -1) as integer
video = m.scene.focusedChild
trackSelected = selectSubtitleTrackDialog(video.Subtitles, video.SelectedSubtitle)
if trackSelected = invalid or trackSelected = -1 ' back pressed in Dialog - no selection made
return -2
else
return trackSelected - 1
end if
end function
' Present Dialog to user to select subtitle track
function selectSubtitleTrackDialog(tracks, currentTrack = -1)
iso6392 = getSubtitleLanguages()
options = ["None"]
for each item in tracks
forced = ""
default = ""
if item.IsForced then forced = " [Forced]"
if item.IsDefault then default = " - Default"
if item.Track.Language <> invalid
language = iso6392.lookup(item.Track.Language)
if language = invalid then language = item.Track.Language
else
language = "Undefined"
end if
options.push(language + forced + default)
end for
return option_dialog(options, "Select a subtitle track", currentTrack + 1)
end function
sub changeSubtitleDuringPlayback(newid)
' If no subtitles set
if newid = invalid or newid = -1
turnoffSubtitles()
return
end if
video = m.scene.focusedChild
' If no change of subtitle track, return
if newid = video.SelectedSubtitle then return
currentSubtitles = video.Subtitles[video.SelectedSubtitle]
newSubtitles = video.Subtitles[newid]
if newSubtitles.IsEncoded
' Switching to Encoded Subtitle stream
video.control = "stop"
AddVideoContent(video, video.audioIndex, newSubtitles.Index, video.position * 10000000)
video.control = "play"
video.globalCaptionMode = "Off" ' Using encoded subtitles - so turn off text subtitles
else if currentSubtitles <> invalid AND currentSubtitles.IsEncoded
' Switching from an Encoded stream to a text stream
video.control = "stop"
AddVideoContent(video, video.audioIndex, -1, video.position * 10000000)
video.control = "play"
video.globalCaptionMode = "On"
video.subtitleTrack = video.availableSubtitleTracks[newSubtitles.TextIndex].TrackName
else
' Switch to Text Subtitle Track
video.globalCaptionMode = "On"
video.subtitleTrack = video.availableSubtitleTracks[newSubtitles.TextIndex].TrackName
end if
video.SelectedSubtitle = newid
end sub
sub turnoffSubtitles()
video = m.scene.focusedChild
current = video.SelectedSubtitle
video.SelectedSubtitle = -1
video.globalCaptionMode = "Off"
m.device.EnableAppFocusEvent(false)
' Check if Enoded subtitles are being displayed, and turn off
if current > -1 and video.Subtitles[current].IsEncoded
video.control = "stop"
AddVideoContent(video, video.audioIndex, -1, video.position * 10000000)
video.control = "play"
end if
end sub
'Checks available subtitle tracks and puts subtitles in forced, default, and non-default/forced but preferred language at the top
function sortSubtitles(id as string, MediaStreams)
tracks = { "forced": [], "default": [], "normal": [] }
'Too many args for using substitute
prefered_lang = m.user.Configuration.SubtitleLanguagePreference
for each stream in MediaStreams
if stream.type = "Subtitle"
url = ""
if stream.DeliveryUrl <> invalid
url = buildURL(stream.DeliveryUrl)
end if
stream = {
"Track": { "Language" : stream.language, "Description": stream.displaytitle , "TrackName": url },
"IsTextSubtitleStream": stream.IsTextSubtitleStream,
"Index": stream.index,
"IsDefault": stream.IsDefault,
"IsForced": stream.IsForced,
"IsExternal": stream.IsExternal
"IsEncoded": stream.DeliveryMethod = "Encode"
}
if stream.isForced
trackType = "forced"
else if stream.IsDefault
trackType = "default"
else
trackType = "normal"
end if
if prefered_lang <> "" and prefered_lang = stream.Track.Language
tracks[trackType].unshift(stream)
else
tracks[trackType].push(stream)
end if
end if
end for
tracks["default"].append(tracks["normal"])
tracks["forced"].append(tracks["default"])
textTracks = []
for i = 0 to tracks["forced"].count() - 1
if tracks["forced"][i].IsTextSubtitleStream
tracks["forced"][i].TextIndex = textTracks.count()
textTracks.push(tracks["forced"][i].Track)
end if
end for
return { "all" : tracks["forced"], "text": textTracks }
end function
function getSubtitleLanguages()
return {
"aar": "Afar",

View File

@ -1,164 +0,0 @@
function selectSubtitleTrack(tracks, current = -1)
video = m.scene.focusedChild
trackSelected = selectSubtitleTrackDialog(video.Subtitles, video.SelectedSubtitle)
if trackSelected = -1 then
return invalid
else
return trackSelected - 1
end if
end function
function selectSubtitleTrackDialog(tracks, currentTrack = -1)
iso6392 = getSubtitleLanguages()
options = ["None"]
for each item in tracks
forced = ""
default = ""
if item.IsForced then forced = " [Forced]"
if item.IsDefault then default = " - Default"
if item.Track.Language <> invalid then
language = iso6392.lookup(item.Track.Language)
if language = invalid then language = item.Track.Language
else
language = "Undefined"
end if
options.push(language + forced + default)
end for
return option_dialog(options, "Select a subtitle track", currentTrack + 1)
end function
sub changeSubtitleDuringPlayback(newid)
if newid = invalid then return
if newid = -1 then
turnoffSubtitles()
return
end if
video = m.scene.focusedChild
oldTrack = video.Subtitles[video.SelectedSubtitle]
newTrack = video.Subtitles[newid]
video.captionMode = video.globalCaptionMode
m.device.EnableAppFocusEvent(not newTrack.IsTextSubtitleStream)
video.SelectedSubtitle = newid
if newTrack.IsTextSubtitleStream then
if video.content.PlayStart > video.position
'User has rewinded to before playback was initiated. The Roku never loaded this portion of the text subtitle
'Changing the track will cause plaback to jump to initial bookmark position.
video.suppressCaptions = true
rebuildURL(false)
end if
video.subtitleTrack = video.availableSubtitleTracks[newTrack.TextIndex].TrackName
video.suppressCaptions = false
else
video.suppressCaptions = true
end if
'Rebuild URL if subtitle track is video or if changed from video subtitle to text subtitle.
if not newTrack.IsTextSubtitleStream then
rebuildURL(true)
else if oldTrack <> invalid and not oldTrack.IsTextSubtitleStream then
rebuildURL(false)
if newTrack.TextIndex > 0 then video.subtitleTrack = video.availableSubtitleTracks[newTrack.TextIndex].TrackName
end if
end sub
function turnoffSubtitles()
video = m.scene.focusedChild
current = video.SelectedSubtitle
video.SelectedSubtitle = -1
video.suppressCaptions = true
m.device.EnableAppFocusEvent(false)
if current > -1 and not video.Subtitles[current].IsTextSubtitleStream then
rebuildURL(false)
end if
end function
function systemOverlayClosed()
video = m.scene.focusedChild
if video.globalCaptionMode <> video.captionMode then
video.captionMode = video.globalCaptionMode
reviewSubtitleDisplay()
end if
end function
function reviewSubtitleDisplay()
'TODO handle changing subtitles tracks during playback
displayed = areSubtitlesDisplayed()
needed = areSubtitlesNeeded()
print "displayed: " displayed " needed: " needed
if areSubtitlesNeeded() and (not areSubtitlesDisplayed()) then
rebuildURL(true)
else if areSubtitlesDisplayed() and (not areSubtitlesNeeded()) then
rebuildURL(false)
end if
end function
function areSubtitlesDisplayed()
index = m.scene.focusedChild.transcodeParams.lookup("SubtitleStreamIndex")
if index <> invalid and index <> -1 then
return true
else
return false
end if
end function
function areSubtitlesNeeded()
captions = m.scene.focusedChild.globalCaptionMode
if captions = "On"
return true
else if captions = "Off"
return false
else if captions = "When mute"
return m.mute
else if captions = "Instant replay"
'Unsupported. Do we want to do this? Is it worth transcoding for rewinded content and then untranscoding?
return false
end if
end function
sub rebuildURL(captions as boolean)
playBackBuffer = -5
video = m.scene.focusedChild
video.control = "pause"
tmpParams = video.transcodeParams
if captions = false then
tmpParams.delete("SubtitleStreamIndex")
else
if video.Subtitles[video.SelectedSubtitle] <> invalid then
tmpParams.addreplace("SubtitleStreamIndex", int(video.Subtitles[video.SelectedSubtitle].Index))
end if
end if
if video.isTranscoded then
deleteTranscode(video.PlaySessionId)
end if
video.PlaySessionId = ItemGetPlaybackInfo(video.id, int(video.position) + playBackBuffer).PlaySessionId
tmpParams.PlaySessionId = video.PlaySessionId
video.transcodeParams = tmpParams
if video.directPlaySupported and video.decodeAudioSupported and not captions then
'Captions are off and we do not need to transcode video or audo
base = Substitute("Videos/{0}/stream", video.id)
params = {
"Static": "true",
"Container": video.container
"PlaySessionId": video.PlaySessionId
}
video.isTranscoded = false
video.content.streamformat = video.container
else
'Captions are on or we need to transcode for any other reason
video.content.streamformat = "hls"
base = Substitute("Videos/{0}/master.m3u8", video.id)
video.isTranscoded = true
params = video.transcodeParams
end if
video.content.url = buildURL(base, params)
video.content.PlayStart = int(video.position + playBackBuffer)
video.control = "play"
end sub

View File

@ -9,51 +9,51 @@ function registry_read(key, section=invalid)
return invalid
end function
function registry_write(key, value, section=invalid)
if section = invalid then return invalid
sub registry_write(key, value, section=invalid)
if section = invalid then return
reg = CreateObject("roRegistrySection", section)
reg.write(key, value)
reg.flush()
end function
end sub
function registry_delete(key, section=invalid)
if section = invalid then return invalid
sub registry_delete(key, section=invalid)
if section = invalid then return
reg = CreateObject("roRegistrySection", section)
reg.delete(key)
reg.flush()
end function
end sub
' "Jellyfin" registry accessors for the default global settings
function get_setting(key, default=invalid)
value = registry_read(key, "Jellyfin")
if value = invalid return default
if value = invalid then return default
return value
end function
function set_setting(key, value)
sub set_setting(key, value)
registry_write(key, value, "Jellyfin")
end function
end sub
function unset_setting(key)
sub unset_setting(key)
registry_delete(key, "Jellyfin")
end function
end sub
' User registry accessors for the currently active user
function get_user_setting(key, default=invalid)
if get_setting("active_user") = invalid then return default
value = registry_read(key, get_setting("active_user"))
if value = invalid return default
if value = invalid then return default
return value
end function
function set_user_setting(key, value)
if get_setting("active_user") = invalid then return invalid
sub set_user_setting(key, value)
if get_setting("active_user") = invalid then return
registry_write(key, value, get_setting("active_user"))
end function
end sub
function unset_user_setting(key)
if get_setting("active_user") = invalid then return invalid
sub unset_user_setting(key)
if get_setting("active_user") = invalid then return
registry_delete(key, get_setting("active_user"))
end function
end sub

View File

@ -21,7 +21,7 @@ function getDeviceProfile() as object
'Check if 5.1 Audio Output connected
maxAudioChannels = 2
di = CreateObject("roDeviceInfo")
if di.GetAudioOutputChannel() = "5.1 surround" then
if di.GetAudioOutputChannel() = "5.1 surround"
maxAudioChannels = 6
end if
@ -155,51 +155,51 @@ function GetDirectPlayProfiles() as object
end if
' Check for supported Audio
if di.CanDecodeAudio({ Codec: "ac3"}).result then
if di.CanDecodeAudio({ Codec: "ac3"}).result
mkvAudio = mkvAudio + ",ac3"
mp4Audio = mp4Audio + ",ac3"
audio = audio + ",ac3"
end if
if di.CanDecodeAudio({ Codec: "wma"}).result then
if di.CanDecodeAudio({ Codec: "wma"}).result
audio = audio + ",wma"
end if
if di.CanDecodeAudio({ Codec: "flac"}).result then
if di.CanDecodeAudio({ Codec: "flac"}).result
mkvAudio = mkvAudio + ",flac"
audio = audio + ",flac"
end if
if di.CanDecodeAudio({ Codec: "alac"}).result then
if di.CanDecodeAudio({ Codec: "alac"}).result
mkvAudio = mkvAudio + ",alac"
mp4Audio = mp4Audio + ",alac"
audio = audio + ",alac"
end if
if di.CanDecodeAudio({ Codec: "aac"}).result then
if di.CanDecodeAudio({ Codec: "aac"}).result
mkvAudio = mkvAudio + ",aac"
mp4Audio = mp4Audio + ",aac"
audio = audio + ",aac"
end if
if di.CanDecodeAudio({ Codec: "opus"}).result then
if di.CanDecodeAudio({ Codec: "opus"}).result
mkvAudio = mkvAudio + ",opus"
end if
if di.CanDecodeAudio({ Codec: "dts"}).result then
if di.CanDecodeAudio({ Codec: "dts"}).result
mkvAudio = mkvAudio + ",dts"
audio = audio + ",dts"
end if
if di.CanDecodeAudio({ Codec: "wmapro"}).result then
if di.CanDecodeAudio({ Codec: "wmapro"}).result
audio = audio + ",wmapro"
end if
if di.CanDecodeAudio({ Codec: "vorbis"}).result then
if di.CanDecodeAudio({ Codec: "vorbis"}).result
mkvAudio = mkvAudio + ",vorbis"
end if
if di.CanDecodeAudio({ Codec: "eac3"}).result then
if di.CanDecodeAudio({ Codec: "eac3"}).result
mkvAudio = mkvAudio + ",eac3"
end if

View File

@ -1,15 +1,15 @@
function initGlobal()
sub initGlobal()
if m.globals = invalid
m.globals = CreateObject("roAssociativeArray")
end if
end function
end sub
function getGlobal(key="" as String) as Dynamic
initGlobal()
return m.globals[key]
end function
function setGlobal(key="" as String, value=invalid as Dynamic)
sub setGlobal(key="" as String, value=invalid as Dynamic)
initGlobal()
m.globals[key] = value
end function
end sub

View File

@ -16,8 +16,8 @@ end function
function getButton(msg, subnode = "buttons" as string) as object
buttons = msg.getRoSGNode().findNode(subnode)
if buttons = invalid then return invalid
active_button = buttons.focusedChild
return active_button
end function
@ -46,7 +46,7 @@ function formatTime(time) as string
hours = time.getHours()
minHourDigits = 1
di = CreateObject("roDeviceInfo")
if di.GetClockFormat() = "12h" then
if di.GetClockFormat() = "12h"
meridian = "AM"
if hours = 0
hours = 12
@ -70,7 +70,7 @@ end function
function div_ceiling(a as integer, b as integer) as integer
if a < b then return 1
if int(a/b) = a/b then
if int(a/b) = a/b
return a/b
end if
return a/b + 1
@ -80,9 +80,9 @@ end function
function get_dialog_result(dialog, port)
while dialog <> invalid
msg = wait(0, port)
if isNodeEvent(msg, "backPressed") then
if isNodeEvent(msg, "backPressed")
return -1
elseif isNodeEvent(msg, "itemSelected")
else if isNodeEvent(msg, "itemSelected")
return dialog.findNode("optionList").itemSelected
end if
end while
@ -93,7 +93,7 @@ end function
function lastFocusedChild(obj as object) as object
child = obj
for i = 0 to obj.getChildCount()
if obj.focusedChild <> invalid then
if obj.focusedChild <> invalid
child = child.focusedChild
end if
end for
@ -101,14 +101,13 @@ function lastFocusedChild(obj as object) as object
end function
function show_dialog(message as string, options = [], defaultSelection = 0) as integer
group = m.scene.focusedChild
lastFocus = lastFocusedChild(m.scene)
'We want to handle backPressed instead of the main loop
m.scene.unobserveField("backPressed")
dialog = createObject("roSGNode", "JFMessageDialog")
if options.count() then dialog.options = options
if message.len() > 0 then
if message.len() > 0
reg = CreateObject("roFontRegistry")
font = reg.GetDefaultFont()
dialog.fontHeight = font.GetOneLineHeight()
@ -116,7 +115,7 @@ function show_dialog(message as string, options = [], defaultSelection = 0) as i
dialog.message = message
end if
if defaultSelection > 0 then
if defaultSelection > 0
dialog.findNode("optionList").jumpToItem = defaultSelection
end if