Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create pause menu #1461

Merged
merged 10 commits into from
Nov 10, 2023
1 change: 1 addition & 0 deletions components/ItemGrid/LoadVideoContentTask.brs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
video.content.contenttype = "episode"
end if

video.chapters = meta.json.Chapters
video.content.title = meta.title
video.showID = meta.showID

Expand Down
63 changes: 63 additions & 0 deletions components/video/PauseMenu.brs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import "pkg:/source/utils/misc.brs"

1hitsong marked this conversation as resolved.
Show resolved Hide resolved
sub init()
m.chapterNavigation = m.top.findNode("chapterNavigation")

m.selectedButtonIndex = 0
m.chapterNavigation.getChild(m.selectedButtonIndex).focus = true
end sub

sub onButtonSelected()
selectedButton = m.chapterNavigation.getChild(m.selectedButtonIndex)

if LCase(selectedButton.id) = "chapterlist"
m.top.showChapterList = not m.top.showChapterList
end if

m.top.action = selectedButton.id
end sub

function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false

if key = "OK"
onButtonSelected()
return true
end if

if key = "right"
if m.selectedButtonIndex + 1 >= m.chapterNavigation.getChildCount()
return true
end if

selectedButton = m.chapterNavigation.getChild(m.selectedButtonIndex)
selectedButton.focus = false

m.selectedButtonIndex++

selectedButton = m.chapterNavigation.getChild(m.selectedButtonIndex)
selectedButton.focus = true

return true
end if

if key = "left"
if m.selectedButtonIndex = 0
return true
end if

selectedButton = m.chapterNavigation.getChild(m.selectedButtonIndex)
selectedButton.focus = false

m.selectedButtonIndex--

selectedButton = m.chapterNavigation.getChild(m.selectedButtonIndex)
selectedButton.focus = true

return true
end if

' All other keys hide the menu
m.top.action = "hide"
return true
end function
14 changes: 14 additions & 0 deletions components/video/PauseMenu.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<component name="PauseMenu" extends="Group" initialFocus="chapterNext">
<children>
<ButtonGroup id="chapterNavigation" itemSpacings="[20]" layoutDirection="horiz">
<IconButton id="chapterList" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/numberList.png" height="65" width="100" />
<IconButton id="chapterBack" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/previous.png" height="65" width="100" />
<IconButton id="chapterNext" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/next.png" height="65" width="100" />
</ButtonGroup>
</children>
<interface>
<field id="action" type="string" alwaysNotify="true" />
<field id="showChapterList" type="boolean" alwaysNotify="true" />
</interface>
</component>
183 changes: 181 additions & 2 deletions components/video/VideoPlayerView.brs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import "pkg:/source/utils/config.brs"
sub init()
' Hide the overhang on init to prevent showing 2 clocks
m.top.getScene().findNode("overhang").visible = false

m.currentItem = m.global.queueManager.callFunc("getCurrentItem")

m.top.id = m.currentItem.id
m.top.seekMode = "accurate"

m.playbackEnum = {
null: -10
}

' Load meta data
m.LoadMetaDataTask = CreateObject("roSGNode", "LoadVideoContentTask")
Expand All @@ -17,6 +21,12 @@ sub init()
m.LoadMetaDataTask.observeField("content", "onVideoContentLoaded")
m.LoadMetaDataTask.control = "RUN"

m.chapterList = m.top.findNode("chapterList")
m.chapterMenu = m.top.findNode("chapterMenu")
m.chapterContent = m.top.findNode("chapterContent")
m.pauseMenu = m.top.findNode("pauseMenu")
m.pauseMenu.observeField("action", "onPauseMenuAction")

m.playbackTimer = m.top.findNode("playbackTimer")
m.bufferCheckTimer = m.top.findNode("bufferCheckTimer")
m.top.observeField("state", "onState")
Expand Down Expand Up @@ -54,7 +64,100 @@ sub init()
m.top.trickPlayBar.filledBarBlendColor = m.global.constants.colors.blue
end sub

' Only setup captain items if captions are allowed
' handleChapterSkipAction: Handles user command to skip chapters in playing video
'
sub handleChapterSkipAction(action as string)
if not isValidAndNotEmpty(m.chapters) then return

currentChapter = getCurrentChapterIndex()

if action = "chapternext"
gotoChapter = currentChapter + 1
' If there is no next chapter, exit
if gotoChapter > m.chapters.count() - 1 then return

m.top.seek = m.chapters[gotoChapter].StartPositionTicks / 10000000#
return
end if

if action = "chapterback"
gotoChapter = currentChapter - 1
' If there is no previous chapter, restart current chapter
if gotoChapter < 0 then gotoChapter = 0

m.top.seek = m.chapters[gotoChapter].StartPositionTicks / 10000000#
return
end if
end sub

' handleHideAction: Handles action to hide pause menu
'
sub handleHideAction()
m.pauseMenu.visible = false
m.chapterList.visible = false
m.pauseMenu.showChapterList = false
m.pauseMenu.setFocus(false)
m.top.setFocus(true)
m.top.control = "resume"
end sub

' handleHideAction: Handles action to show chapter list
'
sub handleChapterListAction()
m.chapterList.visible = m.pauseMenu.showChapterList

if not m.chapterList.visible then return

m.chapterMenu.jumpToItem = getCurrentChapterIndex()

m.pauseMenu.setFocus(false)
m.chapterMenu.setFocus(true)
end sub

' getCurrentChapterIndex: Finds current chapter index
'
' @return {integer} indicating index of current chapter within chapter data or 0 if chapter lookup fails
'
function getCurrentChapterIndex() as integer
if not isValidAndNotEmpty(m.chapters) then return 0

' Give a 15 second buffer to compensate for user expectation and roku video position inaccuracy
' Web client uses 10 seconds, but this wasn't enough for Roku in testing
currentPosition = m.top.position + 15
currentChapter = 0

for i = m.chapters.count() - 1 to 0 step -1
if currentPosition >= (m.chapters[i].StartPositionTicks / 10000000#)
currentChapter = i
exit for
end if
end for

return currentChapter
end function

' onPauseMenuAction: Process action events from pause menu to their respective handlers
'
sub onPauseMenuAction()
action = LCase(m.pauseMenu.action)

if action = "hide"
handleHideAction()
return
end if

if action = "chapterback" or action = "chapternext"
handleChapterSkipAction(action)
return
end if

if action = "chapterlist"
handleChapterListAction()
return
end if
end sub

' Only setup caption items if captions are allowed
sub onAllowCaptionsChange()
if not m.top.allowCaptions then return

Expand Down Expand Up @@ -160,6 +263,9 @@ sub onVideoContentLoaded()
m.top.fullSubtitleData = videoContent[0].fullSubtitleData
m.top.audioIndex = videoContent[0].audioIndex
m.top.transcodeParams = videoContent[0].transcodeparams
m.chapters = videoContent[0].chapters

populateChapterMenu()

if m.LoadMetaDataTask.isIntro
' Disable trackplay bar for intro videos
Expand All @@ -179,6 +285,28 @@ sub onVideoContentLoaded()
m.top.control = "play"
end sub

' populateChapterMenu: ' Parse chapter data from API and appeand to chapter list menu
'
sub populateChapterMenu()
' Clear any existing chapter list data
m.chapterContent.clear()

if not isValidAndNotEmpty(m.chapters)
chapterItem = CreateObject("roSGNode", "ContentNode")
chapterItem.title = tr("No Chapter Data Found")
chapterItem.playstart = m.playbackEnum.null
m.chapterContent.appendChild(chapterItem)
return
end if

for each chapter in m.chapters
chapterItem = CreateObject("roSGNode", "ContentNode")
chapterItem.title = chapter.Name
chapterItem.playstart = chapter.StartPositionTicks / 10000000#
m.chapterContent.appendChild(chapterItem)
end for
end sub

' Event handler for when video content field changes
sub onContentChange()
if not isValid(m.top.content) then return
Expand Down Expand Up @@ -356,6 +484,33 @@ end sub

function onKeyEvent(key as string, press as boolean) as boolean

' Keypress handler while user is inside the chapter menu
if m.chapterMenu.hasFocus()
if not press then return false

if key = "OK" or key = "play"
focusedChapter = m.chapterMenu.itemFocused
selectedChapter = m.chapterMenu.content.getChild(focusedChapter)
seekTime = selectedChapter.playstart

' Don't seek if user clicked on No Chapter Data
if seekTime = m.playbackEnum.null then return true

m.top.seek = seekTime
return true
end if

if key = "back" or key = "replay"
m.chapterList.visible = false
m.pauseMenu.showChapterList = false
m.chapterMenu.setFocus(false)
m.pauseMenu.setFocus(true)
return true
end if

return true
end if

if key = "OK" and m.nextEpisodeButton.hasfocus() and not m.top.trickPlayBar.visible
m.top.control = "stop"
m.top.state = "finished"
Expand All @@ -373,17 +528,21 @@ function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false

if key = "down"
if m.pauseMenu.visible then return true

' Do not show subtitle selection for intro videos
if not m.LoadMetaDataTask.isIntro
m.top.selectSubtitlePressed = true
return true
end if

else if key = "up"
' Do not show playback info for intro videos
if not m.LoadMetaDataTask.isIntro
m.top.selectPlaybackInfoPressed = true
return true
end if

else if key = "OK"
' OK will play/pause depending on current state
' return false to allow selection during seeking
Expand All @@ -392,10 +551,30 @@ function onKeyEvent(key as string, press as boolean) as boolean
return false
else if m.top.state = "playing"
m.top.control = "pause"
' Disable pause menu for intro videos
if not m.LoadMetaDataTask.isIntro
' Show pause menu
m.top.control = "pause"
m.pauseMenu.visible = true
m.pauseMenu.setFocus(true)
return true
end if

return false
end if
end if

' Disable pause menu for intro videos
if not m.LoadMetaDataTask.isIntro
if key = "play"
' Show pause menu
m.top.control = "pause"
m.pauseMenu.visible = true
m.pauseMenu.setFocus(true)
return true
end if
end if

if key = "back"
m.top.control = "stop"
end if
Expand Down
7 changes: 7 additions & 0 deletions components/video/VideoPlayerView.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
<Group id="captionGroup" translation="[960,1020]" />
<timer id="playbackTimer" repeat="true" duration="30" />
<timer id="bufferCheckTimer" repeat="true" />
<PauseMenu id="pauseMenu" visible="false" translation="[50,950]" />

<Rectangle id="chapterList" visible="false" color="0x00000098" width="460" height="380" translation="[50,530]">
<LabelList id="chaptermenu" itemSpacing="[0,20]" numRows="5" font="font:SmallSystemFont" itemSize="[365,40]" translation="[50,20]">
<ContentNode id="chapterContent" role="content" />
</LabelList>
</Rectangle>

<JFButton id="nextEpisode" opacity="0" textColor="#f0f0f0" focusedTextColor="#202020" focusFootprintBitmapUri="pkg:/images/option-menu-bg.9.png" focusBitmapUri="pkg:/images/white.9.png" translation="[1500, 900]" />
<!--animation for the play next episode button-->
Expand Down
Binary file added images/icons/next.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/icons/numberList.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/icons/previous.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions locale/en_US/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1226,5 +1226,10 @@
<translation>Remember the currently logged in user and try to log them in again next time you start the Jellyfin app.</translation>
<extracomment>User Setting - Setting description</extracomment>
</message>
<message>
<source>No Chapter Data Found</source>
<translation>No Chapter Data Found</translation>
<extracomment>Message shown in pause menu when no chapter data is returned by the API</extracomment>
</message>
</context>
</TS>
2 changes: 1 addition & 1 deletion source/api/Items.brs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ end function
' MetaData about an item
function ItemMetaData(id as string)
url = Substitute("Users/{0}/Items/{1}", m.global.session.user.id, id)
resp = APIRequest(url)
resp = APIRequest(url, { "fields": "Chapters" })
data = getJson(resp)
if data = invalid then return invalid

Expand Down