2023-11-11 13:41:20 +00:00
|
|
|
<!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Source: components/music/AudioPlayerView.bs</title><!--[if lt IE 9]>
|
|
|
|
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
|
|
<![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(baseURL=(baseURL="https://jellyfin.github.io/jellyfin-roku/").replace(/https?:\/\//i,"")).substr(baseURL.indexOf("/"))</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d=
|
2023-11-11 02:08:52 +00:00
|
|
|
import "pkg:/source/api/Image.bs"
|
|
|
|
import "pkg:/source/api/baserequest.bs"
|
|
|
|
import "pkg:/source/utils/config.bs"
|
2023-10-06 03:18:36 +00:00
|
|
|
|
|
|
|
sub init()
|
|
|
|
m.top.optionsAvailable = false
|
|
|
|
|
|
|
|
setupAudioNode()
|
|
|
|
setupAnimationTasks()
|
|
|
|
setupButtons()
|
|
|
|
setupInfoNodes()
|
|
|
|
setupDataTasks()
|
|
|
|
setupScreenSaver()
|
|
|
|
|
|
|
|
m.playlistTypeCount = m.global.queueManager.callFunc("getQueueUniqueTypes").count()
|
|
|
|
|
|
|
|
m.buttonCount = m.buttons.getChildCount()
|
|
|
|
|
|
|
|
m.screenSaverTimeout = 300
|
|
|
|
|
|
|
|
m.LoadScreenSaverTimeoutTask.observeField("content", "onScreensaverTimeoutLoaded")
|
|
|
|
m.LoadScreenSaverTimeoutTask.control = "RUN"
|
|
|
|
|
|
|
|
m.di = CreateObject("roDeviceInfo")
|
|
|
|
|
|
|
|
' Write screen tracker for screensaver
|
|
|
|
WriteAsciiFile("tmp:/scene.temp", "nowplaying")
|
|
|
|
MoveFile("tmp:/scene.temp", "tmp:/scene")
|
|
|
|
|
|
|
|
loadButtons()
|
|
|
|
pageContentChanged()
|
|
|
|
setShuffleIconState()
|
|
|
|
setLoopButtonImage()
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub onScreensaverTimeoutLoaded()
|
|
|
|
data = m.LoadScreenSaverTimeoutTask.content
|
|
|
|
m.LoadScreenSaverTimeoutTask.unobserveField("content")
|
|
|
|
if isValid(data)
|
|
|
|
m.screenSaverTimeout = data
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub setupScreenSaver()
|
|
|
|
m.screenSaverBackground = m.top.FindNode("screenSaverBackground")
|
|
|
|
|
|
|
|
' Album Art Screensaver
|
|
|
|
m.screenSaverAlbumCover = m.top.FindNode("screenSaverAlbumCover")
|
|
|
|
m.screenSaverAlbumAnimation = m.top.findNode("screenSaverAlbumAnimation")
|
|
|
|
m.screenSaverAlbumCoverFadeIn = m.top.findNode("screenSaverAlbumCoverFadeIn")
|
|
|
|
|
|
|
|
' Jellyfin Screensaver
|
|
|
|
m.PosterOne = m.top.findNode("PosterOne")
|
|
|
|
m.PosterOne.uri = "pkg:/images/logo.png"
|
|
|
|
m.BounceAnimation = m.top.findNode("BounceAnimation")
|
|
|
|
m.PosterOneFadeIn = m.top.findNode("PosterOneFadeIn")
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub setupAnimationTasks()
|
|
|
|
m.displayButtonsAnimation = m.top.FindNode("displayButtonsAnimation")
|
|
|
|
m.playPositionAnimation = m.top.FindNode("playPositionAnimation")
|
|
|
|
m.playPositionAnimationWidth = m.top.FindNode("playPositionAnimationWidth")
|
|
|
|
|
|
|
|
m.bufferPositionAnimation = m.top.FindNode("bufferPositionAnimation")
|
|
|
|
m.bufferPositionAnimationWidth = m.top.FindNode("bufferPositionAnimationWidth")
|
|
|
|
|
|
|
|
m.screenSaverStartAnimation = m.top.FindNode("screenSaverStartAnimation")
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' Creates tasks to gather data needed to render Scene and play song
|
|
|
|
sub setupDataTasks()
|
|
|
|
' Load meta data
|
|
|
|
m.LoadMetaDataTask = CreateObject("roSGNode", "LoadItemsTask")
|
|
|
|
m.LoadMetaDataTask.itemsToLoad = "metaData"
|
|
|
|
|
|
|
|
' Load background image
|
|
|
|
m.LoadBackdropImageTask = CreateObject("roSGNode", "LoadItemsTask")
|
|
|
|
m.LoadBackdropImageTask.itemsToLoad = "backdropImage"
|
|
|
|
|
|
|
|
' Load audio stream
|
|
|
|
m.LoadAudioStreamTask = CreateObject("roSGNode", "LoadItemsTask")
|
|
|
|
m.LoadAudioStreamTask.itemsToLoad = "audioStream"
|
|
|
|
|
|
|
|
m.LoadScreenSaverTimeoutTask = CreateObject("roSGNode", "LoadScreenSaverTimeoutTask")
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' Creates audio node used to play song(s)
|
|
|
|
sub setupAudioNode()
|
|
|
|
m.global.audioPlayer.observeField("state", "audioStateChanged")
|
|
|
|
m.global.audioPlayer.observeField("position", "audioPositionChanged")
|
|
|
|
m.global.audioPlayer.observeField("bufferingStatus", "bufferPositionChanged")
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' Setup playback buttons, default to Play button selected
|
|
|
|
sub setupButtons()
|
|
|
|
m.buttons = m.top.findNode("buttons")
|
|
|
|
m.top.observeField("selectedButtonIndex", "onButtonSelectedChange")
|
|
|
|
m.previouslySelectedButtonIndex = 1
|
|
|
|
m.top.selectedButtonIndex = 2
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' Event handler when user selected a different playback button
|
|
|
|
sub onButtonSelectedChange()
|
|
|
|
' Change previously selected button back to default image
|
|
|
|
selectedButton = m.buttons.getChild(m.previouslySelectedButtonIndex)
|
|
|
|
selectedButton.uri = selectedButton.uri.Replace("-selected", "-default")
|
|
|
|
|
|
|
|
' Change selected button image to selected image
|
|
|
|
selectedButton = m.buttons.getChild(m.top.selectedButtonIndex)
|
|
|
|
selectedButton.uri = selectedButton.uri.Replace("-default", "-selected")
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub setupInfoNodes()
|
|
|
|
m.albumCover = m.top.findNode("albumCover")
|
|
|
|
m.backDrop = m.top.findNode("backdrop")
|
|
|
|
m.playPosition = m.top.findNode("playPosition")
|
|
|
|
m.bufferPosition = m.top.findNode("bufferPosition")
|
|
|
|
m.seekBar = m.top.findNode("seekBar")
|
|
|
|
m.shuffleIndicator = m.top.findNode("shuffleIndicator")
|
|
|
|
m.loopIndicator = m.top.findNode("loopIndicator")
|
|
|
|
m.positionTimestamp = m.top.findNode("positionTimestamp")
|
|
|
|
m.totalLengthTimestamp = m.top.findNode("totalLengthTimestamp")
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub bufferPositionChanged()
|
|
|
|
if not isValid(m.global.audioPlayer.bufferingStatus)
|
|
|
|
bufferPositionBarWidth = m.seekBar.width
|
|
|
|
else
|
|
|
|
bufferPositionBarWidth = m.seekBar.width * m.global.audioPlayer.bufferingStatus.percentage
|
|
|
|
end if
|
|
|
|
|
|
|
|
' Ensure position bar is never wider than the seek bar
|
|
|
|
if bufferPositionBarWidth > m.seekBar.width
|
|
|
|
bufferPositionBarWidth = m.seekBar.width
|
|
|
|
end if
|
|
|
|
|
|
|
|
' Use animation to make the display smooth
|
|
|
|
m.bufferPositionAnimationWidth.keyValue = [m.bufferPosition.width, bufferPositionBarWidth]
|
|
|
|
m.bufferPositionAnimation.control = "start"
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub audioPositionChanged()
|
|
|
|
if m.global.audioPlayer.position = 0
|
|
|
|
m.playPosition.width = 0
|
|
|
|
end if
|
|
|
|
|
|
|
|
if not isValid(m.global.audioPlayer.position)
|
|
|
|
playPositionBarWidth = 0
|
|
|
|
else if not isValid(m.songDuration)
|
|
|
|
playPositionBarWidth = 0
|
|
|
|
else
|
|
|
|
songPercentComplete = m.global.audioPlayer.position / m.songDuration
|
|
|
|
playPositionBarWidth = m.seekBar.width * songPercentComplete
|
|
|
|
end if
|
|
|
|
|
|
|
|
' Ensure position bar is never wider than the seek bar
|
|
|
|
if playPositionBarWidth > m.seekBar.width
|
|
|
|
playPositionBarWidth = m.seekBar.width
|
|
|
|
end if
|
|
|
|
|
|
|
|
' Use animation to make the display smooth
|
|
|
|
m.playPositionAnimationWidth.keyValue = [m.playPosition.width, playPositionBarWidth]
|
|
|
|
m.playPositionAnimation.control = "start"
|
|
|
|
|
|
|
|
' Update displayed position timestamp
|
|
|
|
if isValid(m.global.audioPlayer.position)
|
2023-11-12 18:13:07 +00:00
|
|
|
m.positionTimestamp.text = secondsToHuman(m.global.audioPlayer.position, false)
|
2023-10-06 03:18:36 +00:00
|
|
|
else
|
|
|
|
m.positionTimestamp.text = "0:00"
|
|
|
|
end if
|
|
|
|
|
|
|
|
' Only fall into screensaver logic if the user has screensaver enabled in Roku settings
|
|
|
|
if m.screenSaverTimeout > 0
|
|
|
|
if m.di.TimeSinceLastKeypress() >= m.screenSaverTimeout - 2
|
|
|
|
if not screenSaverActive()
|
|
|
|
startScreenSaver()
|
|
|
|
end if
|
|
|
|
end if
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
function screenSaverActive() as boolean
|
|
|
|
return m.screenSaverBackground.visible or m.screenSaverAlbumCover.opacity > 0 or m.PosterOne.opacity > 0
|
|
|
|
end function
|
|
|
|
|
|
|
|
sub startScreenSaver()
|
|
|
|
m.screenSaverBackground.visible = true
|
|
|
|
m.top.overhangVisible = false
|
|
|
|
|
|
|
|
if m.albumCover.uri = ""
|
|
|
|
' Jellyfin Logo Screensaver
|
|
|
|
m.PosterOne.visible = true
|
|
|
|
m.PosterOneFadeIn.control = "start"
|
|
|
|
m.BounceAnimation.control = "start"
|
|
|
|
else
|
|
|
|
' Album Art Screensaver
|
|
|
|
m.screenSaverAlbumCoverFadeIn.control = "start"
|
|
|
|
m.screenSaverAlbumAnimation.control = "start"
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub endScreenSaver()
|
|
|
|
m.PosterOneFadeIn.control = "pause"
|
|
|
|
m.screenSaverAlbumCoverFadeIn.control = "pause"
|
|
|
|
m.screenSaverAlbumAnimation.control = "pause"
|
|
|
|
m.BounceAnimation.control = "pause"
|
|
|
|
m.screenSaverBackground.visible = false
|
|
|
|
m.screenSaverAlbumCover.opacity = 0
|
|
|
|
m.PosterOne.opacity = 0
|
|
|
|
m.top.overhangVisible = true
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub audioStateChanged()
|
|
|
|
|
|
|
|
' Song Finished, attempt to move to next song
|
|
|
|
if m.global.audioPlayer.state = "finished"
|
|
|
|
' User has enabled single song loop, play current song again
|
|
|
|
if m.global.audioPlayer.loopMode = "one"
|
|
|
|
playAction()
|
|
|
|
return
|
|
|
|
end if
|
|
|
|
|
|
|
|
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
|
|
|
|
m.top.state = "finished"
|
|
|
|
else
|
|
|
|
' We are at the end of the song queue
|
|
|
|
|
|
|
|
' User has enabled loop for entire song queue, move back to first song
|
|
|
|
if m.global.audioPlayer.loopMode = "all"
|
|
|
|
m.global.queueManager.callFunc("setPosition", -1)
|
|
|
|
LoadNextSong()
|
|
|
|
return
|
|
|
|
end if
|
|
|
|
|
|
|
|
' Return to previous screen
|
|
|
|
m.top.state = "finished"
|
|
|
|
end if
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
function playAction() as boolean
|
|
|
|
if m.global.audioPlayer.state = "playing"
|
|
|
|
m.global.audioPlayer.control = "pause"
|
|
|
|
' Allow screen to go to real screensaver
|
|
|
|
WriteAsciiFile("tmp:/scene.temp", "nowplaying-paused")
|
|
|
|
MoveFile("tmp:/scene.temp", "tmp:/scene")
|
|
|
|
else if m.global.audioPlayer.state = "paused"
|
|
|
|
m.global.audioPlayer.control = "resume"
|
|
|
|
' Write screen tracker for screensaver
|
|
|
|
WriteAsciiFile("tmp:/scene.temp", "nowplaying")
|
|
|
|
MoveFile("tmp:/scene.temp", "tmp:/scene")
|
|
|
|
else if m.global.audioPlayer.state = "finished"
|
|
|
|
m.global.audioPlayer.control = "play"
|
|
|
|
' Write screen tracker for screensaver
|
|
|
|
WriteAsciiFile("tmp:/scene.temp", "nowplaying")
|
|
|
|
MoveFile("tmp:/scene.temp", "tmp:/scene")
|
|
|
|
end if
|
|
|
|
|
|
|
|
return true
|
|
|
|
end function
|
|
|
|
|
|
|
|
function previousClicked() as boolean
|
|
|
|
if m.playlistTypeCount > 1 then return false
|
|
|
|
if m.global.queueManager.callFunc("getPosition") = 0 then return false
|
|
|
|
|
|
|
|
if m.global.audioPlayer.state = "playing"
|
|
|
|
m.global.audioPlayer.control = "stop"
|
|
|
|
end if
|
|
|
|
|
|
|
|
' Reset loop mode due to manual user interaction
|
|
|
|
if m.global.audioPlayer.loopMode = "one"
|
|
|
|
resetLoopModeToDefault()
|
|
|
|
end if
|
|
|
|
|
|
|
|
m.global.queueManager.callFunc("moveBack")
|
|
|
|
pageContentChanged()
|
|
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
end function
|
|
|
|
|
|
|
|
sub resetLoopModeToDefault()
|
|
|
|
m.global.audioPlayer.loopMode = ""
|
|
|
|
setLoopButtonImage()
|
|
|
|
end sub
|
|
|
|
|
|
|
|
function loopClicked() as boolean
|
|
|
|
|
|
|
|
if m.global.audioPlayer.loopMode = ""
|
|
|
|
m.global.audioPlayer.loopMode = "all"
|
|
|
|
else if m.global.audioPlayer.loopMode = "all"
|
|
|
|
m.global.audioPlayer.loopMode = "one"
|
|
|
|
else
|
|
|
|
m.global.audioPlayer.loopMode = ""
|
|
|
|
end if
|
|
|
|
|
|
|
|
setLoopButtonImage()
|
|
|
|
|
|
|
|
return true
|
|
|
|
end function
|
|
|
|
|
|
|
|
sub setLoopButtonImage()
|
|
|
|
if m.global.audioPlayer.loopMode = "all"
|
|
|
|
m.loopIndicator.opacity = "1"
|
|
|
|
m.loopIndicator.uri = m.loopIndicator.uri.Replace("-off", "-on")
|
|
|
|
else if m.global.audioPlayer.loopMode = "one"
|
|
|
|
m.loopIndicator.uri = m.loopIndicator.uri.Replace("-on", "1-on")
|
|
|
|
else
|
|
|
|
m.loopIndicator.uri = m.loopIndicator.uri.Replace("1-on", "-off")
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
function nextClicked() as boolean
|
|
|
|
if m.playlistTypeCount > 1 then return false
|
|
|
|
|
|
|
|
' Reset loop mode due to manual user interaction
|
|
|
|
if m.global.audioPlayer.loopMode = "one"
|
|
|
|
resetLoopModeToDefault()
|
|
|
|
end if
|
|
|
|
|
|
|
|
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
|
|
|
|
LoadNextSong()
|
|
|
|
end if
|
|
|
|
|
|
|
|
return true
|
|
|
|
end function
|
|
|
|
|
|
|
|
sub toggleShuffleEnabled()
|
|
|
|
m.global.queueManager.callFunc("toggleShuffle")
|
|
|
|
end sub
|
|
|
|
|
|
|
|
function findCurrentSongIndex(songList) as integer
|
|
|
|
if not isValidAndNotEmpty(songList) then return 0
|
|
|
|
|
|
|
|
for i = 0 to songList.count() - 1
|
|
|
|
if songList[i].id = m.global.queueManager.callFunc("getCurrentItem").id
|
|
|
|
return i
|
|
|
|
end if
|
|
|
|
end for
|
|
|
|
|
|
|
|
return 0
|
|
|
|
end function
|
|
|
|
|
|
|
|
function shuffleClicked() as boolean
|
|
|
|
|
|
|
|
currentSongIndex = findCurrentSongIndex(m.global.queueManager.callFunc("getUnshuffledQueue"))
|
|
|
|
|
|
|
|
toggleShuffleEnabled()
|
|
|
|
|
|
|
|
if not m.global.queueManager.callFunc("getIsShuffled")
|
|
|
|
m.shuffleIndicator.opacity = ".4"
|
|
|
|
m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-on", "-off")
|
|
|
|
m.global.queueManager.callFunc("setPosition", currentSongIndex)
|
|
|
|
setTrackNumberDisplay()
|
|
|
|
return true
|
|
|
|
end if
|
|
|
|
|
|
|
|
m.shuffleIndicator.opacity = "1"
|
|
|
|
m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-off", "-on")
|
|
|
|
setTrackNumberDisplay()
|
|
|
|
|
|
|
|
return true
|
|
|
|
end function
|
|
|
|
|
|
|
|
sub setShuffleIconState()
|
|
|
|
if m.global.queueManager.callFunc("getIsShuffled")
|
|
|
|
m.shuffleIndicator.opacity = "1"
|
|
|
|
m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-off", "-on")
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub setTrackNumberDisplay()
|
|
|
|
setFieldTextValue("numberofsongs", "Track " + stri(m.global.queueManager.callFunc("getPosition") + 1) + "/" + stri(m.global.queueManager.callFunc("getCount")))
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub LoadNextSong()
|
|
|
|
if m.global.audioPlayer.state = "playing"
|
|
|
|
m.global.audioPlayer.control = "stop"
|
|
|
|
end if
|
|
|
|
|
|
|
|
' Reset playPosition bar without animation
|
|
|
|
m.playPosition.width = 0
|
|
|
|
m.global.queueManager.callFunc("moveForward")
|
|
|
|
pageContentChanged()
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' Update values on screen when page content changes
|
|
|
|
sub pageContentChanged()
|
|
|
|
|
|
|
|
' Reset buffer bar without animation
|
|
|
|
m.bufferPosition.width = 0
|
|
|
|
|
|
|
|
useMetaTask = false
|
|
|
|
currentItem = m.global.queueManager.callFunc("getCurrentItem")
|
|
|
|
|
|
|
|
if not isValid(currentItem.RunTimeTicks)
|
|
|
|
useMetaTask = true
|
|
|
|
end if
|
|
|
|
|
|
|
|
if not isValid(currentItem.AlbumArtist)
|
|
|
|
useMetaTask = true
|
|
|
|
end if
|
|
|
|
|
|
|
|
if not isValid(currentItem.name)
|
|
|
|
useMetaTask = true
|
|
|
|
end if
|
|
|
|
|
|
|
|
if not isValid(currentItem.Artists)
|
|
|
|
useMetaTask = true
|
|
|
|
end if
|
|
|
|
|
|
|
|
if useMetaTask
|
|
|
|
m.LoadMetaDataTask.itemId = currentItem.id
|
|
|
|
m.LoadMetaDataTask.observeField("content", "onMetaDataLoaded")
|
|
|
|
m.LoadMetaDataTask.control = "RUN"
|
|
|
|
else
|
|
|
|
if isValid(currentItem.ParentBackdropItemId)
|
|
|
|
setBackdropImage(ImageURL(currentItem.ParentBackdropItemId, "Backdrop", { "maxHeight": "720", "maxWidth": "1280" }))
|
|
|
|
end if
|
|
|
|
|
|
|
|
setPosterImage(ImageURL(currentItem.id, "Primary", { "maxHeight": 500, "maxWidth": 500 }))
|
|
|
|
setScreenTitle(currentItem)
|
|
|
|
setOnScreenTextValues(currentItem)
|
|
|
|
m.songDuration = currentItem.RunTimeTicks / 10000000.0
|
|
|
|
|
|
|
|
' Update displayed total audio length
|
|
|
|
m.totalLengthTimestamp.text = ticksToHuman(currentItem.RunTimeTicks)
|
|
|
|
end if
|
|
|
|
|
|
|
|
m.LoadAudioStreamTask.itemId = currentItem.id
|
|
|
|
m.LoadAudioStreamTask.observeField("content", "onAudioStreamLoaded")
|
|
|
|
m.LoadAudioStreamTask.control = "RUN"
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' If we have more and 1 song to play, fade in the next and previous controls
|
|
|
|
sub loadButtons()
|
|
|
|
' Don't show audio buttons if we have a mixed playlist
|
|
|
|
if m.playlistTypeCount > 1 then return
|
|
|
|
|
|
|
|
if m.global.queueManager.callFunc("getCount") > 1
|
|
|
|
m.shuffleIndicator.opacity = ".4"
|
|
|
|
m.loopIndicator.opacity = ".4"
|
|
|
|
m.displayButtonsAnimation.control = "start"
|
|
|
|
setLoopButtonImage()
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub onAudioStreamLoaded()
|
|
|
|
data = m.LoadAudioStreamTask.content[0]
|
|
|
|
m.LoadAudioStreamTask.unobserveField("content")
|
|
|
|
if data <> invalid and data.count() > 0
|
|
|
|
m.global.audioPlayer.content = data
|
|
|
|
m.global.audioPlayer.control = "none"
|
|
|
|
m.global.audioPlayer.control = "play"
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub onBackdropImageLoaded()
|
|
|
|
data = m.LoadBackdropImageTask.content[0]
|
|
|
|
m.LoadBackdropImageTask.unobserveField("content")
|
|
|
|
if isValid(data) and data <> ""
|
|
|
|
setBackdropImage(data)
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
sub onMetaDataLoaded()
|
|
|
|
data = m.LoadMetaDataTask.content[0]
|
|
|
|
m.LoadMetaDataTask.unobserveField("content")
|
|
|
|
if isValid(data) and data.count() > 0 and isValid(data.json)
|
|
|
|
' Use metadata to load backdrop image
|
|
|
|
if isValid(data.json.ArtistItems) and isValid(data.json.ArtistItems[0]) and isValid(data.json.ArtistItems[0].id)
|
|
|
|
m.LoadBackdropImageTask.itemId = data.json.ArtistItems[0].id
|
|
|
|
m.LoadBackdropImageTask.observeField("content", "onBackdropImageLoaded")
|
|
|
|
m.LoadBackdropImageTask.control = "RUN"
|
|
|
|
end if
|
|
|
|
|
|
|
|
setPosterImage(data.posterURL)
|
|
|
|
setScreenTitle(data.json)
|
|
|
|
setOnScreenTextValues(data.json)
|
|
|
|
|
|
|
|
if isValid(data.json.RunTimeTicks)
|
|
|
|
m.songDuration = data.json.RunTimeTicks / 10000000.0
|
|
|
|
|
|
|
|
' Update displayed total audio length
|
|
|
|
m.totalLengthTimestamp.text = ticksToHuman(data.json.RunTimeTicks)
|
|
|
|
end if
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' Set poster image on screen
|
|
|
|
sub setPosterImage(posterURL)
|
|
|
|
if isValid(posterURL)
|
|
|
|
if m.albumCover.uri <> posterURL
|
|
|
|
m.albumCover.uri = posterURL
|
|
|
|
m.screenSaverAlbumCover.uri = posterURL
|
|
|
|
end if
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' Set screen's title text
|
|
|
|
sub setScreenTitle(json)
|
|
|
|
newTitle = ""
|
|
|
|
if isValid(json)
|
|
|
|
if isValid(json.AlbumArtist)
|
|
|
|
newTitle = json.AlbumArtist
|
|
|
|
end if
|
|
|
|
if isValid(json.AlbumArtist) and isValid(json.name)
|
|
|
|
newTitle = newTitle + " / "
|
|
|
|
end if
|
|
|
|
if isValid(json.name)
|
|
|
|
newTitle = newTitle + json.name
|
|
|
|
end if
|
|
|
|
end if
|
|
|
|
|
|
|
|
if m.top.overhangTitle <> newTitle
|
|
|
|
m.top.overhangTitle = newTitle
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' Populate on screen text variables
|
|
|
|
sub setOnScreenTextValues(json)
|
|
|
|
if isValid(json)
|
|
|
|
if m.playlistTypeCount = 1
|
|
|
|
setTrackNumberDisplay()
|
|
|
|
end if
|
|
|
|
|
|
|
|
setFieldTextValue("artist", json.Artists[0])
|
|
|
|
setFieldTextValue("song", json.name)
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' Add backdrop image to screen
|
|
|
|
sub setBackdropImage(data)
|
|
|
|
if isValid(data)
|
|
|
|
if m.backDrop.uri <> data
|
|
|
|
m.backDrop.uri = data
|
|
|
|
end if
|
|
|
|
end if
|
|
|
|
end sub
|
|
|
|
|
|
|
|
' Process key press events
|
|
|
|
function onKeyEvent(key as string, press as boolean) as boolean
|
|
|
|
|
|
|
|
' Key bindings for remote control buttons
|
|
|
|
if press
|
|
|
|
' If user presses key to turn off screensaver, don't do anything else with it
|
|
|
|
if screenSaverActive()
|
|
|
|
endScreenSaver()
|
|
|
|
return true
|
|
|
|
end if
|
|
|
|
|
|
|
|
if key = "play"
|
|
|
|
return playAction()
|
|
|
|
else if key = "back"
|
|
|
|
m.global.audioPlayer.control = "stop"
|
|
|
|
m.global.audioPlayer.loopMode = ""
|
|
|
|
else if key = "rewind"
|
|
|
|
return previousClicked()
|
|
|
|
else if key = "fastforward"
|
|
|
|
return nextClicked()
|
|
|
|
else if key = "left"
|
|
|
|
if m.global.queueManager.callFunc("getCount") = 1 then return false
|
|
|
|
|
|
|
|
if m.top.selectedButtonIndex > 0
|
|
|
|
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
|
|
|
|
m.top.selectedButtonIndex = m.top.selectedButtonIndex - 1
|
|
|
|
end if
|
|
|
|
return true
|
|
|
|
else if key = "right"
|
|
|
|
if m.global.queueManager.callFunc("getCount") = 1 then return false
|
|
|
|
|
|
|
|
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
|
|
|
|
if m.top.selectedButtonIndex < m.buttonCount - 1 then m.top.selectedButtonIndex = m.top.selectedButtonIndex + 1
|
|
|
|
return true
|
|
|
|
else if key = "OK"
|
|
|
|
if m.buttons.getChild(m.top.selectedButtonIndex).id = "play"
|
|
|
|
return playAction()
|
|
|
|
else if m.buttons.getChild(m.top.selectedButtonIndex).id = "previous"
|
|
|
|
return previousClicked()
|
|
|
|
else if m.buttons.getChild(m.top.selectedButtonIndex).id = "next"
|
|
|
|
return nextClicked()
|
|
|
|
else if m.buttons.getChild(m.top.selectedButtonIndex).id = "shuffle"
|
|
|
|
return shuffleClicked()
|
|
|
|
else if m.buttons.getChild(m.top.selectedButtonIndex).id = "loop"
|
|
|
|
return loopClicked()
|
|
|
|
end if
|
|
|
|
end if
|
|
|
|
end if
|
|
|
|
|
|
|
|
return false
|
|
|
|
end function
|
|
|
|
|
|
|
|
sub OnScreenHidden()
|
|
|
|
' Write screen tracker for screensaver
|
|
|
|
WriteAsciiFile("tmp:/scene.temp", "")
|
|
|
|
MoveFile("tmp:/scene.temp", "tmp:/scene")
|
|
|
|
end sub
|
2023-11-11 13:41:20 +00:00
|
|
|
</code></pre></article></section><footer class="footer" id="PeOAagUepe"><div class="wrapper"><span class="jsdoc-message">Automatically generated using <a href="https://github.com/jsdoc/jsdoc" target="_blank">JSDoc</a> and the <a href="https://github.com/ankitskvmdam/clean-jsdoc-theme" target="_blank">clean-jsdoc-theme</a>.</span></div></footer></div></div></div><div class="search-container" id="PkfLWpAbet" style="display:none"><div class="wrapper" id="iCxFxjkHbP"><button class="icon-button search-close-button" id="VjLlGakifb" aria-label="close search"><svg><use xlink:href="#close-icon"></use></svg></button><div class="search-box-c"><svg><use xlink:href="#search-icon"></use></svg> <input type="text" id="vpcKVYIppa" class="search-input" placeholder="Search..." autofocus></div><div class="search-result-c" id="fWwVHRuDuN"><span class="search-result-c-text">Type anything to view search result</span></div></div></div><div class="mobile-menu-icon-container"><button class="icon-button" id="mobile-menu" data-isopen="false" aria-label="menu"><svg><use xlink:href="#menu-icon"></use></svg></button></div><div id="mobile-sidebar" class="mobile-sidebar-container"><div class="mobile-sidebar-wrapper"><a href="/" class="sidebar-title sidebar-title-anchor">jellyfin-roku Code Documentation</a><div class="mobile-nav-links"><div class="external-link navbar-item"><a id="jellyfin-link-mobile" href="https://jellyfin.org/" target="_blank">Jellyfin</a></div><div class="external-link navbar-item"><a id="github-link-mobile" href="https://github.com/jellyfin/jellyfin-roku" target="_blank">GitHub</a></div><div class="external-link navbar-item"><a id="forum-link-mobile" href="https://forum.jellyfin.org/f-roku-development" target="_blank">Forum</a></div><div class="external-link navbar-item"><a id="matrix-link-mobile" href="https://matrix.to/#/#jellyfin-dev-roku:matrix.org" target="_blank">Matrix</a></div></div><div class="mobile-sidebar-items-c"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-AlbumData.html">AlbumData</a></div><div class="sidebar-section-children"><a href="module-AlbumGrid.html">AlbumGrid</a></div><div class="sidebar-section-children"><a href="module-AlbumTrackList.html">AlbumTrackList</a></div><div class="sidebar-section-children"><a href="module-AlbumView.html">AlbumView</a></div><div class="sidebar-section-children"><a href="module-Alpha.html">Alpha</a></div><div class="sidebar-section-children"><a href="module-ArtistView.html">ArtistView</a></div><div class="sidebar-section-children"><a href="module-AudioPlayer.html">AudioPlayer</a></div><div class="sidebar-section-children"><a href="module-AudioPlayerView.html">AudioPlayerView</a></div><div class="sidebar-section-children"><a href="module-AudioTrackListItem.html">AudioTrackListItem</a></div><div class="sidebar-section-children"><a href="module-ButtonGroupHoriz.html">ButtonGroupHoriz</a></div><div class="sidebar-section-children"><a href="module-ButtonGroupVert.html">ButtonGroupVert</a></div><div class="sidebar-section-children"><a href="module-ChannelData.html">ChannelData</a></div><div class="sidebar-section-children"><a href="module-Clock.html">Clock</a></div><div class="sidebar-section-children"><a href="module-CollectionData.html">CollectionData</a></div><div class="sidebar-section-children"><a href="module-ConfigData.html">ConfigData</a></div><div class="sidebar-section-children"><a href="module-ConfigItem.html">ConfigItem</a></div><div class="sidebar-section-children"><a href="module-ConfigList.html">ConfigList</a></div><div class="sidebar-section-children"><a href="module-ExtrasItem.html">ExtrasItem</a></div><div class="sidebar-section-children"><a href="module-ExtrasRowList.html">ExtrasRowList</a></div><div class="sidebar-section-children"><a href="module-FavoriteItemsTask.html">FavoriteItemsTask</a></div><div class="sidebar-section-children"><a href="module-Folder
|