Compare commits

...

336 Commits

Author SHA1 Message Date
6158ca2be4 add a shell.nix file 2024-03-21 19:59:14 -04:00
fd46047f8c Merge remote-tracking branch 'upstream/master' into feature/mjh/new 2024-03-21 19:59:01 -04:00
VitoFe
0defe7c941 Translated using Weblate (Italian)
Currently translated at 24.8% (69 of 278 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/it/
2024-03-21 08:38:02 -04:00
VB2007
c45e4169c8 Translated using Weblate (Hungarian)
Currently translated at 43.8% (122 of 278 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/hu/
2024-03-20 17:38:02 -04:00
Kévin LEBLOND
8f83e8f3b0 Translated using Weblate (French)
Currently translated at 50.7% (141 of 278 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2024-03-19 19:38:02 -04:00
Acrotos
e61353bc96 Translated using Weblate (Romanian)
Currently translated at 15.1% (42 of 278 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/ro/
2024-03-19 17:38:01 -04:00
Ewillian9
de27e233a4 Translated using Weblate (French)
Currently translated at 40.6% (113 of 278 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2024-03-18 19:38:02 -04:00
Ewillian9
0353eef777 Translated using Weblate (French)
Currently translated at 16.1% (45 of 278 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2024-03-18 16:38:02 -04:00
Ewillian9
4d625e45ab Translated using Weblate (French)
Currently translated at 100.0% (277 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2024-03-18 11:38:03 -04:00
jellyfin-bot
a4f34613f5 Update API docs 2024-03-17 15:56:25 +00:00
1hitsong
5f18ac9d58
Merge pull request #1702 from chrisforte/preferred-audio-codec
Add a new option to select preferred audio codec for transcodes
2024-03-17 11:55:53 -04:00
1hitsong
b168887bac
Merge pull request #1746 from jellyfin/renovate/actions-setup-java-digest
Update actions/setup-java digest to 99b8673
2024-03-15 08:02:36 -04:00
jellyfin-bot
f08b94302c Update API docs 2024-03-15 12:00:59 +00:00
1hitsong
79f5c15024
Merge pull request #1672 from 1hitsong/OSDInfo
Add logo and season/episode numbers to OSD
2024-03-15 08:00:24 -04:00
renovate[bot]
064317e7a5
Update actions/setup-java digest to 99b8673 2024-03-14 17:05:39 +00:00
841b0b08d4 Merge from remote 2024-03-14 07:49:05 -04:00
82126e7523 Guard for missing ProductionYear/Episode Number 2024-03-14 07:46:17 -04:00
4e3b28ea92 Add year 2024-03-14 07:32:07 -04:00
lee
acd5142b9c add movie release year to video player header 2024-03-14 07:32:07 -04:00
lee
11edc348cf added episode season and number to LoadVideoContentTask 2024-03-14 07:32:00 -04:00
f929f888ce add episode and season number to video player header 2024-03-14 07:29:19 -04:00
1hitsong
3499e32dd0
Merge pull request #1745 from jellyfin/renovate/brighterscript-0.x
Update dependency brighterscript to v0.65.26
2024-03-13 18:14:01 -04:00
1hitsong
c36198b046
Merge pull request #1744 from jellyfin/renovate/actions-setup-java-digest
Update actions/setup-java digest to 5896cec
2024-03-13 18:13:28 -04:00
renovate[bot]
60bd16cff0
Update dependency brighterscript to v0.65.26 2024-03-13 18:30:07 +00:00
renovate[bot]
a7b170088d
Update actions/setup-java digest to 5896cec 2024-03-13 15:59:30 +00:00
jellyfin-bot
d768ea1c32 Update API docs 2024-03-12 12:04:55 +00:00
1hitsong
98d6c551aa
Merge pull request #1742 from 1hitsong/ellipsisTextUnify
Update ellipsis text to use translation. Remove leading space
2024-03-12 08:04:21 -04:00
1hitsong
e939cd9772 Update ellipsis text to use translation. Remove leading space 2024-03-11 18:52:16 -04:00
jellyfin-bot
6573aa426d Update API docs 2024-03-11 00:25:10 +00:00
1hitsong
36eabb43cc
Merge pull request #1741 from jellyfin/renovate/rooibos-roku-5.x
Update dependency rooibos-roku to v5.12.0
2024-03-10 20:24:37 -04:00
1hitsong
97a2fee81c
Merge pull request #1605 from 1hitsong/moveSelectedSubtileToTop
Move selected subtitle to top of selection menu
2024-03-10 20:24:15 -04:00
jellyfin-bot
cdbe7655d6 Update API docs 2024-03-10 21:54:32 +00:00
1hitsong
5ff768bb07
Merge pull request #1674 from 1hitsong/fullDescriptionPopup
Create popup with full TV series description
2024-03-10 17:53:57 -04:00
renovate[bot]
a4042a1a63
Update dependency rooibos-roku to v5.12.0 2024-03-10 21:53:48 +00:00
jellyfin-bot
f90fe37e73 Update API docs 2024-03-10 21:53:41 +00:00
1hitsong
00dbb7cbb9
Merge pull request #1716 from 1hitsong/includeSpecialChars
Include special characters when filtering by #
2024-03-10 17:53:10 -04:00
Zan
67b5439530 Translated using Weblate (Hungarian)
Currently translated at 60.2% (167 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/hu/
2024-03-09 18:38:05 -05:00
1hitsong
cb9c44867c Keep None subtitle at top 2024-03-09 09:04:18 -05:00
1hitsong
357c3f1c0d Move selected subtitle to top of selection menu 2024-03-09 08:57:25 -05:00
1hitsong
84d5a49762 Use translated text for ellipsis 2024-03-09 08:46:52 -05:00
alison2033
a6fda12258 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (277 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/pt_BR/
2024-03-08 13:38:06 -05:00
1hitsong
ee51fd85d9
Merge pull request #1738 from jellyfin/renovate/brighterscript-formatter-1.x
Update dependency brighterscript-formatter to v1.7.0
2024-03-07 18:47:55 -05:00
1hitsong
5c3b76fc07
Merge pull request #1737 from jellyfin/renovate/ropm-0.x
Update dependency ropm to v0.10.23
2024-03-07 17:26:24 -05:00
1hitsong
fa87a3971a
Merge pull request #1736 from jellyfin/renovate/rokucommunity-bslint-0.x
Update dependency @rokucommunity/bslint to v0.8.19
2024-03-07 17:26:15 -05:00
renovate[bot]
0438e859e5
Update dependency brighterscript-formatter to v1.7.0 2024-03-07 22:25:10 +00:00
renovate[bot]
7f05f8eea6
Update dependency ropm to v0.10.23 2024-03-07 22:24:57 +00:00
renovate[bot]
d8bcdd8bf0
Update dependency @rokucommunity/bslint to v0.8.19 2024-03-07 22:24:47 +00:00
1hitsong
e20425997d
Merge pull request #1735 from jellyfin/renovate/brighterscript-0.x
Update dependency brighterscript to v0.65.25
2024-03-07 17:24:00 -05:00
renovate[bot]
a0ff9c3c2d
Update dependency brighterscript to v0.65.25 2024-03-07 20:02:31 +00:00
GuillomoLavallo
e22e6e9b54 Translated using Weblate (Spanish)
Currently translated at 47.6% (132 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/es/
2024-03-02 22:38:05 -05:00
1hitsong
b41ad87466
Merge pull request #1732 from jellyfin/renovate/roku-deploy-3.x
Update dependency roku-deploy to v3.12.0
2024-03-02 12:56:52 -05:00
1hitsong
a189b4e480
Merge pull request #1731 from jellyfin/renovate/brighterscript-formatter-1.x
Update dependency brighterscript-formatter to v1.6.41
2024-03-02 12:56:40 -05:00
renovate[bot]
2e961aaec7
Update dependency roku-deploy to v3.12.0 2024-03-02 17:54:28 +00:00
renovate[bot]
fde6ddec80
Update dependency brighterscript-formatter to v1.6.41 2024-03-02 17:54:16 +00:00
1hitsong
7a8d705ced
Merge pull request #1733 from jellyfin/renovate/ropm-0.x
Update dependency ropm to v0.10.22
2024-03-02 12:53:53 -05:00
1hitsong
22c3969eb4
Merge pull request #1730 from jellyfin/renovate/brighterscript-0.x
Update dependency brighterscript to v0.65.24
2024-03-02 12:53:28 -05:00
renovate[bot]
9b21617283
Update dependency brighterscript to v0.65.24 2024-03-02 17:52:32 +00:00
1hitsong
8600cb4df1
Merge pull request #1729 from jellyfin/renovate/rokucommunity-bslint-0.x
Update dependency @rokucommunity/bslint to v0.8.18
2024-03-02 12:52:10 -05:00
renovate[bot]
3cf821d374
Update dependency ropm to v0.10.22 2024-03-01 13:52:46 +00:00
alison2033
cdd74ee9e4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 43.3% (120 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/pt_BR/
2024-02-29 22:38:04 -05:00
renovate[bot]
2868802b1d
Update dependency @rokucommunity/bslint to v0.8.18 2024-02-29 23:32:06 +00:00
1hitsong
f0a9f13ca5
Merge pull request #1727 from jellyfin/renovate/actions-setup-java-digest
Update actions/setup-java digest to 9704b39
2024-02-28 18:42:28 -05:00
renovate[bot]
f7d99508fb
Update actions/setup-java digest to 9704b39 2024-02-28 07:42:58 +00:00
DJSweder
69467f3f8b Translated using Weblate (Czech)
Currently translated at 17.3% (48 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/cs/
2024-02-26 10:37:13 -05:00
1hitsong
885a639a58
Merge pull request #1725 from jellyfin/renovate/xt0rted-markdownlint-problem-matcher-3.x
Update xt0rted/markdownlint-problem-matcher action to v3
2024-02-26 07:03:52 -05:00
renovate[bot]
e6f843fe3d
Update xt0rted/markdownlint-problem-matcher action to v3 2024-02-25 00:30:23 +00:00
Chris Forte
ca30244290 move setting up 1 level 2024-02-23 14:40:04 -05:00
1hitsong
75f1e1e141 Reduce logo max height so tall logos don't go under buttons 2024-02-22 21:10:19 -05:00
1hitsong
1b863ba34c
Merge branch 'master' into OSDInfo 2024-02-22 20:45:42 -05:00
jellyfin-bot
aa9ad629d8 Update API docs 2024-02-23 01:34:08 +00:00
1hitsong
0c59c2a629
Merge pull request #1703 from 1hitsong/OSD-Item-Navigation
Create method to navigate to Previous/Next items in playlist
2024-02-22 20:33:21 -05:00
1hitsong
39abf06056 If current and next item are both audio, don't leave player 2024-02-22 19:26:35 -05:00
Giuseppe Stelluto
5749569423 Translated using Weblate (Italian)
Currently translated at 99.6% (276 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/it/
2024-02-22 15:37:11 -05:00
jellyfin-bot
480e9d9bb5 Update API docs 2024-02-20 13:55:51 +00:00
Charles Ewert
896efc645d
Merge pull request #1720 from cewert/merge-205-into-master 2024-02-20 08:55:13 -05:00
Charles Ewert
204c977f5c Merge 2.0.z branch 2024-02-19 21:58:11 -05:00
1hitsong
f5f4b436d6 Include special characters when filtering by #
Fixes #1296
2024-02-19 14:56:55 -05:00
Csaba Seprenyi
c0c6a63c5a Translated using Weblate (Hungarian)
Currently translated at 54.1% (150 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/hu/
2024-02-18 17:37:11 -05:00
Chris Forte
ed5d73de4b combine audio codec settings 2024-02-15 09:57:37 -05:00
Charles Ewert
ae4ddc6fd9
Merge pull request #1709 from cewert/use-bot-bump-version 2024-02-13 18:16:56 -05:00
Charles Ewert
67d47a1b05 Use JF bot to create version bump PR 2024-02-13 17:39:03 -05:00
Charles Ewert
91cddbd595
Merge pull request #1707 from cewert/fix-vbump 2024-02-13 17:15:12 -05:00
Charles Ewert
6a78b14949 Run build-dev.yml on push for all branches 2024-02-13 17:08:36 -05:00
Charles Ewert
d49715a8af
Merge pull request #1704 from cewert/create-version-bump-worflow 2024-02-13 16:53:54 -05:00
Charles Ewert
11a251454e Create manual workflow to auto create a PR to bump version 2024-02-13 11:34:48 -05:00
1hitsong
d9f714690e Add item navigation to OSD 2024-02-12 22:47:26 -05:00
Chris Forte
425512b00f add preferred audio codec option 2024-02-12 14:59:06 -05:00
greyluked
f4f5df2edb Translated using Weblate (Spanish)
Currently translated at 41.1% (114 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/es/
2024-02-12 13:30:40 -05:00
1hitsong
7e76f9f802
Merge pull request #1701 from jellyfin/renovate/spellchecker-cli-6.x
Update dependency spellchecker-cli to v6.2.0
2024-02-11 17:41:28 -05:00
renovate[bot]
e8c690b5a2
Update dependency spellchecker-cli to v6.2.0 2024-02-11 22:29:51 +00:00
pendalan
e488407ba5 Translated using Weblate (German)
Currently translated at 100.0% (277 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2024-02-11 05:30:40 -05:00
pendalan
558d72c1e0 Translated using Weblate (German)
Currently translated at 98.1% (272 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2024-02-10 19:30:43 -05:00
1hitsong
8d7ccdf0fc
Merge pull request #1700 from jellyfin/renovate/brighterscript-0.x
Update dependency brighterscript to v0.65.22
2024-02-10 12:57:29 -05:00
jellyfin-bot
e978cdc60b Update API docs 2024-02-10 15:27:33 +00:00
1hitsong
bb96006aa9
Merge pull request #1644 from 1hitsong/musicStartAtTrack
Play song as part of album
2024-02-10 10:26:59 -05:00
renovate[bot]
c8b4ee656d
Update dependency brighterscript to v0.65.22 2024-02-09 21:24:53 +00:00
jellyfin-bot
bfb039ae6b Update API docs 2024-02-09 14:58:42 +00:00
Charles Ewert
ade479fcae
Merge pull request #1699 from cewert/update-master-bugfix 2024-02-09 09:58:06 -05:00
Charles Ewert
d5718501d4 Merge remote-tracking branch 'upstream/2.0.z' into update-master-bugfix 2024-02-08 21:45:39 -05:00
Charles Ewert
9431eef87b
Merge pull request #1698 from cewert/whatsnew-204 2024-02-08 21:00:50 -05:00
Charles Ewert
7b7b7a2a70 update whats new for 2.0.4 2024-02-08 18:04:03 -05:00
jellyfin-bot
cd97aa281f Update API docs 2024-02-08 22:49:20 +00:00
Charles Ewert
b2ee23ee6b
Merge pull request #1514 from cewert/only-show-logo-home 2024-02-08 17:48:45 -05:00
Charles Ewert
c14dfd28f8
Merge pull request #1697 from VTRunner/FixInprocessRecordings 2024-02-08 17:46:43 -05:00
VTRunner
50aa531b7b Fix in-process recording playback with feedback 2024-02-08 15:09:57 -05:00
VTRunner
bee38aea6e Fix in-process recording playback 2024-02-08 11:50:05 -05:00
Charles Ewert
4769c155c2
Merge pull request #1695 from jellyfin/renovate/actions-setup-node-digest 2024-02-07 12:15:38 -05:00
Charles Ewert
d9ce775ab0
Merge pull request #1696 from jellyfin/renovate/actions-upload-pages-artifact-digest 2024-02-07 12:15:11 -05:00
renovate[bot]
6112fbd760
Update actions/upload-pages-artifact digest to 56afc60 2024-02-07 09:56:24 +00:00
renovate[bot]
845ba9f3fd
Update actions/setup-node digest to 60edb5d 2024-02-07 06:18:16 +00:00
Charles Ewert
ad7dbbb990
Merge pull request #1680 from jellyfin/renovate/rooibos-roku-5.x 2024-02-06 19:02:19 -05:00
Charles Ewert
41a57da252
Merge pull request #1691 from cewert/fix-tests 2024-02-06 18:56:25 -05:00
jellyfin-bot
856be7cf7a Update API docs 2024-02-06 17:23:42 +00:00
Charles Ewert
d75b49763e
Merge pull request #1591 from jellyfin/renovate/brighterscript-0.x 2024-02-06 12:23:04 -05:00
Charles Ewert
ed29d4c9fa remove config settings that are no longer needed 2024-02-06 12:16:08 -05:00
Charles Ewert
f354427e08 make test suite work again 2024-02-06 12:15:06 -05:00
Charles Ewert
9c8fab1758 fix new xml validation errors 2024-02-06 11:00:49 -05:00
Charles Ewert
6fd2b97ed6
Merge pull request #1687 from jellyfin/renovate/actions-upload-artifact-digest 2024-02-06 09:14:22 -05:00
Charles Ewert
c44d3bef20
Merge pull request #1689 from jellyfin/renovate/actions-deploy-pages-digest 2024-02-06 09:14:01 -05:00
Charles Ewert
628dab6ce9
Merge pull request #1685 from cewert/bump-version-204 2024-02-06 09:13:23 -05:00
jellyfin-bot
244823f470 Update API docs 2024-02-06 14:11:09 +00:00
Charles Ewert
31f91c7500
Merge pull request #1688 from cewert/sync-master 2024-02-06 09:10:28 -05:00
Andi Chandler
ddfb73c5ca Translated using Weblate (English (United Kingdom))
Currently translated at 33.2% (92 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/en_GB/
2024-02-05 22:30:37 -05:00
renovate[bot]
d27a0efcc2
Update actions/deploy-pages digest to decdde0 2024-02-06 01:09:24 +00:00
Charles Ewert
1999cf5e0b Merge branch '2.0.z' 2024-02-05 18:47:00 -05:00
renovate[bot]
5e83a96ad1
Update actions/upload-artifact digest to 5d5d22a 2024-02-05 21:52:26 +00:00
Charles Ewert
db8617d541 bump bugfix branch version 2024-02-04 11:21:50 -05:00
Charles Ewert
58c746af49
Merge pull request #1683 from 1hitsong/whatsNewContent 2024-02-04 10:36:09 -05:00
Charles Ewert
d5790e150a refactor to remove else clause 2024-02-03 17:42:34 -05:00
1hitsong
3fdd8244be Update What's New Content for 2.0.3 2024-02-03 15:32:16 -05:00
1hitsong
ee8de13268
Merge pull request #1678 from 1hitsong/fixMultiPartCrash
Fix playback of additional parts
2024-02-03 12:23:54 -05:00
renovate[bot]
919a53e0ac
Update dependency rooibos-roku to v5.11.0 2024-02-01 21:49:48 +00:00
Gianluca Coviello
47c548fab4 Translated using Weblate (Italian)
Currently translated at 96.0% (266 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/it/
2024-02-01 10:30:38 -05:00
1hitsong
d7ca790d4b Check selectedItem.json.userdata.PlaybackPositionTicks is valid
Fixes #1658
2024-01-31 20:10:12 -05:00
renovate[bot]
c83f0f85a4
Update dependency brighterscript to v0.65.21 2024-01-31 21:57:42 +00:00
jellyfin-bot
3258d46576 Update API docs 2024-01-31 14:49:53 +00:00
1hitsong
aa87b440c9
Merge pull request #1676 from jellyfin/renovate/brighterscript-formatter-1.x
Update dependency brighterscript-formatter to v1.6.40
2024-01-31 09:49:17 -05:00
1hitsong
d1143d41b5
Merge pull request #1675 from jellyfin/renovate/rokucommunity-bslint-0.x
Update dependency @rokucommunity/bslint to v0.8.17
2024-01-31 09:48:56 -05:00
1hitsong
7de5e684d3
Merge pull request #1622 from 1hitsong/musicTrickBar
Add scrub function to audio trickplay bar
2024-01-31 09:48:28 -05:00
David Vitez
da3b8e8263 Translated using Weblate (Hungarian)
Currently translated at 26.3% (73 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/hu/
2024-01-30 17:30:35 -05:00
renovate[bot]
ad44368782
Update dependency brighterscript-formatter to v1.6.40 2024-01-30 18:31:31 +00:00
renovate[bot]
a814b4ef56
Update dependency @rokucommunity/bslint to v0.8.17 2024-01-30 16:28:43 +00:00
RomeroBA
9ceef486fa Translated using Weblate (Spanish (Latin America))
Currently translated at 20.5% (57 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/es_419/
2024-01-29 21:30:36 -05:00
jellyfin-bot
677643809b Update API docs 2024-01-30 01:36:34 +00:00
1hitsong
94355bc551
Merge pull request #1669 from photonconvergence/multi-episode
Display ending episode number for multi-part TV episodes
2024-01-29 20:36:00 -05:00
photonconvergence
f786e3a684 template literals 2024-01-28 20:50:04 -08:00
1hitsong
f438e1961f Work with videos that span episode numbers 2024-01-28 21:17:51 -05:00
1hitsong
a5d49da8b6 Create popup with full TV series description
Fixes #1673
2024-01-28 21:06:10 -05:00
1hitsong
3d282f9fdc Add logo and season/episode into to OSD 2024-01-28 15:38:02 -05:00
jellyfin-bot
2a9ecce423 Update API docs 2024-01-27 19:54:46 +00:00
1hitsong
90542aa5eb
Merge pull request #1668 from photonconvergence/cleanup
Remove unused function GetSubtitleLanguages in LoadVideoContentTask.bs
2024-01-27 14:54:13 -05:00
1hitsong
94e9e95161
Merge pull request #1626 from jellyfin/renovate/brighterscript-formatter-1.x
Update dependency brighterscript-formatter to v1.6.39
2024-01-27 14:50:05 -05:00
1hitsong
1e0be5f918
Merge pull request #1660 from jellyfin/renovate/actions-upload-artifact-digest
Update actions/upload-artifact digest to 26f96df
2024-01-27 14:47:01 -05:00
1hitsong
f285f67d14
Merge pull request #1657 from jellyfin/renovate/markdownlint-cli2-0.x
Update dependency markdownlint-cli2 to v0.12.1
2024-01-27 14:46:43 -05:00
1hitsong
3c4763ba21
Merge pull request #1625 from jellyfin/renovate/rokucommunity-bslint-0.x
Update dependency @rokucommunity/bslint to v0.8.16
2024-01-27 14:43:15 -05:00
1hitsong
1246244468
Merge pull request #1616 from jellyfin/renovate/rooibos-roku-5.x
Update dependency rooibos-roku to v5.10.0
2024-01-27 14:42:06 -05:00
jellyfin-bot
b7e4c4a0e6 Update API docs 2024-01-27 17:10:18 +00:00
1hitsong
5efbc92b05
Merge pull request #1522 from 1hitsong/selectAudio
Add audio track selection to video player OSD
2024-01-27 12:09:45 -05:00
1hitsong
20d2583daf
Merge pull request #1671 from 1hitsong/fixNoAudioStreamCrash
Fix no audio stream crash
2024-01-27 12:07:45 -05:00
1hitsong
3d55fba207 Add comment 2024-01-27 12:05:39 -05:00
1hitsong
74021e7a2d Allow videos with no audio streams to play
Fixes #1670
2024-01-26 19:49:19 -05:00
photonconvergence
428b8cd73d
Merge branch 'jellyfin:master' into multi-episode 2024-01-25 21:18:35 -08:00
photonconvergence
e102343437
Merge branch 'jellyfin:master' into cleanup 2024-01-25 20:57:59 -08:00
photonconvergence
2e3ed14123 show indexnumberend 2024-01-25 20:53:04 -08:00
renovate[bot]
ff70036fea
Update dependency brighterscript-formatter to v1.6.39 2024-01-25 22:36:33 +00:00
renovate[bot]
f2ac9a49f2
Update dependency @rokucommunity/bslint to v0.8.16 2024-01-25 22:36:19 +00:00
Marcelo
95b974d184 Translated using Weblate (Portuguese (Brazil))
Currently translated at 33.5% (93 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/pt_BR/
2024-01-24 16:30:32 -05:00
photonconvergence
03d3d7815f remove getsubtitlelanguages 2024-01-24 10:17:08 -08:00
Alessandro Vitali
9ce2e2a9c5 Translated using Weblate (German)
Currently translated at 53.4% (148 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2024-01-24 09:30:34 -05:00
renovate[bot]
3bfb01f1e1
Update actions/upload-artifact digest to 26f96df 2024-01-23 18:46:31 +00:00
Lorenzo Morelli
195081c2d0 Translated using Weblate (Italian)
Currently translated at 56.6% (157 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/it/
2024-01-19 12:30:31 -05:00
Ben2003
0dc3c875f0 Translated using Weblate (German)
Currently translated at 52.7% (146 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2024-01-16 10:30:32 -05:00
Joan López Ferrer
73b6b7761d Translated using Weblate (Spanish)
Currently translated at 27.0% (75 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/es/
2024-01-15 06:30:30 -05:00
renovate[bot]
b8a7c1aaae
Update dependency markdownlint-cli2 to v0.12.1 2024-01-14 22:50:09 +00:00
Felipe Oliveira
0a4500e600 Translated using Weblate (Portuguese (Brazil))
Currently translated at 32.4% (90 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/pt_BR/
2024-01-14 14:30:31 -05:00
jellyfin-bot
f7a3618af9 Update API docs 2024-01-14 02:16:13 +00:00
1hitsong
b9634047e8
Merge pull request #1653 from 1hitsong/actorEpisodeSeriesName
Label actor episodes with series name, and season & episode numbers
2024-01-13 21:15:41 -05:00
Charles Ewert
024df70e12
Merge pull request #1656 from cewert/bump-version-2.0.3 2024-01-13 21:09:15 -05:00
jellyfin-bot
424951ccdc Update API docs 2024-01-14 02:05:18 +00:00
1hitsong
127d08f1c0 Update with latest changes 2024-01-13 21:04:46 -05:00
Jimi
6c9b3f10b5 Fix to QuickConnect 2024-01-13 21:04:46 -05:00
1hitsong
5db1b48f96 Don't load song data until stream is loaded
Fixes #1609
2024-01-13 21:04:46 -05:00
Charles Ewert
353fdf275e set default backdrop color to prevent crash 2024-01-13 21:04:46 -05:00
1hitsong
404019c9da Update what's new content 2024-01-13 21:04:46 -05:00
Charles Ewert
a43f42b52b Update source/utils/session.bs 2024-01-13 21:04:46 -05:00
Charles Ewert
52403ca454 Update source/utils/session.bs 2024-01-13 21:04:46 -05:00
1hitsong
6f88e9a032 Ensure caption mode is on when needed 2024-01-13 21:04:46 -05:00
Charles Ewert
a5dbd8fac5 auto update user.friendlyName and api auth header when user.name is updated 2024-01-13 21:04:46 -05:00
1hitsong
c57e7399ce Exit loop once match is found 2024-01-13 21:04:46 -05:00
Charles Ewert
281b6d37f1 fix saved credentials bug #1636 2024-01-13 21:04:46 -05:00
1hitsong
765b36e322 Let subtitles have >1 type
Fixes #1627
2024-01-13 21:04:46 -05:00
1hitsong
190f34edef Prevent unneeded stop/start 2024-01-13 21:04:46 -05:00
Charles Ewert
e46f1097a3 bump version 2024-01-13 21:04:46 -05:00
Charles Ewert
8aa5da228e bump version to 2.0.3 2024-01-13 20:44:12 -05:00
1hitsong
b3ee484220 Add valid checks to used variables
Creates new helper function - isAllValid()
2024-01-13 20:40:44 -05:00
Charles Ewert
a08f8a646d
Merge pull request #1643 from 1hitsong/whatsNewContent 2024-01-13 20:14:16 -05:00
1hitsong
747de69b0b Update with latest changes 2024-01-13 20:05:29 -05:00
Charles Ewert
5be021e662
Merge pull request #1645 from cewert/fix-homeitem-crash 2024-01-13 19:37:08 -05:00
Charles Ewert
6edcf29220
Merge pull request #1651 from jimdogx/fix/1601-quick-connect-crash 2024-01-13 19:36:41 -05:00
1hitsong
3811f298d3 Label actor episodes with Series Name, and season & episode numbers
Fixes #1649
2024-01-13 17:22:50 -05:00
Jimi
509e2cb031 Fix to QuickConnect 2024-01-13 10:20:45 -07:00
1hitsong
917109d7ad
Merge pull request #1646 from 1hitsong/songDataOutOfSync
Fix song data out of sync if changing songs quickly
2024-01-12 23:30:42 -05:00
1hitsong
8bf164ba29 Don't load song data until stream is loaded
Fixes #1609
2024-01-12 21:07:36 -05:00
Charles Ewert
7527d405ed set default backdrop color to prevent crash 2024-01-12 20:55:48 -05:00
1hitsong
f7f43cbd51 Play song as part of album 2024-01-12 19:19:52 -05:00
1hitsong
0c733879f1 Update what's new content 2024-01-12 17:34:06 -05:00
Charles Ewert
5aa26f39aa
Merge pull request #1640 from cewert/fix-saved-credentials 2024-01-12 17:28:45 -05:00
Charles Ewert
e2be70f90b
Update source/utils/session.bs 2024-01-12 17:05:57 -05:00
Charles Ewert
03941b4d77
Update source/utils/session.bs 2024-01-12 17:05:25 -05:00
1hitsong
5854b787a6
Merge pull request #1632 from 1hitsong/subtitleTurnOffBug
Prevent unneeded video stop/start when turning off text subtitles
2024-01-12 17:01:55 -05:00
Charles Ewert
5776339594
Merge pull request #1641 from 1hitsong/fixDefaultSubtileShow 2024-01-12 16:59:16 -05:00
1hitsong
985908138e Ensure caption mode is on when needed 2024-01-12 16:24:41 -05:00
Charles Ewert
3f172dc3e8 auto update user.friendlyName and api auth header when user.name is updated 2024-01-12 16:16:35 -05:00
Charles Ewert
3b888d40a0
Merge pull request #1638 from jellyfin/renovate/actions-deploy-pages-digest 2024-01-12 16:11:20 -05:00
1hitsong
d93520bab4 Exit loop once match is found 2024-01-12 15:52:04 -05:00
Charles Ewert
8874b61781 fix saved credentials bug #1636 2024-01-12 15:49:20 -05:00
renovate[bot]
0ddda93887
Update actions/deploy-pages digest to 87c3283 2024-01-12 20:47:27 +00:00
1hitsong
c4fe6a8c48
Merge pull request #1633 from 1hitsong/fixTextOnlySubtitleOptionList
Allow subtitles to have more than 1 type
2024-01-12 15:47:09 -05:00
Charles Ewert
2e607465a9
Merge pull request #1637 from jellyfin/renovate/actions-upload-artifact-digest 2024-01-12 13:55:35 -05:00
renovate[bot]
f5675a982a
Update actions/upload-artifact digest to 1eb3cb2 2024-01-12 17:39:19 +00:00
Charles Ewert
96ac0fb9d2
Merge pull request #1635 from jellyfin/renovate/markdownlint-cli2-0.x 2024-01-12 12:39:01 -05:00
renovate[bot]
696e8683dc
Update dependency markdownlint-cli2 to v0.12.0 2024-01-12 11:34:06 +00:00
Bence K
e38f46ab13 Translated using Weblate (Hungarian)
Currently translated at 17.6% (49 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/hu/
2024-01-12 05:30:30 -05:00
Charles Ewert
64efebb511
Merge pull request #1587 from jellyfin/renovate/actions-setup-node-digest 2024-01-11 22:26:16 -05:00
Charles Ewert
c1f4e9d389
Merge pull request #1614 from jellyfin/renovate/ci-deps 2024-01-11 22:24:18 -05:00
1hitsong
2dbcba8846 Let subtitles have >1 type
Fixes #1627
2024-01-11 21:16:58 -05:00
1hitsong
d4f4bfc741 Prevent unneeded stop/start 2024-01-11 20:29:15 -05:00
1hitsong
7396d53746 Make left scrub stop at 0 2024-01-11 19:41:38 -05:00
Fynn Gutic
f700f53c05 Translated using Weblate (German)
Currently translated at 23.4% (65 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2024-01-09 05:30:28 -05:00
Charles Ewert
1a875a0a6d specify width for overlayLogo 2024-01-08 23:13:59 -05:00
Charles Ewert
95b0158347 fix logoPoster dimensions 2024-01-08 23:12:58 -05:00
Charles Ewert
7b37ca11db update function definitions 2024-01-08 22:55:05 -05:00
1hitsong
301cd5c58f Merge remote-tracking branch 'origin/master' into selectAudio 2024-01-08 20:35:15 -05:00
1hitsong
0d8f9d127b Add 5 second buffer to end of track as duration may be inaccurate 2024-01-08 20:06:02 -05:00
Andrea Michele Zucchi
dfcf2e4f95 Translated using Weblate (Italian)
Currently translated at 44.4% (123 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/it/
2024-01-08 12:30:29 -05:00
1hitsong
03b2768d58 Rework seek method to reduce buffer 2024-01-07 19:40:01 -05:00
renovate[bot]
5f6880589c
Update dependency rooibos-roku to v5.10.0 2024-01-07 19:08:57 +00:00
1hitsong
39c73755b4 Add scrub function to audio trickplay bar 2024-01-06 20:21:53 -05:00
Charles Ewert
c543e0e373
Merge pull request #1618 from jellyfin/bump-202 2024-01-05 20:49:47 -05:00
jellyfin-bot
9e5bdd4979 Update API docs 2024-01-06 01:31:17 +00:00
Charles Ewert
48c19eda75
Merge pull request #1617 from jellyfin/2.0.z 2024-01-05 20:30:45 -05:00
Charles Ewert
526f075851 bump version 2024-01-05 20:27:28 -05:00
Andrejs
e5027ab6af Translated using Weblate (Latvian)
Currently translated at 15.8% (44 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/lv/
2024-01-05 19:30:26 -05:00
Charles Ewert
ab8c5901a4
Merge pull request #1612 from 1hitsong/updateWhatsNew 2024-01-05 19:07:06 -05:00
Charles Ewert
2640369753
Merge pull request #1604 from 1hitsong/fixDefaultSubtitleTrack 2024-01-05 19:06:21 -05:00
1hitsong
b5ad638449 Update default subtitle logic to work with custom subtitle function 2024-01-04 22:40:31 -05:00
1hitsong
8601adfa14 Improve default subtitle selection logic 2024-01-04 21:45:16 -05:00
1hitsong
497615e214 Fix variable styles 2024-01-04 21:45:16 -05:00
1hitsong
7c8719cbf9 Check var is valid before LCase() 2024-01-04 21:45:16 -05:00
1hitsong
286749ce4c Code cleanup 2024-01-04 21:45:16 -05:00
1hitsong
3a9987f29c Fix default subtitle track selection
Fixes #1571
2024-01-04 21:45:16 -05:00
Antonin Rousseau
853506d87f Translated using Weblate (French)
Currently translated at 100.0% (277 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2024-01-03 20:30:26 -05:00
renovate[bot]
29f5d4fa17
Update alex-page/github-project-automation-plus action to v0.9.0 2024-01-03 22:53:12 +00:00
Adratse
cb22a2ab56 Translated using Weblate (Spanish)
Currently translated at 16.6% (46 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/es/
2024-01-02 22:30:25 -05:00
1hitsong
945a6d3e33
Update source/static/whatsNew/2.0.1.json
Co-authored-by: Charles Ewert <cewert@gmail.com>
2024-01-02 20:51:24 -05:00
Charles Ewert
abafbf53df
Merge pull request #1610 from cewert/prep-v2.0.1 2024-01-02 17:25:36 -05:00
1hitsong
1c274867fc Update What's New content 2024-01-02 17:20:02 -05:00
Charles Ewert
0947deaa30
Merge pull request #1600 from jimdogx/fix/jf-live-tv-not-guide 2024-01-02 17:08:00 -05:00
1hitsong
769c5f40e8
Merge pull request #1603 from 1hitsong/fixArtistNameDisplay
Make music artist presentation views honor the Item Titles setting
2024-01-02 17:03:06 -05:00
Charles Ewert
5baa8f49e7 Bump version for release 2024-01-02 17:02:57 -05:00
Romain Eggermont
f35a6d5f16 Translated using Weblate (French)
Currently translated at 99.2% (275 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2024-01-02 11:30:25 -05:00
Romain Eggermont
833ca95d52 Translated using Weblate (French)
Currently translated at 98.9% (274 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2024-01-02 09:30:30 -05:00
Thomas Schwery
d4911dde1b Translated using Weblate (French)
Currently translated at 98.9% (274 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2024-01-02 09:30:29 -05:00
1hitsong
37bc7dcb91
Merge pull request #1594 from 1hitsong/FixOSDClock
Rework OSD clock to support legacy devices
2024-01-02 08:00:34 -05:00
1hitsong
f4a3c12cb8
Merge pull request #1608 from 1hitsong/fixTextSubtitleChange
Fix subtitle selection for subtitles that are not encoded
2024-01-02 07:57:18 -05:00
jellyfin-bot
23b013f771 Update API docs 2024-01-02 03:50:13 +00:00
Charles Ewert
11f8b60e11
Merge pull request #1547 from photonconvergence/griditem-watched-checkmark 2024-01-01 22:49:39 -05:00
Thomas Schwery
c8d153dc8f Translated using Weblate (French)
Currently translated at 37.9% (105 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2024-01-01 17:30:27 -05:00
Andi Chandler
f2597f25d5 Translated using Weblate (English (United Kingdom))
Currently translated at 31.4% (87 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/en_GB/
2024-01-01 17:30:25 -05:00
1hitsong
a37c7d9a7c Fix subtitle selection
Fixes #1607
2023-12-31 16:12:54 -05:00
Arthur Girard
34c537933b Translated using Weblate (French (Canada))
Currently translated at 33.9% (94 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr_CA/
2023-12-30 23:30:25 -05:00
Kristopher Roller
ecf14e4f43 Translated using Weblate (German)
Currently translated at 19.8% (55 of 277 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2023-12-30 15:30:25 -05:00
Kristopher Roller
b1f8ee4e69 Translated using Weblate (German)
Currently translated at 68.1% (188 of 276 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2023-12-30 02:30:26 -05:00
1hitsong
df6a04f71a Make artist presentation views follow Item Titles setting
Fixes #1548
2023-12-29 09:24:49 -05:00
jellyfin-bot
0d66a4b778 Update API docs 2023-12-29 14:09:14 +00:00
1hitsong
b9e55e0848
Merge pull request #1597 from 1hitsong/fixOKOnEpisodeList
Fix selecting episode using OK button on episode list view
2023-12-29 09:09:12 -05:00
1hitsong
7e3a23467f
Merge pull request #1517 from 1hitsong/sortByRandom
Add Random sorting to supported libraries
2023-12-29 09:08:31 -05:00
1hitsong
cdb73e5287 Add Random sorting to supported libraries
Fixes #1498
2023-12-29 08:55:43 -05:00
ToastKiste21
c0c25d2c6f Translated using Weblate (German)
Currently translated at 59.0% (163 of 276 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2023-12-28 04:30:24 -05:00
Csaba
59e649f9ae Translated using Weblate (Hungarian)
Currently translated at 17.0% (47 of 276 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/hu/
2023-12-27 12:30:23 -05:00
Julian Köck
89fef9d768 Translated using Weblate (German)
Currently translated at 52.5% (145 of 276 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2023-12-25 17:30:24 -05:00
Jimi
19c92f363e Copy / Paste error. 2023-12-24 12:35:23 -07:00
Jimi
a90df38b3a Check for Live TV 2023-12-24 12:25:01 -07:00
Charles Ewert
a9e5e92687
Merge pull request #1599 from jellyfin/renovate/actions-deploy-pages-digest 2023-12-24 12:29:44 -05:00
renovate[bot]
df67d34c7f
Update actions/deploy-pages digest to 7a9bd94 2023-12-24 17:20:04 +00:00
Charles Ewert
36182ae5eb
Merge pull request #1588 from jellyfin/renovate/actions-deploy-pages-4.x 2023-12-24 12:19:48 -05:00
Charles Ewert
d6d5d0b979
Merge pull request #1589 from jellyfin/renovate/actions-upload-pages-artifact-3.x 2023-12-24 12:18:57 -05:00
1hitsong
c480610897 Fix OK button not playing episode from episode list 2023-12-23 17:44:41 -05:00
1hitsong
7b5d553f84 Replace asTimeStringLoc() function with custom code
asTimeStringLoc() was added in Roku OS 12. Rewritten for improved legacy support.
2023-12-23 15:15:16 -05:00
359d3394d3 Add year 2023-12-23 10:45:25 -05:00
Chris
e6bfafb81d Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (276 of 276 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/en_GB/
2023-12-22 09:30:24 -05:00
Luther
d34b1b2608 Translated using Weblate (French)
Currently translated at 29.3% (81 of 276 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2023-12-19 11:30:27 -05:00
Roman Thamm
b4823691a3 Translated using Weblate (German)
Currently translated at 52.1% (144 of 276 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2023-12-19 11:30:27 -05:00
renovate[bot]
bc1210a2ea
Update actions/upload-pages-artifact action to v3 2023-12-19 16:22:49 +00:00
renovate[bot]
0ccfab24da
Update actions/deploy-pages action to v4 2023-12-19 16:22:44 +00:00
renovate[bot]
a8466c6e85
Update actions/setup-node digest to b39b52d 2023-12-18 14:05:32 +00:00
Charles Ewert
2b44960e0a
Merge pull request #1564 from jellyfin/renovate/ropm-0.x 2023-12-17 19:11:43 -05:00
renovate[bot]
efe61fdd0d
Update dependency ropm to v0.10.21 2023-12-18 00:10:16 +00:00
Charles Ewert
cd1a4b4f88
Merge pull request #1563 from jellyfin/renovate/brighterscript-formatter-1.x 2023-12-17 19:09:40 -05:00
renovate[bot]
9fe7de08be
Update dependency brighterscript-formatter to v1.6.36 2023-12-18 00:08:52 +00:00
Charles Ewert
b46b15c94e
Merge pull request #1561 from jellyfin/renovate/rokucommunity-bslint-0.x 2023-12-17 19:07:59 -05:00
renovate[bot]
a42c9fef40
Update dependency @rokucommunity/bslint to v0.8.13 2023-12-18 00:06:54 +00:00
Charles Ewert
5c5ab34385
Merge pull request #1562 from jellyfin/renovate/brighterscript-0.x 2023-12-17 19:05:49 -05:00
renovate[bot]
c81ac215e4
Update dependency brighterscript to v0.65.13 2023-12-18 00:03:03 +00:00
Charles Ewert
48f0afc5dd
Merge pull request #1581 from jellyfin/renovate/actions-upload-artifact-4.x 2023-12-17 19:02:19 -05:00
renovate[bot]
b6fa9c14f0
Update actions/upload-artifact action to v4 2023-12-18 00:01:23 +00:00
Charles Ewert
b6d67c6305
Merge pull request #1560 from jellyfin/renovate/actions-stale-9.x 2023-12-17 18:59:13 -05:00
renovate[bot]
6fc1f35b5e
Update actions/stale action to v9 2023-12-17 23:57:32 +00:00
Charles Ewert
83c7206a1e
Merge pull request #1544 from jellyfin/renovate/actions-deploy-pages-digest 2023-12-17 18:54:59 -05:00
renovate[bot]
6ed0805457
Update actions/deploy-pages digest to 13b55b3 2023-12-17 23:53:53 +00:00
Charles Ewert
aa9cd2d266
Merge pull request #1582 from cewert/edit-renovate 2023-12-17 18:20:38 -05:00
Charles Ewert
dc46ff4f51 stop adding ci and github-actions label to dependency bump PRs 2023-12-17 18:19:25 -05:00
David Poznański
9a598471a0 Translated using Weblate (Italian)
Currently translated at 35.1% (97 of 276 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/it/
2023-12-17 09:30:22 -05:00
Andi Chandler
c9d1fa22d6 Translated using Weblate (English (United Kingdom))
Currently translated at 48.1% (133 of 276 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/en_GB/
2023-12-17 09:30:20 -05:00
Charles Ewert
0a9744004c
Merge pull request #1574 from cewert/fix-migration-crash 2023-12-16 11:14:58 -05:00
Charles Ewert
20cdb6fe5c ensure lastRunVersion is valid 2023-12-16 11:12:07 -05:00
Charles Ewert
51452abe28
Merge pull request #1585 from jellyfin/master 2023-12-16 11:11:09 -05:00
Charles Ewert
b41e08f232 remove left seperator from overhang 2023-12-15 20:54:36 -05:00
Neil Burrows
f28268a0d3 Fixing colour of Dev logo 2023-12-15 20:54:36 -05:00
Neil Burrows
5abe8d2c2a Updating images 2023-12-15 20:54:36 -05:00
Charles Ewert
b2b67ae923 hide overhang logo and user when viewing media 2023-12-15 20:54:36 -05:00
photonconvergence
f74cc10cd4 Update components/ListPoster.bs
Co-authored-by: 1hitsong <3330318+1hitsong@users.noreply.github.com>
2023-12-15 20:50:53 -05:00
photonconvergence
c6a51633da Update components/ItemGrid/GridItemSmall.bs
Co-authored-by: 1hitsong <3330318+1hitsong@users.noreply.github.com>
2023-12-15 20:50:53 -05:00
photonconvergence
ff3f01ef9d Update components/ItemGrid/GridItem.bs
Co-authored-by: 1hitsong <3330318+1hitsong@users.noreply.github.com>
2023-12-15 20:50:53 -05:00
photonconvergence
c1169edb11 Add watched checkmark to Series, align 2023-12-15 20:50:53 -05:00
photonconvergence
680a00cb92 Add watched checkmark to Movies 2023-12-15 20:50:53 -05:00
photonconvergence
caee4fa5a8 Add watched checkmark to TV Shows, Collections 2023-12-15 20:50:53 -05:00
photonconvergence
3be939ff36 Remove API Checkmarks 2023-12-15 20:50:53 -05:00
Charles Ewert
66b08400b7
Fix workflows (#1580) 2023-12-15 20:47:11 -05:00
Charles Ewert
fb6fdbe43e
Merge pull request #1579 from jellyfin/unstable 2023-12-14 09:51:02 -05:00
BenniBoy602
326efd0c61 Translated using Weblate (German)
Currently translated at 24.6% (68 of 276 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/de/
2023-12-13 15:30:20 -05:00
Brieuc Dubois
a0e5d65bc6 Translated using Weblate (French)
Currently translated at 22.9% (63 of 275 strings)

Translation: Jellyfin/Jellyfin Roku
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-roku/fr/
2023-12-13 06:30:20 -05:00
Charles Ewert
d9ae77be68
Merge pull request #1573 from cewert/fix-version-check-again 2023-12-12 10:17:54 -05:00
Charles Ewert
4c66f8a5b3 fix version check job (again) 2023-12-12 09:53:02 -05:00
Charles Ewert
b55fc6179a
Merge pull request #1572 from jellyfin/master 2023-12-12 09:48:45 -05:00
1hitsong
9e010f50b2 Add audio track selection to video player 2023-11-20 15:55:15 -05:00
406 changed files with 12695 additions and 2279 deletions

View File

@ -10,7 +10,7 @@ jobs:
permissions:
pull-requests: write
steps:
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9
with:
days-before-issue-stale: -1
days-before-issue-close: -1

View File

@ -6,8 +6,6 @@ concurrency:
on:
push:
branches:
- unstable
pull_request_target:
jobs:
@ -16,7 +14,7 @@ jobs:
name: Project board 📊
runs-on: ubuntu-latest
steps:
- uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
- uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: ${{ github.event_name == 'pull_request_target' }}
continue-on-error: true
with:

View File

@ -2,18 +2,14 @@ name: build-dev
on:
pull_request:
branches-ignore:
- master
push:
branches:
- unstable
jobs:
dev:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
node-version: "lts/*"
cache: "npm"
@ -23,7 +19,7 @@ jobs:
run: npm run ropm
- name: Build app
run: npm run build
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4
with:
name: Jellyfin-Roku-dev-${{ github.sha }}
path: ${{ github.workspace }}/build/staging

View File

@ -3,7 +3,7 @@ name: build-docs
on:
push:
branches:
- unstable
- master
jobs:
docs:

View File

@ -1,68 +1,19 @@
# Builds the production version of the app
name: build-prod
on:
pull_request:
branches:
- master
push:
branches:
- master
- "*.*.z"
jobs:
version-check:
runs-on: ubuntu-latest
steps:
- name: Checkout master (the latest release)
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
ref: master
- name: Install jq to parse json
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
- name: Save old package.json version
run: echo "oldPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
- name: Find and save old major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
- name: Save old manifest version
run: echo "oldManVersion=${{ env.oldMajor }}.${{ env.oldMinor }}.${{ env.oldBuild }}" >> $GITHUB_ENV
- name: Save old Makefile version
run: awk 'BEGIN { FS=" = " } /^VERSION/ { print "oldMakeVersion="$2; }' Makefile >> $GITHUB_ENV
- name: Checkout PR branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Save new package.json version
run: echo "newPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
- name: package.json version must be updated
if: env.oldPackVersion == env.newPackVersion
run: exit 1
- name: Find and save new major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "newMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save new minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "newMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save new build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "newBuild="$2; }' manifest >> $GITHUB_ENV
- name: Save new manifest version
run: echo "newManVersion=${{ env.newMajor }}.${{ env.newMinor }}.${{ env.newBuild }}" >> $GITHUB_ENV
- name: Manifest version must be updated
if: env.oldManVersion == env.newManVersion
run: exit 1
- name: Save new Makefile version
run: awk 'BEGIN { FS=" := " } /^VERSION/ { print "newMakeVersion="$2; }' Makefile >> $GITHUB_ENV
- name: Makefile version must be updated
if: env.oldMakeVersion == env.newMakeVersion
run: exit 1
- name: All new versions must match
if: (env.newManVersion != env.newPackVersion) || (env.newManVersion != env.newMakeVersion)
run: exit 1
prod:
if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'release-prep') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
node-version: "lts/*"
cache: "npm"
@ -72,7 +23,7 @@ jobs:
run: npm run ropm
- name: Build app for production
run: npm run build-prod
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4
with:
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
path: ${{ github.workspace }}/build/staging

235
.github/workflows/bump-version.yml vendored Normal file
View File

@ -0,0 +1,235 @@
name: "Create PR to bump version"
on:
workflow_dispatch:
inputs:
targetBranch:
description: 'Target Branch'
required: true
type: choice
options:
- bugfix
- master
versionType:
description: 'What Version to Bump'
required: true
type: choice
options:
- build
- minor
- major
jobs:
build:
if: ${{ github.event.inputs.versionType == 'build' }}
runs-on: ubuntu-latest
steps:
# Setup
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Install required packages
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
- name: Save targetBranch to env
if: github.event.inputs.targetBranch != 'bugfix'
run: echo "targetBranch=${{ github.event.inputs.targetBranch }}" >> $GITHUB_ENV
# Save old version
- name: Find and save old major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
# Bugfix branch
- name: Save bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "bugfixBranch=${{ env.oldMajor }}.${{ env.oldMinor }}.z" >> $GITHUB_ENV
- name: Update targetBranch with actual bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
- name: Checkout bugfix branch
if: github.event.inputs.targetBranch == 'bugfix'
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
ref: ${{ env.targetBranch }}
# Save old version again if needed
- name: Find and save old major_version from manifest
if: github.event.inputs.targetBranch == 'bugfix'
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
if: github.event.inputs.targetBranch == 'bugfix'
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
if: github.event.inputs.targetBranch == 'bugfix'
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
# Calculate new version
- name: Calculate new build_version
run: echo "newBuild=$((${{ env.oldBuild }} + 1))" >> $GITHUB_ENV
- name: Save new version to env var
run: echo "newVersion=${{ env.oldMajor }}.${{ env.oldMinor }}.${{ env.newBuild }}" >> $GITHUB_ENV
- name: Save a copy of newVersion without periods to env var
run: echo "newVersionSlug=${{ env.newVersion }}" | sed -e 's/\.//g' >> $GITHUB_ENV
# Update files with new versions
- name: Update manifest build_version
run: sed -i "s/build_version=.*/build_version=${{ env.newBuild }}/g" manifest
- name: Update package-lock.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package-lock.json version 2
run: echo "$( jq '.packages."".version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package.json )" > package.json
- name: Update Makefile version
run: sed -i "s/VERSION := .*/VERSION := ${{ env.newVersion }}/g" Makefile
# Create PR
- name: Save new branch name to env
run: echo "newBranch=bump-${{ github.event.inputs.targetBranch }}-to-${{ env.newVersionSlug }}" >> $GITHUB_ENV
- name: Create PR with new version
env:
GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
run: |-
git config user.name "jellyfin-bot"
git config user.email "team@jellyfin.org"
git checkout -b "${{ env.newBranch }}"
git add .
git commit -m "Bump ${{ github.event.inputs.versionType }} version"
git push --set-upstream origin "${{ env.newBranch }}"
gh pr create --title "Bump ${{ github.event.inputs.targetBranch }} branch to ${{ env.newVersion }}" --body "Bump version to prep for next release." --label ignore-changelog --base ${{ env.targetBranch }}
minor:
if: ${{ github.event.inputs.versionType == 'minor' }}
runs-on: ubuntu-latest
steps:
# Setup
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Install jq to update json
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
- name: Save targetBranch to env
if: github.event.inputs.targetBranch != 'bugfix'
run: echo "targetBranch=${{ github.event.inputs.targetBranch }}" >> $GITHUB_ENV
# Save old version
- name: Find and save old major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
# Bugfix branch
- name: Save bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "bugfixBranch=${{ env.oldMajor }}.${{ env.oldMinor }}.z" >> $GITHUB_ENV
- name: Update targetBranch with actual bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
- name: Checkout bugfix branch
if: github.event.inputs.targetBranch == 'bugfix'
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
ref: ${{ env.targetBranch }}
# Calculate new version
- name: Calculate new build_version
run: echo "newMinor=$((${{ env.oldMinor }} + 1))" >> $GITHUB_ENV
- name: Save new version to env var
run: echo "newVersion=${{ env.oldMajor }}.${{ env.newMinor }}.0" >> $GITHUB_ENV
- name: Save a copy of newVersion without periods to env var
run: echo "newVersionSlug=${{ env.newVersion }}" | sed -e 's/\.//g' >> $GITHUB_ENV
# Update files with new versions
- name: Update manifest minor_version
run: sed -i "s/minor_version=.*/minor_version=${{ env.newMinor }}/g" manifest
- name: Update manifest build_version
run: sed -i "s/build_version=.*/build_version=0/g" manifest
- name: Update package-lock.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package-lock.json version 2
run: echo "$( jq '.packages."".version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package.json )" > package.json
- name: Update Makefile version
run: sed -i "s/VERSION := .*/VERSION := ${{ env.newVersion }}/g" Makefile
# Create PR
- name: Save new branch name to env
run: echo "newBranch=bump-${{ github.event.inputs.targetBranch }}-to-${{ env.newVersionSlug }}" >> $GITHUB_ENV
- name: Create PR with new version
env:
GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
run: |-
git config user.name "jellyfin-bot"
git config user.email "team@jellyfin.org"
git checkout -b "${{ env.newBranch }}"
git add .
git commit -m "Bump ${{ github.event.inputs.versionType }} version"
git push --set-upstream origin "${{ env.newBranch }}"
gh pr create --title "Bump ${{ github.event.inputs.targetBranch }} branch to ${{ env.newVersion }}" --body "Bump version to prep for next release." --label ignore-changelog --base ${{ env.targetBranch }}
major:
if: ${{ github.event.inputs.versionType == 'major' }}
runs-on: ubuntu-latest
steps:
# Setup
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Install jq to update json
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
- name: Save targetBranch to env
if: github.event.inputs.targetBranch != 'bugfix'
run: echo "targetBranch=${{ github.event.inputs.targetBranch }}" >> $GITHUB_ENV
# Save old version
- name: Find and save old major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
# Bugfix branch
- name: Save bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "bugfixBranch=${{ env.oldMajor }}.${{ env.oldMinor }}.z" >> $GITHUB_ENV
- name: Update targetBranch with actual bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
- name: Checkout bugfix branch
if: github.event.inputs.targetBranch == 'bugfix'
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
ref: ${{ env.targetBranch }}
# Calculate new version
- name: Calculate new build_version
run: echo "newMajor=$((${{ env.oldMajor }} + 1))" >> $GITHUB_ENV
- name: Save new version to env var
run: echo "newVersion=${{ env.newMajor }}.0.0" >> $GITHUB_ENV
- name: Save a copy of newVersion without periods to env var
run: echo "newVersionSlug=${{ env.newVersion }}" | sed -e 's/\.//g' >> $GITHUB_ENV
# Update files with new versions
- name: Update manifest major_version
run: sed -i "s/major_version=.*/major_version=${{ env.newMajor }}/g" manifest
- name: Update manifest minor_version
run: sed -i "s/minor_version=.*/minor_version=0/g" manifest
- name: Update manifest build_version
run: sed -i "s/build_version=.*/build_version=0/g" manifest
- name: Update package-lock.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package-lock.json version 2
run: echo "$( jq '.packages."".version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package.json )" > package.json
- name: Update Makefile version
run: sed -i "s/VERSION := .*/VERSION := ${{ env.newVersion }}/g" Makefile
# Create PR
- name: Save new branch name to env
run: echo "newBranch=bump-${{ github.event.inputs.targetBranch }}-to-${{ env.newVersionSlug }}" >> $GITHUB_ENV
- name: Create PR with new version
env:
GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
run: |-
git config user.name "jellyfin-bot"
git config user.email "team@jellyfin.org"
git checkout -b "${{ env.newBranch }}"
git add .
git commit -m "Bump ${{ github.event.inputs.versionType }} version"
git push --set-upstream origin "${{ env.newBranch }}"
gh pr create --title "Bump ${{ github.event.inputs.targetBranch }} branch to ${{ env.newVersion }}" --body "Bump version to prep for next release." --label ignore-changelog --base ${{ env.targetBranch }}

View File

@ -3,7 +3,7 @@ name: deploy-api-docs
on:
push:
branches: ["unstable"]
branches: ["master"]
paths: ["docs/**"] # only run if the docs are updated
# Allows you to run this workflow manually from the Actions tab
@ -34,10 +34,10 @@ jobs:
- name: Setup Pages
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4
- name: Upload artifact
uses: actions/upload-pages-artifact@a753861a5debcf57bf8b404356158c8e1e33150c # v2
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with:
# Only upload the api docs folder
path: "docs/api"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@77d7344265e1f960dab5c00dbff52287a70b0d4f # v3
uses: actions/deploy-pages@decdde0ac072f6dcbe43649d82d9c635fff5b4e4 # v4

View File

@ -2,7 +2,6 @@ name: lint
on:
pull_request:
jobs:
brightscript:
runs-on: ubuntu-latest
@ -62,7 +61,7 @@ jobs:
run: npm ci
- name: Install roku package dependencies
run: npx ropm install
- uses: xt0rted/markdownlint-problem-matcher@98d94724052d20ca2e06c091f202e4c66c3c59fb # v2
- uses: xt0rted/markdownlint-problem-matcher@1a5fabfb577370cfdf5af944d418e4be3ea06f27 # v3
- name: Lint markdown files
run: npm run lint-markdown
spelling:

80
.github/workflows/release-prep.yml vendored Normal file
View File

@ -0,0 +1,80 @@
# All of the jobs in this workflow will only run if the PR that triggered it has a 'release-prep' label
name: release-prep
on:
pull_request:
types: [labeled, opened, reopened, synchronize]
jobs:
version-check:
if: ${{ contains(github.event.pull_request.labels.*.name, 'release-prep') }}
runs-on: ubuntu-latest
steps:
- name: DEBUG ${{ github.event.pull_request.base.ref }}
run: echo ${{ github.event.pull_request.base.ref }}
- name: Checkout the branch this PR wants to update
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
ref: ${{ github.event.pull_request.base.ref }}
- name: Install jq to parse json
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
- name: Save old package.json version
run: echo "oldPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
- name: Find and save old major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
- name: Save old manifest version
run: echo "oldManVersion=${{ env.oldMajor }}.${{ env.oldMinor }}.${{ env.oldBuild }}" >> $GITHUB_ENV
- name: Save old Makefile version
run: awk 'BEGIN { FS=" := " } /^VERSION/ { print "oldMakeVersion="$2; }' Makefile >> $GITHUB_ENV
- name: Checkout PR branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Save new package.json version
run: echo "newPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
- name: package.json version must be updated
if: env.oldPackVersion == env.newPackVersion
run: exit 1
- name: Find and save new major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "newMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save new minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "newMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save new build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "newBuild="$2; }' manifest >> $GITHUB_ENV
- name: Save new manifest version
run: echo "newManVersion=${{ env.newMajor }}.${{ env.newMinor }}.${{ env.newBuild }}" >> $GITHUB_ENV
- name: Manifest version must be updated
if: env.oldManVersion == env.newManVersion
run: exit 1
- name: Save new Makefile version
run: awk 'BEGIN { FS=" := " } /^VERSION/ { print "newMakeVersion="$2; }' Makefile >> $GITHUB_ENV
- name: Makefile version must be updated
if: env.oldMakeVersion == env.newMakeVersion
run: exit 1
- name: All new versions must match
if: (env.newManVersion != env.newPackVersion) || (env.newManVersion != env.newMakeVersion)
run: exit 1
build-prod:
if: ${{ contains(github.event.pull_request.labels.*.name, 'release-prep') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
node-version: "lts/*"
cache: "npm"
- name: NPM install
run: npm ci
- name: Install roku module dependencies
run: npm run ropm
- name: Build app for production
run: npm run build-prod
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4
with:
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
path: ${{ github.workspace }}/build/staging
if-no-files-found: error

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
node-version: "lts/*"
cache: "npm"
@ -27,7 +27,7 @@ jobs:
if: env.BRANCH_NAME == 'master'
run: npm run build-prod
- name: Use Java 17
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4
with:
distribution: "temurin"
java-version: "17"

View File

@ -3,7 +3,7 @@
# If you want to get_images, you'll also need convert from ImageMagick
##########################################################################
VERSION := 2.0.0
VERSION := 2.0.5
## usage

View File

@ -31,9 +31,9 @@
],
"diagnosticFilters": ["node_modules/**/*", "**/roku_modules/**/*"],
"autoImportComponentScript": true,
"allowBrighterScriptInBrightScript": true,
"createPackage": false,
"stagingFolderPath": "build",
"retainStagingDir": true,
"plugins": ["rooibos-roku"],
"rooibos": {
"isRecordingCodeCoverage": false,

View File

@ -18,12 +18,13 @@
{
"src": "settings/**/*",
"dest": "settings"
}
},
"manifest"
],
"diagnosticFilters": ["node_modules/**/*", "**/roku_modules/**/*"],
"autoImportComponentScript": true,
"allowBrighterScriptInBrightScript": true,
"stagingFolderPath": "build",
"retainStagingDir": true,
"plugins": ["rooibos-roku"],
"rooibos": {
"isRecordingCodeCoverage": false,

View File

@ -1,8 +1,16 @@
import "pkg:/source/utils/misc.bs"
' @fileoverview Clock component to display current time formatted based on user's chosen 12 or 24 hour setting
' Possible clock formats
enum ClockFormat
h12 = "12h"
h24 = "24h"
end enum
sub init()
' If hideclick setting is checked, exit without setting any variables
' If hideclick setting is enabled, exit without setting any variables
if m.global.session.user.settings["ui.design.hideclock"]
return
end if
@ -16,11 +24,11 @@ sub init()
m.currentTimeTimer.control = "start"
' Default to 12 hour clock
m.format = "short-h12"
m.format = ClockFormat.h12
' If user has selected a 24 hour clock, update date display format
if LCase(m.global.device.clockFormat) = "24h"
m.format = "short-h24"
if LCase(m.global.device.clockFormat) = ClockFormat.h24
m.format = ClockFormat.h24
end if
end sub
@ -34,6 +42,64 @@ sub onCurrentTimeTimerFire()
' Convert to local time zone
m.dateTimeObject.ToLocalTime()
' Format time as requested
m.clockTime.text = m.dateTimeObject.asTimeStringLoc(m.format)
' Format time for display - based on 12h/24h setting
formattedTime = formatTimeAsString()
' Display time
m.clockTime.text = formattedTime
end sub
' formatTimeAsString: Returns a string with the current time formatted for either a 12 or 24 hour clock
'
' @return {string} current time formatted for either a 12 hour or 24 hour clock
function formatTimeAsString() as string
return m.format = ClockFormat.h12 ? format12HourTime() : format24HourTime()
end function
' format12HourTime: Returns a string with the current time formatted for a 12 hour clock
'
' @return {string} current time formatted for a 12 hour clock
function format12HourTime() as string
currentHour = m.dateTimeObject.GetHours()
currentMinute = m.dateTimeObject.GetMinutes()
displayedHour = StrI(currentHour).trim()
displayedMinute = StrI(currentMinute).trim()
meridian = currentHour < 12 ? "am" : "pm"
if currentHour = 0
displayedHour = "12"
end if
if currentHour > 12
correctedHour = currentHour - 12
displayedHour = StrI(correctedHour).trim()
end if
if currentMinute < 10
displayedMinute = `0${displayedMinute}`
end if
return `${displayedHour}:${displayedMinute} ${meridian}`
end function
' format24HourTime: Returns a string with the current time formatted for a 24 hour clock
'
' @return {string} current time formatted for a 24 hour clock
function format24HourTime() as string
currentHour = m.dateTimeObject.GetHours()
currentMinute = m.dateTimeObject.GetMinutes()
displayedHour = StrI(currentHour).trim()
displayedMinute = StrI(currentMinute).trim()
if currentHour < 10
displayedHour = `0${displayedHour}`
end if
if currentMinute < 10
displayedMinute = `0${displayedMinute}`
end if
return `${displayedHour}:${displayedMinute}`
end function

View File

@ -48,7 +48,6 @@
</children>
<interface>
<field id="selectedItem" type="node" alwaysNotify="true" />
<field id="focusedChild" type="node" onChange="focusChanged" />
<field id="itemAlphaSelected" type="string" />
</interface>
</component>

View File

@ -15,6 +15,10 @@ sub init()
m.unplayedCount = m.top.findNode("unplayedCount")
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
m.playedIndicator = m.top.findNode("playedIndicator")
m.checkmark = m.top.findNode("checkmark")
m.checkmark.width = 90
m.checkmark.height = 60
m.itemText.translation = [0, m.itemPoster.height + 7]
@ -34,21 +38,29 @@ sub init()
end sub
sub itemContentChanged()
m.backdrop.blendColor = "#00a4db" ' set default in case global var is invalid
localGlobal = m.global
' Set Random background colors from pallet
posterBackgrounds = m.global.constants.poster_bg_pallet
if isValid(localGlobal) and isValid(localGlobal.constants) and isValid(localGlobal.constants.poster_bg_pallet)
posterBackgrounds = localGlobal.constants.poster_bg_pallet
m.backdrop.blendColor = posterBackgrounds[rnd(posterBackgrounds.count()) - 1]
end if
itemData = m.top.itemContent
if itemData = invalid then return
if itemData.type = "Movie"
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.Played) and itemData.json.UserData.Played
m.playedIndicator.visible = true
end if
m.itemPoster.uri = itemData.PosterUrl
m.itemIcon.uri = itemData.iconUrl
m.itemText.text = itemData.Title
else if itemData.type = "Series"
if m.global.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false
if isValid(localGlobal) and isValid(localGlobal.session) and isValid(localGlobal.session.user) and isValid(localGlobal.session.user.settings)
if localGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
if itemData.json.UserData.UnplayedItemCount > 0
m.unplayedCount.visible = true
@ -59,6 +71,10 @@ sub itemContentChanged()
end if
end if
end if
end if
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.Played) and itemData.json.UserData.Played = true
m.playedIndicator.visible = true
end if
m.itemPoster.uri = itemData.PosterUrl
m.itemIcon.uri = itemData.iconUrl

View File

@ -7,6 +7,7 @@
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" opacity=".99" translation="[201, 0]">
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:MediumBoldSystemFont" horizAlign="center" vertAlign="center" />
</Rectangle>
<PlayedCheckmark id="playedIndicator" color="#00a4dcFF" width="90" height="60" opacity=".99" translation="[201, 0]" visible="false" />
</Poster>
<Poster id="itemIcon" width="50" height="50" translation="[230,10]" />
<Label id="posterText" width="280" height="415" translation="[5,5]" horizAlign="center" vertAlign="center" ellipsizeOnBoundary="true" wrap="true" />

View File

@ -8,6 +8,7 @@ sub init()
m.posterText.font.size = 30
m.title.font.size = 25
m.backdrop = m.top.findNode("backdrop")
m.playedIndicator = m.top.findNode("playedIndicator")
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
@ -37,6 +38,10 @@ sub itemContentChanged()
if not isValid(itemData) then return
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.Played) and itemData.json.UserData.Played
m.playedIndicator.visible = true
end if
m.itemPoster.uri = itemData.PosterUrl
m.posterText.text = itemData.title
m.title.text = itemData.title

View File

@ -6,6 +6,7 @@
<ScrollingLabel translation="[0,340]" id="title" horizAlign="center" font="font:SmallSystemFont" repeatCount="0" maxWidth="230" />
<Poster id="itemIcon" width="50" height="50" translation="[230,10]" />
<Label id="posterText" width="230" height="320" translation="[5,5]" horizAlign="center" vertAlign="center" ellipsizeOnBoundary="true" wrap="true" />
<PlayedCheckmark id="playedIndicator" color="#00a4dcFF" width="60" height="46" translation="[170, 15]" visible="false" />
</children>
<interface>
<field id="itemContent" type="node" onChange="itemContentChanged" />

View File

@ -256,7 +256,8 @@ sub setMoviesOptions(options)
{ "Title": tr("OFFICIAL_RATING"), "Name": "OfficialRating" },
{ "Title": tr("PLAY_COUNT"), "Name": "PlayCount" },
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
{ "Title": tr("RUNTIME"), "Name": "Runtime" }
{ "Title": tr("RUNTIME"), "Name": "Runtime" },
{ "Title": tr("Random"), "Name": "Random" },
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },
@ -275,6 +276,7 @@ sub setBoxsetsOptions(options)
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
{ "Title": tr("Random"), "Name": "Random" },
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },
@ -299,6 +301,7 @@ sub setTvShowsOptions(options)
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
{ "Title": tr("OFFICIAL_RATING"), "Name": "OfficialRating" },
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
{ "Title": tr("Random"), "Name": "Random" },
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },
@ -345,6 +348,7 @@ sub setMusicOptions(options)
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
{ "Title": tr("Random"), "Name": "Random" },
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },

View File

@ -136,6 +136,36 @@ sub loadItems()
resp = APIRequest(url, params)
data = getJson(resp)
' If user has filtered by #, include special characters sorted after Z as well
if isValid(params.NameLessThan)
if LCase(params.NameLessThan) = "a"
' Use same params except for name filter param
params.NameLessThan = ""
params.NameStartsWithOrGreater = "z"
' Perform 2nd API lookup for items starting with Z or greater
startsWithZAndGreaterResp = APIRequest(url, params)
startsWithZAndGreaterData = getJson(startsWithZAndGreaterResp)
if isValidAndNotEmpty(startsWithZAndGreaterData)
specialCharacterItems = []
' Filter out items starting with Z
for each item in startsWithZAndGreaterData.Items
itemName = LCase(item.name)
if not itemName.StartsWith("z")
specialCharacterItems.Push(item)
end if
end for
' Append data to results from before A
data.Items.Append(specialCharacterItems)
data.TotalRecordCount += specialCharacterItems.Count()
end if
end if
end if
if data <> invalid
if data.TotalRecordCount <> invalid then m.top.totalRecordCount = data.TotalRecordCount
@ -164,6 +194,8 @@ sub loadItems()
tmp.image = PosterImage(item.id, { "maxHeight": 425, "maxWidth": 290, "quality": "90" })
else if item.type = "Episode"
tmp = CreateObject("roSGNode", "TVEpisode")
else if LCase(item.Type) = "recording"
tmp = CreateObject("roSGNode", "RecordingData")
else if item.Type = "Genre"
tmp = CreateObject("roSGNode", "ContentNode")
tmp.title = item.name

View File

@ -7,6 +7,11 @@ import "pkg:/source/api/Image.bs"
import "pkg:/source/api/userauth.bs"
import "pkg:/source/utils/deviceCapabilities.bs"
enum SubtitleSelection
notset = -2
none = -1
end enum
sub init()
m.user = AboutMe()
m.top.functionName = "loadItems"
@ -44,19 +49,18 @@ sub loadItems()
id = m.top.itemId
mediaSourceId = invalid
audio_stream_idx = m.top.selectedAudioStreamIndex
subtitle_idx = m.top.selectedSubtitleIndex
forceTranscoding = false
m.top.content = [LoadItems_VideoPlayer(id, mediaSourceId, audio_stream_idx, subtitle_idx, forceTranscoding)]
m.top.content = [LoadItems_VideoPlayer(id, mediaSourceId, audio_stream_idx, forceTranscoding)]
end sub
function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean) as dynamic
function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean) as dynamic
video = {}
video.id = id
video.content = createObject("RoSGNode", "ContentNode")
LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, subtitle_idx, forceTranscoding)
LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, forceTranscoding)
if video.content = invalid
return invalid
@ -65,9 +69,10 @@ function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic,
return video
end function
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean)
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean)
meta = ItemMetaData(video.id)
subtitle_idx = m.top.selectedSubtitleIndex
if not isValid(meta)
video.errorMsg = "Error loading metadata"
@ -76,16 +81,58 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
end if
videotype = LCase(meta.type)
' Check for any Live TV streams or Recordings coming from other places other than the TV Guide
if videotype = "recording" or (isValid(meta.json) and isValid(meta.json.ChannelId))
if isValid(meta.json.EpisodeTitle)
meta.title = meta.json.EpisodeTitle
else if isValid(meta.json.Name)
meta.title = meta.json.Name
end if
meta.live = true
if LCase(meta.json.type) = "program"
video.id = meta.json.ChannelId
else
video.id = meta.json.id
end if
end if
if videotype = "episode" or videotype = "series"
video.content.contenttype = "episode"
video.content.title = "S" + meta.json.ParentIndexNumber.toStr() + "E" + meta.json.IndexNumber.toStr() + " - " + meta.title
if Type(meta.json.ParentIndexNumber) = "roInt" and Type(meta.json.IndexNumber) = "roInt" and Type(meta.json.ProductionYear) = "roInt"
video.content.title = "S" + meta.json.ParentIndexNumber.toStr() + " - E" + meta.json.IndexNumber.toStr() + " - " + meta.title + + " (" + meta.json.ProductionYear.ToStr() + ")"
else
video.content.title = meta.title
end if
else
if Type(meta.json.ProductionYear) = "roInt"
video.content.title = meta.title + " (" + meta.json.ProductionYear.ToStr() + ")"
else
video.content.title = meta.title
end if
end if
video.chapters = meta.json.Chapters
video.showID = meta.showID
logoLookupID = video.id
if videotype = "episode" or videotype = "series"
video.content.contenttype = "episode"
video.seasonNumber = meta.json.ParentIndexNumber
video.episodeNumber = meta.json.IndexNumber
video.episodeNumberEnd = meta.json.IndexNumberEnd
if isValid(meta.showID)
logoLookupID = meta.showID
end if
end if
logoImageExists = api.items.HeadImageURLByName(logoLookupID, "logo")
if logoImageExists
video.logoImage = api.items.GetImageURL(logoLookupID, "logo", 0, { "maxHeight": 65, "maxWidth": 300, "quality": "90" })
end if
user = AboutMe()
if user.Configuration.EnableNextEpisodeAutoPlay
if LCase(m.top.itemType) = "episode"
@ -108,16 +155,41 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
if meta.live then mediaSourceId = ""
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
video.videoId = video.id
video.mediaSourceId = mediaSourceId
video.audioIndex = audio_stream_idx
if not isValid(m.playbackInfo)
video.errorMsg = "Error loading playback info"
video.content = invalid
return
end if
addSubtitlesToVideo(video, meta)
' Enable default subtitle track
if subtitle_idx = SubtitleSelection.notset
defaultSubtitleIndex = defaultSubtitleTrackFromVid(video.id)
if defaultSubtitleIndex <> SubtitleSelection.none
video.SelectedSubtitle = defaultSubtitleIndex
subtitle_idx = defaultSubtitleIndex
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
if not isValid(m.playbackInfo)
video.errorMsg = "Error loading playback info"
video.content = invalid
return
end if
addSubtitlesToVideo(video, meta)
else
video.SelectedSubtitle = subtitle_idx
end if
else
video.SelectedSubtitle = subtitle_idx
end if
video.videoId = video.id
video.mediaSourceId = mediaSourceId
video.audioIndex = audio_stream_idx
video.PlaySessionId = m.playbackInfo.PlaySessionId
if meta.live
@ -131,8 +203,7 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
m.playbackInfo = meta.json
end if
addSubtitlesToVideo(video, meta)
addAudioStreamsToVideo(video)
if meta.live
video.transcodeParams = {
"MediaSourceId": m.playbackInfo.MediaSources[0].Id,
@ -184,13 +255,113 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
setCertificateAuthority(video.content)
video.audioTrack = (audio_stream_idx + 1).ToStr() ' Roku's track indexes count from 1. Our index is zero based
video.SelectedSubtitle = subtitle_idx
if not fully_external
video.content = authRequest(video.content)
end if
end sub
' defaultSubtitleTrackFromVid: Identifies the default subtitle track given video id
'
' @param {dynamic} videoID - id of video user is playing
' @return {integer} indicating the default track's server-side index. Defaults to {SubtitleSelection.none} is one is not found
function defaultSubtitleTrackFromVid(videoID) as integer
if m.global.session.user.configuration.SubtitleMode = "None"
return SubtitleSelection.none ' No subtitles desired: return none
end if
meta = ItemMetaData(videoID)
if not isValid(meta) then return SubtitleSelection.none
if not isValid(meta.json) then return SubtitleSelection.none
if not isValidAndNotEmpty(meta.json.mediaSources) then return SubtitleSelection.none
if not isValidAndNotEmpty(meta.json.MediaSources[0].MediaStreams) then return SubtitleSelection.none
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
selectedAudioLanguage = ""
audioMediaStream = meta.json.MediaSources[0].MediaStreams[m.top.selectedAudioStreamIndex]
' Ensure audio media stream is valid before using language property
if isValid(audioMediaStream)
selectedAudioLanguage = audioMediaStream.Language ?? ""
end if
defaultTextSubs = defaultSubtitleTrack(subtitles["text"], selectedAudioLanguage, true) ' Find correct subtitle track (forced text)
if defaultTextSubs <> SubtitleSelection.none
return defaultTextSubs
end if
if not m.global.session.user.settings["playback.subs.onlytext"]
return defaultSubtitleTrack(subtitles["all"], selectedAudioLanguage) ' if no appropriate text subs exist, allow non-text
end if
return SubtitleSelection.none
end function
' defaultSubtitleTrack:
'
' @param {dynamic} sortedSubtitles - array of subtitles sorted by type and language
' @param {string} selectedAudioLanguage - language for selected audio track
' @param {boolean} [requireText=false] - indicates if only text subtitles should be considered
' @return {integer} indicating the default track's server-side index. Defaults to {SubtitleSelection.none} is one is not found
function defaultSubtitleTrack(sortedSubtitles, selectedAudioLanguage as string, requireText = false as boolean) as integer
userConfig = m.global.session.user.configuration
subtitleMode = isValid(userConfig.SubtitleMode) ? LCase(userConfig.SubtitleMode) : ""
allowSmartMode = false
' Only evaluate selected audio language if we have a value
if selectedAudioLanguage <> ""
allowSmartMode = selectedAudioLanguage <> userConfig.SubtitleLanguagePreference
end if
for each item in sortedSubtitles
' Only auto-select subtitle if language matches SubtitleLanguagePreference
languageMatch = true
if userConfig.SubtitleLanguagePreference <> ""
languageMatch = (userConfig.SubtitleLanguagePreference = item.Track.Language)
end if
' Ensure textuality of subtitle matches preferenced passed as arg
matchTextReq = ((requireText and item.IsTextSubtitleStream) or not requireText)
if languageMatch and matchTextReq
if subtitleMode = "default" and (item.isForced or item.IsDefault)
' Return first forced or default subtitle track
return item.Index
else if subtitleMode = "always"
' Return the first found subtitle track
return item.Index
else if subtitleMode = "onlyforced" and item.IsForced
' Return first forced subtitle track
return item.Index
else if subtitleMode = "smart" and allowSmartMode
' Return the first found subtitle track
return item.Index
end if
end if
end for
' User has chosed smart subtitle mode
' We already attempted to load subtitles in preferred language, but none were found.
' Fall back to default behaviour while ignoring preferredlanguage
if subtitleMode = "smart" and allowSmartMode
for each item in sortedSubtitles
' Ensure textuality of subtitle matches preferenced passed as arg
matchTextReq = ((requireText and item.IsTextSubtitleStream) or not requireText)
if matchTextReq
if item.isForced or item.IsDefault
' Return first forced or default subtitle track
return item.Index
end if
end if
end for
end if
return SubtitleSelection.none ' Keep current default behavior of "None", if no correct subtitle is identified
end function
sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
if protocol <> "file"
@ -220,6 +391,23 @@ sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
end if
end sub
' addAudioStreamsToVideo: Add audio stream data to video
'
' @param {dynamic} video component to add fullAudioData to
sub addAudioStreamsToVideo(video)
audioStreams = []
mediaStreams = m.playbackInfo.MediaSources[0].MediaStreams
for i = 0 to mediaStreams.Count() - 1
if LCase(mediaStreams[i].Type) = "audio"
audioStreams.push(mediaStreams[i])
end if
end for
video.fullAudioData = audioStreams
end sub
sub addSubtitlesToVideo(video, meta)
subtitles = sortSubtitles(meta.id, m.playbackInfo.MediaSources[0].MediaStreams)
safesubs = subtitles["all"]
@ -359,26 +547,33 @@ function sortSubtitles(id as string, MediaStreams)
"IsExternal": stream.IsExternal,
"IsEncoded": stream.DeliveryMethod = "Encode"
}
if stream.isForced
trackType = "forced"
else if stream.IsDefault
trackType = "default"
else if stream.IsTextSubtitleStream
trackType = "text"
else
trackType = "normal"
end if
if prefered_lang <> "" and prefered_lang = stream.Track.Language
tracks[trackType].unshift(stream)
if stream.IsTextSubtitleStream
tracks["text"].unshift(stream)
end if
else
tracks[trackType].push(stream)
if stream.IsTextSubtitleStream
tracks["text"].push(stream)
end if
end if
end if
end for
tracks["default"].append(tracks["normal"])
tracks["forced"].append(tracks["default"])
tracks["forced"].append(tracks["text"])
return { "all": tracks["forced"], "text": tracks["text"] }
end function
@ -414,497 +609,3 @@ function FindPreferredAudioStream(streams as dynamic) as integer
return 1
end function
function getSubtitleLanguages()
return {
"aar": "Afar",
"abk": "Abkhazian",
"ace": "Achinese",
"ach": "Acoli",
"ada": "Adangme",
"ady": "Adyghe; Adygei",
"afa": "Afro-Asiatic languages",
"afh": "Afrihili",
"afr": "Afrikaans",
"ain": "Ainu",
"aka": "Akan",
"akk": "Akkadian",
"alb": "Albanian",
"ale": "Aleut",
"alg": "Algonquian languages",
"alt": "Southern Altai",
"amh": "Amharic",
"ang": "English, Old (ca.450-1100)",
"anp": "Angika",
"apa": "Apache languages",
"ara": "Arabic",
"arc": "Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)",
"arg": "Aragonese",
"arm": "Armenian",
"arn": "Mapudungun; Mapuche",
"arp": "Arapaho",
"art": "Artificial languages",
"arw": "Arawak",
"asm": "Assamese",
"ast": "Asturian; Bable; Leonese; Asturleonese",
"ath": "Athapascan languages",
"aus": "Australian languages",
"ava": "Avaric",
"ave": "Avestan",
"awa": "Awadhi",
"aym": "Aymara",
"aze": "Azerbaijani",
"bad": "Banda languages",
"bai": "Bamileke languages",
"bak": "Bashkir",
"bal": "Baluchi",
"bam": "Bambara",
"ban": "Balinese",
"baq": "Basque",
"bas": "Basa",
"bat": "Baltic languages",
"bej": "Beja; Bedawiyet",
"bel": "Belarusian",
"bem": "Bemba",
"ben": "Bengali",
"ber": "Berber languages",
"bho": "Bhojpuri",
"bih": "Bihari languages",
"bik": "Bikol",
"bin": "Bini; Edo",
"bis": "Bislama",
"bla": "Siksika",
"bnt": "Bantu (Other)",
"bos": "Bosnian",
"bra": "Braj",
"bre": "Breton",
"btk": "Batak languages",
"bua": "Buriat",
"bug": "Buginese",
"bul": "Bulgarian",
"bur": "Burmese",
"byn": "Blin; Bilin",
"cad": "Caddo",
"cai": "Central American Indian languages",
"car": "Galibi Carib",
"cat": "Catalan; Valencian",
"cau": "Caucasian languages",
"ceb": "Cebuano",
"cel": "Celtic languages",
"cha": "Chamorro",
"chb": "Chibcha",
"che": "Chechen",
"chg": "Chagatai",
"chi": "Chinese",
"chk": "Chuukese",
"chm": "Mari",
"chn": "Chinook jargon",
"cho": "Choctaw",
"chp": "Chipewyan; Dene Suline",
"chr": "Cherokee",
"chu": "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic",
"chv": "Chuvash",
"chy": "Cheyenne",
"cmc": "Chamic languages",
"cop": "Coptic",
"cor": "Cornish",
"cos": "Corsican",
"cpe": "Creoles and pidgins, English based",
"cpf": "Creoles and pidgins, French-based ",
"cpp": "Creoles and pidgins, Portuguese-based ",
"cre": "Cree",
"crh": "Crimean Tatar; Crimean Turkish",
"crp": "Creoles and pidgins ",
"csb": "Kashubian",
"cus": "Cushitic languages",
"cze": "Czech",
"dak": "Dakota",
"dan": "Danish",
"dar": "Dargwa",
"day": "Land Dayak languages",
"del": "Delaware",
"den": "Slave (Athapascan)",
"dgr": "Dogrib",
"din": "Dinka",
"div": "Divehi; Dhivehi; Maldivian",
"doi": "Dogri",
"dra": "Dravidian languages",
"dsb": "Lower Sorbian",
"dua": "Duala",
"dum": "Dutch, Middle (ca.1050-1350)",
"dut": "Dutch; Flemish",
"dyu": "Dyula",
"dzo": "Dzongkha",
"efi": "Efik",
"egy": "Egyptian (Ancient)",
"eka": "Ekajuk",
"elx": "Elamite",
"eng": "English",
"enm": "English, Middle (1100-1500)",
"epo": "Esperanto",
"est": "Estonian",
"ewe": "Ewe",
"ewo": "Ewondo",
"fan": "Fang",
"fao": "Faroese",
"fat": "Fanti",
"fij": "Fijian",
"fil": "Filipino; Pilipino",
"fin": "Finnish",
"fiu": "Finno-Ugrian languages",
"fon": "Fon",
"fre": "French",
"frm": "French, Middle (ca.1400-1600)",
"fro": "French, Old (842-ca.1400)",
"frc": "French (Canada)",
"frr": "Northern Frisian",
"frs": "Eastern Frisian",
"fry": "Western Frisian",
"ful": "Fulah",
"fur": "Friulian",
"gaa": "Ga",
"gay": "Gayo",
"gba": "Gbaya",
"gem": "Germanic languages",
"geo": "Georgian",
"ger": "German",
"gez": "Geez",
"gil": "Gilbertese",
"gla": "Gaelic; Scottish Gaelic",
"gle": "Irish",
"glg": "Galician",
"glv": "Manx",
"gmh": "German, Middle High (ca.1050-1500)",
"goh": "German, Old High (ca.750-1050)",
"gon": "Gondi",
"gor": "Gorontalo",
"got": "Gothic",
"grb": "Grebo",
"grc": "Greek, Ancient (to 1453)",
"gre": "Greek, Modern (1453-)",
"grn": "Guarani",
"gsw": "Swiss German; Alemannic; Alsatian",
"guj": "Gujarati",
"gwi": "Gwich'in",
"hai": "Haida",
"hat": "Haitian; Haitian Creole",
"hau": "Hausa",
"haw": "Hawaiian",
"heb": "Hebrew",
"her": "Herero",
"hil": "Hiligaynon",
"him": "Himachali languages; Western Pahari languages",
"hin": "Hindi",
"hit": "Hittite",
"hmn": "Hmong; Mong",
"hmo": "Hiri Motu",
"hrv": "Croatian",
"hsb": "Upper Sorbian",
"hun": "Hungarian",
"hup": "Hupa",
"iba": "Iban",
"ibo": "Igbo",
"ice": "Icelandic",
"ido": "Ido",
"iii": "Sichuan Yi; Nuosu",
"ijo": "Ijo languages",
"iku": "Inuktitut",
"ile": "Interlingue; Occidental",
"ilo": "Iloko",
"ina": "Interlingua (International Auxiliary Language Association)",
"inc": "Indic languages",
"ind": "Indonesian",
"ine": "Indo-European languages",
"inh": "Ingush",
"ipk": "Inupiaq",
"ira": "Iranian languages",
"iro": "Iroquoian languages",
"ita": "Italian",
"jav": "Javanese",
"jbo": "Lojban",
"jpn": "Japanese",
"jpr": "Judeo-Persian",
"jrb": "Judeo-Arabic",
"kaa": "Kara-Kalpak",
"kab": "Kabyle",
"kac": "Kachin; Jingpho",
"kal": "Kalaallisut; Greenlandic",
"kam": "Kamba",
"kan": "Kannada",
"kar": "Karen languages",
"kas": "Kashmiri",
"kau": "Kanuri",
"kaw": "Kawi",
"kaz": "Kazakh",
"kbd": "Kabardian",
"kha": "Khasi",
"khi": "Khoisan languages",
"khm": "Central Khmer",
"kho": "Khotanese; Sakan",
"kik": "Kikuyu; Gikuyu",
"kin": "Kinyarwanda",
"kir": "Kirghiz; Kyrgyz",
"kmb": "Kimbundu",
"kok": "Konkani",
"kom": "Komi",
"kon": "Kongo",
"kor": "Korean",
"kos": "Kosraean",
"kpe": "Kpelle",
"krc": "Karachay-Balkar",
"krl": "Karelian",
"kro": "Kru languages",
"kru": "Kurukh",
"kua": "Kuanyama; Kwanyama",
"kum": "Kumyk",
"kur": "Kurdish",
"kut": "Kutenai",
"lad": "Ladino",
"lah": "Lahnda",
"lam": "Lamba",
"lao": "Lao",
"lat": "Latin",
"lav": "Latvian",
"lez": "Lezghian",
"lim": "Limburgan; Limburger; Limburgish",
"lin": "Lingala",
"lit": "Lithuanian",
"lol": "Mongo",
"loz": "Lozi",
"ltz": "Luxembourgish; Letzeburgesch",
"lua": "Luba-Lulua",
"lub": "Luba-Katanga",
"lug": "Ganda",
"lui": "Luiseno",
"lun": "Lunda",
"luo": "Luo (Kenya and Tanzania)",
"lus": "Lushai",
"mac": "Macedonian",
"mad": "Madurese",
"mag": "Magahi",
"mah": "Marshallese",
"mai": "Maithili",
"mak": "Makasar",
"mal": "Malayalam",
"man": "Mandingo",
"mao": "Maori",
"map": "Austronesian languages",
"mar": "Marathi",
"mas": "Masai",
"may": "Malay",
"mdf": "Moksha",
"mdr": "Mandar",
"men": "Mende",
"mga": "Irish, Middle (900-1200)",
"mic": "Mi'kmaq; Micmac",
"min": "Minangkabau",
"mis": "Uncoded languages",
"mkh": "Mon-Khmer languages",
"mlg": "Malagasy",
"mlt": "Maltese",
"mnc": "Manchu",
"mni": "Manipuri",
"mno": "Manobo languages",
"moh": "Mohawk",
"mon": "Mongolian",
"mos": "Mossi",
"mul": "Multiple languages",
"mun": "Munda languages",
"mus": "Creek",
"mwl": "Mirandese",
"mwr": "Marwari",
"myn": "Mayan languages",
"myv": "Erzya",
"nah": "Nahuatl languages",
"nai": "North American Indian languages",
"nap": "Neapolitan",
"nau": "Nauru",
"nav": "Navajo; Navaho",
"nbl": "Ndebele, South; South Ndebele",
"nde": "Ndebele, North; North Ndebele",
"ndo": "Ndonga",
"nds": "Low German; Low Saxon; German, Low; Saxon, Low",
"nep": "Nepali",
"new": "Nepal Bhasa; Newari",
"nia": "Nias",
"nic": "Niger-Kordofanian languages",
"niu": "Niuean",
"nno": "Norwegian Nynorsk; Nynorsk, Norwegian",
"nob": "Bokmål, Norwegian; Norwegian Bokmål",
"nog": "Nogai",
"non": "Norse, Old",
"nor": "Norwegian",
"nqo": "N'Ko",
"nso": "Pedi; Sepedi; Northern Sotho",
"nub": "Nubian languages",
"nwc": "Classical Newari; Old Newari; Classical Nepal Bhasa",
"nya": "Chichewa; Chewa; Nyanja",
"nym": "Nyamwezi",
"nyn": "Nyankole",
"nyo": "Nyoro",
"nzi": "Nzima",
"oci": "Occitan (post 1500); Provençal",
"oji": "Ojibwa",
"ori": "Oriya",
"orm": "Oromo",
"osa": "Osage",
"oss": "Ossetian; Ossetic",
"ota": "Turkish, Ottoman (1500-1928)",
"oto": "Otomian languages",
"paa": "Papuan languages",
"pag": "Pangasinan",
"pal": "Pahlavi",
"pam": "Pampanga; Kapampangan",
"pan": "Panjabi; Punjabi",
"pap": "Papiamento",
"pau": "Palauan",
"peo": "Persian, Old (ca.600-400 B.C.)",
"per": "Persian",
"phi": "Philippine languages",
"phn": "Phoenician",
"pli": "Pali",
"pol": "Polish",
"pon": "Pohnpeian",
"por": "Portuguese",
"pob": "Portuguese (Brazil)",
"pra": "Prakrit languages",
"pro": "Provençal, Old (to 1500)",
"pus": "Pushto; Pashto",
"qaa-qtz": "Reserved for local use",
"que": "Quechua",
"raj": "Rajasthani",
"rap": "Rapanui",
"rar": "Rarotongan; Cook Islands Maori",
"roa": "Romance languages",
"roh": "Romansh",
"rom": "Romany",
"rum": "Romanian; Moldavian; Moldovan",
"run": "Rundi",
"rup": "Aromanian; Arumanian; Macedo-Romanian",
"rus": "Russian",
"sad": "Sandawe",
"sag": "Sango",
"sah": "Yakut",
"sai": "South American Indian (Other)",
"sal": "Salishan languages",
"sam": "Samaritan Aramaic",
"san": "Sanskrit",
"sas": "Sasak",
"sat": "Santali",
"scn": "Sicilian",
"sco": "Scots",
"sel": "Selkup",
"sem": "Semitic languages",
"sga": "Irish, Old (to 900)",
"sgn": "Sign Languages",
"shn": "Shan",
"sid": "Sidamo",
"sin": "Sinhala; Sinhalese",
"sio": "Siouan languages",
"sit": "Sino-Tibetan languages",
"sla": "Slavic languages",
"slo": "Slovak",
"slv": "Slovenian",
"sma": "Southern Sami",
"sme": "Northern Sami",
"smi": "Sami languages",
"smj": "Lule Sami",
"smn": "Inari Sami",
"smo": "Samoan",
"sms": "Skolt Sami",
"sna": "Shona",
"snd": "Sindhi",
"snk": "Soninke",
"sog": "Sogdian",
"som": "Somali",
"son": "Songhai languages",
"sot": "Sotho, Southern",
"spa": "Spanish; Latin",
"spa": "Spanish; Castilian",
"srd": "Sardinian",
"srn": "Sranan Tongo",
"srp": "Serbian",
"srr": "Serer",
"ssa": "Nilo-Saharan languages",
"ssw": "Swati",
"suk": "Sukuma",
"sun": "Sundanese",
"sus": "Susu",
"sux": "Sumerian",
"swa": "Swahili",
"swe": "Swedish",
"syc": "Classical Syriac",
"syr": "Syriac",
"tah": "Tahitian",
"tai": "Tai languages",
"tam": "Tamil",
"tat": "Tatar",
"tel": "Telugu",
"tem": "Timne",
"ter": "Tereno",
"tet": "Tetum",
"tgk": "Tajik",
"tgl": "Tagalog",
"tha": "Thai",
"tib": "Tibetan",
"tig": "Tigre",
"tir": "Tigrinya",
"tiv": "Tiv",
"tkl": "Tokelau",
"tlh": "Klingon; tlhIngan-Hol",
"tli": "Tlingit",
"tmh": "Tamashek",
"tog": "Tonga (Nyasa)",
"ton": "Tonga (Tonga Islands)",
"tpi": "Tok Pisin",
"tsi": "Tsimshian",
"tsn": "Tswana",
"tso": "Tsonga",
"tuk": "Turkmen",
"tum": "Tumbuka",
"tup": "Tupi languages",
"tur": "Turkish",
"tut": "Altaic languages",
"tvl": "Tuvalu",
"twi": "Twi",
"tyv": "Tuvinian",
"udm": "Udmurt",
"uga": "Ugaritic",
"uig": "Uighur; Uyghur",
"ukr": "Ukrainian",
"umb": "Umbundu",
"und": "Undetermined",
"urd": "Urdu",
"uzb": "Uzbek",
"vai": "Vai",
"ven": "Venda",
"vie": "Vietnamese",
"vol": "Volapük",
"vot": "Votic",
"wak": "Wakashan languages",
"wal": "Walamo",
"war": "Waray",
"was": "Washo",
"wel": "Welsh",
"wen": "Sorbian languages",
"wln": "Walloon",
"wol": "Wolof",
"xal": "Kalmyk; Oirat",
"xho": "Xhosa",
"yao": "Yao",
"yap": "Yapese",
"yid": "Yiddish",
"yor": "Yoruba",
"ypk": "Yupik languages",
"zap": "Zapotec",
"zbl": "Blissymbols; Blissymbolics; Bliss",
"zen": "Zenaga",
"zgh": "Standard Moroccan Tamazight",
"zha": "Zhuang; Chuang",
"znd": "Zande languages",
"zul": "Zulu",
"zun": "Zuni",
"zxx": "No linguistic content; Not applicable",
"zza": "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki"
}
end function

View File

@ -4,7 +4,7 @@
<interface>
<field id="itemId" type="string" />
<field id="selectedAudioStreamIndex" type="integer" value="0" />
<field id="selectedSubtitleIndex" type="integer" value="-1" />
<field id="selectedSubtitleIndex" type="integer" value="-2" />
<field id="isIntro" type="boolean" />
<field id="startIndex" type="integer" value="0" />
<field id="itemType" type="string" value="" />

View File

@ -253,7 +253,8 @@ sub setMoviesOptions(options)
{ "Title": tr("OFFICIAL_RATING"), "Name": "OfficialRating" },
{ "Title": tr("PLAY_COUNT"), "Name": "PlayCount" },
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
{ "Title": tr("RUNTIME"), "Name": "Runtime" }
{ "Title": tr("RUNTIME"), "Name": "Runtime" },
{ "Title": tr("Random"), "Name": "Random" },
]
options.filter = [
@ -272,7 +273,7 @@ sub setMoviesOptions(options)
if m.options.view = "Studios" or m.view = "Studios"
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" },
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" }
]
options.filter = [
{ "Title": tr("All"), "Name": "All" },

View File

@ -140,14 +140,12 @@ sub loadInitialItems()
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
else if LCase(m.view) = "artistspresentation" or LCase(m.options.view) = "artistspresentation"
m.loadItemsTask.genreIds = ""
m.top.showItemTitles = "hidealways"
else if LCase(m.view) = "artistsgrid" or LCase(m.options.view) = "artistsgrid"
m.loadItemsTask.genreIds = ""
else if LCase(m.view) = "albumartistsgrid" or LCase(m.options.view) = "albumartistsgrid"
m.loadItemsTask.genreIds = ""
else if LCase(m.view) = "albumartistspresentation" or LCase(m.options.view) = "albumartistspresentation"
m.loadItemsTask.genreIds = ""
m.top.showItemTitles = "hidealways"
else
m.loadItemsTask.itemId = m.top.parentItem.Id
end if
@ -228,6 +226,7 @@ sub setMusicOptions(options)
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
{ "Title": tr("DATE_PLAYED"), "Name": "DatePlayed" },
{ "Title": tr("RELEASE_DATE"), "Name": "PremiereDate" },
{ "Title": tr("Random"), "Name": "Random" },
]
options.filter = [
@ -238,6 +237,7 @@ sub setMusicOptions(options)
if LCase(m.options.view) = "genres" or LCase(m.view) = "genres"
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" },
{ "Title": tr("Random"), "Name": "Random" },
]
options.filter = []
end if
@ -246,6 +246,7 @@ sub setMusicOptions(options)
options.sort = [
{ "Title": tr("TITLE"), "Name": "SortName" },
{ "Title": tr("DATE_ADDED"), "Name": "DateCreated" },
{ "Title": tr("Random"), "Name": "Random" },
]
end if
end sub

View File

@ -2,9 +2,10 @@ import "pkg:/source/utils/config.bs"
sub init()
m.top.id = "overhang"
' hide seperators till they're needed
m.leftSeperator = m.top.findNode("overlayLeftSeperator")
m.leftSeperator.visible = "false"
m.top.translation = [54, 0]
m.leftGroup = m.top.findNode("overlayLeftGroup")
m.rightGroup = m.top.findNode("overlayRightGroup")
m.rightSeperator = m.top.findNode("overlayRightSeperator")
' set font sizes
m.optionText = m.top.findNode("overlayOptionsText")
@ -38,7 +39,7 @@ end sub
sub onVisibleChange()
if m.top.disableMoveAnimation
m.top.translation = [0, 0]
m.top.translation = [54, 0]
return
end if
if m.top.isVisible
@ -50,16 +51,7 @@ sub onVisibleChange()
end sub
sub updateTitle()
if m.top.title <> ""
m.leftSeperator.visible = "true"
else
m.leftSeperator.visible = "false"
end if
m.title.text = m.top.title
if not m.hideClock
resetTime()
end if
end sub
sub setClockVisibility()
@ -84,7 +76,9 @@ end sub
sub updateUser()
setRightSeperatorVisibility()
user = m.top.findNode("overlayCurrentUser")
if isValid(user)
user.text = m.top.currentUser
end if
end sub
sub updateTime()
@ -145,3 +139,23 @@ sub updateOptions()
m.optionStar.visible = false
end if
end sub
' component boolean field isLogoVisibleChange has changed value
sub isLogoVisibleChange()
isLogoVisible = m.top.isLogoVisible
scene = m.top.getScene()
logo = scene.findNode("overlayLogo")
if isLogoVisible
if not isValid(logo)
posterLogo = createLogoPoster()
m.leftGroup.insertChild(posterLogo, 0)
end if
else
' remove the logo
if isValid(logo)
m.leftGroup.removeChild(logo)
end if
end if
end sub

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<component name="JFOverhang" extends="Group">
<children>
<Poster id="overlayLogo" uri="pkg:/images/logo.png" translation="[70, 53]" width="270" height="72" />
<LayoutGroup id="overlayLeftGroup" layoutDirection="horiz" translation="[375, 53]" itemSpacings="30">
<Rectangle id="overlayLeftSeperator" color="#666666" width="2" height="64" />
<LayoutGroup id="overlayLeftGroup" layoutDirection="horiz" translation="[54, 54]" itemSpacings="60">
<Poster id="overlayLogo" uri="pkg:/images/logo.png" height="66" width="191" />
<ScrollingLabel id="overlayTitle" font="font:LargeSystemFont" vertAlign="center" height="64" maxWidth="1100" repeatCount="0" />
</LayoutGroup>
<LayoutGroup id="overlayRightGroup" layoutDirection="horiz" itemSpacings="30" translation="[1820, 53]" horizAlignment="right">
<LayoutGroup id="overlayRightGroup" layoutDirection="horiz" itemSpacings="30" translation="[1766, 53]" horizAlignment="right">
<Label id="overlayCurrentUser" font="font:MediumSystemFont" width="300" horizAlign="right" vertAlign="center" height="64" />
<Rectangle id="overlayRightSeperator" color="#666666" width="2" height="64" visible="false" />
<LayoutGroup id="overlayTimeGroup" layoutDirection="horiz" horizAlignment="right" itemSpacings="0">
@ -17,7 +16,7 @@
</LayoutGroup>
</LayoutGroup>
<LayoutGroup layoutDirection="horiz" horizAlignment="right" translation="[1820, 125]" vertAlignment="custom">
<LayoutGroup layoutDirection="horiz" horizAlignment="right" translation="[1766, 125]" vertAlignment="custom">
<Label id="overlayOptionsStar" font="font:LargeSystemFont" text="*" />
<Label id="overlayOptionsText" font="font:SmallSystemFont" text="Options" translation="[0,6]" />
</LayoutGroup>
@ -38,6 +37,6 @@
<field id="showOptions" value="true" type="boolean" onChange="updateOptions" />
<field id="isVisible" value="true" type="boolean" onChange="onVisibleChange" />
<field id="disableMoveAnimation" value="false" type="boolean" />
<function name="resetTime" />
<field id="isLogoVisible" value="true" type="boolean" onChange="isLogoVisibleChange" />
</interface>
</component>

View File

@ -8,6 +8,10 @@ sub init()
m.poster = m.top.findNode("poster")
m.unplayedCount = m.top.findNode("unplayedCount")
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
m.playedIndicator = m.top.findNode("playedIndicator")
m.checkmark = m.top.findNode("checkmark")
m.checkmark.width = 90
m.checkmark.height = 60
m.backdrop = m.top.findNode("backdrop")
@ -65,6 +69,10 @@ sub itemContentChanged() as void
end if
end if
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.Played) and itemData.json.UserData.Played
m.playedIndicator.visible = true
end if
if itemData.json.lookup("Type") = "Episode" and isValid(itemData.json.IndexNumber)
m.title.text = StrI(itemData.json.IndexNumber) + ". " + m.title.text

View File

@ -4,9 +4,10 @@
<Rectangle id="backdrop" />
<ScrollingLabel id="Series" horizAlign="center" font="font:SmallSystemFont" repeatCount="0" visible="false" />
<Poster id="poster" translation="[2,0]" loadDisplayMode="scaleToFit">
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[104, 0]">
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[102, 0]">
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:MediumBoldSystemFont" horizAlign="center" vertAlign="center" />
</Rectangle>
<PlayedCheckmark id="playedIndicator" color="#00a4dcFF" width="90" height="60" translation="[102, 0]" visible="false" />
</Poster>
<ScrollingLabel id="title" horizAlign="center" font="font:SmallSystemFont" repeatCount="0" visible="false" />
<Label id="staticTitle" horizAlign="center" font="font:SmallSystemFont" wrap="false" />

View File

@ -109,7 +109,8 @@ sub onContentDataChanged()
m.radioOptions.selectedIndex = i
end if
textLine = cardItem.CreateChild("SimpleLabel")
textLine = cardItem.CreateChild("ScrollingLabel")
textLine.maxWidth = "750"
textLine.text = item.track.description
cardItem.observeField("selected", "onItemSelected")
i++

View File

@ -22,6 +22,11 @@ sub init()
"fontSize": 35,
"fontUri": "font:SystemFontFile",
"color": "#00a4dcFF"
},
"p": {
"fontSize": 27,
"fontUri": "font:SystemFontFile",
"color": "#EFEFEFFF"
}
}

View File

@ -1,8 +1,20 @@
import "pkg:/source/utils/misc.bs"
sub init()
m.top.setFocus(true)
m.top.optionsAvailable = false
end sub
' JFScreen hook.
sub OnScreenShown()
scene = m.top.getScene()
overhang = scene.findNode("overhang")
if isValid(overhang)
overhang.isLogoVisible = true
overhang.currentUser = ""
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean
' Returns true if user navigates to a new focusable element
if not press then return false

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<component name="LoginScene" extends="JFGroup">
<component name="LoginScene" extends="JFScreen">
<children>
<label text="Enter Configuration"
id="prompt"

View File

@ -156,3 +156,14 @@ end function
sub clearErrorMessage()
m.top.errorMessage = ""
end sub
' JFScreen hook called when the screen is displayed by the screen manager
sub OnScreenShown()
scene = m.top.getScene()
overhang = scene.findNode("overhang")
if isValid(overhang)
overhang.isLogoVisible = true
overhang.currentUser = ""
overhang.title = ""
end if
end sub

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<component name="SetServerScreen" extends="JFGroup">
<component name="SetServerScreen" extends="JFScreen">
<interface>
<field id="serverUrl" type="string" alias="serverUrlTextbox.text" />
<field id="serverWidth" alias="serverUrlOutline.width,serverUrlTextbox.width,serverUrlContainer.width,submitSizer.width" value="1620" />

View File

@ -31,7 +31,7 @@ sub setData()
m.top.iconUrl = "pkg:/images/media_type_icons/folder_white.png"
end if
else if datum.type = "Episode" or datum.type = "MusicVideo"
else if datum.type = "Episode" or LCase(datum.type) = "recording" or datum.type = "MusicVideo"
m.top.isWatched = datum.UserData.Played
imgParams = {}

View File

@ -0,0 +1,5 @@
' Called whenever m.top.json changes.
' It is expected that each node that extends JFContentItem will override this function
sub setFields()
end sub

View File

@ -0,0 +1,20 @@
import "pkg:/source/utils/misc.bs"
sub setFields()
datum = m.top.json
m.top.id = datum.id
m.top.title = datum.name
m.top.showID = datum.SeriesID
m.top.seasonID = datum.SeasonID
m.top.overview = datum.overview
m.top.favorite = datum.UserData.isFavorite
end sub
sub setPoster()
if isValid(m.top.image)
m.top.posterURL = m.top.image.url
else
m.top.posterURL = ""
end if
end sub

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<component name="RecordingData" extends="ContentNode">
<interface>
<field id="id" type="string" />
<field id="title" type="string" />
<field id="image" type="node" onChange="setPoster" />
<field id="posterURL" type="string" />
<field id="showID" type="string" />
<field id="seasonID" type="string" />
<field id="overview" type="string" />
<field id="type" type="string" value="Recording" />
<field id="startingPoint" type="longinteger" value="0" />
<field id="json" type="assocarray" onChange="setFields" />
<field id="selectedVideoStreamId" type="string" />
<field id="selectedAudioStreamIndex" type="integer" />
<field id="favorite" type="boolean" />
</interface>
</component>

View File

@ -124,20 +124,18 @@ sub popScene()
stopLoadingSpinner()
end sub
'
' Return group at top of stack without removing
function getActiveScene() as object
return m.groups.peek()
end function
'
' Clear all content from group stack
sub clearScenes()
if m.content <> invalid then m.content.removeChildrenIndex(m.content.getChildCount(), 0)
for each group in m.groups
if LCase(group.subtype()) = "jfscreen"
if type(group) = "roSGNode" and group.isSubtype("JFScreen")
group.callFunc("OnScreenHidden")
end if
end for
@ -191,35 +189,30 @@ sub registerOverhangData(group)
end if
end sub
'
' Remove observers for overhang data
sub unregisterOverhangData(group)
group.unobserveField("overhangTitle")
end sub
'
' Update overhang title
sub updateOverhangTitle(msg)
m.overhang.title = msg.getData()
end sub
'
' Update options availability
sub updateOptions(msg)
m.overhang.showOptions = msg.getData()
end sub
'
' Update whether the overhang is visible or not
sub updateOverhangVisible(msg)
m.overhang.visible = msg.getData()
end sub
'
' Update username in overhang
sub updateUser()
@ -227,7 +220,6 @@ sub updateUser()
if m.overhang <> invalid then m.overhang.currentUser = m.top.currentUser
end sub
'
' Reset time
sub resetTime()

View File

@ -1,3 +1,5 @@
import "pkg:/source/utils/misc.bs"
sub init()
m.top.visible = true
updateSize()
@ -183,9 +185,20 @@ function buildRow(rowTitle as string, items, imgWdth = 0)
row = CreateObject("roSGNode", "ContentNode")
row.Title = tr(rowTitle)
for each mov in items
mov.Id = mov.json.Id
if LCase(mov.json.type) = "episode"
if isAllValid([mov.json.SeriesName, mov.json.ParentIndexNumber, mov.json.IndexNumber, mov.json.Name])
mov.labelText = mov.json.SeriesName
mov.subTitle = `S${mov.json.ParentIndexNumber}:E${mov.json.IndexNumber} - ${mov.json.Name}`
else
mov.labelText = mov.json.Name
mov.subTitle = mov.json.ProductionYear
end if
else
mov.labelText = mov.json.Name
mov.subTitle = mov.json.ProductionYear
end if
mov.Id = mov.json.Id
mov.Type = mov.json.Type
if imgWdth > 0
mov.imageWidth = imgWdth

View File

@ -30,9 +30,16 @@ sub loadLibraries()
m.fadeInFocusBitmap.control = "start"
end sub
' JFScreen hook that gets ran as needed.
' Used to update the focus, the state of the data, and tells the server about the device profile
' JFScreen hook called when the screen is displayed by the screen manager
sub OnScreenShown()
scene = m.top.getScene()
overhang = scene.findNode("overhang")
if isValid(overhang)
overhang.isLogoVisible = true
overhang.currentUser = m.global.session.user.name
overhang.title = tr("Home")
end if
if isValid(m.top.lastFocus)
m.top.lastFocus.setFocus(true)
else
@ -53,6 +60,17 @@ sub OnScreenShown()
end if
end sub
' JFScreen hook called when the screen is hidden by the screen manager
sub OnScreenHidden()
scene = m.top.getScene()
overhang = scene.findNode("overhang")
if isValid(overhang)
overhang.isLogoVisible = false
overhang.currentUser = ""
overhang.title = ""
end if
end sub
' Triggered by m.postTask after completing a post.
' Empty the task data when finished.
sub postFinished()

View File

@ -21,16 +21,25 @@ sub init()
m.showProgressBarField = m.top.findNode("showProgressBarField")
' Randomize the background colors
backdropColor = "#00a4db" ' set default in case global var is invalid
localGlobal = m.global
if isValid(localGlobal) and isValid(localGlobal.constants) and isValid(localGlobal.constants.poster_bg_pallet)
posterBackgrounds = localGlobal.constants.poster_bg_pallet
backdropColor = posterBackgrounds[rnd(posterBackgrounds.count()) - 1]
end if
' update the backdrop node
m.backdrop = m.top.findNode("backdrop")
posterBackgrounds = m.global.constants.poster_bg_pallet
m.backdrop.color = posterBackgrounds[rnd(posterBackgrounds.count()) - 1]
m.backdrop.color = backdropColor
end sub
sub itemContentChanged()
m.unplayedCount.visible = false
if isValid(m.unplayedCount) then m.unplayedCount.visible = false
itemData = m.top.itemContent
if itemData = invalid then return
localGlobal = m.global
itemData.Title = itemData.name ' Temporarily required while we move from "HomeItem" to "JFContentItem"
@ -48,21 +57,22 @@ sub itemContentChanged()
if itemData.isWatched
m.playedIndicator.visible = true
m.unplayedCount.visible = false
else
m.playedIndicator.visible = false
if LCase(itemData.type) = "series"
if m.global.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false
if isValid(localGlobal) and isValid(localGlobal.session) and isValid(localGlobal.session.user) and isValid(localGlobal.session.user.settings)
if not localGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"]
if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
if itemData.json.UserData.UnplayedItemCount > 0
m.unplayedCount.visible = true
if isValid(m.unplayedCount) then m.unplayedCount.visible = true
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
end if
end if
end if
end if
end if
end if
' Format the Data based on the type of Home Data
if itemData.type = "CollectionFolder" or itemData.type = "UserView" or itemData.type = "Channel"
@ -113,7 +123,7 @@ sub itemContentChanged()
return
end if
if itemData.type = "Episode"
if itemData.type = "Episode" or LCase(itemData.type) = "recording"
m.itemText.text = itemData.json.SeriesName
if itemData.PlayedPercentage > 0

View File

@ -75,12 +75,17 @@ sub processUserSections()
m.expectedRowCount = 1 ' the favorites row is hardcoded to always show atm
m.processedRowCount = 0
sessionUser = m.global.session.user
' calculate expected row count by processing homesections
for i = 0 to 6
sectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()])
userSection = sessionUser.settings["homesection" + i.toStr()]
sectionName = userSection ?? "none"
sectionName = LCase(sectionName)
if sectionName = "latestmedia"
' expect 1 row per filtered media library
m.filteredLatest = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.LatestItemsExcludes)
m.filteredLatest = filterNodeArray(m.libraryData, "id", sessionUser.configuration.LatestItemsExcludes)
for each latestLibrary in m.filteredLatest
if latestLibrary.collectionType <> "boxsets" and latestLibrary.collectionType <> "livetv" and latestLibrary.json.CollectionType <> "Program"
m.expectedRowCount++
@ -94,7 +99,10 @@ sub processUserSections()
' Add home sections in order based on user settings
loadedSections = 0
for i = 0 to 6
sectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()])
userSection = sessionUser.settings["homesection" + i.toStr()]
sectionName = userSection ?? "none"
sectionName = LCase(sectionName)
sectionLoaded = false
if sectionName <> "none"
sectionLoaded = addHomeSection(sectionName)
@ -141,8 +149,13 @@ function getOriginalSectionIndex(sectionName as string) as integer
sectionIndex = 0
indexLatestMediaSection = 0
sessionUser = m.global.session.user
for i = 0 to 6
settingSectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()])
userSection = sessionUser.settings["homesection" + i.toStr()]
settingSectionName = userSection ?? "none"
settingSectionName = LCase(settingSectionName)
if settingSectionName = "latestmedia"
indexLatestMediaSection = i
end if

View File

@ -24,6 +24,16 @@ sub redraw()
m.top.findNode("UserRow").translation = [leftBorder, topBorder]
end sub
' JFScreen hook called when the screen is displayed by the screen manager
sub OnScreenShown()
scene = m.top.getScene()
overhang = scene.findNode("overhang")
if isValid(overhang)
overhang.isLogoVisible = true
overhang.currentUser = ""
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<component name="UserSelect" extends="JFGroup">
<component name="UserSelect" extends="JFScreen">
<children>
<Label text="Please sign in" horizAlign="center" font="font:LargeSystemFont" height="100" width="1920" translation="[0, 200]" />
<UserRow id="userRow" translation="[130, 360]" />

View File

@ -125,6 +125,11 @@ sub playQueue()
return
end if
if nextItemMediaType = "audiobook"
CreateAudioPlayerView()
return
end if
if nextItemMediaType = "musicvideo"
CreateVideoPlayerView()
return
@ -145,6 +150,11 @@ sub playQueue()
return
end if
if nextItemMediaType = "recording"
CreateVideoPlayerView()
return
end if
if nextItemMediaType = "trailer"
CreateVideoPlayerView()
return
@ -249,6 +259,10 @@ sub setTopStartingPoint(positionTicks)
m.queue[0].startingPoint = positionTicks
end sub
' getItemType: Returns the media type of the passed item
'
' @param {dynamic} item - Item to evaluate
' @return {string} indicating type of media item is
function getItemType(item) as string
if isValid(item) and isValid(item.json) and isValid(item.json.mediatype) and item.json.mediatype <> ""
return LCase(item.json.mediatype)

View File

@ -9,6 +9,7 @@
<function name="getHold" />
<function name="getIsShuffled" />
<function name="getItemByIndex" />
<function name="getItemType" />
<function name="getPosition" />
<function name="getQueue" />
<function name="getQueueTypes" />

View File

@ -14,6 +14,7 @@ sub CreateVideoPlayerView()
m.view.observeField("state", "onStateChange")
m.view.observeField("selectPlaybackInfoPressed", "onSelectPlaybackInfoPressed")
m.view.observeField("selectSubtitlePressed", "onSelectSubtitlePressed")
m.view.observeField("selectAudioPressed", "onSelectAudioPressed")
mediaSourceId = m.global.queueManager.callFunc("getCurrentItem").mediaSourceId
@ -32,18 +33,40 @@ end sub
' Event Handlers
' -----------------
' onSelectAudioPressed: Display audio selection dialog
'
sub onSelectAudioPressed()
audioData = {
data: []
}
for each item in m.view.fullAudioData
audioStreamItem = {
"Index": item.Index,
"IsExternal": item.IsExternal,
"Track": {
"description": item.DisplayTitle
},
"Type": "audioselection"
}
if m.view.audioIndex = item.Index
audioStreamItem.selected = true
end if
audioData.data.push(audioStreamItem)
end for
m.global.sceneManager.callFunc("radioDialog", tr("Select Audio"), audioData)
m.global.sceneManager.observeField("returnData", "onSelectionMade")
end sub
' User requested subtitle selection popup
sub onSelectSubtitlePressed()
' None is always first in the subtitle list
subtitleData = {
data: [{
"Index": -1,
"IsExternal": false,
"Track": {
"description": "None"
},
"Type": "subtitleselection"
}]
data: []
}
for each item in m.view.fullSubtitleData
@ -69,9 +92,24 @@ sub onSelectSubtitlePressed()
end if
end if
' Put the selected item at the top of the option list
if isValid(item.selected) and item.selected
subtitleData.data.Unshift(item)
else
subtitleData.data.push(item)
end if
end for
' Manually create the None option and place at top
subtitleData.data.Unshift({
"Index": -1,
"IsExternal": false,
"Track": {
"description": "None"
},
"Type": "subtitleselection"
})
m.global.sceneManager.callFunc("radioDialog", tr("Select Subtitles"), subtitleData)
m.global.sceneManager.observeField("returnData", "onSelectionMade")
end sub
@ -85,6 +123,25 @@ sub onSelectionMade()
if LCase(m.global.sceneManager.returnData.type) = "subtitleselection"
processSubtitleSelection()
return
end if
if LCase(m.global.sceneManager.returnData.type) = "audioselection"
processAudioSelection()
return
end if
end sub
' processAudioSelection: Audio track selection handler
'
sub processAudioSelection()
selectedAudioTrack = m.global.sceneManager.returnData
if isValid(selectedAudioTrack)
if isValid(selectedAudioTrack.index)
m.view.audioIndex = selectedAudioTrack.index
end if
end if
end sub
@ -99,6 +156,14 @@ sub processSubtitleSelection()
' The playbackData is now outdated and must be refreshed
m.playbackData = invalid
' Find previously selected subtitle and identify if it was encoded
for each item in m.view.fullSubtitleData
if item.index = m.view.selectedSubtitle
m.view.previousSubtitleWasEncoded = item.IsEncoded
exit for
end if
end for
if LCase(m.selectedSubtitle.track.description) = "none"
m.view.globalCaptionMode = "Off"
m.view.subtitleTrack = ""
@ -111,19 +176,20 @@ sub processSubtitleSelection()
end if
if m.selectedSubtitle.IsEncoded
' Roku can not natively display these subtitles, so turn off the caption mode on the device
m.view.globalCaptionMode = "Off"
else
' Roku can natively display these subtitles, ensure the caption mode on the device is on
m.view.globalCaptionMode = "On"
end if
if m.selectedSubtitle.IsExternal
' Roku may rearrange subtitle tracks. Look up track based on name to ensure we get the correct index
availableSubtitleTrackIndex = availSubtitleTrackIdx(m.selectedSubtitle.Track.TrackName)
if availableSubtitleTrackIndex = -1 then return
m.view.subtitleTrack = m.view.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName
else
m.view.selectedSubtitle = m.selectedSubtitle.Index
end if
m.view.selectedSubtitle = m.selectedSubtitle.Index
end sub
' User requested playback info

View File

@ -14,6 +14,7 @@ sub init()
m.songList.observeField("doneLoading", "onDoneLoading")
m.dscr = m.top.findNode("overview")
m.dscr.ellipsisText = tr("... (Press * to read more)")
createDialogPallete()
end sub

View File

@ -13,7 +13,7 @@
<JFButton id="instantMix" minChars="8" text="Instant Mix"></JFButton>
</LayoutGroup>
<LayoutGroup id="infoGroup" layoutDirection="vert" itemSpacings="[15]">
<Label id="overview" wrap="true" height="310" width="1250" ellipsisText=" ... (Press * to read more)" />
<Label id="overview" wrap="true" height="310" width="1250" />
<Rectangle id='songListRect' translation="[-30, 0]" width="1260" height="510" color="0x202020ff">
<AlbumTrackList itemComponentName="SongItem" id="songList" translation="[45, 25]" itemSize="[1170,60]" numRows="7" />
</Rectangle>

View File

@ -39,6 +39,7 @@ sub init()
m.backDrop = m.top.findNode("backdrop")
m.artistImage = m.top.findNode("artistImage")
m.dscr = m.top.findNode("overview")
m.dscr.ellipsisText = tr("... (Press * to read more)")
m.dscr.observeField("isTextEllipsized", "onEllipsisChanged")
createDialogPallete()
end sub

View File

@ -8,7 +8,7 @@
<LayoutGroup id="toplevel" layoutDirection="vert" itemSpacings="[75]">
<LayoutGroup id="main_group" layoutDirection="horiz" itemSpacings="[125]">
<LayoutGroup layoutDirection="vert" itemSpacings="[75]">
<Label id="overview" wrap="true" lineSpacing="25" maxLines="6" width="1080" ellipsisText=" ... (Press * to read more)" />
<Label id="overview" wrap="true" lineSpacing="25" maxLines="6" width="1080" />
<ButtonGroupHoriz id="buttons" itemSpacings="[20]">
<IconButton id="play" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/play.png" text="Play" height="85" width="150" />
<IconButton id="instantMix" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/instantMix.png" text="Instant Mix" height="85" width="150" />

View File

@ -5,6 +5,11 @@ import "pkg:/source/utils/config.bs"
sub init()
m.top.optionsAvailable = false
m.inScrubMode = false
m.lastRecordedPositionTimestamp = 0
m.scrubTimestamp = -1
m.playlistTypeCount = m.global.queueManager.callFunc("getQueueUniqueTypes").count()
setupAudioNode()
setupAnimationTasks()
@ -13,9 +18,8 @@ sub init()
setupDataTasks()
setupScreenSaver()
m.playlistTypeCount = m.global.queueManager.callFunc("getQueueUniqueTypes").count()
m.buttonCount = m.buttons.getChildCount()
m.seekPosition.translation = [720 - (m.seekPosition.width / 2), m.seekPosition.translation[1]]
m.screenSaverTimeout = 300
@ -32,6 +36,8 @@ sub init()
pageContentChanged()
setShuffleIconState()
setLoopButtonImage()
m.buttons.setFocus(true)
end sub
sub onScreensaverTimeoutLoaded()
@ -96,6 +102,20 @@ end sub
sub setupButtons()
m.buttons = m.top.findNode("buttons")
m.top.observeField("selectedButtonIndex", "onButtonSelectedChange")
' If we're playing a mixed playlist, remove the shuffle and loop buttons
if m.playlistTypeCount > 1
shuffleButton = m.top.findNode("shuffle")
m.buttons.removeChild(shuffleButton)
loopButton = m.top.findNode("loop")
m.buttons.removeChild(loopButton)
m.previouslySelectedButtonIndex = 0
m.top.selectedButtonIndex = 1
return
end if
m.previouslySelectedButtonIndex = 1
m.top.selectedButtonIndex = 2
end sub
@ -117,13 +137,18 @@ sub setupInfoNodes()
m.playPosition = m.top.findNode("playPosition")
m.bufferPosition = m.top.findNode("bufferPosition")
m.seekBar = m.top.findNode("seekBar")
m.thumb = m.top.findNode("thumb")
m.shuffleIndicator = m.top.findNode("shuffleIndicator")
m.loopIndicator = m.top.findNode("loopIndicator")
m.positionTimestamp = m.top.findNode("positionTimestamp")
m.seekPosition = m.top.findNode("seekPosition")
m.seekTimestamp = m.top.findNode("seekTimestamp")
m.totalLengthTimestamp = m.top.findNode("totalLengthTimestamp")
end sub
sub bufferPositionChanged()
if m.inScrubMode then return
if not isValid(m.global.audioPlayer.bufferingStatus)
bufferPositionBarWidth = m.seekBar.width
else
@ -141,6 +166,8 @@ sub bufferPositionChanged()
end sub
sub audioPositionChanged()
stopLoadingSpinner()
if m.global.audioPlayer.position = 0
m.playPosition.width = 0
end if
@ -159,14 +186,22 @@ sub audioPositionChanged()
playPositionBarWidth = m.seekBar.width
end if
if not m.inScrubMode
moveSeekbarThumb(playPositionBarWidth)
' Change the seek position timestamp
m.seekTimestamp.text = secondsToHuman(m.global.audioPlayer.position, false)
end if
' Use animation to make the display smooth
m.playPositionAnimationWidth.keyValue = [m.playPosition.width, playPositionBarWidth]
m.playPositionAnimation.control = "start"
' Update displayed position timestamp
if isValid(m.global.audioPlayer.position)
m.lastRecordedPositionTimestamp = m.global.audioPlayer.position
m.positionTimestamp.text = secondsToHuman(m.global.audioPlayer.position, false)
else
m.lastRecordedPositionTimestamp = 0
m.positionTimestamp.text = "0:00"
end if
@ -217,7 +252,9 @@ sub audioStateChanged()
if m.global.audioPlayer.state = "finished"
' User has enabled single song loop, play current song again
if m.global.audioPlayer.loopMode = "one"
m.scrubTimestamp = -1
playAction()
exitScrubMode()
return
end if
@ -261,8 +298,28 @@ function playAction() as boolean
end function
function previousClicked() as boolean
if m.playlistTypeCount > 1 then return false
if m.global.queueManager.callFunc("getPosition") = 0 then return false
currentQueuePosition = m.global.queueManager.callFunc("getPosition")
if currentQueuePosition = 0 then return false
if m.playlistTypeCount > 1
previousItem = m.global.queueManager.callFunc("getItemByIndex", currentQueuePosition - 1)
previousItemType = m.global.queueManager.callFunc("getItemType", previousItem)
if previousItemType <> "audio"
m.global.audioPlayer.control = "stop"
m.global.sceneManager.callFunc("clearPreviousScene")
m.global.queueManager.callFunc("moveBack")
m.global.queueManager.callFunc("playQueue")
return true
end if
end if
exitScrubMode()
m.lastRecordedPositionTimestamp = 0
m.positionTimestamp.text = "0:00"
if m.global.audioPlayer.state = "playing"
m.global.audioPlayer.control = "stop"
@ -276,7 +333,6 @@ function previousClicked() as boolean
m.global.queueManager.callFunc("moveBack")
pageContentChanged()
return true
end function
@ -312,7 +368,28 @@ sub setLoopButtonImage()
end sub
function nextClicked() as boolean
if m.playlistTypeCount > 1 then return false
if m.playlistTypeCount > 1
currentQueuePosition = m.global.queueManager.callFunc("getPosition")
if currentQueuePosition < m.global.queueManager.callFunc("getCount") - 1
nextItem = m.global.queueManager.callFunc("getItemByIndex", currentQueuePosition + 1)
nextItemType = m.global.queueManager.callFunc("getItemType", nextItem)
if nextItemType <> "audio"
m.global.audioPlayer.control = "stop"
m.global.sceneManager.callFunc("clearPreviousScene")
m.global.queueManager.callFunc("moveForward")
m.global.queueManager.callFunc("playQueue")
return true
end if
end if
end if
exitScrubMode()
m.lastRecordedPositionTimestamp = 0
m.positionTimestamp.text = "0:00"
' Reset loop mode due to manual user interaction
if m.global.audioPlayer.loopMode = "one"
@ -379,6 +456,8 @@ sub LoadNextSong()
m.global.audioPlayer.control = "stop"
end if
exitScrubMode()
' Reset playPosition bar without animation
m.playPosition.width = 0
m.global.queueManager.callFunc("moveForward")
@ -388,6 +467,30 @@ end sub
' Update values on screen when page content changes
sub pageContentChanged()
m.LoadAudioStreamTask.control = "STOP"
currentItem = m.global.queueManager.callFunc("getCurrentItem")
m.LoadAudioStreamTask.itemId = currentItem.id
m.LoadAudioStreamTask.observeField("content", "onAudioStreamLoaded")
m.LoadAudioStreamTask.control = "RUN"
end sub
' If we have more and 1 song to play, fade in the next and previous controls
sub loadButtons()
if m.global.queueManager.callFunc("getCount") > 1
m.shuffleIndicator.opacity = ".4"
m.loopIndicator.opacity = ".4"
m.displayButtonsAnimation.control = "start"
setLoopButtonImage()
end if
end sub
sub onAudioStreamLoaded()
stopLoadingSpinner()
data = m.LoadAudioStreamTask.content[0]
m.LoadAudioStreamTask.unobserveField("content")
if data <> invalid and data.count() > 0
' Reset buffer bar without animation
m.bufferPosition.width = 0
@ -428,29 +531,6 @@ sub pageContentChanged()
m.totalLengthTimestamp.text = ticksToHuman(currentItem.RunTimeTicks)
end if
m.LoadAudioStreamTask.itemId = currentItem.id
m.LoadAudioStreamTask.observeField("content", "onAudioStreamLoaded")
m.LoadAudioStreamTask.control = "RUN"
end sub
' If we have more and 1 song to play, fade in the next and previous controls
sub loadButtons()
' Don't show audio buttons if we have a mixed playlist
if m.playlistTypeCount > 1 then return
if m.global.queueManager.callFunc("getCount") > 1
m.shuffleIndicator.opacity = ".4"
m.loopIndicator.opacity = ".4"
m.displayButtonsAnimation.control = "start"
setLoopButtonImage()
end if
end sub
sub onAudioStreamLoaded()
stopLoadingSpinner()
data = m.LoadAudioStreamTask.content[0]
m.LoadAudioStreamTask.unobserveField("content")
if data <> invalid and data.count() > 0
m.global.audioPlayer.content = data
m.global.audioPlayer.control = "none"
m.global.audioPlayer.control = "play"
@ -540,6 +620,96 @@ sub setBackdropImage(data)
end if
end sub
' setSelectedButtonState: Changes the icon state url for the currently selected button
'
' @param {string} oldState - current state to replace in icon url
' @param {string} newState - state to replace {oldState} with in icon url
sub setSelectedButtonState(oldState as string, newState as string)
selectedButton = m.buttons.getChild(m.top.selectedButtonIndex)
selectedButton.uri = selectedButton.uri.Replace(oldState, newState)
end sub
' processScrubAction: Handles +/- seeking for the audio trickplay bar
'
' @param {integer} seekStep - seconds to move the trickplay position (negative values allowed)
sub processScrubAction(seekStep as integer)
' Prepare starting playStart property value
if m.scrubTimestamp = -1
m.scrubTimestamp = m.lastRecordedPositionTimestamp
end if
' Don't let seek to go past the end of the song
if m.scrubTimestamp + seekStep > m.songDuration - 5
return
end if
if seekStep > 0
' Move seek forward
m.scrubTimestamp += seekStep
else if m.scrubTimestamp >= Abs(seekStep)
' If back seek won't go below 0, move seek back
m.scrubTimestamp += seekStep
else
' Back seek would go below 0, set to 0 directly
m.scrubTimestamp = 0
end if
' Move the seedbar thumb forward
songPercentComplete = m.scrubTimestamp / m.songDuration
playPositionBarWidth = m.seekBar.width * songPercentComplete
moveSeekbarThumb(playPositionBarWidth)
' Change the displayed position timestamp
m.seekTimestamp.text = secondsToHuman(m.scrubTimestamp, false)
end sub
' resetSeekbarThumb: Resets the thumb to the playing position
'
sub resetSeekbarThumb()
m.scrubTimestamp = -1
moveSeekbarThumb(m.playPosition.width)
end sub
' moveSeekbarThumb: Positions the thumb on the seekbar
'
' @param {float} playPositionBarWidth - width of the play position bar
sub moveSeekbarThumb(playPositionBarWidth as float)
' Center the thumb on the play position bar
thumbPostionLeft = playPositionBarWidth - 10
' Don't let thumb go below 0
if thumbPostionLeft < 0 then thumbPostionLeft = 0
' Don't let thumb go past end of seekbar
if thumbPostionLeft > m.seekBar.width - 25
thumbPostionLeft = m.seekBar.width - 25
end if
' Move the thumb
m.thumb.translation = [thumbPostionLeft, m.thumb.translation[1]]
' Move the seek position element so it follows the thumb
m.seekPosition.translation = [720 + thumbPostionLeft - (m.seekPosition.width / 2), m.seekPosition.translation[1]]
end sub
' exitScrubMode: Moves player out of scrub mode state, resets back to standard play mode
'
sub exitScrubMode()
m.buttons.setFocus(true)
m.thumb.setFocus(false)
if m.seekPosition.visible
m.seekPosition.visible = false
end if
resetSeekbarThumb()
m.inScrubMode = false
m.thumb.visible = false
setSelectedButtonState("-default", "-selected")
end sub
' Process key press events
function onKeyEvent(key as string, press as boolean) as boolean
@ -551,9 +721,58 @@ function onKeyEvent(key as string, press as boolean) as boolean
return true
end if
' Key Event handler when m.thumb is in focus
if m.thumb.hasFocus()
if key = "right"
m.inScrubMode = true
processScrubAction(10)
return true
end if
if key = "left"
m.inScrubMode = true
processScrubAction(-10)
return true
end if
if key = "OK" or key = "play"
if m.inScrubMode
startLoadingSpinner()
m.inScrubMode = false
m.global.audioPlayer.seek = m.scrubTimestamp
return true
end if
return playAction()
end if
end if
if key = "play"
return playAction()
else if key = "back"
end if
if key = "up"
if not m.thumb.visible
m.thumb.visible = true
setSelectedButtonState("-selected", "-default")
end if
if not m.seekPosition.visible
m.seekPosition.visible = true
end if
m.thumb.setFocus(true)
m.buttons.setFocus(false)
return true
end if
if key = "down"
if m.thumb.visible
exitScrubMode()
end if
return true
end if
if key = "back"
m.global.audioPlayer.control = "stop"
m.global.audioPlayer.loopMode = ""
else if key = "rewind"
@ -561,6 +780,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
else if key = "fastforward"
return nextClicked()
else if key = "left"
if m.buttons.hasFocus()
if m.global.queueManager.callFunc("getCount") = 1 then return false
if m.top.selectedButtonIndex > 0
@ -568,13 +788,17 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.top.selectedButtonIndex = m.top.selectedButtonIndex - 1
end if
return true
end if
else if key = "right"
if m.buttons.hasFocus()
if m.global.queueManager.callFunc("getCount") = 1 then return false
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
if m.top.selectedButtonIndex < m.buttonCount - 1 then m.top.selectedButtonIndex = m.top.selectedButtonIndex + 1
return true
end if
else if key = "OK"
if m.buttons.hasFocus()
if m.buttons.getChild(m.top.selectedButtonIndex).id = "play"
return playAction()
else if m.buttons.getChild(m.top.selectedButtonIndex).id = "previous"
@ -588,6 +812,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if
end if
end if
end if
return false
end function

View File

@ -4,8 +4,9 @@
<Poster id="backdrop" opacity=".5" loadDisplayMode="scaleToZoom" width="1920" height="1200" blendColor="#3f3f3f" />
<Poster id="shuffleIndicator" width="64" height="64" uri="pkg:/images/icons/shuffleIndicator-off.png" translation="[1150,775]" opacity="0" />
<Poster id="loopIndicator" width="64" height="64" uri="pkg:/images/icons/loopIndicator-off.png" translation="[700,775]" opacity="0" />
<Label id="positionTimestamp" width="100" height="25" horizAlign="right" font="font:SmallestSystemFont" translation="[590,825]" color="#999999" text="0:00" />
<Label id="totalLengthTimestamp" width="100" height="25" horizAlign="left" font="font:SmallestSystemFont" translation="[1230,825]" color="#999999" />
<Label id="positionTimestamp" width="100" height="25" horizAlign="right" font="font:SmallestSystemFont" translation="[590,838]" color="#999999" text="0:00" />
<Label id="totalLengthTimestamp" width="100" height="25" horizAlign="left" font="font:SmallestSystemFont" translation="[1230,838]" color="#999999" />
<LayoutGroup id="toplevel" layoutDirection="vert" horizAlignment="center" translation="[960,175]" itemSpacings="[40]">
<LayoutGroup id="main_group" layoutDirection="vert" horizAlignment="center" itemSpacings="[15]">
<Poster id="albumCover" width="500" height="500" />
@ -16,6 +17,7 @@
<Rectangle id="seekBar" color="0x00000099" width="500" height="10">
<Rectangle id="bufferPosition" color="0xFFFFFF44" height="10"></Rectangle>
<Rectangle id="playPosition" color="#00a4dcFF" height="10"></Rectangle>
<Poster id="thumb" width="25" height="25" uri="pkg:/images/icons/circle.png" visible="false" translation="[0, -10]" />
</Rectangle>
<LayoutGroup id="buttons" layoutDirection="horiz" horizAlignment="center" itemSpacings="[45]">
<Poster id="loop" width="64" height="64" uri="pkg:/images/icons/loop-default.png" opacity="0" />
@ -37,6 +39,9 @@
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[0.0, 1.0]" fieldToInterp="loop.opacity" />
</Animation>
</LayoutGroup>
<Rectangle id="seekPosition" visible="false" color="0x00000090" height="40" width="110" translation="[720, 790]">
<Label text="0:00" id="seekTimestamp" width="110" height="40" horizAlign="center" vertAlign="center" font="font:SmallestSystemFont" />
</Rectangle>
<Rectangle id="screenSaverBackground" width="1920" height="1080" color="#000000" visible="false" />
<Poster id="screenSaverAlbumCover" width="500" height="500" translation="[960,575]" opacity="0" />
<Poster id="PosterOne" width="389" height="104" translation="[960,540]" opacity="0" />

View File

@ -13,6 +13,7 @@ sub init()
m.songList.observeField("doneLoading", "onDoneLoading")
m.dscr = m.top.findNode("overview")
m.dscr.ellipsisText = tr("... (Press * to read more)")
createDialogPallete()
end sub

View File

@ -12,7 +12,7 @@
<JFButton id="playAll" minChars="8" text="Play All"></JFButton>
</LayoutGroup>
<LayoutGroup id="infoGroup" layoutDirection="vert" itemSpacings="[15]">
<Label id="overview" wrap="true" height="310" width="1250" ellipsisText=" ... (Press * to read more)" />
<Label id="overview" wrap="true" height="310" width="1250" />
<Rectangle id='songListRect' translation="[-30, 0]" width="1260" height="510" color="0x202020ff">
<AlbumTrackList itemComponentName="SongItem" id="songList" translation="[45, 25]" itemSize="[1170,60]" numRows="7" />
</Rectangle>

View File

@ -10,8 +10,16 @@ sub monitorQuickConnect()
authenticated = checkQuickConnect(m.top.secret)
if authenticated = true
loggedIn = AuthenticateViaQuickConnect(m.top.secret)
if loggedIn
currentUser = AboutMe()
session.user.Login(currentUser, m.top.saveCredentials)
session.user.LoadUserPreferences()
LoadUserAbilities()
m.top.authenticated = 1
else
m.top.authenticated = -1
return
end if
end if
m.top.authenticated = -1
end sub

View File

@ -4,5 +4,6 @@
<interface>
<field id="secret" type="string" />
<field id="authenticated" type="integer" value="0" />
<field id="saveCredentials" type="boolean" />
</interface>
</component>

View File

@ -14,11 +14,13 @@ sub quickConnectStatus()
m.quickConnectTimer.control = "stop"
m.checkTask = CreateObject("roSGNode", "QuickConnect")
m.checkTask.secret = m.top.quickConnectJson.secret
m.checkTask.saveCredentials = m.top.saveCredentials
m.checkTask.observeField("authenticated", "OnAuthenticated")
m.checkTask.control = "run"
end sub
sub OnAuthenticated()
m.checkTask.control = "stop"
m.checkTask.unobserveField("authenticated")
' Did we get the A-OK to authenticate?
@ -26,28 +28,18 @@ sub OnAuthenticated()
if authenticated < 0
' Still waiting, check again in 3 seconds...
authenticated = 0
m.checkTask.observeField("authenticated", "OnAuthenticated")
m.quickConnectTimer.control = "start"
else if authenticated > 0
' We've been given the go ahead, try to authenticate via Quick Connect...
authenticated = AuthenticateViaQuickConnect(m.top.quickConnectJson.secret)
if authenticated <> invalid and authenticated = true
currentUser = AboutMe()
session.user.Login(currentUser, m.top.saveCredentials)
session.user.LoadUserPreferences()
LoadUserAbilities()
' We've been logged in via Quick Connect...
m.top.close = true
m.top.authenticated = true
else
m.top.close = true
m.top.authenticated = false
end if
end if
end sub
sub quickConnectClosed()
m.quickConnectTimer.control = "stop"
if m.checkTask <> invalid
m.checkTask.control = "stop"
m.checkTask.unobserveField("authenticated")
end if
m.top.close = true

View File

@ -93,6 +93,8 @@ function onKeyEvent(key as string, press as boolean) as boolean
focusedItem = getFocusedItem()
if isValid(focusedItem)
m.top.selectedItem = focusedItem
'Prevent the selected item event from double firing
m.top.selectedItem = invalid
end if
return true
end if

View File

@ -28,7 +28,10 @@ sub itemContentChanged()
end if
if isValid(itemData.indexNumber)
indexNumber = itemData.indexNumber.toStr() + ". "
indexNumber = `${itemData.indexNumber}. `
if isValid(itemData.indexNumberEnd)
indexNumber = `${itemData.indexNumber}-${itemData.indexNumberEnd}. `
end if
else
indexNumber = ""
end if

View File

@ -12,6 +12,9 @@ sub init()
m.Shuffle = m.top.findNode("Shuffle")
m.extrasSlider.visible = true
m.seasons = m.top.findNode("seasons")
m.overview = m.top.findNode("overview")
m.overview.ellipsisText = tr("... (Press * to read more)")
end sub
sub itemContentChanged()
@ -194,11 +197,20 @@ function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
overview = m.top.findNode("overview")
if key = "options"
if m.overview.isTextEllipsized
if isAllValid([m.top.itemContent.json.name, m.top.itemContent.json.overview])
m.global.sceneManager.callFunc("standardDialog", m.top.itemContent.json.name, { data: ["<p>" + m.top.itemContent.json.overview + "</p>"] })
return true
end if
end if
return false
end if
topGrp = m.top.findNode("seasons")
bottomGrp = m.top.findNode("extrasGrid")
if key = "down" and overview.hasFocus()
if key = "down" and m.overview.hasFocus()
m.Shuffle.setFocus(true)
return true
else if key = "down" and m.Shuffle.hasFocus()
@ -222,7 +234,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.Shuffle.setFocus(true)
return true
else if key = "up" and m.Shuffle.hasFocus()
overview.setFocus(true)
m.overview.setFocus(true)
return true
else if key = "play" and m.seasons.hasFocus()
print "play was pressed from the seasons row"

View File

@ -1,10 +1,14 @@
import "pkg:/source/utils/misc.bs"
const LOGO_RIGHT_PADDING = 30
const OPTIONCONTROLS_TOP_PADDING = 50
sub init()
m.videoControls = m.top.findNode("videoControls")
m.optionControls = m.top.findNode("optionControls")
m.inactivityTimer = m.top.findNode("inactivityTimer")
m.videoInfo = m.top.findNode("videoInfo")
m.itemTitle = m.top.findNode("itemTitle")
m.videoPlayPause = m.top.findNode("videoPlayPause")
m.videoPositionTime = m.top.findNode("videoPositionTime")
@ -16,10 +20,16 @@ sub init()
m.top.observeField("hasFocus", "onFocusChanged")
m.top.observeField("progressPercentage", "onProgressPercentageChanged")
m.top.observeField("playbackState", "onPlaybackStateChanged")
m.top.observeField("itemTitleText", "onItemTitleTextChanged")
m.defaultButtonIndex = 1
m.focusedButtonIndex = 1
m.top.observeField("itemTitleText", "onItemTitleTextChanged")
m.top.observeField("seasonNumber", "onSeasonNumberChanged")
m.top.observeField("episodeNumber", "onEpisodeNumberChanged")
m.top.observeField("episodeNumberEnd", "onEpisodeNumberEndChanged")
m.top.observeField("logoImage", "onLogoImageChanged")
m.defaultButtonIndex = 2
m.focusedButtonIndex = 2
m.optionControlsMoved = false
m.videoControls.buttonFocused = m.defaultButtonIndex
m.optionControls.buttonFocused = m.optionControls.getChildCount() - 1
@ -53,6 +63,92 @@ sub onItemTitleTextChanged()
m.itemTitle.text = m.top.itemTitleText
end sub
' onSeasonNumberChanged: Handler for changes to m.top.seasonNumber param.
'
sub onSeasonNumberChanged()
m.top.unobserveField("seasonNumber")
itemSeason = m.top.findNode("itemSeason")
itemSeason.font.size = 32
itemSeason.text = `S${m.top.seasonNumber}`
' Move the option controls down to give room for season number
if not m.optionControlsMoved
moveOptionControls(0, OPTIONCONTROLS_TOP_PADDING)
m.optionControlsMoved = true
end if
end sub
' onEpisodeNumberChanged: Handler for changes to m.top.episodeNumber param.
'
sub onEpisodeNumberChanged()
m.top.unobserveField("episodeNumber")
itemEpisode = m.top.findNode("itemEpisode")
itemEpisode.font.size = 32
itemEpisode.text = `E${m.top.episodeNumber}`
' Move the option controls down to give room for episode number
if not m.optionControlsMoved
moveOptionControls(0, OPTIONCONTROLS_TOP_PADDING)
m.optionControlsMoved = true
end if
end sub
' onEpisodeNumberEndChanged: Handler for changes to m.top.episodeNumberEnd param.
'
sub onEpisodeNumberEndChanged()
m.top.unobserveField("episodeNumberEnd")
itemEpisodeEnd = m.top.findNode("itemEpisodeEnd")
itemEpisodeEnd.font.size = 32
itemEpisodeEnd.text = `-${m.top.episodeNumberEnd}`
' Move the option controls down to give room for episode number
if not m.optionControlsMoved
moveOptionControls(0, OPTIONCONTROLS_TOP_PADDING)
m.optionControlsMoved = true
end if
end sub
' moveOptionControls: Moves option controls node based on passed pixel values
'
' @param {integer} horizontalPixels - Number of horizontal pixels to move option controls
' @param {integer} verticalPixels - Number of vertical pixels to move option controls
sub moveOptionControls(horizontalPixels as integer, verticalPixels as integer)
m.optionControls.translation = `[${m.optionControls.translation[0] + horizontalPixels}, ${m.optionControls.translation[1] + verticalPixels}]`
end sub
' onLogoLoadStatusChanged: Handler for changes to logo image's status.
'
' @param {dynamic} event - field change event
sub onLogoLoadStatusChanged(event)
if LCase(event.GetData()) = "ready"
logoImage = event.getRoSGNode()
logoImage.unobserveField("loadStatus")
' Move video info to the right based on the logo width
m.videoInfo.translation = `[${m.videoInfo.translation[0] + logoImage.bitmapWidth + LOGO_RIGHT_PADDING}, ${m.videoInfo.translation[1]}]`
m.itemTitle.maxWidth = m.itemTitle.maxWidth - (logoImage.bitmapWidth + LOGO_RIGHT_PADDING)
' Move the option controls down based on the logo height
if not m.optionControlsMoved
moveOptionControls(0, OPTIONCONTROLS_TOP_PADDING)
m.optionControlsMoved = true
end if
end if
end sub
' onLogoImageChanged: Handler for changes to m.top.logoImage param.
'
sub onLogoImageChanged()
if isValidAndNotEmpty(m.top.logoImage)
logoImage = createObject("roSGNode", "Poster")
logoImage.Id = "logoImage"
logoImage.observeField("loadStatus", "onLogoLoadStatusChanged")
logoImage.uri = m.top.logoImage
logoImage.translation = [103, 61]
m.top.appendChild(logoImage)
end if
end sub
' resetFocusToDefaultButton: Reset focus back to the default button
'
sub resetFocusToDefaultButton()
@ -75,7 +171,7 @@ sub resetFocusToDefaultButton()
m.videoControls.setFocus(true)
m.focusedButtonIndex = m.defaultButtonIndex
m.videoControls.getChild(m.defaultButtonIndex).focus = true
m.videoControls.buttonFocused = 1
m.videoControls.buttonFocused = m.defaultButtonIndex
m.optionControls.buttonFocused = m.optionControls.getChildCount() - 1
end sub

View File

@ -1,19 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<component name="OSD" extends="Group" initialFocus="chapterNext">
<children>
<ScrollingLabel id="itemTitle" font="font:LargeBoldSystemFont" translation="[103,61]" maxWidth="1400" />
<Poster uri="pkg:/images/osdBackground.png" width="1920" height="279" />
<LayoutGroup id="videoInfo" layoutDirection="vert" translation="[103,61]">
<ScrollingLabel id="itemTitle" font="font:LargeBoldSystemFont" maxWidth="1400" />
<LayoutGroup id="videoInfo" layoutDirection="horiz" translation="[103,61]">
<Label id="itemSeason" font="font:MediumSystemFont" color="0xffffffFF" />
<Label id="itemEpisode" font="font:MediumSystemFont" color="0xffffffFF" />
<Label id="itemEpisodeEnd" font="font:MediumSystemFont" color="0xffffffFF" />
</LayoutGroup>
</LayoutGroup>
<Clock id="clock" translation="[1618, 46]" />
<ButtonGroup id="optionControls" itemSpacings="[20]" layoutDirection="horiz" horizAlignment="left" translation="[103,120]">
<IconButton id="showVideoInfoPopup" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/videoInfo.png" height="65" width="100" />
<IconButton id="chapterList" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/numberList.png" height="65" width="100" />
<IconButton id="showSubtitleMenu" background="#070707" focusBackground="#00a4dc" padding="0" icon="pkg:/images/icons/subtitle.png" height="65" width="100" />
<IconButton id="showAudioMenu" background="#070707" focusBackground="#00a4dc" padding="27" icon="pkg:/images/icons/musicNote.png" height="65" width="100" />
</ButtonGroup>
<ButtonGroup id="videoControls" itemSpacings="[20]" layoutDirection="horiz" horizAlignment="center" translation="[960,875]">
<IconButton id="itemBack" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/itemPrevious.png" height="65" width="100" />
<IconButton id="chapterBack" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/previousChapter.png" height="65" width="100" />
<IconButton id="videoPlayPause" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/play.png" height="65" width="100" />
<IconButton id="chapterNext" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/nextChapter.png" height="65" width="100" />
<IconButton id="itemNext" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/itemNext.png" height="65" width="100" />
</ButtonGroup>
<Rectangle id="progressBarBackground" color="0x00000098" width="1714" height="8" translation="[103,970]">
@ -27,6 +40,10 @@
</children>
<interface>
<field id="itemTitleText" type="string" />
<field id="seasonNumber" type="integer" />
<field id="episodeNumber" type="integer" />
<field id="episodeNumberEnd" type="integer" />
<field id="logoImage" type="string" />
<field id="inactiveTimeout" type="integer" />
<field id="progressPercentage" type="float" />
<field id="positionTime" type="float" />

View File

@ -32,6 +32,7 @@ sub init()
m.top.observeField("state", "onState")
m.top.observeField("content", "onContentChange")
m.top.observeField("selectedSubtitle", "onSubtitleChange")
m.top.observeField("audioIndex", "onAudioIndexChange")
' Custom Caption Function
m.top.observeField("allowCaptions", "onAllowCaptionsChange")
@ -91,6 +92,35 @@ sub handleChapterSkipAction(action as string)
end if
end sub
' handleItemSkipAction: Handles user command to skip items
'
' @param {string} action - skip action to take
sub handleItemSkipAction(action as string)
if action = "itemnext"
' If there is something next in the queue, play it
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
m.top.control = "stop"
m.global.sceneManager.callFunc("clearPreviousScene")
m.global.queueManager.callFunc("moveForward")
m.global.queueManager.callFunc("playQueue")
end if
return
end if
if action = "itemback"
' If there is something previous in the queue, play it
if m.global.queueManager.callFunc("getPosition") > 0
m.top.control = "stop"
m.global.sceneManager.callFunc("clearPreviousScene")
m.global.queueManager.callFunc("moveBack")
m.global.queueManager.callFunc("playQueue")
end if
return
end if
end sub
' handleHideAction: Handles action to hide OSD menu
'
' @param {boolean} resume - controls whether or not to resume video playback when sub is called
@ -163,6 +193,12 @@ sub handleShowSubtitleMenuAction()
m.top.selectSubtitlePressed = true
end sub
' handleShowAudioMenuAction: Handles action to show audio selection menu
'
sub handleShowAudioMenuAction()
m.top.selectAudioPressed = true
end sub
' handleShowVideoInfoPopupAction: Handles action to show video info popup
'
sub handleShowVideoInfoPopupAction()
@ -204,10 +240,20 @@ sub onOSDAction()
return
end if
if action = "showaudiomenu"
handleShowAudioMenuAction()
return
end if
if action = "showvideoinfopopup"
handleShowVideoInfoPopupAction()
return
end if
if action = "itemback" or action = "itemnext"
handleItemSkipAction(action)
return
end if
end sub
' Only setup caption items if captions are allowed
@ -256,12 +302,42 @@ end sub
' Event handler for when selectedSubtitle changes
sub onSubtitleChange()
switchWithoutRefresh = true
if m.top.SelectedSubtitle <> -1
' If the global caption mode is off, then Roku can't display the subtitles natively and needs a video stop/start
if LCase(m.top.globalCaptionMode) <> "on" then switchWithoutRefresh = false
end if
' If previous sustitle was encoded, then we need to a video stop/start to change subtitle content
if m.top.previousSubtitleWasEncoded then switchWithoutRefresh = false
if switchWithoutRefresh then return
' Save the current video position
m.global.queueManager.callFunc("setTopStartingPoint", int(m.top.position) * 10000000&)
m.top.control = "stop"
m.LoadMetaDataTask.selectedSubtitleIndex = m.top.SelectedSubtitle
m.LoadMetaDataTask.selectedAudioStreamIndex = m.top.audioIndex
m.LoadMetaDataTask.itemId = m.currentItem.id
m.LoadMetaDataTask.observeField("content", "onVideoContentLoaded")
m.LoadMetaDataTask.control = "RUN"
end sub
' Event handler for when audioIndex changes
sub onAudioIndexChange()
' Skip initial audio index setting
if m.top.position = 0 then return
' Save the current video position
m.global.queueManager.callFunc("setTopStartingPoint", int(m.top.position) * 10000000&)
m.top.control = "stop"
m.LoadMetaDataTask.selectedSubtitleIndex = m.top.SelectedSubtitle
m.LoadMetaDataTask.selectedAudioStreamIndex = m.top.audioIndex
m.LoadMetaDataTask.itemId = m.currentItem.id
m.LoadMetaDataTask.observeField("content", "onVideoContentLoaded")
m.LoadMetaDataTask.control = "RUN"
@ -316,12 +392,39 @@ sub onVideoContentLoaded()
m.top.container = videoContent[0].container
m.top.mediaSourceId = videoContent[0].mediaSourceId
m.top.fullSubtitleData = videoContent[0].fullSubtitleData
m.top.fullAudioData = videoContent[0].fullAudioData
m.top.audioIndex = videoContent[0].audioIndex
m.top.transcodeParams = videoContent[0].transcodeparams
m.chapters = videoContent[0].chapters
m.top.showID = videoContent[0].showID
m.osd.itemTitleText = m.top.content.title
' If video is an episode, attempt to add season and episode numbers to OSD
if m.top.content.contenttype = 4
if isValid(videoContent[0].seasonNumber)
m.osd.seasonNumber = videoContent[0].seasonNumber
end if
if isValid(videoContent[0].episodeNumber)
m.osd.episodeNumber = videoContent[0].episodeNumber
end if
if isValid(videoContent[0].episodeNumberEnd)
m.osd.episodeNumberEnd = videoContent[0].episodeNumberEnd
end if
end if
' Attempt to add logo to OSD
if isValidAndNotEmpty(videoContent[0].logoImage)
m.osd.logoImage = videoContent[0].logoImage
' Don't show both the logo and the video title if this isn't an episode
if m.top.content.contenttype <> 4
m.osd.itemTitleText = ""
end if
end if
populateChapterMenu()
if m.LoadMetaDataTask.isIntro
@ -332,6 +435,32 @@ sub onVideoContentLoaded()
m.top.allowCaptions = true
end if
' Allow default subtitles
m.top.unobserveField("selectedSubtitle")
' Set subtitleTrack property if subs are natively supported by Roku
selectedSubtitle = invalid
for each subtitle in m.top.fullSubtitleData
if subtitle.Index = videoContent[0].selectedSubtitle
selectedSubtitle = subtitle
exit for
end if
end for
if isValid(selectedSubtitle)
availableSubtitleTrackIndex = availSubtitleTrackIdx(selectedSubtitle.Track.TrackName)
if availableSubtitleTrackIndex <> -1
if not selectedSubtitle.IsEncoded
m.top.globalCaptionMode = "On"
m.top.subtitleTrack = m.top.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName
end if
end if
end if
m.top.selectedSubtitle = videoContent[0].selectedSubtitle
m.top.observeField("selectedSubtitle", "onSubtitleChange")
if isValid(m.top.audioIndex)
m.top.audioTrack = (m.top.audioIndex + 1).toStr()
else
@ -375,6 +504,11 @@ sub onNextEpisodeDataLoaded()
m.checkedForNextEpisode = true
m.top.observeField("position", "onPositionChanged")
' If there is no next episode, disable next episode button
if m.getNextEpisodeTask.nextEpisodeData.Items.count() <> 2
m.nextupbuttonseconds = 0
end if
end sub
'
@ -489,7 +623,7 @@ sub onState(msg)
m.top.backPressed = true
else if m.top.state = "playing"
' Check if next episde is available
' Check if next episode is available
if isValid(m.top.showID)
if m.top.showID <> "" and not m.checkedForNextEpisode and m.top.content.contenttype = 4
m.getNextEpisodeTask.showID = m.top.showID
@ -579,6 +713,25 @@ function stateAllowsOSD() as boolean
return inArray(validStates, m.top.state)
end function
' availSubtitleTrackIdx: Returns Roku's index for requested subtitle track
'
' @param {string} tracknameToFind - TrackName for subtitle we're looking to match
' @return {integer} indicating Roku's index for requested subtitle track. Returns -1 if not found
function availSubtitleTrackIdx(tracknameToFind as string) as integer
idx = 0
for each availTrack in m.top.availableSubtitleTracks
' The TrackName must contain the URL we supplied originally, though
' Roku mangles the name a bit, so we check if the URL is a substring, rather
' than strict equality
if Instr(1, availTrack.TrackName, tracknameToFind)
return idx
end if
idx = idx + 1
end for
return -1
end function
function onKeyEvent(key as string, press as boolean) as boolean
' Keypress handler while user is inside the chapter menu

View File

@ -3,10 +3,12 @@
<interface>
<field id="backPressed" type="boolean" alwaysNotify="true" />
<field id="selectSubtitlePressed" type="boolean" alwaysNotify="true" />
<field id="selectAudioPressed" type="boolean" alwaysNotify="true" />
<field id="selectPlaybackInfoPressed" type="boolean" alwaysNotify="true" />
<field id="PlaySessionId" type="string" />
<field id="Subtitles" type="array" />
<field id="SelectedSubtitle" type="integer" value="-1" alwaysNotify="true" />
<field id="SelectedSubtitle" type="integer" value="-2" alwaysNotify="true" />
<field id="previousSubtitleWasEncoded" type="boolean" />
<field id="container" type="string" />
<field id="directPlaySupported" type="boolean" />
<field id="systemOverlay" type="boolean" value="false" />
@ -22,6 +24,7 @@
<field id="videoId" type="string" />
<field id="mediaSourceId" type="string" />
<field id="fullSubtitleData" type="array" />
<field id="fullAudioData" type="array" />
<field id="audioIndex" type="integer" />
<field id="allowCaptions" type="boolean" value="false" />
</interface>

5
default.nix Normal file
View File

@ -0,0 +1,5 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
# nativeBuildInputs are usually what you want -- tools you need to run
nativeBuildInputs = with pkgs.buildPackages; [ nodejs ];
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More