Merge remote-tracking branch 'upstream/unstable' into fix-crashes-main
This commit is contained in:
commit
2b2c366f37
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -7,33 +7,34 @@ assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Software Versions**
|
## Software Versions
|
||||||
Jellyfin Server Version:
|
|
||||||
Roku Client Version:
|
|
||||||
|
|
||||||
**Describe the bug**
|
- Jellyfin Server Version:
|
||||||
|
- Roku Client Version:
|
||||||
|
|
||||||
|
## Describe the bug
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
**How To Reproduce**
|
## How To Reproduce
|
||||||
<!-- Steps to reproduce the behavior: -->
|
<!-- Steps to reproduce the behavior: -->
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. Bug occurs
|
4. Bug occurs
|
||||||
|
|
||||||
**Expected behavior**
|
## Expected behavior
|
||||||
<!-- A clear and concise description of what you expected to happen. -->
|
<!-- A clear and concise description of what you expected to happen. -->
|
||||||
|
|
||||||
**Logs**
|
## Logs
|
||||||
<!-- Please paste any log errors. -->
|
<!-- Please paste any log errors. -->
|
||||||
|
|
||||||
**Screenshots**
|
## Screenshots
|
||||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||||
|
|
||||||
**Connection Information**
|
## Connection Information
|
||||||
Is server local or remote?
|
|
||||||
|
|
||||||
Is server connection http or https?
|
- Is server local or remote?
|
||||||
|
- Is server connection HTTP or HTTPS?
|
||||||
|
|
||||||
**Additional context**
|
## Additional context
|
||||||
<!-- Add any other context about the problem here. -->
|
<!-- Add any other context about the problem here. -->
|
||||||
|
|
10
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
10
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
|
@ -1,20 +1,20 @@
|
||||||
---
|
---
|
||||||
name: Enhancement request
|
name: Enhancement request
|
||||||
about: Suggest an modification to an existing feature
|
about: Suggest a modification to an existing feature
|
||||||
title: ''
|
title: ''
|
||||||
labels: enhancement
|
labels: enhancement
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
## Is your feature request related to a problem? Please describe
|
||||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
## Describe the solution you'd like
|
||||||
<!-- A clear and concise description of what you want to happen. -->
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
## Describe alternatives you've considered
|
||||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
**Additional context**
|
## Additional context
|
||||||
<!-- Add any other context or screenshots about the feature request here. -->
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
|
|
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -7,8 +7,8 @@ assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the feature you'd like**
|
## Describe the feature you'd like
|
||||||
<!-- A clear and concise description of what you want to happen. -->
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
**Additional context**
|
## Additional context
|
||||||
<!-- Add any other context or screenshots about the feature request here. -->
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
|
|
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
|
@ -2,10 +2,10 @@
|
||||||
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
|
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
|
||||||
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.readthedocs.io/en/latest/developer-docs/contributing/ page.
|
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.readthedocs.io/en/latest/developer-docs/contributing/ page.
|
||||||
-->
|
-->
|
||||||
|
<!-- markdownlint-disable MD041 first-line-heading -->
|
||||||
**Changes**
|
## Changes
|
||||||
<!-- Describe your changes here in 1-5 sentences. -->
|
<!-- Describe your changes here in 1-5 sentences. -->
|
||||||
|
|
||||||
**Issues**
|
## Issues
|
||||||
<!-- Tag any issues that this PR solves here.
|
<!-- Tag any issues that this PR solves here.
|
||||||
ex. Fixes # -->
|
ex. Fixes # -->
|
||||||
|
|
17
.github/workflows/lint.yml
vendored
17
.github/workflows/lint.yml
vendored
|
@ -57,4 +57,19 @@ jobs:
|
||||||
run: npx ropm install
|
run: npx ropm install
|
||||||
- uses: xt0rted/markdownlint-problem-matcher@98d94724052d20ca2e06c091f202e4c66c3c59fb # v2
|
- uses: xt0rted/markdownlint-problem-matcher@98d94724052d20ca2e06c091f202e4c66c3c59fb # v2
|
||||||
- name: Lint markdown files
|
- name: Lint markdown files
|
||||||
run: npm run lint-markdown
|
run: npm run lint-markdown
|
||||||
|
spelling:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clone github repo
|
||||||
|
uses: actions/checkout@master
|
||||||
|
- uses: actions/setup-node@master
|
||||||
|
with:
|
||||||
|
node-version: "lts/*"
|
||||||
|
cache: "npm"
|
||||||
|
- name: Install npm dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Install roku package dependencies
|
||||||
|
run: npx ropm install
|
||||||
|
- name: Check markdown files for spelling errors
|
||||||
|
run: npm run lint-spelling
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"ignore": [
|
|
||||||
"node_modules/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
17
.spelling
17
.spelling
|
@ -1,17 +0,0 @@
|
||||||
jellyfin
|
|
||||||
brightscript
|
|
||||||
vscode
|
|
||||||
roku
|
|
||||||
github
|
|
||||||
pre-release
|
|
||||||
sideload
|
|
||||||
dev
|
|
||||||
repo
|
|
||||||
hardcode
|
|
||||||
hardcoding
|
|
||||||
breakpoint
|
|
||||||
DEVGUIDE
|
|
||||||
runtime
|
|
||||||
translations.ts
|
|
||||||
en_US
|
|
||||||
ing
|
|
12
DEVGUIDE.md
12
DEVGUIDE.md
|
@ -60,16 +60,16 @@ We recommend using Visual Studio Code when working on this project. The [BrightS
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
1. Open the `jellyfin-roku` folder in vscode
|
1. Open the `jellyfin-roku` folder in VSCode
|
||||||
2. Press `F5` on your keyboard or click `Run` -> `Start Debugging` from the vscode menu. ![image](https://user-images.githubusercontent.com/2544493/170696233-8ba49bf4-bebb-4655-88f3-ac45150dda02.png)
|
2. Press `F5` on your keyboard or click `Run` -> `Start Debugging` from the VSCode menu. ![image](https://user-images.githubusercontent.com/2544493/170696233-8ba49bf4-bebb-4655-88f3-ac45150dda02.png)
|
||||||
|
|
||||||
3. Enter your Roku IP address and developer password when prompted
|
3. Enter your Roku IP address and developer password when prompted
|
||||||
|
|
||||||
That's it! vscode will auto-package the project, sideload it to the specified device, and the channel is up and running. (assuming you remembered to put your device in [developer mode](#developer-mode))
|
That's it! VSCode will auto-package the project, sideload it to the specified device, and the channel is up and running. (assuming you remembered to put your device in [developer mode](#developer-mode))
|
||||||
|
|
||||||
### Hardcoding Roku Information
|
### Hardcoding Roku Information
|
||||||
|
|
||||||
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:
|
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
|
```js
|
||||||
{
|
{
|
||||||
|
@ -95,7 +95,7 @@ Build the package
|
||||||
make dev
|
make dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This will create a zip in `out/apps/Jellyfin_Roku-dev.zip`. Login to your roku's device in your browser and upload the zip file then run install.
|
This will create a zip in `out/apps/Jellyfin_Roku-dev.zip`. Login to your Roku's device in your browser and upload the zip file then run install.
|
||||||
|
|
||||||
## Method 3: Direct load to Roku Device
|
## Method 3: Direct load to Roku Device
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ make install
|
||||||
|
|
||||||
Modify code -> `make install` -> Use Roku remote to test changes -> `telnet ${ROKU_DEV_TARGET} 8085` -> `CTRL + ]` -> `quit + ENTER`
|
Modify code -> `make install` -> Use Roku remote to test changes -> `telnet ${ROKU_DEV_TARGET} 8085` -> `CTRL + ]` -> `quit + ENTER`
|
||||||
|
|
||||||
Unfortunately there is no debugger. You will need to use telnet to see log statements, warnings, and error reports. You won't always need to telnet into your device but the workflow above is typical when you are new to Brightscript or are working on tricky code.
|
Unfortunately there is no debugger. You will need to use telnet to see log statements, warnings, and error reports. You won't always need to telnet into your device but the workflow above is typical when you are new to BrightScript or are working on tricky code.
|
||||||
|
|
||||||
Install necessary packages:
|
Install necessary packages:
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ The channel is available on the [Roku Channel Store](https://my.roku.com/add/jel
|
||||||
|
|
||||||
## Getting Involved<a name="get_involved"></a>
|
## Getting Involved<a name="get_involved"></a>
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ sub init()
|
||||||
'Parent is MarkupGrid and it's parent is the ItemGrid
|
'Parent is MarkupGrid and it's parent is the ItemGrid
|
||||||
m.topParent = m.top.GetParent().GetParent()
|
m.topParent = m.top.GetParent().GetParent()
|
||||||
'Get the imageDisplayMode for these grid items
|
'Get the imageDisplayMode for these grid items
|
||||||
if m.topParent.imageDisplayMode <> invalid
|
if isValid(m.topParent.imageDisplayMode)
|
||||||
m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode
|
m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ sub itemContentChanged()
|
||||||
m.itemText.text = itemData.Title
|
m.itemText.text = itemData.Title
|
||||||
else if itemData.type = "Series"
|
else if itemData.type = "Series"
|
||||||
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
||||||
if itemData?.json?.UserData?.UnplayedItemCount <> invalid
|
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
|
||||||
if itemData.json.UserData.UnplayedItemCount > 0
|
if itemData.json.UserData.UnplayedItemCount > 0
|
||||||
m.unplayedCount.visible = true
|
m.unplayedCount.visible = true
|
||||||
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
||||||
|
@ -98,7 +98,7 @@ sub itemContentChanged()
|
||||||
|
|
||||||
m.posterText.height = 200
|
m.posterText.height = 200
|
||||||
m.posterText.width = 280
|
m.posterText.width = 280
|
||||||
else if itemData.json.type = "MusicAlbum"
|
else if isValid(itemData.json.type) and itemData.json.type = "MusicAlbum"
|
||||||
m.itemPoster.uri = itemData.PosterUrl
|
m.itemPoster.uri = itemData.PosterUrl
|
||||||
m.itemText.text = itemData.Title
|
m.itemText.text = itemData.Title
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<component name="GridItem" extends="Group">
|
<component name="GridItem" extends="Group">
|
||||||
<children>
|
<children>
|
||||||
<maskGroup id="posterMask" maskUri="pkg:/images/postermask.png" scaleRotateCenter="[145, 212.5]" scale="[0.85,0.85]">
|
<maskGroup id="posterMask" maskUri="pkg:/images/postermask.png" scaleRotateCenter="[145, 212.5]" scale="[0.85,0.85]">
|
||||||
|
@ -20,4 +20,5 @@
|
||||||
</interface>
|
</interface>
|
||||||
<script type="text/brightscript" uri="GridItem.brs" />
|
<script type="text/brightscript" uri="GridItem.brs" />
|
||||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||||
</component>
|
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||||
|
</component>
|
|
@ -4,7 +4,7 @@ sub init()
|
||||||
m.top.limit = 60
|
m.top.limit = 60
|
||||||
usersettingLimit = get_user_setting("itemgrid.Limit")
|
usersettingLimit = get_user_setting("itemgrid.Limit")
|
||||||
|
|
||||||
if usersettingLimit <> invalid
|
if isValid(usersettingLimit)
|
||||||
m.top.limit = usersettingLimit
|
m.top.limit = usersettingLimit
|
||||||
end if
|
end if
|
||||||
end sub
|
end sub
|
||||||
|
@ -135,7 +135,7 @@ sub LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtit
|
||||||
if m.playbackInfo.MediaSources[0].MediaStreams.Count() > 0 and meta.live = false
|
if m.playbackInfo.MediaSources[0].MediaStreams.Count() > 0 and meta.live = false
|
||||||
tryDirectPlay = get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
|
tryDirectPlay = get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
|
||||||
tryDirectPlay = tryDirectPlay or (get_user_setting("playback.tryDirect.hevcProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc")
|
tryDirectPlay = tryDirectPlay or (get_user_setting("playback.tryDirect.hevcProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc")
|
||||||
if tryDirectPlay and m.playbackInfo.MediaSources[0].TranscodingUrl <> invalid and forceTranscoding = false
|
if tryDirectPlay and isValid(m.playbackInfo.MediaSources[0].TranscodingUrl) and forceTranscoding = false
|
||||||
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
|
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
|
||||||
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
|
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
|
||||||
video.directPlaySupported = true
|
video.directPlaySupported = true
|
||||||
|
@ -262,7 +262,7 @@ end function
|
||||||
|
|
||||||
function directPlaySupported(meta as object) as boolean
|
function directPlaySupported(meta as object) as boolean
|
||||||
devinfo = CreateObject("roDeviceInfo")
|
devinfo = CreateObject("roDeviceInfo")
|
||||||
if meta.json.MediaSources[0] <> invalid and meta.json.MediaSources[0].SupportsDirectPlay = false
|
if isValid(meta.json.MediaSources[0]) and meta.json.MediaSources[0].SupportsDirectPlay = false
|
||||||
return false
|
return false
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
@ -271,10 +271,10 @@ function directPlaySupported(meta as object) as boolean
|
||||||
end if
|
end if
|
||||||
|
|
||||||
streamInfo = { Codec: meta.json.MediaStreams[0].codec }
|
streamInfo = { Codec: meta.json.MediaStreams[0].codec }
|
||||||
if meta.json.MediaStreams[0].Profile <> invalid and meta.json.MediaStreams[0].Profile.len() > 0
|
if isValid(meta.json.MediaStreams[0].Profile) and meta.json.MediaStreams[0].Profile.len() > 0
|
||||||
streamInfo.Profile = LCase(meta.json.MediaStreams[0].Profile)
|
streamInfo.Profile = LCase(meta.json.MediaStreams[0].Profile)
|
||||||
end if
|
end if
|
||||||
if meta.json.MediaSources[0].container <> invalid and meta.json.MediaSources[0].container.len() > 0
|
if isValid(meta.json.MediaSources[0].container) and meta.json.MediaSources[0].container.len() > 0
|
||||||
'CanDecodeVideo() requires the .container to be format: “mp4”, “hls”, “mkv”, “ism”, “dash”, “ts” if its to direct stream
|
'CanDecodeVideo() requires the .container to be format: “mp4”, “hls”, “mkv”, “ism”, “dash”, “ts” if its to direct stream
|
||||||
if meta.json.MediaSources[0].container = "mov"
|
if meta.json.MediaSources[0].container = "mov"
|
||||||
streamInfo.Container = "mp4"
|
streamInfo.Container = "mp4"
|
||||||
|
@ -333,12 +333,12 @@ sub autoPlayNextEpisode(videoID as string, showID as string)
|
||||||
resp = APIRequest(url, urlParams)
|
resp = APIRequest(url, urlParams)
|
||||||
data = getJson(resp)
|
data = getJson(resp)
|
||||||
|
|
||||||
if data <> invalid and data.Items.Count() = 2
|
if isValid(data) and data.Items.Count() = 2
|
||||||
' setup new video node
|
' setup new video node
|
||||||
nextVideo = invalid
|
nextVideo = invalid
|
||||||
' remove last videoplayer scene
|
' remove last videoplayer scene
|
||||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||||
if nextVideo <> invalid
|
if isValid(nextVideo)
|
||||||
m.global.sceneManager.callFunc("pushScene", nextVideo)
|
m.global.sceneManager.callFunc("pushScene", nextVideo)
|
||||||
else
|
else
|
||||||
m.global.sceneManager.callFunc("popScene")
|
m.global.sceneManager.callFunc("popScene")
|
||||||
|
@ -356,7 +356,7 @@ end sub
|
||||||
' In the future, with a custom playback info view, we can return an associated array.
|
' In the future, with a custom playback info view, we can return an associated array.
|
||||||
function GetPlaybackInfo()
|
function GetPlaybackInfo()
|
||||||
sessions = api_API().sessions.get()
|
sessions = api_API().sessions.get()
|
||||||
if sessions <> invalid and sessions.Count() > 0
|
if isValid(sessions) and sessions.Count() > 0
|
||||||
return GetTranscodingStats(sessions[0])
|
return GetTranscodingStats(sessions[0])
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
@ -507,7 +507,7 @@ end function
|
||||||
' returns the server-side track index for the appriate subtitle
|
' returns the server-side track index for the appriate subtitle
|
||||||
function defaultSubtitleTrackFromVid(video_id) as integer
|
function defaultSubtitleTrackFromVid(video_id) as integer
|
||||||
meta = ItemMetaData(video_id)
|
meta = ItemMetaData(video_id)
|
||||||
if meta?.json?.mediaSources <> invalid
|
if isValid(meta) and isValid(meta.json) and isValid(meta.json.mediaSources)
|
||||||
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
|
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
|
||||||
default_text_subs = defaultSubtitleTrack(subtitles["all"], true) ' Find correct subtitle track (forced text)
|
default_text_subs = defaultSubtitleTrack(subtitles["all"], true) ' Find correct subtitle track (forced text)
|
||||||
if default_text_subs <> -1
|
if default_text_subs <> -1
|
||||||
|
@ -608,7 +608,7 @@ function sortSubtitles(id as string, MediaStreams)
|
||||||
if stream.type = "Subtitle"
|
if stream.type = "Subtitle"
|
||||||
|
|
||||||
url = ""
|
url = ""
|
||||||
if stream.DeliveryUrl <> invalid
|
if isValid(stream.DeliveryUrl)
|
||||||
url = buildURL(stream.DeliveryUrl)
|
url = buildURL(stream.DeliveryUrl)
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,7 @@ sub updateTime()
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
sub resetTime()
|
sub resetTime()
|
||||||
|
if m.hideClock then return
|
||||||
m.currentTimeTimer.control = "stop"
|
m.currentTimeTimer.control = "stop"
|
||||||
m.currentTimeTimer.control = "start"
|
m.currentTimeTimer.control = "start"
|
||||||
updateTime()
|
updateTime()
|
||||||
|
|
|
@ -18,9 +18,8 @@ sub init()
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
sub updateSize()
|
sub updateSize()
|
||||||
|
|
||||||
image = invalid
|
image = invalid
|
||||||
if m.top.itemContent <> invalid and m.top.itemContent.image <> invalid
|
if isValid(m.top.itemContent) and isValid(m.top.itemContent.image)
|
||||||
image = m.top.itemContent.image
|
image = m.top.itemContent.image
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
@ -49,7 +48,6 @@ sub updateSize()
|
||||||
|
|
||||||
m.backdrop.width = m.poster.width
|
m.backdrop.width = m.poster.width
|
||||||
m.backdrop.height = m.poster.height
|
m.backdrop.height = m.poster.height
|
||||||
|
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
sub itemContentChanged() as void
|
sub itemContentChanged() as void
|
||||||
|
@ -58,7 +56,7 @@ sub itemContentChanged() as void
|
||||||
m.title.text = itemData.title
|
m.title.text = itemData.title
|
||||||
|
|
||||||
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
||||||
if itemData?.json?.UserData?.UnplayedItemCount <> invalid
|
if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
|
||||||
if itemData.json.UserData.UnplayedItemCount > 0
|
if itemData.json.UserData.UnplayedItemCount > 0
|
||||||
m.unplayedCount.visible = true
|
m.unplayedCount.visible = true
|
||||||
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
||||||
|
@ -66,12 +64,11 @@ sub itemContentChanged() as void
|
||||||
end if
|
end if
|
||||||
end if
|
end if
|
||||||
|
|
||||||
if itemData.json.lookup("Type") = "Episode" and itemData.json.IndexNumber <> invalid
|
if itemData.json.lookup("Type") = "Episode" and isValid(itemData.json.IndexNumber)
|
||||||
m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text
|
m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text
|
||||||
|
|
||||||
m.series.text = itemData.json.Series
|
m.series.text = itemData.json.Series
|
||||||
m.series.visible = true
|
m.series.visible = true
|
||||||
|
|
||||||
else if itemData.json.lookup("Type") = "MusicAlbum"
|
else if itemData.json.lookup("Type") = "MusicAlbum"
|
||||||
m.title.font = "font:SmallestSystemFont"
|
m.title.font = "font:SmallestSystemFont"
|
||||||
m.staticTitle.font = "font:SmallestSystemFont"
|
m.staticTitle.font = "font:SmallestSystemFont"
|
||||||
|
@ -83,8 +80,7 @@ sub itemContentChanged() as void
|
||||||
imageUrl = itemData.posterURL
|
imageUrl = itemData.posterURL
|
||||||
|
|
||||||
if get_user_setting("ui.tvshows.blurunwatched") = "true"
|
if get_user_setting("ui.tvshows.blurunwatched") = "true"
|
||||||
|
if itemData.json.lookup("Type") = "Episode" and isValid(itemData.json.userdata)
|
||||||
if itemData.json.lookup("Type") = "Episode" and itemData.json.userdata <> invalid
|
|
||||||
if not itemData.json.userdata.played
|
if not itemData.json.userdata.played
|
||||||
imageUrl = imageUrl + "&blur=15"
|
imageUrl = imageUrl + "&blur=15"
|
||||||
end if
|
end if
|
||||||
|
@ -99,25 +95,21 @@ end sub
|
||||||
'
|
'
|
||||||
' Enable title scrolling based on item Focus
|
' Enable title scrolling based on item Focus
|
||||||
sub focusChanged()
|
sub focusChanged()
|
||||||
|
|
||||||
if m.top.itemHasFocus = true
|
if m.top.itemHasFocus = true
|
||||||
m.title.repeatCount = -1
|
m.title.repeatCount = -1
|
||||||
m.series.repeatCount = -1
|
m.series.repeatCount = -1
|
||||||
m.staticTitle.visible = false
|
m.staticTitle.visible = false
|
||||||
m.title.visible = true
|
m.title.visible = true
|
||||||
|
|
||||||
' text to speech for accessibility
|
' text to speech for accessibility
|
||||||
if m.deviceInfo.IsAudioGuideEnabled() = true
|
if m.deviceInfo.IsAudioGuideEnabled() = true
|
||||||
txt2Speech = CreateObject("roTextToSpeech")
|
txt2Speech = CreateObject("roTextToSpeech")
|
||||||
txt2Speech.Flush()
|
txt2Speech.Flush()
|
||||||
txt2Speech.Say(m.title.text)
|
txt2Speech.Say(m.title.text)
|
||||||
end if
|
end if
|
||||||
|
|
||||||
else
|
else
|
||||||
m.title.repeatCount = 0
|
m.title.repeatCount = 0
|
||||||
m.series.repeatCount = 0
|
m.series.repeatCount = 0
|
||||||
m.staticTitle.visible = true
|
m.staticTitle.visible = true
|
||||||
m.title.visible = false
|
m.title.visible = false
|
||||||
end if
|
end if
|
||||||
|
|
||||||
end sub
|
end sub
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<component name="ListPoster" extends="Group">
|
<component name="ListPoster" extends="Group">
|
||||||
<children>
|
<children>
|
||||||
<Rectangle id="backdrop" />
|
<Rectangle id="backdrop" />
|
||||||
|
@ -12,10 +12,11 @@
|
||||||
<Label id="staticTitle" horizAlign="center" font="font:SmallSystemFont" wrap="false" />
|
<Label id="staticTitle" horizAlign="center" font="font:SmallSystemFont" wrap="false" />
|
||||||
</children>
|
</children>
|
||||||
<interface>
|
<interface>
|
||||||
<field id="itemContent" type="node" onChange="itemContentChanged"/>
|
<field id="itemContent" type="node" onChange="itemContentChanged" />
|
||||||
<field id="itemWidth" type="integer" />
|
<field id="itemWidth" type="integer" />
|
||||||
<field id="itemHasFocus" type="boolean" onChange="focusChanged" />
|
<field id="itemHasFocus" type="boolean" onChange="focusChanged" />
|
||||||
</interface>
|
</interface>
|
||||||
<script type="text/brightscript" uri="ListPoster.brs" />
|
<script type="text/brightscript" uri="ListPoster.brs" />
|
||||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||||
</component>
|
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||||
|
</component>
|
|
@ -33,13 +33,13 @@ sub itemContentChanged()
|
||||||
|
|
||||||
m.backdrop.width = itemData.imageWidth
|
m.backdrop.width = itemData.imageWidth
|
||||||
|
|
||||||
if itemData.iconUrl <> invalid
|
if isValid(itemData.iconUrl)
|
||||||
m.itemIcon.uri = itemData.iconUrl
|
m.itemIcon.uri = itemData.iconUrl
|
||||||
end if
|
end if
|
||||||
|
|
||||||
if LCase(itemData.type) = "series"
|
if LCase(itemData.type) = "series"
|
||||||
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
||||||
if itemData?.json?.UserData?.UnplayedItemCount <> invalid
|
if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
|
||||||
if itemData.json.UserData.UnplayedItemCount > 0
|
if itemData.json.UserData.UnplayedItemCount > 0
|
||||||
m.unplayedCount.visible = true
|
m.unplayedCount.visible = true
|
||||||
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
||||||
|
@ -84,7 +84,7 @@ sub itemContentChanged()
|
||||||
end if
|
end if
|
||||||
|
|
||||||
' Set Episode title if available
|
' Set Episode title if available
|
||||||
if itemData.json.EpisodeTitle <> invalid
|
if isValid(itemData.json.EpisodeTitle)
|
||||||
m.itemTextExtra.text = itemData.json.EpisodeTitle
|
m.itemTextExtra.text = itemData.json.EpisodeTitle
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
@ -106,10 +106,10 @@ sub itemContentChanged()
|
||||||
|
|
||||||
' Set Series and Episode Number for Extra Text
|
' Set Series and Episode Number for Extra Text
|
||||||
extraPrefix = ""
|
extraPrefix = ""
|
||||||
if itemData.json.ParentIndexNumber <> invalid
|
if isValid(itemData.json.ParentIndexNumber)
|
||||||
extraPrefix = "S" + StrI(itemData.json.ParentIndexNumber).trim()
|
extraPrefix = "S" + StrI(itemData.json.ParentIndexNumber).trim()
|
||||||
end if
|
end if
|
||||||
if itemData.json.IndexNumber <> invalid
|
if isValid(itemData.json.IndexNumber)
|
||||||
extraPrefix = extraPrefix + "E" + StrI(itemData.json.IndexNumber).trim()
|
extraPrefix = extraPrefix + "E" + StrI(itemData.json.IndexNumber).trim()
|
||||||
end if
|
end if
|
||||||
if extraPrefix.len() > 0
|
if extraPrefix.len() > 0
|
||||||
|
@ -136,10 +136,10 @@ sub itemContentChanged()
|
||||||
|
|
||||||
' Set Release Year and Age Rating for Extra Text
|
' Set Release Year and Age Rating for Extra Text
|
||||||
textExtra = ""
|
textExtra = ""
|
||||||
if itemData.json.ProductionYear <> invalid
|
if isValid(itemData.json.ProductionYear)
|
||||||
textExtra = StrI(itemData.json.ProductionYear).trim()
|
textExtra = StrI(itemData.json.ProductionYear).trim()
|
||||||
end if
|
end if
|
||||||
if itemData.json.OfficialRating <> invalid
|
if isValid(itemData.json.OfficialRating)
|
||||||
if textExtra <> ""
|
if textExtra <> ""
|
||||||
textExtra = textExtra + " - " + itemData.json.OfficialRating
|
textExtra = textExtra + " - " + itemData.json.OfficialRating
|
||||||
else
|
else
|
||||||
|
@ -181,14 +181,14 @@ sub itemContentChanged()
|
||||||
end if
|
end if
|
||||||
|
|
||||||
textExtra = ""
|
textExtra = ""
|
||||||
if itemData.json.ProductionYear <> invalid
|
if isValid(itemData.json.ProductionYear)
|
||||||
textExtra = StrI(itemData.json.ProductionYear).trim()
|
textExtra = StrI(itemData.json.ProductionYear).trim()
|
||||||
end if
|
end if
|
||||||
|
|
||||||
' Set Years Run for Extra Text
|
' Set Years Run for Extra Text
|
||||||
if itemData.json.Status = "Continuing"
|
if itemData.json.Status = "Continuing"
|
||||||
textExtra = textExtra + " - Present"
|
textExtra = textExtra + " - Present"
|
||||||
else if itemData.json.Status = "Ended" and itemData.json.EndDate <> invalid
|
else if itemData.json.Status = "Ended" and isValid(itemData.json.EndDate)
|
||||||
textExtra = textExtra + " - " + LEFT(itemData.json.EndDate, 4)
|
textExtra = textExtra + " - " + LEFT(itemData.json.EndDate, 4)
|
||||||
end if
|
end if
|
||||||
m.itemTextExtra.text = textExtra
|
m.itemTextExtra.text = textExtra
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<component name="HomeItem" extends="Group">
|
<component name="HomeItem" extends="Group">
|
||||||
<children>
|
<children>
|
||||||
<Rectangle id="backdrop" width="464" height="261" translation="[8,5]" />
|
<Rectangle id="backdrop" width="464" height="261" translation="[8,5]" />
|
||||||
|
@ -26,4 +26,5 @@
|
||||||
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
|
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
|
||||||
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
||||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||||
|
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||||
</component>
|
</component>
|
|
@ -96,7 +96,7 @@ sub onLibrariesLoaded()
|
||||||
m.LoadFavoritesTask.control = "RUN"
|
m.LoadFavoritesTask.control = "RUN"
|
||||||
|
|
||||||
' validate library data
|
' validate library data
|
||||||
if m.libraryData <> invalid and m.libraryData.count() > 0
|
if isValid(m.libraryData) and m.libraryData.count() > 0
|
||||||
userConfig = m.top.userConfig
|
userConfig = m.top.userConfig
|
||||||
|
|
||||||
' populate My Media row
|
' populate My Media row
|
||||||
|
@ -163,7 +163,7 @@ sub updateFavoritesItems()
|
||||||
rowIndex = getRowIndex("Favorites")
|
rowIndex = getRowIndex("Favorites")
|
||||||
|
|
||||||
if itemData.count() < 1
|
if itemData.count() < 1
|
||||||
if rowIndex <> invalid
|
if isValid(rowIndex)
|
||||||
' remove the row
|
' remove the row
|
||||||
deleteFromSizeArray(rowIndex)
|
deleteFromSizeArray(rowIndex)
|
||||||
homeRows.removeChildIndex(rowIndex)
|
homeRows.removeChildIndex(rowIndex)
|
||||||
|
@ -208,7 +208,7 @@ sub updateContinueItems()
|
||||||
continueRowIndex = getRowIndex("Continue Watching")
|
continueRowIndex = getRowIndex("Continue Watching")
|
||||||
|
|
||||||
if itemData.count() < 1
|
if itemData.count() < 1
|
||||||
if continueRowIndex <> invalid
|
if isValid(continueRowIndex)
|
||||||
' remove the row
|
' remove the row
|
||||||
deleteFromSizeArray(continueRowIndex)
|
deleteFromSizeArray(continueRowIndex)
|
||||||
homeRows.removeChildIndex(continueRowIndex)
|
homeRows.removeChildIndex(continueRowIndex)
|
||||||
|
@ -219,7 +219,7 @@ sub updateContinueItems()
|
||||||
row.title = tr("Continue Watching")
|
row.title = tr("Continue Watching")
|
||||||
itemSize = [464, 331]
|
itemSize = [464, 331]
|
||||||
for each item in itemData
|
for each item in itemData
|
||||||
if item.json?.UserData?.PlayedPercentage <> invalid
|
if isValid(item.json) and isValid(item.json.UserData) and isValid(item.json.UserData.PlayedPercentage)
|
||||||
item.PlayedPercentage = item.json.UserData.PlayedPercentage
|
item.PlayedPercentage = item.json.UserData.PlayedPercentage
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ sub updateNextUpItems()
|
||||||
nextUpRowIndex = getRowIndex("Next Up >")
|
nextUpRowIndex = getRowIndex("Next Up >")
|
||||||
|
|
||||||
if itemData.count() < 1
|
if itemData.count() < 1
|
||||||
if nextUpRowIndex <> invalid
|
if isValid(nextUpRowIndex)
|
||||||
' remove the row
|
' remove the row
|
||||||
deleteFromSizeArray(nextUpRowIndex)
|
deleteFromSizeArray(nextUpRowIndex)
|
||||||
homeRows.removeChildIndex(nextUpRowIndex)
|
homeRows.removeChildIndex(nextUpRowIndex)
|
||||||
|
@ -269,7 +269,7 @@ sub updateNextUpItems()
|
||||||
if nextUpRowIndex = invalid
|
if nextUpRowIndex = invalid
|
||||||
' insert new row under "Continue Watching"
|
' insert new row under "Continue Watching"
|
||||||
continueRowIndex = getRowIndex("Continue Watching")
|
continueRowIndex = getRowIndex("Continue Watching")
|
||||||
if continueRowIndex <> invalid
|
if isValid(continueRowIndex)
|
||||||
updateSizeArray(itemSize, continueRowIndex + 1)
|
updateSizeArray(itemSize, continueRowIndex + 1)
|
||||||
homeRows.insertChild(row, continueRowIndex + 1)
|
homeRows.insertChild(row, continueRowIndex + 1)
|
||||||
else
|
else
|
||||||
|
@ -305,7 +305,7 @@ sub updateLatestItems(msg)
|
||||||
|
|
||||||
if itemData.count() < 1
|
if itemData.count() < 1
|
||||||
' remove row
|
' remove row
|
||||||
if rowIndex <> invalid
|
if isValid(rowIndex)
|
||||||
deleteFromSizeArray(rowIndex)
|
deleteFromSizeArray(rowIndex)
|
||||||
homeRows.removeChildIndex(rowIndex)
|
homeRows.removeChildIndex(rowIndex)
|
||||||
end if
|
end if
|
||||||
|
@ -355,7 +355,7 @@ sub updateOnNowItems()
|
||||||
onNowRowIndex = getRowIndex("On Now")
|
onNowRowIndex = getRowIndex("On Now")
|
||||||
|
|
||||||
if itemData.count() < 1
|
if itemData.count() < 1
|
||||||
if onNowRowIndex <> invalid
|
if isValid(onNowRowIndex)
|
||||||
' remove the row
|
' remove the row
|
||||||
deleteFromSizeArray(onNowRowIndex)
|
deleteFromSizeArray(onNowRowIndex)
|
||||||
homeRows.removeChildIndex(onNowRowIndex)
|
homeRows.removeChildIndex(onNowRowIndex)
|
||||||
|
@ -409,11 +409,11 @@ sub updateSizeArray(rowItemSize, rowIndex = invalid, action = "insert")
|
||||||
newSizeArray.Push(rowItemSize)
|
newSizeArray.Push(rowItemSize)
|
||||||
else if action = "insert"
|
else if action = "insert"
|
||||||
newSizeArray.Push(rowItemSize)
|
newSizeArray.Push(rowItemSize)
|
||||||
if sizeArray[i] <> invalid
|
if isValid(sizeArray[i])
|
||||||
newSizeArray.Push(sizeArray[i])
|
newSizeArray.Push(sizeArray[i])
|
||||||
end if
|
end if
|
||||||
end if
|
end if
|
||||||
else if sizeArray[i] <> invalid
|
else if isValid(sizeArray[i])
|
||||||
newSizeArray.Push(sizeArray[i])
|
newSizeArray.Push(sizeArray[i])
|
||||||
end if
|
end if
|
||||||
end for
|
end for
|
||||||
|
@ -433,7 +433,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||||
if press
|
if press
|
||||||
if key = "play"
|
if key = "play"
|
||||||
itemToPlay = m.top.content.getChild(m.top.rowItemFocused[0]).getChild(m.top.rowItemFocused[1])
|
itemToPlay = m.top.content.getChild(m.top.rowItemFocused[0]).getChild(m.top.rowItemFocused[1])
|
||||||
if itemToPlay <> invalid and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
|
if isValid(itemToPlay) and (itemToPlay.type = "Movie" or itemToPlay.type = "Episode")
|
||||||
m.top.quickPlayNode = itemToPlay
|
m.top.quickPlayNode = itemToPlay
|
||||||
end if
|
end if
|
||||||
handled = true
|
handled = true
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<component name="HomeRows" extends="RowList">
|
<component name="HomeRows" extends="RowList">
|
||||||
<interface>
|
<interface>
|
||||||
<field id="selectedItem" type="node" alwaysNotify="true" />
|
<field id="selectedItem" type="node" alwaysNotify="true" />
|
||||||
|
@ -7,5 +7,6 @@
|
||||||
<function name="updateHomeRows" />
|
<function name="updateHomeRows" />
|
||||||
<function name="loadLibraries" />
|
<function name="loadLibraries" />
|
||||||
</interface>
|
</interface>
|
||||||
<script type="text/brightscript" uri="HomeRows.brs"/>
|
<script type="text/brightscript" uri="HomeRows.brs" />
|
||||||
</component>
|
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||||
|
</component>
|
|
@ -4,7 +4,6 @@ sub init()
|
||||||
m.position = 0
|
m.position = 0
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
'
|
|
||||||
' Clear all content from play queue
|
' Clear all content from play queue
|
||||||
sub clear()
|
sub clear()
|
||||||
m.queue = []
|
m.queue = []
|
||||||
|
@ -12,72 +11,52 @@ sub clear()
|
||||||
setPosition(0)
|
setPosition(0)
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Delete item from play queue at passed index
|
' Delete item from play queue at passed index
|
||||||
sub deleteAtIndex(index)
|
sub deleteAtIndex(index)
|
||||||
m.queue.Delete(index)
|
m.queue.Delete(index)
|
||||||
m.queueTypes.Delete(index)
|
m.queueTypes.Delete(index)
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Return the number of items in the play queue
|
' Return the number of items in the play queue
|
||||||
function getCount()
|
function getCount()
|
||||||
return m.queue.count()
|
return m.queue.count()
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Return the item currently in focus from the play queue
|
' Return the item currently in focus from the play queue
|
||||||
function getCurrentItem()
|
function getCurrentItem()
|
||||||
return getItemByIndex(m.position)
|
return getItemByIndex(m.position)
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Return the item in the passed index from the play queue
|
' Return the item in the passed index from the play queue
|
||||||
function getItemByIndex(index)
|
function getItemByIndex(index)
|
||||||
return m.queue[index]
|
return m.queue[index]
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Returns current playback position within the queue
|
' Returns current playback position within the queue
|
||||||
function getPosition()
|
function getPosition()
|
||||||
return m.position
|
return m.position
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Move queue position back one
|
' Move queue position back one
|
||||||
sub moveBack()
|
sub moveBack()
|
||||||
m.position--
|
m.position--
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Move queue position ahead one
|
' Move queue position ahead one
|
||||||
sub moveForward()
|
sub moveForward()
|
||||||
m.position++
|
m.position++
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Return the current play queue
|
' Return the current play queue
|
||||||
function getQueue()
|
function getQueue()
|
||||||
return m.queue
|
return m.queue
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Return the types of items in current play queue
|
' Return the types of items in current play queue
|
||||||
function getQueueTypes()
|
function getQueueTypes()
|
||||||
return m.queueTypes
|
return m.queueTypes
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Return the unique types of items in current play queue
|
' Return the unique types of items in current play queue
|
||||||
function getQueueUniqueTypes()
|
function getQueueUniqueTypes()
|
||||||
itemTypes = []
|
itemTypes = []
|
||||||
|
@ -91,15 +70,11 @@ function getQueueUniqueTypes()
|
||||||
return itemTypes
|
return itemTypes
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Return item at end of play queue without removing
|
' Return item at end of play queue without removing
|
||||||
function peek()
|
function peek()
|
||||||
return m.queue.peek()
|
return m.queue.peek()
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Play items in queue
|
' Play items in queue
|
||||||
sub playQueue()
|
sub playQueue()
|
||||||
nextItem = getCurrentItem()
|
nextItem = getCurrentItem()
|
||||||
|
@ -116,37 +91,28 @@ sub playQueue()
|
||||||
end if
|
end if
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Remove item at end of play queue
|
' Remove item at end of play queue
|
||||||
sub pop()
|
sub pop()
|
||||||
m.queue.pop()
|
m.queue.pop()
|
||||||
m.queueTypes.pop()
|
m.queueTypes.pop()
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Push new items to the play queue
|
' Push new items to the play queue
|
||||||
sub push(newItem)
|
sub push(newItem)
|
||||||
m.queue.push(newItem)
|
m.queue.push(newItem)
|
||||||
m.queueTypes.push(getItemType(newItem))
|
m.queueTypes.push(getItemType(newItem))
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
'
|
|
||||||
' Set the queue position
|
' Set the queue position
|
||||||
sub setPosition(newPosition)
|
sub setPosition(newPosition)
|
||||||
m.position = newPosition
|
m.position = newPosition
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Return the fitst item in the play queue
|
' Return the fitst item in the play queue
|
||||||
function top()
|
function top()
|
||||||
return getItemByIndex(0)
|
return getItemByIndex(0)
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
||||||
'
|
|
||||||
' Replace play queue with passed array
|
' Replace play queue with passed array
|
||||||
sub set(items)
|
sub set(items)
|
||||||
setPosition(0)
|
setPosition(0)
|
||||||
|
@ -157,10 +123,9 @@ sub set(items)
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
function getItemType(item) as string
|
function getItemType(item) as string
|
||||||
|
if isValid(item) and isValid(item.json) and isValid(item.json.mediatype) and item.json.mediatype <> ""
|
||||||
if isValid(item?.json?.mediatype) and item.json.mediatype <> ""
|
|
||||||
return LCase(item.json.mediatype)
|
return LCase(item.json.mediatype)
|
||||||
else if isValid(item?.type) and item.type <> ""
|
else if isValid(item) and isValid(item.type) and item.type <> ""
|
||||||
return LCase(item.type)
|
return LCase(item.type)
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
'
|
|
||||||
' View Creators
|
|
||||||
' ----------------
|
|
||||||
|
|
||||||
' Play Audio
|
' Play Audio
|
||||||
sub CreateAudioPlayerView()
|
sub CreateAudioPlayerView()
|
||||||
m.view = CreateObject("roSGNode", "AudioPlayerView")
|
m.view = CreateObject("roSGNode", "AudioPlayerView")
|
||||||
|
@ -26,14 +22,12 @@ sub CreateVideoPlayerView()
|
||||||
m.global.sceneManager.callFunc("pushScene", m.view)
|
m.global.sceneManager.callFunc("pushScene", m.view)
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
' -----------------
|
||||||
'
|
|
||||||
' Event Handlers
|
' Event Handlers
|
||||||
' -----------------
|
' -----------------
|
||||||
|
|
||||||
' User requested subtitle selection popup
|
' User requested subtitle selection popup
|
||||||
sub onSelectSubtitlePressed()
|
sub onSelectSubtitlePressed()
|
||||||
|
|
||||||
' None is always first in the subtitle list
|
' None is always first in the subtitle list
|
||||||
subtitleData = {
|
subtitleData = {
|
||||||
data: [{ "description": "None", "type": "subtitleselection" }]
|
data: [{ "description": "None", "type": "subtitleselection" }]
|
||||||
|
@ -80,9 +74,8 @@ end sub
|
||||||
|
|
||||||
' User requested playback info
|
' User requested playback info
|
||||||
sub onSelectPlaybackInfoPressed()
|
sub onSelectPlaybackInfoPressed()
|
||||||
|
|
||||||
' Check if we already have playback info and show it in a popup
|
' Check if we already have playback info and show it in a popup
|
||||||
if isValid(m.playbackData?.playbackinfo)
|
if isValid(m.playbackData) and isValid(m.playbackData.playbackinfo)
|
||||||
m.global.sceneManager.callFunc("standardDialog", tr("Playback Info"), m.playbackData.playbackinfo)
|
m.global.sceneManager.callFunc("standardDialog", tr("Playback Info"), m.playbackData.playbackinfo)
|
||||||
return
|
return
|
||||||
end if
|
end if
|
||||||
|
@ -95,12 +88,11 @@ sub onPlaybackInfoLoaded()
|
||||||
m.playbackData = m.getPlaybackInfoTask.data
|
m.playbackData = m.getPlaybackInfoTask.data
|
||||||
|
|
||||||
' Check if we have playback info and show it in a popup
|
' Check if we have playback info and show it in a popup
|
||||||
if isValid(m.playbackData?.playbackinfo)
|
if isValid(m.playbackData) and isValid(m.playbackData.playbackinfo)
|
||||||
m.global.sceneManager.callFunc("standardDialog", tr("Playback Info"), m.playbackData.playbackinfo)
|
m.global.sceneManager.callFunc("standardDialog", tr("Playback Info"), m.playbackData.playbackinfo)
|
||||||
end if
|
end if
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
|
||||||
' Playback state change event handlers
|
' Playback state change event handlers
|
||||||
sub onStateChange()
|
sub onStateChange()
|
||||||
if LCase(m.view.state) = "finished"
|
if LCase(m.view.state) = "finished"
|
||||||
|
|
|
@ -19,7 +19,7 @@ end sub
|
||||||
|
|
||||||
sub updateSeason()
|
sub updateSeason()
|
||||||
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
||||||
if m.top.seasonData?.UserData?.UnplayedItemCount <> invalid
|
if isValid(m.top.seasonData) and isValid(m.top.seasonData.UserData) and isValid(m.top.seasonData.UserData.UnplayedItemCount)
|
||||||
if m.top.seasonData.UserData.UnplayedItemCount > 0
|
if m.top.seasonData.UserData.UnplayedItemCount > 0
|
||||||
m.unplayedCount.visible = true
|
m.unplayedCount.visible = true
|
||||||
m.unplayedEpisodeCount.text = m.top.seasonData.UserData.UnplayedItemCount
|
m.unplayedEpisodeCount.text = m.top.seasonData.UserData.UnplayedItemCount
|
||||||
|
@ -57,7 +57,6 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||||
return true
|
return true
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
|
||||||
if key = "OK" or key = "play"
|
if key = "OK" or key = "play"
|
||||||
if m.Random.hasFocus()
|
if m.Random.hasFocus()
|
||||||
randomEpisode = Rnd(m.rows.getChild(0).objects.items.count()) - 1
|
randomEpisode = Rnd(m.rows.getChild(0).objects.items.count()) - 1
|
||||||
|
@ -81,7 +80,6 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||||
end if
|
end if
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
|
||||||
focusedChild = m.top.focusedChild.focusedChild
|
focusedChild = m.top.focusedChild.focusedChild
|
||||||
if focusedChild.content = invalid then return handled
|
if focusedChild.content = invalid then return handled
|
||||||
|
|
||||||
|
@ -94,7 +92,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||||
if press and key = "play" or proceed = true
|
if press and key = "play" or proceed = true
|
||||||
m.top.lastFocus = focusedChild
|
m.top.lastFocus = focusedChild
|
||||||
itemToPlay = focusedChild.content.getChild(focusedChild.rowItemFocused[0]).getChild(0)
|
itemToPlay = focusedChild.content.getChild(focusedChild.rowItemFocused[0]).getChild(0)
|
||||||
if itemToPlay <> invalid and itemToPlay.id <> ""
|
if isValid(itemToPlay) and isValid(itemToPlay.id) and itemToPlay.id <> ""
|
||||||
itemToPlay.type = "Episode"
|
itemToPlay.type = "Episode"
|
||||||
m.top.quickPlayNode = itemToPlay
|
m.top.quickPlayNode = itemToPlay
|
||||||
end if
|
end if
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<component name="TVEpisodes" extends="JFGroup">
|
<component name="TVEpisodes" extends="JFGroup">
|
||||||
<children>
|
<children>
|
||||||
<Poster id="seasonPoster" width="300" height="450" translation="[95,175]">
|
<Poster id="seasonPoster" width="300" height="450" translation="[95,175]">
|
||||||
|
@ -20,4 +20,5 @@
|
||||||
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
|
<script type="text/brightscript" uri="pkg:/source/api/Image.brs" />
|
||||||
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
||||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||||
</component>
|
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||||
|
</component>
|
|
@ -18,7 +18,7 @@ end sub
|
||||||
sub itemContentChanged()
|
sub itemContentChanged()
|
||||||
item = m.top.itemContent
|
item = m.top.itemContent
|
||||||
itemData = item.json
|
itemData = item.json
|
||||||
if itemData.indexNumber <> invalid
|
if isValid(itemData.indexNumber)
|
||||||
indexNumber = itemData.indexNumber.toStr() + ". "
|
indexNumber = itemData.indexNumber.toStr() + ". "
|
||||||
else
|
else
|
||||||
indexNumber = ""
|
indexNumber = ""
|
||||||
|
@ -26,7 +26,7 @@ sub itemContentChanged()
|
||||||
m.title.text = indexNumber + item.title
|
m.title.text = indexNumber + item.title
|
||||||
m.overview.text = item.overview
|
m.overview.text = item.overview
|
||||||
|
|
||||||
if itemData.PremiereDate <> invalid
|
if isValid(itemData.PremiereDate)
|
||||||
airDate = CreateObject("roDateTime")
|
airDate = CreateObject("roDateTime")
|
||||||
airDate.FromISO8601String(itemData.PremiereDate)
|
airDate.FromISO8601String(itemData.PremiereDate)
|
||||||
m.top.findNode("aired").text = tr("Aired") + ": " + airDate.AsDateString("short-month-no-weekday")
|
m.top.findNode("aired").text = tr("Aired") + ": " + airDate.AsDateString("short-month-no-weekday")
|
||||||
|
@ -70,12 +70,12 @@ sub itemContentChanged()
|
||||||
end if
|
end if
|
||||||
|
|
||||||
' Add checkmark in corner (if applicable)
|
' Add checkmark in corner (if applicable)
|
||||||
if isValid(itemData?.UserData?.Played) and itemData.UserData.Played = true
|
if isValid(itemData.UserData) and isValid(itemData.UserData.Played) and itemData.UserData.Played = true
|
||||||
m.playedIndicator.visible = true
|
m.playedIndicator.visible = true
|
||||||
end if
|
end if
|
||||||
|
|
||||||
' Add progress bar on bottom (if applicable)
|
' Add progress bar on bottom (if applicable)
|
||||||
if isValid(itemData?.UserData?.PlayedPercentage) and itemData?.UserData?.PlayedPercentage > 0
|
if isValid(itemData.UserData) and isValid(itemData.UserData.PlayedPercentage) and itemData.UserData.PlayedPercentage > 0
|
||||||
m.progressBackground.width = m.poster.width
|
m.progressBackground.width = m.poster.width
|
||||||
m.progressBackground.visible = true
|
m.progressBackground.visible = true
|
||||||
progressWidthInPixels = int(m.progressBackground.width * itemData.UserData.PlayedPercentage / 100)
|
progressWidthInPixels = int(m.progressBackground.width * itemData.UserData.PlayedPercentage / 100)
|
||||||
|
@ -86,7 +86,7 @@ sub itemContentChanged()
|
||||||
videoIdx = invalid
|
videoIdx = invalid
|
||||||
audioIdx = invalid
|
audioIdx = invalid
|
||||||
|
|
||||||
if itemData.MediaStreams <> invalid
|
if isValid(itemData.MediaStreams)
|
||||||
for i = 0 to itemData.MediaStreams.Count() - 1
|
for i = 0 to itemData.MediaStreams.Count() - 1
|
||||||
if itemData.MediaStreams[i].Type = "Video" and videoIdx = invalid
|
if itemData.MediaStreams[i].Type = "Video" and videoIdx = invalid
|
||||||
videoIdx = i
|
videoIdx = i
|
||||||
|
@ -99,12 +99,12 @@ sub itemContentChanged()
|
||||||
end if
|
end if
|
||||||
m.top.findNode("audio_codec").text = tr("Audio") + ": " + itemData.mediaStreams[audioIdx].DisplayTitle
|
m.top.findNode("audio_codec").text = tr("Audio") + ": " + itemData.mediaStreams[audioIdx].DisplayTitle
|
||||||
end if
|
end if
|
||||||
if videoIdx <> invalid and audioIdx <> invalid then exit for
|
if isValid(videoIdx) and isValid(audioIdx) then exit for
|
||||||
end for
|
end for
|
||||||
end if
|
end if
|
||||||
|
|
||||||
m.top.findNode("video_codec").visible = videoIdx <> invalid
|
m.top.findNode("video_codec").visible = isValid(videoIdx)
|
||||||
if audioIdx <> invalid
|
if isValid(audioIdx)
|
||||||
m.top.findNode("audio_codec").visible = true
|
m.top.findNode("audio_codec").visible = true
|
||||||
DisplayAudioAvailable(itemData.mediaStreams)
|
DisplayAudioAvailable(itemData.mediaStreams)
|
||||||
else
|
else
|
||||||
|
@ -113,7 +113,6 @@ sub itemContentChanged()
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
sub DisplayAudioAvailable(streams)
|
sub DisplayAudioAvailable(streams)
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
for i = 0 to streams.Count() - 1
|
for i = 0 to streams.Count() - 1
|
||||||
if streams[i].Type = "Audio"
|
if streams[i].Type = "Audio"
|
||||||
|
@ -124,7 +123,6 @@ sub DisplayAudioAvailable(streams)
|
||||||
if count > 1
|
if count > 1
|
||||||
m.top.findnode("audio_codec_count").text = "+" + stri(count - 1).trim()
|
m.top.findnode("audio_codec_count").text = "+" + stri(count - 1).trim()
|
||||||
end if
|
end if
|
||||||
|
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
function getRuntime() as integer
|
function getRuntime() as integer
|
||||||
|
|
|
@ -17,7 +17,7 @@ sub itemContentChanged()
|
||||||
itemData = item.json
|
itemData = item.json
|
||||||
|
|
||||||
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
||||||
if itemData?.UserData?.UnplayedItemCount <> invalid
|
if isValid(itemData.UserData) and isValid(itemData.UserData.UnplayedItemCount)
|
||||||
if itemData.UserData.UnplayedItemCount > 0
|
if itemData.UserData.UnplayedItemCount > 0
|
||||||
m.unplayedCount.visible = true
|
m.unplayedCount.visible = true
|
||||||
m.unplayedEpisodeCount.text = itemData.UserData.UnplayedItemCount
|
m.unplayedEpisodeCount.text = itemData.UserData.UnplayedItemCount
|
||||||
|
@ -31,21 +31,21 @@ sub itemContentChanged()
|
||||||
m.top.overhangTitle = itemData.name
|
m.top.overhangTitle = itemData.name
|
||||||
|
|
||||||
'Check production year, if invalid remove label
|
'Check production year, if invalid remove label
|
||||||
if itemData.productionYear <> invalid
|
if isValid(itemData.productionYear)
|
||||||
setFieldText("releaseYear", itemData.productionYear)
|
setFieldText("releaseYear", itemData.productionYear)
|
||||||
else
|
else
|
||||||
m.top.findNode("main_group").removeChild(m.top.findNode("releaseYear"))
|
m.top.findNode("main_group").removeChild(m.top.findNode("releaseYear"))
|
||||||
end if
|
end if
|
||||||
|
|
||||||
'Check officialRating, if invalid remove label
|
'Check officialRating, if invalid remove label
|
||||||
if itemData.officialRating <> invalid
|
if isValid(itemData.officialRating)
|
||||||
setFieldText("officialRating", itemData.officialRating)
|
setFieldText("officialRating", itemData.officialRating)
|
||||||
else
|
else
|
||||||
m.top.findNode("main_group").removeChild(m.top.findNode("officialRating"))
|
m.top.findNode("main_group").removeChild(m.top.findNode("officialRating"))
|
||||||
end if
|
end if
|
||||||
|
|
||||||
'Check communityRating, if invalid remove label
|
'Check communityRating, if invalid remove label
|
||||||
if itemData.communityRating <> invalid
|
if isValid(itemData.communityRating)
|
||||||
m.top.findNode("star").visible = true
|
m.top.findNode("star").visible = true
|
||||||
setFieldText("communityRating", int(itemData.communityRating * 10) / 10)
|
setFieldText("communityRating", int(itemData.communityRating * 10) / 10)
|
||||||
else
|
else
|
||||||
|
@ -134,7 +134,7 @@ function getHistory() as string
|
||||||
|
|
||||||
airdays = itemData.airdays
|
airdays = itemData.airdays
|
||||||
airtime = itemData.airtime
|
airtime = itemData.airtime
|
||||||
if airtime <> invalid and airdays.count() = 1
|
if isValid(airtime) and airdays.count() = 1
|
||||||
airwords = airdays[0] + " at " + airtime
|
airwords = airdays[0] + " at " + airtime
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
@ -148,10 +148,10 @@ function getHistory() as string
|
||||||
end if
|
end if
|
||||||
|
|
||||||
words = verb
|
words = verb
|
||||||
if airwords <> invalid
|
if isValid(airwords)
|
||||||
words = words + " " + airwords
|
words = words + " " + airwords
|
||||||
end if
|
end if
|
||||||
if studio <> invalid
|
if isValid(studio)
|
||||||
words = words + " on " + studio
|
words = words + " on " + studio
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
|
17
dictionary.txt
Normal file
17
dictionary.txt
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
Jellyfin
|
||||||
|
VSCode
|
||||||
|
BrightScript
|
||||||
|
sideload
|
||||||
|
Sideload
|
||||||
|
DEVGUIDE
|
||||||
|
ing
|
||||||
|
hardcode
|
||||||
|
Hardcoding
|
||||||
|
pre-release
|
||||||
|
breakpoint
|
||||||
|
repo
|
||||||
|
Repo
|
||||||
|
dev
|
||||||
|
Dev
|
||||||
|
assignees
|
||||||
|
HTTPS
|
|
@ -6,7 +6,7 @@
|
||||||
<name>default</name>
|
<name>default</name>
|
||||||
<message>
|
<message>
|
||||||
<source>192.168.1.100:8096 or https://example.com/jellyfin</source>
|
<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>
|
||||||
<message>
|
<message>
|
||||||
<source>Cancel</source>
|
<source>Cancel</source>
|
||||||
|
@ -928,11 +928,6 @@
|
||||||
<translation>MPEG-4 Support</translation>
|
<translation>MPEG-4 Support</translation>
|
||||||
<extracomment>Settings Menu - Title for option</extracomment>
|
<extracomment>Settings Menu - Title for option</extracomment>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<source>Parental Ratings</source>
|
<source>Parental Ratings</source>
|
||||||
<translation>Parental Ratings</translation>
|
<translation>Parental Ratings</translation>
|
||||||
|
@ -975,14 +970,6 @@
|
||||||
<translation>Default view for Movie Libraries.</translation>
|
<translation>Default view for Movie Libraries.</translation>
|
||||||
<extracomment>Settings Menu - Description for option</extracomment>
|
<extracomment>Settings Menu - Description for option</extracomment>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Movies (Presentation)</source>
|
|
||||||
<translation>Movies (Presentation)</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Movies (Grid)</source>
|
|
||||||
<translation>Movies (Grid)</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Item Titles</source>
|
<source>Item Titles</source>
|
||||||
<translation>Item Titles</translation>
|
<translation>Item Titles</translation>
|
||||||
|
@ -1138,4 +1125,4 @@
|
||||||
<extracomment>Settings Menu - Description for option</extracomment>
|
<extracomment>Settings Menu - Description for option</extracomment>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
6956
package-lock.json
generated
6956
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
@ -7,17 +7,18 @@
|
||||||
"@rokucommunity/bslint": "0.8.1",
|
"@rokucommunity/bslint": "0.8.1",
|
||||||
"brighterscript": "0.61.3",
|
"brighterscript": "0.61.3",
|
||||||
"ropm": "0.10.11",
|
"ropm": "0.10.11",
|
||||||
"jsonlint-cli": "1.0.1",
|
"jshint": "^2.13.6",
|
||||||
"markdownlint-cli": "0.33.0",
|
"markdownlint-cli2": "0.6.0",
|
||||||
"markdown-spellcheck": "1.3.1"
|
"spellchecker-cli": "6.1.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "npx ropm copy",
|
"postinstall": "npx ropm copy",
|
||||||
"validate": "npx bsc --copy-to-staging=false --create-package=false",
|
"validate": "npx bsc --copy-to-staging=false --create-package=false",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"lint": "bslint",
|
"lint": "bslint",
|
||||||
"lint-json": "jsonlint-cli **/*.json",
|
"lint-json": "jshint --extra-ext .json --verbose --exclude node_modules ./",
|
||||||
"lint-markdown": "markdownlint **/*.md --ignore node_modules && mdspell --en-us -r **/*.md !**/node_modules/**/*.md",
|
"lint-markdown": "markdownlint-cli2 \"**/*.md\" \"#node_modules\"",
|
||||||
|
"lint-spelling": "spellchecker -d dictionary.txt --files \"**/*.md\" \"**/.*/**/*.md\" \"!node_modules/**/*.md\"",
|
||||||
"check-formatting": "npx bsfmt --check",
|
"check-formatting": "npx bsfmt --check",
|
||||||
"format": "npx bsfmt --write"
|
"format": "npx bsfmt --write"
|
||||||
},
|
},
|
||||||
|
|
|
@ -570,6 +570,7 @@ sub Main (args as dynamic) as void
|
||||||
sceneManager.callFunc("resetTime")
|
sceneManager.callFunc("resetTime")
|
||||||
group = sceneManager.callFunc("getActiveScene")
|
group = sceneManager.callFunc("getActiveScene")
|
||||||
if isValid(group) and isValid(group.subtype())
|
if isValid(group) and isValid(group.subtype())
|
||||||
|
' refresh the current view
|
||||||
if group.subtype() = "Home"
|
if group.subtype() = "Home"
|
||||||
currentTime = CreateObject("roDateTime").AsSeconds()
|
currentTime = CreateObject("roDateTime").AsSeconds()
|
||||||
group.timeLastRefresh = currentTime
|
group.timeLastRefresh = currentTime
|
||||||
|
|
|
@ -31,10 +31,10 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
|
||||||
|
|
||||||
' Special handling for "Programs" or "Vidoes" launched from "On Now" or elsewhere on the home screen...
|
' Special handling for "Programs" or "Vidoes" launched from "On Now" or elsewhere on the home screen...
|
||||||
' basically anything that is a Live Channel.
|
' basically anything that is a Live Channel.
|
||||||
if isValid(meta?.json?.ChannelId)
|
if isValid(meta.json) and isValid(meta.json.ChannelId)
|
||||||
if meta.json.EpisodeTitle <> invalid
|
if isValid(meta.json.EpisodeTitle)
|
||||||
meta.title = meta.json.EpisodeTitle
|
meta.title = meta.json.EpisodeTitle
|
||||||
else if meta.json.Name <> invalid
|
else if isValid(meta.json.Name)
|
||||||
meta.title = meta.json.Name
|
meta.title = meta.json.Name
|
||||||
end if
|
end if
|
||||||
meta.showID = meta.json.id
|
meta.showID = meta.json.id
|
||||||
|
@ -47,7 +47,7 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
|
||||||
end if
|
end if
|
||||||
|
|
||||||
if m.videotype = "Episode" or m.videotype = "Series"
|
if m.videotype = "Episode" or m.videotype = "Series"
|
||||||
if isValid(meta.json.RunTimeTicks)
|
if isValid(meta.json) and isValid(meta.json.RunTimeTicks)
|
||||||
video.runTime = (meta.json.RunTimeTicks / 10000000.0)
|
video.runTime = (meta.json.RunTimeTicks / 10000000.0)
|
||||||
end if
|
end if
|
||||||
video.content.contenttype = "episode"
|
video.content.contenttype = "episode"
|
||||||
|
@ -243,11 +243,9 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
|
||||||
video.content.SubtitleTracks = subtitles["text"]
|
video.content.SubtitleTracks = subtitles["text"]
|
||||||
|
|
||||||
' 'TODO: allow user selection of subtitle track before playback initiated, for now set to no subtitles
|
' 'TODO: allow user selection of subtitle track before playback initiated, for now set to no subtitles
|
||||||
|
|
||||||
video.directPlaySupported = m.playbackInfo.MediaSources[0].SupportsDirectPlay
|
video.directPlaySupported = m.playbackInfo.MediaSources[0].SupportsDirectPlay
|
||||||
fully_external = false
|
fully_external = false
|
||||||
|
|
||||||
|
|
||||||
' For h264/hevc video, Roku spec states that it supports specfic encoding levels
|
' For h264/hevc video, Roku spec states that it supports specfic encoding levels
|
||||||
' The device can decode content with a Higher Encoding level but may play it back with certain
|
' The device can decode content with a Higher Encoding level but may play it back with certain
|
||||||
' artifacts. If the user preference is set, and the only reason the server says we need to
|
' artifacts. If the user preference is set, and the only reason the server says we need to
|
||||||
|
@ -256,7 +254,7 @@ sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -
|
||||||
if m.playbackInfo.MediaSources[0].MediaStreams.Count() > 0 and meta.live = false
|
if m.playbackInfo.MediaSources[0].MediaStreams.Count() > 0 and meta.live = false
|
||||||
tryDirectPlay = get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
|
tryDirectPlay = get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
|
||||||
tryDirectPlay = tryDirectPlay or (get_user_setting("playback.tryDirect.hevcProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc")
|
tryDirectPlay = tryDirectPlay or (get_user_setting("playback.tryDirect.hevcProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc")
|
||||||
if tryDirectPlay and m.playbackInfo.MediaSources[0].TranscodingUrl <> invalid and forceTranscoding = false
|
if tryDirectPlay and isValid(m.playbackInfo.MediaSources[0].TranscodingUrl) and forceTranscoding = false
|
||||||
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
|
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
|
||||||
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
|
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
|
||||||
video.directPlaySupported = true
|
video.directPlaySupported = true
|
||||||
|
@ -324,7 +322,6 @@ end sub
|
||||||
function PlayIntroVideo(video_id, audio_stream_idx) as boolean
|
function PlayIntroVideo(video_id, audio_stream_idx) as boolean
|
||||||
' Intro videos only play if user has cinema mode setting enabled
|
' Intro videos only play if user has cinema mode setting enabled
|
||||||
if get_user_setting("playback.cinemamode") = "true"
|
if get_user_setting("playback.cinemamode") = "true"
|
||||||
|
|
||||||
' Check if server has intro videos setup and available
|
' Check if server has intro videos setup and available
|
||||||
introVideos = GetIntroVideos(video_id)
|
introVideos = GetIntroVideos(video_id)
|
||||||
|
|
||||||
|
@ -362,7 +359,6 @@ end function
|
||||||
' Extract array of Transcode Reasons from the content URL
|
' Extract array of Transcode Reasons from the content URL
|
||||||
' @returns Array of Strings
|
' @returns Array of Strings
|
||||||
function getTranscodeReasons(url as string) as object
|
function getTranscodeReasons(url as string) as object
|
||||||
|
|
||||||
regex = CreateObject("roRegex", "&TranscodeReasons=([^&]*)", "")
|
regex = CreateObject("roRegex", "&TranscodeReasons=([^&]*)", "")
|
||||||
match = regex.Match(url)
|
match = regex.Match(url)
|
||||||
|
|
||||||
|
@ -384,19 +380,18 @@ end function
|
||||||
|
|
||||||
function directPlaySupported(meta as object) as boolean
|
function directPlaySupported(meta as object) as boolean
|
||||||
devinfo = CreateObject("roDeviceInfo")
|
devinfo = CreateObject("roDeviceInfo")
|
||||||
if meta.json.MediaSources[0] <> invalid and meta.json.MediaSources[0].SupportsDirectPlay = false
|
if isValid(meta.json.MediaSources[0]) and meta.json.MediaSources[0].SupportsDirectPlay = false
|
||||||
return false
|
return false
|
||||||
end if
|
end if
|
||||||
|
if not isValid(meta.json.MediaSources[0])
|
||||||
if meta.json.MediaStreams[0] = invalid
|
|
||||||
return false
|
return false
|
||||||
end if
|
end if
|
||||||
|
|
||||||
streamInfo = { Codec: meta.json.MediaStreams[0].codec }
|
streamInfo = { Codec: meta.json.MediaStreams[0].codec }
|
||||||
if meta.json.MediaStreams[0].Profile <> invalid and meta.json.MediaStreams[0].Profile.len() > 0
|
if isValid(meta.json.MediaStreams[0].Profile) and meta.json.MediaStreams[0].Profile.len() > 0
|
||||||
streamInfo.Profile = LCase(meta.json.MediaStreams[0].Profile)
|
streamInfo.Profile = LCase(meta.json.MediaStreams[0].Profile)
|
||||||
end if
|
end if
|
||||||
if meta.json.MediaSources[0].container <> invalid and meta.json.MediaSources[0].container.len() > 0
|
if isValid(meta.json.MediaSources[0].container) and meta.json.MediaSources[0].container.len() > 0
|
||||||
'CanDecodeVideo() requires the .container to be format: “mp4”, “hls”, “mkv”, “ism”, “dash”, “ts” if its to direct stream
|
'CanDecodeVideo() requires the .container to be format: “mp4”, “hls”, “mkv”, “ism”, “dash”, “ts” if its to direct stream
|
||||||
if meta.json.MediaSources[0].container = "mov"
|
if meta.json.MediaSources[0].container = "mov"
|
||||||
streamInfo.Container = "mp4"
|
streamInfo.Container = "mp4"
|
||||||
|
@ -406,8 +401,7 @@ function directPlaySupported(meta as object) as boolean
|
||||||
end if
|
end if
|
||||||
|
|
||||||
decodeResult = devinfo.CanDecodeVideo(streamInfo)
|
decodeResult = devinfo.CanDecodeVideo(streamInfo)
|
||||||
return decodeResult <> invalid and decodeResult.result
|
return isValid(decodeResult) and decodeResult.result
|
||||||
|
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function getContainerType(meta as object) as string
|
function getContainerType(meta as object) as string
|
||||||
|
@ -455,12 +449,12 @@ sub autoPlayNextEpisode(videoID as string, showID as string)
|
||||||
resp = APIRequest(url, urlParams)
|
resp = APIRequest(url, urlParams)
|
||||||
data = getJson(resp)
|
data = getJson(resp)
|
||||||
|
|
||||||
if data <> invalid and data.Items.Count() = 2
|
if isValid(data) and data.Items.Count() = 2
|
||||||
' setup new video node
|
' setup new video node
|
||||||
nextVideo = CreateVideoPlayerGroup(data.Items[1].Id, invalid, 1, false, false)
|
nextVideo = CreateVideoPlayerGroup(data.Items[1].Id, invalid, 1, false, false)
|
||||||
' remove last videoplayer scene
|
' remove last videoplayer scene
|
||||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||||
if nextVideo <> invalid
|
if isValid(nextVideo)
|
||||||
m.global.sceneManager.callFunc("pushScene", nextVideo)
|
m.global.sceneManager.callFunc("pushScene", nextVideo)
|
||||||
else
|
else
|
||||||
m.global.sceneManager.callFunc("popScene")
|
m.global.sceneManager.callFunc("popScene")
|
||||||
|
@ -478,7 +472,7 @@ end sub
|
||||||
' In the future, with a custom playback info view, we can return an associated array.
|
' In the future, with a custom playback info view, we can return an associated array.
|
||||||
function GetPlaybackInfo()
|
function GetPlaybackInfo()
|
||||||
sessions = api_API().sessions.get()
|
sessions = api_API().sessions.get()
|
||||||
if sessions <> invalid and sessions.Count() > 0
|
if isValid(sessions) and sessions.Count() > 0
|
||||||
return GetTranscodingStats(sessions[0])
|
return GetTranscodingStats(sessions[0])
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,6 @@ function searchMedia(query as string)
|
||||||
' This appears to be done differently on the web now
|
' This appears to be done differently on the web now
|
||||||
' For each potential type, a separate query is done:
|
' For each potential type, a separate query is done:
|
||||||
' varying item types, and artists, and people
|
' varying item types, and artists, and people
|
||||||
|
|
||||||
if query <> ""
|
if query <> ""
|
||||||
resp = APIRequest(Substitute("Search/Hints", get_setting("active_user")), {
|
resp = APIRequest(Substitute("Search/Hints", get_setting("active_user")), {
|
||||||
"searchTerm": query,
|
"searchTerm": query,
|
||||||
|
@ -55,7 +54,6 @@ function searchMedia(query as string)
|
||||||
"limit": 100
|
"limit": 100
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
data = getJson(resp)
|
data = getJson(resp)
|
||||||
results = []
|
results = []
|
||||||
for each item in data.SearchHints
|
for each item in data.SearchHints
|
||||||
|
@ -79,7 +77,7 @@ function ItemMetaData(id as string)
|
||||||
|
|
||||||
imgParams = {}
|
imgParams = {}
|
||||||
if data.type <> "Audio"
|
if data.type <> "Audio"
|
||||||
if data?.UserData?.PlayedPercentage <> invalid
|
if data.UserData <> invalid and data.UserData.PlayedPercentage <> invalid
|
||||||
param = { "PercentPlayed": data.UserData.PlayedPercentage }
|
param = { "PercentPlayed": data.UserData.PlayedPercentage }
|
||||||
imgParams.Append(param)
|
imgParams.Append(param)
|
||||||
end if
|
end if
|
||||||
|
|
|
@ -21,7 +21,7 @@ end function
|
||||||
' returns the server-side track index for the appriate subtitle
|
' returns the server-side track index for the appriate subtitle
|
||||||
function defaultSubtitleTrackFromVid(video_id) as integer
|
function defaultSubtitleTrackFromVid(video_id) as integer
|
||||||
meta = ItemMetaData(video_id)
|
meta = ItemMetaData(video_id)
|
||||||
if meta?.json?.mediaSources <> invalid
|
if isValid(meta) and isValid(meta.json) and isValid(meta.json.mediaSources)
|
||||||
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
|
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
|
||||||
default_text_subs = defaultSubtitleTrack(subtitles["all"], true) ' Find correct subtitle track (forced text)
|
default_text_subs = defaultSubtitleTrack(subtitles["all"], true) ' Find correct subtitle track (forced text)
|
||||||
if default_text_subs <> -1
|
if default_text_subs <> -1
|
||||||
|
@ -130,7 +130,7 @@ function selectSubtitleTrackDialog(tracks, currentTrack = -1)
|
||||||
default = ""
|
default = ""
|
||||||
if item.IsForced then forced = " [Forced]"
|
if item.IsForced then forced = " [Forced]"
|
||||||
if item.IsDefault then default = " - Default"
|
if item.IsDefault then default = " - Default"
|
||||||
if item.Track.Language <> invalid
|
if isValid(item.Track.Language)
|
||||||
language = iso6392.lookup(item.Track.Language)
|
language = iso6392.lookup(item.Track.Language)
|
||||||
if language = invalid then language = item.Track.Language
|
if language = invalid then language = item.Track.Language
|
||||||
else
|
else
|
||||||
|
@ -157,7 +157,7 @@ sub changeSubtitleDuringPlayback(newid)
|
||||||
currentSubtitles = video.Subtitles[video.SelectedSubtitle]
|
currentSubtitles = video.Subtitles[video.SelectedSubtitle]
|
||||||
newSubtitles = video.Subtitles[newid]
|
newSubtitles = video.Subtitles[newid]
|
||||||
|
|
||||||
if newSubtitles.IsEncoded or (currentSubtitles <> invalid and currentSubtitles.IsEncoded)
|
if newSubtitles.IsEncoded or (isValid(currentSubtitles) and currentSubtitles.IsEncoded)
|
||||||
' With encoded subtitles we need to stop/start playback
|
' With encoded subtitles we need to stop/start playback
|
||||||
video.control = "stop"
|
video.control = "stop"
|
||||||
AddVideoContent(video, video.mediaSourceId, video.audioIndex, newSubtitles.Index, video.position * 10000000)
|
AddVideoContent(video, video.mediaSourceId, video.audioIndex, newSubtitles.Index, video.position * 10000000)
|
||||||
|
@ -195,7 +195,7 @@ function sortSubtitles(id as string, MediaStreams)
|
||||||
if stream.type = "Subtitle"
|
if stream.type = "Subtitle"
|
||||||
|
|
||||||
url = ""
|
url = ""
|
||||||
if stream.DeliveryUrl <> invalid
|
if isValid(stream.DeliveryUrl)
|
||||||
url = buildURL(stream.DeliveryUrl)
|
url = buildURL(stream.DeliveryUrl)
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
|
|
@ -91,19 +91,23 @@ function get_dialog_result(dialog, port)
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function lastFocusedChild(obj as object) as object
|
function lastFocusedChild(obj as object) as object
|
||||||
if LCase(obj.focusedChild.focusedChild.subType()) = "tvepisodes"
|
if isValid(obj)
|
||||||
if isValid(obj?.focusedChild?.focusedChild?.lastFocus)
|
if isValid(obj.focusedChild) and isValid(obj.focusedChild.focusedChild) and LCase(obj.focusedChild.focusedChild.subType()) = "tvepisodes"
|
||||||
return obj.focusedChild.focusedChild.lastFocus
|
if isValid(obj.focusedChild.focusedChild.lastFocus)
|
||||||
|
return obj.focusedChild.focusedChild.lastFocus
|
||||||
|
end if
|
||||||
end if
|
end if
|
||||||
end if
|
|
||||||
|
|
||||||
child = obj
|
child = obj
|
||||||
for i = 0 to obj.getChildCount()
|
for i = 0 to obj.getChildCount()
|
||||||
if obj.focusedChild <> invalid
|
if isValid(obj.focusedChild)
|
||||||
child = child.focusedChild
|
child = child.focusedChild
|
||||||
end if
|
end if
|
||||||
end for
|
end for
|
||||||
return child
|
return child
|
||||||
|
else
|
||||||
|
return invalid
|
||||||
|
end if
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function show_dialog(message as string, options = [], defaultSelection = 0) as integer
|
function show_dialog(message as string, options = [], defaultSelection = 0) as integer
|
||||||
|
@ -348,7 +352,7 @@ sub stopLoadingSpinner()
|
||||||
if isValid(m.spinner)
|
if isValid(m.spinner)
|
||||||
m.spinner.visible = false
|
m.spinner.visible = false
|
||||||
end if
|
end if
|
||||||
if isValid(m.scene?.dialog)
|
if isValid(m.scene) and isValid(m.scene.dialog)
|
||||||
m.scene.dialog.close = true
|
m.scene.dialog.close = true
|
||||||
end if
|
end if
|
||||||
end sub
|
end sub
|
||||||
|
|
Loading…
Reference in New Issue
Block a user