Floatplane channel plugin for Jellyfin 10.10.x: browse, HLS proxy, cookie session, CI release
Some checks failed
release / build-release (push) Has been cancelled
Some checks failed
release / build-release (push) Has been cancelled
- IChannel: creators -> sub-channels -> video posts (public listing API, search mapped) - IRequiresMediaInfoCallback -> signed proxy URLs; stream controller rewrites HLS so the AES-128 key is fetched server-side with the session cookie, segments stay on the CDN - delivery/info cached per token lifetime and throttled (~2/min) - config page: paste sails.sid + validate, extra creators, quality, search test - scripts/package.sh -> plugin folder + zip + md5 + manifest.json; Gitea release workflow - research docs, HAR analysis (HAR itself gitignored), install-pi5.sh Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
355
doc/research/reference-sources/hydra_details_screen.brs
Normal file
355
doc/research/reference-sources/hydra_details_screen.brs
Normal file
@@ -0,0 +1,355 @@
|
||||
sub init()
|
||||
m.title = m.top.FindNode("title")
|
||||
m.date = m.top.FindNode("date")
|
||||
m.layout_group = m.top.FindNode("layout_group")
|
||||
m.thumbnail = m.top.FindNode("thumbnail")
|
||||
m.postType = m.top.FindNode("postType")
|
||||
m.play_button = m.top.FindNode("play_button")
|
||||
m.resume_button = m.top.FindNode("resume_button")
|
||||
m.like_button = m.top.FindNode("like")
|
||||
m.dislike_button = m.top.FindNode("dislike")
|
||||
m.description = m.top.FindNode("description")
|
||||
m.attachmentsList = m.top.FindNode("attachmentsList")
|
||||
m.top.observeField("visible", "onVisibleChange")
|
||||
m.like_button.observeField("buttonSelected", "onLikeButtonPressed")
|
||||
m.dislike_button.observeField("buttonSelected", "onDislikeButtonPressed")
|
||||
end sub
|
||||
|
||||
sub fillAttachment(attachment as Object, raw as Dynamic, fallbackTitle as String, fallbackPoster as String)
|
||||
attachment.title = fallbackTitle
|
||||
attachment.HDPOSTERURL = fallbackPoster
|
||||
id = contentAttachmentId(raw)
|
||||
attachment.id = id
|
||||
attachment.guid = id
|
||||
if type(raw) = "roAssociativeArray"
|
||||
if contentIsString(raw.title) and raw.title <> "" then attachment.title = raw.title
|
||||
poster = contentImagePath(raw.thumbnail)
|
||||
if poster <> "" then attachment.HDPOSTERURL = poster
|
||||
if contentIsNumber(raw.duration) and raw.duration > 0 then attachment.duration = Int(raw.duration)
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub onVisibleChange()
|
||||
if m.top.visible = true THEN
|
||||
if m.play_button.visible = false
|
||||
m.play_button.focusable = false
|
||||
m.like_button.setFocus(true)
|
||||
else
|
||||
m.play_button.focusable = true
|
||||
m.play_button.setFocus(true)
|
||||
if m.resume_button.focusable = true then
|
||||
m.resume_button.setFocus(true)
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub OnContentChange(obj)
|
||||
item = obj.getData()
|
||||
m.title.text = item.TITLE
|
||||
m.thumbnail.uri = item.HDPOSTERURL
|
||||
|
||||
'Scrolling description to bottom would hide other video descriptions, and with no way to programmatically scroll back to the beginning, we just have to manually create/destroy the ScrollingText object
|
||||
if m.description <> invalid
|
||||
m.layout_group.removeChild(m.description)
|
||||
end if
|
||||
m.description = CreateObject("roSGNode", "ScrollableText")
|
||||
m.description.id = "description"
|
||||
m.description.font = "font:MediumSystemFont"
|
||||
m.description.color = "0xFFFFFF"
|
||||
m.description.width = "1180"
|
||||
m.description.height = "260"
|
||||
m.description.lineSpacing="4.0"
|
||||
m.description.translation="[50,200]"
|
||||
m.description.text = item.DESCRIPTION
|
||||
|
||||
m.layout_group.appendChild(m.description)
|
||||
|
||||
dt = createObject("roDateTime")
|
||||
dt.FromISO8601String(item.id)
|
||||
m.date.text = dt.AsDateString("short-month-short-weekday")
|
||||
|
||||
m.postType.text = item.ShortDescriptionLine2
|
||||
|
||||
'Hide or show play button based on media type
|
||||
if item.hasPicture = true
|
||||
m.play_button.visible = false
|
||||
end if
|
||||
if item.hasAudio = true
|
||||
m.play_button.visible = true
|
||||
end if
|
||||
if item.hasVideo = true
|
||||
m.play_button.visible = true
|
||||
end if
|
||||
if item.hasAudio = false AND item.hasVideo = false
|
||||
m.play_button.visible = false
|
||||
end if
|
||||
|
||||
'Get rid of attachments row if it shouldn't exist but... does?
|
||||
if m.attachmentsList <> invalid
|
||||
if item.hasVideo = false AND item.hasAudio = false then
|
||||
m.layout_group.removeChild(m.attachmentsList)
|
||||
end if
|
||||
end if
|
||||
|
||||
'Add attachments to row. Detail payloads are objects; older list payloads were string ids.
|
||||
m.attachments = CreateObject("roSGNode", "ContentNode")
|
||||
if item.hasVideo = true and type(item.videoAttachments) = "roArray" then
|
||||
for each vid in item.videoAttachments
|
||||
attachment = CreateObject("roSGNode", "media_node")
|
||||
fillAttachment(attachment, vid, "Video", item.HDPOSTERURL)
|
||||
attachment.isVideo = true
|
||||
attachment.isAudio = false
|
||||
m.attachments.appendChild(attachment)
|
||||
end for
|
||||
end if
|
||||
if item.hasAudio = true and type(item.audioAttachments) = "roArray" then
|
||||
for each aud in item.audioAttachments
|
||||
attachment = CreateObject("roSGNode", "media_node")
|
||||
fillAttachment(attachment, aud, "Audio", "pkg:/images/sound-poster.png")
|
||||
attachment.isVideo = false
|
||||
attachment.isAudio = true
|
||||
attachment.HDPOSTERURL = "pkg:/images/sound-poster.png"
|
||||
m.attachments.appendChild(attachment)
|
||||
end for
|
||||
end if
|
||||
|
||||
if m.attachmentsList <> invalid
|
||||
m.layout_group.removeChild(m.attachmentsList)
|
||||
end if
|
||||
m.attachmentsList.content = m.attachments
|
||||
m.layout_group.appendChild(m.attachmentsList)
|
||||
|
||||
'Hide or show resume button based on progress
|
||||
m.resume_button.visible = false
|
||||
m.resume_button.focusable = false
|
||||
if item.hasVideo = true
|
||||
? "DURATION: " + item.duration.ToStr()
|
||||
? "PROGRESS: " + item.progress.ToStr()
|
||||
if (item.progress <> 0) AND (item.progress < item.duration)
|
||||
m.resume_button.visible = true
|
||||
m.resume_button.focusable = true
|
||||
m.play_button.text = "RESTART"
|
||||
m.play_button.maxWidth = 220
|
||||
m.play_button.minWidth = 220
|
||||
m.play_button.iconUri="pkg:/images/replay.png"
|
||||
m.play_button.focusedIconUri="pkg:/images/replay.png"
|
||||
m.resume_button.maxWidth = 200
|
||||
m.resume_button.minWidth = 200
|
||||
m.resume_button.setFocus(true)
|
||||
else
|
||||
m.play_button.text = "PLAY"
|
||||
m.play_button.maxWidth = 170
|
||||
m.play_button.minWidth = 170
|
||||
m.play_button.iconUri="pkg:/images/play.png"
|
||||
m.play_button.focusedIconUri="pkg:/images/play.png"
|
||||
m.resume_button.maxWidth = 0
|
||||
m.resume_button.minWidth = 0
|
||||
end if
|
||||
end if
|
||||
|
||||
'Hide or show date if media type is VOD or live
|
||||
if item.id = "live"
|
||||
m.date.visible = false
|
||||
else
|
||||
m.date.visible = true
|
||||
end if
|
||||
|
||||
'Set the number of current likes/dislikes for video
|
||||
m.like_button.text = item.likes
|
||||
m.dislike_button.text = item.dislikes
|
||||
m.likes = item.likes
|
||||
m.dislikes = item.dislikes
|
||||
m.postId = item.postId
|
||||
|
||||
if item.id = "live"
|
||||
m.like_button.visible = false
|
||||
m.dislike_button.visible = false
|
||||
m.like_button.focusable = false
|
||||
m.dislike_button.focusable = false
|
||||
else
|
||||
m.like_button.visible = true
|
||||
m.dislike_button.visible = true
|
||||
m.like_button.focusable = true
|
||||
m.dislike_button.focusable = true
|
||||
end if
|
||||
|
||||
'Set if user has liked or disliked video
|
||||
m.dislike_button.opacity = 0.5
|
||||
m.like_button.opacity = 0.5
|
||||
if item.userInteraction <> invalid
|
||||
for each interaction in item.userInteraction
|
||||
if interaction = "like"
|
||||
m.like_button.opacity = 1
|
||||
else if interaction = "dislike"
|
||||
m.dislike_button.opacity = 1
|
||||
end if
|
||||
end for
|
||||
end if
|
||||
end sub
|
||||
|
||||
'sub onItemSelected()
|
||||
' selected = m.attachmentsList.content.getChild(m.attachmentsList.itemSelected)
|
||||
' 'm.attachedMediaSelected = selected
|
||||
' m.top.addField("attachedMediaSelected","node", true)
|
||||
' m.top.setField("attachedMediaSelected", selected)
|
||||
'end sub
|
||||
|
||||
sub onLikeButtonPressed()
|
||||
if m.like_button.opacity = 0.5
|
||||
m.likes += 1
|
||||
m.like_button.opacity = 1
|
||||
m.like_button.text = m.likes
|
||||
if m.dislike_button.opacity = 1
|
||||
m.dislike_button.opacity = 0.5
|
||||
m.dislikes -= 1
|
||||
m.dislike_button.text = m.dislikes
|
||||
end if
|
||||
else if m.like_button.opacity = 1
|
||||
m.likes -= 1
|
||||
m.like_button.opacity = 0.5
|
||||
m.like_button.text = m.likes
|
||||
end if
|
||||
like_task = CreateObject("roSGNode", "likeTask")
|
||||
like_task.setField("do", "like")
|
||||
like_task.setField("id", m.postId)
|
||||
like_task.control = "RUN"
|
||||
end sub
|
||||
|
||||
sub onDislikeButtonPressed()
|
||||
if m.dislike_button.opacity = 0.5
|
||||
m.dislikes += 1
|
||||
m.dislike_button.opacity = 1
|
||||
m.dislike_button.text = m.dislikes
|
||||
if m.like_button.opacity = 1
|
||||
m.like_button.opacity = 0.5
|
||||
m.likes -= 1
|
||||
m.like_button.text = m.likes
|
||||
end if
|
||||
else if m.dislike_button.opacity = 1
|
||||
m.dislikes -= 1
|
||||
m.dislike_button.opacity = 0.5
|
||||
m.dislike_button.text = m.dislikes
|
||||
end if
|
||||
dislike_task = CreateObject("roSGNode", "likeTask")
|
||||
dislike_task.setField("do", "dislike")
|
||||
dislike_task.setField("id", m.postId)
|
||||
dislike_task.control = "RUN"
|
||||
end sub
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
if not press then return false
|
||||
|
||||
if m.play_button.hasFocus()
|
||||
if key = "up"
|
||||
if m.attachmentsList <> Invalid AND m.attachments.getChildCount() > 0 then
|
||||
m.attachmentsList.setFocus(true)
|
||||
else
|
||||
m.description.setFocus(true)
|
||||
end if
|
||||
return true
|
||||
else if key = "left"
|
||||
if m.dislike_button.focusable = true
|
||||
m.dislike_button.setFocus(true)
|
||||
return true
|
||||
end if
|
||||
else if key = "right"
|
||||
if m.resume_button.focusable = true
|
||||
m.resume_button.setFocus(true)
|
||||
return true
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.resume_button.hasFocus()
|
||||
if key = "up"
|
||||
if m.attachmentsList <> Invalid AND m.attachments.getChildCount() > 0 then
|
||||
m.attachmentsList.setFocus(true)
|
||||
else
|
||||
m.description.setFocus(true)
|
||||
end if
|
||||
return true
|
||||
else if key = "left"
|
||||
m.play_button.setFocus(true)
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.dislike_button.hasFocus()
|
||||
if key = "left"
|
||||
m.like_button.setFocus(true)
|
||||
return true
|
||||
else if key = "right"
|
||||
if m.play_button.visible = false
|
||||
m.play_button.focusable = false
|
||||
else
|
||||
m.play_button.setFocus(true)
|
||||
end if
|
||||
return true
|
||||
else if key = "up"
|
||||
if m.attachmentsList <> Invalid AND m.attachments.getChildCount() > 0 then
|
||||
m.attachmentsList.setFocus(true)
|
||||
else
|
||||
m.description.setFocus(true)
|
||||
end if
|
||||
return true
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.like_button.hasFocus()
|
||||
if key = "right"
|
||||
m.dislike_button.setFocus(true)
|
||||
return true
|
||||
else if key = "up"
|
||||
if m.attachmentsList <> Invalid AND m.attachments.getChildCount() > 0 then
|
||||
? "attachmentList found"
|
||||
m.attachmentsList.setFocus(true)
|
||||
else
|
||||
m.description.setFocus(true)
|
||||
end if
|
||||
return true
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.description.hasFocus()
|
||||
if key = "down"
|
||||
if m.attachmentsList <> Invalid AND m.attachments.getChildCount() > 0 then
|
||||
m.attachmentsList.setFocus(true)
|
||||
else
|
||||
if m.play_button.focusable = true then
|
||||
m.play_button.setFocus(true)
|
||||
else
|
||||
m.like_button.setFocus(true)
|
||||
end if
|
||||
end if
|
||||
return true
|
||||
else if key = "back" or key = "left" or key = "right"
|
||||
if m.play_button.visible = false
|
||||
m.like_button.setFocus(true)
|
||||
else
|
||||
m.play_button.setFocus(true)
|
||||
end if
|
||||
return true
|
||||
end if
|
||||
end if
|
||||
|
||||
if m.attachmentsList.hasFocus()
|
||||
if key = "up"
|
||||
m.description.setFocus(true)
|
||||
else if key = "down"
|
||||
if m.play_button.focusable = true then
|
||||
m.play_button.setFocus(true)
|
||||
else
|
||||
m.like_button.setFocus(true)
|
||||
end if
|
||||
else if key = "back"
|
||||
if m.play_button.focusable = true then
|
||||
m.play_button.setFocus(true)
|
||||
else
|
||||
m.like_button.setFocus(true)
|
||||
end if
|
||||
end if
|
||||
return true
|
||||
end if
|
||||
|
||||
return false
|
||||
end function
|
||||
Reference in New Issue
Block a user