Skip to content

Commit

Permalink
Merge pull request #1198 from cewert/unstable
Browse files Browse the repository at this point in the history
  • Loading branch information
cewert authored Apr 14, 2023
2 parents 010fed5 + fd47e53 commit 86305e4
Show file tree
Hide file tree
Showing 26 changed files with 10,915 additions and 10,241 deletions.
4 changes: 4 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions DEVGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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/**\*

Expand Down
62 changes: 32 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 0 additions & 1 deletion components/ItemGrid/LoadVideoContentTask.brs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion components/JFOverhang.brs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 8 additions & 5 deletions components/JFVideo.brs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 0 additions & 2 deletions components/JFVideo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
69 changes: 39 additions & 30 deletions components/home/HomeRows.brs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
57 changes: 56 additions & 1 deletion components/manager/QueueManager.brs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -108,14 +115,62 @@ 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)
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))
Expand Down
Loading

0 comments on commit 86305e4

Please sign in to comment.