Merge pull request #1198 from cewert/unstable
This commit is contained in:
commit
86305e420b
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
@ -6,6 +6,10 @@
|
|||
"request": "launch",
|
||||
"name": "Jellyfin Debug: Launch",
|
||||
"stopOnEntry": false,
|
||||
// To enable RALE:
|
||||
// set "brightscript.debug.raleTrackerTaskFileLocation": "/absolute/path/to/rale/TrackerTask.xml" in your vscode user settings
|
||||
// set the below field to true
|
||||
"injectRaleTrackerTask": false,
|
||||
//WARNING: don't edit this value. Instead, set "brightscript.debug.host": "YOUR_HOST_HERE" in your vscode user settings
|
||||
//"host": "${promptForHost}",
|
||||
//WARNING: don't edit this value. Instead, set "brightscript.debug.password": "YOUR_PASSWORD_HERE" in your vscode user settings
|
||||
|
|
|
@ -27,7 +27,7 @@ Follow the steps below to install the app on your personal Roku device. This wil
|
|||
|
||||
## Developer Mode
|
||||
|
||||
Put your Roku device in [developer mode](https://blog.roku.com/developer/2016/02/04/developer-setup-guide). Write down your Roku device IP and the password you created, you will need these later.
|
||||
Put your Roku device in [developer mode](https://blog.roku.com/developer/2016/02/04/developer-setup-guide). Write down your Roku device IP and the password you created - you will need these!
|
||||
|
||||
## Clone the GitHub Repo
|
||||
|
||||
|
@ -71,7 +71,7 @@ That's it! VSCode will auto-package the project, sideload it to the specified de
|
|||
|
||||
Out of the box, the BrightScript extension will prompt you to pick a Roku device (from devices found on your local network) and enter a password on every launch. If you'd prefer to hardcode this information rather than entering it every time, you can set these values in your VSCode user settings:
|
||||
|
||||
```js
|
||||
```json
|
||||
{
|
||||
"brightscript.debug.host": "YOUR_ROKU_HOST_HERE",
|
||||
"brightscript.debug.password": "YOUR_ROKU_DEV_PASSWORD_HERE",
|
||||
|
|
2
Makefile
2
Makefile
|
@ -10,7 +10,7 @@
|
|||
##########################################################################
|
||||
|
||||
APPNAME = Jellyfin_Roku
|
||||
VERSION = 1.6.4
|
||||
VERSION = 1.6.5
|
||||
|
||||
ZIP_EXCLUDE= -x xml/* -x artwork/* -x \*.pkg -x storeassets\* -x keys\* -x \*/.\* -x *.git* -x *.DS* -x *.pkg* -x dist/**\* -x out/**\*
|
||||
|
||||
|
|
62
README.md
62
README.md
|
@ -1,39 +1,41 @@
|
|||
<h1 style="text-align: center;">Jellyfin app for Roku</h1>
|
||||
<h3 style="text-align: center;">Part of the <a href="https://jellyfin.media/">Jellyfin</a> Project</h3>
|
||||
<h1 align="center">Jellyfin Roku</h1>
|
||||
<h2 align="center">Part of the <a href="https://jellyfin.org">Jellyfin Project</a></h2>
|
||||
|
||||
<p align="center">
|
||||
<img alt="Logo banner" src="https://raw.githubusercontent.com/jellyfin/jellyfin-ux/master/branding/SVG/banner-logo-solid.svg?sanitize=true"/>
|
||||
<br/><br/>
|
||||
<a href="https://github.com/jellyfin/jellyfin-roku">
|
||||
<img alt="GPL 2.0 License" src="https://img.shields.io/github/license/jellyfin/jellyfin-roku.svg"/>
|
||||
</a>
|
||||
<a href="https://github.com/jellyfin/jellyfin-roku/releases">
|
||||
<img alt="Current Release" src="https://img.shields.io/github/release/jellyfin/jellyfin-roku.svg"/>
|
||||
</a>
|
||||
<a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/?utm_source=widget">
|
||||
<img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-roku/svg-badge.svg" alt="Translation status" />
|
||||
</a>
|
||||
<br/>
|
||||
<a href="https://matrix.to/#/#jellyfin-dev-roku:matrix.org">
|
||||
<img alt="Chat on Matrix" src="https://img.shields.io/matrix/jellyfin:matrix.org.svg?logo=matrix"/>
|
||||
</a>
|
||||
<a href="https://www.reddit.com/r/jellyfin">
|
||||
<img alt="Join our Subreddit" src="https://img.shields.io/badge/reddit-r%2Fjellyfin-%23FF5700.svg"/>
|
||||
</a>
|
||||
</p>
|
||||
[![Logo Banner](https://raw.githubusercontent.com/jellyfin/jellyfin-ux/master/branding/SVG/banner-logo-solid.svg?sanitize=true "Jellyfin")](https://jellyfin.org)
|
||||
|
||||
The Jellyfin Roku App is a Jellyfin client for Roku Devices. This is still very much a work in progress, so we would encourage you to [get involved](#get_involved) if you can.
|
||||
[![Build Status](https://img.shields.io/github/actions/workflow/status/jellyfin/jellyfin-roku/build-dev.yml?logo=github&branch=unstable "Build Status")](https://github.com/jellyfin/jellyfin-roku/actions/workflows/build-dev.yml?query=branch%3Aunstable)
|
||||
[![Current Release](https://img.shields.io/github/release/jellyfin/jellyfin-roku.svg?logo=github "Current Release")](https://github.com/jellyfin/jellyfin-roku/releases)
|
||||
[![Translation Status](https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-roku/svg-badge.svg "Translation Status")](https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/?utm_source=widget)
|
||||
[![Matrix](https://img.shields.io/matrix/jellyfin:matrix.org.svg?logo=matrix "Chat on Matrix")](https://matrix.to/#/#jellyfin-dev-roku:matrix.org)
|
||||
[![Reddit](https://img.shields.io/badge/reddit-r%2Fjellyfin-%23FF5700.svg?logo=reddit "Join our Subreddit")](https://www.reddit.com/r/jellyfin)
|
||||
[![License](https://img.shields.io/github/license/jellyfin/jellyfin-roku.svg "GPL 2.0 License")](LICENSE)
|
||||
|
||||
## Getting Started
|
||||
Jellyfin Roku is the official Jellyfin client for Roku devices. We welcome all contributions and pull requests! If you have a larger feature in mind please [open an issue](https://github.com/jellyfin/jellyfin-roku/issues/new?assignees=&labels=feature&template=feature_request.md&title=) so we can discuss the implementation before you start.
|
||||
|
||||
The channel is available on the [Roku Channel Store](https://channelstore.roku.com/details/cc5e559d08d9ec87c5f30dcebdeebc12/jellyfin).
|
||||
## Install
|
||||
|
||||
## Getting Involved<a name="get_involved"></a>
|
||||
Download the latest release on the [Roku Channel Store](https://channelstore.roku.com/details/cc5e559d08d9ec87c5f30dcebdeebc12/jellyfin).
|
||||
|
||||
No matter what your interests or skill are, you can help to make this client better for everyone by simply using the client and letting us know if you find a problem with it. Either give us a shout on [matrix](https://matrix.to/#/+jellyfin:matrix.org) or create a GitHub issue.
|
||||
## Get Involved
|
||||
|
||||
Feature requests are always welcome too, but please have a read though the existing issues to see if someone has already raised one for something similar.
|
||||
No matter what your interests or skills are you can help make this client better for everyone by simply using the client and giving feedback to the developers when things break. [Create an issue](https://github.com/jellyfin/jellyfin-roku/issues/new/choose) here on GitHub or give us a shout on [Matrix](https://matrix.to/#/#jellyfin-dev-roku:matrix.org).
|
||||
|
||||
If you fancy some development, then read the [DEVGUIDE](DEVGUIDE.md) to find out the best ways to help.
|
||||
## Beta Test
|
||||
|
||||
As Roku have severely limited their Beta channel program, the best way to test pre-release versions is by following the [DEVGUIDE](DEVGUIDE.md) to install and test the latest changes. Feedback is always welcome.
|
||||
To test the latest features before they get released:
|
||||
|
||||
1. Put your Roku device in [developer mode](https://blog.roku.com/developer/2016/02/04/developer-setup-guide). Write down your Roku device IP and the password you created - you will need these!
|
||||
2. Download the [latest build](https://github.com/jellyfin/jellyfin-roku/actions/workflows/build-dev.yml?query=branch%3Aunstable). Select the first item listed then click the link at the bottom of the page i.e. `Jellyfin-Roku-dev-d3352495c579f6adeca085cdbc137ac36e70d558`. This will download a zip file to your computer.
|
||||
3. Put your Roku's IP from step 1 into a browser i.e. `http://192.168.1.2` and press enter.
|
||||
4. Log in with credentials from step 1.
|
||||
5. Upload and install the zip file downloaded in step 2.
|
||||
|
||||
> NOTE: The beta app will always be at the bottom of your Roku's channel list and it will *not* automatically update.
|
||||
|
||||
## Advanced
|
||||
|
||||
For more advanced deployment methods, access to crash logs, or to learn how to setup a developer environment so you can write some code yourself please read the [DEVGUIDE](DEVGUIDE.md).
|
||||
|
||||
## Feature Requests
|
||||
|
||||
New feature requests are always welcome but before creating an issue please read through the [existing issues](https://github.com/jellyfin/jellyfin-roku/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) to see if someone has already raised one for what you're looking for.
|
||||
|
|
|
@ -44,7 +44,6 @@ sub LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtit
|
|||
videotype = LCase(meta.type)
|
||||
|
||||
if videotype = "episode" or videotype = "series"
|
||||
video.runTime = (meta.json.RunTimeTicks / 10000000.0)
|
||||
video.content.contenttype = "episode"
|
||||
end if
|
||||
|
||||
|
|
|
@ -26,8 +26,10 @@ sub init()
|
|||
m.overlayMinutes = m.top.findNode("overlayMinutes")
|
||||
m.overlayMeridian = m.top.findNode("overlayMeridian")
|
||||
m.overlayMeridian.font.size = 20
|
||||
' start timer
|
||||
m.currentTimeTimer = m.top.findNode("currentTimeTimer")
|
||||
' display current time
|
||||
updateTime()
|
||||
' start timer to update clock every minute
|
||||
m.currentTimeTimer.control = "start"
|
||||
m.currentTimeTimer.ObserveField("fire", "updateTime")
|
||||
end if
|
||||
|
|
|
@ -20,6 +20,11 @@ sub init()
|
|||
m.nextEpisodeButton.text = tr("Next Episode")
|
||||
m.nextEpisodeButton.setFocus(false)
|
||||
m.nextupbuttonseconds = get_user_setting("playback.nextupbuttonseconds", "30")
|
||||
if isValid(m.nextupbuttonseconds)
|
||||
m.nextupbuttonseconds = val(m.nextupbuttonseconds)
|
||||
else
|
||||
m.nextupbuttonseconds = 30
|
||||
end if
|
||||
|
||||
m.showNextEpisodeButtonAnimation = m.top.findNode("showNextEpisodeButton")
|
||||
m.hideNextEpisodeButtonAnimation = m.top.findNode("hideNextEpisodeButton")
|
||||
|
@ -28,8 +33,6 @@ sub init()
|
|||
m.getNextEpisodeTask = createObject("roSGNode", "GetNextEpisodeTask")
|
||||
m.getNextEpisodeTask.observeField("nextEpisodeData", "onNextEpisodeDataLoaded")
|
||||
|
||||
m.top.observeField("state", "onState")
|
||||
m.top.observeField("content", "onContentChange")
|
||||
m.top.observeField("allowCaptions", "onAllowCaptionsChange")
|
||||
end sub
|
||||
|
||||
|
@ -89,7 +92,6 @@ end sub
|
|||
'
|
||||
' Runs Next Episode button animation and sets focus to button
|
||||
sub showNextEpisodeButton()
|
||||
if m.top.content.contenttype <> 4 then return
|
||||
if m.global.userConfig.EnableNextEpisodeAutoPlay and not m.nextEpisodeButton.visible
|
||||
m.showNextEpisodeButtonAnimation.control = "start"
|
||||
m.nextEpisodeButton.setFocus(true)
|
||||
|
@ -100,7 +102,7 @@ end sub
|
|||
'
|
||||
'Update count down text
|
||||
sub updateCount()
|
||||
nextEpisodeCountdown = Int(m.top.runTime - m.top.position)
|
||||
nextEpisodeCountdown = Int(m.top.duration - m.top.position)
|
||||
if nextEpisodeCountdown < 0
|
||||
nextEpisodeCountdown = 0
|
||||
end if
|
||||
|
@ -118,8 +120,9 @@ end sub
|
|||
' Checks if we need to display the Next Episode button
|
||||
sub checkTimeToDisplayNextEpisode()
|
||||
if m.top.content.contenttype <> 4 then return
|
||||
if m.nextupbuttonseconds = 0 then return
|
||||
|
||||
if int(m.top.position) >= (m.top.runTime - 30)
|
||||
if int(m.top.position) >= (m.top.duration - m.nextupbuttonseconds)
|
||||
showNextEpisodeButton()
|
||||
updateCount()
|
||||
return
|
||||
|
|
|
@ -22,8 +22,6 @@
|
|||
<field id="videoId" type="string" />
|
||||
<field id="mediaSourceId" type="string" />
|
||||
<field id="audioIndex" type="integer" />
|
||||
<field id="runTime" type="integer" />
|
||||
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="JFVideo.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||
|
|
|
@ -83,18 +83,6 @@ sub onLibrariesLoaded()
|
|||
|
||||
haveLiveTV = false
|
||||
|
||||
' Load the NextUp Data
|
||||
m.LoadNextUpTask.observeField("content", "updateNextUpItems")
|
||||
m.LoadNextUpTask.control = "RUN"
|
||||
|
||||
' Load the Continue Watching Data
|
||||
m.LoadContinueTask.observeField("content", "updateContinueItems")
|
||||
m.LoadContinueTask.control = "RUN"
|
||||
|
||||
' Load the Favorites Data
|
||||
m.LoadFavoritesTask.observeField("content", "updateFavoritesItems")
|
||||
m.LoadFavoritesTask.control = "RUN"
|
||||
|
||||
' validate library data
|
||||
if isValid(m.libraryData) and m.libraryData.count() > 0
|
||||
userConfig = m.global.userConfig
|
||||
|
@ -112,42 +100,42 @@ sub onLibrariesLoaded()
|
|||
latestInRow = content.CreateChild("HomeRow")
|
||||
latestInRow.title = tr("Latest in") + " " + lib.name + " >"
|
||||
sizeArray.Push([464, 331])
|
||||
|
||||
loadLatest = createObject("roSGNode", "LoadItemsTask")
|
||||
loadLatest.itemsToLoad = "latest"
|
||||
loadLatest.itemId = lib.id
|
||||
|
||||
metadata = { "title": lib.name }
|
||||
metadata.Append({ "contentType": lib.json.CollectionType })
|
||||
loadLatest.metadata = metadata
|
||||
|
||||
loadLatest.observeField("content", "updateLatestItems")
|
||||
loadLatest.control = "RUN"
|
||||
else if lib.collectionType = "livetv"
|
||||
' If we have Live TV, add "On Now"
|
||||
onNowRow = content.CreateChild("HomeRow")
|
||||
onNowRow.title = tr("On Now")
|
||||
sizeArray.Push([464, 331])
|
||||
haveLiveTV = true
|
||||
' If we have Live TV access, load "On Now" data
|
||||
if haveLiveTV
|
||||
m.LoadOnNowTask.observeField("content", "updateOnNowItems")
|
||||
m.LoadOnNowTask.control = "RUN"
|
||||
end if
|
||||
end if
|
||||
end for
|
||||
end if
|
||||
|
||||
m.top.rowItemSize = sizeArray
|
||||
m.top.content = content
|
||||
|
||||
' Load the Continue Watching Data
|
||||
m.LoadContinueTask.observeField("content", "updateContinueItems")
|
||||
m.LoadContinueTask.control = "RUN"
|
||||
|
||||
' Load the Favorites Data
|
||||
m.LoadFavoritesTask.observeField("content", "updateFavoritesItems")
|
||||
m.LoadFavoritesTask.control = "RUN"
|
||||
|
||||
' If we have Live TV access, load "On Now" data
|
||||
if haveLiveTV
|
||||
m.LoadOnNowTask.observeField("content", "updateOnNowItems")
|
||||
m.LoadOnNowTask.control = "RUN"
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub updateHomeRows()
|
||||
if m.global.playstateTask.state = "run"
|
||||
m.global.playstateTask.observeField("state", "updateHomeRows")
|
||||
else
|
||||
m.global.playstateTask.unobserveField("state")
|
||||
return
|
||||
end if
|
||||
|
||||
m.global.playstateTask.unobserveField("state")
|
||||
|
||||
m.LoadContinueTask.observeField("content", "updateContinueItems")
|
||||
m.LoadContinueTask.control = "RUN"
|
||||
end sub
|
||||
|
@ -237,6 +225,9 @@ sub updateContinueItems()
|
|||
homeRows.replaceChild(row, continueRowIndex)
|
||||
end if
|
||||
end if
|
||||
|
||||
m.LoadNextUpTask.observeField("content", "updateNextUpItems")
|
||||
m.LoadNextUpTask.control = "RUN"
|
||||
end sub
|
||||
|
||||
sub updateNextUpItems()
|
||||
|
@ -289,6 +280,24 @@ sub updateNextUpItems()
|
|||
m.global.app_loaded = true
|
||||
end if
|
||||
|
||||
' create task nodes for "Latest In" rows
|
||||
userConfig = m.global.userConfig
|
||||
filteredLatest = filterNodeArray(m.libraryData, "id", userConfig.LatestItemsExcludes)
|
||||
for each lib in filteredLatest
|
||||
if lib.collectionType <> "livetv" and lib.collectionType <> "boxsets" and lib.json.CollectionType <> "Program"
|
||||
loadLatest = createObject("roSGNode", "LoadItemsTask")
|
||||
loadLatest.itemsToLoad = "latest"
|
||||
loadLatest.itemId = lib.id
|
||||
|
||||
metadata = { "title": lib.name }
|
||||
metadata.Append({ "contentType": lib.json.CollectionType })
|
||||
loadLatest.metadata = metadata
|
||||
|
||||
loadLatest.observeField("content", "updateLatestItems")
|
||||
loadLatest.control = "RUN"
|
||||
end if
|
||||
end for
|
||||
|
||||
end sub
|
||||
|
||||
sub updateLatestItems(msg)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
sub init()
|
||||
m.queue = []
|
||||
m.originalQueue = []
|
||||
m.queueTypes = []
|
||||
m.position = 0
|
||||
m.shuffleEnabled = false
|
||||
end sub
|
||||
|
||||
' Clear all content from play queue
|
||||
|
@ -27,6 +29,11 @@ function getCurrentItem()
|
|||
return getItemByIndex(m.position)
|
||||
end function
|
||||
|
||||
' Return whether or not shuffle is enabled
|
||||
function getIsShuffled()
|
||||
return m.shuffleEnabled
|
||||
end function
|
||||
|
||||
' Return the item in the passed index from the play queue
|
||||
function getItemByIndex(index)
|
||||
return m.queue[index]
|
||||
|
@ -108,6 +115,54 @@ sub setPosition(newPosition)
|
|||
m.position = newPosition
|
||||
end sub
|
||||
|
||||
' Reset shuffle to off state
|
||||
sub resetShuffle()
|
||||
m.shuffleEnabled = false
|
||||
end sub
|
||||
|
||||
' Toggle shuffleEnabled state
|
||||
sub toggleShuffle()
|
||||
m.shuffleEnabled = not m.shuffleEnabled
|
||||
|
||||
if m.shuffleEnabled
|
||||
shuffleQueueItems()
|
||||
return
|
||||
end if
|
||||
|
||||
resetQueueItemOrder()
|
||||
end sub
|
||||
|
||||
' Reset queue items back to original, unshuffled order
|
||||
sub resetQueueItemOrder()
|
||||
set(m.originalQueue)
|
||||
end sub
|
||||
|
||||
' Return original, unshuffled queue
|
||||
function getUnshuffledQueue()
|
||||
return m.originalQueue
|
||||
end function
|
||||
|
||||
' Save a copy of the original queue and randomize order of queue items
|
||||
sub shuffleQueueItems()
|
||||
' By calling getQueue 2 different ways, Roku avoids needing to do a deep copy
|
||||
m.originalQueue = m.global.queueManager.callFunc("getQueue")
|
||||
songIDArray = getQueue()
|
||||
|
||||
' Move the currently playing song to the front of the queue
|
||||
temp = top()
|
||||
songIDArray[0] = getCurrentItem()
|
||||
songIDArray[getPosition()] = temp
|
||||
|
||||
for i = 1 to songIDArray.count() - 1
|
||||
j = Rnd(songIDArray.count() - 1)
|
||||
temp = songIDArray[i]
|
||||
songIDArray[i] = songIDArray[j]
|
||||
songIDArray[j] = temp
|
||||
end for
|
||||
|
||||
set(songIDArray)
|
||||
end sub
|
||||
|
||||
' Return the fitst item in the play queue
|
||||
function top()
|
||||
return getItemByIndex(0)
|
||||
|
@ -115,7 +170,7 @@ end function
|
|||
|
||||
' Replace play queue with passed array
|
||||
sub set(items)
|
||||
setPosition(0)
|
||||
clear()
|
||||
m.queue = items
|
||||
for each item in items
|
||||
m.queueTypes.push(getItemType(item))
|
||||
|
|
|
@ -5,19 +5,23 @@
|
|||
<function name="deleteAtIndex" />
|
||||
<function name="getCount" />
|
||||
<function name="getCurrentItem" />
|
||||
<function name="getIsShuffled" />
|
||||
<function name="getItemByIndex" />
|
||||
<function name="getPosition" />
|
||||
<function name="getQueue" />
|
||||
<function name="getQueueTypes" />
|
||||
<function name="getQueueUniqueTypes" />
|
||||
<function name="getUnshuffledQueue" />
|
||||
<function name="moveBack" />
|
||||
<function name="moveForward" />
|
||||
<function name="peek" />
|
||||
<function name="playQueue" />
|
||||
<function name="pop" />
|
||||
<function name="push" />
|
||||
<function name="resetShuffle" />
|
||||
<function name="set" />
|
||||
<function name="setPosition" />
|
||||
<function name="toggleShuffle" />
|
||||
<function name="top" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="QueueManager.brs" />
|
||||
|
|
|
@ -10,7 +10,6 @@ sub init()
|
|||
|
||||
m.playlistTypeCount = m.global.queueManager.callFunc("getQueueUniqueTypes").count()
|
||||
|
||||
m.shuffleEnabled = false
|
||||
m.buttonCount = m.buttons.getChildCount()
|
||||
|
||||
m.screenSaverTimeout = 300
|
||||
|
@ -26,6 +25,8 @@ sub init()
|
|||
|
||||
loadButtons()
|
||||
pageContentChanged()
|
||||
setShuffleIconState()
|
||||
setLoopButtonImage()
|
||||
end sub
|
||||
|
||||
sub onScreensaverTimeoutLoaded()
|
||||
|
@ -111,9 +112,10 @@ sub setupInfoNodes()
|
|||
m.playPosition = m.top.findNode("playPosition")
|
||||
m.bufferPosition = m.top.findNode("bufferPosition")
|
||||
m.seekBar = m.top.findNode("seekBar")
|
||||
m.numberofsongsField = m.top.findNode("numberofsongs")
|
||||
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()
|
||||
|
@ -156,6 +158,13 @@ sub audioPositionChanged()
|
|||
m.playPositionAnimationWidth.keyValue = [m.playPosition.width, playPositionBarWidth]
|
||||
m.playPositionAnimation.control = "start"
|
||||
|
||||
' Update displayed position timestamp
|
||||
if isValid(m.global.audioPlayer.position)
|
||||
m.positionTimestamp.text = secondsToHuman(m.global.audioPlayer.position)
|
||||
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
|
||||
|
@ -254,6 +263,11 @@ function previousClicked() as boolean
|
|||
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()
|
||||
|
||||
|
@ -261,6 +275,11 @@ function previousClicked() as boolean
|
|||
return true
|
||||
end function
|
||||
|
||||
sub resetLoopModeToDefault()
|
||||
m.global.audioPlayer.loopMode = ""
|
||||
setLoopButtonImage()
|
||||
end sub
|
||||
|
||||
function loopClicked() as boolean
|
||||
|
||||
if m.global.audioPlayer.loopMode = ""
|
||||
|
@ -290,6 +309,11 @@ 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
|
||||
|
@ -298,10 +322,12 @@ function nextClicked() as boolean
|
|||
end function
|
||||
|
||||
sub toggleShuffleEnabled()
|
||||
m.shuffleEnabled = not m.shuffleEnabled
|
||||
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
|
||||
|
@ -313,44 +339,36 @@ end function
|
|||
|
||||
function shuffleClicked() as boolean
|
||||
|
||||
currentSongIndex = findCurrentSongIndex(m.global.queueManager.callFunc("getUnshuffledQueue"))
|
||||
|
||||
toggleShuffleEnabled()
|
||||
|
||||
if not m.shuffleEnabled
|
||||
if not m.global.queueManager.callFunc("getIsShuffled")
|
||||
m.shuffleIndicator.opacity = ".4"
|
||||
m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-on", "-off")
|
||||
|
||||
currentSongIndex = findCurrentSongIndex(m.originalSongList)
|
||||
m.global.queueManager.callFunc("set", m.originalSongList)
|
||||
m.global.queueManager.callFunc("setPosition", currentSongIndex)
|
||||
setFieldTextValue("numberofsongs", "Track " + stri(m.global.queueManager.callFunc("getPosition") + 1) + "/" + stri(m.global.queueManager.callFunc("getCount")))
|
||||
|
||||
setTrackNumberDisplay()
|
||||
return true
|
||||
end if
|
||||
|
||||
m.shuffleIndicator.opacity = "1"
|
||||
m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-off", "-on")
|
||||
|
||||
m.originalSongList = m.global.queueManager.callFunc("getQueue")
|
||||
|
||||
songIDArray = m.global.queueManager.callFunc("getQueue")
|
||||
|
||||
' Move the currently playing song to the front of the queue
|
||||
temp = m.global.queueManager.callFunc("top")
|
||||
songIDArray[0] = m.global.queueManager.callFunc("getCurrentItem")
|
||||
songIDArray[m.global.queueManager.callFunc("getPosition")] = temp
|
||||
|
||||
for i = 1 to songIDArray.count() - 1
|
||||
j = Rnd(songIDArray.count() - 1)
|
||||
temp = songIDArray[i]
|
||||
songIDArray[i] = songIDArray[j]
|
||||
songIDArray[j] = temp
|
||||
end for
|
||||
|
||||
m.global.queueManager.callFunc("set", songIDArray)
|
||||
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"
|
||||
|
@ -400,6 +418,9 @@ sub pageContentChanged()
|
|||
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
|
||||
|
@ -455,6 +476,9 @@ sub onMetaDataLoaded()
|
|||
|
||||
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
|
||||
|
@ -492,14 +516,8 @@ end sub
|
|||
' Populate on screen text variables
|
||||
sub setOnScreenTextValues(json)
|
||||
if isValid(json)
|
||||
currentSongIndex = m.global.queueManager.callFunc("getPosition")
|
||||
|
||||
if m.shuffleEnabled
|
||||
currentSongIndex = findCurrentSongIndex(m.originalSongList)
|
||||
end if
|
||||
|
||||
if m.playlistTypeCount = 1
|
||||
setFieldTextValue("numberofsongs", "Track " + stri(currentSongIndex + 1) + "/" + stri(m.global.queueManager.callFunc("getCount")))
|
||||
setTrackNumberDisplay()
|
||||
end if
|
||||
|
||||
setFieldTextValue("artist", json.Artists[0])
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
<Poster id="backdrop" opacity=".5" loadDisplayMode="scaleToZoom" width="1920" height="1200" blendColor="#3f3f3f" />
|
||||
<Poster id="shuffleIndicator" width="64" height="64" uri="pkg:/images/icons/shuffleIndicator-off.png" translation="[1150,775]" opacity="0" />
|
||||
<Poster id="loopIndicator" width="64" height="64" uri="pkg:/images/icons/loopIndicator-off.png" translation="[700,775]" opacity="0" />
|
||||
<Label id="positionTimestamp" width="100" height="25" horizAlign="right" font="font:SmallestSystemFont" translation="[590,825]" color="#999999" text="0:00" />
|
||||
<Label id="totalLengthTimestamp" width="100" height="25" horizAlign="left" font="font:SmallestSystemFont" translation="[1230,825]" color="#999999" />
|
||||
<LayoutGroup id="toplevel" layoutDirection="vert" horizAlignment="center" translation="[960,175]" itemSpacings="[40]">
|
||||
<LayoutGroup id="main_group" layoutDirection="vert" horizAlignment="center" itemSpacings="[15]">
|
||||
<Poster id="albumCover" width="500" height="500" />
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
<field id="videoId" type="string" />
|
||||
<field id="mediaSourceId" type="string" />
|
||||
<field id="audioIndex" type="integer" />
|
||||
<field id="runTime" type="integer" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="VideoPlayerView.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||
|
|
|
@ -3,6 +3,7 @@ VSCode
|
|||
BrightScript
|
||||
sideload
|
||||
Sideload
|
||||
Reddit
|
||||
DEVGUIDE
|
||||
ing
|
||||
hardcode
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.0" language="de_DE" sourcelanguage="en_US">
|
||||
<defaultcodec>UTF-8</defaultcodec>
|
||||
<context>
|
||||
<defaultcodec>UTF-8</defaultcodec>
|
||||
<context>
|
||||
<name>default</name>
|
||||
<message>
|
||||
<source>192.168.1.100:8096 or https://example.com/jellyfin</source>
|
||||
<translation>Standard: 192.168.1.100:8096 oder https://example.com/jellyfin</translation>
|
||||
<translation>192.168.1.100:8096 oder https://example.com/jellyfin</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel</source>
|
||||
|
@ -180,8 +180,8 @@
|
|||
<source>Server</source>
|
||||
<translation>Server</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
</context>
|
||||
<context>
|
||||
<name></name>
|
||||
<message>
|
||||
<source>Sign Out</source>
|
||||
|
@ -9961,5 +9961,58 @@
|
|||
<source>Save Credentials?</source>
|
||||
<translation>Zugangsdaten speichern?</translation>
|
||||
</message>
|
||||
</context>
|
||||
<message>
|
||||
<comment>Name or Title field of media item</comment>
|
||||
<source>TITLE</source>
|
||||
<translation>Name</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sign Out</source>
|
||||
<translation>Abmelden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete Saved</source>
|
||||
<translation>Löschen gespeichert</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error During Playback</source>
|
||||
<translation>Fehler bei der Wiedergabe</translation>
|
||||
<extracomment>Dialog title when error occurs during playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>There was an error retrieving the data for this item from the server.</source>
|
||||
<translation>Fehler beim Laden der Daten vom Server</translation>
|
||||
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>An error was encountered while playing this item.</source>
|
||||
<translation>Bei der Wiedergabe trat ein Fehler auf</translation>
|
||||
<extracomment>Dialog detail when error occurs during playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Zugangsdaten speichern?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Retrieving Content</source>
|
||||
<translation>Fehler beim Laden des Inhalts</translation>
|
||||
<extracomment>Dialog title when unable to load Content from Server</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Loading Channel Data</source>
|
||||
<translation>Lade Kanaldaten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error loading Channel Data</source>
|
||||
<translation>Fehler beim Laden der Kanaldaten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to load Channel Data from the server</source>
|
||||
<translation>Kanaldaten können nicht vom Server geladen werden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Change Server</source>
|
||||
<translation>Server ändern</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
|
@ -6,7 +6,7 @@
|
|||
<name>default</name>
|
||||
<message>
|
||||
<source>192.168.1.100:8096 or https://example.com/jellyfin</source>
|
||||
<translation>default192.168.1.100:8096 or https://example.com/jellyfin</translation>
|
||||
<translation>192.168.1.100:8096 or https://example.com/jellyfin</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel</source>
|
||||
|
@ -3254,5 +3254,345 @@
|
|||
<source>Save Credentials?</source>
|
||||
<translation>Save Credentials?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>More Like This</source>
|
||||
<translation>More Like This</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Additional Parts</source>
|
||||
<translation>Additional Parts</translation>
|
||||
<extracomment>Additional parts of a video</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record</source>
|
||||
<translation>Record</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter the server name or IP address</source>
|
||||
<translation>Enter the server name or IP address</translation>
|
||||
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>...or enter server URL manually:</source>
|
||||
<translation>If no server is listed above, you may also enter the server URL manually:</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to manually enter a server URL</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Getting Playback Information</source>
|
||||
<translation>Error Getting Playback Information</translation>
|
||||
<extracomment>Dialog Title: Received error from server when trying to get information about the selected item for playback</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>An error was encountered while playing this item. Server did not provide required transcoding data.</source>
|
||||
<translation>An error was encountered while playing this item. Server did not provide required transcoding data.</translation>
|
||||
<extracomment>Content of message box when trying to play an item which requires transcoding, and the server did not provide transcode url</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>MPEG-4</source>
|
||||
<translation>MPEG-4</translation>
|
||||
<extracomment>Name of codec used in settings menu</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel Series Recording</source>
|
||||
<translation>Cancel Series Recording</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</source>
|
||||
<translation>Support Direct Play of MPEG-2 content (e.g., Live TV). This will prevent transcoding of MPEG-2 content, but uses significantly more bandwidth.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</source>
|
||||
<translation>Support Direct Play of MPEG-4 content. This may need to be disabled for playback of DIVX encoded video files.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Media Grid</source>
|
||||
<translation>Media Grid</translation>
|
||||
<extracomment>UI -> Media Grid section in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show item count in the library and index of selected item.</source>
|
||||
<translation>Show item count in the library and index of selected item.</translation>
|
||||
<extracomment>Description for option in Setting Screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use voice remote to search</source>
|
||||
<translation>Use voice remote to search</translation>
|
||||
<extracomment>Help text in search voice text box</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>AV1</source>
|
||||
<translation>AV1</translation>
|
||||
<extracomment>Name of a setting - should we try to direct play experimental av1 codec</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>You can search for Titles, People, Live TV Channels and more</source>
|
||||
<translation>You can search for Titles, People, Live TV Channels and more</translation>
|
||||
<extracomment>Help text in search results</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>(Dialog will close automatically)</source>
|
||||
<translation>(Dialogue will close automatically)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Blur Unwatched Episodes</source>
|
||||
<translation>Blur Unwatched Episodes</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</source>
|
||||
<translation>Use the replay button to slowly animate to the first item in the folder. (If disabled, the folder will reset to the first item immediately).</translation>
|
||||
<extracomment>Description for option in Setting Screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for TV Shows.</source>
|
||||
<translation>Options for TV Shows.</translation>
|
||||
<extracomment>Description for TV Shows user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cast & Crew</source>
|
||||
<translation>Cast & Crew</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Special Features</source>
|
||||
<translation>Special Features</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TV Shows</source>
|
||||
<translation>TV Shows</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Close</source>
|
||||
<translation>Close</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unknown</source>
|
||||
<translation>Unknown</translation>
|
||||
<extracomment>Title for a cast member for which we have no information for</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pick a Jellyfin server from the local network</source>
|
||||
<translation>Select an available Jellyfin server from your local network:</translation>
|
||||
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel Recording</source>
|
||||
<translation>Cancel Recording</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If enabled, selecting a TV series with only one season will go straight to the episode list rather than the show details and season list.</source>
|
||||
<translation>If enabled, selecting a TV series with only one season will go straight to the episode list rather than the show details and season list.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use generated splashscreen image as Jellyfin's screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</source>
|
||||
<translation>Use generated splashscreen image as Jellyfin's screensaver background. Jellyfin will need to be closed and reopened for change to take effect.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set how many seconds before the end of an episode the Next Episode button should appear. Set to 0 to disable.</source>
|
||||
<translation>Set how many seconds before the end of an episode the Next Episode button should appear. Set to 0 to disable.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quick Connect</source>
|
||||
<translation>Quick Connect</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Return to Top</source>
|
||||
<translation>Return to Top</translation>
|
||||
<extracomment>UI -> Media Grid -> Item Title in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Media Grid options.</source>
|
||||
<translation>Media Grid options.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Studios</source>
|
||||
<translation>Studios</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hides tagline text on details pages.</source>
|
||||
<translation>Hides tagline text on details pages.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If enabled, images of unwatched episodes will be blurred.</source>
|
||||
<translation>If enabled, images of unwatched episodes will be blurred.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for Jellyfin's screensaver.</source>
|
||||
<translation>Options for Jellyfin's screensaver.</translation>
|
||||
<extracomment>Description for Screensaver user settings.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use Splashscreen as Screensaver</source>
|
||||
<translation>Use Splashscreen as Screensaver</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record Series</source>
|
||||
<translation>Record Series</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Version</source>
|
||||
<translation>Version</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Playback</source>
|
||||
<translation>Playback</translation>
|
||||
<extracomment>Title for Playback section in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>User Interface</source>
|
||||
<translation>User Interface</translation>
|
||||
<extracomment>Title for User Interface section in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Item Count</source>
|
||||
<translation>Item Count</translation>
|
||||
<extracomment>UI -> Media Grid -> Item Count in user setting screen.</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set Watched</source>
|
||||
<translation>Set Watched</translation>
|
||||
<extracomment>Button Text - When pressed, marks item as Warched</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Here is your Quick Connect code:</source>
|
||||
<translation>Here is your Quick Connect code:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Networks</source>
|
||||
<translation>Networks</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Codec Support</source>
|
||||
<translation>Codec Support</translation>
|
||||
<extracomment>Settings Menu - Title for settings group related to codec support</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>MPEG-2</source>
|
||||
<translation>MPEG-2</translation>
|
||||
<extracomment>Name of codec used in settings menu</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</source>
|
||||
<translation>** EXPERIMENTAL** Support Direct Play of AV1 content if this Roku device supports it.</translation>
|
||||
<extracomment>Description of a setting - should we try to direct play experimental av1 codec</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disabled</source>
|
||||
<translation>Disabled</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set Favorite</source>
|
||||
<translation>Set Favourite</translation>
|
||||
<extracomment>Button Text - When pressed, sets item as Favorite</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to series</source>
|
||||
<translation>Go to series</translation>
|
||||
<extracomment>Continue Watching Popup Menu - Navigate to the Series Detail Page</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to season</source>
|
||||
<translation>Go to season</translation>
|
||||
<extracomment>Continue Watching Popup Menu - Navigate to the Season Page</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 of %2</source>
|
||||
<translation>%1 of %2</translation>
|
||||
<extracomment>Item position and count. %1 = current item. %2 = total number of items</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>There was an error authenticating via Quick Connect.</source>
|
||||
<translation>There was an error authenticating via Quick Connect.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide Taglines</source>
|
||||
<translation>Hide Taglines</translation>
|
||||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Skip Details for Single Seasons</source>
|
||||
<translation>Skip Details for Single Seasons</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press 'OK' to Close</source>
|
||||
<translation>Press 'OK' to Close</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Screensaver</source>
|
||||
<translation>Screensaver</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Search now</source>
|
||||
<translation>Search now</translation>
|
||||
<extracomment>Help text in search Box</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Go to episode</source>
|
||||
<translation>Go to episode</translation>
|
||||
<extracomment>Continue Watching Popup Menu - Navigate to the Episode Detail Page</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Shows</source>
|
||||
<translation>Shows</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enabled</source>
|
||||
<translation>Enabled</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Credentials?</source>
|
||||
<translation>Save Credentials?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Age</source>
|
||||
<translation>Age</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Additional Parts</source>
|
||||
<translation>Additional Parts</translation>
|
||||
<extracomment>Additional parts of a video</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>On Now</source>
|
||||
<translation>On Now</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cast & Crew</source>
|
||||
<translation>Cast & Crew</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>More Like This</source>
|
||||
<translation>More Like This</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Died</source>
|
||||
<translation>Died</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete Saved</source>
|
||||
<translation>Delete Saved</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Born</source>
|
||||
<translation>Born</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Special Features</source>
|
||||
<translation>Special Features</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press 'OK' to Close</source>
|
||||
<translation>Press 'OK' to Close</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set how many seconds before the end of an episode the Next Episode button should appear. Set to 0 to disable.</source>
|
||||
<translation>Set how many seconds before the end of an episode the Next Episode button should appear. Set to 0 to disable.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -675,13 +675,13 @@
|
|||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>If enabled, selecting a TV series with only one season will go straight to the episode list rather than the show details and season list.</source>
|
||||
<translation>If enabled, selecting a TV series with only one season will go straight to the episode list rather than the show details and season list.</translation>
|
||||
<source>Go directly to the episode list if a TV series has only one season.</source>
|
||||
<translation>Go directly to the episode list if a TV series has only one season.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>If enabled, images of unwatched episodes will be blurred.</source>
|
||||
<translation>If enabled, images of unwatched episodes will be blurred.</translation>
|
||||
<source>Blur images of unwatched episodes.</source>
|
||||
<translation>Blur images of unwatched episodes.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Design Elements</source>
|
||||
|
@ -708,8 +708,8 @@
|
|||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
|
||||
<translation>Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.</translation>
|
||||
<source>Bring the theater experience straight to your living room with the ability to play custom intros before the main feature.</source>
|
||||
<translation>Bring the theater experience straight to your living room with the ability to play custom intros before the main feature.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
|
@ -718,8 +718,8 @@
|
|||
<extracomment>Option Title in user setting screen</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</source>
|
||||
<translation>Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.</translation>
|
||||
<source>Hide all clocks in Jellyfin. Jellyfin will need to be closed and reopened for changes to take effect.</source>
|
||||
<translation>Hide all clocks in Jellyfin. Jellyfin will need to be closed and reopened for changes to take effect.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
|
@ -1007,36 +1007,36 @@
|
|||
<translation>Disable Community Rating for Episodes</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If enabled, the star and community rating for episodes of a TV show will be removed. This is to prevent spoilers of an upcoming good/bad episode.</source>
|
||||
<translation>If enabled, the star and community rating for episodes of a TV show will be removed. This is to prevent spoilers of an upcoming good/bad episode.</translation>
|
||||
<source>Hide the star and community rating for episodes of a TV show. This is to prevent spoilers of an upcoming good/bad episode.</source>
|
||||
<translation>Hide the star and community rating for episodes of a TV show. This is to prevent spoilers of an upcoming good/bad episode.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Configure the maximum playback bitrate.</source>
|
||||
<translation>Configure the maximum playback bitrate.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Biographical information for this person is not currently available.</source>
|
||||
<translation>Biographical information for this person is not currently available.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Playback Bitrate Limits</source>
|
||||
<translation>Playback Bitrate Limits</translation>
|
||||
<source>Enable Limit</source>
|
||||
<translation>Enable Limit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set limits for how high playback bitrates are allowed to be.</source>
|
||||
<translation>Set limits for how high playback bitrates are allowed to be.</translation>
|
||||
<source>Enable or disable the 'Maximum Bitrate' setting.</source>
|
||||
<translation>Enable or disable the 'Maximum Bitrate' setting.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Limits Enabled</source>
|
||||
<translation>Limits Enabled</translation>
|
||||
<source>Bitrate Limit</source>
|
||||
<translation>Bitrate Limit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If enabled, playback bitrates will be limited based on the 'Playback Bitrate Limit' setting.</source>
|
||||
<translation>If enabled, playback bitrates will be limited based on the 'Playback Bitrate Limit' setting.</translation>
|
||||
<source>Maximum Bitrate</source>
|
||||
<translation>Maximum Bitrate</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Playback Bitrate Limit</source>
|
||||
<translation>Playback Bitrate Limit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Max bitrate (Mbps) allowed if limits are enabled. Set to 0 to use Roku's specifications.</source>
|
||||
<translation>Max bitrate (Mbps) allowed if limits are enabled. Set to 0 to use Roku's specifications.</translation>
|
||||
<source>Set the maximum bitrate in Mbps. Set to 0 to use Roku's specifications. This setting must be enabled to take effect.</source>
|
||||
<translation>Set the maximum bitrate in Mbps. Set to 0 to use Roku's specifications. This setting must be enabled to take effect.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Libraries</source>
|
||||
|
|
4
manifest
4
manifest
|
@ -3,7 +3,7 @@
|
|||
title=Jellyfin
|
||||
major_version=1
|
||||
minor_version=6
|
||||
build_version=4
|
||||
build_version=5
|
||||
|
||||
### Main Menu Icons / Channel Poster Artwork
|
||||
|
||||
|
@ -21,4 +21,6 @@ splash_min_time=1500
|
|||
|
||||
ui_resolutions=fhd
|
||||
|
||||
confirm_partner_button=1
|
||||
|
||||
supports_input_launch=1
|
||||
|
|
269
package-lock.json
generated
269
package-lock.json
generated
|
@ -18,7 +18,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@rokucommunity/bslint": "0.8.2",
|
||||
"brighterscript": "0.62.0",
|
||||
"brighterscript": "0.64.0",
|
||||
"jshint": "^2.13.6",
|
||||
"markdownlint-cli2": "0.6.0",
|
||||
"ropm": "0.10.12",
|
||||
|
@ -448,9 +448,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/brighterscript": {
|
||||
"version": "0.62.0",
|
||||
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.62.0.tgz",
|
||||
"integrity": "sha512-aFDlceBnU5oQI+/pb+QMwkIGylT880KteRxDF1wS5F2TMK31rRn3Ifh1WcQrr5gEKnPebpCL0ZYvWNSeVKVqmg==",
|
||||
"version": "0.64.0",
|
||||
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.64.0.tgz",
|
||||
"integrity": "sha512-uLxQlrUcsW1QS9I8xerevDYgE/Tozwa+O3PSUnPcdCFUytE8hIDTtyroQG+WApKaUeHpGI0HlEzr7pWyEqXRAA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rokucommunity/bslib": "^0.1.1",
|
||||
"@xml-tools/parser": "^1.0.7",
|
||||
|
@ -503,6 +504,81 @@
|
|||
"bsfmt": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/brighterscript-formatter/node_modules/brighterscript": {
|
||||
"version": "0.62.0",
|
||||
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.62.0.tgz",
|
||||
"integrity": "sha512-aFDlceBnU5oQI+/pb+QMwkIGylT880KteRxDF1wS5F2TMK31rRn3Ifh1WcQrr5gEKnPebpCL0ZYvWNSeVKVqmg==",
|
||||
"dependencies": {
|
||||
"@rokucommunity/bslib": "^0.1.1",
|
||||
"@xml-tools/parser": "^1.0.7",
|
||||
"array-flat-polyfill": "^1.0.1",
|
||||
"chalk": "^2.4.2",
|
||||
"chevrotain": "^7.0.1",
|
||||
"chokidar": "^3.5.1",
|
||||
"clear": "^0.1.0",
|
||||
"cross-platform-clear-console": "^2.3.0",
|
||||
"debounce-promise": "^3.1.0",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"fast-glob": "^3.2.11",
|
||||
"file-url": "^3.0.0",
|
||||
"fs-extra": "^8.0.0",
|
||||
"jsonc-parser": "^2.3.0",
|
||||
"long": "^3.2.0",
|
||||
"luxon": "^2.5.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"moment": "^2.23.0",
|
||||
"p-settle": "^2.1.0",
|
||||
"parse-ms": "^2.1.0",
|
||||
"require-relative": "^0.8.7",
|
||||
"roku-deploy": "^3.10.0",
|
||||
"serialize-error": "^7.0.1",
|
||||
"source-map": "^0.7.4",
|
||||
"vscode-languageserver": "7.0.0",
|
||||
"vscode-languageserver-protocol": "3.16.0",
|
||||
"vscode-languageserver-textdocument": "^1.0.1",
|
||||
"vscode-uri": "^2.1.1",
|
||||
"xml2js": "^0.4.19",
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"bsc": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/brighterscript-formatter/node_modules/brighterscript/node_modules/jsonc-parser": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz",
|
||||
"integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="
|
||||
},
|
||||
"node_modules/brighterscript-formatter/node_modules/brighterscript/node_modules/yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"dependencies": {
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.0",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^20.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/brighterscript-formatter/node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6 <7 || >=8"
|
||||
}
|
||||
},
|
||||
"node_modules/brighterscript-formatter/node_modules/glob-all": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-all/-/glob-all-3.3.1.tgz",
|
||||
|
@ -585,6 +661,7 @@
|
|||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
|
@ -598,6 +675,7 @@
|
|||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
|
@ -3991,6 +4069,61 @@
|
|||
"chevrotain": "7.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ropm/node_modules/brighterscript": {
|
||||
"version": "0.62.0",
|
||||
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.62.0.tgz",
|
||||
"integrity": "sha512-aFDlceBnU5oQI+/pb+QMwkIGylT880KteRxDF1wS5F2TMK31rRn3Ifh1WcQrr5gEKnPebpCL0ZYvWNSeVKVqmg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rokucommunity/bslib": "^0.1.1",
|
||||
"@xml-tools/parser": "^1.0.7",
|
||||
"array-flat-polyfill": "^1.0.1",
|
||||
"chalk": "^2.4.2",
|
||||
"chevrotain": "^7.0.1",
|
||||
"chokidar": "^3.5.1",
|
||||
"clear": "^0.1.0",
|
||||
"cross-platform-clear-console": "^2.3.0",
|
||||
"debounce-promise": "^3.1.0",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"fast-glob": "^3.2.11",
|
||||
"file-url": "^3.0.0",
|
||||
"fs-extra": "^8.0.0",
|
||||
"jsonc-parser": "^2.3.0",
|
||||
"long": "^3.2.0",
|
||||
"luxon": "^2.5.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"moment": "^2.23.0",
|
||||
"p-settle": "^2.1.0",
|
||||
"parse-ms": "^2.1.0",
|
||||
"require-relative": "^0.8.7",
|
||||
"roku-deploy": "^3.10.0",
|
||||
"serialize-error": "^7.0.1",
|
||||
"source-map": "^0.7.4",
|
||||
"vscode-languageserver": "7.0.0",
|
||||
"vscode-languageserver-protocol": "3.16.0",
|
||||
"vscode-languageserver-textdocument": "^1.0.1",
|
||||
"vscode-uri": "^2.1.1",
|
||||
"xml2js": "^0.4.19",
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"bsc": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ropm/node_modules/brighterscript/node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6 <7 || >=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ropm/node_modules/chevrotain": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz",
|
||||
|
@ -5353,9 +5486,10 @@
|
|||
}
|
||||
},
|
||||
"brighterscript": {
|
||||
"version": "0.62.0",
|
||||
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.62.0.tgz",
|
||||
"integrity": "sha512-aFDlceBnU5oQI+/pb+QMwkIGylT880KteRxDF1wS5F2TMK31rRn3Ifh1WcQrr5gEKnPebpCL0ZYvWNSeVKVqmg==",
|
||||
"version": "0.64.0",
|
||||
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.64.0.tgz",
|
||||
"integrity": "sha512-uLxQlrUcsW1QS9I8xerevDYgE/Tozwa+O3PSUnPcdCFUytE8hIDTtyroQG+WApKaUeHpGI0HlEzr7pWyEqXRAA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rokucommunity/bslib": "^0.1.1",
|
||||
"@xml-tools/parser": "^1.0.7",
|
||||
|
@ -5393,6 +5527,7 @@
|
|||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
|
@ -5403,6 +5538,7 @@
|
|||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
|
@ -5427,6 +5563,74 @@
|
|||
"yargs": "^17.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"brighterscript": {
|
||||
"version": "0.62.0",
|
||||
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.62.0.tgz",
|
||||
"integrity": "sha512-aFDlceBnU5oQI+/pb+QMwkIGylT880KteRxDF1wS5F2TMK31rRn3Ifh1WcQrr5gEKnPebpCL0ZYvWNSeVKVqmg==",
|
||||
"requires": {
|
||||
"@rokucommunity/bslib": "^0.1.1",
|
||||
"@xml-tools/parser": "^1.0.7",
|
||||
"array-flat-polyfill": "^1.0.1",
|
||||
"chalk": "^2.4.2",
|
||||
"chevrotain": "^7.0.1",
|
||||
"chokidar": "^3.5.1",
|
||||
"clear": "^0.1.0",
|
||||
"cross-platform-clear-console": "^2.3.0",
|
||||
"debounce-promise": "^3.1.0",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"fast-glob": "^3.2.11",
|
||||
"file-url": "^3.0.0",
|
||||
"fs-extra": "^8.0.0",
|
||||
"jsonc-parser": "^2.3.0",
|
||||
"long": "^3.2.0",
|
||||
"luxon": "^2.5.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"moment": "^2.23.0",
|
||||
"p-settle": "^2.1.0",
|
||||
"parse-ms": "^2.1.0",
|
||||
"require-relative": "^0.8.7",
|
||||
"roku-deploy": "^3.10.0",
|
||||
"serialize-error": "^7.0.1",
|
||||
"source-map": "^0.7.4",
|
||||
"vscode-languageserver": "7.0.0",
|
||||
"vscode-languageserver-protocol": "3.16.0",
|
||||
"vscode-languageserver-textdocument": "^1.0.1",
|
||||
"vscode-uri": "^2.1.1",
|
||||
"xml2js": "^0.4.19",
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonc-parser": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz",
|
||||
"integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="
|
||||
},
|
||||
"yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"requires": {
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.0",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^20.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"glob-all": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-all/-/glob-all-3.3.1.tgz",
|
||||
|
@ -7965,6 +8169,57 @@
|
|||
"chevrotain": "7.1.1"
|
||||
}
|
||||
},
|
||||
"brighterscript": {
|
||||
"version": "0.62.0",
|
||||
"resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.62.0.tgz",
|
||||
"integrity": "sha512-aFDlceBnU5oQI+/pb+QMwkIGylT880KteRxDF1wS5F2TMK31rRn3Ifh1WcQrr5gEKnPebpCL0ZYvWNSeVKVqmg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rokucommunity/bslib": "^0.1.1",
|
||||
"@xml-tools/parser": "^1.0.7",
|
||||
"array-flat-polyfill": "^1.0.1",
|
||||
"chalk": "^2.4.2",
|
||||
"chevrotain": "^7.0.1",
|
||||
"chokidar": "^3.5.1",
|
||||
"clear": "^0.1.0",
|
||||
"cross-platform-clear-console": "^2.3.0",
|
||||
"debounce-promise": "^3.1.0",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"fast-glob": "^3.2.11",
|
||||
"file-url": "^3.0.0",
|
||||
"fs-extra": "^8.0.0",
|
||||
"jsonc-parser": "^2.3.0",
|
||||
"long": "^3.2.0",
|
||||
"luxon": "^2.5.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"moment": "^2.23.0",
|
||||
"p-settle": "^2.1.0",
|
||||
"parse-ms": "^2.1.0",
|
||||
"require-relative": "^0.8.7",
|
||||
"roku-deploy": "^3.10.0",
|
||||
"serialize-error": "^7.0.1",
|
||||
"source-map": "^0.7.4",
|
||||
"vscode-languageserver": "7.0.0",
|
||||
"vscode-languageserver-protocol": "3.16.0",
|
||||
"vscode-languageserver-textdocument": "^1.0.1",
|
||||
"vscode-uri": "^2.1.1",
|
||||
"xml2js": "^0.4.19",
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"chevrotain": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz",
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "jellyfin-roku",
|
||||
"version": "1.6.4",
|
||||
"version": "1.6.5",
|
||||
"description": "Roku app for Jellyfin media server",
|
||||
"main": "index.js",
|
||||
"devDependencies": {
|
||||
"@rokucommunity/bslint": "0.8.2",
|
||||
"brighterscript": "0.62.0",
|
||||
"brighterscript": "0.64.0",
|
||||
"ropm": "0.10.12",
|
||||
"jshint": "^2.13.6",
|
||||
"markdownlint-cli2": "0.6.0",
|
||||
|
|
|
@ -3,9 +3,29 @@
|
|||
"title": "Playback",
|
||||
"description": "Settings relating to playback and supported codec and media types.",
|
||||
"children": [
|
||||
{
|
||||
"title": "Bitrate Limit",
|
||||
"description": "Configure the maximum playback bitrate.",
|
||||
"children": [
|
||||
{
|
||||
"title": "Enable Limit",
|
||||
"description": "Enable or disable the 'Maximum Bitrate' setting.",
|
||||
"settingName": "playback.bitrate.maxlimited",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"title": "Maximum Bitrate",
|
||||
"description": "Set the maximum bitrate in Mbps. Set to 0 to use Roku's specifications. This setting must be enabled to take effect.",
|
||||
"settingName": "playback.bitrate.limit",
|
||||
"type": "integer",
|
||||
"default": "0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Codec Support",
|
||||
"description": "Enable or disable Direct Play support for certain codecs",
|
||||
"description": "Enable or disable Direct Play support for certain codecs.",
|
||||
"children": [
|
||||
{
|
||||
"title": "AV1",
|
||||
|
@ -30,26 +50,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Playback Bitrate Limits",
|
||||
"description": "Set limits for how high playback bitrates are allowed to be.",
|
||||
"children": [
|
||||
{
|
||||
"title": "Limits Enabled",
|
||||
"description": "If enabled, playback bitrates will be limited based on the 'Playback Bitrate Limit' setting.",
|
||||
"settingName": "playback.bitrate.maxlimited",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"title": "Playback Bitrate Limit",
|
||||
"description": "Max bitrate (Mbps) allowed if limits are enabled. Set to 0 to use Roku's specifications.",
|
||||
"settingName": "playback.bitrate.limit",
|
||||
"type": "integer",
|
||||
"default": "0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Profile Level Support",
|
||||
"description": "Attempt Direct Play of potentially unsupported profile levels",
|
||||
|
@ -72,7 +72,7 @@
|
|||
},
|
||||
{
|
||||
"title": "Cinema Mode",
|
||||
"description": "Cinema Mode brings the theater experience straight to your living room with the ability to play custom intros before the main feature.",
|
||||
"description": "Bring the theater experience straight to your living room with the ability to play custom intros before the main feature.",
|
||||
"settingName": "playback.cinemamode",
|
||||
"type": "bool",
|
||||
"default": "false"
|
||||
|
@ -110,7 +110,7 @@
|
|||
"children": [
|
||||
{
|
||||
"title": "Hide Clock",
|
||||
"description": "Hides all clocks in Jellyfin. Jellyfin will need to be closed and reopened for change to take effect.",
|
||||
"description": "Hide all clocks in Jellyfin. Jellyfin will need to be closed and reopened for changes to take effect.",
|
||||
"settingName": "ui.design.hideclock",
|
||||
"type": "bool",
|
||||
"default": "false"
|
||||
|
@ -232,14 +232,14 @@
|
|||
"children": [
|
||||
{
|
||||
"title": "Blur Unwatched Episodes",
|
||||
"description": "If enabled, images of unwatched episodes will be blurred.",
|
||||
"description": "Blur images of unwatched episodes.",
|
||||
"settingName": "ui.tvshows.blurunwatched",
|
||||
"type": "bool",
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"title": "Disable Community Rating for Episodes",
|
||||
"description": "If enabled, the star and community rating for episodes of a TV show will be removed. This is to prevent spoilers of an upcoming good/bad episode.",
|
||||
"description": "Hide the star and community rating for episodes of a TV show. This is to prevent spoilers of an upcoming good/bad episode.",
|
||||
"settingName": "ui.tvshows.disableCommunityRating",
|
||||
"type": "bool",
|
||||
"default": "false"
|
||||
|
|
|
@ -30,7 +30,7 @@ sub Main (args as dynamic) as void
|
|||
m.port = CreateObject("roMessagePort")
|
||||
m.screen.setMessagePort(m.port)
|
||||
m.scene = m.screen.CreateScene("JFScene")
|
||||
m.screen.show()
|
||||
m.screen.show() ' vscode_rale_tracker_entry
|
||||
|
||||
' Set any initial Global Variables
|
||||
m.global = m.screen.getGlobalNode()
|
||||
|
@ -60,15 +60,21 @@ sub Main (args as dynamic) as void
|
|||
m.scene.observeField("exit", m.port)
|
||||
|
||||
' Downloads and stores a fallback font to tmp:/
|
||||
if parseJSON(APIRequest("/System/Configuration/encoding").GetToString())["EnableFallbackFont"] = true
|
||||
configEncoding = api_API().system.getconfigurationbyname("encoding")
|
||||
|
||||
if isValid(configEncoding) and isValid(configEncoding.EnableFallbackFont)
|
||||
if configEncoding.EnableFallbackFont
|
||||
re = CreateObject("roRegex", "Name.:.(.*?).,.Size", "s")
|
||||
filename = APIRequest("FallbackFont/Fonts").GetToString()
|
||||
if isValid(filename)
|
||||
filename = re.match(filename)
|
||||
if filename.count() > 0
|
||||
if isValid(filename) and filename.count() > 0
|
||||
filename = filename[1]
|
||||
APIRequest("FallbackFont/Fonts/" + filename).gettofile("tmp:/font")
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
|
||||
' Only show the Whats New popup the first time a user runs a new client version.
|
||||
if appInfo.GetVersion() <> get_setting("LastRunVersion")
|
||||
|
@ -239,6 +245,7 @@ sub Main (args as dynamic) as void
|
|||
group = CreatePlaylistView(selectedItem.json)
|
||||
else if selectedItem.type = "Audio"
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("resetShuffle")
|
||||
m.global.queueManager.callFunc("push", selectedItem.json)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
else
|
||||
|
@ -280,6 +287,7 @@ sub Main (args as dynamic) as void
|
|||
screenContent = msg.getRoSGNode()
|
||||
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("resetShuffle")
|
||||
m.global.queueManager.callFunc("push", screenContent.albumData.items[selectedIndex])
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
else if isNodeEvent(msg, "playItem")
|
||||
|
@ -288,6 +296,7 @@ sub Main (args as dynamic) as void
|
|||
screenContent = msg.getRoSGNode()
|
||||
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("resetShuffle")
|
||||
m.global.queueManager.callFunc("push", screenContent.albumData.items[selectedIndex])
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
else if isNodeEvent(msg, "playAllSelected")
|
||||
|
@ -297,6 +306,7 @@ sub Main (args as dynamic) as void
|
|||
m.spinner.visible = true
|
||||
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("resetShuffle")
|
||||
m.global.queueManager.callFunc("set", screenContent.albumData.items)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
else if isNodeEvent(msg, "playArtistSelected")
|
||||
|
@ -304,6 +314,7 @@ sub Main (args as dynamic) as void
|
|||
screenContent = msg.getRoSGNode()
|
||||
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("resetShuffle")
|
||||
m.global.queueManager.callFunc("set", CreateArtistMix(screenContent.pageContent.id).Items)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
|
||||
|
@ -323,6 +334,7 @@ sub Main (args as dynamic) as void
|
|||
if isValid(screenContent.albumData.items)
|
||||
if screenContent.albumData.items.count() > 0
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("resetShuffle")
|
||||
m.global.queueManager.callFunc("set", CreateInstantMix(screenContent.albumData.items[0].id).Items)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
|
||||
|
@ -334,6 +346,7 @@ sub Main (args as dynamic) as void
|
|||
if not viewHandled
|
||||
' Create instant mix based on selected artist
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("resetShuffle")
|
||||
m.global.queueManager.callFunc("set", CreateInstantMix(screenContent.pageContent.id).Items)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
end if
|
||||
|
@ -381,6 +394,7 @@ sub Main (args as dynamic) as void
|
|||
group = CreateAlbumView(node.json)
|
||||
else if node.type = "Audio"
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("resetShuffle")
|
||||
m.global.queueManager.callFunc("push", node.json)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
else if node.type = "Person"
|
||||
|
@ -395,6 +409,7 @@ sub Main (args as dynamic) as void
|
|||
selectedIndex = msg.getData()
|
||||
screenContent = msg.getRoSGNode()
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("resetShuffle")
|
||||
m.global.queueManager.callFunc("push", screenContent.albumData.items[node.id])
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
else
|
||||
|
|
|
@ -47,9 +47,6 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
|
|||
end if
|
||||
|
||||
if m.videotype = "Episode" or m.videotype = "Series"
|
||||
if isValid(meta.json) and isValid(meta.json.RunTimeTicks)
|
||||
video.runTime = (meta.json.RunTimeTicks / 10000000.0)
|
||||
end if
|
||||
video.content.contenttype = "episode"
|
||||
end if
|
||||
|
||||
|
|
|
@ -1,146 +1,50 @@
|
|||
[
|
||||
{
|
||||
"description": "Feature: Phase 1 CJK subtitle support - external files only",
|
||||
"author": "jkim2492"
|
||||
"description": "Bug Fix: Rows on home view not refreshing when data has changed",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Bug Fix: Fix multiple client crashes identified by crashlogs",
|
||||
"description": "Bug Fix: Next up section does not populate correctly after watching a TV episode",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Bug Fix: Next episode button does not display consistently",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Bug Fix: Crash when fallback font API call fails",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Core: Fix Jellyfin links in readme",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "Feature: Add TV series & season shuffle",
|
||||
"description": "Bug Fix: Music shuffle not working",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Updated View: Show \"Actor\" when an actor has no role",
|
||||
"description": "Updated View: Add current playback time and song length to the sides of the audio progress bar",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Bug Fix: Turn off loop mode when user manually changes song",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Core: Enable confirm partner button and setup RALE",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "Feature: Phase 1 playlist support",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Fix Typo: trancoding -> transcoding",
|
||||
"author": "RussianCow"
|
||||
},
|
||||
{
|
||||
"description": "Feature: Create global audio player",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Core: Add user policy to check if canDelete",
|
||||
"author": "candry7731"
|
||||
},
|
||||
{
|
||||
"description": "Bug Fix: Only show next episode button if \"Play next episode automatically\" setting is enabled in web client",
|
||||
"description": "Bug Fix: Clock not displaying on login view",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "New Setting: Disable unwatched episode count",
|
||||
"author": "1hitsong"
|
||||
"description": "Core: Add build badge and rework readme content",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "New Setting: Next episode button time",
|
||||
"author": "candry7731"
|
||||
},
|
||||
{
|
||||
"description": "New View: New persondetails view",
|
||||
"description": "Core: Adjust settings to follow latest guidelines",
|
||||
"author": "sevenrats"
|
||||
},
|
||||
{
|
||||
"description": "Updated View: Make title scrolling consistent in extras slider",
|
||||
"author": "sevenrats"
|
||||
},
|
||||
{
|
||||
"description": "Feature: Make pressing the options button close settings menu",
|
||||
"author": "sevenrats"
|
||||
},
|
||||
{
|
||||
"description": "Bug Fix: Graceful episode playback failure",
|
||||
"author": "sevenrats"
|
||||
},
|
||||
{
|
||||
"description": "Bug Fix: Fix default view setting for movie genres",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Core: Update CI ubuntu version and node version",
|
||||
"author": "sevenrats"
|
||||
},
|
||||
{
|
||||
"description": "New Setting: Custom max video bitrate",
|
||||
"author": "jimdogx"
|
||||
},
|
||||
{
|
||||
"description": "Updated View: Updated \"OnNow\" home row to default to channel images if program images are not availible",
|
||||
"author": "candry7731"
|
||||
},
|
||||
{
|
||||
"description": "Updated View: Improve settings menu, implement title hover and hide in missing locations",
|
||||
"author": "sevenrats"
|
||||
},
|
||||
{
|
||||
"description": "Updated View: Make home view load faster",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Bug Fix: Fix option menu focus if opened while library still loading",
|
||||
"author": "ApexArray"
|
||||
},
|
||||
{
|
||||
"description": "Updated View: Add Genres, Parental Ratings, and Years as movie filters",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Bug Fix: Revert change that removed image cache busting",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Updated View: Fix distorted TV episode posters. Add client-side progress bar and played indicator.",
|
||||
"author": "ApexArray"
|
||||
},
|
||||
{
|
||||
"description": "Updated View: Improve quality of album art on now playing view",
|
||||
"author": "1hitsong"
|
||||
},
|
||||
{
|
||||
"description": "Updated View: Loading spinner, Progress Dialog and movie details button animate",
|
||||
"author": "candry7731"
|
||||
},
|
||||
{
|
||||
"description": "Core: Add settings guidelines to Dev Guide",
|
||||
"author": "sevenrats"
|
||||
},
|
||||
{
|
||||
"description": "Core: Add workflow to validate language translation files",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "Core: Remove optional chaining operators from code",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "Core: Make CI throw error for duplicate translation entries",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "Core: Fix en_US translation file",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "Core: Add a production build workflow",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "Core: Add json and markdown to lint workflow + add automation workflow",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "Core: Make workflows use latest LTS release + cache npm",
|
||||
"author": "cewert"
|
||||
},
|
||||
{
|
||||
"description": "Core: Install & Configure code unit test suite",
|
||||
"author": "1hitsong"
|
||||
}
|
||||
]
|
|
@ -41,6 +41,18 @@ function ticksToHuman(ticks as longinteger) as string
|
|||
return r
|
||||
end function
|
||||
|
||||
function secondsToHuman(totalSeconds as integer) as string
|
||||
hours = stri(int(totalSeconds / 3600)).trim()
|
||||
minutes = stri(int((totalSeconds - (val(hours) * 3600)) / 60)).trim()
|
||||
seconds = stri(totalSeconds - (val(hours) * 3600) - (val(minutes) * 60)).trim()
|
||||
if val(hours) > 0 and val(minutes) < 10 then minutes = "0" + minutes
|
||||
if val(seconds) < 10 then seconds = "0" + seconds
|
||||
r = ""
|
||||
if val(hours) > 0 then r = hours + ":"
|
||||
r = r + minutes + ":" + seconds
|
||||
return r
|
||||
end function
|
||||
|
||||
' Format time as 12 or 24 hour format based on system clock setting
|
||||
function formatTime(time) as string
|
||||
hours = time.getHours()
|
||||
|
|
Loading…
Reference in New Issue
Block a user