Merge pull request #1462 from cewert/post-task-device-profile
Create and use generic PostTask to post device profile
This commit is contained in:
commit
ea16b6cd4d
|
@ -98,14 +98,6 @@ sub popScene()
|
|||
if group <> invalid
|
||||
registerOverhangData(group)
|
||||
|
||||
if group.subtype() = "Home"
|
||||
currentTime = CreateObject("roDateTime").AsSeconds()
|
||||
if group.timeLastRefresh = invalid or (currentTime - group.timeLastRefresh) > 20
|
||||
group.timeLastRefresh = currentTime
|
||||
group.callFunc("refresh")
|
||||
end if
|
||||
end if
|
||||
|
||||
group.visible = true
|
||||
|
||||
m.content.replaceChild(group, 0)
|
||||
|
@ -143,6 +135,11 @@ end function
|
|||
' Clear all content from group stack
|
||||
sub clearScenes()
|
||||
if m.content <> invalid then m.content.removeChildrenIndex(m.content.getChildCount(), 0)
|
||||
for each group in m.groups
|
||||
if LCase(group.subtype()) = "jfscreen"
|
||||
group.callFunc("OnScreenHidden")
|
||||
end if
|
||||
end for
|
||||
m.groups = []
|
||||
end sub
|
||||
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import "pkg:/source/api/baserequest.brs"
|
||||
import "pkg:/source/utils/config.brs"
|
||||
import "pkg:/source/utils/misc.brs"
|
||||
import "pkg:/source/utils/deviceCapabilities.brs"
|
||||
|
||||
sub init()
|
||||
m.isFirstRun = true
|
||||
m.top.overhangTitle = "Home"
|
||||
m.top.optionsAvailable = true
|
||||
m.postTask = createObject("roSGNode", "PostTask")
|
||||
|
||||
if m.global.session.user.settings["ui.home.splashBackground"] = true
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
m.backdrop.uri = buildURL("/Branding/Splashscreen?format=jpg&foregroundLayer=0.15&fillWidth=1280&width=1280&fillHeight=720&height=720&tag=splash")
|
||||
|
@ -18,3 +22,31 @@ end sub
|
|||
sub loadLibraries()
|
||||
m.top.findNode("homeRows").callFunc("loadLibraries")
|
||||
end sub
|
||||
|
||||
' JFScreen hook that gets ran as needed.
|
||||
' Used to update the focus, the state of the data, and tells the server about the device profile
|
||||
sub OnScreenShown()
|
||||
if isValid(m.top.lastFocus)
|
||||
m.top.lastFocus.setFocus(true)
|
||||
else
|
||||
m.top.setFocus(true)
|
||||
end if
|
||||
|
||||
refresh()
|
||||
|
||||
' post the device profile the first time this screen is loaded
|
||||
if m.isFirstRun
|
||||
m.isFirstRun = false
|
||||
m.postTask.arrayData = getDeviceCapabilities()
|
||||
m.postTask.apiUrl = "/Sessions/Capabilities/Full"
|
||||
m.postTask.control = "RUN"
|
||||
m.postTask.observeField("responseCode", "postFinished")
|
||||
end if
|
||||
end sub
|
||||
|
||||
' Triggered by m.postTask after completing a post.
|
||||
' Empty the task data when finished.
|
||||
sub postFinished()
|
||||
m.postTask.unobserveField("responseCode")
|
||||
m.postTask.callFunc("empty")
|
||||
end sub
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component name="Home" extends="JFGroup">
|
||||
<component name="Home" extends="JFScreen">
|
||||
<children>
|
||||
<Poster id="backdrop" loadDisplayMode="scaleToZoom" width="1920" height="1200" />
|
||||
<HomeRows id="homeRows" />
|
||||
|
@ -8,7 +8,6 @@
|
|||
<interface>
|
||||
<field id="selectedItem" alias="homeRows.selectedItem" />
|
||||
<field id="quickPlayNode" alias="homeRows.quickPlayNode" />
|
||||
<field id="timeLastRefresh" type="integer" />
|
||||
<function name="refresh" />
|
||||
<function name="loadLibraries" />
|
||||
</interface>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import "pkg:/source/utils/config.brs"
|
||||
import "pkg:/source/utils/misc.brs"
|
||||
import "pkg:/source/roku_modules/log/LogMixin.brs"
|
||||
' post device profile
|
||||
import "pkg:/source/utils/deviceCapabilities.brs"
|
||||
|
||||
sub init()
|
||||
m.log = log.Logger("Settings")
|
||||
|
@ -27,6 +29,8 @@ sub init()
|
|||
m.boolSetting.observeField("checkedItem", "boolSettingChanged")
|
||||
m.radioSetting.observeField("checkedItem", "radioSettingChanged")
|
||||
|
||||
m.postTask = createObject("roSGNode", "PostTask")
|
||||
|
||||
' Load Configuration Tree
|
||||
m.configTree = GetConfigTree()
|
||||
LoadMenu({ children: m.configTree })
|
||||
|
@ -201,6 +205,23 @@ sub radioSettingChanged()
|
|||
set_user_setting(selectedSetting.settingName, m.radioSetting.content.getChild(m.radioSetting.checkedItem).id)
|
||||
end sub
|
||||
|
||||
' JFScreen hook that gets ran as needed.
|
||||
' Assumes settings were changed and they affect the device profile.
|
||||
' Posts a new device profile to the server using the task thread
|
||||
sub OnScreenHidden()
|
||||
m.postTask.arrayData = getDeviceCapabilities()
|
||||
m.postTask.apiUrl = "/Sessions/Capabilities/Full"
|
||||
m.postTask.control = "RUN"
|
||||
m.postTask.observeField("responseCode", "postFinished")
|
||||
end sub
|
||||
|
||||
' Triggered by m.postTask after completing a post.
|
||||
' Empty the task data when finished.
|
||||
sub postFinished()
|
||||
m.postTask.unobserveField("responseCode")
|
||||
m.postTask.callFunc("empty")
|
||||
end sub
|
||||
|
||||
' Returns true if any of the data entry forms are in focus
|
||||
function isFormInFocus() as boolean
|
||||
if isValid(m.settingDetail.focusedChild) or m.radioSetting.hasFocus() or m.boolSetting.hasFocus() or m.integerSetting.hasFocus()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component name="Settings" extends="JFGroup">
|
||||
<component name="Settings" extends="JFScreen">
|
||||
<children>
|
||||
|
||||
<Label id="path" translation="[95,175]" font="font:SmallestBoldSystemFont" />
|
||||
|
|
79
components/tasks/PostTask.bs
Normal file
79
components/tasks/PostTask.bs
Normal file
|
@ -0,0 +1,79 @@
|
|||
import "pkg:/source/api/baserequest.brs"
|
||||
import "pkg:/source/utils/misc.brs"
|
||||
|
||||
sub init()
|
||||
m.top.functionName = "postItems"
|
||||
end sub
|
||||
|
||||
' Main function for PostTask.
|
||||
' Posts either an array of data
|
||||
' or a string of data to an API endpoint.
|
||||
' Saves the response information
|
||||
sub postItems()
|
||||
if m.top.apiUrl = ""
|
||||
print "ERROR in PostTask. Invalid API URL provided"
|
||||
return
|
||||
end if
|
||||
if m.top.arrayData.count() > 0 and m.top.stringData = ""
|
||||
print "PostTask Started - Posting array to " + m.top.apiUrl
|
||||
req = APIRequest(m.top.apiUrl)
|
||||
req.SetRequest("POST")
|
||||
httpResponse = asyncPost(req, FormatJson(m.top.arrayData))
|
||||
m.top.responseCode = httpResponse
|
||||
print "PostTask Finished. " + m.top.apiUrl + " Response = " + httpResponse.toStr()
|
||||
else if m.top.arrayData.count() = 0 and m.top.stringData <> ""
|
||||
print "PostTask Started - Posting string(" + m.top.stringData + ") to " + m.top.apiUrl
|
||||
req = APIRequest(m.top.apiUrl)
|
||||
req.SetRequest("POST")
|
||||
httpResponse = asyncPost(req, m.top.stringData)
|
||||
m.top.responseCode = httpResponse
|
||||
print "PostTask Finished. " + m.top.apiUrl + " Response = " + httpResponse.toStr()
|
||||
else
|
||||
print "ERROR processing data for PostTask"
|
||||
end if
|
||||
end sub
|
||||
|
||||
' Post data and wait for response code
|
||||
function asyncPost(req, data = "" as string) as integer
|
||||
' response code 0 means there was an error
|
||||
respCode = 0
|
||||
|
||||
req.setMessagePort(CreateObject("roMessagePort"))
|
||||
req.AddHeader("Content-Type", "application/json")
|
||||
req.AsyncPostFromString(data)
|
||||
' wait up to m.top.timeoutSeconds for a response
|
||||
' NOTE: wait() uses milliseconds - multiply by 1000 to convert
|
||||
resp = wait(m.top.timeoutSeconds * 1000, req.GetMessagePort())
|
||||
|
||||
respString = resp.GetString()
|
||||
if isValidAndNotEmpty(respString)
|
||||
m.top.responseBody = ParseJson(respString)
|
||||
print "m.top.responseBody=", m.top.responseBody
|
||||
end if
|
||||
|
||||
respCode = resp.GetResponseCode()
|
||||
if respCode < 0
|
||||
' there was an unexpected error
|
||||
m.top.failureReason = resp.GetFailureReason()
|
||||
else if respCode >= 200 and respCode < 300
|
||||
' save response headers if they're available
|
||||
m.top.responseHeaders = resp.GetResponseHeaders()
|
||||
end if
|
||||
|
||||
return respCode
|
||||
end function
|
||||
|
||||
' Revert PostTask to default state
|
||||
sub empty()
|
||||
' These should match the defaults set in PostTask.xml
|
||||
m.top.apiUrl = ""
|
||||
m.top.timeoutSeconds = 30
|
||||
|
||||
m.top.arrayData = {}
|
||||
m.top.stringData = ""
|
||||
|
||||
m.top.responseCode = invalid
|
||||
m.top.responseBody = {}
|
||||
m.top.responseHeaders = {}
|
||||
m.top.failureReason = ""
|
||||
end sub
|
18
components/tasks/PostTask.xml
Normal file
18
components/tasks/PostTask.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<component name="PostTask" extends="Task">
|
||||
<interface>
|
||||
<field id="apiUrl" type="string" />
|
||||
<field id="timeoutSeconds" type="integer" value="30" />
|
||||
|
||||
<field id="arrayData" type="assocarray" value="{}" />
|
||||
<field id="stringData" type="string" value="" />
|
||||
|
||||
<field id="responseCode" type="integer" />
|
||||
<field id="responseBody" type="assocarray" value="{}" />
|
||||
<field id="responseHeaders" type="assocarray" value="{}" />
|
||||
<field id="failureReason" type="string" value="" />
|
||||
|
||||
<function name="empty" />
|
||||
</interface>
|
||||
</component>
|
|
@ -31,9 +31,8 @@ sub Main (args as dynamic) as void
|
|||
app_start:
|
||||
' First thing to do is validate the ability to use the API
|
||||
if not LoginFlow() then return
|
||||
' tell jellyfin server about device capabilities
|
||||
PostDeviceProfile()
|
||||
' remove previous scenes from the stack
|
||||
|
||||
' remove login scenes from the stack
|
||||
sceneManager.callFunc("clearScenes")
|
||||
|
||||
stopLoadingSpinner()
|
||||
|
@ -634,14 +633,11 @@ sub Main (args as dynamic) as void
|
|||
if event.exitedScreensaver = true
|
||||
sceneManager.callFunc("resetTime")
|
||||
group = sceneManager.callFunc("getActiveScene")
|
||||
if isValid(group) and isValid(group.subtype())
|
||||
if isValid(group)
|
||||
' refresh the current view
|
||||
if group.subtype() = "Home"
|
||||
currentTime = CreateObject("roDateTime").AsSeconds()
|
||||
group.timeLastRefresh = currentTime
|
||||
group.callFunc("refresh")
|
||||
if group.isSubType("JFScreen")
|
||||
group.callFunc("OnScreenShown")
|
||||
end if
|
||||
' todo: add other screens to be refreshed - movie detail, tv series, episode list etc.
|
||||
end if
|
||||
else if isValid(event.audioGuideEnabled)
|
||||
tmpGlobalDevice = m.global.device
|
||||
|
@ -673,12 +669,18 @@ sub Main (args as dynamic) as void
|
|||
' The audio codec capability has changed if true.
|
||||
print "event.audioCodecCapabilityChanged = ", event.audioCodecCapabilityChanged
|
||||
|
||||
PostDeviceProfile()
|
||||
postTask = createObject("roSGNode", "PostTask")
|
||||
postTask.arrayData = getDeviceCapabilities()
|
||||
postTask.apiUrl = "/Sessions/Capabilities/Full"
|
||||
postTask.control = "RUN"
|
||||
else if isValid(event.videoCodecCapabilityChanged)
|
||||
' The video codec capability has changed if true.
|
||||
print "event.videoCodecCapabilityChanged = ", event.videoCodecCapabilityChanged
|
||||
|
||||
PostDeviceProfile()
|
||||
postTask = createObject("roSGNode", "PostTask")
|
||||
postTask.arrayData = getDeviceCapabilities()
|
||||
postTask.apiUrl = "/Sessions/Capabilities/Full"
|
||||
postTask.control = "RUN"
|
||||
else if isValid(event.appFocus)
|
||||
' It is set to False when the System Overlay (such as the confirm partner button HUD or the caption control overlay) takes focus and True when the channel regains focus
|
||||
print "event.appFocus = ", event.appFocus
|
||||
|
|
|
@ -73,7 +73,6 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx
|
|||
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
|
||||
|
|
|
@ -195,7 +195,13 @@ sub setCertificateAuthority(request as object) as void
|
|||
end sub
|
||||
|
||||
' Takes and returns a roUrlTransfer object after adding a Jellyfin "Authorization" header
|
||||
function authRequest(request as object) as object
|
||||
function authRequest(req as object) as object
|
||||
req.AddHeader("Authorization", buildAuthHeader())
|
||||
return req
|
||||
end function
|
||||
|
||||
' Returns a string containing the "Authorization" header payload
|
||||
function buildAuthHeader() as string
|
||||
QUOTE = Chr(34)
|
||||
auth = "MediaBrowser" + " Client=" + QUOTE + "Jellyfin Roku" + QUOTE
|
||||
auth = auth + ", Device=" + QUOTE + m.global.device.name + " (" + m.global.device.model + ")" + QUOTE
|
||||
|
@ -211,6 +217,5 @@ function authRequest(request as object) as object
|
|||
auth = auth + ", Token=" + QUOTE + m.global.session.user.authToken + QUOTE
|
||||
end if
|
||||
|
||||
request.AddHeader("Authorization", auth)
|
||||
return request
|
||||
return auth
|
||||
end function
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import "pkg:/source/utils/misc.brs"
|
||||
import "pkg:/source/api/baserequest.brs"
|
||||
|
||||
'Device Capabilities for Roku.
|
||||
'This will likely need further tweaking
|
||||
' Returns the Device Capabilities for Roku.
|
||||
' Also prints out the device profile for debugging
|
||||
function getDeviceCapabilities() as object
|
||||
|
||||
return {
|
||||
deviceProfile = {
|
||||
"PlayableMediaTypes": [
|
||||
"Audio",
|
||||
"Video",
|
||||
|
@ -19,44 +18,11 @@ function getDeviceCapabilities() as object
|
|||
"DeviceProfile": getDeviceProfile(),
|
||||
"AppStoreUrl": "https://channelstore.roku.com/details/cc5e559d08d9ec87c5f30dcebdeebc12/jellyfin"
|
||||
}
|
||||
end function
|
||||
|
||||
' Send Device Profile information to server
|
||||
sub PostDeviceProfile()
|
||||
profile = getDeviceCapabilities()
|
||||
req = APIRequest("/Sessions/Capabilities/Full")
|
||||
req.SetRequest("POST")
|
||||
print "profile =", profile
|
||||
print "profile.DeviceProfile =", profile.DeviceProfile
|
||||
print "profile.DeviceProfile.CodecProfiles ="
|
||||
for each prof in profile.DeviceProfile.CodecProfiles
|
||||
print prof
|
||||
for each cond in prof.Conditions
|
||||
print cond
|
||||
end for
|
||||
end for
|
||||
print "profile.DeviceProfile.ContainerProfiles =", profile.DeviceProfile.ContainerProfiles
|
||||
print "profile.DeviceProfile.DirectPlayProfiles ="
|
||||
for each prof in profile.DeviceProfile.DirectPlayProfiles
|
||||
print prof
|
||||
end for
|
||||
print "profile.DeviceProfile.SubtitleProfiles ="
|
||||
for each prof in profile.DeviceProfile.SubtitleProfiles
|
||||
print prof
|
||||
end for
|
||||
print "profile.DeviceProfile.TranscodingProfiles ="
|
||||
for each prof in profile.DeviceProfile.TranscodingProfiles
|
||||
print prof
|
||||
if isValid(prof.Conditions)
|
||||
for each condition in prof.Conditions
|
||||
print condition
|
||||
end for
|
||||
end if
|
||||
end for
|
||||
print "profile.PlayableMediaTypes =", profile.PlayableMediaTypes
|
||||
print "profile.SupportedCommands =", profile.SupportedCommands
|
||||
postJson(req, FormatJson(profile))
|
||||
end sub
|
||||
printDeviceProfile(deviceProfile)
|
||||
|
||||
return deviceProfile
|
||||
end function
|
||||
|
||||
function getDeviceProfile() as object
|
||||
globalDevice = m.global.device
|
||||
|
@ -1078,6 +1044,38 @@ function removeDecimals(value as string) as string
|
|||
return value
|
||||
end function
|
||||
|
||||
' Print out the deviceProfile for debugging
|
||||
sub printDeviceProfile(profile as object)
|
||||
print "profile =", profile
|
||||
print "profile.DeviceProfile =", profile.DeviceProfile
|
||||
print "profile.DeviceProfile.CodecProfiles ="
|
||||
for each prof in profile.DeviceProfile.CodecProfiles
|
||||
print prof
|
||||
for each cond in prof.Conditions
|
||||
print cond
|
||||
end for
|
||||
end for
|
||||
print "profile.DeviceProfile.ContainerProfiles =", profile.DeviceProfile.ContainerProfiles
|
||||
print "profile.DeviceProfile.DirectPlayProfiles ="
|
||||
for each prof in profile.DeviceProfile.DirectPlayProfiles
|
||||
print prof
|
||||
end for
|
||||
print "profile.DeviceProfile.SubtitleProfiles ="
|
||||
for each prof in profile.DeviceProfile.SubtitleProfiles
|
||||
print prof
|
||||
end for
|
||||
print "profile.DeviceProfile.TranscodingProfiles ="
|
||||
for each prof in profile.DeviceProfile.TranscodingProfiles
|
||||
print prof
|
||||
if isValid(prof.Conditions)
|
||||
for each condition in prof.Conditions
|
||||
print condition
|
||||
end for
|
||||
end if
|
||||
end for
|
||||
print "profile.PlayableMediaTypes =", profile.PlayableMediaTypes
|
||||
print "profile.SupportedCommands =", profile.SupportedCommands
|
||||
end sub
|
||||
' Takes and returns a comma delimited string of codecs.
|
||||
' Moves the preferred codec to the front of the string
|
||||
function setPreferredCodec(codecString as string, preferredCodec as string) as string
|
||||
|
|
|
@ -107,6 +107,11 @@ namespace session
|
|||
tmpSessionServer.AddReplace("isLocalHTTPS", isLocalServerHTTPS)
|
||||
' update global server session using the temp array
|
||||
session.Update("server", tmpSessionServer)
|
||||
|
||||
if m.global.app.isDev
|
||||
print "m.global.session.server = ", m.global.session.server
|
||||
end if
|
||||
|
||||
return true
|
||||
end function
|
||||
end namespace
|
||||
|
@ -165,6 +170,9 @@ namespace session
|
|||
end for
|
||||
|
||||
if m.global.app.isDev
|
||||
print "m.global.session.user = ", m.global.session.user
|
||||
print "m.global.session.user.Configuration = ", m.global.session.user.Configuration
|
||||
print "m.global.session.user.Policy = ", m.global.session.user.Policy
|
||||
print "m.global.session.user.settings = ", m.global.session.user.settings
|
||||
end if
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user