Merge pull request #1461 from 1hitsong/ChapterSkip
This commit is contained in:
commit
94c105d6be
39
components/Clock.bs
Normal file
39
components/Clock.bs
Normal file
|
@ -0,0 +1,39 @@
|
|||
import "pkg:/source/utils/misc.brs"
|
||||
|
||||
sub init()
|
||||
|
||||
' If hideclick setting is checked, exit without setting any variables
|
||||
if m.global.session.user.settings["ui.design.hideclock"]
|
||||
return
|
||||
end if
|
||||
|
||||
m.clockTime = m.top.findNode("clockTime")
|
||||
|
||||
m.currentTimeTimer = m.top.findNode("currentTimeTimer")
|
||||
m.dateTimeObject = CreateObject("roDateTime")
|
||||
|
||||
m.currentTimeTimer.observeField("fire", "onCurrentTimeTimerFire")
|
||||
m.currentTimeTimer.control = "start"
|
||||
|
||||
' Default to 12 hour clock
|
||||
m.format = "short-h12"
|
||||
|
||||
' If user has selected a 24 hour clock, update date display format
|
||||
if LCase(m.global.device.clockFormat) = "24h"
|
||||
m.format = "short-h24"
|
||||
end if
|
||||
end sub
|
||||
|
||||
|
||||
' onCurrentTimeTimerFire: Code that runs every time the currentTimeTimer fires
|
||||
'
|
||||
sub onCurrentTimeTimerFire()
|
||||
' Refresh time variable
|
||||
m.dateTimeObject.Mark()
|
||||
|
||||
' Convert to local time zone
|
||||
m.dateTimeObject.ToLocalTime()
|
||||
|
||||
' Format time as requested
|
||||
m.clockTime.text = m.dateTimeObject.asTimeStringLoc(m.format)
|
||||
end sub
|
9
components/Clock.xml
Normal file
9
components/Clock.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component name="Clock" extends="Group">
|
||||
<children>
|
||||
<Label id="clockTime" font="font:SmallSystemFont" horizAlign="right" vertAlign="center" height="64" width="200" />
|
||||
<Timer id="currentTimeTimer" repeat="true" duration="1" />
|
||||
</children>
|
||||
<interface>
|
||||
</interface>
|
||||
</component>
|
|
@ -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
|
||||
|
||||
|
|
227
components/video/PauseMenu.bs
Normal file
227
components/video/PauseMenu.bs
Normal file
|
@ -0,0 +1,227 @@
|
|||
import "pkg:/source/utils/misc.brs"
|
||||
|
||||
sub init()
|
||||
m.videoControls = m.top.findNode("videoControls")
|
||||
m.optionControls = m.top.findNode("optionControls")
|
||||
|
||||
m.inactivityTimer = m.top.findNode("inactivityTimer")
|
||||
m.itemTitle = m.top.findNode("itemTitle")
|
||||
m.videoPlayPause = m.top.findNode("videoPlayPause")
|
||||
|
||||
m.top.observeField("visible", "onVisibleChanged")
|
||||
m.top.observeField("hasFocus", "onFocusChanged")
|
||||
m.top.observeField("playbackState", "onPlaybackStateChanged")
|
||||
m.top.observeField("itemTitleText", "onItemTitleTextChanged")
|
||||
|
||||
m.defaultButtonIndex = 1
|
||||
m.focusedButtonIndex = 1
|
||||
|
||||
m.videoControls.buttonFocused = m.defaultButtonIndex
|
||||
m.optionControls.buttonFocused = m.optionControls.getChildCount() - 1
|
||||
|
||||
m.videoControls.getChild(m.defaultButtonIndex).focus = true
|
||||
m.deviceInfo = CreateObject("roDeviceInfo")
|
||||
end sub
|
||||
|
||||
' onPlaybackStateChanged: Handler for changes to m.top.playbackState param
|
||||
'
|
||||
sub onPlaybackStateChanged()
|
||||
if LCase(m.top.playbackState) = "playing"
|
||||
m.videoPlayPause.icon = "pkg:/images/icons/pause.png"
|
||||
return
|
||||
end if
|
||||
|
||||
m.videoPlayPause.icon = "pkg:/images/icons/play.png"
|
||||
end sub
|
||||
|
||||
' onItemTitleTextChanged: Handler for changes to m.top.itemTitleText param.
|
||||
'
|
||||
sub onItemTitleTextChanged()
|
||||
m.itemTitle.text = m.top.itemTitleText
|
||||
end sub
|
||||
|
||||
' resetFocusToDefaultButton: Reset focus back to the default button
|
||||
'
|
||||
sub resetFocusToDefaultButton()
|
||||
' Remove focus from previously selected button
|
||||
for each child in m.videoControls.getChildren(-1, 0)
|
||||
if isValid(child.focus)
|
||||
child.focus = false
|
||||
end if
|
||||
end for
|
||||
|
||||
for each child in m.optionControls.getChildren(-1, 0)
|
||||
if isValid(child.focus)
|
||||
child.focus = false
|
||||
end if
|
||||
end for
|
||||
|
||||
m.optionControls.setFocus(false)
|
||||
|
||||
' Set focus back to the default button
|
||||
m.videoControls.setFocus(true)
|
||||
m.focusedButtonIndex = m.defaultButtonIndex
|
||||
m.videoControls.getChild(m.defaultButtonIndex).focus = true
|
||||
m.videoControls.buttonFocused = 1
|
||||
m.optionControls.buttonFocused = m.optionControls.getChildCount() - 1
|
||||
end sub
|
||||
|
||||
' onVisibleChanged: Handler for changes to the visibility of this pause menu.
|
||||
'
|
||||
sub onVisibleChanged()
|
||||
if m.top.visible
|
||||
resetFocusToDefaultButton()
|
||||
m.inactivityTimer.observeField("fire", "inactiveCheck")
|
||||
m.inactivityTimer.control = "start"
|
||||
return
|
||||
end if
|
||||
|
||||
m.inactivityTimer.unobserveField("fire")
|
||||
m.inactivityTimer.control = "stop"
|
||||
end sub
|
||||
|
||||
' onFocusChanged: Handler for changes to the focus of this pause menu.
|
||||
'
|
||||
sub onFocusChanged()
|
||||
if m.top.hasfocus
|
||||
focusedButton = m.optionControls.getChild(m.focusedButtonIndex)
|
||||
if focusedButton.focus
|
||||
m.optionControls.setFocus(true)
|
||||
return
|
||||
end if
|
||||
|
||||
m.videoControls.setFocus(true)
|
||||
end if
|
||||
end sub
|
||||
|
||||
' inactiveCheck: Checks if the time since last keypress is greater than or equal to the allowed inactive time of the pause menu.
|
||||
'
|
||||
sub inactiveCheck()
|
||||
if m.deviceInfo.timeSinceLastKeypress() >= m.top.inactiveTimeout
|
||||
m.top.action = "hide"
|
||||
end if
|
||||
end sub
|
||||
|
||||
' onButtonSelected: Handler for selection of buttons from the pause menu.
|
||||
'
|
||||
sub onButtonSelected()
|
||||
if m.optionControls.isInFocusChain()
|
||||
buttonGroup = m.optionControls
|
||||
else
|
||||
buttonGroup = m.videoControls
|
||||
end if
|
||||
|
||||
selectedButton = buttonGroup.getChild(m.focusedButtonIndex)
|
||||
|
||||
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 = "play"
|
||||
m.top.action = "videoplaypause"
|
||||
return true
|
||||
end if
|
||||
|
||||
if key = "right"
|
||||
if m.optionControls.isInFocusChain()
|
||||
buttonGroup = m.optionControls
|
||||
else
|
||||
buttonGroup = m.videoControls
|
||||
end if
|
||||
|
||||
if m.focusedButtonIndex + 1 >= buttonGroup.getChildCount()
|
||||
return true
|
||||
end if
|
||||
|
||||
focusedButton = buttonGroup.getChild(m.focusedButtonIndex)
|
||||
focusedButton.focus = false
|
||||
|
||||
' Skip spacer elements until next button is found
|
||||
for i = m.focusedButtonIndex + 1 to buttonGroup.getChildCount()
|
||||
m.focusedButtonIndex = i
|
||||
focusedButton = buttonGroup.getChild(m.focusedButtonIndex)
|
||||
|
||||
if isValid(focusedButton.focus)
|
||||
buttonGroup.buttonFocused = m.focusedButtonIndex
|
||||
focusedButton.focus = true
|
||||
exit for
|
||||
end if
|
||||
end for
|
||||
|
||||
return true
|
||||
end if
|
||||
|
||||
if key = "left"
|
||||
if m.focusedButtonIndex = 0
|
||||
return true
|
||||
end if
|
||||
|
||||
if m.optionControls.isInFocusChain()
|
||||
buttonGroup = m.optionControls
|
||||
else
|
||||
buttonGroup = m.videoControls
|
||||
end if
|
||||
|
||||
focusedButton = buttonGroup.getChild(m.focusedButtonIndex)
|
||||
focusedButton.focus = false
|
||||
|
||||
' Skip spacer elements until next button is found
|
||||
for i = m.focusedButtonIndex - 1 to 0 step -1
|
||||
m.focusedButtonIndex = i
|
||||
focusedButton = buttonGroup.getChild(m.focusedButtonIndex)
|
||||
|
||||
if isValid(focusedButton.focus)
|
||||
buttonGroup.buttonFocused = m.focusedButtonIndex
|
||||
focusedButton.focus = true
|
||||
exit for
|
||||
end if
|
||||
end for
|
||||
|
||||
return true
|
||||
end if
|
||||
|
||||
if key = "up"
|
||||
if m.videoControls.isInFocusChain()
|
||||
focusedButton = m.videoControls.getChild(m.focusedButtonIndex)
|
||||
focusedButton.focus = false
|
||||
m.videoControls.setFocus(false)
|
||||
|
||||
m.focusedButtonIndex = m.optionControls.buttonFocused
|
||||
focusedButton = m.optionControls.getChild(m.focusedButtonIndex)
|
||||
focusedButton.focus = true
|
||||
m.optionControls.setFocus(true)
|
||||
end if
|
||||
|
||||
return true
|
||||
end if
|
||||
|
||||
if key = "down"
|
||||
if m.optionControls.isInFocusChain()
|
||||
focusedButton = m.optionControls.getChild(m.focusedButtonIndex)
|
||||
focusedButton.focus = false
|
||||
m.optionControls.setFocus(false)
|
||||
|
||||
m.focusedButtonIndex = m.videoControls.buttonFocused
|
||||
focusedButton = m.videoControls.getChild(m.focusedButtonIndex)
|
||||
focusedButton.focus = true
|
||||
m.videoControls.setFocus(true)
|
||||
end if
|
||||
|
||||
return true
|
||||
end if
|
||||
|
||||
' All other keys hide the menu
|
||||
m.top.action = "hide"
|
||||
return true
|
||||
end function
|
29
components/video/PauseMenu.xml
Normal file
29
components/video/PauseMenu.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component name="PauseMenu" extends="Group" initialFocus="chapterNext">
|
||||
<children>
|
||||
<Label id="itemTitle" font="font:LargeBoldSystemFont" translation="[103,61]" />
|
||||
<Clock id="clock" translation="[1618, 46]" />
|
||||
|
||||
<ButtonGroup id="optionControls" itemSpacings="[20]" layoutDirection="horiz" horizAlignment="left" translation="[103,120]">
|
||||
<IconButton id="showVideoInfoPopup" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/videoInfo.png" height="65" width="100" />
|
||||
<IconButton id="chapterList" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/numberList.png" height="65" width="100" />
|
||||
<IconButton id="showSubtitleMenu" background="#070707" focusBackground="#00a4dc" padding="0" icon="pkg:/images/icons/subtitle.png" height="65" width="100" />
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup id="videoControls" itemSpacings="[20]" layoutDirection="horiz" horizAlignment="center" translation="[960,950]">
|
||||
<IconButton id="chapterBack" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/previousChapter.png" height="65" width="100" />
|
||||
<IconButton id="videoPlayPause" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/play.png" height="65" width="100" />
|
||||
<IconButton id="chapterNext" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/nextChapter.png" height="65" width="100" />
|
||||
</ButtonGroup>
|
||||
|
||||
<Timer id="inactivityTimer" duration="1" repeat="true" />
|
||||
</children>
|
||||
<interface>
|
||||
<field id="itemTitleText" type="string" />
|
||||
<field id="inactiveTimeout" type="integer" />
|
||||
<field id="playbackState" type="string" alwaysNotify="true" />
|
||||
<field id="action" type="string" alwaysNotify="true" />
|
||||
<field id="showChapterList" type="boolean" alwaysNotify="true" />
|
||||
<field id="hasFocus" type="boolean" alwaysNotify="true" />
|
||||
</interface>
|
||||
</component>
|
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -55,7 +65,154 @@ 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
|
||||
'
|
||||
' @param {boolean} resume - controls whether or not to resume video playback when sub is called
|
||||
'
|
||||
sub handleHideAction(resume as boolean)
|
||||
m.pauseMenu.visible = false
|
||||
m.chapterList.visible = false
|
||||
m.pauseMenu.showChapterList = false
|
||||
m.chapterList.setFocus(false)
|
||||
m.pauseMenu.hasFocus = false
|
||||
m.pauseMenu.setFocus(false)
|
||||
m.top.setFocus(true)
|
||||
if resume
|
||||
m.top.control = "resume"
|
||||
end if
|
||||
end sub
|
||||
|
||||
' handleChapterListAction: 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.hasFocus = false
|
||||
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
|
||||
|
||||
' handleVideoPlayPauseAction: Handles action to either play or pause the video content
|
||||
'
|
||||
sub handleVideoPlayPauseAction()
|
||||
' If video is paused, resume it
|
||||
if m.top.state = "paused"
|
||||
handleHideAction(true)
|
||||
return
|
||||
end if
|
||||
|
||||
' Pause video
|
||||
m.top.control = "pause"
|
||||
end sub
|
||||
|
||||
' handleShowSubtitleMenuAction: Handles action to show subtitle selection menu
|
||||
'
|
||||
sub handleShowSubtitleMenuAction()
|
||||
m.top.selectSubtitlePressed = true
|
||||
handleHideAction(false)
|
||||
end sub
|
||||
|
||||
' handleShowVideoInfoPopupAction: Handles action to show video info popup
|
||||
'
|
||||
sub handleShowVideoInfoPopupAction()
|
||||
m.top.selectPlaybackInfoPressed = true
|
||||
handleHideAction(false)
|
||||
end sub
|
||||
|
||||
' onPauseMenuAction: Process action events from pause menu to their respective handlers
|
||||
'
|
||||
sub onPauseMenuAction()
|
||||
action = LCase(m.pauseMenu.action)
|
||||
|
||||
if action = "hide"
|
||||
handleHideAction(false)
|
||||
return
|
||||
end if
|
||||
|
||||
if action = "play"
|
||||
handleHideAction(true)
|
||||
return
|
||||
end if
|
||||
|
||||
if action = "chapterback" or action = "chapternext"
|
||||
handleChapterSkipAction(action)
|
||||
return
|
||||
end if
|
||||
|
||||
if action = "chapterlist"
|
||||
handleChapterListAction()
|
||||
return
|
||||
end if
|
||||
|
||||
if action = "videoplaypause"
|
||||
handleVideoPlayPauseAction()
|
||||
return
|
||||
end if
|
||||
|
||||
if action = "showsubtitlemenu"
|
||||
handleShowSubtitleMenuAction()
|
||||
return
|
||||
end if
|
||||
|
||||
if action = "showvideoinfopopup"
|
||||
handleShowVideoInfoPopupAction()
|
||||
return
|
||||
end if
|
||||
end sub
|
||||
|
||||
' Only setup caption items if captions are allowed
|
||||
sub onAllowCaptionsChange()
|
||||
if not m.top.allowCaptions then return
|
||||
|
||||
|
@ -161,6 +318,11 @@ 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
|
||||
|
||||
m.pauseMenu.itemTitleText = m.top.content.title
|
||||
|
||||
populateChapterMenu()
|
||||
|
||||
if m.LoadMetaDataTask.isIntro
|
||||
' Disable trackplay bar for intro videos
|
||||
|
@ -180,6 +342,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
|
||||
|
@ -196,6 +380,7 @@ end sub
|
|||
'
|
||||
' Runs Next Episode button animation and sets focus to button
|
||||
sub showNextEpisodeButton()
|
||||
if m.pauseMenu.visible then return
|
||||
if m.top.content.contenttype <> 4 then return ' only display when content is type "Episode"
|
||||
if m.nextupbuttonseconds = 0 then return ' is the button disabled?
|
||||
|
||||
|
@ -273,6 +458,9 @@ sub onState(msg)
|
|||
m.captionTask.playerState = m.top.state + m.top.globalCaptionMode
|
||||
end if
|
||||
|
||||
' Pass video state into pause menu
|
||||
m.pauseMenu.playbackState = m.top.state
|
||||
|
||||
' When buffering, start timer to monitor buffering process
|
||||
if m.top.state = "buffering" and m.bufferCheckTimer <> invalid
|
||||
|
||||
|
@ -376,6 +564,38 @@ 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"
|
||||
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.hasFocus = true
|
||||
m.pauseMenu.setFocus(true)
|
||||
return true
|
||||
end if
|
||||
|
||||
if key = "play"
|
||||
handleVideoPlayPauseAction()
|
||||
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"
|
||||
|
@ -393,26 +613,48 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
|||
if not press then return false
|
||||
|
||||
if key = "down"
|
||||
' Do not show subtitle selection for intro videos
|
||||
if not m.LoadMetaDataTask.isIntro
|
||||
m.top.selectSubtitlePressed = true
|
||||
m.pauseMenu.visible = true
|
||||
m.pauseMenu.hasFocus = true
|
||||
m.pauseMenu.setFocus(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
|
||||
m.pauseMenu.visible = true
|
||||
m.pauseMenu.hasFocus = true
|
||||
m.pauseMenu.setFocus(true)
|
||||
return true
|
||||
end if
|
||||
else if key = "OK"
|
||||
' OK will play/pause depending on current state
|
||||
' return false to allow selection during seeking
|
||||
if m.top.state = "paused"
|
||||
m.top.control = "resume"
|
||||
return false
|
||||
else if m.top.state = "playing"
|
||||
|
||||
else if key = "OK" and not m.top.trickPlayBar.visible
|
||||
if not m.LoadMetaDataTask.isIntro
|
||||
' Show pause menu, but don't pause video
|
||||
m.pauseMenu.visible = true
|
||||
m.pauseMenu.hasFocus = true
|
||||
m.pauseMenu.setFocus(true)
|
||||
return true
|
||||
end if
|
||||
|
||||
return false
|
||||
end if
|
||||
|
||||
' Disable pause menu for intro videos
|
||||
if not m.LoadMetaDataTask.isIntro
|
||||
if key = "play" and not m.top.trickPlayBar.visible
|
||||
' If video is paused, resume it and don't show pause menu
|
||||
if m.top.state = "paused"
|
||||
m.top.control = "resume"
|
||||
return true
|
||||
end if
|
||||
|
||||
' Pause video and show pause menu
|
||||
m.top.control = "pause"
|
||||
return false
|
||||
m.pauseMenu.visible = true
|
||||
m.pauseMenu.hasFocus = true
|
||||
m.pauseMenu.setFocus(true)
|
||||
return true
|
||||
end if
|
||||
end if
|
||||
|
||||
|
|
|
@ -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" inactiveTimeout="5" />
|
||||
|
||||
<Rectangle id="chapterList" visible="false" color="0x00000098" width="400" height="380" translation="[103,210]">
|
||||
<LabelList id="chaptermenu" itemSpacing="[0,20]" numRows="5" font="font:SmallSystemFont" itemSize="[315,40]" translation="[40,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-->
|
||||
|
|
BIN
images/icons/nextChapter.png
Normal file
BIN
images/icons/nextChapter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
BIN
images/icons/numberList.png
Normal file
BIN
images/icons/numberList.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
images/icons/pause.png
Normal file
BIN
images/icons/pause.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 734 B |
BIN
images/icons/previousChapter.png
Normal file
BIN
images/icons/previousChapter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
BIN
images/icons/subtitle.png
Normal file
BIN
images/icons/subtitle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
BIN
images/icons/videoInfo.png
Normal file
BIN
images/icons/videoInfo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
|
@ -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>
|
|
@ -69,7 +69,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
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user